Files
feuerwerk/src/ui/color.ts
T
rootandClaude Fable 5.1 5a46124454 Initial commit: Game Maker phase 1
Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript.

- core: asset model (Scene/Camera/Sprite), scene objects with sparse
  overrides, property schema + resolve, command stack (undo/redo),
  serialization and project store interface (16 unit tests)
- ui: custom in-canvas widget toolkit (panels, fields, color picker,
  list/scroll, popup) with a hidden <input> keyboard bridge
- editor: asset/hierarchy panels, schema-driven inspector, toolbar
- viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan
- runtime: world/entity instantiation for play mode, input, camera follow
- api: PHP JSON project store with revision counter (409 on conflict)
- tools: CDP-based e2e smoke test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 10:07:06 +02:00

64 lines
1.9 KiB
TypeScript

/** Color helpers. Colors are 0xRRGGBB numbers throughout the application. */
export interface HSV {
h: number; // 0..360
s: number; // 0..1
v: number; // 0..1
}
export function toHex(color: number): string {
return '#' + (color & 0xffffff).toString(16).padStart(6, '0');
}
/** Parses "#rgb", "#rrggbb", "rrggbb". Returns null when invalid. */
export function parseHex(text: string): number | null {
const t = text.trim().replace(/^#/, '');
if (/^[0-9a-f]{3}$/i.test(t)) {
const [r, g, b] = t.split('') as [string, string, string];
return parseInt(r + r + g + g + b + b, 16);
}
if (/^[0-9a-f]{6}$/i.test(t)) return parseInt(t, 16);
return null;
}
export function rgbToHsv(color: number): HSV {
const r = ((color >> 16) & 0xff) / 255;
const g = ((color >> 8) & 0xff) / 255;
const b = (color & 0xff) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const d = max - min;
let h = 0;
if (d > 0) {
if (max === r) h = ((g - b) / d) % 6;
else if (max === g) h = (b - r) / d + 2;
else h = (r - g) / d + 4;
h *= 60;
if (h < 0) h += 360;
}
return { h, s: max === 0 ? 0 : d / max, v: max };
}
export function hsvToRgb({ h, s, v }: HSV): number {
const c = v * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = v - c;
let r = 0, g = 0, b = 0;
if (h < 60) [r, g, b] = [c, x, 0];
else if (h < 120) [r, g, b] = [x, c, 0];
else if (h < 180) [r, g, b] = [0, c, x];
else if (h < 240) [r, g, b] = [0, x, c];
else if (h < 300) [r, g, b] = [x, 0, c];
else [r, g, b] = [c, 0, x];
const to = (n: number) => Math.round((n + m) * 255);
return (to(r) << 16) | (to(g) << 8) | to(b);
}
/** Perceived brightness 0..1, used to pick readable text on a swatch. */
export function luminance(color: number): number {
const r = (color >> 16) & 0xff;
const g = (color >> 8) & 0xff;
const b = color & 0xff;
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
}