compare.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.compare = compare;
  6. var _colorUtils = require("./colorUtils");
  7. var _imageChannel = require("./imageChannel");
  8. var _stats = require("./stats");
  9. /**
  10. * Copyright (c) Microsoft Corporation.
  11. *
  12. * Licensed under the Apache License, Version 2.0 (the 'License");
  13. * you may not use this file except in compliance with the License.
  14. * You may obtain a copy of the License at
  15. *
  16. * http://www.apache.org/licenses/LICENSE-2.0
  17. *
  18. * Unless required by applicable law or agreed to in writing, software
  19. * distributed under the License is distributed on an "AS IS" BASIS,
  20. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  21. * See the License for the specific language governing permissions and
  22. * limitations under the License.
  23. */
  24. const SSIM_WINDOW_RADIUS = 15;
  25. const VARIANCE_WINDOW_RADIUS = 1;
  26. function drawPixel(width, data, x, y, r, g, b) {
  27. const idx = (y * width + x) * 4;
  28. data[idx + 0] = r;
  29. data[idx + 1] = g;
  30. data[idx + 2] = b;
  31. data[idx + 3] = 255;
  32. }
  33. function compare(actual, expected, diff, width, height, options = {}) {
  34. const {
  35. maxColorDeltaE94 = 1.0
  36. } = options;
  37. const paddingSize = Math.max(VARIANCE_WINDOW_RADIUS, SSIM_WINDOW_RADIUS);
  38. const paddingColorEven = [255, 0, 255];
  39. const paddingColorOdd = [0, 255, 0];
  40. const [r1, g1, b1] = _imageChannel.ImageChannel.intoRGB(width, height, expected, {
  41. paddingSize,
  42. paddingColorEven,
  43. paddingColorOdd
  44. });
  45. const [r2, g2, b2] = _imageChannel.ImageChannel.intoRGB(width, height, actual, {
  46. paddingSize,
  47. paddingColorEven,
  48. paddingColorOdd
  49. });
  50. const noop = (x, y) => {};
  51. const drawRedPixel = diff ? (x, y) => drawPixel(width, diff, x - paddingSize, y - paddingSize, 255, 0, 0) : noop;
  52. const drawYellowPixel = diff ? (x, y) => drawPixel(width, diff, x - paddingSize, y - paddingSize, 255, 255, 0) : noop;
  53. const drawGrayPixel = diff ? (x, y) => {
  54. const gray = (0, _colorUtils.rgb2gray)(r1.get(x, y), g1.get(x, y), b1.get(x, y));
  55. const value = (0, _colorUtils.blendWithWhite)(gray, 0.1);
  56. drawPixel(width, diff, x - paddingSize, y - paddingSize, value, value, value);
  57. } : noop;
  58. let fastR, fastG, fastB;
  59. let diffCount = 0;
  60. for (let y = paddingSize; y < r1.height - paddingSize; ++y) {
  61. for (let x = paddingSize; x < r1.width - paddingSize; ++x) {
  62. // Fast-path: equal pixels.
  63. if (r1.get(x, y) === r2.get(x, y) && g1.get(x, y) === g2.get(x, y) && b1.get(x, y) === b2.get(x, y)) {
  64. drawGrayPixel(x, y);
  65. continue;
  66. }
  67. // Compare pixel colors using the dE94 color difference formulae.
  68. // The dE94 is normalized so that the value of 1.0 is the "just-noticeable-difference".
  69. // Color difference below 1.0 is not noticeable to a human eye, so we can disregard it.
  70. // See https://en.wikipedia.org/wiki/Color_difference
  71. const delta = (0, _colorUtils.colorDeltaE94)([r1.get(x, y), g1.get(x, y), b1.get(x, y)], [r2.get(x, y), g2.get(x, y), b2.get(x, y)]);
  72. if (delta <= maxColorDeltaE94) {
  73. drawGrayPixel(x, y);
  74. continue;
  75. }
  76. if (!fastR || !fastG || !fastB) {
  77. fastR = new _stats.FastStats(r1, r2);
  78. fastG = new _stats.FastStats(g1, g2);
  79. fastB = new _stats.FastStats(b1, b2);
  80. }
  81. const [varX1, varY1] = r1.boundXY(x - VARIANCE_WINDOW_RADIUS, y - VARIANCE_WINDOW_RADIUS);
  82. const [varX2, varY2] = r1.boundXY(x + VARIANCE_WINDOW_RADIUS, y + VARIANCE_WINDOW_RADIUS);
  83. const var1 = fastR.varianceC1(varX1, varY1, varX2, varY2) + fastG.varianceC1(varX1, varY1, varX2, varY2) + fastB.varianceC1(varX1, varY1, varX2, varY2);
  84. const var2 = fastR.varianceC2(varX1, varY1, varX2, varY2) + fastG.varianceC2(varX1, varY1, varX2, varY2) + fastB.varianceC2(varX1, varY1, varX2, varY2);
  85. // if this pixel is a part of a flood fill of a 3x3 square of either of the images, then it cannot be
  86. // anti-aliasing pixel so it must be a pixel difference.
  87. if (var1 === 0 || var2 === 0) {
  88. drawRedPixel(x, y);
  89. ++diffCount;
  90. continue;
  91. }
  92. const [ssimX1, ssimY1] = r1.boundXY(x - SSIM_WINDOW_RADIUS, y - SSIM_WINDOW_RADIUS);
  93. const [ssimX2, ssimY2] = r1.boundXY(x + SSIM_WINDOW_RADIUS, y + SSIM_WINDOW_RADIUS);
  94. const ssimRGB = ((0, _stats.ssim)(fastR, ssimX1, ssimY1, ssimX2, ssimY2) + (0, _stats.ssim)(fastG, ssimX1, ssimY1, ssimX2, ssimY2) + (0, _stats.ssim)(fastB, ssimX1, ssimY1, ssimX2, ssimY2)) / 3.0;
  95. const isAntialiased = ssimRGB >= 0.99;
  96. if (isAntialiased) {
  97. drawYellowPixel(x, y);
  98. } else {
  99. drawRedPixel(x, y);
  100. ++diffCount;
  101. }
  102. }
  103. }
  104. return diffCount;
  105. }