initial import
[vuplus_webkit] / Tools / DumpRenderTree / chromium / ImageDiff.cpp
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 <iterator>
42 #include <stdio.h>
43 #include <string.h>
44 #include <vector>
45 #include <wtf/OwnArrayPtr.h>
46 #include <wtf/Vector.h>
47
48 #if OS(WINDOWS)
49 #include <windows.h>
50 #define PATH_MAX MAX_PATH
51 #endif
52
53 using namespace std;
54
55 // Causes the app to remain open, waiting for pairs of filenames on stdin.
56 // The caller is then responsible for terminating this app.
57 static const char optionPollStdin[] = "--use-stdin";
58 static const char optionGenerateDiff[] = "--diff";
59
60 // If --diff is passed, causes the app to output the image difference
61 // metric (percentageDifferent()) on stdout.
62 static const char optionWrite[] = "--write-image-diff-metrics";
63
64 // Use weightedPercentageDifferent() instead of the default image
65 // comparator proc.
66 static const char optionWeightedIntensity[] = "--weighted-intensity";
67
68 // Return codes used by this utility.
69 static const int statusSame = 0;
70 static const int statusDifferent = 1;
71 static const int statusError = 2;
72
73 // Color codes.
74 static const uint32_t rgbaRed = 0x000000ff;
75 static const uint32_t rgbaAlpha = 0xff000000;
76
77 class Image {
78 public:
79     Image()
80         : m_width(0)
81         , m_height(0) { }
82
83     Image(const Image& image)
84         : m_width(image.m_width)
85         , m_height(image.m_height)
86         , m_data(image.m_data) { }
87
88     bool hasImage() const { return m_width > 0 && m_height > 0; }
89     int width() const { return m_width; }
90     int height() const { return m_height; }
91     const unsigned char* data() const { return &m_data.front(); }
92
93     // Creates the image from stdin with the given data length. On success, it
94     // will return true. On failure, no other methods should be accessed.
95     bool createFromStdin(size_t byteLength)
96     {
97         if (!byteLength)
98             return false;
99
100         OwnArrayPtr<unsigned char> source = adoptArrayPtr(new unsigned char[byteLength]);
101         if (fread(source.get(), 1, byteLength, stdin) != byteLength)
102             return false;
103
104         if (!webkit_support::DecodePNG(source.get(), byteLength, &m_data, &m_width, &m_height)) {
105             clear();
106             return false;
107         }
108         return true;
109     }
110
111     // Creates the image from the given filename on disk, and returns true on
112     // success.
113     bool createFromFilename(const char* filename)
114     {
115         FILE* f = fopen(filename, "rb");
116         if (!f)
117             return false;
118
119         vector<unsigned char> compressed;
120         const int bufSize = 1024;
121         unsigned char buf[bufSize];
122         size_t numRead = 0;
123         while ((numRead = fread(buf, 1, bufSize, f)) > 0)
124             std::copy(buf, &buf[numRead], std::back_inserter(compressed));
125
126         fclose(f);
127
128         if (!webkit_support::DecodePNG(&compressed[0], compressed.size(), &m_data, &m_width, &m_height)) {
129             clear();
130             return false;
131         }
132         return true;
133     }
134
135     void clear()
136     {
137         m_width = m_height = 0;
138         m_data.clear();
139     }
140
141     // Returns the RGBA value of the pixel at the given location
142     const uint32_t pixelAt(int x, int y) const
143     {
144         ASSERT(x >= 0 && x < m_width);
145         ASSERT(y >= 0 && y < m_height);
146         return *reinterpret_cast<const uint32_t*>(&(m_data[(y * m_width + x) * 4]));
147     }
148
149     void setPixelAt(int x, int y, uint32_t color) const
150     {
151         ASSERT(x >= 0 && x < m_width);
152         ASSERT(y >= 0 && y < m_height);
153         void* addr = &const_cast<unsigned char*>(&m_data.front())[(y * m_width + x) * 4];
154         *reinterpret_cast<uint32_t*>(addr) = color;
155     }
156
157 private:
158     // pixel dimensions of the image
159     int m_width, m_height;
160
161     vector<unsigned char> m_data;
162 };
163
164 typedef float (*ImageComparisonProc) (const Image&, const Image&);
165
166 float percentageDifferent(const Image& baseline, const Image& actual)
167 {
168     int w = min(baseline.width(), actual.width());
169     int h = min(baseline.height(), actual.height());
170
171     // Compute pixels different in the overlap
172     int pixelsDifferent = 0;
173     for (int y = 0; y < h; ++y) {
174         for (int x = 0; x < w; ++x) {
175             if (baseline.pixelAt(x, y) != actual.pixelAt(x, y))
176                 pixelsDifferent++;
177         }
178     }
179
180     // Count pixels that are a difference in size as also being different
181     int maxWidth = max(baseline.width(), actual.width());
182     int maxHeight = max(baseline.height(), actual.height());
183
184     // ...pixels off the right side, but not including the lower right corner
185     pixelsDifferent += (maxWidth - w) * h;
186
187     // ...pixels along the bottom, including the lower right corner
188     pixelsDifferent += (maxHeight - h) * maxWidth;
189
190     // Like the WebKit ImageDiff tool, we define percentage different in terms
191     // of the size of the 'actual' bitmap.
192     float totalPixels = static_cast<float>(actual.width()) * static_cast<float>(actual.height());
193     if (!totalPixels)
194         return 100.0f; // When the bitmap is empty, they are 100% different.
195     return static_cast<float>(pixelsDifferent) / totalPixels * 100;
196 }
197
198 inline uint32_t maxOf3(uint32_t a, uint32_t b, uint32_t c)
199 {
200     if (a < b)
201         return std::max(b, c);
202     return std::max(a, c);
203 }
204
205 inline uint32_t getRedComponent(uint32_t color)
206 {
207     return (color << 24) >> 24;
208 }
209
210 inline uint32_t getGreenComponent(uint32_t color)
211 {
212     return (color << 16) >> 24;
213 }
214
215 inline uint32_t getBlueComponent(uint32_t color)
216 {
217     return (color << 8) >> 24;
218 }
219
220 /// Rank small-pixel-count high-intensity changes as more important than
221 /// large-pixel-count low-intensity changes.
222 float weightedPercentageDifferent(const Image& baseline, const Image& actual)
223 {
224     int w = min(baseline.width(), actual.width());
225     int h = min(baseline.height(), actual.height());
226
227     float weightedPixelsDifferent = 0;
228     for (int y = 0; y < h; ++y) {
229         for (int x = 0; x < w; ++x) {
230             uint32_t actualColor = actual.pixelAt(x, y);
231             uint32_t baselineColor = baseline.pixelAt(x, y);
232             if (baselineColor != actualColor) {
233                 uint32_t actualR = getRedComponent(actualColor);
234                 uint32_t actualG = getGreenComponent(actualColor);
235                 uint32_t actualB = getBlueComponent(actualColor);
236                 uint32_t baselineR = getRedComponent(baselineColor);
237                 uint32_t baselineG = getGreenComponent(baselineColor);
238                 uint32_t baselineB = getBlueComponent(baselineColor);
239                 uint32_t deltaR = std::max(actualR, baselineR)
240                     - std::min(actualR, baselineR);
241                 uint32_t deltaG = std::max(actualG, baselineG)
242                     - std::min(actualG, baselineG);
243                 uint32_t deltaB = std::max(actualB, baselineB)
244                     - std::min(actualB, baselineB);
245                 weightedPixelsDifferent +=
246                     static_cast<float>(maxOf3(deltaR, deltaG, deltaB)) / 255;
247             }
248         }
249     }
250
251     int maxWidth = max(baseline.width(), actual.width());
252     int maxHeight = max(baseline.height(), actual.height());
253
254     weightedPixelsDifferent += (maxWidth - w) * h;
255
256     weightedPixelsDifferent += (maxHeight - h) * maxWidth;
257
258     float totalPixels = static_cast<float>(actual.width())
259         * static_cast<float>(actual.height());
260     if (!totalPixels)
261         return 100.0f;
262     return weightedPixelsDifferent / totalPixels * 100;
263 }
264
265
266 void printHelp()
267 {
268     fprintf(stderr,
269             "Usage:\n"
270             "  ImageDiff <compare file> <reference file>\n"
271             "    Compares two files on disk, returning 0 when they are the same\n"
272             "  ImageDiff --use-stdin\n"
273             "    Stays open reading pairs of filenames from stdin, comparing them,\n"
274             "    and sending 0 to stdout when they are the same\n"
275             "  ImageDiff --diff <compare file> <reference file> <output file>\n"
276             "    Compares two files on disk, outputs an image that visualizes the"
277             "    difference to <output file>\n"
278             "    --write-image-diff-metrics prints a difference metric to stdout\n"
279             "    --weighted-intensity weights the difference metric by intensity\n"
280             "      at each pixel\n");
281     /* For unfinished webkit-like-mode (see below)
282        "\n"
283        "  ImageDiff -s\n"
284        "    Reads stream input from stdin, should be EXACTLY of the format\n"
285        "    \"Content-length: <byte length> <data>Content-length: ...\n"
286        "    it will take as many file pairs as given, and will compare them as\n"
287        "    (cmp_file, reference_file) pairs\n");
288     */
289 }
290
291 int compareImages(const char* file1, const char* file2,
292                   ImageComparisonProc comparator)
293 {
294     Image actualImage;
295     Image baselineImage;
296
297     if (!actualImage.createFromFilename(file1)) {
298         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
299         return statusError;
300     }
301     if (!baselineImage.createFromFilename(file2)) {
302         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
303         return statusError;
304     }
305
306     float percent = (*comparator)(actualImage, baselineImage);
307     if (percent > 0.0) {
308         // failure: The WebKit version also writes the difference image to
309         // stdout, which seems excessive for our needs.
310         printf("diff: %01.2f%% failed\n", percent);
311         return statusDifferent;
312     }
313
314     // success
315     printf("diff: %01.2f%% passed\n", percent);
316     return statusSame;
317
318 }
319
320 // Untested mode that acts like WebKit's image comparator. I wrote this but
321 // decided it's too complicated. We may use it in the future if it looks useful.
322 int untestedCompareImages(ImageComparisonProc comparator)
323 {
324     Image actualImage;
325     Image baselineImage;
326     char buffer[2048];
327     while (fgets(buffer, sizeof(buffer), stdin)) {
328         if (!strncmp("Content-length: ", buffer, 16)) {
329             char* context;
330 #if OS(WINDOWS)
331             strtok_s(buffer, " ", &context);
332             int imageSize = strtol(strtok_s(0, " ", &context), 0, 10);
333 #else
334             strtok_r(buffer, " ", &context);
335             int imageSize = strtol(strtok_r(0, " ", &context), 0, 10);
336 #endif
337
338             bool success = false;
339             if (imageSize > 0 && !actualImage.hasImage()) {
340                 if (!actualImage.createFromStdin(imageSize)) {
341                     fputs("Error, input image can't be decoded.\n", stderr);
342                     return 1;
343                 }
344             } else if (imageSize > 0 && !baselineImage.hasImage()) {
345                 if (!baselineImage.createFromStdin(imageSize)) {
346                     fputs("Error, baseline image can't be decoded.\n", stderr);
347                     return 1;
348                 }
349             } else {
350                 fputs("Error, image size must be specified.\n", stderr);
351                 return 1;
352             }
353         }
354
355         if (actualImage.hasImage() && baselineImage.hasImage()) {
356             float percent = (*comparator)(actualImage, baselineImage);
357             if (percent > 0.0) {
358                 // failure: The WebKit version also writes the difference image to
359                 // stdout, which seems excessive for our needs.
360                 printf("diff: %01.2f%% failed\n", percent);
361             } else {
362                 // success
363                 printf("diff: %01.2f%% passed\n", percent);
364             }
365             actualImage.clear();
366             baselineImage.clear();
367         }
368         fflush(stdout);
369     }
370     return 0;
371 }
372
373 bool createImageDiff(const Image& image1, const Image& image2, Image* out)
374 {
375     int w = min(image1.width(), image2.width());
376     int h = min(image1.height(), image2.height());
377     *out = Image(image1);
378     bool same = (image1.width() == image2.width()) && (image1.height() == image2.height());
379
380     // FIXME: do something with the extra pixels if the image sizes are different.
381     for (int y = 0; y < h; ++y) {
382         for (int x = 0; x < w; ++x) {
383             uint32_t basePixel = image1.pixelAt(x, y);
384             if (basePixel != image2.pixelAt(x, y)) {
385                 // Set differing pixels red.
386                 out->setPixelAt(x, y, rgbaRed | rgbaAlpha);
387                 same = false;
388             } else {
389                 // Set same pixels as faded.
390                 uint32_t alpha = basePixel & rgbaAlpha;
391                 uint32_t newPixel = basePixel - ((alpha / 2) & rgbaAlpha);
392                 out->setPixelAt(x, y, newPixel);
393             }
394         }
395     }
396
397     return same;
398 }
399
400 static bool writeFile(const char* outFile, const unsigned char* data, size_t dataSize)
401 {
402     FILE* file = fopen(outFile, "wb");
403     if (!file) {
404         fprintf(stderr, "ImageDiff: Unable to create file \"%s\"\n", outFile);
405         return false;
406     }
407     if (dataSize != fwrite(data, 1, dataSize, file)) {
408         fclose(file);
409         fprintf(stderr, "ImageDiff: Unable to write data to file \"%s\"\n", outFile);
410         return false;
411     }
412     fclose(file);
413     return true;
414 }
415
416 int diffImages(const char* file1, const char* file2, const char* outFile,
417                bool shouldWritePercentages, ImageComparisonProc comparator)
418 {
419     Image actualImage;
420     Image baselineImage;
421
422     if (!actualImage.createFromFilename(file1)) {
423         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
424         return statusError;
425     }
426     if (!baselineImage.createFromFilename(file2)) {
427         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
428         return statusError;
429     }
430
431     Image diffImage;
432     bool same = createImageDiff(baselineImage, actualImage, &diffImage);
433     if (same)
434         return statusSame;
435
436     vector<unsigned char> pngData;
437     webkit_support::EncodeRGBAPNG(diffImage.data(), diffImage.width(), diffImage.height(),
438                                   diffImage.width() * 4, &pngData);
439     if (!writeFile(outFile, &pngData.front(), pngData.size()))
440         return statusError;
441
442     if (shouldWritePercentages) {
443         float percent = (*comparator)(actualImage, baselineImage);
444         fprintf(stdout, "%.3f\n", percent);
445     }
446
447     return statusDifferent;
448 }
449
450 int main(int argc, const char* argv[])
451 {
452     Vector<const char*> values;
453     bool pollStdin = false;
454     bool generateDiff = false;
455     bool shouldWritePercentages = false;
456     ImageComparisonProc comparator = percentageDifferent;
457     for (int i = 1; i < argc; ++i) {
458         if (!strcmp(argv[i], optionPollStdin))
459             pollStdin = true;
460         else if (!strcmp(argv[i], optionGenerateDiff))
461             generateDiff = true;
462         else if (!strcmp(argv[i], optionWrite))
463             shouldWritePercentages = true;
464         else if (!strcmp(argv[i], optionWeightedIntensity))
465             comparator = weightedPercentageDifferent;
466         else
467             values.append(argv[i]);
468     }
469
470     if (pollStdin) {
471         // Watch stdin for filenames.
472         const size_t bufferSize = PATH_MAX;
473         char stdinBuffer[bufferSize];
474         char firstName[bufferSize];
475         bool haveFirstName = false;
476         while (fgets(stdinBuffer, bufferSize, stdin)) {
477             if (!stdinBuffer[0])
478                 continue;
479
480             if (haveFirstName) {
481                 // compareImages writes results to stdout unless an error occurred.
482                 if (compareImages(firstName, stdinBuffer,
483                                   comparator) == statusError)
484                     printf("error\n");
485                 fflush(stdout);
486                 haveFirstName = false;
487             } else {
488                 // Save the first filename in another buffer and wait for the second
489                 // filename to arrive via stdin.
490                 strcpy(firstName, stdinBuffer);
491                 haveFirstName = true;
492             }
493         }
494         return 0;
495     }
496
497     if (generateDiff) {
498         if (values.size() == 3)
499             return diffImages(values[0], values[1], values[2],
500                               shouldWritePercentages, comparator);
501     } else if (values.size() == 2)
502         return compareImages(argv[1], argv[2], comparator);
503
504     printHelp();
505     return statusError;
506 }