diff --git a/README.md b/README.md index 1698b7f..054719b 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ Implements ideas from the following papers: - `diffColor` — The color of differing pixels in the diff output in `[R, G, B]` format. `[255, 0, 0]` by default. - `diffColorAlt` — An alternative color to use for dark on light differences to differentiate between "added" and "removed" parts. If not provided, all differing pixels use the color specified by `diffColor`. `null` by default. - `diffMask` — Draw the diff over a transparent background (a mask), rather than over the original image. Will not draw anti-aliased pixels (if detected). +- `ignoredRegions` — Regions to ignore in the diff output. +- `ignoredRegionsColor` —Color to draw the ignored regions in the diff output. Compares two images, writes the output diff and returns the number of mismatched pixels. diff --git a/index.js b/index.js index 7855307..e0cf706 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,8 @@ * @param {[number, number, number]} [options.diffColor=[255, 0, 0]] Color of different pixels in diff output. * @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. * @param {boolean} [options.diffMask=false] Draw the diff over a transparent background (a mask). + * @param {Array<{x1: number, y1: number, x2: number, y2: number}> | null} [options.ignoredRegions=null] Regions to ignore in the diff output. + * @param {[number, number, number]} [options.ignoredColor=null] Color to draw the ignored regions in the diff output. * * @return {number} The number of mismatched pixels. */ @@ -24,7 +26,11 @@ export default function pixelmatch(img1, img2, output, width, height, options = alpha = 0.1, aaColor = [255, 255, 0], diffColor = [255, 0, 0], - includeAA, diffColorAlt, diffMask + includeAA, + diffColorAlt, + diffMask, + ignoredRegions = null, + ignoredColor = null, } = options; if (!isPixelData(img1) || !isPixelData(img2) || (output && !isPixelData(output))) @@ -65,6 +71,17 @@ export default function pixelmatch(img1, img2, output, width, height, options = const i = y * width + x; const pos = i * 4; + if (isPixelIgnored(x, y, ignoredRegions)) { + if (output && !options.diffMask) { + + if (ignoredColor) { + drawPixel(output, pos, ...ignoredColor); + } else { + drawGrayPixel(img1, pos, alpha, output); + } + } + continue; + } // squared YUV distance between colors at this pixel position, negative if the img2 pixel is darker const delta = a32[i] === b32[i] ? 0 : colorDelta(img1, img2, pos, pos, false); @@ -269,3 +286,39 @@ function drawGrayPixel(img, i, alpha, output) { const val = 255 + (img[i] * 0.29889531 + img[i + 1] * 0.58662247 + img[i + 2] * 0.11448223 - 255) * alpha * img[i + 3] / 255; drawPixel(output, i, val, val, val); } + +/** + * Checks if the specified pixel coordinates are within the ignored regions. + * + * @param {number} x - The x-coordinate of the pixel. + * @param {number} y - The y-coordinate of the pixel. + * @param {Array<{x1: number, y1: number, x2: number, y2: number}> | null} ignoredRegions - An array of ignored regions, each defined by x1, y1, x2, y2. + * @returns {boolean} Returns true if the pixel is within any of the ignored regions; otherwise, returns false. + */ +function isPixelIgnored(x, y, ignoredRegions) { + if (ignoredRegions == null) { + return false; + } + for (const region of ignoredRegions) { + if (isPixelInRegion(x, y, region)) { + return true; + } + } + return false; +} + +/** + * Checks if the specified pixel coordinates are within the given region. + * + * @param {number} x - The x-coordinate of the pixel. + * @param {number} y - The y-coordinate of the pixel. + * @param {{x1: number, y1: number, x2: number, y2: number}} region - An object containing the region boundaries, must have x1, y1, x2, y2 properties. + * @returns {boolean} Returns true if the pixel is within the region; otherwise, returns false. + */ +function isPixelInRegion(x, y, region) { + const {x1, y1, x2, y2} = region; + if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) { + return false; + } + return x1 <= x && x <= x2 && y1 <= y && y <= y2; +} diff --git a/test/fixtures/2diff_ignored_regions.png b/test/fixtures/2diff_ignored_regions.png new file mode 100644 index 0000000..a43797d Binary files /dev/null and b/test/fixtures/2diff_ignored_regions.png differ diff --git a/test/test.js b/test/test.js index 1b9af70..2824f2b 100644 --- a/test/test.js +++ b/test/test.js @@ -25,6 +25,24 @@ diffTest('6a', '6b', '6diff', options, 51); diffTest('6a', '6a', '6empty', {threshold: 0}, 0); diffTest('7a', '7b', '7diff', {diffColorAlt: [0, 255, 0]}, 2448); diffTest('8a', '5b', '8diff', options, 32896); +diffTest('2a', '2b', '2diff_ignored_regions', { + threshold: 0.05, + ignoredRegions: [ + { + x1: 128, + y1: 0, + x2: 256, + y2: 128, + }, + { + x1: 0, + y1: 128, + x2: 128, + y2: 256 + } + ], + ignoredColor: [0, 255, 255] +}, 2229); test('throws error if image sizes do not match', () => { assert.throws(() => match(new Uint8Array(8), new Uint8Array(9), null, 2, 1), 'Image sizes do not match');