Home | History | Annotate | Download | only in chromium
      1 /*
      2  * Copyright (C) 2010 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      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
     13  * distribution.
     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.
     17  *
     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.
     29  */
     30 
     31 // This file input format is based loosely on
     32 // WebKitTools/DumpRenderTree/ImageDiff.m
     33 
     34 // The exact format of this tool's output to stdout is important, to match
     35 // what the run-webkit-tests script expects.
     36 
     37 #include "config.h"
     38 
     39 #include "webkit/support/webkit_support_gfx.h"
     40 #include <algorithm>
     41 #include <stdio.h>
     42 #include <string.h>
     43 #include <vector>
     44 #include <wtf/OwnArrayPtr.h>
     45 #include <wtf/Vector.h>
     46 
     47 #if OS(WINDOWS)
     48 #include <windows.h>
     49 #define PATH_MAX MAX_PATH
     50 #endif
     51 
     52 using namespace std;
     53 
     54 // Causes the app to remain open, waiting for pairs of filenames on stdin.
     55 // The caller is then responsible for terminating this app.
     56 static const char optionPollStdin[] = "--use-stdin";
     57 static const char optionGenerateDiff[] = "--diff";
     58 
     59 // Return codes used by this utility.
     60 static const int statusSame = 0;
     61 static const int statusDifferent = 1;
     62 static const int statusError = 2;
     63 
     64 // Color codes.
     65 static const uint32_t rgbaRed = 0x000000ff;
     66 static const uint32_t rgbaAlpha = 0xff000000;
     67 
     68 class Image {
     69 public:
     70     Image()
     71         : m_width(0)
     72         , m_height(0) {}
     73 
     74     Image(const Image& image)
     75         : m_width(image.m_width)
     76         , m_height(image.m_height)
     77         , m_data(image.m_data) {}
     78 
     79     bool hasImage() const { return m_width > 0 && m_height > 0; }
     80     int width() const { return m_width; }
     81     int height() const { return m_height; }
     82     const unsigned char* data() const { return &m_data.front(); }
     83 
     84     // Creates the image from stdin with the given data length. On success, it
     85     // will return true. On failure, no other methods should be accessed.
     86     bool craeteFromStdin(size_t byteLength)
     87     {
     88         if (!byteLength)
     89             return false;
     90 
     91         OwnArrayPtr<unsigned char> source = adoptArrayPtr(new unsigned char[byteLength]);
     92         if (fread(source.get(), 1, byteLength, stdin) != byteLength)
     93             return false;
     94 
     95         if (!webkit_support::DecodePNG(source.get(), byteLength, &m_data, &m_width, &m_height)) {
     96             clear();
     97             return false;
     98         }
     99         return true;
    100     }
    101 
    102     // Creates the image from the given filename on disk, and returns true on
    103     // success.
    104     bool createFromFilename(const char* filename)
    105     {
    106         FILE* f = fopen(filename, "rb");
    107         if (!f)
    108             return false;
    109 
    110         vector<unsigned char> compressed;
    111         const int bufSize = 1024;
    112         unsigned char buf[bufSize];
    113         size_t numRead = 0;
    114         while ((numRead = fread(buf, 1, bufSize, f)) > 0)
    115             std::copy(buf, &buf[numRead], std::back_inserter(compressed));
    116 
    117         fclose(f);
    118 
    119         if (!webkit_support::DecodePNG(&compressed[0], compressed.size(), &m_data, &m_width, &m_height)) {
    120             clear();
    121             return false;
    122         }
    123         return true;
    124     }
    125 
    126     void clear()
    127     {
    128         m_width = m_height = 0;
    129         m_data.clear();
    130     }
    131 
    132     // Returns the RGBA value of the pixel at the given location
    133     const uint32_t pixelAt(int x, int y) const
    134     {
    135         ASSERT(x >= 0 && x < m_width);
    136         ASSERT(y >= 0 && y < m_height);
    137         return *reinterpret_cast<const uint32_t*>(&(m_data[(y * m_width + x) * 4]));
    138     }
    139 
    140     void setPixelAt(int x, int y, uint32_t color) const
    141     {
    142         ASSERT(x >= 0 && x < m_width);
    143         ASSERT(y >= 0 && y < m_height);
    144         void* addr = &const_cast<unsigned char*>(&m_data.front())[(y * m_width + x) * 4];
    145         *reinterpret_cast<uint32_t*>(addr) = color;
    146     }
    147 
    148 private:
    149     // pixel dimensions of the image
    150     int m_width, m_height;
    151 
    152     vector<unsigned char> m_data;
    153 };
    154 
    155 float percentageDifferent(const Image& baseline, const Image& actual)
    156 {
    157     int w = min(baseline.width(), actual.width());
    158     int h = min(baseline.height(), actual.height());
    159 
    160     // Compute pixels different in the overlap
    161     int pixelsDifferent = 0;
    162     for (int y = 0; y < h; y++) {
    163         for (int x = 0; x < w; x++) {
    164             if (baseline.pixelAt(x, y) != actual.pixelAt(x, y))
    165                 pixelsDifferent++;
    166         }
    167     }
    168 
    169     // Count pixels that are a difference in size as also being different
    170     int maxWidth = max(baseline.width(), actual.width());
    171     int maxHeight = max(baseline.height(), actual.height());
    172 
    173     // ...pixels off the right side, but not including the lower right corner
    174     pixelsDifferent += (maxWidth - w) * h;
    175 
    176     // ...pixels along the bottom, including the lower right corner
    177     pixelsDifferent += (maxHeight - h) * maxWidth;
    178 
    179     // Like the WebKit ImageDiff tool, we define percentage different in terms
    180     // of the size of the 'actual' bitmap.
    181     float totalPixels = static_cast<float>(actual.width()) * static_cast<float>(actual.height());
    182     if (!totalPixels)
    183         return 100.0f; // When the bitmap is empty, they are 100% different.
    184     return static_cast<float>(pixelsDifferent) / totalPixels * 100;
    185 }
    186 
    187 void printHelp()
    188 {
    189     fprintf(stderr,
    190             "Usage:\n"
    191             "  ImageDiff <compare file> <reference file>\n"
    192             "    Compares two files on disk, returning 0 when they are the same\n"
    193             "  ImageDiff --use-stdin\n"
    194             "    Stays open reading pairs of filenames from stdin, comparing them,\n"
    195             "    and sending 0 to stdout when they are the same\n"
    196             "  ImageDiff --diff <compare file> <reference file> <output file>\n"
    197             "    Compares two files on disk, outputs an image that visualizes the"
    198             "    difference to <output file>\n");
    199     /* For unfinished webkit-like-mode (see below)
    200        "\n"
    201        "  ImageDiff -s\n"
    202        "    Reads stream input from stdin, should be EXACTLY of the format\n"
    203        "    \"Content-length: <byte length> <data>Content-length: ...\n"
    204        "    it will take as many file pairs as given, and will compare them as\n"
    205        "    (cmp_file, reference_file) pairs\n");
    206     */
    207 }
    208 
    209 int compareImages(const char* file1, const char* file2)
    210 {
    211     Image actualImage;
    212     Image baselineImage;
    213 
    214     if (!actualImage.createFromFilename(file1)) {
    215         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
    216         return statusError;
    217     }
    218     if (!baselineImage.createFromFilename(file2)) {
    219         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
    220         return statusError;
    221     }
    222 
    223     float percent = percentageDifferent(actualImage, baselineImage);
    224     if (percent > 0.0) {
    225         // failure: The WebKit version also writes the difference image to
    226         // stdout, which seems excessive for our needs.
    227         printf("diff: %01.2f%% failed\n", percent);
    228         return statusDifferent;
    229     }
    230 
    231     // success
    232     printf("diff: %01.2f%% passed\n", percent);
    233     return statusSame;
    234 
    235 }
    236 
    237 // Untested mode that acts like WebKit's image comparator. I wrote this but
    238 // decided it's too complicated. We may use it in the future if it looks useful.
    239 int untestedCompareImages()
    240 {
    241     Image actualImage;
    242     Image baselineImage;
    243     char buffer[2048];
    244     while (fgets(buffer, sizeof(buffer), stdin)) {
    245         if (!strncmp("Content-length: ", buffer, 16)) {
    246             char* context;
    247 #if OS(WINDOWS)
    248             strtok_s(buffer, " ", &context);
    249             int imageSize = strtol(strtok_s(0, " ", &context), 0, 10);
    250 #else
    251             strtok_r(buffer, " ", &context);
    252             int imageSize = strtol(strtok_r(0, " ", &context), 0, 10);
    253 #endif
    254 
    255             bool success = false;
    256             if (imageSize > 0 && !actualImage.hasImage()) {
    257                 if (!actualImage.craeteFromStdin(imageSize)) {
    258                     fputs("Error, input image can't be decoded.\n", stderr);
    259                     return 1;
    260                 }
    261             } else if (imageSize > 0 && !baselineImage.hasImage()) {
    262                 if (!baselineImage.craeteFromStdin(imageSize)) {
    263                     fputs("Error, baseline image can't be decoded.\n", stderr);
    264                     return 1;
    265                 }
    266             } else {
    267                 fputs("Error, image size must be specified.\n", stderr);
    268                 return 1;
    269             }
    270         }
    271 
    272         if (actualImage.hasImage() && baselineImage.hasImage()) {
    273             float percent = percentageDifferent(actualImage, baselineImage);
    274             if (percent > 0.0) {
    275                 // failure: The WebKit version also writes the difference image to
    276                 // stdout, which seems excessive for our needs.
    277                 printf("diff: %01.2f%% failed\n", percent);
    278             } else {
    279                 // success
    280                 printf("diff: %01.2f%% passed\n", percent);
    281             }
    282             actualImage.clear();
    283             baselineImage.clear();
    284         }
    285         fflush(stdout);
    286     }
    287     return 0;
    288 }
    289 
    290 bool createImageDiff(const Image& image1, const Image& image2, Image* out)
    291 {
    292     int w = min(image1.width(), image2.width());
    293     int h = min(image1.height(), image2.height());
    294     *out = Image(image1);
    295     bool same = (image1.width() == image2.width()) && (image1.height() == image2.height());
    296 
    297     // FIXME: do something with the extra pixels if the image sizes are different.
    298     for (int y = 0; y < h; y++) {
    299         for (int x = 0; x < w; x++) {
    300             uint32_t basePixel = image1.pixelAt(x, y);
    301             if (basePixel != image2.pixelAt(x, y)) {
    302                 // Set differing pixels red.
    303                 out->setPixelAt(x, y, rgbaRed | rgbaAlpha);
    304                 same = false;
    305             } else {
    306                 // Set same pixels as faded.
    307                 uint32_t alpha = basePixel & rgbaAlpha;
    308                 uint32_t newPixel = basePixel - ((alpha / 2) & rgbaAlpha);
    309                 out->setPixelAt(x, y, newPixel);
    310             }
    311         }
    312     }
    313 
    314     return same;
    315 }
    316 
    317 static bool writeFile(const char* outFile, const unsigned char* data, size_t dataSize)
    318 {
    319     FILE* file = fopen(outFile, "wb");
    320     if (!file) {
    321         fprintf(stderr, "ImageDiff: Unable to create file \"%s\"\n", outFile);
    322         return false;
    323     }
    324     if (dataSize != fwrite(data, 1, dataSize, file)) {
    325         fclose(file);
    326         fprintf(stderr, "ImageDiff: Unable to write data to file \"%s\"\n", outFile);
    327         return false;
    328     }
    329     fclose(file);
    330     return true;
    331 }
    332 
    333 int diffImages(const char* file1, const char* file2, const char* outFile)
    334 {
    335     Image actualImage;
    336     Image baselineImage;
    337 
    338     if (!actualImage.createFromFilename(file1)) {
    339         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file1);
    340         return statusError;
    341     }
    342     if (!baselineImage.createFromFilename(file2)) {
    343         fprintf(stderr, "ImageDiff: Unable to open file \"%s\"\n", file2);
    344         return statusError;
    345     }
    346 
    347     Image diffImage;
    348     bool same = createImageDiff(baselineImage, actualImage, &diffImage);
    349     if (same)
    350         return statusSame;
    351 
    352     vector<unsigned char> pngData;
    353     webkit_support::EncodeRGBAPNG(diffImage.data(), diffImage.width(), diffImage.height(),
    354                                   diffImage.width() * 4, &pngData);
    355     if (!writeFile(outFile, &pngData.front(), pngData.size()))
    356         return statusError;
    357     return statusDifferent;
    358 }
    359 
    360 int main(int argc, const char* argv[])
    361 {
    362     Vector<const char*> values;
    363     bool pollStdin = false;
    364     bool generateDiff = false;
    365     for (int i = 1; i < argc; ++i) {
    366         if (!strcmp(argv[i], optionPollStdin))
    367             pollStdin = true;
    368         else if (!strcmp(argv[i], optionGenerateDiff))
    369             generateDiff = true;
    370         else
    371             values.append(argv[i]);
    372     }
    373 
    374     if (pollStdin) {
    375         // Watch stdin for filenames.
    376         const size_t bufferSize = PATH_MAX;
    377         char stdinBuffer[bufferSize];
    378         char firstName[bufferSize];
    379         bool haveFirstName = false;
    380         while (fgets(stdinBuffer, bufferSize, stdin)) {
    381             if (!stdinBuffer[0])
    382                 continue;
    383 
    384             if (haveFirstName) {
    385                 // compareImages writes results to stdout unless an error occurred.
    386                 if (compareImages(firstName, stdinBuffer) == statusError)
    387                     printf("error\n");
    388                 fflush(stdout);
    389                 haveFirstName = false;
    390             } else {
    391                 // Save the first filename in another buffer and wait for the second
    392                 // filename to arrive via stdin.
    393                 strcpy(firstName, stdinBuffer);
    394                 haveFirstName = true;
    395             }
    396         }
    397         return 0;
    398     }
    399 
    400     if (generateDiff) {
    401         if (values.size() == 3)
    402             return diffImages(values[0], values[1], values[2]);
    403     } else if (values.size() == 2)
    404         return compareImages(argv[1], argv[2]);
    405 
    406     printHelp();
    407     return statusError;
    408 }
    409