Skip to content

Commit c771f3f

Browse files
committed
add an option to disable checkerboard blending
1 parent 973afc7 commit c771f3f

2 files changed

Lines changed: 31 additions & 12 deletions

File tree

index.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* @param {[number, number, number]} [options.diffColor=[255, 0, 0]] Color of different pixels in diff output.
1616
* @param {[number, number, number]} [options.diffColorAlt=options.diffColor] Whether to detect dark on light differences between img1 and img2 and set an alternative color to differentiate between the two.
1717
* @param {boolean} [options.diffMask=false] Draw the diff over a transparent background (a mask).
18+
* @param {boolean} [options.checkerboard=true] Whether to blend semi-transparent pixels against a checkerboard pattern (true) or plain white (false) when comparing.
1819
*
1920
* @return {number} The number of mismatched pixels.
2021
*/
@@ -24,6 +25,7 @@ export default function pixelmatch(img1, img2, output, width, height, options =
2425
alpha = 0.1,
2526
aaColor = [255, 255, 0],
2627
diffColor = [255, 0, 0],
28+
checkerboard = true,
2729
includeAA, diffColorAlt, diffMask
2830
} = options;
2931

@@ -62,14 +64,14 @@ export default function pixelmatch(img1, img2, output, width, height, options =
6264
// compare each pixel of one image against the other one
6365
for (let i = 0, pos = 0; i < len; i++, pos += 4) {
6466
// squared YUV distance between colors at this pixel position, negative if the img2 pixel is darker
65-
const delta = a32[i] === b32[i] ? 0 : colorDelta(img1, img2, pos, pos);
67+
const delta = a32[i] === b32[i] ? 0 : colorDelta(img1, img2, pos, pos, checkerboard);
6668

6769
// the color difference is above the threshold
6870
if (Math.abs(delta) > maxDelta) {
6971
const x = i % width;
7072
const y = (i / width) | 0;
7173
// check it's a real rendering difference or just anti-aliasing
72-
const isExcludedAA = !includeAA && (antialiased(img1, x, y, width, height, a32, b32) || antialiased(img2, x, y, width, height, b32, a32));
74+
const isExcludedAA = !includeAA && (antialiased(img1, x, y, width, height, a32, b32, checkerboard) || antialiased(img2, x, y, width, height, b32, a32, checkerboard));
7375
if (isExcludedAA) {
7476
// one of the pixels is anti-aliasing; draw as yellow and do not count as difference
7577
// note that we do not include such pixels in a mask
@@ -113,8 +115,9 @@ function isPixelData(arr) {
113115
* @param {number} height
114116
* @param {Uint32Array} a32
115117
* @param {Uint32Array} b32
118+
* @param {boolean} checkerboard
116119
*/
117-
function antialiased(img, x1, y1, width, height, a32, b32) {
120+
function antialiased(img, x1, y1, width, height, a32, b32, checkerboard) {
118121
const x0 = Math.max(x1 - 1, 0);
119122
const y0 = Math.max(y1 - 1, 0);
120123
const x2 = Math.min(x1 + 1, width - 1);
@@ -139,7 +142,7 @@ function antialiased(img, x1, y1, width, height, a32, b32) {
139142
if (x === x1 && y === y1) continue;
140143

141144
// brightness delta between the center pixel and adjacent one
142-
const delta = brightnessDelta(img, pos4, (y * width + x) * 4, cr, cg, cb, ca);
145+
const delta = brightnessDelta(img, pos4, (y * width + x) * 4, cr, cg, cb, ca, checkerboard);
143146

144147
// count the number of equal, darker and brighter adjacent pixels
145148
if (delta === 0) {
@@ -206,8 +209,9 @@ function hasManySiblings(img, x1, y1, width, height) {
206209
* @param {Uint8Array | Uint8ClampedArray} img2
207210
* @param {number} k
208211
* @param {number} m
212+
* @param {boolean} checkerboard
209213
*/
210-
function colorDelta(img1, img2, k, m) {
214+
function colorDelta(img1, img2, k, m, checkerboard) {
211215
const r1 = img1[k];
212216
const g1 = img1[k + 1];
213217
const b1 = img1[k + 2];
@@ -223,9 +227,12 @@ function colorDelta(img1, img2, k, m) {
223227
const da = a1 - a2;
224228

225229
if (a1 < 255 || a2 < 255) { // blend pixels with background
226-
const rb = 48 + 159 * (k % 2);
227-
const gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);
228-
const bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);
230+
let rb = 255, gb = 255, bb = 255;
231+
if (checkerboard) {
232+
rb = 48 + 159 * (k % 2);
233+
gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);
234+
bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);
235+
}
229236
dr = (r1 * a1 - r2 * a2 - rb * da) / 255;
230237
dg = (g1 * a1 - g2 * a2 - gb * da) / 255;
231238
db = (b1 * a1 - b2 * a2 - bb * da) / 255;
@@ -251,8 +258,9 @@ function colorDelta(img1, img2, k, m) {
251258
* @param {number} g1
252259
* @param {number} b1
253260
* @param {number} a1
261+
* @param {boolean} checkerboard
254262
*/
255-
function brightnessDelta(img, k, m, r1, g1, b1, a1) {
263+
function brightnessDelta(img, k, m, r1, g1, b1, a1, checkerboard) {
256264
const r2 = img[m];
257265
const g2 = img[m + 1];
258266
const b2 = img[m + 2];
@@ -266,9 +274,12 @@ function brightnessDelta(img, k, m, r1, g1, b1, a1) {
266274
if (!dr && !dg && !db && !da) return 0;
267275

268276
if (a1 < 255 || a2 < 255) {
269-
const rb = 48 + 159 * (k % 2);
270-
const gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);
271-
const bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);
277+
let rb = 255, gb = 255, bb = 255;
278+
if (checkerboard) {
279+
rb = 48 + 159 * (k % 2);
280+
gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);
281+
bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);
282+
}
272283
dr = (r1 * a1 - r2 * a2 - rb * da) / 255;
273284
dg = (g1 * a1 - g2 * a2 - gb * da) / 255;
274285
db = (b1 * a1 - b2 * a2 - bb * da) / 255;

test/test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ diffTest('6a', '6a', '6empty', {threshold: 0}, 0);
2626
diffTest('7a', '7b', '7diff', {diffColorAlt: [0, 255, 0]}, 2448);
2727
diffTest('8a', '5b', '8diff', options, 32896);
2828

29+
test('checkerboard: false blends semi-transparent pixels against white', () => {
30+
// These two pixels are visually identical composited on white but differ on a dark checkerboard
31+
const img1 = new Uint8Array([0, 0, 0, 128]); // 50% transparent black
32+
const img2 = new Uint8Array([127, 127, 127, 255]); // opaque gray
33+
assert.equal(match(img1, img2, null, 1, 1, {checkerboard: false}), 0);
34+
assert.equal(match(img1, img2, null, 1, 1), 1);
35+
});
36+
2937
test('throws error if image sizes do not match', () => {
3038
assert.throws(() => match(new Uint8Array(8), new Uint8Array(9), null, 2, 1), 'Image sizes do not match');
3139
});

0 commit comments

Comments
 (0)