Skip to content

Commit 029ca05

Browse files
committed
test(pdf): make visual smoke checks cross-platform
1 parent 272bb6e commit 029ca05

1 file changed

Lines changed: 39 additions & 9 deletions

File tree

.github/scripts/validate-pdf-visual.mjs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ const BASELINE_DIR = path.join(".github", "test-artifacts", "pdf-baselines");
2626
const OUTPUT_DIR = path.join("output", "playwright", "pdf-visual");
2727
const VIEWPORT = { width: 1200, height: 760 };
2828
const PIXELMATCH_THRESHOLD = 0.1;
29-
const MAX_DIFF_RATIO = 0.003;
29+
const DHASH_WIDTH = 17;
30+
const DHASH_HEIGHT = 16;
31+
const MAX_DHASH_DISTANCE = 20;
3032

3133
function parseArgs(argv) {
3234
return {
@@ -49,6 +51,38 @@ function pngPaths(name) {
4951
};
5052
}
5153

54+
function grayscaleAt(image, x, y) {
55+
const index = (y * image.width + x) * 4;
56+
const r = image.data[index];
57+
const g = image.data[index + 1];
58+
const b = image.data[index + 2];
59+
return 0.299 * r + 0.587 * g + 0.114 * b;
60+
}
61+
62+
function differenceHash(image, width = DHASH_WIDTH, height = DHASH_HEIGHT) {
63+
const bits = [];
64+
for (let y = 0; y < height; y += 1) {
65+
for (let x = 0; x < width - 1; x += 1) {
66+
const leftX = Math.floor(((x + 0.5) * image.width) / width);
67+
const rightX = Math.floor(((x + 1.5) * image.width) / width);
68+
const sampleY = Math.floor(((y + 0.5) * image.height) / height);
69+
bits.push(grayscaleAt(image, leftX, sampleY) > grayscaleAt(image, rightX, sampleY) ? 1 : 0);
70+
}
71+
}
72+
return bits;
73+
}
74+
75+
function hammingDistance(left, right) {
76+
assert(left.length === right.length, "Cannot compare hashes of different lengths");
77+
let distance = 0;
78+
for (let index = 0; index < left.length; index += 1) {
79+
if (left[index] !== right[index]) {
80+
distance += 1;
81+
}
82+
}
83+
return distance;
84+
}
85+
5286
function contentTypeFor(filePath) {
5387
if (filePath.endsWith(".pdf")) {
5488
return "application/pdf";
@@ -141,20 +175,16 @@ async function compareCase(browser, baseUrl, caseSpec) {
141175
actual.height,
142176
{ threshold: PIXELMATCH_THRESHOLD }
143177
);
144-
const totalPixels = actual.width * actual.height;
145-
const diffRatio = diffPixels / totalPixels;
146-
147178
await writeFile(paths.diff, PNG.sync.write(diff));
179+
const dHashDistance = hammingDistance(differenceHash(baseline), differenceHash(actual));
148180

149-
if (diffRatio > MAX_DIFF_RATIO) {
181+
if (dHashDistance > MAX_DHASH_DISTANCE) {
150182
throw new Error(
151-
`${caseSpec.name} visual diff too large: ${diffPixels} pixels (${(diffRatio * 100).toFixed(3)}%)`
183+
`${caseSpec.name} perceptual diff too large: dHash distance ${dHashDistance}, raw diff ${diffPixels} pixels`
152184
);
153185
}
154186

155-
console.log(
156-
`OK [${caseSpec.name}] visual diff ${diffPixels} pixels (${(diffRatio * 100).toFixed(3)}%)`
157-
);
187+
console.log(`OK [${caseSpec.name}] dHash distance ${dHashDistance}, raw diff ${diffPixels} pixels`);
158188
}
159189

160190
async function main() {

0 commit comments

Comments
 (0)