UNPKG

@fimbul-works/vec-color

Version:

A comprehensive, type-safe color manipulation library for TypeScript that provides a wide range of color space conversions, blending operations, and accessibility utilities.

95 lines (94 loc) 3.08 kB
import { labToRGB, rgbToLAB } from "./lab"; import { Vec3 } from "@fimbul-works/vec"; /** * Checks if a color is within the sRGB gamut * @param color Color to check * @returns Boolean indicating if color is in gamut */ export function isInGamut(color) { return (color.x >= 0 && color.x <= 1 && color.y >= 0 && color.y <= 1 && color.z >= 0 && color.z <= 1); } /** * Clips a color to the sRGB gamut * Simple but can lose relationships between colors * @param color Color to clip * @returns Clipped color */ export function clipToGamut(color) { return new Vec3(Math.min(1, Math.max(0, color.x)), Math.min(1, Math.max(0, color.y)), Math.min(1, Math.max(0, color.z))); } /** * Compresses out-of-gamut colors while preserving relationships * More sophisticated than clipping but more computationally expensive * @param color Color to compress * @param preserveHue Whether to preserve the color's hue (default: true) * @returns Compressed color within gamut */ export function compressToGamut(color, preserveHue = true) { if (isInGamut(color)) return color; const lab = rgbToLAB(color); let compressed; if (preserveHue) { // Preserve hue by adjusting only lightness and chroma const L = lab.x; const a = lab.y; const b = lab.z; const chroma = Math.sqrt(a * a + b * b); // Binary search for maximum in-gamut chroma let low = 0; let high = chroma; let bestInGamut = clipToGamut(color); for (let i = 0; i < 8; i++) { const mid = (low + high) / 2; const scale = mid / chroma; const testLab = new Vec3(L, a * scale, b * scale); const testRgb = labToRGB(testLab); if (isInGamut(testRgb)) { bestInGamut = testRgb; low = mid; } else { high = mid; } } compressed = bestInGamut; } else { // Simple compression in LAB space const center = new Vec3(50, 0, 0); // LAB middle gray const vector = lab.subtract(center); let scale = 1; while (!isInGamut((compressed = labToRGB(center.add(vector.scale(scale))))) && scale > 0) { scale *= 0.9; } } return compressed; } /** * Projects an out-of-gamut color back into gamut while preserving lightness * Useful for maintaining perceived brightness while ensuring displayable colors * @param color Color to project * @returns Projected in-gamut color */ export function projectToGamut(color) { if (isInGamut(color)) return color; const lab = rgbToLAB(color); const L = lab.x; let result = clipToGamut(color); let resultLab = rgbToLAB(result); // Adjust result to maintain original lightness const lightnessDiff = L - resultLab.x; if (Math.abs(lightnessDiff) > 0.01) { resultLab = new Vec3(L, resultLab.y, resultLab.z); result = clipToGamut(labToRGB(resultLab)); } return result; }