feat: item color map, fix regex matching, fix sort order, fix resize handle
This commit is contained in:
87
web/bin/fix-colors.ts
Normal file
87
web/bin/fix-colors.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
const CSV = 'public/factorio_item_colors.csv';
|
||||
|
||||
function hexToHsl(h: string): [number, number, number] {
|
||||
let r = parseInt(h.slice(1, 3), 16) / 255, g = parseInt(h.slice(3, 5), 16) / 255, b = parseInt(h.slice(5, 7), 16) / 255;
|
||||
const mx = Math.max(r, g, b), mn = Math.min(r, g, b), l = (mx + mn) / 2;
|
||||
if (mx === mn) return [0, 0, Math.round(l * 100)];
|
||||
const d = mx - mn, s = l > 0.5 ? d / (2 - mx - mn) : d / (mx + mn);
|
||||
let hue = 0;
|
||||
if (mx === r) hue = ((g - b) / d + (g < b ? 6 : 0)) * 60;
|
||||
else if (mx === g) hue = ((b - r) / d + 2) * 60;
|
||||
else hue = ((r - g) / d + 4) * 60;
|
||||
return [Math.round(hue), Math.round(s * 100), Math.round(l * 100)];
|
||||
}
|
||||
function hslToHex(h: number, s: number, l: number): string {
|
||||
s /= 100; l /= 100;
|
||||
const c = (1 - Math.abs(2 * l - 1)) * s, x = c * (1 - Math.abs(((h / 60) % 2) - 1)), m = l - c / 2;
|
||||
let r = 0, g = 0, b = 0;
|
||||
if (h < 60) { r = c; g = x; } else if (h < 120) { r = x; g = c; } else if (h < 180) { g = c; b = x; }
|
||||
else if (h < 240) { g = x; b = c; } else if (h < 300) { r = x; b = c; } else { r = c; b = x; }
|
||||
const to = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, '0');
|
||||
return '#' + to(r) + to(g) + to(b);
|
||||
}
|
||||
function djb2(s: string): number {
|
||||
let h = 5381;
|
||||
for (let i = 0; i < s.length; i++) h = (h * 33) ^ s.charCodeAt(i);
|
||||
return h >>> 0;
|
||||
}
|
||||
function hueDist(a: number, b: number): number {
|
||||
const d = Math.abs(a - b);
|
||||
return Math.min(d, 360 - d);
|
||||
}
|
||||
|
||||
const lines = readFileSync(CSV, 'utf-8').trim().split('\n');
|
||||
const header = lines[0];
|
||||
const raw = lines.slice(1).filter(l => l.trim()).map(l => {
|
||||
const [k, c] = l.split(',');
|
||||
return { key: k.trim(), color: c.trim() };
|
||||
});
|
||||
|
||||
// Dedup by key
|
||||
const seen = new Set<string>();
|
||||
const rows: typeof raw = [];
|
||||
for (const r of raw) { if (seen.has(r.key)) continue; seen.add(r.key); rows.push(r); }
|
||||
|
||||
const n = rows.length;
|
||||
const parent = Array.from({ length: n }, (_, i) => i);
|
||||
function find(x: number): number {
|
||||
while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; }
|
||||
return x;
|
||||
}
|
||||
function union(a: number, b: number) { parent[find(a)] = find(b); }
|
||||
|
||||
const hsl = rows.map((r, i) => ({ ...r, hsl: hexToHsl(r.color), idx: i }));
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let j = i + 1; j < n; j++) {
|
||||
if (hueDist(hsl[i].hsl[0], hsl[j].hsl[0]) <= 0.5 &&
|
||||
Math.abs(hsl[i].hsl[2] - hsl[j].hsl[2]) <= 0.5)
|
||||
union(i, j);
|
||||
}
|
||||
}
|
||||
|
||||
const groups = new Map<number, typeof hsl>();
|
||||
for (let i = 0; i < n; i++) {
|
||||
const root = find(i);
|
||||
if (!groups.has(root)) groups.set(root, []);
|
||||
groups.get(root)!.push(hsl[i]);
|
||||
}
|
||||
|
||||
let fixed = 0;
|
||||
for (const [, items] of groups) {
|
||||
if (items.length < 2) continue;
|
||||
items.sort((a, b) => a.idx - b.idx);
|
||||
const [oh, os, ol] = items[0].hsl;
|
||||
for (let i = 1; i < items.length; i++) {
|
||||
const hash = djb2(items[i].key);
|
||||
const h = (oh + (hash % 5 - 2) + i * 7 + 360) % 360;
|
||||
const l = Math.max(0, Math.min(100, ol + ((hash >> 4) % 5 - 2)));
|
||||
items[i].color = hslToHex(h, os, l);
|
||||
fixed++;
|
||||
}
|
||||
}
|
||||
|
||||
hsl.sort((a, b) => a.idx - b.idx);
|
||||
writeFileSync(CSV, header + '\n' + hsl.map(r => `${r.key},${r.color}`).join('\n') + '\n');
|
||||
if (fixed) console.log(`Fixed ${fixed} close colors`);
|
||||
Reference in New Issue
Block a user