index.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. 'use strict';
  2. module.exports = pixelmatch;
  3. function pixelmatch(img1, img2, output, width, height, options) {
  4. if (!options) options = {};
  5. var threshold = options.threshold === undefined ? 0.1 : options.threshold;
  6. // maximum acceptable square distance between two colors;
  7. // 35215 is the maximum possible value for the YIQ difference metric
  8. var maxDelta = 35215 * threshold * threshold,
  9. diff = 0;
  10. // compare each pixel of one image against the other one
  11. for (var y = 0; y < height; y++) {
  12. for (var x = 0; x < width; x++) {
  13. var pos = (y * width + x) * 4;
  14. // squared YUV distance between colors at this pixel position
  15. var delta = colorDelta(img1, img2, pos, pos);
  16. // the color difference is above the threshold
  17. if (delta > maxDelta) {
  18. // check it's a real rendering difference or just anti-aliasing
  19. if (!options.includeAA && (antialiased(img1, x, y, width, height, img2) ||
  20. antialiased(img2, x, y, width, height, img1))) {
  21. // one of the pixels is anti-aliasing; draw as yellow and do not count as difference
  22. if (output) drawPixel(output, pos, 255, 255, 0);
  23. } else {
  24. // found substantial difference not caused by anti-aliasing; draw it as red
  25. if (output) drawPixel(output, pos, 255, 0, 0);
  26. diff++;
  27. }
  28. } else if (output) {
  29. // pixels are similar; draw background as grayscale image blended with white
  30. var val = blend(grayPixel(img1, pos), 0.1);
  31. drawPixel(output, pos, val, val, val);
  32. }
  33. }
  34. }
  35. // return the number of different pixels
  36. return diff;
  37. }
  38. // check if a pixel is likely a part of anti-aliasing;
  39. // based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009
  40. function antialiased(img, x1, y1, width, height, img2) {
  41. var x0 = Math.max(x1 - 1, 0),
  42. y0 = Math.max(y1 - 1, 0),
  43. x2 = Math.min(x1 + 1, width - 1),
  44. y2 = Math.min(y1 + 1, height - 1),
  45. pos = (y1 * width + x1) * 4,
  46. zeroes = 0,
  47. positives = 0,
  48. negatives = 0,
  49. min = 0,
  50. max = 0,
  51. minX, minY, maxX, maxY;
  52. // go through 8 adjacent pixels
  53. for (var x = x0; x <= x2; x++) {
  54. for (var y = y0; y <= y2; y++) {
  55. if (x === x1 && y === y1) continue;
  56. // brightness delta between the center pixel and adjacent one
  57. var delta = colorDelta(img, img, pos, (y * width + x) * 4, true);
  58. // count the number of equal, darker and brighter adjacent pixels
  59. if (delta === 0) zeroes++;
  60. else if (delta < 0) negatives++;
  61. else if (delta > 0) positives++;
  62. // if found more than 2 equal siblings, it's definitely not anti-aliasing
  63. if (zeroes > 2) return false;
  64. if (!img2) continue;
  65. // remember the darkest pixel
  66. if (delta < min) {
  67. min = delta;
  68. minX = x;
  69. minY = y;
  70. }
  71. // remember the brightest pixel
  72. if (delta > max) {
  73. max = delta;
  74. maxX = x;
  75. maxY = y;
  76. }
  77. }
  78. }
  79. if (!img2) return true;
  80. // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing
  81. if (negatives === 0 || positives === 0) return false;
  82. // if either the darkest or the brightest pixel has more than 2 equal siblings in both images
  83. // (definitely not anti-aliased), this pixel is anti-aliased
  84. return (!antialiased(img, minX, minY, width, height) && !antialiased(img2, minX, minY, width, height)) ||
  85. (!antialiased(img, maxX, maxY, width, height) && !antialiased(img2, maxX, maxY, width, height));
  86. }
  87. // calculate color difference according to the paper "Measuring perceived color difference
  88. // using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos
  89. function colorDelta(img1, img2, k, m, yOnly) {
  90. var a1 = img1[k + 3] / 255,
  91. a2 = img2[m + 3] / 255,
  92. r1 = blend(img1[k + 0], a1),
  93. g1 = blend(img1[k + 1], a1),
  94. b1 = blend(img1[k + 2], a1),
  95. r2 = blend(img2[m + 0], a2),
  96. g2 = blend(img2[m + 1], a2),
  97. b2 = blend(img2[m + 2], a2),
  98. y = rgb2y(r1, g1, b1) - rgb2y(r2, g2, b2);
  99. if (yOnly) return y; // brightness difference only
  100. var i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2),
  101. q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2);
  102. return 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
  103. }
  104. function rgb2y(r, g, b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; }
  105. function rgb2i(r, g, b) { return r * 0.59597799 - g * 0.27417610 - b * 0.32180189; }
  106. function rgb2q(r, g, b) { return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; }
  107. // blend semi-transparent color with white
  108. function blend(c, a) {
  109. return 255 + (c - 255) * a;
  110. }
  111. function drawPixel(output, pos, r, g, b) {
  112. output[pos + 0] = r;
  113. output[pos + 1] = g;
  114. output[pos + 2] = b;
  115. output[pos + 3] = 255;
  116. }
  117. function grayPixel(img, i) {
  118. var a = img[i + 3] / 255,
  119. r = blend(img[i + 0], a),
  120. g = blend(img[i + 1], a),
  121. b = blend(img[i + 2], a);
  122. return rgb2y(r, g, b);
  123. }