[
  {
    "path": ".gitattributes",
    "content": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text=auto\n\n# Declare files that will always have LF line endings on checkout, so that it matches the configured eslint rules.\n*.js text eol=lf\nbin/pixelmatch text eol=lf"
  },
  {
    "path": ".github/workflows/node.yml",
    "content": "name: Node\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n\n    - name: Setup Node\n      uses: actions/setup-node@v4\n      with:\n        node-version: 20\n\n    - name: Install dependencies\n      run: npm install\n\n    - name: Run tests\n      run: npm test\n\n    - name: Run the CLI tool\n      run: node bin/pixelmatch test/fixtures/1a.png test/fixtures/1a.png\n"
  },
  {
    "path": ".gitignore",
    "content": "*.log\nnode_modules\n.DS_Store\n.nyc_output\ncoverage\ntmp\nindex.d.ts\n"
  },
  {
    "path": "LICENSE",
    "content": "ISC License\n\nCopyright (c) 2025, Mapbox\n\nPermission to use, copy, modify, and/or distribute this software for any purpose\nwith or without fee is hereby granted, provided that the above copyright notice\nand this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER\nTORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF\nTHIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# pixelmatch\n\n[![Node](https://github.com/mapbox/pixelmatch/actions/workflows/node.yml/badge.svg)](https://github.com/mapbox/pixelmatch/actions/workflows/node.yml)\n[![](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects)\n\nThe smallest, simplest and fastest JavaScript pixel-level image comparison library,\noriginally created to compare screenshots in tests.\n\nFeatures accurate **anti-aliased pixels detection**\nand **perceptual color difference metrics**.\n\nInspired by [Resemble.js](https://github.com/Huddle/Resemble.js)\nand [Blink-diff](https://github.com/yahoo/blink-diff).\nUnlike these libraries, pixelmatch is around **150 lines of code**,\nhas **no dependencies**, and works on **raw typed arrays** of image data,\nso it's **blazing fast** and can be used in **any environment** (Node or browsers).\n\n```js\nconst numDiffPixels = pixelmatch(img1, img2, diff, 800, 600, {threshold: 0.1});\n```\n\nImplements ideas from the following papers:\n\n- [Measuring perceived color difference using YIQ NTSC transmission color space in mobile applications](https://www.spiedigitallibrary.org/conference-proceedings-of-spie/8011/80119D/Simple-perceptual-color-space-for-color-specification-and-real-time/10.1117/12.901997.full) (2010, Yuriy Kotsarenko, Fernando Ramos)\n- [Anti-aliased pixel and intensity slope detector](https://www.researchgate.net/publication/234126755_Anti-aliased_Pixel_and_Intensity_Slope_Detector) (2009, Vytautas Vyšniauskas)\n\n## [Demo](https://observablehq.com/@mourner/pixelmatch-demo)\n\n## Example output\n\n| expected | actual | diff |\n| --- | --- | --- |\n| ![](test/fixtures/4a.png) | ![](test/fixtures/4b.png) | ![1diff](test/fixtures/4diff.png) |\n| ![](test/fixtures/3a.png) | ![](test/fixtures/3b.png) | ![1diff](test/fixtures/3diff.png) |\n| ![](test/fixtures/6a.png) | ![](test/fixtures/6b.png) | ![1diff](test/fixtures/6diff.png) |\n\n## API\n\n### pixelmatch(img1, img2, output, width, height[, options])\n\n- `img1`, `img2` — Image data of the images to compare (`Buffer`, `Uint8Array` or `Uint8ClampedArray`). **Note:** image dimensions must be equal.\n- `output` — Image data to write the diff to, or `null` if don't need a diff image.\n- `width`, `height` — Width and height of the images. Note that _all three images_ need to have the same dimensions.\n\n`options` is an object literal with the following properties:\n\n- `threshold` — Matching threshold, ranges from `0` to `1`. Smaller values make the comparison more sensitive. `0.1` by default.\n- `includeAA` — If `true`, disables detecting and ignoring anti-aliased pixels. `false` by default.\n- `alpha` — Blending factor of unchanged pixels in the diff output. Ranges from `0` for pure white to `1` for original brightness. `0.1` by default.\n- `aaColor` — The color of anti-aliased pixels in the diff output in `[R, G, B]` format. `[255, 255, 0]` by default.\n- `diffColor` — The color of differing pixels in the diff output in `[R, G, B]` format. `[255, 0, 0]` by default.\n- `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.\n- `diffMask` — Draw the diff over a transparent background (a mask), rather than over the original image. Will not draw anti-aliased pixels (if detected).\n\nCompares two images, writes the output diff and returns the number of mismatched pixels.\n\n## Command line\n\nPixelmatch comes with a binary that works with PNG images:\n\n```bash\npixelmatch image1.png image2.png output.png 0.1\n```\n\n## Example usage\n\n### Node.js\n\n```js\nimport fs from 'fs';\nimport {PNG} from 'pngjs';\nimport pixelmatch from 'pixelmatch';\n\nconst img1 = PNG.sync.read(fs.readFileSync('img1.png'));\nconst img2 = PNG.sync.read(fs.readFileSync('img2.png'));\nconst {width, height} = img1;\nconst diff = new PNG({width, height});\n\npixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.1});\n\nfs.writeFileSync('diff.png', PNG.sync.write(diff));\n```\n\n### Browsers\n\n```js\nconst img1 = img1Context.getImageData(0, 0, width, height);\nconst img2 = img2Context.getImageData(0, 0, width, height);\nconst diff = diffContext.createImageData(width, height);\n\npixelmatch(img1.data, img2.data, diff.data, width, height, {threshold: 0.1});\n\ndiffContext.putImageData(diff, 0, 0);\n```\n\n## Install\n\nInstall with NPM:\n\n```bash\nnpm install pixelmatch\n```\n\nOr use in the browser from a CDN:\n\n```html\n<script type=\"module\">\n\timport pixelmatch from 'https://esm.run/pixelmatch';\n```\n\n## [Changelog](https://github.com/mapbox/pixelmatch/releases)\n"
  },
  {
    "path": "bench.js",
    "content": "import match from './index.js';\nimport {PNG} from 'pngjs';\nimport fs from 'fs';\n\nconst data = [1, 2, 3, 4, 5, 6, 7].map(i => [\n    readImage(`${i}a`),\n    readImage(`${i}b`)\n]);\n\nconsole.time('match');\nlet sum = 0;\nfor (let i = 0; i < 100; i++) {\n    for (const [img1, img2] of data) {\n        sum += match(img1.data, img2.data, null, img1.width, img1.height);\n    }\n}\nconsole.timeEnd('match');\nconsole.log(sum);\n\nfunction readImage(name) {\n    return PNG.sync.read(fs.readFileSync(new URL(`test/fixtures/${name}.png`, import.meta.url)));\n}\n"
  },
  {
    "path": "bin/pixelmatch",
    "content": "#!/usr/bin/env node\n\nimport {PNG} from 'pngjs';\nimport fs from 'fs';\nimport match from '../index.js';\n\nif (process.argv.length < 4) {\n    console.log('Usage: pixelmatch image1.png image2.png [diff.png] [threshold] [includeAA]');\n    process.exit(64);\n}\n\nconst [,, img1Path, img2Path, diffPath, threshold, includeAA] = process.argv;\nconst options = {};\nif (threshold !== undefined) options.threshold = +threshold;\nif (includeAA !== undefined) options.includeAA = includeAA !== 'false';\n\nconst img1 = PNG.sync.read(fs.readFileSync(img1Path));\nconst img2 = PNG.sync.read(fs.readFileSync(img2Path));\n\nconst {width, height} = img1;\n\nif (img2.width !== width || img2.height !== height) {\n    console.log(`Image dimensions do not match: ${width}x${height} vs ${img2.width}x${img2.height}`);\n    process.exit(65);\n}\n\nconst diff = diffPath ? new PNG({width, height}) : null;\n\nconsole.time('matched in');\nconst diffs = match(img1.data, img2.data, diff ? diff.data : null, width, height, options);\nconsole.timeEnd('matched in');\n\nconsole.log(`different pixels: ${diffs}`);\nconsole.log(`error: ${Math.round(100 * 100 * diffs / (width * height)) / 100}%`);\n\nif (diff) {\n    fs.writeFileSync(diffPath, PNG.sync.write(diff));\n}\nprocess.exit(diffs ? 66 : 0);\n"
  },
  {
    "path": "eslint.config.js",
    "content": "import config from 'eslint-config-mourner';\n\nexport default [\n    ...config,\n    {\n        files: ['*.js', 'test/test.js', 'bin/pixelmatch']\n    }\n];\n"
  },
  {
    "path": "index.js",
    "content": "/**\n * Compare two equally sized images, pixel by pixel.\n *\n * @param {Uint8Array | Uint8ClampedArray} img1 First image data.\n * @param {Uint8Array | Uint8ClampedArray} img2 Second image data.\n * @param {Uint8Array | Uint8ClampedArray | void} output Image data to write the diff to, if provided.\n * @param {number} width Input images width.\n * @param {number} height Input images height.\n *\n * @param {Object} [options]\n * @param {number} [options.threshold=0.1] Matching threshold (0 to 1); smaller is more sensitive.\n * @param {boolean} [options.includeAA=false] Whether to skip anti-aliasing detection.\n * @param {number} [options.alpha=0.1] Opacity of original image in diff output.\n * @param {[number, number, number]} [options.aaColor=[255, 255, 0]] Color of anti-aliased pixels in diff output.\n * @param {[number, number, number]} [options.diffColor=[255, 0, 0]] Color of different pixels in diff output.\n * @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.\n * @param {boolean} [options.diffMask=false] Draw the diff over a transparent background (a mask).\n *\n * @return {number} The number of mismatched pixels.\n */\nexport default function pixelmatch(img1, img2, output, width, height, options = {}) {\n    const {\n        threshold = 0.1,\n        alpha = 0.1,\n        aaColor = [255, 255, 0],\n        diffColor = [255, 0, 0],\n        includeAA, diffColorAlt, diffMask\n    } = options;\n\n    if (!isPixelData(img1) || !isPixelData(img2) || (output && !isPixelData(output)))\n        throw new Error('Image data: Uint8Array, Uint8ClampedArray or Buffer expected.');\n\n    if (img1.length !== img2.length || (output && output.length !== img1.length))\n        throw new Error(`Image sizes do not match. Image 1 size: ${img1.length}, image 2 size: ${img2.length}`);\n\n    if (img1.length !== width * height * 4) throw new Error(`Image data size does not match width/height. Expecting ${width * height * 4}. Got ${img1.length}`);\n\n    // check if images are identical\n    const len = width * height;\n    const a32 = new Uint32Array(img1.buffer, img1.byteOffset, len);\n    const b32 = new Uint32Array(img2.buffer, img2.byteOffset, len);\n    let identical = true;\n\n    for (let i = 0; i < len; i++) {\n        if (a32[i] !== b32[i]) { identical = false; break; }\n    }\n    if (identical) { // fast path if identical\n        if (output && !diffMask) {\n            for (let i = 0; i < len; i++) drawGrayPixel(img1, 4 * i, alpha, output);\n        }\n        return 0;\n    }\n\n    // maximum acceptable square distance between two colors;\n    // 35215 is the maximum possible value for the YIQ difference metric\n    const maxDelta = 35215 * threshold * threshold;\n    const [aaR, aaG, aaB] = aaColor;\n    const [diffR, diffG, diffB] = diffColor;\n    const [altR, altG, altB] = diffColorAlt || diffColor;\n    let diff = 0;\n\n    // compare each pixel of one image against the other one\n    for (let y = 0; y < height; y++) {\n        for (let x = 0; x < width; x++) {\n\n            const i = y * width + x;\n            const pos = i * 4;\n\n            // squared YUV distance between colors at this pixel position, negative if the img2 pixel is darker\n            const delta = a32[i] === b32[i] ? 0 : colorDelta(img1, img2, pos, pos, false);\n\n            // the color difference is above the threshold\n            if (Math.abs(delta) > maxDelta) {\n                // check it's a real rendering difference or just anti-aliasing\n                const isExcludedAA = !includeAA && (antialiased(img1, x, y, width, height, a32, b32) || antialiased(img2, x, y, width, height, b32, a32));\n                if (isExcludedAA) {\n                    // one of the pixels is anti-aliasing; draw as yellow and do not count as difference\n                    // note that we do not include such pixels in a mask\n                    if (output && !diffMask) drawPixel(output, pos, aaR, aaG, aaB);\n\n                } else {\n                    // found substantial difference not caused by anti-aliasing; draw it as such\n                    if (output) {\n                        if (delta < 0) {\n                            drawPixel(output, pos, altR, altG, altB);\n                        } else {\n                            drawPixel(output, pos, diffR, diffG, diffB);\n                        }\n                    }\n                    diff++;\n                }\n\n            } else if (output && !diffMask) {\n                // pixels are similar; draw background as grayscale image blended with white\n                drawGrayPixel(img1, pos, alpha, output);\n            }\n        }\n    }\n\n    // return the number of different pixels\n    return diff;\n}\n\n/** @param {Uint8Array | Uint8ClampedArray} arr */\nfunction isPixelData(arr) {\n    // work around instanceof Uint8Array not working properly in some Jest environments\n    return ArrayBuffer.isView(arr) && arr.BYTES_PER_ELEMENT === 1;\n}\n\n/**\n * Check if a pixel is likely a part of anti-aliasing;\n * based on \"Anti-aliased Pixel and Intensity Slope Detector\" paper by V. Vysniauskas, 2009\n * @param {Uint8Array | Uint8ClampedArray} img\n * @param {number} x1\n * @param {number} y1\n * @param {number} width\n * @param {number} height\n * @param {Uint32Array} a32\n * @param {Uint32Array} b32\n */\nfunction antialiased(img, x1, y1, width, height, a32, b32) {\n    const x0 = Math.max(x1 - 1, 0);\n    const y0 = Math.max(y1 - 1, 0);\n    const x2 = Math.min(x1 + 1, width - 1);\n    const y2 = Math.min(y1 + 1, height - 1);\n    const pos = y1 * width + x1;\n    let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0;\n    let min = 0;\n    let max = 0;\n    let minX = 0;\n    let minY = 0;\n    let maxX = 0;\n    let maxY = 0;\n\n    // go through 8 adjacent pixels\n    for (let x = x0; x <= x2; x++) {\n        for (let y = y0; y <= y2; y++) {\n            if (x === x1 && y === y1) continue;\n\n            // brightness delta between the center pixel and adjacent one\n            const delta = colorDelta(img, img, pos * 4, (y * width + x) * 4, true);\n\n            // count the number of equal, darker and brighter adjacent pixels\n            if (delta === 0) {\n                zeroes++;\n                // if found more than 2 equal siblings, it's definitely not anti-aliasing\n                if (zeroes > 2) return false;\n\n            // remember the darkest pixel\n            } else if (delta < min) {\n                min = delta;\n                minX = x;\n                minY = y;\n\n            // remember the brightest pixel\n            } else if (delta > max) {\n                max = delta;\n                maxX = x;\n                maxY = y;\n            }\n        }\n    }\n\n    // if there are no both darker and brighter pixels among siblings, it's not anti-aliasing\n    if (min === 0 || max === 0) return false;\n\n    // if either the darkest or the brightest pixel has 3+ equal siblings in both images\n    // (definitely not anti-aliased), this pixel is anti-aliased\n    return (hasManySiblings(a32, minX, minY, width, height) && hasManySiblings(b32, minX, minY, width, height)) ||\n           (hasManySiblings(a32, maxX, maxY, width, height) && hasManySiblings(b32, maxX, maxY, width, height));\n}\n\n/**\n * Check if a pixel has 3+ adjacent pixels of the same color.\n * @param {Uint32Array} img\n * @param {number} x1\n * @param {number} y1\n * @param {number} width\n * @param {number} height\n */\nfunction hasManySiblings(img, x1, y1, width, height) {\n    const x0 = Math.max(x1 - 1, 0);\n    const y0 = Math.max(y1 - 1, 0);\n    const x2 = Math.min(x1 + 1, width - 1);\n    const y2 = Math.min(y1 + 1, height - 1);\n    const val = img[y1 * width + x1];\n    let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0;\n\n    // go through 8 adjacent pixels\n    for (let x = x0; x <= x2; x++) {\n        for (let y = y0; y <= y2; y++) {\n            if (x === x1 && y === y1) continue;\n            zeroes += +(val === img[y * width + x]);\n            if (zeroes > 2) return true;\n        }\n    }\n    return false;\n}\n\n/**\n * Calculate color difference according to the paper \"Measuring perceived color difference\n * using YIQ NTSC transmission color space in mobile applications\" by Y. Kotsarenko and F. Ramos\n * @param {Uint8Array | Uint8ClampedArray} img1\n * @param {Uint8Array | Uint8ClampedArray} img2\n * @param {number} k\n * @param {number} m\n * @param {boolean} yOnly\n */\nfunction colorDelta(img1, img2, k, m, yOnly) {\n    const r1 = img1[k];\n    const g1 = img1[k + 1];\n    const b1 = img1[k + 2];\n    const a1 = img1[k + 3];\n    const r2 = img2[m];\n    const g2 = img2[m + 1];\n    const b2 = img2[m + 2];\n    const a2 = img2[m + 3];\n\n    let dr = r1 - r2;\n    let dg = g1 - g2;\n    let db = b1 - b2;\n    const da = a1 - a2;\n\n    if (!dr && !dg && !db && !da) return 0;\n\n    if (a1 < 255 || a2 < 255) { // blend pixels with background\n        const rb = 48 + 159 * (k % 2);\n        const gb = 48 + 159 * ((k / 1.618033988749895 | 0) % 2);\n        const bb = 48 + 159 * ((k / 2.618033988749895 | 0) % 2);\n        dr = (r1 * a1 - r2 * a2 - rb * da) / 255;\n        dg = (g1 * a1 - g2 * a2 - gb * da) / 255;\n        db = (b1 * a1 - b2 * a2 - bb * da) / 255;\n    }\n\n    const y = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;\n\n    if (yOnly) return y; // brightness difference only\n\n    const i = dr * 0.59597799 - dg * 0.27417610 - db * 0.32180189;\n    const q = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694;\n\n    const delta = 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;\n\n    // encode whether the pixel lightens or darkens in the sign\n    return y > 0 ? -delta : delta;\n}\n\n/**\n * @param {Uint8Array | Uint8ClampedArray} output\n * @param {number} pos\n * @param {number} r\n * @param {number} g\n * @param {number} b\n */\nfunction drawPixel(output, pos, r, g, b) {\n    output[pos + 0] = r;\n    output[pos + 1] = g;\n    output[pos + 2] = b;\n    output[pos + 3] = 255;\n}\n\n/**\n * @param {Uint8Array | Uint8ClampedArray} img\n * @param {number} i\n * @param {number} alpha\n * @param {Uint8Array | Uint8ClampedArray} output\n */\nfunction drawGrayPixel(img, i, alpha, output) {\n    const val = 255 + (img[i] * 0.29889531 + img[i + 1] * 0.58662247 + img[i + 2] * 0.11448223 - 255) * alpha * img[i + 3] / 255;\n    drawPixel(output, i, val, val, val);\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"pixelmatch\",\n  \"version\": \"7.1.0\",\n  \"type\": \"module\",\n  \"description\": \"The smallest and fastest pixel-level image comparison library.\",\n  \"main\": \"index.js\",\n  \"types\": \"index.d.ts\",\n  \"bin\": {\n    \"pixelmatch\": \"bin/pixelmatch\"\n  },\n  \"files\": [\n    \"bin/pixelmatch\",\n    \"index.d.ts\"\n  ],\n  \"dependencies\": {\n    \"pngjs\": \"^7.0.0\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^9.27.0\",\n    \"eslint-config-mourner\": \"^4.0.2\",\n    \"typescript\": \"^5.8.3\"\n  },\n  \"scripts\": {\n    \"pretest\": \"eslint\",\n    \"test\": \"tsc && node --test\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mapbox/pixelmatch.git\"\n  },\n  \"keywords\": [\n    \"image\",\n    \"comparison\",\n    \"diff\"\n  ],\n  \"author\": \"Volodymyr Agafonkin\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mapbox/pixelmatch/issues\"\n  },\n  \"homepage\": \"https://github.com/mapbox/pixelmatch#readme\"\n}\n"
  },
  {
    "path": "test/test.js",
    "content": "\nimport assert from 'node:assert/strict';\nimport test from 'node:test';\nimport fs from 'node:fs';\n\nimport {PNG} from 'pngjs';\nimport match from '../index.js';\n\nconst options = {threshold: 0.05};\n\ndiffTest('1a', '1b', '1diff', options, 143);\ndiffTest('1a', '1b', '1diffdefaultthreshold', {threshold: undefined}, 106);\ndiffTest('1a', '1b', '1diffmask', {threshold: 0.05, includeAA: false, diffMask: true}, 143);\ndiffTest('1a', '1a', '1emptydiffmask', {threshold: 0, diffMask: true}, 0);\ndiffTest('2a', '2b', '2diff', {\n    threshold: 0.05,\n    alpha: 0.5,\n    aaColor: [0, 192, 0],\n    diffColor: [255, 0, 255]\n}, 12437);\ndiffTest('3a', '3b', '3diff', options, 212);\ndiffTest('4a', '4b', '4diff', options, 36049);\ndiffTest('5a', '5b', '5diff', options, 6);\ndiffTest('6a', '6b', '6diff', options, 51);\ndiffTest('6a', '6a', '6empty', {threshold: 0}, 0);\ndiffTest('7a', '7b', '7diff', {diffColorAlt: [0, 255, 0]}, 2448);\ndiffTest('8a', '5b', '8diff', options, 32896);\n\ntest('throws error if image sizes do not match', () => {\n    assert.throws(() => match(new Uint8Array(8), new Uint8Array(9), null, 2, 1), 'Image sizes do not match');\n});\n\ntest('throws error if image sizes do not match width and height', () => {\n    assert.throws(() => match(new Uint8Array(9), new Uint8Array(9), null, 2, 1), 'Image data size does not match width/height');\n});\n\ntest('throws error if provided wrong image data format', () => {\n    const err = 'Image data: Uint8Array, Uint8ClampedArray or Buffer expected';\n    const arr = new Uint8Array(4 * 20 * 20);\n    const bad = new Array(arr.length).fill(0);\n    assert.throws(() => match(bad, arr, null, 20, 20), err);\n    assert.throws(() => match(arr, bad, null, 20, 20), err);\n    assert.throws(() => match(arr, arr, bad, 20, 20), err);\n});\n\nfunction diffTest(imgPath1, imgPath2, diffPath, options, expectedMismatch) {\n    const name = `comparing ${imgPath1} to ${imgPath2}, ${JSON.stringify(options)}`;\n\n    test(name, () => {\n        const img1 = readImage(imgPath1);\n        const img2 = readImage(imgPath2);\n        const {width, height} = img1;\n        const diff = new PNG({width, height});\n\n        const mismatch = match(img1.data, img2.data, diff.data, width, height, options);\n        const mismatch2 = match(img1.data, img2.data, null, width, height, options);\n\n        if (process.env.UPDATE) {\n            writeImage(diffPath, diff);\n        } else {\n            const expectedDiff = readImage(diffPath);\n            assert.ok(diff.data.equals(expectedDiff.data), 'diff image');\n        }\n        assert.equal(mismatch, expectedMismatch, 'number of mismatched pixels');\n        assert.equal(mismatch, mismatch2, 'number of mismatched pixels without diff');\n    });\n}\n\nfunction readImage(name) {\n    return PNG.sync.read(fs.readFileSync(new URL(`fixtures/${name}.png`, import.meta.url)));\n}\nfunction writeImage(name, image) {\n    fs.writeFileSync(new URL(`fixtures/${name}.png`, import.meta.url), PNG.sync.write(image));\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowJs\": true,\n        \"checkJs\": true,\n        \"strict\": true,\n        \"emitDeclarationOnly\": true,\n        \"declaration\": true,\n        \"target\": \"es2017\",\n        \"module\": \"nodenext\",\n        \"moduleResolution\": \"nodenext\"\n    },\n    \"files\": [\n        \"index.js\"\n    ]\n}\n"
  }
]