Reviewed by Darin Adler.
[WebKit-https.git] / WebKitTools / DumpRenderTree / mac / ImageDiff.m
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  * Copyright (C) 2005 Ben La Monica <ben.lamonica@gmail.com>.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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. 
25  */
26
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>
32
33 #import <getopt.h>
34
35 /* prototypes */
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);
41
42
43 int main(int argc, const char *argv[])
44 {
45     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
46
47     unsigned threshold = 0;
48
49     struct option options[] = {
50         {"threshold", required_argument, NULL, 't'},
51         {NULL, 0, NULL, 0}
52     };
53
54     int option;
55     while ((option = getopt_long(argc, (char * const *)argv, "", options, NULL)) != -1) {
56         switch (option) {
57         case 't':
58             threshold = strtol(optarg, NULL, 0);
59             break;
60         case '?':   // unknown or ambiguous option
61         case ':':   // missing argument
62             exit(1);
63             break;
64         }
65     }
66     
67     char buffer[2048];
68     CGImageRef actualImage = nil;
69     CGImageRef baselineImage = nil;
70
71     NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
72     while (fgets(buffer, sizeof(buffer), stdin)) {
73         // remove the CR
74         char *newLineCharacter = strchr(buffer, '\n');
75         if (newLineCharacter) {
76             *newLineCharacter = '\0';
77         }
78         
79         if (strncmp("Content-length: ", buffer, 16) == 0) {
80             strtok(buffer, " ");
81             int imageSize = strtol(strtok(NULL, " "), NULL, 10);
82
83             if(imageSize > 0 && actualImage == nil) 
84                 actualImage = createImageFromStdin(imageSize);
85             else if (imageSize > 0 && baselineImage == nil)
86                 baselineImage = createImageFromStdin(imageSize);
87             else
88                 fputs("error, image size must be specified.\n", stdout);
89         }
90
91         if (actualImage != nil && baselineImage != nil) {
92             compareImages(actualImage, baselineImage, threshold);
93             CGImageRelease(actualImage);
94             CGImageRelease(baselineImage);
95             actualImage = nil;
96             baselineImage = nil;
97             [innerPool release];
98             innerPool = [[NSAutoreleasePool alloc] init];
99         }
100         
101         fflush(stdout);
102     }
103     [innerPool release];
104     
105     [pool release];
106     return 0;
107 }
108
109 CGImageRef createImageFromStdin(int bytesRemaining)
110 {
111     unsigned char buffer[2048];
112     NSMutableData *data = [[NSMutableData alloc] initWithCapacity:bytesRemaining];
113     
114     int bytesRead = 0;
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;
120     }
121     CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)data);
122     CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, kCGRenderingIntentDefault);
123     [data release];
124     CGDataProviderRelease(dataProvider);
125     
126     return image; 
127 }
128
129 void compareImages(CGImageRef actualBitmap, CGImageRef baselineBitmap, unsigned threshold)
130 {
131     // prepare the difference blend to check for pixel variations
132     NSBitmapImageRep *diffBitmap = getDifferenceBitmap(actualBitmap, baselineBitmap);
133             
134     float percentage = computePercentageDifferent(diffBitmap, threshold);
135     
136     percentage = (float)((int)(percentage * 100.0f)) / 100.0f; // round to 2 decimal places
137     
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);
145     } else
146         fprintf(stdout, "diff: %01.2f%% passed\n", percentage);
147 }
148
149 NSBitmapImageRep *getDifferenceBitmap(CGImageRef testBitmap, CGImageRef referenceBitmap)
150 {
151     // we must have both images to take diff
152     if (testBitmap == nil || referenceBitmap == nil)
153         return nil;
154
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)
161                                 hasAlpha:YES
162                                 isPlanar:NO
163                           colorSpaceName:NSCalibratedRGBColorSpace
164                             bitmapFormat:0
165                              bytesPerRow:CGImageGetBytesPerRow(testBitmap)
166                             bitsPerPixel:CGImageGetBitsPerPixel(testBitmap)
167     ];
168
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);
175
176     return [diffBitmap autorelease];
177 }
178
179 /**
180  * Counts the number of non-black pixels, and returns the percentage
181  * of non-black pixels to total pixels in the image.
182  */
183 float computePercentageDifferent(NSBitmapImageRep *diffBitmap, unsigned threshold)
184 {
185     // if diffBiatmap is nil, then there was an error, and it didn't match.
186     if (diffBitmap == nil)
187         return 100.0f;
188     
189     unsigned bitmapFormat = [diffBitmap bitmapFormat];
190     assert(!(bitmapFormat & NSAlphaFirstBitmapFormat));
191     assert(!(bitmapFormat & NSFloatingPointSamplesBitmapFormat));
192     
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;
198     
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) {
207                 differences++;
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);
212             }
213         }
214         pixelRowData += bytesPerRow;
215     }
216     
217     float totalPixels = pixelsHigh * pixelsWide;
218     return (differences * 100.f) / totalPixels;
219 }