import sharp from "sharp"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const target = join(__dirname, "..", "src", "assets", "icons", "castle-gold-land.png"); /** Near-black threshold: R+G+B <= this counts as background candidate */ const NEAR_BLACK = 40; /** Soften: fade alpha within this distance (px) of flood-filled region */ const SOFT_RADIUS = 2; const { data, info } = await sharp(target) .ensureAlpha() .raw() .toBuffer({ resolveWithObject: true }); const { width: w, height: h } = info; const n = w * h; const out = Buffer.from(data); const bg = new Uint8Array(n); // 1 = background to remove const isNearBlack = (i) => { const o = i * 4; return out[o] + out[o + 1] + out[o + 2] <= NEAR_BLACK; }; const queue = []; const push = (x, y) => { const i = y * w + x; if (bg[i]) return; if (!isNearBlack(i)) return; bg[i] = 1; queue.push(i); }; // Seed BFS from all border pixels that are near-black for (let x = 0; x < w; x++) { push(x, 0); push(x, h - 1); } for (let y = 0; y < h; y++) { push(0, y); push(w - 1, y); } // Flood-fill only connected near-black from edges (internal dark windows stay) const dirs = [ [1, 0], [-1, 0], [0, 1], [0, -1], ]; let qh = 0; while (qh < queue.length) { const i = queue[qh++]; const x = i % w; const y = (i / w) | 0; for (const [dx, dy] of dirs) { const nx = x + dx; const ny = y + dy; if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue; push(nx, ny); } } // Soften edges: for non-bg pixels near bg, reduce alpha by proximity const soft = new Float32Array(n); for (let i = 0; i < n; i++) { if (bg[i]) { soft[i] = 0; continue; } const x = i % w; const y = (i / w) | 0; let minD = SOFT_RADIUS + 1; for (let dy = -SOFT_RADIUS; dy <= SOFT_RADIUS; dy++) { for (let dx = -SOFT_RADIUS; dx <= SOFT_RADIUS; dx++) { const nx = x + dx; const ny = y + dy; if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue; const j = ny * w + nx; if (!bg[j]) continue; const d = Math.hypot(dx, dy); if (d < minD) minD = d; } } if (minD <= SOFT_RADIUS) { soft[i] = minD / (SOFT_RADIUS + 0.001); } else { soft[i] = 1; } } let removed = 0; for (let i = 0; i < n; i++) { const a = i * 4 + 3; if (bg[i]) { out[a] = 0; removed++; } else if (soft[i] < 1) { out[a] = Math.round(out[a] * soft[i]); } } await sharp(out, { raw: { width: w, height: h, channels: 4 }, }) .png() .toFile(target); console.log(`Wrote ${target}`); console.log(`Removed ${removed} background pixels (edge flood-fill); soft radius=${SOFT_RADIUS}`);