Skip to content

Commit ee17d57

Browse files
committed
Added support for optimised reference image PNGs
This allows to use apps like ImageOptim.app to optimise recorded reference images. Optimising reference image can heavily reduce its size, meaning we need to store less binary data in repo.
1 parent c944c1f commit ee17d57

File tree

6 files changed

+56
-11
lines changed

6 files changed

+56
-11
lines changed

FBSnapshotTestCase.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
827137A21C63AC0D00354E42 /* square-copy.png in Resources */ = {isa = PBXBuildFile; fileRef = B32447DA1AB78B5E00B1D6FF /* square-copy.png */; };
3737
827137A31C63AC0D00354E42 /* square.png in Resources */ = {isa = PBXBuildFile; fileRef = B32447DB1AB78B5E00B1D6FF /* square.png */; };
3838
827137A41C63AC0F00354E42 /* FBSnapshotControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B31988301AB784CB00B0A900 /* FBSnapshotControllerTests.m */; };
39+
A9E093ED2261EEA400B1EDE3 /* square_with_graphics_imageoptim.png in Resources */ = {isa = PBXBuildFile; fileRef = A9E093EB2261EEA300B1EDE3 /* square_with_graphics_imageoptim.png */; };
40+
A9E093EE2261EEA400B1EDE3 /* square_with_graphics_imageoptim.png in Resources */ = {isa = PBXBuildFile; fileRef = A9E093EB2261EEA300B1EDE3 /* square_with_graphics_imageoptim.png */; };
41+
A9E093EF2261EEA400B1EDE3 /* square_with_graphics.png in Resources */ = {isa = PBXBuildFile; fileRef = A9E093EC2261EEA400B1EDE3 /* square_with_graphics.png */; };
42+
A9E093F02261EEA400B1EDE3 /* square_with_graphics.png in Resources */ = {isa = PBXBuildFile; fileRef = A9E093EC2261EEA400B1EDE3 /* square_with_graphics.png */; };
3943
B31987FC1AB782D100B0A900 /* FBSnapshotTestCase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B31987F01AB782D000B0A900 /* FBSnapshotTestCase.framework */; };
4044
B31988281AB7849400B0A900 /* FBSnapshotTestCase.h in Headers */ = {isa = PBXBuildFile; fileRef = B31988201AB7849400B0A900 /* FBSnapshotTestCase.h */; settings = {ATTRIBUTES = (Public, ); }; };
4145
B31988291AB7849400B0A900 /* FBSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = B31988211AB7849400B0A900 /* FBSnapshotTestCase.m */; };
@@ -80,6 +84,8 @@
8084
42F2B74320C0D7A400ABED24 /* rect_shade.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = rect_shade.png; sourceTree = "<group>"; };
8185
8271377A1C63AB6F00354E42 /* FBSnapshotTestCase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FBSnapshotTestCase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8286
827137831C63AB7000354E42 /* FBSnapshotTestCase tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FBSnapshotTestCase tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
87+
A9E093EB2261EEA300B1EDE3 /* square_with_graphics_imageoptim.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = square_with_graphics_imageoptim.png; sourceTree = "<group>"; };
88+
A9E093EC2261EEA400B1EDE3 /* square_with_graphics.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = square_with_graphics.png; sourceTree = "<group>"; };
8389
B31987F01AB782D000B0A900 /* FBSnapshotTestCase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FBSnapshotTestCase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8490
B31987F41AB782D000B0A900 /* FBSnapshotTestCase-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FBSnapshotTestCase-Info.plist"; sourceTree = "<group>"; };
8591
B31987FB1AB782D100B0A900 /* FBSnapshotTestCase iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FBSnapshotTestCase iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -196,6 +202,8 @@
196202
children = (
197203
42F2B74320C0D7A400ABED24 /* rect_shade.png */,
198204
B76C68271C6BD68100586E5B /* rect.png */,
205+
A9E093EB2261EEA300B1EDE3 /* square_with_graphics_imageoptim.png */,
206+
A9E093EC2261EEA400B1EDE3 /* square_with_graphics.png */,
199207
E5C2CD611B1F399A00669887 /* square_with_pixel.png */,
200208
B32447D91AB78B5E00B1D6FF /* square_with_text.png */,
201209
B32447DA1AB78B5E00B1D6FF /* square-copy.png */,
@@ -380,11 +388,13 @@
380388
buildActionMask = 2147483647;
381389
files = (
382390
B76C682A1C6BD6D500586E5B /* rect.png in Resources */,
391+
A9E093EE2261EEA400B1EDE3 /* square_with_graphics_imageoptim.png in Resources */,
383392
827137A01C63AC0700354E42 /* square_with_pixel.png in Resources */,
384393
827137A21C63AC0D00354E42 /* square-copy.png in Resources */,
385394
827137A31C63AC0D00354E42 /* square.png in Resources */,
386395
827137A11C63AC0900354E42 /* square_with_text.png in Resources */,
387396
42F2B74520C0D7A400ABED24 /* rect_shade.png in Resources */,
397+
A9E093F02261EEA400B1EDE3 /* square_with_graphics.png in Resources */,
388398
);
389399
runOnlyForDeploymentPostprocessing = 0;
390400
};
@@ -400,11 +410,13 @@
400410
buildActionMask = 2147483647;
401411
files = (
402412
B76C68291C6BD6D200586E5B /* rect.png in Resources */,
413+
A9E093ED2261EEA400B1EDE3 /* square_with_graphics_imageoptim.png in Resources */,
403414
B32447DC1AB78B5E00B1D6FF /* square_with_text.png in Resources */,
404415
E5C2CD621B1F399A00669887 /* square_with_pixel.png in Resources */,
405416
B32447DE1AB78B5E00B1D6FF /* square.png in Resources */,
406417
B32447DD1AB78B5E00B1D6FF /* square-copy.png in Resources */,
407418
42F2B74420C0D7A400ABED24 /* rect_shade.png in Resources */,
419+
A9E093EF2261EEA400B1EDE3 /* square_with_graphics.png in Resources */,
408420
);
409421
runOnlyForDeploymentPostprocessing = 0;
410422
};

