- add file that I forgot in the last checkin
[WebKit-https.git] / WebKitTools / DumpRenderTree / 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/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>
34
35 /* prototypes */
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);
41
42
43 int main(int argc, const char *argv[])
44 {
45     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
46
47     char buffer[2048];
48     NSBitmapImageRep *actualImage = nil;
49     NSBitmapImageRep *baselineImage = nil;
50
51     NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
52     while (fgets(buffer, sizeof(buffer), stdin)) {
53         // remove the CR
54         char *newLineCharacter = strchr(buffer, '\n');
55         if (newLineCharacter) {
56             *newLineCharacter = '\0';
57         }
58         
59         if (strncmp("Content-length: ", buffer, 16) == 0) {
60             strtok(buffer, " ");
61             int imageSize = strtol(strtok(NULL, " "), NULL, 10);
62
63             if(imageSize > 0 && actualImage == nil) 
64                 actualImage = getImageFromStdin(imageSize);
65             else if (imageSize > 0 && baselineImage == nil)
66                 baselineImage = getImageFromStdin(imageSize);
67             else
68                 fputs("error, image size must be specified.\n", stdout);
69         }
70         
71         if (actualImage != nil && baselineImage != nil) {
72             compareImages(actualImage, 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 NSBitmapImageRep *getImageFromStdin(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     
100     NSBitmapImageRep *image = [NSBitmapImageRep imageRepWithData:data];
101     [data release];
102     
103     return image; 
104 }
105
106 void compareImages(NSBitmapImageRep *actualBitmap, NSBitmapImageRep *baselineBitmap)
107 {
108     // prepare the difference blend to check for pixel variations
109     NSBitmapImageRep *diffBitmap = getDifferenceBitmap(actualBitmap, baselineBitmap);
110             
111     float percentage = computePercentageDifferent(diffBitmap);
112     
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);
120     } else
121         fprintf(stdout, "diff: %01.2f%% passed\n", percentage);
122 }
123
124 NSBitmapImageRep *getDifferenceBitmap(NSBitmapImageRep *testBitmap, NSBitmapImageRep *referenceBitmap)
125 {
126     // we must have both images to take diff
127     if (testBitmap == nil || referenceBitmap == nil)
128         return nil;
129
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"];
136
137     // generate the diff image
138     [diffBlend setValue:referenceImage forKey:@"inputImage"];
139     [diffBlend setValue:testImage forKey:@"inputBackgroundImage"];
140     CIImage *diffImage = [diffBlend valueForKey:@"outputImage"];
141     
142     // prepare to draw the image, save current state
143     [NSGraphicsContext saveGraphicsState];
144     [NSGraphicsContext setCurrentContext: nsContext];
145     
146     // draw the difference image
147     [[nsContext CIContext] drawImage:diffImage atPoint:CGPointZero fromRect:[diffImage extent]];
148     
149     // restore the previous context and state
150     [NSGraphicsContext restoreGraphicsState];
151         
152     [referenceImage release];
153     [testImage release];
154     
155     return diffBitmap;
156 }
157
158 /**
159  * Counts the number of non-black pixels, and returns the percentage
160  * of non-black pixels to total pixels in the image.
161  */
162 float computePercentageDifferent(NSBitmapImageRep *diffBitmap)
163 {
164     // if diffBiatmap is nil, then there was an error, and it didn't match.
165     if (diffBitmap == nil)
166         return 100.0;
167         
168     int totalPixels = [diffBitmap pixelsHigh] * [diffBitmap pixelsWide];
169     int totalBytes = [diffBitmap bytesPerRow] * [diffBitmap pixelsHigh];
170     unsigned char *bitmapData = [diffBitmap bitmapData];
171     int differences = 0;
172     
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)
176             differences++;
177     }
178     
179     return (differences * 100.0)/totalPixels;
180 }