Home | History | Annotate | Download | only in imagediff
      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