2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
3 * Copyright (C) 2005 Ben La Monica <ben.lamonica@gmail.com>. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #import <Foundation/Foundation.h>
28 #import <QuartzCore/CoreImage.h>
29 #import <AppKit/NSBitmapImageRep.h>
30 #import <AppKit/NSGraphicsContext.h>
31 #import <AppKit/NSCIImageRep.h>
36 int main(int argc, const char *argv[]);
37 CGImageRef createImageFromStdin(int imageSize);
38 void compareImages(CGImageRef actualBitmap, CGImageRef baselineImage, unsigned threshold);
39 NSBitmapImageRep *getDifferenceBitmap(CGImageRef actualBitmap, CGImageRef baselineImage);
40 float computePercentageDifferent(NSBitmapImageRep *diffBitmap, unsigned threshold);
43 int main(int argc, const char *argv[])
45 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
47 unsigned threshold = 0;
49 struct option options[] = {
50 {"threshold", required_argument, NULL, 't'},
55 while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) {
58 threshold = strtol(optarg, NULL, 0);
60 case '?': // unknown or ambiguous option
61 case ':': // missing argument
68 CGImageRef actualImage = nil;
69 CGImageRef baselineImage = nil;
71 NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
72 while (fgets(buffer, sizeof(buffer), stdin)) {
74 char *newLineCharacter = strchr(buffer, '\n');
75 if (newLineCharacter) {
76 *newLineCharacter = '\0';
79 if (strncmp("Content-length: ", buffer, 16) == 0) {
81 int imageSize = strtol(strtok(NULL, " "), NULL, 10);
83 if(imageSize > 0 && actualImage == nil)
84 actualImage = createImageFromStdin(imageSize);
85 else if (imageSize > 0 && baselineImage == nil)
86 baselineImage = createImageFromStdin(imageSize);
88 fputs("error, image size must be specified.\n", stdout);
91 if (actualImage != nil && baselineImage != nil) {
92 compareImages(actualImage, baselineImage, threshold);
93 CGImageRelease(actualImage);
94 CGImageRelease(baselineImage);
98 innerPool = [[NSAutoreleasePool alloc] init];
109 CGImageRef createImageFromStdin(int bytesRemaining)
111 unsigned char buffer[2048];
112 NSMutableData *data = [[NSMutableData alloc] initWithCapacity:bytesRemaining];
115 while (bytesRemaining > 0) {
116 bytesRead = (bytesRemaining > 2048 ? 2048 : bytesRemaining);
117 fread(buffer, bytesRead, 1, stdin);
118 [data appendBytes:buffer length:bytesRead];
119 bytesRemaining -= bytesRead;
121 CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)data);
122 CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, kCGRenderingIntentDefault);
124 CGDataProviderRelease(dataProvider);
129 void compareImages(CGImageRef actualBitmap, CGImageRef baselineBitmap, unsigned threshold)
131 // prepare the difference blend to check for pixel variations
132 NSBitmapImageRep *diffBitmap = getDifferenceBitmap(actualBitmap, baselineBitmap);
134 float percentage = computePercentageDifferent(diffBitmap, threshold);
136 percentage = (float)((int)(percentage * 100.0f)) / 100.0f; // round to 2 decimal places
138 // send message to let them know if an image was wrong
139 if (percentage > 0.0) {
140 // since the diff might actually show something, send it to stdout
141 NSData *diffPNGData = [diffBitmap representationUsingType:NSPNGFileType properties:nil];
142 fprintf(stdout, "Content-length: %d\n", [diffPNGData length]);
143 fwrite([diffPNGData bytes], [diffPNGData length], 1, stdout);
144 fprintf(stdout, "diff: %01.2f%% failed\n", percentage);
146 fprintf(stdout, "diff: %01.2f%% passed\n", percentage);
149 NSBitmapImageRep *getDifferenceBitmap(CGImageRef testBitmap, CGImageRef referenceBitmap)
151 // we must have both images to take diff
152 if (testBitmap == nil || referenceBitmap == nil)
155 NSBitmapImageRep *diffBitmap = [NSBitmapImageRep alloc];
156 [diffBitmap initWithBitmapDataPlanes:NULL
157 pixelsWide:CGImageGetWidth(testBitmap)
158 pixelsHigh:CGImageGetHeight(testBitmap)
159 bitsPerSample:CGImageGetBitsPerComponent(testBitmap)
160 samplesPerPixel:CGImageGetBitsPerPixel(testBitmap) / CGImageGetBitsPerComponent(testBitmap)
163 colorSpaceName:NSCalibratedRGBColorSpace
165 bytesPerRow:CGImageGetBytesPerRow(testBitmap)
166 bitsPerPixel:CGImageGetBitsPerPixel(testBitmap)
169 NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:diffBitmap];
170 CGContextRef cgContext = [nsContext graphicsPort];
171 CGContextSetBlendMode(cgContext, kCGBlendModeNormal);
172 CGContextDrawImage(cgContext, CGRectMake(0, 0, CGImageGetWidth(testBitmap), CGImageGetHeight(testBitmap)), testBitmap);
173 CGContextSetBlendMode(cgContext, kCGBlendModeDifference);
174 CGContextDrawImage(cgContext, CGRectMake(0, 0, CGImageGetWidth(referenceBitmap), CGImageGetHeight(referenceBitmap)), referenceBitmap);
176 return [diffBitmap autorelease];
180 * Counts the number of non-black pixels, and returns the percentage
181 * of non-black pixels to total pixels in the image.
183 float computePercentageDifferent(NSBitmapImageRep *diffBitmap, unsigned threshold)
185 // if diffBiatmap is nil, then there was an error, and it didn't match.
186 if (diffBitmap == nil)
189 unsigned bitmapFormat = [diffBitmap bitmapFormat];
190 assert(!(bitmapFormat & NSAlphaFirstBitmapFormat));
191 assert(!(bitmapFormat & NSFloatingPointSamplesBitmapFormat));
193 unsigned pixelsHigh = [diffBitmap pixelsHigh];
194 unsigned pixelsWide = [diffBitmap pixelsWide];
195 unsigned bytesPerRow = [diffBitmap bytesPerRow];
196 unsigned char *pixelRowData = [diffBitmap bitmapData];
197 unsigned differences = 0;
199 // NOTE: This may not be safe when switching between ENDIAN types
200 for (unsigned row = 0; row < pixelsHigh; row++) {
201 for (unsigned col = 0; col < (pixelsWide * 4); col += 4) {
202 unsigned char* red = pixelRowData + col;
203 unsigned char* green = red + 1;
204 unsigned char* blue = red + 2;
205 unsigned distance = *red + *green + *blue;
206 if (distance > threshold) {
208 // shift the pixels towards white to make them more visible
209 *red = MIN(UCHAR_MAX, *red + 100);
210 *green = MIN(UCHAR_MAX, *green + 100);
211 *blue = MIN(UCHAR_MAX, *blue + 100);
214 pixelRowData += bytesPerRow;
217 float totalPixels = pixelsHigh * pixelsWide;
218 return (differences * 100.f) / totalPixels;