first commit

This commit is contained in:
Karl committed 2026-10-13 10:34:48 +02:00
commit ff482ebe23
251 files changed
+19126

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
'use strict';
const {PNG}=require('pngjs');
const {inflateSync}=require('node:zlib');
const {visualFingerprint}=require('./visual-hash.cjs');
const SIGNATURE=Buffer.from([137,80,78,71,13,10,26,10]);
function decodePNG(input){
const bytes=Buffer.from(input);
if(bytes.length<45||bytes.length>10*1024*1024||!bytes.subarray(0,8).equals(SIGNATURE)||bytes.readUInt32BE(8)!==13||bytes.toString('ascii',12,16)!=='IHDR')throw Error('Invalid bounded PNG reference');
const width=bytes.readUInt32BE(16),height=bytes.readUInt32BE(20),depth=bytes[24],type=bytes[25];
if(width<16||height<16||width>8192||height>8192||width*height>8*1024*1024||depth!==8||![0,2,4,6].includes(type)||bytes[26]!==0||bytes[27]!==0||bytes[28]!==0)throw Error('Unsupported PNG dimensions or encoding');
const parts=[];let ended=false;
for(let offset=8;offset<bytes.length;){
if(offset+12>bytes.length)throw Error('Truncated PNG');
const length=bytes.readUInt32BE(offset),chunk=bytes.toString('ascii',offset+4,offset+8),next=offset+12+length;
if(next>bytes.length||ended||(chunk==='IHDR'&&offset!==8)||['acTL','fcTL','fdAT'].includes(chunk))throw Error('Unsupported PNG structure');
if(chunk==='IDAT')parts.push(bytes.subarray(offset+8,offset+8+length));
if(chunk==='IEND'){if(length!==0||next!==bytes.length)throw Error('Invalid PNG end');ended=true;}
offset=next;
}
if(!ended||!parts.length)throw Error('Incomplete PNG');
const channels={0:1,2:3,4:2,6:4}[type],expected=(width*channels+1)*height;
const inflated=inflateSync(Buffer.concat(parts),{maxOutputLength:expected+1});
try{if(inflated.length!==expected)throw Error('PNG data size differs from header');}finally{inflated.fill(0);}
const image=PNG.sync.read(bytes,{checkCRC:true});
if(image.width!==width||image.height!==height||image.data.length!==width*height*4)throw Error('PNG output dimensions invalid');
return {rgba:image.data,width,height};
}
function fingerprintPNG(bytes){const image=decodePNG(bytes);try{return {...visualFingerprint(image),width:image.width,height:image.height};}finally{image.rgba.fill(0);}}
module.exports={decodePNG,fingerprintPNG};