Skip to content

Commit

Permalink
Add color tolerance parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Li committed Jan 26, 2017
1 parent ef24bce commit e8eff24
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 19 deletions.
2 changes: 1 addition & 1 deletion FBSnapshotTestCase/Categories/UIImage+Compare.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@

@interface UIImage (Compare)

- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance;
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance colorTolerance:(CGFloat)colorTolerance;

@end
34 changes: 26 additions & 8 deletions FBSnapshotTestCase/Categories/UIImage+Compare.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

@implementation UIImage (Compare)

- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance colorTolerance:(CGFloat)colorTolerance
{
NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");

Expand Down Expand Up @@ -97,7 +97,7 @@ - (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
BOOL imageEqual = YES;

// Do a fast compare if we can
if (tolerance == 0) {
if (tolerance == 0 && colorTolerance == 0) {
imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
} else {
// Go through each pixel in turn and see if it is different
Expand All @@ -111,12 +111,30 @@ - (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
// If this pixel is different, increment the pixel diff count and see
// if we have hit our limit.
if (p1->raw != p2->raw) {
numDiffPixels ++;

CGFloat percent = (CGFloat)numDiffPixels / pixelCount;
if (percent > tolerance) {
imageEqual = NO;
break;
BOOL pixelsEqual = YES;
if (colorTolerance == 0) {
pixelsEqual = NO;
} else {
long dr = (long)p1->pixels.red - (long)p2->pixels.red;
long dg = (long)p1->pixels.green - (long)p2->pixels.green;
long db = (long)p1->pixels.blue - (long)p2->pixels.blue;

double distanceSquared = (float) (dr * dr + dg * dg + db * db);
distanceSquared /= (255.0 * 255.0); // Scale each channel from 0 - 255 to 0 - 1.0

if (distanceSquared > colorTolerance * colorTolerance) {
pixelsEqual = NO;
}
}

if (!pixelsEqual) {
numDiffPixels++;

CGFloat percent = (CGFloat)numDiffPixels / pixelCount;
if (percent > tolerance) {
imageEqual = NO;
break;
}
}
}

Expand Down
35 changes: 35 additions & 0 deletions FBSnapshotTestCase/FBSnapshotTestCase.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,19 +163,54 @@
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;

/**
Performs the comparison or records a snapshot of the layer if recordMode is YES.
@param layer The Layer to snapshot
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
@param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different.
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr;

/**
Performs the comparison or records a snapshot of the view if recordMode is YES.
@param view The view to snapshot
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;


/**
Performs the comparison or records a snapshot of the view if recordMode is YES.
@param view The view to snapshot
@param referenceImagesDirectory The directory in which reference images are stored.
@param identifier An optional identifier, used if there are multiple snapshot tests in a given -test method.
@param tolerance The percentage difference to still count as identical - 0 mean pixel perfect, 1 means I don't care
@param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different.
@param errorPtr An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr;

/**
Expand Down
37 changes: 37 additions & 0 deletions FBSnapshotTestCase/FBSnapshotTestCase.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,54 @@ - (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
tolerance:tolerance
colorTolerance:0
error:errorPtr];
}



- (BOOL)compareSnapshotOfLayer:(CALayer *)layer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:layer
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
tolerance:tolerance
colorTolerance:colorTolerance
error:errorPtr];
}

- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:view
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
tolerance:tolerance
colorTolerance:0
error:errorPtr];
}


- (BOOL)compareSnapshotOfView:(UIView *)view
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr
{
return [self _compareSnapshotOfViewOrLayer:view
referenceImagesDirectory:referenceImagesDirectory
identifier:identifier
tolerance:tolerance
colorTolerance:colorTolerance
error:errorPtr];
}

Expand Down Expand Up @@ -123,13 +158,15 @@ - (BOOL)_compareSnapshotOfViewOrLayer:(id)viewOrLayer
referenceImagesDirectory:(NSString *)referenceImagesDirectory
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr
{
_snapshotController.referenceImagesDirectory = referenceImagesDirectory;
return [_snapshotController compareSnapshotOfViewOrLayer:viewOrLayer
selector:self.invocation.selector
identifier:identifier
tolerance:tolerance
colorTolerance:colorTolerance
error:errorPtr];
}

Expand Down
32 changes: 32 additions & 0 deletions FBSnapshotTestCase/FBSnapshotTestController.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ extern NSString *const FBDiffedImageKey;
identifier:(NSString *)identifier
error:(NSError **)errorPtr;

/**
Performs the comparison of a view or layer.
@param view The view or layer to snapshot.
@param selector The test method being run.
@param identifier An optional identifier, used is there are muliptle snapshot tests in a given -test method.
@param tolerance The percentage of pixels that can differ and still be considered 'identical'
@param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different.
@param error An error to log in an XCTAssert() macro if the method fails (missing reference image, images differ, etc).
@returns YES if the comparison (or saving of the reference image) succeeded.
*/
- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr;

/**
Performs the comparison of a view or layer.
@param view The view or layer to snapshot.
Expand Down Expand Up @@ -149,6 +166,21 @@ extern NSString *const FBDiffedImageKey;
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr;

/**
Performs a pixel-by-pixel comparison of the two images with an allowable margin of error.
@param referenceImage The reference (correct) image.
@param image The image to test against the reference.
@param tolerance The percentage of pixels that can differ and still be considered 'identical'
@param colorTolerance The euclidean distance allowable between two colors (with each channel 0-1.0) to consider pixels to be "identical". colorTolerance is applied before calculating percentage of pixels that are different.
@param errorPtr An error that indicates why the comparison failed if it does.
@returns YES if the comparison succeeded and the images are the same(ish).
*/
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr;

/**
Saves the reference image and the test image to `failedOutputDirectory`.
@param referenceImage The reference (correct) image.
Expand Down
25 changes: 22 additions & 3 deletions FBSnapshotTestCase/FBSnapshotTestController.m
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,21 @@ - (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr
{
return [self compareSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance colorTolerance:0.0 error:errorPtr];
}

- (BOOL)compareSnapshotOfViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr
{
if (self.recordMode) {
return [self _recordSnapshotOfViewOrLayer:viewOrLayer selector:selector identifier:identifier error:errorPtr];
} else {
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance error:errorPtr];
return [self _performPixelComparisonWithViewOrLayer:viewOrLayer selector:selector identifier:identifier tolerance:tolerance colorTolerance:colorTolerance error:errorPtr];
}
}

Expand Down Expand Up @@ -127,10 +137,18 @@ - (UIImage *)referenceImageForSelector:(SEL)selector
- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
error:(NSError **)errorPtr {
return [self compareReferenceImage:image toImage:image tolerance:0 colorTolerance:0 error:errorPtr];
}

- (BOOL)compareReferenceImage:(UIImage *)referenceImage
toImage:(UIImage *)image
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr
{
BOOL sameImageDimensions = CGSizeEqualToSize(referenceImage.size, image.size);
if (sameImageDimensions && [referenceImage fb_compareWithImage:image tolerance:tolerance]) {
if (sameImageDimensions && [referenceImage fb_compareWithImage:image tolerance:tolerance colorTolerance:colorTolerance]) {
return YES;
}

Expand Down Expand Up @@ -275,12 +293,13 @@ - (BOOL)_performPixelComparisonWithViewOrLayer:(id)viewOrLayer
selector:(SEL)selector
identifier:(NSString *)identifier
tolerance:(CGFloat)tolerance
colorTolerance:(CGFloat)colorTolerance
error:(NSError **)errorPtr
{
UIImage *referenceImage = [self referenceImageForSelector:selector identifier:identifier error:errorPtr];
if (nil != referenceImage) {
UIImage *snapshot = [self _imageForViewOrLayer:viewOrLayer];
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance error:errorPtr];
BOOL imagesSame = [self compareReferenceImage:referenceImage toImage:snapshot tolerance:tolerance colorTolerance:colorTolerance error:errorPtr];
if (!imagesSame) {
NSError *saveError = nil;
if ([self saveFailedReferenceImage:referenceImage testImage:snapshot selector:selector identifier:identifier error:&saveError] == NO) {
Expand Down
14 changes: 7 additions & 7 deletions FBSnapshotTestCase/SwiftSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
*/

public extension FBSnapshotTestCase {
public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line)
public func FBSnapshotVerifyView(view: UIView, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, colorTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
FBSnapshotVerifyViewOrLayer(view, identifier: identifier, suffixes: suffixes, tolerance: tolerance, colorTolerance: colorTolerance,file: file, line: line)
}

public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, file: file, line: line)
public func FBSnapshotVerifyLayer(layer: CALayer, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, colorTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
FBSnapshotVerifyViewOrLayer(layer, identifier: identifier, suffixes: suffixes, tolerance: tolerance, colorTolerance: colorTolerance, file: file, line: line)
}

private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
private func FBSnapshotVerifyViewOrLayer(viewOrLayer: AnyObject, identifier: String = "", suffixes: NSOrderedSet = FBSnapshotTestCaseDefaultSuffixes(), tolerance: CGFloat = 0, colorTolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
let envReferenceImageDirectory = self.getReferenceImageDirectoryWithDefault(FB_REFERENCE_IMAGE_DIR)
var error: NSError?
var comparisonSuccess = false
Expand All @@ -27,15 +27,15 @@ public extension FBSnapshotTestCase {
let referenceImagesDirectory = "\(envReferenceImageDirectory)\(suffix)"
if viewOrLayer.isKindOfClass(UIView) {
do {
try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
try compareSnapshotOfView(viewOrLayer as! UIView, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance, colorTolerance: colorTolerance)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
comparisonSuccess = false
}
} else if viewOrLayer.isKindOfClass(CALayer) {
do {
try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance)
try compareSnapshotOfLayer(viewOrLayer as! CALayer, referenceImagesDirectory: referenceImagesDirectory, identifier: identifier, tolerance: tolerance, colorTolerance: colorTolerance)
comparisonSuccess = true
} catch let error1 as NSError {
error = error1
Expand Down

0 comments on commit e8eff24

Please sign in to comment.