FBSnapshotTestCase/Categories/UIImage+Compare.m

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,15 @@ - (BOOL)fb_compareWithImage:(UIImage *)image perPixelTolerance:(CGFloat)perPixel
5151
CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage));
5252
CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));
5353

54-
// The images have the equal size, so we could use the smallest amount of bytes because of byte padding
55-
size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage));
56-
size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow;
54+
// Find image which requires more bytes in memory. We have to normalise both images to context requiring "bigger" memory representation.
55+
// This allows comparing 2 visually identic images even if their bit representation in file can be different
56+
// (for example reference images optimised using ImageOptim app).
57+
// Because both images have the same pixel size, image with more bytes per row is the image dictating the context confix for comparison.
58+
UIImage *contextConfigImage = (CGImageGetBytesPerRow(image.CGImage) > CGImageGetBytesPerRow(self.CGImage) ? image : self);
59+
60+
// Create contexts for both images using the same configuration.
61+
size_t bytesPerRow = CGImageGetBytesPerRow(contextConfigImage.CGImage);
62+
size_t referenceImageSizeBytes = referenceImageSize.height * bytesPerRow;
5763
void *referenceImagePixels = calloc(1, referenceImageSizeBytes);
5864
void *imagePixels = calloc(1, referenceImageSizeBytes);
5965

@@ -63,20 +69,23 @@ - (BOOL)fb_compareWithImage:(UIImage *)image perPixelTolerance:(CGFloat)perPixel
6369
return NO;
6470
}
6571

