Permalink
Browse files

Image diff. Improved image viewer.

  • Loading branch information...
1 parent fc987da commit 7a779b07a0d415e37377e89ef29090213f13afb8 @johnboiles johnboiles committed Oct 27, 2011
@@ -0,0 +1,49 @@
+//
+// GHImageDiffView.h
+// GHUnitIOS
+//
+// Created by John Boiles on 10/27/11.
+// Copyright (c) 2011. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface GHImageDiffView : UIView {
+ UIScrollView *scrollView_;
+ UISegmentedControl *segmentedControl_;
+
+ UIImageView *originalImageView_;
+ UIImageView *newImageView_;
+ UIImageView *diffImageView_;
+}
+
+- (void)setOriginalImage:(UIImage *)originalImage newImage:(UIImage *)newImage diffImage:(UIImage *)diffImage;
+
+- (void)showOriginalImage;
+
+- (void)showNewImage;
+
+- (void)showDiffImage;
+
+@end
@@ -0,0 +1,111 @@
+//
+// GHImageDiffView.m
+// GHUnitIOS
+//
+// Created by John Boiles on 10/27/11.
+// Copyright (c) 2011. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import "GHImageDiffView.h"
+
+@implementation GHImageDiffView
+
+- (id)initWithFrame:(CGRect)frame {
+ if ((self = [super initWithFrame:frame])) {
+ scrollView_ = [[UIScrollView alloc] initWithFrame:CGRectZero];
+ scrollView_.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];
+ scrollView_.contentInset = UIEdgeInsetsMake(0, 0, 50, 0);
+ [self addSubview:scrollView_];
+ [scrollView_ release];
+
+ segmentedControl_ = [[UISegmentedControl alloc] initWithFrame:CGRectZero];
+ [segmentedControl_ insertSegmentWithTitle:@"Original" atIndex:0 animated:NO];
+ [segmentedControl_ insertSegmentWithTitle:@"New" atIndex:1 animated:NO];
+ [segmentedControl_ insertSegmentWithTitle:@"Diff" atIndex:2 animated:NO];
+ [segmentedControl_ addTarget:self action:@selector(segmentedControlDidChange:) forControlEvents:UIControlEventValueChanged];
+ [self addSubview:segmentedControl_];
+ [segmentedControl_ release];
+
+ originalImageView_ = [[UIImageView alloc] initWithFrame:CGRectZero];
+ [scrollView_ addSubview:originalImageView_];
+ [originalImageView_ release];
+
+ newImageView_ = [[UIImageView alloc] initWithFrame:CGRectZero];
+ [scrollView_ addSubview:newImageView_];
+ [newImageView_ release];
+
+ diffImageView_ = [[UIImageView alloc] initWithFrame:CGRectZero];
+ [scrollView_ addSubview:diffImageView_];
+ [diffImageView_ release];
+ }
+ return self;
+}
+
+- (void)layoutSubviews {
+ [super layoutSubviews];
+ scrollView_.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
+
+ segmentedControl_.frame = CGRectMake((self.frame.size.width - 300) / 2, self.frame.size.height - 40, 300, 30);
+}
+
+- (void)setOriginalImage:(UIImage *)originalImage newImage:(UIImage *)newImage diffImage:(UIImage *)diffImage {
+ originalImageView_.image = originalImage;
+ [originalImageView_ sizeToFit];
+ newImageView_.image = newImage;
+ [newImageView_ sizeToFit];
+ diffImageView_.image = diffImage;
+ [diffImageView_ sizeToFit];
+ scrollView_.contentSize = CGSizeMake(MAX(originalImage.size.width, newImage.size.width), MAX(originalImage.size.height, newImage.size.height));
+}
+
+- (void)showOriginalImage {
+ originalImageView_.hidden = NO;
+ newImageView_.hidden = YES;
+ diffImageView_.hidden = YES;
+ segmentedControl_.selectedSegmentIndex = 0;
+}
+
+- (void)showNewImage {
+ originalImageView_.hidden = YES;
+ newImageView_.hidden = NO;
+ diffImageView_.hidden = YES;
+ segmentedControl_.selectedSegmentIndex = 1;
+}
+
+- (void)showDiffImage {
+ originalImageView_.hidden = YES;
+ newImageView_.hidden = YES;
+ diffImageView_.hidden = NO;
+ segmentedControl_.selectedSegmentIndex = 2;
+}
+
+#pragma mark UISegmentedControl
+
+- (void)segmentedControlDidChange:(UISegmentedControl *)segmentedControl {
+ if (segmentedControl.selectedSegmentIndex == 0) [self showOriginalImage];
+ else if (segmentedControl.selectedSegmentIndex == 1) [self showNewImage];
+ else if (segmentedControl.selectedSegmentIndex == 2) [self showDiffImage];
+}
+
+@end
@@ -34,10 +34,8 @@ @implementation GHUnitIOSTestView
@synthesize controlDelegate=controlDelegate_;
-- (id)initWithFrame:(CGRect)frame
-{
- self = [super initWithFrame:frame];
- if (self) {
+- (id)initWithFrame:(CGRect)frame {
+ if ((self = [super initWithFrame:frame])) {
self.backgroundColor = [UIColor whiteColor];
textLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(10, 0, 300, 100)];
@@ -30,12 +30,14 @@
#import <UIKit/UIKit.h>
#import "GHTestViewModel.h"
#import "GHUnitIOSTestView.h"
+#import "GHImageDiffView.h"
/*
View controller for a test.
*/
@interface GHUnitIOSTestViewController : UIViewController <GHTestRunnerDelegate, GHUnitIOSTestViewDelegate> {
GHUnitIOSTestView *testView_;
+ GHImageDiffView *imageDiffView_;
GHTestNode *testNode_;
@@ -67,6 +67,18 @@ - (void)_runTest {
[test release];
}
+- (void)_showImageDiff {
+ if (!imageDiffView_) imageDiffView_ = [[GHImageDiffView alloc] initWithFrame:CGRectZero];
+ UIImage *originalImage = [testNode_.test.exception.userInfo objectForKey:@"OriginalImage"];
+ UIImage *newImage = [testNode_.test.exception.userInfo objectForKey:@"NewImage"];
+ UIImage *diffImage = [testNode_.test.exception.userInfo objectForKey:@"DiffImage"];
+ [imageDiffView_ setOriginalImage:originalImage newImage:newImage diffImage:diffImage];
+ UIViewController *viewController = [[UIViewController alloc] init];
+ viewController.view = imageDiffView_;
+ [self.navigationController pushViewController:viewController animated:YES];
+ [viewController release];
+}
+
- (NSString *)updateTestView {
NSMutableString *text = [NSMutableString stringWithCapacity:200];
[text appendFormat:@"%@ %@\n", [testNode_ identifier], [testNode_ statusString]];
@@ -102,23 +114,13 @@ - (void)setTest:(id<GHTest>)test {
#pragma mark Delegates (GHUnitIOSTestView)
- (void)testViewDidSelectOriginalImage:(GHUnitIOSTestView *)testView {
- UIViewController *viewController = [[UIViewController alloc] init];
- UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
- UIImage *originalImage = [testNode_.test.exception.userInfo objectForKey:@"OriginalImage"];
- [scrollView addSubview:[[[UIImageView alloc] initWithImage:originalImage] autorelease]];
- scrollView.contentSize = originalImage.size;
- viewController.view = scrollView;
- [self.navigationController pushViewController:viewController animated:YES];
+ [self _showImageDiff];
+ [imageDiffView_ showOriginalImage];
}
- (void)testViewDidSelectNewImage:(GHUnitIOSTestView *)testView {
- UIViewController *viewController = [[UIViewController alloc] init];
- UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
- UIImage *newImage = [testNode_.test.exception.userInfo objectForKey:@"NewImage"];
- [scrollView addSubview:[[[UIImageView alloc] initWithImage:newImage] autorelease]];
- scrollView.contentSize = newImage.size;
- viewController.view = scrollView;
- [self.navigationController pushViewController:viewController animated:YES];
+ [self _showImageDiff];
+ [imageDiffView_ showNewImage];
}
- (void)testViewDidApproveChange:(GHUnitIOSTestView *)testView {
@@ -38,7 +38,7 @@
@interface GHUnitIOSViewController : UIViewController <UITableViewDelegate, GHTestRunnerDelegate, UISearchBarDelegate> {
GHUnitIOSView *view_;
-
+
GHUnitIOSTableViewDataSource *dataSource_;
GHTestSuite *suite_;
@@ -139,7 +139,7 @@ + (BOOL)compareImage:(UIImage *)image withNewImage:(UIImage *)newImage {
CGImageGetColorSpace(newImage.CGImage),
kCGImageAlphaPremultipliedLast
);
- if (!imageContext || !imageContext) {
+ if (!imageContext || !newImageContext) {
GHUDebug(@"Unable to create image contexts for image comparison");
return NO;
}
@@ -173,6 +173,29 @@ + (BOOL)compareImage:(UIImage *)image withNewImage:(UIImage *)newImage {
return YES;
}
++ (UIImage *)diffWithImage:(UIImage *)image newImage:(UIImage *)newImage {
+ if (!image || !newImage) return nil;
+ // Use the largest size and width
+ CGSize imageSize = CGSizeMake(MAX(image.size.width, newImage.size.width), MAX(image.size.height, newImage.size.height));
+
+ UIGraphicsBeginImageContext(imageSize);
+ CGContextRef context = UIGraphicsGetCurrentContext();
+ // Draw the original image
+ [image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
+ // Overlay the new image inverted and at half alpha
+ CGContextSetAlpha(context, 0.5);
+ CGContextBeginTransparencyLayer(context, NULL);
+ [newImage drawInRect:CGRectMake(0, 0, newImage.size.width, newImage.size.height)];
+ CGContextSetBlendMode(context, kCGBlendModeDifference);
+ CGContextSetFillColorWithColor(context,[UIColor whiteColor].CGColor);
+ CGContextFillRect(context, CGRectMake(0, 0, image.size.width, image.size.height));
+ CGContextEndTransparencyLayer(context);
+ UIImage *returnImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+ return returnImage;
+}
+
+
- (void)_setUp {
imageVerifyCount_ = 0;
}
@@ -217,6 +240,8 @@ - (void)verifyView:(UIView *)view filename:(NSString *)filename lineNumber:(int)
GHUDebug(@"No image available for filename %@", filename);
[[NSException exceptionWithName:@"GHViewUnavailableException" reason:@"No image saved for view" userInfo:exceptionDictionary] raise];
} else if (![[self class] compareImage:originalViewImage withNewImage:newViewImage]) {
+ UIImage *diffImage = [[self class] diffWithImage:originalViewImage newImage:newViewImage];
+ [exceptionDictionary setObject:diffImage forKey:@"DiffImage"];
[exceptionDictionary setObject:originalViewImage forKey:@"OriginalImage"];
[[NSException exceptionWithName:@"GHViewChangeException" reason:@"View has changed" userInfo:exceptionDictionary] raise];
}
@@ -0,0 +1,49 @@
+//
+// GHImageDiffView.h
+// GHUnitIOS
+//
+// Created by John Boiles on 10/27/11.
+// Copyright (c) 2011. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface GHImageDiffView : UIView {
+ UIScrollView *scrollView_;
+ UISegmentedControl *segmentedControl_;
+
+ UIImageView *originalImageView_;
+ UIImageView *newImageView_;
+ UIImageView *diffImageView_;
+}
+
+- (void)setOriginalImage:(UIImage *)originalImage newImage:(UIImage *)newImage diffImage:(UIImage *)diffImage;
+
+- (void)showOriginalImage;
+
+- (void)showNewImage;
+
+- (void)showDiffImage;
+
+@end
@@ -30,12 +30,14 @@
#import <UIKit/UIKit.h>
#import "GHTestViewModel.h"
#import "GHUnitIOSTestView.h"
+#import "GHImageDiffView.h"
/*
View controller for a test.
*/
@interface GHUnitIOSTestViewController : UIViewController <GHTestRunnerDelegate, GHUnitIOSTestViewDelegate> {
GHUnitIOSTestView *testView_;
+ GHImageDiffView *imageDiffView_;
GHTestNode *testNode_;
@@ -38,7 +38,7 @@
@interface GHUnitIOSViewController : UIViewController <UITableViewDelegate, GHTestRunnerDelegate, UISearchBarDelegate> {
GHUnitIOSView *view_;
-
+
GHUnitIOSTableViewDataSource *dataSource_;
GHTestSuite *suite_;
@@ -64,6 +64,14 @@ reason:@"GHVerifyView can only be called from within a GHViewTestCase class"] ra
@end
+ In order to record results across test runs, the PrepareUITests.sh script needs
+ to be run as a build step. This script copies any test images (saved locally in
+ $PROJECT_DIR/TestImages) to the app bundle so that calls to GHVerifyView have
+ images from previous runs with which to compare.
+
+ After changes to views are approved in the simulator, the CopyTestImages.sh script
+ should be run manually in Terminal. This script copies any approved view changes
+ back to the project directory.
*/
@interface GHViewTestCase : GHTestCase {
NSInteger imageVerifyCount_;
Oops, something went wrong. Retry.

0 comments on commit 7a779b0

Please sign in to comment.