1/* 2 * Copyright (C) 2005, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2005 Ben La Monica <ben.lamonica@gmail.com>. All rights reserved. 4 * Copyright (C) 2011 Brent Fulgham. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 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 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR 19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28// FIXME: We need to be able to include these defines from a config.h somewhere. 29#define JS_EXPORT_PRIVATE 30#define WTF_EXPORT_PRIVATE 31 32#include <cairo.h> 33#include <stdio.h> 34#include <wtf/Platform.h> 35#include <wtf/RefPtr.h> 36#include <wtf/RetainPtr.h> 37 38#if PLATFORM(WIN) 39#include <fcntl.h> 40#include <io.h> 41#include <windows.h> 42#include <wtf/MathExtras.h> 43#endif 44 45using namespace std; 46 47static const int s_bufferSize = 2048; 48static const int s_bytesPerPixel = 4; 49static cairo_user_data_key_t s_imageDataKey; 50 51 52#if PLATFORM(WIN) 53#undef min 54#undef max 55 56static inline float strtof(const char* inputString, char** endptr) 57{ 58 return strtod(inputString, endptr); 59} 60#endif 61 62static cairo_status_t readFromData(void* closure, unsigned char* data, unsigned int length) 63{ 64 CFMutableDataRef dataSource = reinterpret_cast<CFMutableDataRef>(closure); 65 66 CFRange range = CFRangeMake(0, length); 67 CFDataGetBytes(dataSource, range, data); 68 CFDataDeleteBytes(dataSource, range); 69 70 return CAIRO_STATUS_SUCCESS; 71} 72 73static cairo_surface_t* createImageFromStdin(int bytesRemaining) 74{ 75 unsigned char buffer[s_bufferSize]; 76 RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(0, bytesRemaining)); 77 78 while (bytesRemaining > 0) { 79 size_t bytesToRead = min(bytesRemaining, s_bufferSize); 80 size_t bytesRead = fread(buffer, 1, bytesToRead, stdin); 81 CFDataAppendBytes(data.get(), buffer, static_cast<CFIndex>(bytesRead)); 82 bytesRemaining -= static_cast<int>(bytesRead); 83 } 84 85 return cairo_image_surface_create_from_png_stream (static_cast<cairo_read_func_t>(readFromData), data.get()); 86} 87 88static void releaseMallocBuffer(void* data) 89{ 90 free(data); 91} 92 93static inline float pixelDifference(float expected, float actual) 94{ 95 return (actual - expected) / max<float>(255 - expected, expected); 96} 97 98static inline void normalizeBuffer(float maxDistance, unsigned char* buffer, size_t length) 99{ 100 if (maxDistance >= 1) 101 return; 102 103 for (size_t p = 0; p < length; ++p) 104 buffer[p] /= maxDistance; 105} 106 107static cairo_surface_t* createDifferenceImage(cairo_surface_t* baselineImage, cairo_surface_t* actualImage, float& difference) 108{ 109 size_t width = cairo_image_surface_get_width(baselineImage); 110 size_t height = cairo_image_surface_get_height(baselineImage); 111 112 unsigned char* baselinePixel = cairo_image_surface_get_data(baselineImage); 113 unsigned char* actualPixel = cairo_image_surface_get_data(actualImage); 114 115 // Compare the content of the 2 bitmaps 116 void* diffBuffer = malloc(width * height); 117 unsigned char* diffPixel = reinterpret_cast<unsigned char*>(diffBuffer); 118 119 float count = 0; 120 float sum = 0; 121 float maxDistance = 0; 122 for (size_t y = 0; y < height; ++y) { 123 for (size_t x = 0; x < width; ++x) { 124 float red = pixelDifference(baselinePixel[0], actualPixel[0]); 125 float green = pixelDifference(baselinePixel[1], actualPixel[1]); 126 float blue = pixelDifference(baselinePixel[2], actualPixel[2]); 127 float alpha = pixelDifference(baselinePixel[3], actualPixel[3]); 128 129 float distance = sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0; 130 131 *diffPixel++ = static_cast<unsigned char>(distance * 255); 132 133 if (distance >= 1.0 / 255.0) { 134 ++count; 135 sum += distance; 136 if (distance > maxDistance) 137 maxDistance = distance; 138 } 139 140 baselinePixel += s_bytesPerPixel; 141 actualPixel += s_bytesPerPixel; 142 } 143 } 144 145 // Compute the difference as a percentage combining both the number of different pixels and their difference amount i.e. the average distance over the entire image 146 if (count > 0) 147 difference = 100.0f * sum / (height * width); 148 else 149 difference = 0; 150 151 if (!difference) { 152 free(diffBuffer); 153 return 0; 154 } 155 156 // Generate a normalized diff image 157 normalizeBuffer(maxDistance, reinterpret_cast<unsigned char*>(diffBuffer), height * width); 158 159 cairo_surface_t* diffImage = cairo_image_surface_create_for_data(diffPixel, CAIRO_FORMAT_ARGB32, width, height, width * s_bytesPerPixel); 160 cairo_surface_set_user_data(diffImage, &s_imageDataKey, diffBuffer, releaseMallocBuffer); 161 162 return diffImage; 163} 164 165static inline bool imageHasAlpha(cairo_surface_t* image) 166{ 167 return (cairo_image_surface_get_format(image) == CAIRO_FORMAT_ARGB32); 168} 169 170static cairo_status_t writeToData(void* closure, unsigned char* data, unsigned int length) 171{ 172 CFMutableDataRef dataTarget = reinterpret_cast<CFMutableDataRef>(closure); 173 174 CFDataAppendBytes(dataTarget, data, length); 175 176 return CAIRO_STATUS_SUCCESS; 177} 178 179int main(int argc, const char* argv[]) 180{ 181#if PLATFORM(WIN) 182 _setmode(0, _O_BINARY); 183 _setmode(1, _O_BINARY); 184#endif 185 186 float tolerance = 0; 187 188 for (int i = 1; i < argc; ++i) { 189 if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--tolerance")) { 190 if (i >= argc - 1) 191 exit(1); 192 tolerance = strtof(argv[i + 1], 0); 193 ++i; 194 continue; 195 } 196 } 197 198 char buffer[s_bufferSize]; 199 cairo_surface_t* actualImage = 0; 200 cairo_surface_t* baselineImage = 0; 201 202 while (fgets(buffer, sizeof(buffer), stdin)) { 203 char* newLineCharacter = strchr(buffer, '\n'); 204 if (newLineCharacter) 205 *newLineCharacter = '\0'; 206 207 if (!strncmp("Content-Length: ", buffer, 16)) { 208 strtok(buffer, " "); 209 int imageSize = strtol(strtok(0, " "), 0, 10); 210 211 if (imageSize > 0 && !actualImage) 212 actualImage = createImageFromStdin(imageSize); 213 else if (imageSize > 0 && !baselineImage) 214 baselineImage = createImageFromStdin(imageSize); 215 else 216 fputs("error, image size must be specified.\n", stdout); 217 } 218 219 if (actualImage && baselineImage) { 220 cairo_surface_t* diffImage = 0; 221 float difference = 100.0; 222 223 if ((cairo_image_surface_get_width(actualImage) == cairo_image_surface_get_width(baselineImage)) 224 && (cairo_image_surface_get_height(actualImage) == cairo_image_surface_get_height(baselineImage)) 225 && (imageHasAlpha(actualImage) == imageHasAlpha(baselineImage))) { 226 diffImage = createDifferenceImage(actualImage, baselineImage, difference); // difference is passed by reference 227 if (difference <= tolerance) 228 difference = 0; 229 else { 230 difference = roundf(difference * 100.0) / 100.0; 231 difference = max<float>(difference, 0.01); // round to 2 decimal places 232 } 233 } else 234 fputs("error, test and reference image have different properties.\n", stderr); 235 236 if (difference > 0.0) { 237 if (diffImage) { 238 RetainPtr<CFMutableDataRef> imageData(AdoptCF, CFDataCreateMutable(0, 0)); 239 cairo_surface_write_to_png_stream(diffImage, (cairo_write_func_t)writeToData, imageData.get()); 240 printf("Content-Length: %lu\n", CFDataGetLength(imageData.get())); 241 fwrite(CFDataGetBytePtr(imageData.get()), 1, CFDataGetLength(imageData.get()), stdout); 242 cairo_surface_destroy(diffImage); 243 diffImage = 0; 244 } 245 246 fprintf(stdout, "diff: %01.2f%% failed\n", difference); 247 } else 248 fprintf(stdout, "diff: %01.2f%% passed\n", difference); 249 250 cairo_surface_destroy(actualImage); 251 cairo_surface_destroy(baselineImage); 252 actualImage = 0; 253 baselineImage = 0; 254 } 255 256 fflush(stdout); 257 } 258 259 return 0; 260} 261