Flutter Engine
GoldenImage.m
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #import "GoldenImage.h"
6 
7 #import <XCTest/XCTest.h>
8 #include <sys/sysctl.h>
9 
10 static const double kRmseThreshold = 0.5;
11 
12 @interface GoldenImage ()
13 
14 @end
15 
16 @implementation GoldenImage
17 
18 - (instancetype)initWithGoldenNamePrefix:(NSString*)prefix {
19  self = [super init];
20  if (self) {
21  _goldenName = [prefix stringByAppendingString:_platformName()];
22  NSBundle* bundle = [NSBundle bundleForClass:[self class]];
23  NSURL* goldenURL = [bundle URLForResource:_goldenName withExtension:@"png"];
24  NSData* data = [NSData dataWithContentsOfURL:goldenURL];
25  _image = [[UIImage alloc] initWithData:data];
26  }
27  return self;
28 }
29 
30 - (BOOL)compareGoldenToImage:(UIImage*)image {
31  if (!self.image || !image) {
32  return NO;
33  }
34  CGImageRef imageRefA = [self.image CGImage];
35  CGImageRef imageRefB = [image CGImage];
36 
37  NSUInteger widthA = CGImageGetWidth(imageRefA);
38  NSUInteger heightA = CGImageGetHeight(imageRefA);
39  NSUInteger widthB = CGImageGetWidth(imageRefB);
40  NSUInteger heightB = CGImageGetHeight(imageRefB);
41 
42  if (widthA != widthB || heightA != heightB) {
43  return NO;
44  }
45  NSUInteger bytesPerPixel = 4;
46  NSUInteger size = widthA * heightA * bytesPerPixel;
47  NSMutableData* rawA = [NSMutableData dataWithLength:size];
48  NSMutableData* rawB = [NSMutableData dataWithLength:size];
49 
50  if (!rawA || !rawB) {
51  return NO;
52  }
53 
54  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
55 
56  NSUInteger bytesPerRow = bytesPerPixel * widthA;
57  NSUInteger bitsPerComponent = 8;
58  CGContextRef contextA =
59  CGBitmapContextCreate(rawA.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
60  colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
61 
62  CGContextDrawImage(contextA, CGRectMake(0, 0, widthA, heightA), imageRefA);
63  CGContextRelease(contextA);
64 
65  CGContextRef contextB =
66  CGBitmapContextCreate(rawB.mutableBytes, widthA, heightA, bitsPerComponent, bytesPerRow,
67  colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
68  CGColorSpaceRelease(colorSpace);
69 
70  CGContextDrawImage(contextB, CGRectMake(0, 0, widthA, heightA), imageRefB);
71  CGContextRelease(contextB);
72 
73  const char* apos = rawA.mutableBytes;
74  const char* bpos = rawB.mutableBytes;
75  double sum = 0.0;
76  for (size_t i = 0; i < size; ++i, ++apos, ++bpos) {
77  // Skip transparent pixels.
78  if (*apos == 0 && *bpos == 0 && i % 4 == 0) {
79  i += 3;
80  apos += 3;
81  bpos += 3;
82  } else {
83  double aval = *apos;
84  double bval = *bpos;
85  double diff = aval - bval;
86  sum += diff * diff;
87  }
88  }
89  double rmse = sqrt(sum / size);
90  return rmse <= kRmseThreshold;
91 }
92 
93 NS_INLINE NSString* _platformName() {
94  NSString* simulatorName =
95  [[NSProcessInfo processInfo].environment objectForKey:@"SIMULATOR_DEVICE_NAME"];
96  if (simulatorName) {
97  return [NSString stringWithFormat:@"%@_simulator", simulatorName];
98  }
99 
100  size_t size;
101  sysctlbyname("hw.model", NULL, &size, NULL, 0);
102  char* answer = malloc(size);
103  sysctlbyname("hw.model", answer, &size, NULL, 0);
104 
105  NSString* results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding];
106  free(answer);
107  return results;
108 }
109 
110 @end
static const double kRmseThreshold
Definition: GoldenImage.m:10
constexpr std::size_t size(T(&array)[N])
Definition: size.h:13
UIImage * image
Definition: GoldenImage.h:12