// 颜色基础:HEX/RGB 转换、OKLab 感知色彩空间、亮度 // OKLab 比 RGB 欧氏距离更接近人眼感受,配色更自然;距离统一放大 100 倍便于调参 function hexToRgb(hex) { const value = String(hex || '').replace('#', '') const full = value.length === 3 ? value.split('').map((c) => c + c).join('') : value const num = parseInt(full, 16) || 0 return [(num >> 16) & 255, (num >> 8) & 255, num & 255] } function rgbToHex(r, g, b) { const h = (v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0') return `#${h(r)}${h(g)}${h(b)}` } // sRGB 伽马解码查表,避免每次调用 Math.pow const SRGB_TO_LINEAR = new Float32Array(256) for (let i = 0; i < 256; i += 1) { const c = i / 255 SRGB_TO_LINEAR[i] = c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4) } // 写入 out[offset..offset+2](L, a, b,均 ×100) function rgbToOklabInto(r, g, b, out, offset) { const lr = SRGB_TO_LINEAR[r] const lg = SRGB_TO_LINEAR[g] const lb = SRGB_TO_LINEAR[b] const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb) const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb) const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb) out[offset] = (0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s) * 100 out[offset + 1] = (1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s) * 100 out[offset + 2] = (0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s) * 100 } // 感知亮度(与旧版一致的 Rec.601 加权,0~1),用于二维码深/浅模块约束 function luma(r, g, b) { return (r * 0.299 + g * 0.587 + b * 0.114) / 255 } function lumaHex(hex) { const [r, g, b] = hexToRgb(hex) return luma(r, g, b) } module.exports = { hexToRgb, rgbToHex, rgbToOklabInto, luma, lumaHex }