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 36using namespace std; 37 38static double tolerance = 0; 39static 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 45GdkPixbuf* 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 71GdkPixbuf* 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 89float 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 150void 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 164void 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 179int 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 gchar** tokens = g_strsplit(buffer, " ", 0); 203 if (!tokens[1]) { 204 g_strfreev(tokens); 205 printf("Error, image size must be specified..\n"); 206 return 1; 207 } 208 209 long imageSize = strtol(tokens[1], 0, 10); 210 g_strfreev(tokens); 211 if (imageSize > 0 && !actualImage) { 212 if (!(actualImage = readPixbufFromStdin(imageSize))) { 213 printf("Error, could not read actual image.\n"); 214 return 1; 215 } 216 } else if (imageSize > 0 && !baselineImage) { 217 if (!(baselineImage = readPixbufFromStdin(imageSize))) { 218 printf("Error, could not read baseline image.\n"); 219 return 1; 220 } 221 } else { 222 printf("Error, image size must be specified..\n"); 223 return 1; 224 } 225 } 226 227 if (actualImage && baselineImage) { 228 printImageDifferences(baselineImage, actualImage); 229 g_object_unref(actualImage); 230 g_object_unref(baselineImage); 231 actualImage = 0; 232 baselineImage = 0; 233 } 234 235 fflush(stdout); 236 } 237 238 return 0; 239} 240