• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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