Files
matrix-screen-controller/核桃派软件源代码/app/static/pixel-png.js
T

55 lines
3.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Fixed 192x192 RGBA PNG codec, independent of DOM and shared by Node tests.
import { zlibSync, unzlibSync } from "./vendor/fflate/fflate.js";
const SIDE = 192, STRIDE = SIDE * 4;
const SIGNATURE = [137,80,78,71,13,10,26,10];
const crcTable = Uint32Array.from({ length: 256 }, (_, n) => {
for (let k = 0; k < 8; k++) n = n & 1 ? 0xedb88320 ^ (n >>> 1) : n >>> 1;
return n >>> 0;
});
function crc(bytes) { let n = 0xffffffff; for (const b of bytes) n = crcTable[(n ^ b) & 255] ^ (n >>> 8); return (n ^ 0xffffffff) >>> 0; }
function chunk(name, data) {
const out = new Uint8Array(data.length + 12), view = new DataView(out.buffer);
view.setUint32(0, data.length); out.set(Array.from(name, c => c.charCodeAt(0)), 4); out.set(data, 8);
view.setUint32(out.length - 4, crc(out.subarray(4, out.length - 4))); return out;
}
export function encodePixels(rgba) {
if (rgba.length !== SIDE * STRIDE) throw new Error("画笔像素长度无效");
const ihdr = new Uint8Array(13), v = new DataView(ihdr.buffer);
v.setUint32(0, SIDE); v.setUint32(4, SIDE); ihdr[8] = 8; ihdr[9] = 6;
const raw = new Uint8Array((STRIDE + 1) * SIDE);
for (let y = 0; y < SIDE; y++) raw.set(rgba.subarray(y * STRIDE, (y + 1) * STRIDE), y * (STRIDE + 1) + 1);
const parts = [new Uint8Array(SIGNATURE), chunk("IHDR", ihdr), chunk("IDAT", zlibSync(raw, { level: 6 })), chunk("IEND", new Uint8Array())];
const png = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); let at = 0;
for (const p of parts) { png.set(p, at); at += p.length; }
let binary = ""; for (const b of png) binary += String.fromCharCode(b);
return btoa(binary);
}
function paeth(a,b,c) { const p=a+b-c, x=Math.abs(p-a), y=Math.abs(p-b), z=Math.abs(p-c); return x<=y && x<=z ? a : y<=z ? b : c; }
export function decodePixels(value) {
if (typeof value !== "string" || value.length > 300000 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) throw new Error("画笔PNG无效");
const bytes = Uint8Array.from(atob(value), c => c.charCodeAt(0)), view = new DataView(bytes.buffer);
if (!SIGNATURE.every((b,i) => bytes[i] === b)) throw new Error("画笔PNG签名无效");
const parts=[]; let header=false, ended=false;
for (let at=8; at+12<=bytes.length;) {
const n=view.getUint32(at); if (n>bytes.length-at-12) throw new Error("PNG截断");
const name=String.fromCharCode(...bytes.subarray(at+4,at+8)), data=bytes.subarray(at+8,at+8+n);
if (crc(bytes.subarray(at+4,at+8+n))!==view.getUint32(at+8+n)) throw new Error("PNG校验失败");
if (name==="IHDR") { if (header || n!==13 || view.getUint32(at+8)!==SIDE || view.getUint32(at+12)!==SIDE || data[8]!==8 || data[9]!==6 || data[10] || data[11] || data[12]) throw new Error("PNG必须为192×192 RGBA8"); header=true; }
else if (name==="IDAT") { if (!header || ended) throw new Error("PNG顺序无效"); parts.push(data); }
else if (name==="IEND") { if (n || at+12!==bytes.length) throw new Error("PNG结尾无效"); ended=true; }
else if (name[0]===name[0].toUpperCase()) throw new Error("PNG不支持此块");
at+=n+12;
}
if (!header || !ended || !parts.length) throw new Error("PNG不完整");
const compressed=new Uint8Array(parts.reduce((n,p)=>n+p.length,0)); let at=0; for (const p of parts) { compressed.set(p,at); at+=p.length; }
const expected=(STRIDE+1)*SIDE, raw=unzlibSync(compressed,{out:new Uint8Array(expected+1)});
if (raw.length!==expected) throw new Error("PNG展开长度无效");
const out=new Uint8ClampedArray(SIDE*STRIDE);
for (let y=0;y<SIDE;y++) { const filter=raw[y*(STRIDE+1)]; if (filter>4) throw new Error("PNG过滤器无效");
for (let x=0;x<STRIDE;x++) { const i=y*STRIDE+x, a=x>=4?out[i-4]:0, b=y?out[i-STRIDE]:0, c=y&&x>=4?out[i-STRIDE-4]:0;
out[i]=(raw[y*(STRIDE+1)+1+x]+([0,a,b,Math.floor((a+b)/2),paeth(a,b,c)][filter]))&255;
}
}
return out;
}