mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
chore: bundle pixelmatch, it has not changed for 2 years (#13585)
This commit is contained in:
parent
7ffce1da53
commit
5f843c347d
61
package-lock.json
generated
61
package-lock.json
generated
@ -24,7 +24,6 @@
|
||||
"@types/minimatch": "^3.0.5",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "=14.17.15",
|
||||
"@types/pixelmatch": "^5.2.4",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
"@types/progress": "^2.0.5",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
@ -1322,15 +1321,6 @@
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/pixelmatch": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.4.tgz",
|
||||
"integrity": "sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pngjs": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.1.tgz",
|
||||
@ -4923,25 +4913,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz",
|
||||
"integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==",
|
||||
"dependencies": {
|
||||
"pngjs": "^4.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"pixelmatch": "bin/pixelmatch"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch/node_modules/pngjs": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz",
|
||||
"integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pkginfo": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz",
|
||||
@ -6214,7 +6185,6 @@
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"jpeg-js": "0.4.3",
|
||||
"mime": "3.0.0",
|
||||
"pixelmatch": "5.2.1",
|
||||
"pngjs": "6.0.0",
|
||||
"progress": "2.0.3",
|
||||
"proper-lockfile": "4.1.2",
|
||||
@ -6243,7 +6213,7 @@
|
||||
},
|
||||
"packages/playwright-ct-react": {
|
||||
"name": "@playwright/experimental-ct-react",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.22.0-next"
|
||||
@ -6254,7 +6224,7 @@
|
||||
},
|
||||
"packages/playwright-ct-svelte": {
|
||||
"name": "@playwright/experimental-ct-svelte",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.22.0-next"
|
||||
@ -6265,7 +6235,7 @@
|
||||
},
|
||||
"packages/playwright-ct-vue": {
|
||||
"name": "@playwright/experimental-ct-vue",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.3",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.22.0-next"
|
||||
@ -7370,15 +7340,6 @@
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/pixelmatch": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.4.tgz",
|
||||
"integrity": "sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/pngjs": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.1.tgz",
|
||||
@ -9992,21 +9953,6 @@
|
||||
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||
"dev": true
|
||||
},
|
||||
"pixelmatch": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz",
|
||||
"integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==",
|
||||
"requires": {
|
||||
"pngjs": "^4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"pngjs": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz",
|
||||
"integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"pkginfo": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz",
|
||||
@ -10035,7 +9981,6 @@
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"jpeg-js": "0.4.3",
|
||||
"mime": "3.0.0",
|
||||
"pixelmatch": "5.2.1",
|
||||
"pngjs": "6.0.0",
|
||||
"progress": "2.0.3",
|
||||
"proper-lockfile": "4.1.2",
|
||||
|
@ -56,7 +56,6 @@
|
||||
"@types/minimatch": "^3.0.5",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "=14.17.15",
|
||||
"@types/pixelmatch": "^5.2.4",
|
||||
"@types/pngjs": "^6.0.1",
|
||||
"@types/progress": "^2.0.5",
|
||||
"@types/proper-lockfile": "^4.1.2",
|
||||
|
@ -49,7 +49,6 @@
|
||||
"https-proxy-agent": "5.0.0",
|
||||
"jpeg-js": "0.4.3",
|
||||
"mime": "3.0.0",
|
||||
"pixelmatch": "5.2.1",
|
||||
"pngjs": "6.0.0",
|
||||
"progress": "2.0.3",
|
||||
"proper-lockfile": "4.1.2",
|
||||
|
255
packages/playwright-core/src/third_party/pixelmatch.js
vendored
Normal file
255
packages/playwright-core/src/third_party/pixelmatch.js
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
/**
|
||||
*
|
||||
* ISC License
|
||||
*
|
||||
* Copyright (c) 2019, Mapbox
|
||||
|
||||
* Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
* with or without fee is hereby granted, provided that the above copyright notice
|
||||
* and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
* THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = pixelmatch;
|
||||
|
||||
const defaultOptions = {
|
||||
threshold: 0.1, // matching threshold (0 to 1); smaller is more sensitive
|
||||
includeAA: false, // whether to skip anti-aliasing detection
|
||||
alpha: 0.1, // opacity of original image in diff output
|
||||
aaColor: [255, 255, 0], // color of anti-aliased pixels in diff output
|
||||
diffColor: [255, 0, 0], // color of different pixels in diff output
|
||||
diffColorAlt: null, // whether to detect dark on light differences between img1 and img2 and set an alternative color to differentiate between the two
|
||||
diffMask: false // draw the diff over a transparent background (a mask)
|
||||
};
|
||||
|
||||
function pixelmatch(img1, img2, output, width, height, options) {
|
||||
|
||||
if (!isPixelData(img1) || !isPixelData(img2) || (output && !isPixelData(output)))
|
||||
throw new Error('Image data: Uint8Array, Uint8ClampedArray or Buffer expected.');
|
||||
|
||||
if (img1.length !== img2.length || (output && output.length !== img1.length))
|
||||
throw new Error('Image sizes do not match.');
|
||||
|
||||
if (img1.length !== width * height * 4) throw new Error('Image data size does not match width/height.');
|
||||
|
||||
options = Object.assign({}, defaultOptions, options);
|
||||
|
||||
// check if images are identical
|
||||
const len = width * height;
|
||||
const a32 = new Uint32Array(img1.buffer, img1.byteOffset, len);
|
||||
const b32 = new Uint32Array(img2.buffer, img2.byteOffset, len);
|
||||
let identical = true;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (a32[i] !== b32[i]) { identical = false; break; }
|
||||
}
|
||||
if (identical) { // fast path if identical
|
||||
if (output && !options.diffMask) {
|
||||
for (let i = 0; i < len; i++) drawGrayPixel(img1, 4 * i, options.alpha, output);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// maximum acceptable square distance between two colors;
|
||||
// 35215 is the maximum possible value for the YIQ difference metric
|
||||
const maxDelta = 35215 * options.threshold * options.threshold;
|
||||
let diff = 0;
|
||||
|
||||
// compare each pixel of one image against the other one
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
|
||||
const pos = (y * width + x) * 4;
|
||||
|
||||
// squared YUV distance between colors at this pixel position, negative if the img2 pixel is darker
|
||||
const delta = colorDelta(img1, img2, pos, pos);
|
||||
|
||||
// the color difference is above the threshold
|
||||
if (Math.abs(delta) > maxDelta) {
|
||||
// check it's a real rendering difference or just anti-aliasing
|
||||
if (!options.includeAA && (antialiased(img1, x, y, width, height, img2) ||
|
||||
antialiased(img2, x, y, width, height, img1))) {
|
||||
// one of the pixels is anti-aliasing; draw as yellow and do not count as difference
|
||||
// note that we do not include such pixels in a mask
|
||||
if (output && !options.diffMask) drawPixel(output, pos, ...options.aaColor);
|
||||
|
||||
} else {
|
||||
// found substantial difference not caused by anti-aliasing; draw it as such
|
||||
if (output) {
|
||||
drawPixel(output, pos, ...(delta < 0 && options.diffColorAlt || options.diffColor));
|
||||
}
|
||||
diff++;
|
||||
}
|
||||
|
||||
} else if (output) {
|
||||
// pixels are similar; draw background as grayscale image blended with white
|
||||
if (!options.diffMask) drawGrayPixel(img1, pos, options.alpha, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return the number of different pixels
|
||||
return diff;
|
||||
}
|
||||
|
||||
function isPixelData(arr) {
|
||||
// work around instanceof Uint8Array not working properly in some Jest environments
|
||||
return ArrayBuffer.isView(arr) && arr.constructor.BYTES_PER_ELEMENT === 1;
|
||||
}
|
||||
|
||||
// check if a pixel is likely a part of anti-aliasing;
|
||||
// based on "Anti-aliased Pixel and Intensity Slope Detector" paper by V. Vysniauskas, 2009
|
||||
|
||||
function antialiased(img, x1, y1, width, height, img2) {
|
||||
const x0 = Math.max(x1 - 1, 0);
|
||||
const y0 = Math.max(y1 - 1, 0);
|
||||
const x2 = Math.min(x1 + 1, width - 1);
|
||||
const y2 = Math.min(y1 + 1, height - 1);
|
||||
const pos = (y1 * width + x1) * 4;
|
||||
let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0;
|
||||
let min = 0;
|
||||
let max = 0;
|
||||
let minX, minY, maxX, maxY;
|
||||
|
||||
// go through 8 adjacent pixels
|
||||
for (let x = x0; x <= x2; x++) {
|
||||
for (let y = y0; y <= y2; y++) {
|
||||
if (x === x1 && y === y1) continue;
|
||||
|
||||
// brightness delta between the center pixel and adjacent one
|
||||
const delta = colorDelta(img, img, pos, (y * width + x) * 4, true);
|
||||
|
||||
// count the number of equal, darker and brighter adjacent pixels
|
||||
if (delta === 0) {
|
||||
zeroes++;
|
||||
// if found more than 2 equal siblings, it's definitely not anti-aliasing
|
||||
if (zeroes > 2) return false;
|
||||
|
||||
// remember the darkest pixel
|
||||
} else if (delta < min) {
|
||||
min = delta;
|
||||
minX = x;
|
||||
minY = y;
|
||||
|
||||
// remember the brightest pixel
|
||||
} else if (delta > max) {
|
||||
max = delta;
|
||||
maxX = x;
|
||||
maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no both darker and brighter pixels among siblings, it's not anti-aliasing
|
||||
if (min === 0 || max === 0) return false;
|
||||
|
||||
// if either the darkest or the brightest pixel has 3+ equal siblings in both images
|
||||
// (definitely not anti-aliased), this pixel is anti-aliased
|
||||
return (hasManySiblings(img, minX, minY, width, height) && hasManySiblings(img2, minX, minY, width, height)) ||
|
||||
(hasManySiblings(img, maxX, maxY, width, height) && hasManySiblings(img2, maxX, maxY, width, height));
|
||||
}
|
||||
|
||||
// check if a pixel has 3+ adjacent pixels of the same color.
|
||||
function hasManySiblings(img, x1, y1, width, height) {
|
||||
const x0 = Math.max(x1 - 1, 0);
|
||||
const y0 = Math.max(y1 - 1, 0);
|
||||
const x2 = Math.min(x1 + 1, width - 1);
|
||||
const y2 = Math.min(y1 + 1, height - 1);
|
||||
const pos = (y1 * width + x1) * 4;
|
||||
let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0;
|
||||
|
||||
// go through 8 adjacent pixels
|
||||
for (let x = x0; x <= x2; x++) {
|
||||
for (let y = y0; y <= y2; y++) {
|
||||
if (x === x1 && y === y1) continue;
|
||||
|
||||
const pos2 = (y * width + x) * 4;
|
||||
if (img[pos] === img[pos2] &&
|
||||
img[pos + 1] === img[pos2 + 1] &&
|
||||
img[pos + 2] === img[pos2 + 2] &&
|
||||
img[pos + 3] === img[pos2 + 3]) zeroes++;
|
||||
|
||||
if (zeroes > 2) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// calculate color difference according to the paper "Measuring perceived color difference
|
||||
// using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos
|
||||
|
||||
function colorDelta(img1, img2, k, m, yOnly) {
|
||||
let r1 = img1[k + 0];
|
||||
let g1 = img1[k + 1];
|
||||
let b1 = img1[k + 2];
|
||||
let a1 = img1[k + 3];
|
||||
|
||||
let r2 = img2[m + 0];
|
||||
let g2 = img2[m + 1];
|
||||
let b2 = img2[m + 2];
|
||||
let a2 = img2[m + 3];
|
||||
|
||||
if (a1 === a2 && r1 === r2 && g1 === g2 && b1 === b2) return 0;
|
||||
|
||||
if (a1 < 255) {
|
||||
a1 /= 255;
|
||||
r1 = blend(r1, a1);
|
||||
g1 = blend(g1, a1);
|
||||
b1 = blend(b1, a1);
|
||||
}
|
||||
|
||||
if (a2 < 255) {
|
||||
a2 /= 255;
|
||||
r2 = blend(r2, a2);
|
||||
g2 = blend(g2, a2);
|
||||
b2 = blend(b2, a2);
|
||||
}
|
||||
|
||||
const y1 = rgb2y(r1, g1, b1);
|
||||
const y2 = rgb2y(r2, g2, b2);
|
||||
const y = y1 - y2;
|
||||
|
||||
if (yOnly) return y; // brightness difference only
|
||||
|
||||
const i = rgb2i(r1, g1, b1) - rgb2i(r2, g2, b2);
|
||||
const q = rgb2q(r1, g1, b1) - rgb2q(r2, g2, b2);
|
||||
|
||||
const delta = 0.5053 * y * y + 0.299 * i * i + 0.1957 * q * q;
|
||||
|
||||
// encode whether the pixel lightens or darkens in the sign
|
||||
return y1 > y2 ? -delta : delta;
|
||||
}
|
||||
|
||||
function rgb2y(r, g, b) { return r * 0.29889531 + g * 0.58662247 + b * 0.11448223; }
|
||||
function rgb2i(r, g, b) { return r * 0.59597799 - g * 0.27417610 - b * 0.32180189; }
|
||||
function rgb2q(r, g, b) { return r * 0.21147017 - g * 0.52261711 + b * 0.31114694; }
|
||||
|
||||
// blend semi-transparent color with white
|
||||
function blend(c, a) {
|
||||
return 255 + (c - 255) * a;
|
||||
}
|
||||
|
||||
function drawPixel(output, pos, r, g, b) {
|
||||
output[pos + 0] = r;
|
||||
output[pos + 1] = g;
|
||||
output[pos + 2] = b;
|
||||
output[pos + 3] = 255;
|
||||
}
|
||||
|
||||
function drawGrayPixel(img, i, alpha, output) {
|
||||
const r = img[i + 0];
|
||||
const g = img[i + 1];
|
||||
const b = img[i + 2];
|
||||
const val = blend(rgb2y(r, g, b), alpha * img[i + 3] / 255);
|
||||
drawPixel(output, i, val, val, val);
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
[*]
|
||||
./
|
||||
../third_party/diff_match_patch
|
||||
../third_party/pixelmatch
|
||||
|
@ -17,11 +17,9 @@
|
||||
|
||||
import colors from 'colors/safe';
|
||||
import jpeg from 'jpeg-js';
|
||||
import pixelmatch from 'pixelmatch';
|
||||
import pixelmatch from '../third_party/pixelmatch';
|
||||
import { diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL } from '../third_party/diff_match_patch';
|
||||
|
||||
// Note: we require the pngjs version of pixelmatch to avoid version mismatches.
|
||||
const { PNG } = require(require.resolve('pngjs', { paths: [require.resolve('pixelmatch')] })) as typeof import('pngjs');
|
||||
import { PNG } from 'pngjs';
|
||||
|
||||
export type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxDiffPixelRatio?: number };
|
||||
export type ComparatorResult = { diff?: Buffer; errorMessage: string; } | null;
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import pixelmatch from 'pixelmatch';
|
||||
import pixelmatch from '../../packages/playwright-core/src/third_party/pixelmatch';
|
||||
import { PNG } from 'pngjs';
|
||||
import { expect, playwrightTest as it } from '../config/browserTest';
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user