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