28 lines
1.7 KiB
JavaScript
28 lines
1.7 KiB
JavaScript
// Lossless Chromium screenshots: tolerate a few rasterization pixels while
|
|
// keeping strict geometry/style comparisons in the calling UI test.
|
|
const { inflateSync } = require('node:zlib');
|
|
function decode(buffer) {
|
|
let width,height,channels;const chunks=[];
|
|
for(let offset=8;offset<buffer.length;){
|
|
const length=buffer.readUInt32BE(offset),type=buffer.toString('ascii',offset+4,offset+8),data=buffer.subarray(offset+8,offset+8+length);offset+=length+12;
|
|
if(type==='IHDR'){width=data.readUInt32BE(0);height=data.readUInt32BE(4);channels=data[9]===6?4:3;if(data[8]!==8||![2,6].includes(data[9])||data[12])throw new Error('Expected an 8-bit RGB Chromium PNG');}
|
|
if(type==='IDAT')chunks.push(data);
|
|
}
|
|
const raw=inflateSync(Buffer.concat(chunks)),stride=width*channels,pixels=Buffer.alloc(stride*height);let position=0;
|
|
const paeth=(a,b,c)=>{const p=a+b-c,da=Math.abs(p-a),db=Math.abs(p-b),dc=Math.abs(p-c);return da<=db&&da<=dc?a:db<=dc?b:c;};
|
|
for(let y=0;y<height;y++){
|
|
const filter=raw[position++];if(filter>4)throw new Error('Unexpected PNG filter');
|
|
for(let x=0;x<stride;x++){
|
|
const index=y*stride+x,a=x>=channels?pixels[index-channels]:0,b=y?pixels[index-stride]:0,c=y&&x>=channels?pixels[index-stride-channels]:0;
|
|
pixels[index]=(raw[position++]+[0,a,b,Math.floor((a+b)/2),paeth(a,b,c)][filter])&255;
|
|
}
|
|
}
|
|
return {width,height,channels,pixels};
|
|
}
|
|
function differentPixels(first,second){
|
|
const a=decode(first),b=decode(second);if(a.width!==b.width||a.height!==b.height||a.channels!==b.channels)return Infinity;
|
|
let count=0;for(let i=0;i<a.pixels.length;i+=a.channels)if(!a.pixels.subarray(i,i+a.channels).equals(b.pixels.subarray(i,i+a.channels)))count++;
|
|
return count;
|
|
}
|
|
module.exports={differentPixels};
|