/** * Color utility functions for conversions and manipulation */ export interface RGB { r: number; g: number; b: number; } export interface HSV { h: number; s: number; v: number; } /** * Convert hex to RGB */ export function hexToRgb(hex: string): RGB { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16), } : { r: 0, g: 0, b: 0 }; } /** * Convert RGB to hex */ export function rgbToHex(r: number, g: number, b: number): string { return ( '#' + [r, g, b] .map((x) => { const hex = Math.round(x).toString(16); return hex.length === 1 ? '0' + hex : hex; }) .join('') ); } /** * Convert RGB to HSV */ export function rgbToHsv(r: number, g: number, b: number): HSV { r = r / 255; g = g / 255; b = b / 255; const max = Math.max(r, g, b); const min = Math.min(r, g, b); const diff = max - min; let h = 0; let s = max === 0 ? 0 : diff / max; let v = max; if (diff !== 0) { switch (max) { case r: h = ((g - b) / diff + (g < b ? 6 : 0)) / 6; break; case g: h = ((b - r) / diff + 2) / 6; break; case b: h = ((r - g) / diff + 4) / 6; break; } } return { h: h * 360, s: s * 100, v: v * 100, }; } /** * Convert HSV to RGB */ export function hsvToRgb(h: number, s: number, v: number): RGB { h = h / 360; s = s / 100; v = v / 100; let r = 0, g = 0, b = 0; const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: (r = v), (g = t), (b = p); break; case 1: (r = q), (g = v), (b = p); break; case 2: (r = p), (g = v), (b = t); break; case 3: (r = p), (g = q), (b = v); break; case 4: (r = t), (g = p), (b = v); break; case 5: (r = v), (g = p), (b = q); break; } return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), }; } /** * Get color from canvas at coordinates */ export function getColorAtPoint( ctx: CanvasRenderingContext2D, x: number, y: number ): string { const imageData = ctx.getImageData(x, y, 1, 1); const data = imageData.data; return rgbToHex(data[0], data[1], data[2]); } /** * Validate hex color */ export function isValidHex(hex: string): boolean { return /^#?[0-9A-F]{6}$/i.test(hex); } /** * Ensure hex has # prefix */ export function normalizeHex(hex: string): string { return hex.startsWith('#') ? hex : '#' + hex; } /** * Default color palette */ export const DEFAULT_PALETTE = [ '#000000', '#FFFFFF', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF', '#FF8800', '#8800FF', '#00FF88', '#FF0088', '#808080', '#C0C0C0', '#800000', '#008000', '#000080', '#808000', '#800080', '#008080', ];