import fs from "node:fs"; const meta = JSON.parse(fs.readFileSync("clip.rgba.json", "utf8")); const px = fs.readFileSync("clip.rgba"); const { w, ch, h } = meta; const at = (x, y) => { const o = (y * w + x) * ch; return [px[o], px[o + 1], px[o + 2]]; }; const D = (a, b) => Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]); const hex = (c) => c.map((v) => v.toString(16).padStart(2, "0")).join(""); const Y0 = Number(process.argv[2] || 372); const Y1 = Number(process.argv[3] || 500); const isInk = (x, y) => { const c = at(x, y); if (D(c, [255, 255, 255]) <= 20) return false; if (D(c, [227, 234, 246]) <= 14) return false; if (D(c, [230, 235, 244]) <= 14) return false; if (D(c, [236, 237, 243]) <= 10) return false; return true; }; const seen = new Uint8Array(w * h); const comps = []; for (let y = Y0; y < Y1; y++) { for (let x = 0; x < w; x++) { if (seen[y * w + x] || !isInk(x, y)) continue; let minX = x, maxX = x, minY = y, maxY = y, n = 0, sr = 0, sg = 0, sb = 0; const st = [x, y]; seen[y * w + x] = 1; while (st.length) { const cy = st.pop(), cx = st.pop(); n++; const c = at(cx, cy); sr += c[0]; sg += c[1]; sb += c[2]; if (cx < minX) minX = cx; if (cx > maxX) maxX = cx; if (cy < minY) minY = cy; if (cy > maxY) maxY = cy; for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) { const nx = cx + dx, ny = cy + dy; if (nx < 0 || nx >= w || ny < Y0 || ny >= Y1) continue; if (!seen[ny * w + nx] && isInk(nx, ny)) { seen[ny * w + nx] = 1; st.push(nx, ny); } } } if (n > 25) comps.push({ x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1, n, c: [Math.round(sr / n), Math.round(sg / n), Math.round(sb / n)] }); } } comps.sort((a, b) => a.x - b.x); console.log(`区域 y${Y0}-${Y1} 内「墨迹」连通域 ${comps.length} 个(按 x 排序,CSS 坐标):`); for (const b of comps) { console.log(` x${Math.round(b.x / 2)} y${Math.round(b.y / 2)} ${Math.round(b.w / 2)}x${Math.round(b.h / 2)} px${b.n} avg#${hex(b.c)}`); }