Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / tools / imagediff / image_diff.cc
blob608b7c96756ed49c96685851154f3a6c2b38c4a5
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.
5 // This file input format is based loosely on
6 // Tools/DumpRenderTree/ImageDiff.m
8 // The exact format of this tool's output to stdout is important, to match
9 // what the run-webkit-tests script expects.
11 #include <algorithm>
12 #include <iostream>
13 #include <string>
14 #include <vector>
16 #include "base/basictypes.h"
17 #include "base/command_line.h"
18 #include "base/containers/hash_tables.h"
19 #include "base/files/file_path.h"
20 #include "base/files/file_util.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"
29 #if defined(OS_WIN)
30 #include "windows.h"
31 #endif
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";
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;
47 // Color codes.
48 static const uint32 RGBA_RED = 0x000000ff;
49 static const uint32 RGBA_ALPHA = 0xff000000;
51 class Image {
52 public:
53 Image() : w_(0), h_(0) {
56 Image(const Image& image)
57 : w_(image.w_),
58 h_(image.h_),
59 data_(image.data_) {
62 bool has_image() const {
63 return w_ > 0 && h_ > 0;
66 int w() const {
67 return w_;
70 int h() const {
71 return h_;
74 const unsigned char* data() const {
75 return &data_.front();
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;
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;
88 if (!image_diff_png::DecodePNG(source.get(), byte_length,
89 &data_, &w_, &h_)) {
90 Clear();
91 return false;
93 return true;
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;
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);
111 base::CloseFile(f);
113 if (!image_diff_png::DecodePNG(&compressed[0], compressed.size(),
114 &data_, &w_, &h_)) {
115 Clear();
116 return false;
118 return true;
121 void Clear() {
122 w_ = h_ = 0;
123 data_.clear();
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]));
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;
140 private:
141 // pixel dimensions of the image
142 int w_, h_;
144 std::vector<unsigned char> data_;
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());
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++;
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;
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;
176 return 100.0f * pixels_different / total_pixels;
179 typedef base::hash_map<uint32, int32> RgbaToCountMap;
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
186 int w = std::min(baseline.w(), actual.w());
187 int h = std::min(baseline.h(), actual.h());
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)]++;
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++;
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;
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;
227 return 100.0f * pixels_different / total_pixels;
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");
253 int CompareImages(const base::FilePath& file1,
254 const base::FilePath& file2,
255 bool compare_histograms) {
256 Image actual_image;
257 Image baseline_image;
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;
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;
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);
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;
285 // success
286 return kStatusSame;
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
291 char buffer[2048];
292 while (fgets(buffer, sizeof(buffer), stdin)) {
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);
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;
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;
310 } else {
311 fputs("Error, image size must be specified.\n", stderr);
312 return 1;
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);
326 actual_image.Clear();
327 baseline_image.Clear();
330 fflush(stdout);
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());
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);
359 return same;
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;
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;
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;
378 Image diff_image;
379 bool same = CreateImageDiff(baseline_image, actual_image, &diff_image);
380 if (same)
381 return kStatusSame;
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;
392 return kStatusDifferent;
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::ASCIIToUTF16(str));
401 #else
402 return base::FilePath(str);
403 #endif
406 int main(int argc, const char* argv[]) {
407 base::EnableTerminationOnHeapCorruption();
408 base::CommandLine::Init(argc, argv);
409 const base::CommandLine& parsed_command_line =
410 *base::CommandLine::ForCurrentProcess();
411 bool histograms = parsed_command_line.HasSwitch(kOptionCompareHistograms);
412 if (parsed_command_line.HasSwitch(kOptionPollStdin)) {
413 // Watch stdin for filenames.
414 std::string stdin_buffer;
415 base::FilePath filename1;
416 while (std::getline(std::cin, stdin_buffer)) {
417 if (stdin_buffer.empty())
418 continue;
420 if (!filename1.empty()) {
421 // CompareImages writes results to stdout unless an error occurred.
422 base::FilePath filename2 = FilePathFromASCII(stdin_buffer);
423 if (CompareImages(filename1, filename2, histograms) == kStatusError)
424 printf("error\n");
425 fflush(stdout);
426 filename1 = base::FilePath();
427 } else {
428 // Save the first filename in another buffer and wait for the second
429 // filename to arrive via stdin.
430 filename1 = FilePathFromASCII(stdin_buffer);
433 return 0;
436 const base::CommandLine::StringVector& args = parsed_command_line.GetArgs();
437 if (parsed_command_line.HasSwitch(kOptionGenerateDiff)) {
438 if (args.size() == 3) {
439 return DiffImages(base::FilePath(args[0]),
440 base::FilePath(args[1]),
441 base::FilePath(args[2]));
443 } else if (args.size() == 2) {
444 return CompareImages(
445 base::FilePath(args[0]), base::FilePath(args[1]), histograms);
448 PrintHelp();
449 return kStatusError;