2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
31 // This file input format is based loosely on
32 // WebKitTools/DumpRenderTree/ImageDiff.m
34 // The exact format of this tool's output to stdout is important, to match
35 // what the run-webkit-tests script expects.
39 #include "webkit/support/webkit_support_gfx.h"
45 #include <wtf/OwnArrayPtr.h>
46 #include <wtf/Vector.h>
50 #define PATH_MAX MAX_PATH
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";
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";
64 // Use weightedPercentageDifferent() instead of the default image
66 static const char optionWeightedIntensity[] = "--weighted-intensity";
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;
74 static const uint32_t rgbaRed = 0x000000ff;
75 static const uint32_t rgbaAlpha = 0xff000000;
83 Image(const Image& image)
84 : m_width(image.m_width)
85 , m_height(image.m_height)
86 , m_data(image.m_data) { }
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(); }
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)
100 OwnArrayPtr<unsigned char> source = adoptArrayPtr(new unsigned char[byteLength]);
101 if (fread(source.get(), 1, byteLength, stdin) != byteLength)
104 if (!webkit_support::DecodePNG(source.get(), byteLength, &m_data, &m_width, &m_height)) {
111 // Creates the image from the given filename on disk, and returns true on
113 bool createFromFilename(const char* filename)
115 FILE* f = fopen(filename, "rb");
119 vector<unsigned char> compressed;
120 const int bufSize = 1024;
121 unsigned char buf[bufSize];
123 while ((numRead = fread(buf, 1, bufSize, f)) > 0)
124 std::copy(buf, &buf[numRead], std::back_inserter(compressed));
128 if (!webkit_support::DecodePNG(&compressed[0], compressed.size(), &m_data, &m_width, &m_height)) {
137 m_width = m_height = 0;
141 // Returns the RGBA value of the pixel at the given location
142 const uint32_t pixelAt(int x, int y) const
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]));
149 void setPixelAt(int x, int y, uint32_t color) const
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;
158 // pixel dimensions of the image
159 int m_width, m_height;
161 vector<unsigned char> m_data;
164 typedef float (*ImageComparisonProc) (const Image&, const Image&);
166 float percentageDifferent(const Image& baseline, const Image& actual)
168 int w = min(baseline.width(), actual.width());
169 int h = min(baseline.height(), actual.height());
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))
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());
184 // ...pixels off the right side, but not including the lower right corner
185 pixelsDifferent += (maxWidth - w) * h;
187 // ...pixels along the bottom, including the lower right corner
188 pixelsDifferent += (maxHeight - h) * maxWidth;
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());
194 return 100.0f; // When the bitmap is empty, they are 100% different.
195 return static_cast<float>(pixelsDifferent) / totalPixels * 100;
198 inline uint32_t maxOf3(uint32_t a, uint32_t b, uint32_t c)
201 return std::max(b, c);
202 return std::max(a, c);
205 inline uint32_t getRedComponent(uint32_t color)
207 return (color << 24) >> 24;
210 inline uint32_t getGreenComponent(uint32_t color)
212 return (color << 16) >> 24;
215 inline uint32_t getBlueComponent(uint32_t color)
217 return (color << 8) >> 24;
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)
224 int w = min(baseline.width(), actual.width());
225 int h = min(baseline.height(), actual.height());
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;
251 int maxWidth = max(baseline.width(), actual.width());
252 int maxHeight = max(baseline.height(), actual.height());
254 weightedPixelsDifferent += (maxWidth - w) * h;
256 weightedPixelsDifferent += (maxHeight - h) * maxWidth;
258 float totalPixels = static_cast<float>(actual.width())
259 * static_cast<float>(actual.height());
262 return weightedPixelsDifferent / totalPixels * 100;
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"
281 /* For unfinished webkit-like-mode (see below)
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");
291 int compareImages(const char* file1, const char* file2,
292 ImageComparisonProc comparator)
297 if (!actualImage.createFromFilename(file1)) {
298 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
301 if (!baselineImage.createFromFilename(file2)) {
302 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
306 float percent = (*comparator)(actualImage, baselineImage);
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;
315 printf("diff: %01.2f%% passed\n", percent);
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)
327 while (fgets(buffer, sizeof(buffer), stdin)) {
328 if (!strncmp("Content-length: ", buffer, 16)) {
331 strtok_s(buffer, " ", &context);
332 int imageSize = strtol(strtok_s(0, " ", &context), 0, 10);
334 strtok_r(buffer, " ", &context);
335 int imageSize = strtol(strtok_r(0, " ", &context), 0, 10);
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);
344 } else if (imageSize > 0 && !baselineImage.hasImage()) {
345 if (!baselineImage.createFromStdin(imageSize)) {
346 fputs("Error, baseline image can't be decoded.\n", stderr);
350 fputs("Error, image size must be specified.\n", stderr);
355 if (actualImage.hasImage() && baselineImage.hasImage()) {
356 float percent = (*comparator)(actualImage, baselineImage);
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);
363 printf("diff: %01.2f%% passed\n", percent);
366 baselineImage.clear();
373 bool createImageDiff(const Image& image1, const Image& image2, Image* out)
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());
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);
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);
400 static bool writeFile(const char* outFile, const unsigned char* data, size_t dataSize)
402 FILE* file = fopen(outFile, "wb");
404 fprintf(stderr, "ImageDiff: Unable to create file \"%s\"\n", outFile);
407 if (dataSize != fwrite(data, 1, dataSize, file)) {
409 fprintf(stderr, "ImageDiff: Unable to write data to file \"%s\"\n", outFile);
416 int diffImages(const char* file1, const char* file2, const char* outFile,
417 bool shouldWritePercentages, ImageComparisonProc comparator)
422 if (!actualImage.createFromFilename(file1)) {
423 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
426 if (!baselineImage.createFromFilename(file2)) {
427 fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
432 bool same = createImageDiff(baselineImage, actualImage, &diffImage);
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()))
442 if (shouldWritePercentages) {
443 float percent = (*comparator)(actualImage, baselineImage);
444 fprintf(stdout, "%.3f\n", percent);
447 return statusDifferent;
450 int main(int argc, const char* argv[])
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))
460 else if (!strcmp(argv[i], optionGenerateDiff))
462 else if (!strcmp(argv[i], optionWrite))
463 shouldWritePercentages = true;
464 else if (!strcmp(argv[i], optionWeightedIntensity))
465 comparator = weightedPercentageDifferent;
467 values.append(argv[i]);
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)) {
481 // compareImages writes results to stdout unless an error occurred.
482 if (compareImages(firstName, stdinBuffer,
483 comparator) == statusError)
486 haveFirstName = false;
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;
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);