8ceb0bf700746c623b1a27205dc7f1c1f60b5041
[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 /* prototypes */
34 int main(int argc, const char *argv[]);
35 CGImageRef createImageFromStdin(int imageSize);
36 void compareImages(CGImageRef actualBitmap, CGImageRef baselineImage);
37 NSBitmapImageRep *getDifferenceBitmap(CGImageRef actualBitmap, CGImageRef baselineImage);
38 float computePercentageDifferent(NSBitmapImageRep *diffBitmap);
39
40
41 int main(int argc, const char *argv[])
42 {
43     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
44
45     char buffer[2048];
46     CGImageRef actualImage = nil;
47     CGImageRef baselineImage = nil;
48
49     NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
50     while (fgets(buffer, sizeof(buffer), stdin)) {
51         // remove the CR
52         char *newLineCharacter = strchr(buffer, '\n');
53         if (newLineCharacter) {
54             *newLineCharacter = '\0';
55         }
56         
57         if (strncmp("Content-length: ", buffer, 16) == 0) {
58             strtok(buffer, " ");
59             int imageSize = strtol(strtok(NULL, " "), NULL, 10);
60
61             if(imageSize > 0 && actualImage == nil) 
62                 actualImage = createImageFromStdin(imageSize);
63             else if (imageSize > 0 && baselineImage == nil)
64                 baselineImage = createImageFromStdin(imageSize);
65             else
66                 fputs("error, image size must be specified.\n", stdout);
67         }
68
69         if (actualImage != nil && baselineImage != nil) {
70             compareImages(actualImage, baselineImage);
71             CGImageRelease(actualImage);
72             CGImageRelease(baselineImage);
73             actualImage = nil;
74             baselineImage = nil;
75             [innerPool release];
76             innerPool = [[NSAutoreleasePool alloc] init];
77         }
78         
79         fflush(stdout);
80     }
81     [innerPool release];
82     
83     [pool release];
84     return 0;
85 }
86
87 CGImageRef createImageFromStdin(int bytesRemaining)
88 {
89     unsigned char buffer[2048];
90     NSMutableData *data = [[NSMutableData alloc] initWithCapacity:bytesRemaining];
91     
92     int bytesRead = 0;
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;
98     }
99     CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)data);
100     CGImageRef image = CGImageCreateWithPNGDataProvider(dataProvider, NULL, NO, kCGRenderingIntentDefault);
101     [data release];
102     CGDataProviderRelease(dataProvider);
103     
104     return image; 
105 }
106
107 void compareImages(CGImageRef actualBitmap, CGImageRef baselineBitmap)
108 {
109     // prepare the difference blend to check for pixel variations
110     NSBitmapImageRep *diffBitmap = getDifferenceBitmap(actualBitmap, baselineBitmap);
111             
112     float percentage = computePercentageDifferent(diffBitmap);
113     
114     percentage = (float)((int)(percentage * 100.0f)) / 100.0f; // round to 2 decimal places
115     
116     // send message to let them know if an image was wrong
117     if (percentage > 0.0) {
118         // since the diff might actually show something, send it to stdout
119         NSData *diffPNGData = [diffBitmap representationUsingType:NSPNGFileType properties:nil];
120         fprintf(stdout, "Content-length: %d\n", [diffPNGData length]);
121         fwrite([diffPNGData bytes], [diffPNGData length], 1, stdout);
122         fprintf(stdout, "diff: %01.2f%% failed\n", percentage);
123     } else
124         fprintf(stdout, "diff: %01.2f%% passed\n", percentage);
125 }
126
127 NSBitmapImageRep *getDifferenceBitmap(CGImageRef testBitmap, CGImageRef referenceBitmap)
128 {
129     // we must have both images to take diff
130     if (testBitmap == nil || referenceBitmap == nil)
131         return nil;
132
133     NSBitmapImageRep *diffBitmap = [NSBitmapImageRep alloc];
134     [diffBitmap initWithBitmapDataPlanes:NULL
135                               pixelsWide:CGImageGetWidth(testBitmap)
136                               pixelsHigh:CGImageGetHeight(testBitmap)
137                            bitsPerSample:CGImageGetBitsPerComponent(testBitmap)
138                          samplesPerPixel:CGImageGetBitsPerPixel(testBitmap) / CGImageGetBitsPerComponent(testBitmap)
139                                 hasAlpha:YES
140                                 isPlanar:NO
141                           colorSpaceName:NSCalibratedRGBColorSpace
142                             bitmapFormat:0
143                              bytesPerRow:CGImageGetBytesPerRow(testBitmap)
144                             bitsPerPixel:CGImageGetBitsPerPixel(testBitmap)
145     ];
146
147     NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:diffBitmap];
148     CGContextRef cgContext = [nsContext graphicsPort];
149     CGContextSetBlendMode(cgContext, kCGBlendModeNormal);
150     CGContextDrawImage(cgContext, CGRectMake(0, 0, CGImageGetWidth(testBitmap), CGImageGetHeight(testBitmap)), testBitmap);
151     CGContextSetBlendMode(cgContext, kCGBlendModeDifference);
152     CGContextDrawImage(cgContext, CGRectMake(0, 0, CGImageGetWidth(referenceBitmap), CGImageGetHeight(referenceBitmap)), referenceBitmap);
153
154     return [diffBitmap autorelease];
155 }
156
157 /**
158  * Counts the number of non-black pixels, and returns the percentage
159  * of non-black pixels to total pixels in the image.
160  */
161 float computePercentageDifferent(NSBitmapImageRep *diffBitmap)
162 {
163     // if diffBiatmap is nil, then there was an error, and it didn't match.
164     if (diffBitmap == nil)
165         return 100.0f;
166     
167     unsigned bitmapFormat = [diffBitmap bitmapFormat];
168     assert(!(bitmapFormat & NSAlphaFirstBitmapFormat));
169     assert(!(bitmapFormat & NSFloatingPointSamplesBitmapFormat));
170     
171     unsigned pixelsHigh = [diffBitmap pixelsHigh];
172     unsigned pixelsWide = [diffBitmap pixelsWide];
173     unsigned bytesPerRow = [diffBitmap bytesPerRow];
174     unsigned char *pixelRowData = [diffBitmap bitmapData];
175     unsigned differences = 0;
176     
177     // NOTE: This may not be safe when switching between ENDIAN types
178     for (unsigned row = 0; row < pixelsHigh; row++) {
179         for (unsigned col = 0; col < (pixelsWide * 4); col += 4) {
180             unsigned char* red = pixelRowData + col;
181             unsigned char* green = red + 1;
182             unsigned char* blue = red + 2;
183             if (*red != 0 || *green != 0 || *blue != 0) {
184                 differences++;
185                 // shift the pixels towards white to make them more visible
186                 *red = MIN(UCHAR_MAX, *red + 100);
187                 *green = MIN(UCHAR_MAX, *green + 100);
188                 *blue = MIN(UCHAR_MAX, *blue + 100);
189             }
190         }
191         pixelRowData += bytesPerRow;
192     }
193     
194     float totalPixels = pixelsHigh * pixelsWide;
195     return (differences * 100.f) / totalPixels;
196 }