export interface Hsv { h: number; s: number; v: number } const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value)); const byte = (value: number) => Math.round(clamp(value, 0, 255)).toString(16).padStart(2, '0'); /** "#rgb" / "#rrggbb"(大小写均可)转成小写 "#rrggbb",其他返回 undefined */ export function parseHex(text: string): string | undefined { const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(text.trim()); if (!m) return undefined; const hex = m[1]!.toLowerCase(); return `#${hex.length === 3 ? [...hex].map((c) => c + c).join('') : hex}`; } export function hexToHsv(hex: string): Hsv { const value = parseHex(hex) ?? '#000000'; const r = parseInt(value.slice(1, 3), 16) / 255; const g = parseInt(value.slice(3, 5), 16) / 255; const b = parseInt(value.slice(5, 7), 16) / 255; const max = Math.max(r, g, b); const delta = max - Math.min(r, g, b); let h = 0; if (delta) { if (max === r) h = ((g - b) / delta) % 6; else if (max === g) h = (b - r) / delta + 2; else h = (r - g) / delta + 4; h = (h * 60 + 360) % 360; } return { h, s: max ? delta / max : 0, v: max }; } export function hsvToHex({ h, s, v }: Hsv): string { const f = (n: number) => { const k = (n + h / 60) % 6; return v - v * s * Math.max(0, Math.min(k, 4 - k, 1)); }; return `#${byte(f(5) * 255)}${byte(f(3) * 255)}${byte(f(1) * 255)}`; } /** * 从一段文字里取出颜色:兼容在线渐变生成器复制出来的 CSS(linear-gradient(90deg, #ff8a3d 0%, rgb(240,54,107) 100%) 之类)。 * 按出现顺序最多取 max 个,透明度忽略。 */ export function extractColors(text: string, max = 5): string[] { const found: string[] = []; const re = /#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3})\b|rgba?\(\s*(\d{1,3})[\s,]+(\d{1,3})[\s,]+(\d{1,3})/gi; for (let m = re.exec(text); m && found.length < max; m = re.exec(text)) { if (m[1]) found.push(parseHex(m[1].length === 8 ? m[1].slice(0, 6) : m[1])!); else found.push(`#${byte(Number(m[2]))}${byte(Number(m[3]))}${byte(Number(m[4]))}`); } return found; }