// 逐像素比对两张图(解码成 RGBA 后比),并且区分"差异是否只发生在完全透明的像素下"。 // 这个区分很关键:本项目依赖"A=0 处 RGB 已清零",因为贴图是 Linear 过滤, // 透明像素的 RGB 会在线性插值时渗进边缘像素——所以"仅在 A=0 处不同"的转码仍然是不可接受的。 // 用法:node tools/rawdiff.mjs import { spawn } from "node:child_process"; function decode(file) { return new Promise((resolve, reject) => { const p = spawn("ffmpeg", ["-v", "error", "-i", file, "-f", "rawvideo", "-pix_fmt", "rgba", "-"], { stdio: ["ignore", "pipe", "pipe"], }); const chunks = []; let err = ""; p.stdout.on("data", (d) => chunks.push(d)); p.stderr.on("data", (d) => (err += d)); p.on("close", (code) => (code === 0 ? resolve(Buffer.concat(chunks)) : reject(new Error(err || `exit ${code}`)))); }); } const [a, b] = process.argv.slice(2); if (!a || !b) { console.error("usage: node tools/rawdiff.mjs "); process.exit(2); } const [da, db] = await Promise.all([decode(a), decode(b)]); if (da.length !== db.length) { console.log(`尺寸/格式不同:${da.length} vs ${db.length} 字节`); process.exit(1); } const px = da.length / 4; let diffAny = 0; let diffHidden = 0; // 仅因为 A=0 而不可见 let diffVisible = 0; let maxVisible = 0; let maxHidden = 0; for (let i = 0; i < da.length; i += 4) { const dr = Math.abs(da[i] - db[i]); const dg = Math.abs(da[i + 1] - db[i + 1]); const db_ = Math.abs(da[i + 2] - db[i + 2]); const dA = Math.abs(da[i + 3] - db[i + 3]); const m = Math.max(dr, dg, db_, dA); if (!m) continue; diffAny++; if (dA === 0 && da[i + 3] === 0) { diffHidden++; if (m > maxHidden) maxHidden = m; } else { diffVisible++; if (m > maxVisible) maxVisible = m; } } const pct = (n) => ((n / px) * 100).toFixed(4) + "%"; console.log(`${a} vs ${b}`); console.log(` 总像素 ${px}`); console.log(` 有差异像素 ${diffAny} (${pct(diffAny)})`); console.log(` 可见差异 ${diffVisible} (${pct(diffVisible)}) 最大通道差 ${maxVisible}`); console.log(` 仅透明下差异 ${diffHidden} (${pct(diffHidden)}) 最大通道差 ${maxHidden}`); console.log( diffAny === 0 ? " 结论: 逐像素相同" : diffVisible === 0 ? " 结论: 只在完全透明像素下有差异——渲染仍可能因 Linear 过滤在边缘渗色,不算安全" : " 结论: 可见像素被改动", ); process.exit(diffVisible === 0 && diffAny === 0 ? 0 : 1);