| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- // 下载新版全部切图到 design-archive-v2/,按 PNG 实际像素尺寸匹配回导出图层
- import { readFileSync, writeFileSync, readdirSync, mkdirSync } from 'node:fs'
- import { createHash } from 'node:crypto'
- import { join } from 'node:path'
- const raw = '.scratch/lanhu-cockpit-asset-replacement/research/raw-v2'
- const outDir = '.scratch/lanhu-cockpit-asset-replacement/design-archive-v2'
- mkdirSync(outDir, { recursive: true })
- const files = readdirSync(raw).filter((f) => /^desktop-\d+\.json$/.test(f)).sort()
- function pngSize(buf) {
- if (buf.length < 24 || buf.readUInt32BE(0) !== 0x89504e47) return null
- return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) }
- }
- const perArtboard = []
- for (const file of files) {
- const j = JSON.parse(readFileSync(join(raw, file), 'utf8'))
- const ab = j.artboard
- const exported = []
- ;(function walk(node, path) {
- const p = path ? `${path}/${node.name}` : node.name
- if (node.hasExportImage) exported.push({ path: p, w: Math.round(node.frame?.width ?? 0), h: Math.round(node.frame?.height ?? 0), type: node.type })
- for (const ch of node.layers || []) walk(ch, p)
- })(ab, '')
- perArtboard.push({ artboard: ab.name, exported, assets: j.assets || [] })
- }
- const index = []
- for (const { artboard, exported, assets } of perArtboard) {
- const pngs = []
- for (let i = 0; i < assets.length; i++) {
- const url = assets[i]
- if (!/\.png($|\?)/i.test(url)) continue
- const res = await fetch(url)
- if (!res.ok) continue
- const buf = Buffer.from(await res.arrayBuffer())
- const size = pngSize(buf)
- pngs.push({ url, buf, ...(size || {}) })
- }
- // 按像素尺寸匹配回图层(sliceScale 在旧版为 4,新版以实际像素为准)
- const used = new Set()
- for (const p of pngs) {
- const scale = p.w && exported.find((e) => e.w) ? p.w / exported.find((e) => e.w).w : 0
- let best = null
- let bestScore = Infinity
- for (const e of exported) {
- if (used.has(e.path)) continue
- const sr = e.w ? p.w / e.w : 0
- const shr = e.h ? p.h / e.h : 0
- if (sr <= 0 || Math.abs(sr - shr) > 0.02) continue
- const score = Math.abs(sr - Math.max(1, Math.round(sr)))
- if (score < bestScore) {
- bestScore = score
- best = e
- }
- }
- if (best) used.add(best.path)
- const hash8 = (p.url.match(/([0-9a-f]{8})[0-9a-f]*\.png$/i) || [, 'x'])[1]
- const name = `${artboard.replace(/\s+/g, '_')}__${hash8}_${p.w}x${p.h}.png`
- writeFileSync(join(outDir, name), p.buf)
- index.push({
- artboard,
- layer: best ? best.path : '(unmatched)',
- frame: best ? `${best.w}x${best.h}` : '',
- file: name,
- px: `${p.w}x${p.h}`,
- bytes: p.buf.length,
- sha256: createHash('sha256').update(p.buf).digest('hex'),
- url: p.url,
- })
- }
- }
- writeFileSync(join(outDir, 'index.json'), JSON.stringify({ generatedFrom: 'raw-v2', count: index.length, files: index }, null, 1))
- console.log('downloaded png slices:', index.length)
- const bySize = {}
- for (const f of index) {
- const k = f.px
- bySize[k] = bySize[k] || []
- bySize[k].push(f)
- }
- console.log('\nsmall slices (likely icons):')
- for (const [px, list] of Object.entries(bySize).sort()) {
- const [w, h] = px.split('x').map(Number)
- if (w <= 64 && h <= 64) {
- for (const f of list) console.log(` ${px} | ${f.artboard} | ${f.layer}`)
- }
- }
- console.log('\nall sizes:', Object.keys(bySize).sort((a, b) => parseInt(a) - parseInt(b)).join(' '))
|