import { init, parse_color, lighten_color, darken_color, saturate_color, desaturate_color, rotate_hue, complement_color, generate_random_colors, generate_gradient, generate_palette, version, } from '@valknarthing/pastel-wasm'; import type { ApiResponse, ColorInfoRequest, ColorInfoData, ConvertFormatRequest, ConvertFormatData, ColorManipulationRequest, ColorManipulationData, RandomColorsRequest, RandomColorsData, GradientRequest, GradientData, HealthData, CapabilitiesData, PaletteGenerateRequest, PaletteGenerateData, } from './types'; // Initialize WASM module let wasmInitialized = false; async function ensureWasmInit() { if (!wasmInitialized) { init(); // Initialize panic hook wasmInitialized = true; } } /** * WASM-based Color client * Provides the same interface as ColorAPIClient but uses WebAssembly * Zero network latency, works offline! */ export class ColorWASMClient { constructor() { // Initialize WASM eagerly ensureWasmInit().catch(console.error); } private async request(fn: () => T): Promise> { try { await ensureWasmInit(); const data = fn(); return { success: true, data, }; } catch (error) { return { success: false, error: { code: 'WASM_ERROR', message: error instanceof Error ? error.message : 'Unknown error', }, }; } } // Color Information async getColorInfo(request: ColorInfoRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => { const info = parse_color(colorStr) as any; return { input: info.input, hex: info.hex, rgb: { r: info.rgb[0], g: info.rgb[1], b: info.rgb[2], }, hsl: { h: info.hsl[0], s: info.hsl[1], l: info.hsl[2], }, hsv: { h: info.hsv[0], s: info.hsv[1], v: info.hsv[2], }, lab: { l: info.lab[0], a: info.lab[1], b: info.lab[2], }, oklab: { l: info.oklab ? info.oklab[0] : info.lab[0] / 100.0, a: info.oklab ? info.oklab[1] : info.lab[1] / 100.0, b: info.oklab ? info.oklab[2] : info.lab[2] / 100.0, }, lch: { l: info.lch[0], c: info.lch[1], h: info.lch[2], }, oklch: { l: info.oklch ? info.oklch[0] : info.lch[0] / 100.0, c: info.oklch ? info.oklch[1] : info.lch[1] / 100.0, h: info.oklch ? info.oklch[2] : info.lch[2], }, cmyk: { c: 0, m: 0, y: 0, k: 0, }, brightness: info.brightness, luminance: info.luminance, is_light: info.is_light, }; }); return { colors }; }); } // Format Conversion async convertFormat(request: ConvertFormatRequest): Promise> { return this.request(() => { const conversions = request.colors.map((colorStr) => { const parsed = parse_color(colorStr) as any; let output: string; switch (request.format) { case 'hex': output = parsed.hex; break; case 'rgb': output = `rgb(${parsed.rgb[0]}, ${parsed.rgb[1]}, ${parsed.rgb[2]})`; break; case 'hsl': output = `hsl(${parsed.hsl[0].toFixed(1)}, ${(parsed.hsl[1] * 100).toFixed(1)}%, ${(parsed.hsl[2] * 100).toFixed(1)}%)`; break; case 'hsv': output = `hsv(${parsed.hsv[0].toFixed(1)}, ${(parsed.hsv[1] * 100).toFixed(1)}%, ${(parsed.hsv[2] * 100).toFixed(1)}%)`; break; case 'lab': output = `lab(${parsed.lab[0].toFixed(2)}, ${parsed.lab[1].toFixed(2)}, ${parsed.lab[2].toFixed(2)})`; break; case 'lch': output = `lch(${parsed.lch[0].toFixed(2)}, ${parsed.lch[1].toFixed(2)}, ${parsed.lch[2].toFixed(2)})`; break; case 'oklab': { const l = parsed.oklab ? parsed.oklab[0] : parsed.lab[0] / 100.0; const a = parsed.oklab ? parsed.oklab[1] : parsed.lab[1] / 100.0; const b = parsed.oklab ? parsed.oklab[2] : parsed.lab[2] / 100.0; output = `oklab(${(l * 100).toFixed(1)}% ${a.toFixed(3)} ${b.toFixed(3)})`; break; } case 'oklch': { const l = parsed.oklch ? parsed.oklch[0] : parsed.lch[0] / 100.0; const c = parsed.oklch ? parsed.oklch[1] : parsed.lch[1] / 100.0; const h = parsed.oklch ? parsed.oklch[2] : parsed.lch[2]; output = `oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(2)})`; break; } default: output = parsed.hex; } return { input: colorStr, output, }; }); return { conversions }; }); } // Color Manipulation async lighten(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: lighten_color(colorStr, request.amount), })); return { operation: 'lighten', amount: request.amount, colors }; }); } async darken(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: darken_color(colorStr, request.amount), })); return { operation: 'darken', amount: request.amount, colors }; }); } async saturate(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: saturate_color(colorStr, request.amount), })); return { operation: 'saturate', amount: request.amount, colors }; }); } async desaturate(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: desaturate_color(colorStr, request.amount), })); return { operation: 'desaturate', amount: request.amount, colors }; }); } async rotate(request: ColorManipulationRequest): Promise> { return this.request(() => { const colors = request.colors.map((colorStr) => ({ input: colorStr, output: rotate_hue(colorStr, request.amount), })); return { operation: 'rotate', amount: request.amount, colors }; }); } async complement(colors: string[]): Promise> { return this.request(() => { const results = colors.map((colorStr) => ({ input: colorStr, output: complement_color(colorStr), })); return { operation: 'complement', colors: results }; }); } async grayscale(colors: string[]): Promise> { return this.request(() => { const results = colors.map((colorStr) => ({ input: colorStr, output: desaturate_color(colorStr, 1.0), })); return { operation: 'grayscale', colors: results }; }); } // Color Generation async generateRandom(request: RandomColorsRequest): Promise> { return this.request(() => { const vivid = request.strategy === 'vivid' || request.strategy === 'lch'; const colors = generate_random_colors(request.count, vivid); return { colors }; }); } async generateGradient(request: GradientRequest): Promise> { return this.request(() => { if (request.stops.length < 2) { throw new Error('At least 2 color stops are required'); } // For 2 stops, use the WASM gradient function if (request.stops.length === 2) { const gradient = generate_gradient(request.stops[0], request.stops[1], request.count); return { stops: request.stops, count: request.count, gradient, }; } // For multiple stops, interpolate segments const segments = request.stops.length - 1; const colorsPerSegment = Math.floor(request.count / segments); const gradient: string[] = []; for (let i = 0; i < segments; i++) { const segmentColors = generate_gradient( request.stops[i], request.stops[i + 1], i === segments - 1 ? request.count - gradient.length : colorsPerSegment ); gradient.push(...segmentColors.slice(0, -1)); // Avoid duplicates } gradient.push(request.stops[request.stops.length - 1]); return { stops: request.stops, count: request.count, gradient, }; }); } // System async getHealth(): Promise> { return this.request(() => ({ status: 'healthy', version: version(), })); } async getCapabilities(): Promise> { return this.request(() => ({ endpoints: [ 'colors/info', 'colors/convert', 'colors/lighten', 'colors/darken', 'colors/saturate', 'colors/desaturate', 'colors/rotate', 'colors/complement', 'colors/grayscale', 'colors/random', 'colors/gradient', 'colors/names', ], formats: ['hex', 'rgb', 'hsl', 'hsv', 'lab', 'lch'], distance_metrics: ['cie76', 'ciede2000'], colorblindness_types: ['protanopia', 'deuteranopia', 'tritanopia'], })); } // Palette Generation async generatePalette(request: PaletteGenerateRequest): Promise> { return this.request(() => { const colors = generate_palette(request.base, request.scheme); return { base: request.base, scheme: request.scheme, palette: { primary: colors[0], secondary: colors.slice(1), }, }; }); } } // Export singleton instance export const colorWASM = new ColorWASMClient();