WebKitTools/DumpRenderTree/gtk/ImageDiff.cpp
changeset 2 303757a437d3
parent 0 4f2f89ce4247
equal deleted inserted replaced
0:4f2f89ce4247 2:303757a437d3
     1 /*
       
     2  * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
       
     3  * Copyright (C) 2010 Igalia S.L.
       
     4  *
       
     5  * Redistribution and use in source and binary forms, with or without
       
     6  * modification, are permitted provided that the following conditions
       
     7  * are met:
       
     8  *
       
     9  * 1.  Redistributions of source code must retain the above copyright
       
    10  *     notice, this list of conditions and the following disclaimer.
       
    11  * 2.  Redistributions in binary form must reproduce the above copyright
       
    12  *     notice, this list of conditions and the following disclaimer in the
       
    13  *     documentation and/or other materials provided with the distribution.
       
    14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
       
    15  *     its contributors may be used to endorse or promote products derived
       
    16  *     from this software without specific prior written permission.
       
    17  *
       
    18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
       
    19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
    20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
       
    22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
       
    23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
       
    24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
       
    25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
       
    26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
       
    27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    28  */
       
    29 
       
    30 #include <algorithm>
       
    31 #include <cmath>
       
    32 #include <cstdio>
       
    33 #include <cstring>
       
    34 #include <gdk/gdk.h>
       
    35 
       
    36 using namespace std;
       
    37 
       
    38 static double tolerance = 0;
       
    39 static GOptionEntry commandLineOptionEntries[] = 
       
    40 {
       
    41     { "tolerance", 0, 0, G_OPTION_ARG_DOUBLE, &tolerance, "Percentage difference between images before considering them different", "T" },
       
    42     { 0, 0, 0, G_OPTION_ARG_NONE, 0, 0, 0 },
       
    43 };
       
    44 
       
    45 GdkPixbuf* readPixbufFromStdin(long imageSize)
       
    46 {
       
    47     unsigned char imageBuffer[2048];
       
    48     GdkPixbufLoader* loader = gdk_pixbuf_loader_new_with_type("png", 0);
       
    49     GError* error = 0;
       
    50 
       
    51     while (imageSize > 0) {
       
    52         size_t bytesToRead = min<int>(imageSize, 2048);
       
    53         size_t bytesRead = fread(imageBuffer, 1, bytesToRead, stdin);
       
    54 
       
    55         if (!gdk_pixbuf_loader_write(loader, reinterpret_cast<const guchar*>(imageBuffer), bytesRead, &error)) {
       
    56             g_error_free(error);
       
    57             gdk_pixbuf_loader_close(loader, 0);
       
    58             g_object_unref(loader);
       
    59             return 0;
       
    60         }
       
    61 
       
    62         imageSize -= static_cast<int>(bytesRead);
       
    63     }
       
    64 
       
    65     gdk_pixbuf_loader_close(loader, 0);
       
    66     GdkPixbuf* decodedImage = gdk_pixbuf_loader_get_pixbuf(loader);
       
    67     g_object_ref(decodedImage);
       
    68     return decodedImage;
       
    69 }
       
    70 
       
    71 GdkPixbuf* differenceImageFromDifferenceBuffer(unsigned char* buffer, int width, int height)
       
    72 {
       
    73     GdkPixbuf* image = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, width, height);
       
    74     if (!image)
       
    75         return image;
       
    76 
       
    77     int rowStride = gdk_pixbuf_get_rowstride(image);
       
    78     unsigned char* diffPixels = gdk_pixbuf_get_pixels(image);
       
    79     for (int x = 0; x < width; x++) {
       
    80         for (int y = 0; y < height; y++) {
       
    81             unsigned char* diffPixel = diffPixels + (y * rowStride) + (x * 3);
       
    82             diffPixel[0] = diffPixel[1] = diffPixel[2] = *buffer++;
       
    83         }
       
    84     }
       
    85 
       
    86     return image;
       
    87 }
       
    88 
       
    89 float calculateDifference(GdkPixbuf* baselineImage, GdkPixbuf* actualImage, GdkPixbuf** differenceImage)
       
    90 {
       
    91     int width = gdk_pixbuf_get_width(actualImage);
       
    92     int height = gdk_pixbuf_get_height(actualImage);
       
    93     int numberOfChannels = gdk_pixbuf_get_n_channels(actualImage);
       
    94     if ((width != gdk_pixbuf_get_width(baselineImage))
       
    95         || (height != gdk_pixbuf_get_height(baselineImage))
       
    96         || (numberOfChannels != gdk_pixbuf_get_n_channels(baselineImage))
       
    97         || (gdk_pixbuf_get_has_alpha(actualImage) != gdk_pixbuf_get_has_alpha(baselineImage))) {
       
    98         fprintf(stderr, "Error, test and reference image have different properties.\n");
       
    99         return 100; // Completely different.
       
   100     }
       
   101 
       
   102     unsigned char* diffBuffer = static_cast<unsigned char*>(malloc(width * height));
       
   103     float count = 0;
       
   104     float sum = 0;
       
   105     float maxDistance = 0;
       
   106     int actualRowStride = gdk_pixbuf_get_rowstride(actualImage);
       
   107     int baseRowStride = gdk_pixbuf_get_rowstride(baselineImage);
       
   108     unsigned char* actualPixels = gdk_pixbuf_get_pixels(actualImage);
       
   109     unsigned char* basePixels = gdk_pixbuf_get_pixels(baselineImage);
       
   110     unsigned char* currentDiffPixel = diffBuffer;
       
   111     for (int x = 0; x < width; x++) {
       
   112         for (int y = 0; y < height; y++) {
       
   113             unsigned char* actualPixel = actualPixels + (y * actualRowStride) + (x * numberOfChannels);
       
   114             unsigned char* basePixel = basePixels + (y * baseRowStride) + (x * numberOfChannels);
       
   115 
       
   116             float red = (actualPixel[0] - basePixel[0]) / max<float>(255 - basePixel[0], basePixel[0]);
       
   117             float green = (actualPixel[1] - basePixel[1]) / max<float>(255 - basePixel[1], basePixel[1]);
       
   118             float blue = (actualPixel[2] - basePixel[2]) / max<float>(255 - basePixel[2], basePixel[2]);
       
   119             float alpha = (actualPixel[3] - basePixel[3]) / max<float>(255 - basePixel[3], basePixel[3]);
       
   120             float distance = sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0f;
       
   121 
       
   122             *currentDiffPixel++ = (unsigned char)(distance * 255.0f);
       
   123 
       
   124             if (distance >= 1.0f / 255.0f) {
       
   125                 count += 1.0f;
       
   126                 sum += distance;
       
   127                 maxDistance = max<float>(maxDistance, distance);
       
   128             }
       
   129         }
       
   130     }
       
   131 
       
   132     // Compute the difference as a percentage combining both the number of
       
   133     // different pixels and their difference amount i.e. the average distance
       
   134     // over the entire image
       
   135     float difference = 0;
       
   136     if (count > 0.0f)
       
   137         difference = 100.0f * sum / (height * width);
       
   138     if (difference <= tolerance)
       
   139         difference = 0;
       
   140     else {
       
   141         difference = roundf(difference * 100.0f) / 100.0f;
       
   142         difference = max(difference, 0.01f); // round to 2 decimal places
       
   143         *differenceImage = differenceImageFromDifferenceBuffer(diffBuffer, width, height);
       
   144     }
       
   145 
       
   146     free(diffBuffer);
       
   147     return difference;
       
   148 }
       
   149 
       
   150 void printImage(GdkPixbuf* image)
       
   151 {
       
   152     char* buffer;
       
   153     gsize bufferSize;
       
   154     GError* error = 0;
       
   155     if (!gdk_pixbuf_save_to_buffer(image, &buffer, &bufferSize, "png", &error, NULL)) {
       
   156         g_error_free(error);
       
   157         return; // Don't bail out, as we can still use the percentage output.
       
   158     }
       
   159 
       
   160     printf("Content-Length: %"G_GSIZE_FORMAT"\n", bufferSize);
       
   161     fwrite(buffer, 1, bufferSize, stdout);
       
   162 }
       
   163 
       
   164 void printImageDifferences(GdkPixbuf* baselineImage, GdkPixbuf* actualImage)
       
   165 {
       
   166     GdkPixbuf* differenceImage = 0;
       
   167     float difference = calculateDifference(baselineImage, actualImage, &differenceImage);
       
   168     if (difference > 0.0f) {
       
   169         if (differenceImage) {
       
   170             printImage(differenceImage);
       
   171             g_object_unref(differenceImage);
       
   172         }
       
   173         printf("diff: %01.2f%% failed\n", difference);
       
   174     } else {
       
   175         printf("diff: %01.2f%% passed\n", difference);
       
   176     }
       
   177 }
       
   178 
       
   179 int main(int argc, char* argv[])
       
   180 {
       
   181     gdk_init(&argc, &argv);
       
   182 
       
   183     GError* error = 0;
       
   184     GOptionContext* context = g_option_context_new("- compare two image files, printing their percentage difference and the difference image to stdout");
       
   185     g_option_context_add_main_entries(context, commandLineOptionEntries, 0);
       
   186     if (!g_option_context_parse(context, &argc, &argv, &error)) {
       
   187         printf("Option parsing failed: %s\n", error->message);
       
   188         g_error_free(error);
       
   189         return 1;
       
   190     }
       
   191 
       
   192     GdkPixbuf* actualImage = 0;
       
   193     GdkPixbuf* baselineImage = 0;
       
   194     char buffer[2048];
       
   195     while (fgets(buffer, sizeof(buffer), stdin)) {
       
   196         // Convert the first newline into a NUL character so that strtok doesn't produce it.
       
   197         char* newLineCharacter = strchr(buffer, '\n');
       
   198         if (newLineCharacter)
       
   199             *newLineCharacter = '\0';
       
   200 
       
   201         if (!strncmp("Content-Length: ", buffer, 16)) {
       
   202             char* context;
       
   203             strtok_r(buffer, " ", &context);
       
   204             long imageSize = strtol(strtok_r(0, " ", &context), 0, 10);
       
   205 
       
   206             if (imageSize > 0 && !actualImage) {
       
   207                 if (!(actualImage = readPixbufFromStdin(imageSize))) {
       
   208                     printf("Error, could not read actual image.\n");
       
   209                     return 1;
       
   210                 }
       
   211             } else if (imageSize > 0 && !baselineImage) {
       
   212                 if (!(baselineImage = readPixbufFromStdin(imageSize))) {
       
   213                     printf("Error, could not read baseline image.\n");
       
   214                     return 1;
       
   215                 }
       
   216             } else {
       
   217                 printf("Error, image size must be specified..\n");
       
   218                 return 1;
       
   219             }
       
   220         }
       
   221 
       
   222         if (actualImage && baselineImage) {
       
   223             printImageDifferences(baselineImage, actualImage);
       
   224             g_object_unref(actualImage);
       
   225             g_object_unref(baselineImage);
       
   226             actualImage = 0;
       
   227             baselineImage = 0;
       
   228         }
       
   229 
       
   230         fflush(stdout);
       
   231     }
       
   232 
       
   233     return 0;
       
   234 }