72+
size_t bitsPerComponent = CGImageGetBitsPerComponent(contextConfigImage.CGImage);
73+
CGBitmapInfo bitmapInfo = (CGBitmapInfo)kCGImageAlphaPremultipliedLast;
74+
6675
CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels,
6776
referenceImageSize.width,
6877
referenceImageSize.height,
69-
CGImageGetBitsPerComponent(self.CGImage),
70-
minBytesPerRow,
71-
CGImageGetColorSpace(self.CGImage),
72-
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
78+
bitsPerComponent,
79+
bytesPerRow,
80+
CGImageGetColorSpace(contextConfigImage.CGImage),
81+
bitmapInfo);
7382
CGContextRef imageContext = CGBitmapContextCreate(imagePixels,
7483
imageSize.width,
7584
imageSize.height,
76-
CGImageGetBitsPerComponent(image.CGImage),
77-
minBytesPerRow,
78-
CGImageGetColorSpace(image.CGImage),
79-
(CGBitmapInfo)kCGImageAlphaPremultipliedLast);
85+
bitsPerComponent,
86+
bytesPerRow,
87+
CGImageGetColorSpace(contextConfigImage.CGImage),
88+
bitmapInfo);
8089

8190
if (!referenceImageContext || !imageContext) {
8291
CGContextRelease(referenceImageContext);

FBSnapshotTestCaseTests/FBSnapshotControllerTests.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ - (void)testCompareReferenceImageToImageShouldNotBeEqual
4848
XCTAssertEqual(error.code, FBSnapshotTestControllerErrorCodeImagesDifferent);
4949
}
5050

51+
- (void)testCompareOptimisedReferenceImageToImageShouldBeEqual
52+
{
53+
UIImage *referenceImage = [self _bundledImageNamed:@"square_with_graphics_imageoptim" type:@"png"];
54+
XCTAssertNotNil(referenceImage);
55+
UIImage *testImage = [self _bundledImageNamed:@"square_with_graphics" type:@"png"];
56+
XCTAssertNotNil(testImage);
57+
58+
id testClass = nil;
59+
FBSnapshotTestController *controller = [[FBSnapshotTestController alloc] initWithTestClass:testClass];
60+
NSError *error = nil;
61+
XCTAssertTrue([controller compareReferenceImage:referenceImage toImage:testImage overallTolerance:0 error:&error]);
62+
XCTAssertNil(error);
63+
}
64+
5165
- (void)testCompareReferenceImageWithVeryLowToleranceShouldNotMatch
5266
{
5367
UIImage *referenceImage = [self _bundledImageNamed:@"square" type:@"png"];
8.77 KB
Loading
6.24 KB
Loading

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Features
7979
- Support for `CALayer` via `FBSnapshotVerifyLayer`.
8080
- `usesDrawViewHierarchyInRect` to handle cases like `UIVisualEffect`, `UIAppearance` and Size Classes.
8181
- `fileNameOptions` to control appending the device model (`iPhone`, `iPad`, `iPod Touch`, etc), OS version, screen size and screen scale to the images (allowing to have multiple tests for the same «snapshot» for different `OS`s and devices).
82+
- Support of reference images optimized for reduced size.
8283

8384
Notes
8485
-----
@@ -94,6 +95,15 @@ have separate targets for the two types.
9495

9596
Read more on this [here](docs/LibraryVsApplicationTestBundles.md).
9697

98+
Optimizing reference images to reduce size
99+
------------------------------------------
100+
101+
Recorded reference image PNG can be optimised to reduce its size using lossless compression.
102+
103+
Once you have final reference images recorded, you can manually optimise them using tools like [ImageOptim](https://imageoptim.com). ImageOptim will reduce size of the image without loss of any visual information. The savings in image sizes can be significant (up to 80%+ depending on the original image).
104+
105+
If you are committing reference images to repository, optimizing them reduces their impact growing to repo size.
106+
97107
Authors
98108
-------
99109

0 commit comments

Comments
 (0)