ImageDiffCG.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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. *
  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. * 1. Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * 2. Redistributions in binary form must reproduce the above copyright
  11. * notice, this list of conditions and the following disclaimer in the
  12. * documentation and/or other materials provided with the distribution.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
  15. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  17. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
  18. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  19. * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  20. * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  21. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  22. * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #if defined(WIN32) || defined(_WIN32)
  27. #define max max
  28. #define min min
  29. #endif
  30. // FIXME: We need to be able to include these defines from a config.h somewhere.
  31. #define JS_EXPORT_PRIVATE
  32. #define WTF_EXPORT_PRIVATE
  33. #include <stdio.h>
  34. #include <wtf/Platform.h>
  35. #include <wtf/RetainPtr.h>
  36. #if PLATFORM(WIN)
  37. #include <winsock2.h>
  38. #include <windows.h>
  39. #include <fcntl.h>
  40. #include <io.h>
  41. #include <wtf/MathExtras.h>
  42. #endif
  43. #include <CoreGraphics/CGBitmapContext.h>
  44. #include <CoreGraphics/CGImage.h>
  45. #include <ImageIO/CGImageDestination.h>
  46. #if PLATFORM(MAC)
  47. #include <LaunchServices/UTCoreTypes.h>
  48. #endif
  49. #ifndef CGFLOAT_DEFINED
  50. #ifdef __LP64__
  51. typedef double CGFloat;
  52. #else
  53. typedef float CGFloat;
  54. #endif
  55. #define CGFLOAT_DEFINED 1
  56. #endif
  57. using namespace std;
  58. #if PLATFORM(WIN)
  59. static inline float strtof(const char *nptr, char **endptr)
  60. {
  61. return strtod(nptr, endptr);
  62. }
  63. static const CFStringRef kUTTypePNG = CFSTR("public.png");
  64. #endif
  65. static RetainPtr<CGImageRef> createImageFromStdin(int bytesRemaining)
  66. {
  67. unsigned char buffer[2048];
  68. RetainPtr<CFMutableDataRef> data = adoptCF(CFDataCreateMutable(0, bytesRemaining));
  69. while (bytesRemaining > 0) {
  70. size_t bytesToRead = min(bytesRemaining, 2048);
  71. size_t bytesRead = fread(buffer, 1, bytesToRead, stdin);
  72. CFDataAppendBytes(data.get(), buffer, static_cast<CFIndex>(bytesRead));
  73. bytesRemaining -= static_cast<int>(bytesRead);
  74. }
  75. RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData(data.get()));
  76. return adoptCF(CGImageCreateWithPNGDataProvider(dataProvider.get(), 0, false, kCGRenderingIntentDefault));
  77. }
  78. static void releaseMallocBuffer(void* info, const void* data, size_t size)
  79. {
  80. free((void*)data);
  81. }
  82. static RetainPtr<CGImageRef> createDifferenceImage(CGImageRef baseImage, CGImageRef testImage, float& difference)
  83. {
  84. size_t width = CGImageGetWidth(baseImage);
  85. size_t height = CGImageGetHeight(baseImage);
  86. size_t rowBytes = width * 4;
  87. // Draw base image in bitmap context
  88. void* baseBuffer = calloc(height, rowBytes);
  89. RetainPtr<CGContextRef> baseContext = adoptCF(CGBitmapContextCreate(baseBuffer, width, height, 8, rowBytes, CGImageGetColorSpace(baseImage), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
  90. CGContextDrawImage(baseContext.get(), CGRectMake(0, 0, width, height), baseImage);
  91. // Draw test image in bitmap context
  92. void* buffer = calloc(height, rowBytes);
  93. RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(buffer, width, height, 8, rowBytes, CGImageGetColorSpace(testImage), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
  94. CGContextDrawImage(context.get(), CGRectMake(0, 0, width, height), testImage);
  95. // Compare the content of the 2 bitmaps
  96. void* diffBuffer = malloc(width * height);
  97. float count = 0.0f;
  98. float sum = 0.0f;
  99. float maxDistance = 0.0f;
  100. unsigned char* basePixel = (unsigned char*)baseBuffer;
  101. unsigned char* pixel = (unsigned char*)buffer;
  102. unsigned char* diff = (unsigned char*)diffBuffer;
  103. for (size_t y = 0; y < height; ++y) {
  104. for (size_t x = 0; x < width; ++x) {
  105. float red = (pixel[0] - basePixel[0]) / max<float>(255 - basePixel[0], basePixel[0]);
  106. float green = (pixel[1] - basePixel[1]) / max<float>(255 - basePixel[1], basePixel[1]);
  107. float blue = (pixel[2] - basePixel[2]) / max<float>(255 - basePixel[2], basePixel[2]);
  108. float alpha = (pixel[3] - basePixel[3]) / max<float>(255 - basePixel[3], basePixel[3]);
  109. float distance = sqrtf(red * red + green * green + blue * blue + alpha * alpha) / 2.0f;
  110. *diff++ = (unsigned char)(distance * 255.0f);
  111. if (distance >= 1.0f / 255.0f) {
  112. count += 1.0f;
  113. sum += distance;
  114. if (distance > maxDistance)
  115. maxDistance = distance;
  116. }
  117. basePixel += 4;
  118. pixel += 4;
  119. }
  120. }
  121. // 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
  122. if (count > 0.0f)
  123. difference = 100.0f * sum / (height * width);
  124. else
  125. difference = 0.0f;
  126. RetainPtr<CGImageRef> diffImage;
  127. // Generate a normalized diff image if there is any difference
  128. if (difference > 0.0f) {
  129. if (maxDistance < 1.0f) {
  130. diff = (unsigned char*)diffBuffer;
  131. for(size_t p = 0; p < height * width; ++p)
  132. diff[p] = diff[p] / maxDistance;
  133. }
  134. static CGColorSpaceRef diffColorspace = CGColorSpaceCreateDeviceGray();
  135. RetainPtr<CGDataProviderRef> provider = adoptCF(CGDataProviderCreateWithData(0, diffBuffer, width * height, releaseMallocBuffer));
  136. diffImage = adoptCF(CGImageCreate(width, height, 8, 8, width, diffColorspace, 0, provider.get(), 0, false, kCGRenderingIntentDefault));
  137. }
  138. else
  139. free(diffBuffer);
  140. // Destroy drawing buffers
  141. if (buffer)
  142. free(buffer);
  143. if (baseBuffer)
  144. free(baseBuffer);
  145. return diffImage;
  146. }
  147. static inline bool imageHasAlpha(CGImageRef image)
  148. {
  149. CGImageAlphaInfo info = CGImageGetAlphaInfo(image);
  150. return (info >= kCGImageAlphaPremultipliedLast) && (info <= kCGImageAlphaFirst);
  151. }
  152. int main(int argc, const char* argv[])
  153. {
  154. #if PLATFORM(WIN)
  155. _setmode(0, _O_BINARY);
  156. _setmode(1, _O_BINARY);
  157. #endif
  158. float tolerance = 0.0f;
  159. for (int i = 1; i < argc; ++i) {
  160. if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--tolerance")) {
  161. if (i >= argc - 1)
  162. exit(1);
  163. tolerance = strtof(argv[i + 1], 0);
  164. ++i;
  165. continue;
  166. }
  167. }
  168. char buffer[2048];
  169. RetainPtr<CGImageRef> actualImage;
  170. RetainPtr<CGImageRef> baselineImage;
  171. while (fgets(buffer, sizeof(buffer), stdin)) {
  172. // remove the CR
  173. char* newLineCharacter = strchr(buffer, '\n');
  174. if (newLineCharacter)
  175. *newLineCharacter = '\0';
  176. if (!strncmp("Content-Length: ", buffer, 16)) {
  177. strtok(buffer, " ");
  178. int imageSize = strtol(strtok(0, " "), 0, 10);
  179. if (imageSize > 0 && !actualImage)
  180. actualImage = createImageFromStdin(imageSize);
  181. else if (imageSize > 0 && !baselineImage)
  182. baselineImage = createImageFromStdin(imageSize);
  183. else
  184. fputs("Error: image size must be specified.\n", stderr);
  185. }
  186. if (actualImage && baselineImage) {
  187. RetainPtr<CGImageRef> diffImage;
  188. float difference = 100.0f;
  189. if ((CGImageGetWidth(actualImage.get()) == CGImageGetWidth(baselineImage.get())) && (CGImageGetHeight(actualImage.get()) == CGImageGetHeight(baselineImage.get())) && (imageHasAlpha(actualImage.get()) == imageHasAlpha(baselineImage.get()))) {
  190. diffImage = createDifferenceImage(actualImage.get(), baselineImage.get(), difference); // difference is passed by reference
  191. if (difference <= tolerance)
  192. difference = 0.0f;
  193. else {
  194. difference = roundf(difference * 100.0f) / 100.0f;
  195. difference = max(difference, 0.01f); // round to 2 decimal places
  196. }
  197. } else {
  198. if (CGImageGetWidth(actualImage.get()) != CGImageGetWidth(baselineImage.get()) || CGImageGetHeight(actualImage.get()) != CGImageGetHeight(baselineImage.get()))
  199. fprintf(stderr, "Error: test and reference images have different sizes. Test image is %lux%lu, reference image is %lux%lu\n",
  200. CGImageGetWidth(actualImage.get()), CGImageGetHeight(actualImage.get()),
  201. CGImageGetWidth(baselineImage.get()), CGImageGetHeight(baselineImage.get()));
  202. else if (imageHasAlpha(actualImage.get()) != imageHasAlpha(baselineImage.get()))
  203. fprintf(stderr, "Error: test and reference images differ in alpha. Test image %s alpha, reference image %s alpha.\n",
  204. imageHasAlpha(actualImage.get()) ? "has" : "does not have",
  205. imageHasAlpha(baselineImage.get()) ? "has" : "does not have");
  206. }
  207. if (difference > 0.0f) {
  208. if (diffImage) {
  209. RetainPtr<CFMutableDataRef> imageData = adoptCF(CFDataCreateMutable(0, 0));
  210. RetainPtr<CGImageDestinationRef> imageDest = adoptCF(CGImageDestinationCreateWithData(imageData.get(), kUTTypePNG, 1, 0));
  211. CGImageDestinationAddImage(imageDest.get(), diffImage.get(), 0);
  212. CGImageDestinationFinalize(imageDest.get());
  213. printf("Content-Length: %lu\n", CFDataGetLength(imageData.get()));
  214. fwrite(CFDataGetBytePtr(imageData.get()), 1, CFDataGetLength(imageData.get()), stdout);
  215. }
  216. fprintf(stdout, "diff: %01.2f%% failed\n", difference);
  217. } else
  218. fprintf(stdout, "diff: %01.2f%% passed\n", difference);
  219. actualImage = 0;
  220. baselineImage = 0;
  221. }
  222. fflush(stdout);
  223. }
  224. return 0;
  225. }