Initial commit: Vue 3 Gold Land frontend
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import sharp from 'sharp'
|
||||
import { writeFileSync } from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname, join } from 'path'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const input = join(__dirname, '../src/assets/profile/avatar.png')
|
||||
|
||||
const { data, info } = await sharp(input)
|
||||
.ensureAlpha()
|
||||
.raw()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const r = data[i]
|
||||
const g = data[i + 1]
|
||||
const b = data[i + 2]
|
||||
const sum = r + g + b
|
||||
|
||||
const isBlack = sum < 55
|
||||
const isNavy = b > r + 8 && b > g + 2 && sum < 140
|
||||
const isDarkBlueBg = b > 15 && r < 35 && g < 45 && b < 90 && sum < 100
|
||||
|
||||
if (isBlack || isNavy || isDarkBlueBg) {
|
||||
data[i + 3] = 0
|
||||
}
|
||||
}
|
||||
|
||||
const output = await sharp(data, {
|
||||
raw: { width: info.width, height: info.height, channels: 4 },
|
||||
})
|
||||
.png()
|
||||
.toBuffer()
|
||||
|
||||
writeFileSync(input, output)
|
||||
console.log('Done:', info.width, 'x', info.height)
|
||||
@@ -0,0 +1,113 @@
|
||||
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}`);
|
||||
@@ -0,0 +1,131 @@
|
||||
import sharp from "sharp";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { dirname, join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const target = join(__dirname, "..", "src", "assets", "illustrations", "treasure-chest.png");
|
||||
const tempOut = join(tmpdir(), "treasure-chest-out.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 chest parts 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]);
|
||||
}
|
||||
}
|
||||
|
||||
// Write via ASCII temp path — Node/libvips can fail writing Unicode apostrophe paths on Windows
|
||||
await sharp(out, {
|
||||
raw: { width: w, height: h, channels: 4 },
|
||||
})
|
||||
.png()
|
||||
.toFile(tempOut);
|
||||
|
||||
const ps = spawnSync(
|
||||
"powershell.exe",
|
||||
[
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
`Copy-Item -LiteralPath '${tempOut.replace(/'/g, "''")}' -Destination '${target.replace(/'/g, "''")}' -Force`,
|
||||
],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
if (ps.status !== 0) {
|
||||
console.error(ps.stderr || ps.stdout);
|
||||
process.exit(ps.status ?? 1);
|
||||
}
|
||||
|
||||
console.log(`Wrote ${target}`);
|
||||
console.log(`Removed ${removed} background pixels (edge flood-fill); soft radius=${SOFT_RADIUS}`);
|
||||
Reference in New Issue
Block a user