chore: establish YCode replication baseline and TODO
This commit is contained in:
commit
6dc59f89ba
1118 files changed
+196002
No files matched your search
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* 无依赖 PNG 像素差异对比(8 位、非隔行、RGB/RGBA)。
|
||||
*
|
||||
* 用途:验证 CSS 重构(如 token 化)是否改变了实际渲染。
|
||||
*
|
||||
* 用法:node scripts/pngdiff.mjs <before.png> <after.png> [channelThreshold]
|
||||
*/
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { inflateSync } from 'node:zlib'
|
||||
|
||||
function decodePng(buf) {
|
||||
if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error('不是 PNG')
|
||||
let pos = 8
|
||||
let width = 0
|
||||
let height = 0
|
||||
let bitDepth = 0
|
||||
let colorType = 0
|
||||
let interlace = 0
|
||||
const idat = []
|
||||
|
||||
while (pos < buf.length) {
|
||||
const len = buf.readUInt32BE(pos)
|
||||
const type = buf.toString('ascii', pos + 4, pos + 8)
|
||||
const data = buf.subarray(pos + 8, pos + 8 + len)
|
||||
if (type === 'IHDR') {
|
||||
width = data.readUInt32BE(0)
|
||||
height = data.readUInt32BE(4)
|
||||
bitDepth = data[8]
|
||||
colorType = data[9]
|
||||
interlace = data[12]
|
||||
} else if (type === 'IDAT') {
|
||||
idat.push(data)
|
||||
} else if (type === 'IEND') {
|
||||
break
|
||||
}
|
||||
pos += 12 + len
|
||||
}
|
||||
|
||||
if (bitDepth !== 8) throw new Error(`仅支持 8 位深度,实际 ${bitDepth}`)
|
||||
if (interlace !== 0) throw new Error('不支持隔行 PNG')
|
||||
const channels = colorType === 6 ? 4 : colorType === 2 ? 3 : 0
|
||||
if (!channels) throw new Error(`不支持的颜色类型 ${colorType}`)
|
||||
|
||||
const raw = inflateSync(Buffer.concat(idat))
|
||||
const stride = width * channels
|
||||
const pixels = Buffer.alloc(height * stride)
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
const filter = raw[y * (stride + 1)]
|
||||
const line = raw.subarray(y * (stride + 1) + 1, y * (stride + 1) + 1 + stride)
|
||||
const prev = y > 0 ? pixels.subarray((y - 1) * stride, y * stride) : null
|
||||
const out = pixels.subarray(y * stride, (y + 1) * stride)
|
||||
for (let i = 0; i < stride; i++) {
|
||||
const a = i >= channels ? out[i - channels] : 0
|
||||
const b = prev ? prev[i] : 0
|
||||
const c = prev && i >= channels ? prev[i - channels] : 0
|
||||
let v = line[i]
|
||||
switch (filter) {
|
||||
case 0:
|
||||
break
|
||||
case 1:
|
||||
v = (v + a) & 0xff
|
||||
break
|
||||
case 2:
|
||||
v = (v + b) & 0xff
|
||||
break
|
||||
case 3:
|
||||
v = (v + ((a + b) >> 1)) & 0xff
|
||||
break
|
||||
case 4: {
|
||||
const p = a + b - c
|
||||
const pa = Math.abs(p - a)
|
||||
const pb = Math.abs(p - b)
|
||||
const pc = Math.abs(p - c)
|
||||
const pr = pa <= pb && pa <= pc ? a : pb <= pc ? b : c
|
||||
v = (v + pr) & 0xff
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`未知过滤器 ${filter}`)
|
||||
}
|
||||
out[i] = v
|
||||
}
|
||||
}
|
||||
return { width, height, channels, pixels }
|
||||
}
|
||||
|
||||
const [, , beforePath, afterPath, thresholdArg] = process.argv
|
||||
const threshold = Number(thresholdArg ?? 0)
|
||||
|
||||
const a = decodePng(readFileSync(beforePath))
|
||||
const b = decodePng(readFileSync(afterPath))
|
||||
|
||||
if (a.width !== b.width || a.height !== b.height) {
|
||||
console.log(`尺寸不同:${a.width}x${a.height} vs ${b.width}x${b.height}`)
|
||||
const minW = Math.min(a.width, b.width)
|
||||
const minH = Math.min(a.height, b.height)
|
||||
console.log(`仅比较重叠区域 ${minW}x${minH}`)
|
||||
}
|
||||
|
||||
const w = Math.min(a.width, b.width)
|
||||
const h = Math.min(a.height, b.height)
|
||||
let changed = 0
|
||||
let maxDelta = 0
|
||||
let sumDelta = 0
|
||||
const perChannel = [0, 0, 0, 0]
|
||||
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
let diff = false
|
||||
for (let c = 0; c < 3; c++) {
|
||||
const ia = y * a.width * a.channels + x * a.channels + c
|
||||
const ib = y * b.width * b.channels + x * b.channels + c
|
||||
const d = Math.abs(a.pixels[ia] - b.pixels[ib])
|
||||
if (d > threshold) diff = true
|
||||
if (d > 0) perChannel[c] += 1
|
||||
if (d > maxDelta) maxDelta = d
|
||||
sumDelta += d
|
||||
}
|
||||
if (diff) changed++
|
||||
}
|
||||
}
|
||||
|
||||
const total = w * h
|
||||
console.log(`总像素 ${total}`)
|
||||
console.log(`变化像素 ${changed} (${((changed / total) * 100).toFixed(3)}%)`)
|
||||
console.log(`最大通道差 ${maxDelta}`)
|
||||
console.log(`平均通道差 ${(sumDelta / (total * 3)).toFixed(4)}`)
|
||||
console.log(`通道变化数 R=${perChannel[0]} G=${perChannel[1]} B=${perChannel[2]}`)
|
||||
Reference in new issue
Block a user