export function clamp(value, minimum, maximum) { return Math.min(maximum, Math.max(minimum, Number(value))); } export function normalizeHex(value) { const match = /^#?([0-9a-f]{6})$/i.exec(String(value).trim()); return match ? `#${match[1].toUpperCase()}` : null; } export function rgbToHex({ r, g, b }) { return `#${[r, g, b] .map((channel) => Math.round(clamp(channel, 0, 255)).toString(16).padStart(2, "0")) .join("")}`.toUpperCase(); } export function hexToRgb(value) { const hex = normalizeHex(value); if (!hex) throw new TypeError("颜色必须是 #RRGGBB"); return { r: Number.parseInt(hex.slice(1, 3), 16), g: Number.parseInt(hex.slice(3, 5), 16), b: Number.parseInt(hex.slice(5, 7), 16), }; } export function rgbToHsv({ r, g, b }) { const red = clamp(r, 0, 255) / 255; const green = clamp(g, 0, 255) / 255; const blue = clamp(b, 0, 255) / 255; const maximum = Math.max(red, green, blue); const minimum = Math.min(red, green, blue); const delta = maximum - minimum; let hue = 0; if (delta !== 0) { if (maximum === red) hue = 60 * (((green - blue) / delta) % 6); else if (maximum === green) hue = 60 * ((blue - red) / delta + 2); else hue = 60 * ((red - green) / delta + 4); } if (hue < 0) hue += 360; return { h: Math.round(hue), s: Math.round(maximum === 0 ? 0 : (delta / maximum) * 100), v: Math.round(maximum * 100), }; } export function hsvToRgb({ h, s, v }) { const hue = ((Number(h) % 360) + 360) % 360; const saturation = clamp(s, 0, 100) / 100; const value = clamp(v, 0, 100) / 100; const chroma = value * saturation; const segment = hue / 60; const secondary = chroma * (1 - Math.abs((segment % 2) - 1)); let channels = [0, 0, 0]; if (segment < 1) channels = [chroma, secondary, 0]; else if (segment < 2) channels = [secondary, chroma, 0]; else if (segment < 3) channels = [0, chroma, secondary]; else if (segment < 4) channels = [0, secondary, chroma]; else if (segment < 5) channels = [secondary, 0, chroma]; else channels = [chroma, 0, secondary]; const match = value - chroma; return { r: Math.round((channels[0] + match) * 255), g: Math.round((channels[1] + match) * 255), b: Math.round((channels[2] + match) * 255), }; } export function updateRecentColors(colors, value, limit = 10) { const normalized = normalizeHex(value); if (!normalized) return Array.isArray(colors) ? colors.slice(0, limit) : []; const existing = Array.isArray(colors) ? colors.map(normalizeHex).filter(Boolean).filter((color) => color !== normalized) : []; return [normalized, ...existing].slice(0, limit); }