1 /*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 // This file input format is based loosely on
32 // WebKitTools/DumpRenderTree/ImageDiff.m
33
34 // The exact format of this tool's output to stdout is important, to match
35 // what the run-webkit-tests script expects.
36
37 #include "config.h"
38
39 #include "webkit/support/webkit_support_gfx.h"
40 #include <algorithm>
41 #include <stdio.h>
42 #include <string.h>
43 #include <vector>
44 #include <wtf/OwnArrayPtr.h>
45 #include <wtf/Vector.h>
46
47 #if OS(WINDOWS)
48 #include <windows.h>
49 #define PATH_MAX MAX_PATH
50 #endif
51
52 using namespace std;
53
54 // Causes the app to remain open, waiting for pairs of filenames on stdin.
55 // The caller is then responsible for terminating this app.
56 static const char optionPollStdin[] = "--use-stdin";
57 static const char optionGenerateDiff[] = "--diff";
58
59 // Return codes used by this utility.
60 static const int statusSame = 0;
61 static const int statusDifferent = 1;
62 static const int statusError = 2;
63
64 // Color codes.
65 static const uint32_t rgbaRed = 0x000000ff;
66 static const uint32_t rgbaAlpha = 0xff000000;
67
68 class Image {
69 public:
Image()70 Image()
71 : m_width(0)
72 , m_height(0) {}
73
Image(const Image & image)74 Image(const Image& image)
75 : m_width(image.m_width)
76 , m_height(image.m_height)
77 , m_data(image.m_data) {}
78
hasImage() const79 bool hasImage() const { return m_width > 0 && m_height > 0; }
width() const80 int width() const { return m_width; }
height() const81 int height() const { return m_height; }
data() const82 const unsigned char* data() const { return &m_data.front(); }
83
84 // Creates the image from stdin with the given data length. On success, it
85 // will return true. On failure, no other methods should be accessed.
craeteFromStdin(size_t byteLength)86 bool craeteFromStdin(size_t byteLength)
87 {
88 if (!byteLength)
89 return false;
90
91 OwnArrayPtr<unsigned char> source = adoptArrayPtr(new unsigned char[byteLength]);
92 if (fread(source.get(), 1, byteLength, stdin) != byteLength)
93 return false;
94
95 if (!webkit_support::DecodePNG(source.get(), byteLength, &m_data, &m_width, &m_height)) {
96 clear();
97 return false;
98 }
99 return true;
100 }
101
102 // Creates the image from the given filename on disk, and returns true on
103 // success.
createFromFilename(const char * filename)104 bool createFromFilename(const char* filename)
105 {
106 FILE* f = fopen(filename, "rb");
107 if (!f)
108 return false;
109
110 vector<unsigned char> compressed;
111 const int bufSize = 1024;
112 unsigned char buf[bufSize];
113 size_t numRead = 0;
114 while ((numRead = fread(buf, 1, bufSize, f)) > 0)
115 std::copy(buf, &buf[numRead], std::back_inserter(compressed));
116
117 fclose(f);
118
119 if (!webkit_support::DecodePNG(&compressed[0], compressed.size(), &m_data, &m_width, &m_height)) {
120 clear();
121 return false;
122 }
123 return true;
124 }
125
clear()126 void clear()
127 {
128 m_width = m_height = 0;
129 m_data.clear();
130 }
131
132 // Returns the RGBA value of the pixel at the given location
pixelAt(int x,int y) const133 const uint32_t pixelAt(int x, int y) const
134 {
135 ASSERT(x >= 0 && x < m_width);
136 ASSERT(y >= 0 && y < m_height);
137 return *reinterpret_cast<const uint32_t*>(&(m_data[(y * m_width + x) * 4]));
138 }
139
setPixelAt(int x,int y,uint32_t color) const140 void setPixelAt(int x, int y, uint32_t color) const
141 {
142 ASSERT(x >= 0 && x < m_width);
143 ASSERT(y >= 0 && y < m_height);
144 void* addr = &const_cast<unsigned char*>(&m_data.front())[(y * m_width + x) * 4];
145 *reinterpret_cast<uint32_t*>(addr) = color;
146 }
147
148 private:
149 // pixel dimensions of the image
150 int m_width, m_height;
151
152 vector<unsigned char> m_data;
153 };
154
percentageDifferent(const Image & baseline,const Image & actual)155 float percentageDifferent(const Image& baseline, const Image& actual)
156 {
157 int w = min(baseline.width(), actual.width());
158 int h = min(baseline.height(), actual.height());
159
160 // Compute pixels different in the overlap
161 int pixelsDifferent = 0;
162 for (int y = 0; y < h; y++) {
163 for (int x = 0; x < w; x++) {
164 if (baseline.pixelAt(x, y) != actual.pixelAt(x, y))
165 pixelsDifferent++;
166 }
167 }
168
169 // Count pixels that are a difference in size as also being different
170 int maxWidth = max(baseline.width(), actual.width());
171 int maxHeight = max(baseline.height(), actual.height());
172
173 // ...pixels off the right side, but not including the lower right corner
174 pixelsDifferent += (maxWidth - w) * h;
175
176 // ...pixels along the bottom, including the lower right corner
177 pixelsDifferent += (maxHeight - h) * maxWidth;
178
179 // Like the WebKit ImageDiff tool, we define percentage different in terms
180 // of the size of the 'actual' bitmap.
181 float totalPixels = static_cast<float>(actual.width()) * static_cast<float>(actual.height());
182 if (!totalPixels)
183 return 100.0f; // When the bitmap is empty, they are 100% different.
184 return static_cast<float>(pixelsDifferent) / totalPixels * 100;
185 }
186
printHelp()187 void printHelp()
188 {
189 fprintf(stderr,
190 "Usage:\n"
191 " ImageDiff <compare file> <reference file>\n"
192 " Compares two files on disk, returning 0 when they are the same\n"
193 " ImageDiff --use-stdin\n"
194 " Stays open reading pairs of filenames from stdin, comparing them,\n"
195 " and sending 0 to stdout when they are the same\n"
196 " ImageDiff --diff <compare file> <reference file> <output file>\n"
197 " Compares two files on disk, outputs an image that visualizes the"
198 " difference to <output file>\n");
199 /* For unfinished webkit-like-mode (see below)
200 "\n"
201 " ImageDiff -s\n"
202 " Reads stream input from stdin, should be EXACTLY of the format\n"
203 " \"Content-length: <byte length> <data>Content-length: ...\n"
204 " it will take as many file pairs as given, and will compare them as\n"
205 " (cmp_file, reference_file) pairs\n");
206 */
207 }
208
compareImages(const char * file1,const char * file2)209 int compareImages(const char* file1, const char* file2)
210 {
211 Image actualImage;
212 Image baselineImage;
213
214 if (!actualImage.createFromFilename(file1)) {
215 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
216 return statusError;
217 }
218 if (!baselineImage.createFromFilename(file2)) {
219 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
220 return statusError;
221 }
222
223 float percent = percentageDifferent(actualImage, baselineImage);
224 if (percent > 0.0) {
225 // failure: The WebKit version also writes the difference image to
226 // stdout, which seems excessive for our needs.
227 printf("diff: %01.2f%% failed\n", percent);
228 return statusDifferent;
229 }
230
231 // success
232 printf("diff: %01.2f%% passed\n", percent);
233 return statusSame;
234
235 }
236
237 // Untested mode that acts like WebKit's image comparator. I wrote this but
238 // decided it's too complicated. We may use it in the future if it looks useful.
untestedCompareImages()239 int untestedCompareImages()
240 {
241 Image actualImage;
242 Image baselineImage;
243 char buffer[2048];
244 while (fgets(buffer, sizeof(buffer), stdin)) {
245 if (!strncmp("Content-length: ", buffer, 16)) {
246 char* context;
247 #if OS(WINDOWS)
248 strtok_s(buffer, " ", &context);
249 int imageSize = strtol(strtok_s(0, " ", &context), 0, 10);
250 #else
251 strtok_r(buffer, " ", &context);
252 int imageSize = strtol(strtok_r(0, " ", &context), 0, 10);
253 #endif
254
255 bool success = false;
256 if (imageSize > 0 && !actualImage.hasImage()) {
257 if (!actualImage.craeteFromStdin(imageSize)) {
258 fputs("Error, input image can't be decoded.\n", stderr);
259 return 1;
260 }
261 } else if (imageSize > 0 && !baselineImage.hasImage()) {
262 if (!baselineImage.craeteFromStdin(imageSize)) {
263 fputs("Error, baseline image can't be decoded.\n", stderr);
264 return 1;
265 }
266 } else {
267 fputs("Error, image size must be specified.\n", stderr);
268 return 1;
269 }
270 }
271
272 if (actualImage.hasImage() && baselineImage.hasImage()) {
273 float percent = percentageDifferent(actualImage, baselineImage);
274 if (percent > 0.0) {
275 // failure: The WebKit version also writes the difference image to
276 // stdout, which seems excessive for our needs.
277 printf("diff: %01.2f%% failed\n", percent);
278 } else {
279 // success
280 printf("diff: %01.2f%% passed\n", percent);
281 }
282 actualImage.clear();
283 baselineImage.clear();
284 }
285 fflush(stdout);
286 }
287 return 0;
288 }
289
createImageDiff(const Image & image1,const Image & image2,Image * out)290 bool createImageDiff(const Image& image1, const Image& image2, Image* out)
291 {
292 int w = min(image1.width(), image2.width());
293 int h = min(image1.height(), image2.height());
294 *out = Image(image1);
295 bool same = (image1.width() == image2.width()) && (image1.height() == image2.height());
296
297 // FIXME: do something with the extra pixels if the image sizes are different.
298 for (int y = 0; y < h; y++) {
299 for (int x = 0; x < w; x++) {
300 uint32_t basePixel = image1.pixelAt(x, y);
301 if (basePixel != image2.pixelAt(x, y)) {
302 // Set differing pixels red.
303 out->setPixelAt(x, y, rgbaRed | rgbaAlpha);
304 same = false;
305 } else {
306 // Set same pixels as faded.
307 uint32_t alpha = basePixel & rgbaAlpha;
308 uint32_t newPixel = basePixel - ((alpha / 2) & rgbaAlpha);
309 out->setPixelAt(x, y, newPixel);
310 }
311 }
312 }
313
314 return same;
315 }
316
writeFile(const char * outFile,const unsigned char * data,size_t dataSize)317 static bool writeFile(const char* outFile, const unsigned char* data, size_t dataSize)
318 {
319 FILE* file = fopen(outFile, "wb");
320 if (!file) {
321 fprintf(stderr, "ImageDiff: Unable to create file \"%s\"\n", outFile);
322 return false;
323 }
324 if (dataSize != fwrite(data, 1, dataSize, file)) {
325 fclose(file);
326 fprintf(stderr, "ImageDiff: Unable to write data to file \"%s\"\n", outFile);
327 return false;
328 }
329 fclose(file);
330 return true;
331 }
332
diffImages(const char * file1,const char * file2,const char * outFile)333 int diffImages(const char* file1, const char* file2, const char* outFile)
334 {
335 Image actualImage;
336 Image baselineImage;
337
338 if (!actualImage.createFromFilename(file1)) {
339 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
340 return statusError;
341 }
342 if (!baselineImage.createFromFilename(file2)) {
343 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
344 return statusError;
345 }
346
347 Image diffImage;
348 bool same = createImageDiff(baselineImage, actualImage, &diffImage);
349 if (same)
350 return statusSame;
351
352 vector<unsigned char> pngData;
353 webkit_support::EncodeRGBAPNG(diffImage.data(), diffImage.width(), diffImage.height(),
354 diffImage.width() * 4, &pngData);
355 if (!writeFile(outFile, &pngData.front(), pngData.size()))
356 return statusError;
357 return statusDifferent;
358 }
359
main(int argc,const char * argv[])360 int main(int argc, const char* argv[])
361 {
362 Vector<const char*> values;
363 bool pollStdin = false;
364 bool generateDiff = false;
365 for (int i = 1; i < argc; ++i) {
366 if (!strcmp(argv[i], optionPollStdin))
367 pollStdin = true;
368 else if (!strcmp(argv[i], optionGenerateDiff))
369 generateDiff = true;
370 else
371 values.append(argv[i]);
372 }
373
374 if (pollStdin) {
375 // Watch stdin for filenames.
376 const size_t bufferSize = PATH_MAX;
377 char stdinBuffer[bufferSize];
378 char firstName[bufferSize];
379 bool haveFirstName = false;
380 while (fgets(stdinBuffer, bufferSize, stdin)) {
381 if (!stdinBuffer[0])
382 continue;
383
384 if (haveFirstName) {
385 // compareImages writes results to stdout unless an error occurred.
386 if (compareImages(firstName, stdinBuffer) == statusError)
387 printf("error\n");
388 fflush(stdout);
389 haveFirstName = false;
390 } else {
391 // Save the first filename in another buffer and wait for the second
392 // filename to arrive via stdin.
393 strcpy(firstName, stdinBuffer);
394 haveFirstName = true;
395 }
396 }
397 return 0;
398 }
399
400 if (generateDiff) {
401 if (values.size() == 3)
402 return diffImages(values[0], values[1], values[2]);
403 } else if (values.size() == 2)
404 return compareImages(argv[1], argv[2]);
405
406 printHelp();
407 return statusError;
408 }
409