c7fad6adf13692b87aa281441a1ce75beb8a3df3
[platform/framework/web/crosswalk.git] / src / tools / imagediff / image_diff.cc
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // This file input format is based loosely on
6 // Tools/DumpRenderTree/ImageDiff.m
7
8 // The exact format of this tool's output to stdout is important, to match
9 // what the run-webkit-tests script expects.
10
11 #include <algorithm>
12 #include <iostream>
13 #include <string>
14 #include <vector>
15
16 #include "base/basictypes.h"
17 #include "base/command_line.h"
18 #include "base/containers/hash_tables.h"
19 #include "base/file_util.h"
20 #include "base/files/file_path.h"
21 #include "base/logging.h"
22 #include "base/memory/scoped_ptr.h"
23 #include "base/numerics/safe_conversions.h"
24 #include "base/process/memory.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/utf_string_conversions.h"
27 #include "tools/imagediff/image_diff_png.h"
28
29 #if defined(OS_WIN)
30 #include "windows.h"
31 #endif
32
33 // Causes the app to remain open, waiting for pairs of filenames on stdin.
34 // The caller is then responsible for terminating this app.
35 static const char kOptionPollStdin[] = "use-stdin";
36 // Causes the app to additionally calculate a diff of the color histograms
37 // (which is resistant to shifts in layout).
38 static const char kOptionCompareHistograms[] = "histogram";
39 // Causes the app to output an image that visualizes the difference.
40 static const char kOptionGenerateDiff[] = "diff";
41
42 // Return codes used by this utility.
43 static const int kStatusSame = 0;
44 static const int kStatusDifferent = 1;
45 static const int kStatusError = 2;
46
47 // Color codes.
48 static const uint32 RGBA_RED = 0x000000ff;
49 static const uint32 RGBA_ALPHA = 0xff000000;
50
51 class Image {
52  public:
53   Image() : w_(0), h_(0) {
54   }
55
56   Image(const Image& image)
57       : w_(image.w_),
58         h_(image.h_),
59         data_(image.data_) {
60   }
61
62   bool has_image() const {
63     return w_ > 0 && h_ > 0;
64   }
65
66   int w() const {
67     return w_;
68   }
69
70   int h() const {
71     return h_;
72   }
73
74   const unsigned char* data() const {
75     return &data_.front();
76   }
77
78   // Creates the image from stdin with the given data length. On success, it
79   // will return true. On failure, no other methods should be accessed.
80   bool CreateFromStdin(size_t byte_length) {
81     if (byte_length == 0)
82       return false;
83
84     scoped_ptr<unsigned char[]> source(new unsigned char[byte_length]);
85     if (fread(source.get(), 1, byte_length, stdin) != byte_length)
86       return false;
87
88     if (!image_diff_png::DecodePNG(source.get(), byte_length,
89                                    &data_, &w_, &h_)) {
90       Clear();
91       return false;
92     }
93     return true;
94   }
95
96   // Creates the image from the given filename on disk, and returns true on
97   // success.
98   bool CreateFromFilename(const base::FilePath& path) {
99     FILE* f = base::OpenFile(path, "rb");
100     if (!f)
101       return false;
102
103     std::vector<unsigned char> compressed;
104     const int buf_size = 1024;
105     unsigned char buf[buf_size];
106     size_t num_read = 0;
107     while ((num_read = fread(buf, 1, buf_size, f)) > 0) {
108       compressed.insert(compressed.end(), buf, buf + num_read);
109     }
110
111     base::CloseFile(f);
112
113     if (!image_diff_png::DecodePNG(&compressed[0], compressed.size(),
114                                    &data_, &w_, &h_)) {
115       Clear();
116       return false;
117     }
118     return true;
119   }
120
121   void Clear() {
122     w_ = h_ = 0;
123     data_.clear();
124   }
125
126   // Returns the RGBA value of the pixel at the given location
127   uint32 pixel_at(int x, int y) const {
128     DCHECK(x >= 0 && x < w_);
129     DCHECK(y >= 0 && y < h_);
130     return *reinterpret_cast<const uint32*>(&(data_[(y * w_ + x) * 4]));
131   }
132
133   void set_pixel_at(int x, int y, uint32 color) const {
134     DCHECK(x >= 0 && x < w_);
135     DCHECK(y >= 0 && y < h_);
136     void* addr = &const_cast<unsigned char*>(&data_.front())[(y * w_ + x) * 4];
137     *reinterpret_cast<uint32*>(addr) = color;
138   }
139
140  private:
141   // pixel dimensions of the image
142   int w_, h_;
143
144   std::vector<unsigned char> data_;
145 };
146
147 float PercentageDifferent(const Image& baseline, const Image& actual) {
148   int w = std::min(baseline.w(), actual.w());
149   int h = std::min(baseline.h(), actual.h());
150
151   // Compute pixels different in the overlap.
152   int pixels_different = 0;
153   for (int y = 0; y < h; y++) {
154     for (int x = 0; x < w; x++) {
155       if (baseline.pixel_at(x, y) != actual.pixel_at(x, y))
156         pixels_different++;
157     }
158   }
159
160   // Count pixels that are a difference in size as also being different.
161   int max_w = std::max(baseline.w(), actual.w());
162   int max_h = std::max(baseline.h(), actual.h());
163   // These pixels are off the right side, not including the lower right corner.
164   pixels_different += (max_w - w) * h;
165   // These pixels are along the bottom, including the lower right corner.
166   pixels_different += (max_h - h) * max_w;
167
168   // Like the WebKit ImageDiff tool, we define percentage different in terms
169   // of the size of the 'actual' bitmap.
170   float total_pixels = static_cast<float>(actual.w()) *
171                        static_cast<float>(actual.h());
172   if (total_pixels == 0) {
173     // When the bitmap is empty, they are 100% different.
174     return 100.0f;
175   }
176   return 100.0f * pixels_different / total_pixels;
177 }
178
179 typedef base::hash_map<uint32, int32> RgbaToCountMap;
180
181 float HistogramPercentageDifferent(const Image& baseline, const Image& actual) {
182   // TODO(johnme): Consider using a joint histogram instead, as described in
183   // "Comparing Images Using Joint Histograms" by Pass & Zabih
184   // http://www.cs.cornell.edu/~rdz/papers/pz-jms99.pdf
185
186   int w = std::min(baseline.w(), actual.w());
187   int h = std::min(baseline.h(), actual.h());
188
189   // Count occurences of each RGBA pixel value of baseline in the overlap.
190   RgbaToCountMap baseline_histogram;
191   for (int y = 0; y < h; y++) {
192     for (int x = 0; x < w; x++) {
193       // hash_map operator[] inserts a 0 (default constructor) if key not found.
194       baseline_histogram[baseline.pixel_at(x, y)]++;
195     }
196   }
197
198   // Compute pixels different in the histogram of the overlap.
199   int pixels_different = 0;
200   for (int y = 0; y < h; y++) {
201     for (int x = 0; x < w; x++) {
202       uint32 actual_rgba = actual.pixel_at(x, y);
203       RgbaToCountMap::iterator it = baseline_histogram.find(actual_rgba);
204       if (it != baseline_histogram.end() && it->second > 0)
205         it->second--;
206       else
207         pixels_different++;
208     }
209   }
210
211   // Count pixels that are a difference in size as also being different.
212   int max_w = std::max(baseline.w(), actual.w());
213   int max_h = std::max(baseline.h(), actual.h());
214   // These pixels are off the right side, not including the lower right corner.
215   pixels_different += (max_w - w) * h;
216   // These pixels are along the bottom, including the lower right corner.
217   pixels_different += (max_h - h) * max_w;
218
219   // Like the WebKit ImageDiff tool, we define percentage different in terms
220   // of the size of the 'actual' bitmap.
221   float total_pixels = static_cast<float>(actual.w()) *
222                        static_cast<float>(actual.h());
223   if (total_pixels == 0) {
224     // When the bitmap is empty, they are 100% different.
225     return 100.0f;
226   }
227   return 100.0f * pixels_different / total_pixels;
228 }
229
230 void PrintHelp() {
231   fprintf(stderr,
232     "Usage:\n"
233     "  image_diff [--histogram] <compare file> <reference file>\n"
234     "    Compares two files on disk, returning 0 when they are the same;\n"
235     "    passing \"--histogram\" additionally calculates a diff of the\n"
236     "    RGBA value histograms (which is resistant to shifts in layout)\n"
237     "  image_diff --use-stdin\n"
238     "    Stays open reading pairs of filenames from stdin, comparing them,\n"
239     "    and sending 0 to stdout when they are the same\n"
240     "  image_diff --diff <compare file> <reference file> <output file>\n"
241     "    Compares two files on disk, outputs an image that visualizes the\n"
242     "    difference to <output file>\n");
243   /* For unfinished webkit-like-mode (see below)
244     "\n"
245     "  image_diff -s\n"
246     "    Reads stream input from stdin, should be EXACTLY of the format\n"
247     "    \"Content-length: <byte length> <data>Content-length: ...\n"
248     "    it will take as many file pairs as given, and will compare them as\n"
249     "    (cmp_file, reference_file) pairs\n");
250   */
251 }
252
253 int CompareImages(const base::FilePath& file1,
254                   const base::FilePath& file2,
255                   bool compare_histograms) {
256   Image actual_image;
257   Image baseline_image;
258
259   if (!actual_image.CreateFromFilename(file1)) {
260     fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n",
261             file1.value().c_str());
262     return kStatusError;
263   }
264   if (!baseline_image.CreateFromFilename(file2)) {
265     fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n",
266             file2.value().c_str());
267     return kStatusError;
268   }
269
270   if (compare_histograms) {
271     float percent = HistogramPercentageDifferent(actual_image, baseline_image);
272     const char* passed = percent > 0.0 ? "failed" : "passed";
273     printf("histogram diff: %01.2f%% %s\n", percent, passed);
274   }
275
276   const char* diff_name = compare_histograms ? "exact diff" : "diff";
277   float percent = PercentageDifferent(actual_image, baseline_image);
278   const char* passed = percent > 0.0 ? "failed" : "passed";
279   printf("%s: %01.2f%% %s\n", diff_name, percent, passed);
280   if (percent > 0.0) {
281     // failure: The WebKit version also writes the difference image to
282     // stdout, which seems excessive for our needs.
283     return kStatusDifferent;
284   }
285   // success
286   return kStatusSame;
287
288 /* Untested mode that acts like WebKit's image comparator. I wrote this but
289    decided it's too complicated. We may use it in the future if it looks useful
290
291   char buffer[2048];
292   while (fgets(buffer, sizeof(buffer), stdin)) {
293
294     if (strncmp("Content-length: ", buffer, 16) == 0) {
295       char* context;
296       strtok_s(buffer, " ", &context);
297       int image_size = strtol(strtok_s(NULL, " ", &context), NULL, 10);
298
299       bool success = false;
300       if (image_size > 0 && actual_image.has_image() == 0) {
301         if (!actual_image.CreateFromStdin(image_size)) {
302           fputs("Error, input image can't be decoded.\n", stderr);
303           return 1;
304         }
305       } else if (image_size > 0 && baseline_image.has_image() == 0) {
306         if (!baseline_image.CreateFromStdin(image_size)) {
307           fputs("Error, baseline image can't be decoded.\n", stderr);
308           return 1;
309         }
310       } else {
311         fputs("Error, image size must be specified.\n", stderr);
312         return 1;
313       }
314     }
315
316     if (actual_image.has_image() && baseline_image.has_image()) {
317       float percent = PercentageDifferent(actual_image, baseline_image);
318       if (percent > 0.0) {
319         // failure: The WebKit version also writes the difference image to
320         // stdout, which seems excessive for our needs.
321         printf("diff: %01.2f%% failed\n", percent);
322       } else {
323         // success
324         printf("diff: %01.2f%% passed\n", percent);
325       }
326       actual_image.Clear();
327       baseline_image.Clear();
328     }
329
330     fflush(stdout);
331   }
332 */
333 }
334
335 bool CreateImageDiff(const Image& image1, const Image& image2, Image* out) {
336   int w = std::min(image1.w(), image2.w());
337   int h = std::min(image1.h(), image2.h());
338   *out = Image(image1);
339   bool same = (image1.w() == image2.w()) && (image1.h() == image2.h());
340
341   // TODO(estade): do something with the extra pixels if the image sizes
342   // are different.
343   for (int y = 0; y < h; y++) {
344     for (int x = 0; x < w; x++) {
345       uint32 base_pixel = image1.pixel_at(x, y);
346       if (base_pixel != image2.pixel_at(x, y)) {
347         // Set differing pixels red.
348         out->set_pixel_at(x, y, RGBA_RED | RGBA_ALPHA);
349         same = false;
350       } else {
351         // Set same pixels as faded.
352         uint32 alpha = base_pixel & RGBA_ALPHA;
353         uint32 new_pixel = base_pixel - ((alpha / 2) & RGBA_ALPHA);
354         out->set_pixel_at(x, y, new_pixel);
355       }
356     }
357   }
358
359   return same;
360 }
361
362 int DiffImages(const base::FilePath& file1, const base::FilePath& file2,
363                const base::FilePath& out_file) {
364   Image actual_image;
365   Image baseline_image;
366
367   if (!actual_image.CreateFromFilename(file1)) {
368     fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n",
369             file1.value().c_str());
370     return kStatusError;
371   }
372   if (!baseline_image.CreateFromFilename(file2)) {
373     fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n",
374             file2.value().c_str());
375     return kStatusError;
376   }
377
378   Image diff_image;
379   bool same = CreateImageDiff(baseline_image, actual_image, &diff_image);
380   if (same)
381     return kStatusSame;
382
383   std::vector<unsigned char> png_encoding;
384   image_diff_png::EncodeRGBAPNG(
385       diff_image.data(), diff_image.w(), diff_image.h(),
386       diff_image.w() * 4, &png_encoding);
387   if (base::WriteFile(out_file,
388           reinterpret_cast<char*>(&png_encoding.front()),
389           base::checked_cast<int>(png_encoding.size())) < 0)
390     return kStatusError;
391
392   return kStatusDifferent;
393 }
394
395 // It isn't strictly correct to only support ASCII paths, but this
396 // program reads paths on stdin and the program that spawns it outputs
397 // paths as non-wide strings anyway.
398 base::FilePath FilePathFromASCII(const std::string& str) {
399 #if defined(OS_WIN)
400   return base::FilePath(base::ASCIIToWide(str));
401 #else
402   return base::FilePath(str);
403 #endif
404 }
405
406 int main(int argc, const char* argv[]) {
407   base::EnableTerminationOnHeapCorruption();
408   CommandLine::Init(argc, argv);
409   const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
410   bool histograms = parsed_command_line.HasSwitch(kOptionCompareHistograms);
411   if (parsed_command_line.HasSwitch(kOptionPollStdin)) {
412     // Watch stdin for filenames.
413     std::string stdin_buffer;
414     base::FilePath filename1;
415     while (std::getline(std::cin, stdin_buffer)) {
416       if (stdin_buffer.empty())
417         continue;
418
419       if (!filename1.empty()) {
420         // CompareImages writes results to stdout unless an error occurred.
421         base::FilePath filename2 = FilePathFromASCII(stdin_buffer);
422         if (CompareImages(filename1, filename2, histograms) == kStatusError)
423           printf("error\n");
424         fflush(stdout);
425         filename1 = base::FilePath();
426       } else {
427         // Save the first filename in another buffer and wait for the second
428         // filename to arrive via stdin.
429         filename1 = FilePathFromASCII(stdin_buffer);
430       }
431     }
432     return 0;
433   }
434
435   const CommandLine::StringVector& args = parsed_command_line.GetArgs();
436   if (parsed_command_line.HasSwitch(kOptionGenerateDiff)) {
437     if (args.size() == 3) {
438       return DiffImages(base::FilePath(args[0]),
439                         base::FilePath(args[1]),
440                         base::FilePath(args[2]));
441     }
442   } else if (args.size() == 2) {
443     return CompareImages(
444         base::FilePath(args[0]), base::FilePath(args[1]), histograms);
445   }
446
447   PrintHelp();
448   return kStatusError;
449 }