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/file_util.h" 19 #include "base/files/file_path.h" 20 #include "base/logging.h" 21 #include "base/memory/scoped_ptr.h" 22 #include "base/process/memory.h" 23 #include "base/safe_numerics.h" 24 #include "base/strings/string_util.h" 25 #include "base/strings/utf_string_conversions.h" 26 #include "tools/imagediff/image_diff_png.h" 27 28 #if defined(OS_WIN) 29 #include "windows.h" 30 #endif 31 32 // Causes the app to remain open, waiting for pairs of filenames on stdin. 33 // The caller is then responsible for terminating this app. 34 static const char kOptionPollStdin[] = "use-stdin"; 35 static const char kOptionGenerateDiff[] = "diff"; 36 37 // Return codes used by this utility. 38 static const int kStatusSame = 0; 39 static const int kStatusDifferent = 1; 40 static const int kStatusError = 2; 41 42 // Color codes. 43 static const uint32 RGBA_RED = 0x000000ff; 44 static const uint32 RGBA_ALPHA = 0xff000000; 45 46 class Image { 47 public: 48 Image() : w_(0), h_(0) { 49 } 50 51 Image(const Image& image) 52 : w_(image.w_), 53 h_(image.h_), 54 data_(image.data_) { 55 } 56 57 bool has_image() const { 58 return w_ > 0 && h_ > 0; 59 } 60 61 int w() const { 62 return w_; 63 } 64 65 int h() const { 66 return h_; 67 } 68 69 const unsigned char* data() const { 70 return &data_.front(); 71 } 72 73 // Creates the image from stdin with the given data length. On success, it 74 // will return true. On failure, no other methods should be accessed. 75 bool CreateFromStdin(size_t byte_length) { 76 if (byte_length == 0) 77 return false; 78 79 scoped_ptr<unsigned char[]> source(new unsigned char[byte_length]); 80 if (fread(source.get(), 1, byte_length, stdin) != byte_length) 81 return false; 82 83 if (!image_diff_png::DecodePNG(source.get(), byte_length, 84 &data_, &w_, &h_)) { 85 Clear(); 86 return false; 87 } 88 return true; 89 } 90 91 // Creates the image from the given filename on disk, and returns true on 92 // success. 93 bool CreateFromFilename(const base::FilePath& path) { 94 FILE* f = file_util::OpenFile(path, "rb"); 95 if (!f) 96 return false; 97 98 std::vector<unsigned char> compressed; 99 const int buf_size = 1024; 100 unsigned char buf[buf_size]; 101 size_t num_read = 0; 102 while ((num_read = fread(buf, 1, buf_size, f)) > 0) { 103 compressed.insert(compressed.end(), buf, buf + num_read); 104 } 105 106 file_util::CloseFile(f); 107 108 if (!image_diff_png::DecodePNG(&compressed[0], compressed.size(), 109 &data_, &w_, &h_)) { 110 Clear(); 111 return false; 112 } 113 return true; 114 } 115 116 void Clear() { 117 w_ = h_ = 0; 118 data_.clear(); 119 } 120 121 // Returns the RGBA value of the pixel at the given location 122 uint32 pixel_at(int x, int y) const { 123 DCHECK(x >= 0 && x < w_); 124 DCHECK(y >= 0 && y < h_); 125 return *reinterpret_cast<const uint32*>(&(data_[(y * w_ + x) * 4])); 126 } 127 128 void set_pixel_at(int x, int y, uint32 color) const { 129 DCHECK(x >= 0 && x < w_); 130 DCHECK(y >= 0 && y < h_); 131 void* addr = &const_cast<unsigned char*>(&data_.front())[(y * w_ + x) * 4]; 132 *reinterpret_cast<uint32*>(addr) = color; 133 } 134 135 private: 136 // pixel dimensions of the image 137 int w_, h_; 138 139 std::vector<unsigned char> data_; 140 }; 141 142 float PercentageDifferent(const Image& baseline, const Image& actual) { 143 int w = std::min(baseline.w(), actual.w()); 144 int h = std::min(baseline.h(), actual.h()); 145 146 // compute pixels different in the overlap 147 int pixels_different = 0; 148 for (int y = 0; y < h; y++) { 149 for (int x = 0; x < w; x++) { 150 if (baseline.pixel_at(x, y) != actual.pixel_at(x, y)) 151 pixels_different++; 152 } 153 } 154 155 // count pixels that are a difference in size as also being different 156 int max_w = std::max(baseline.w(), actual.w()); 157 int max_h = std::max(baseline.h(), actual.h()); 158 159 // ...pixels off the right side, but not including the lower right corner 160 pixels_different += (max_w - w) * h; 161 162 // ...pixels along the bottom, including the lower right corner 163 pixels_different += (max_h - h) * max_w; 164 165 // Like the WebKit ImageDiff tool, we define percentage different in terms 166 // of the size of the 'actual' bitmap. 167 float total_pixels = static_cast<float>(actual.w()) * 168 static_cast<float>(actual.h()); 169 if (total_pixels == 0) 170 return 100.0f; // when the bitmap is empty, they are 100% different 171 return static_cast<float>(pixels_different) / total_pixels * 100; 172 } 173 174 void PrintHelp() { 175 fprintf(stderr, 176 "Usage:\n" 177 " image_diff <compare file> <reference file>\n" 178 " Compares two files on disk, returning 0 when they are the same\n" 179 " image_diff --use-stdin\n" 180 " Stays open reading pairs of filenames from stdin, comparing them,\n" 181 " and sending 0 to stdout when they are the same\n" 182 " image_diff --diff <compare file> <reference file> <output file>\n" 183 " Compares two files on disk, outputs an image that visualizes the" 184 " difference to <output file>\n"); 185 /* For unfinished webkit-like-mode (see below) 186 "\n" 187 " image_diff -s\n" 188 " Reads stream input from stdin, should be EXACTLY of the format\n" 189 " \"Content-length: <byte length> <data>Content-length: ...\n" 190 " it will take as many file pairs as given, and will compare them as\n" 191 " (cmp_file, reference_file) pairs\n"); 192 */ 193 } 194 195 int CompareImages(const base::FilePath& file1, const base::FilePath& file2) { 196 Image actual_image; 197 Image baseline_image; 198 199 if (!actual_image.CreateFromFilename(file1)) { 200 fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n", 201 file1.value().c_str()); 202 return kStatusError; 203 } 204 if (!baseline_image.CreateFromFilename(file2)) { 205 fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n", 206 file2.value().c_str()); 207 return kStatusError; 208 } 209 210 float percent = PercentageDifferent(actual_image, baseline_image); 211 if (percent > 0.0) { 212 // failure: The WebKit version also writes the difference image to 213 // stdout, which seems excessive for our needs. 214 printf("diff: %01.2f%% failed\n", percent); 215 return kStatusDifferent; 216 } 217 218 // success 219 printf("diff: %01.2f%% passed\n", percent); 220 return kStatusSame; 221 222 /* Untested mode that acts like WebKit's image comparator. I wrote this but 223 decided it's too complicated. We may use it in the future if it looks useful 224 225 char buffer[2048]; 226 while (fgets(buffer, sizeof(buffer), stdin)) { 227 228 if (strncmp("Content-length: ", buffer, 16) == 0) { 229 char* context; 230 strtok_s(buffer, " ", &context); 231 int image_size = strtol(strtok_s(NULL, " ", &context), NULL, 10); 232 233 bool success = false; 234 if (image_size > 0 && actual_image.has_image() == 0) { 235 if (!actual_image.CreateFromStdin(image_size)) { 236 fputs("Error, input image can't be decoded.\n", stderr); 237 return 1; 238 } 239 } else if (image_size > 0 && baseline_image.has_image() == 0) { 240 if (!baseline_image.CreateFromStdin(image_size)) { 241 fputs("Error, baseline image can't be decoded.\n", stderr); 242 return 1; 243 } 244 } else { 245 fputs("Error, image size must be specified.\n", stderr); 246 return 1; 247 } 248 } 249 250 if (actual_image.has_image() && baseline_image.has_image()) { 251 float percent = PercentageDifferent(actual_image, baseline_image); 252 if (percent > 0.0) { 253 // failure: The WebKit version also writes the difference image to 254 // stdout, which seems excessive for our needs. 255 printf("diff: %01.2f%% failed\n", percent); 256 } else { 257 // success 258 printf("diff: %01.2f%% passed\n", percent); 259 } 260 actual_image.Clear(); 261 baseline_image.Clear(); 262 } 263 264 fflush(stdout); 265 } 266 */ 267 } 268 269 bool CreateImageDiff(const Image& image1, const Image& image2, Image* out) { 270 int w = std::min(image1.w(), image2.w()); 271 int h = std::min(image1.h(), image2.h()); 272 *out = Image(image1); 273 bool same = (image1.w() == image2.w()) && (image1.h() == image2.h()); 274 275 // TODO(estade): do something with the extra pixels if the image sizes 276 // are different. 277 for (int y = 0; y < h; y++) { 278 for (int x = 0; x < w; x++) { 279 uint32 base_pixel = image1.pixel_at(x, y); 280 if (base_pixel != image2.pixel_at(x, y)) { 281 // Set differing pixels red. 282 out->set_pixel_at(x, y, RGBA_RED | RGBA_ALPHA); 283 same = false; 284 } else { 285 // Set same pixels as faded. 286 uint32 alpha = base_pixel & RGBA_ALPHA; 287 uint32 new_pixel = base_pixel - ((alpha / 2) & RGBA_ALPHA); 288 out->set_pixel_at(x, y, new_pixel); 289 } 290 } 291 } 292 293 return same; 294 } 295 296 int DiffImages(const base::FilePath& file1, const base::FilePath& file2, 297 const base::FilePath& out_file) { 298 Image actual_image; 299 Image baseline_image; 300 301 if (!actual_image.CreateFromFilename(file1)) { 302 fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n", 303 file1.value().c_str()); 304 return kStatusError; 305 } 306 if (!baseline_image.CreateFromFilename(file2)) { 307 fprintf(stderr, "image_diff: Unable to open file \"%" PRFilePath "\"\n", 308 file2.value().c_str()); 309 return kStatusError; 310 } 311 312 Image diff_image; 313 bool same = CreateImageDiff(baseline_image, actual_image, &diff_image); 314 if (same) 315 return kStatusSame; 316 317 std::vector<unsigned char> png_encoding; 318 image_diff_png::EncodeRGBAPNG( 319 diff_image.data(), diff_image.w(), diff_image.h(), 320 diff_image.w() * 4, &png_encoding); 321 if (file_util::WriteFile(out_file, 322 reinterpret_cast<char*>(&png_encoding.front()), 323 base::checked_numeric_cast<int>(png_encoding.size())) < 0) 324 return kStatusError; 325 326 return kStatusDifferent; 327 } 328 329 // It isn't strictly correct to only support ASCII paths, but this 330 // program reads paths on stdin and the program that spawns it outputs 331 // paths as non-wide strings anyway. 332 base::FilePath FilePathFromASCII(const std::string& str) { 333 #if defined(OS_WIN) 334 return base::FilePath(ASCIIToWide(str)); 335 #else 336 return base::FilePath(str); 337 #endif 338 } 339 340 int main(int argc, const char* argv[]) { 341 base::EnableTerminationOnHeapCorruption(); 342 CommandLine::Init(argc, argv); 343 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 344 if (parsed_command_line.HasSwitch(kOptionPollStdin)) { 345 // Watch stdin for filenames. 346 std::string stdin_buffer; 347 base::FilePath filename1; 348 while (std::getline(std::cin, stdin_buffer)) { 349 if (stdin_buffer.empty()) 350 continue; 351 352 if (!filename1.empty()) { 353 // CompareImages writes results to stdout unless an error occurred. 354 base::FilePath filename2 = FilePathFromASCII(stdin_buffer); 355 if (CompareImages(filename1, filename2) == kStatusError) 356 printf("error\n"); 357 fflush(stdout); 358 filename1 = base::FilePath(); 359 } else { 360 // Save the first filename in another buffer and wait for the second 361 // filename to arrive via stdin. 362 filename1 = FilePathFromASCII(stdin_buffer); 363 } 364 } 365 return 0; 366 } 367 368 const CommandLine::StringVector& args = parsed_command_line.GetArgs(); 369 if (parsed_command_line.HasSwitch(kOptionGenerateDiff)) { 370 if (args.size() == 3) { 371 return DiffImages(base::FilePath(args[0]), 372 base::FilePath(args[1]), 373 base::FilePath(args[2])); 374 } 375 } else if (args.size() == 2) { 376 return CompareImages(base::FilePath(args[0]), base::FilePath(args[1])); 377 } 378 379 PrintHelp(); 380 return kStatusError; 381 } 382