probe-industry-colors.mjs 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. // 从行业切图 PNG 取主色(支持全部 5 种扫描线滤波器;RGBA 8bit)
  2. import { readFileSync } from 'node:fs'
  3. import { inflateSync } from 'node:zlib'
  4. function pngColors(file) {
  5. const buf = readFileSync(file)
  6. let off = 8
  7. const idats = []
  8. let w = 0
  9. let h = 0
  10. let colorType = 6
  11. while (off < buf.length) {
  12. const len = buf.readUInt32BE(off)
  13. const type = buf.toString('ascii', off + 4, off + 8)
  14. if (type === 'IHDR') {
  15. w = buf.readUInt32BE(off + 8)
  16. h = buf.readUInt32BE(off + 12)
  17. colorType = buf[off + 17]
  18. }
  19. if (type === 'IDAT') idats.push(buf.slice(off + 8, off + 8 + len))
  20. off += 12 + len
  21. }
  22. const bpp = colorType === 6 ? 4 : colorType === 2 ? 3 : 4
  23. const raw = inflateSync(Buffer.concat(idats))
  24. const stride = w * bpp
  25. const recon = Buffer.alloc(h * stride)
  26. for (let y = 0; y < h; y += 1) {
  27. const filter = raw[y * (stride + 1)]
  28. const row = raw.slice(y * (stride + 1) + 1, y * (stride + 1) + 1 + stride)
  29. const prev = y > 0 ? recon.slice((y - 1) * stride, y * stride) : Buffer.alloc(stride)
  30. const cur = recon.slice(y * stride, (y + 1) * stride)
  31. for (let x = 0; x < stride; x += 1) {
  32. const a = x >= bpp ? cur[x - bpp] : 0
  33. const b = prev[x]
  34. const c = x >= bpp ? prev[x - bpp] : 0
  35. let val = row[x]
  36. if (filter === 1) val += a
  37. else if (filter === 2) val += b
  38. else if (filter === 3) val += Math.floor((a + b) / 2)
  39. else if (filter === 4) {
  40. const p = a + b - c
  41. const pa = Math.abs(p - a)
  42. const pb = Math.abs(p - b)
  43. const pc = Math.abs(p - c)
  44. val += pa <= pb && pa <= pc ? a : pb <= pc ? b : c
  45. }
  46. cur[x] = val & 0xff
  47. }
  48. }
  49. const colors = new Map()
  50. for (let i = 0; i < recon.length; i += bpp) {
  51. const alpha = bpp === 4 ? recon[i + 3] : 255
  52. if (alpha < 128) continue
  53. const key = `#${[recon[i], recon[i + 1], recon[i + 2]].map((v) => v.toString(16).padStart(2, '0')).join('')}`
  54. colors.set(key, (colors.get(key) || 0) + 1)
  55. }
  56. return [...colors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, v]) => `${k}×${v}`).join(' ')
  57. }
  58. for (const f of ['industry-gas', 'industry-water', 'industry-drainage', 'manhole']) {
  59. console.log(`${f}: ${pngColors(`public/cockpit/assets/icons/${f}.png`)}`)
  60. }