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/CIFilter.h>
29 #import <QuartzCore/CIImage.h>
30 #import <QuartzCore/CIContext.h>
31 #import <AppKit/NSBitmapImageRep.h>
32 #import <AppKit/NSGraphicsContext.h>
33 #import <AppKit/NSCIImageRep.h>
36 int main(int argc, const char *argv[]);
37 NSBitmapImageRep *getImageFromStdin(int imageSize);
38 void compareImages(NSBitmapImageRep *actualBitmap, NSBitmapImageRep *baselineImage);
39 NSBitmapImageRep *getDifferenceBitmap(NSBitmapImageRep *testBitmap, NSBitmapImageRep *referenceBitmap);
40 float computePercentageDifferent(NSBitmapImageRep *diffBitmap);
43 int main(int argc, const char *argv[])
45 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
48 NSBitmapImageRep *actualImage = nil;
49 NSBitmapImageRep *baselineImage = nil;
51 NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
52 while (fgets(buffer, sizeof(buffer), stdin)) {
54 char *newLineCharacter = strchr(buffer, '\n');
55 if (newLineCharacter) {
56 *newLineCharacter = '\0';
59 if (strncmp("Content-length: ", buffer, 16) == 0) {
61 int imageSize = strtol(strtok(NULL, " "), NULL, 10);
63 if(imageSize > 0 && actualImage == nil)
64 actualImage = getImageFromStdin(imageSize);
65 else if (imageSize > 0 && baselineImage == nil)
66 baselineImage = getImageFromStdin(imageSize);
68 fputs("error, image size must be specified.\n", stdout);
71 if (actualImage != nil && baselineImage != nil) {
72 compareImages(actualImage, baselineImage);
76 innerPool = [[NSAutoreleasePool alloc] init];
87 NSBitmapImageRep *getImageFromStdin(int bytesRemaining)
89 unsigned char buffer[2048];
90 NSMutableData *data = [[NSMutableData alloc] initWithCapacity:bytesRemaining];
93 while (bytesRemaining > 0) {
94 bytesRead = (bytesRemaining > 2048 ? 2048 : bytesRemaining);
95 fread(buffer, bytesRead, 1, stdin);
96 [data appendBytes:buffer length:bytesRead];
97 bytesRemaining -= bytesRead;
100 NSBitmapImageRep *image = [NSBitmapImageRep imageRepWithData:data];
106 void compareImages(NSBitmapImageRep *actualBitmap, NSBitmapImageRep *baselineBitmap)
108 // prepare the difference blend to check for pixel variations
109 NSBitmapImageRep *diffBitmap = getDifferenceBitmap(actualBitmap, baselineBitmap);
111 float percentage = computePercentageDifferent(diffBitmap);
113 // send message to let them know if an image was wrong
114 if (percentage > 0.0) {
115 // since the diff might actually show something, send it to stdout
116 NSData *diffPNGData = [diffBitmap representationUsingType:NSPNGFileType properties:nil];
117 fprintf(stdout, "Content-length: %d\n", [diffPNGData length]);
118 fwrite([diffPNGData bytes], [diffPNGData length], 1, stdout);
119 fprintf(stdout, "diff: %01.2f%% failed\n", percentage);
121 fprintf(stdout, "diff: %01.2f%% passed\n", percentage);
124 NSBitmapImageRep *getDifferenceBitmap(NSBitmapImageRep *testBitmap, NSBitmapImageRep *referenceBitmap)
126 // we must have both images to take diff
127 if (testBitmap == nil || referenceBitmap == nil)
130 // create a new graphics context to draw our CIImage on
131 NSBitmapImageRep *diffBitmap = [[testBitmap copy] autorelease]; // FIXME: likely faster ways than copying.
132 NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:diffBitmap];
133 CIImage *referenceImage = [[CIImage alloc] initWithBitmapImageRep:referenceBitmap];
134 CIImage *testImage = [[CIImage alloc] initWithBitmapImageRep:testBitmap];
135 CIFilter *diffBlend = [CIFilter filterWithName:@"CIDifferenceBlendMode"];
137 // generate the diff image
138 [diffBlend setValue:referenceImage forKey:@"inputImage"];
139 [diffBlend setValue:testImage forKey:@"inputBackgroundImage"];
140 CIImage *diffImage = [diffBlend valueForKey:@"outputImage"];
142 // prepare to draw the image, save current state
143 [NSGraphicsContext saveGraphicsState];
144 [NSGraphicsContext setCurrentContext: nsContext];
146 // draw the difference image
147 [[nsContext CIContext] drawImage:diffImage atPoint:CGPointZero fromRect:[diffImage extent]];
149 // restore the previous context and state
150 [NSGraphicsContext restoreGraphicsState];
152 [referenceImage release];
159 * Counts the number of non-black pixels, and returns the percentage
160 * of non-black pixels to total pixels in the image.
162 float computePercentageDifferent(NSBitmapImageRep *diffBitmap)
164 // if diffBiatmap is nil, then there was an error, and it didn't match.
165 if (diffBitmap == nil)
168 int totalPixels = [diffBitmap pixelsHigh] * [diffBitmap pixelsWide];
169 int totalBytes = [diffBitmap bytesPerRow] * [diffBitmap pixelsHigh];
170 unsigned char *bitmapData = [diffBitmap bitmapData];
173 // NOTE: This may not be safe when switching between ENDIAN types
174 for (int i = 0; i < totalBytes; i += 4) {
175 if (*(bitmapData + i) != 0 || *(bitmapData + i + 1) != 0 || *(bitmapData + i + 2) != 0)
179 return (differences * 100.0)/totalPixels;