better-color-tools
Advanced tools
Comparing version 0.7.3 to 0.8.0
# better-color-tools | ||
## 0.8.0 | ||
### Minor Changes | ||
- a6f9631: Adds sRGB gamut clamping for Oklab/Oklch | ||
- a6f9631: Improve XYZ accuracy | ||
### Patch Changes | ||
- a6f9631: Improved mix() function | ||
- a6f9631: Remove luv colorspace (at least until CSS Color Module mentions it) | ||
- a6f9631: Minor performance improvements | ||
- a6f9631: Output oklab() and oklch() functions for CSS | ||
## 0.7.3 | ||
@@ -4,0 +18,0 @@ |
@@ -15,8 +15,2 @@ export declare type HSL = [number, number, number, number]; | ||
export declare type ColorMatrix = [MatrixRow, MatrixRow, MatrixRow]; | ||
export declare const LMS_TO_OKLAB: ColorMatrix; | ||
export declare const LMS_TO_LINEAR_RGB: ColorMatrix; | ||
export declare const LINEAR_RGB_TO_XYZ_D65: ColorMatrix; | ||
export declare const LINEAR_RGB_TO_LMS: ColorMatrix; | ||
export declare const OKLAB_TO_LMS: ColorMatrix; | ||
export declare const XYZ_D65_TO_LINEAR_RGB: ColorMatrix; | ||
/** HSL -> sRGB */ | ||
@@ -27,3 +21,3 @@ export declare function hslTosRGB(hsl: HSL): sRGB; | ||
/** Lab -> LCh / Oklab -> Oklch) */ | ||
export declare function labToLCH(lab: LAB): LCH; | ||
export declare function labToLCH(lab: LAB, ε?: number): LCH; | ||
/** LCh -> Lab / Oklch -> Oklab */ | ||
@@ -39,6 +33,6 @@ export declare function lchToLAB(lch: LCH): LAB; | ||
export declare function linearRGBToLMS(lrgb: LinearRGB): LMS; | ||
/** Linear sRGB -> XYZ (D65) */ | ||
export declare function linearRGBToXYZ(rgb: LinearRGB): XYZ_D65; | ||
/** LUV -> sRGB */ | ||
/** LUV -> XYZ (D65) */ | ||
export declare function luvToXYZ(luv: LUV): XYZ_D65; | ||
/** LUV -> sRGB */ | ||
export declare function luvTosRGB(luv: LUV): sRGB; | ||
/** Oklab -> LMS */ | ||
@@ -50,6 +44,5 @@ export declare function oklabToLMS(oklab: Oklab): sRGB; | ||
export declare function oklchTosRGB(oklch: Oklch): sRGB; | ||
/** sRGB -> Linear RGB */ | ||
/** sRGB -> Linear sRGB */ | ||
export declare function sRGBToLinearRGB(rgb: sRGB): LinearRGB; | ||
/** sRGB -> Luv */ | ||
export declare function sRGBToLuv(rgb: sRGB): LUV; | ||
/** Linear sRGB -> Luv */ | ||
/** sRGB -> Oklab */ | ||
@@ -59,8 +52,5 @@ export declare function sRGBToOklab(rgb: sRGB): Oklab; | ||
export declare function sRGBToOklch(rgb: sRGB): Oklch; | ||
/** sRGB -> XYZ (D65) */ | ||
export declare function sRGBToXYZ(rgb: sRGB): XYZ_D65; | ||
/** XYZ (D65) -> sRGB */ | ||
export declare function xyzTosRGB(xyz: XYZ_D65): LinearRGB; | ||
/** XYZ (D65) -> Linear sRGB */ | ||
export declare function xyzToLinearRGB(xyz: XYZ_D65): sRGB; | ||
export {}; | ||
/** XYZ (D65) -> Luv */ | ||
export declare function xyzToLuv(xyz: XYZ_D65): LUV; | ||
export {}; |
import { clamp, degToRad, multiplyColorMatrix, radToDeg } from './utils.js'; | ||
const ε = 216 / 24389; | ||
const κ = 24389 / 27; | ||
const D65_WHITEPOINT = [95.47, 100, 108.33]; | ||
const D65_LUV_DENOMINATOR = D65_WHITEPOINT[0] + 15 * D65_WHITEPOINT[1] + 3 * D65_WHITEPOINT[2]; | ||
const D65_U_REF = (4 * D65_WHITEPOINT[0]) / D65_LUV_DENOMINATOR; | ||
const D65_V_REF = (9 * D65_WHITEPOINT[1]) / D65_LUV_DENOMINATOR; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const LMS_TO_OKLAB = [ | ||
[0.2104542553, 0.793617785, -0.0040720468], | ||
[1.9779984951, -2.428592205, 0.4505937099], | ||
[0.0259040371, 0.7827717662, -0.808675766], | ||
]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const LMS_TO_LINEAR_RGB = [ | ||
[4.0767416621, -3.3077115913, 0.2309699292], | ||
[-1.2684380046, 2.6097574011, -0.3413193965], | ||
[-0.0041960863, -0.7034186147, 1.707614701], | ||
]; | ||
// https://github.com/muak/ColorMinePortable/ | ||
export const LINEAR_RGB_TO_XYZ_D65 = [ | ||
[0.4124, 0.3576, 0.1805], | ||
[0.2126, 0.7152, 0.0722], | ||
[0.0193, 0.1192, 0.9505], | ||
]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const LINEAR_RGB_TO_LMS = [ | ||
[0.4122214708, 0.5363325363, 0.0514459929], | ||
[0.2119034982, 0.6806995451, 0.1073969566], | ||
[0.0883024619, 0.2817188376, 0.6299787005], | ||
]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const OKLAB_TO_LMS = [ | ||
[1, 0.3963377774, 0.2158037573], | ||
[1, -0.1055613458, -0.0638541728], | ||
[1, -0.0894841775, -1.291485548], | ||
]; | ||
// http://www.easyrgb.com/ | ||
export const XYZ_D65_TO_LINEAR_RGB = [ | ||
[3.2406, -1.5372, -0.4986], | ||
[-0.9689, 1.8758, 0.0415], | ||
[0.0557, -0.204, 1.057], | ||
]; | ||
import { LMS_TO_OKLAB, LMS_TO_LINEAR_RGB, LINEAR_RGB_TO_LMS, OKLAB_TO_LMS, LINEAR_RGB_TO_XYZ_D65, XYZ_D65_TO_LINEAR_RGB, findGamutIntersection } from './lib.js'; | ||
/** HSL -> sRGB */ | ||
@@ -95,10 +54,14 @@ export function hslTosRGB(hsl) { | ||
/** Lab -> LCh / Oklab -> Oklch) */ | ||
export function labToLCH(lab) { | ||
export function labToLCH(lab, ε = 0.0002) { | ||
const [L, a, b, alpha] = lab; | ||
const h = a === 0 && b === 0 ? 0 : radToDeg(Math.atan2(b, a)); // if desaturated, set hue to 0 | ||
let h = Math.abs(a) < ε && Math.abs(b) < ε ? 0 : radToDeg(Math.atan2(b, a)); // if desaturated, set hue to 0 | ||
while (h < 0) | ||
h += 360; | ||
while (h >= 360) | ||
h -= 360; | ||
return [ | ||
L, | ||
Math.sqrt(a ** 2 + b ** 2), | ||
h < 0 ? h + 360 : h, | ||
alpha, // alpha | ||
h, | ||
alpha, // alpha; | ||
]; | ||
@@ -108,8 +71,16 @@ } | ||
export function lchToLAB(lch) { | ||
const [L, C, h, alpha] = lch; | ||
let [L, C, h, alpha] = lch; | ||
// treat L === 0 as pure black | ||
if (L === 0) { | ||
return [0, 0, 0, lch[3]]; | ||
} | ||
while (h < 0) | ||
h += 360; | ||
while (h >= 360) | ||
h -= 360; | ||
const h2 = degToRad(h); | ||
return [ | ||
L, | ||
C * Math.cos(h2), | ||
C * Math.sin(h2), | ||
Math.cos(h2) * C, | ||
Math.sin(h2) * C, | ||
alpha, // alpha | ||
@@ -120,17 +91,12 @@ ]; | ||
export function lmsToOklab(lms) { | ||
let l2 = Math.cbrt(lms[0]); | ||
let m2 = Math.cbrt(lms[1]); | ||
let s2 = Math.cbrt(lms[2]); | ||
let alpha = lms[3]; | ||
return multiplyColorMatrix([l2, m2, s2, alpha], LMS_TO_OKLAB); | ||
return multiplyColorMatrix(lms, LMS_TO_OKLAB); | ||
} | ||
/** LMS -> Linear sRGB */ | ||
export function lmsToLinearRGB(lms) { | ||
const [r, g, b, a] = multiplyColorMatrix(lms, LMS_TO_LINEAR_RGB); | ||
// bump any negative #s to 0 | ||
const [r, g, b, a] = multiplyColorMatrix([lms[0] ** 3, lms[1] ** 3, lms[2] ** 3, lms[3]], LMS_TO_LINEAR_RGB); | ||
return [ | ||
Math.max(0, r), | ||
Math.max(0, g), | ||
Math.max(0, b), | ||
Math.max(0, a), // a | ||
r, | ||
g, | ||
b, | ||
a, // a | ||
]; | ||
@@ -140,47 +106,69 @@ } | ||
export function linearRGBTosRGB(rgb) { | ||
return rgb.map((value, n) => { | ||
if (n === 3) | ||
return clamp(value, 0, 1); // alpha | ||
if (value <= 0.0031308) | ||
return clamp(value * 12.92, 0, 1); | ||
else | ||
return clamp(1.055 * Math.pow(value, 1 / 2.4) - 0.055, 0, 1); | ||
}); | ||
const r = Math.abs(rgb[0]); | ||
const g = Math.abs(rgb[1]); | ||
const b = Math.abs(rgb[2]); | ||
return [ | ||
r < 0.0031308 ? rgb[0] * 12.92 : 1.055 * Math.pow(r, 1 / 2.4) - 0.055, | ||
g < 0.0031308 ? rgb[1] * 12.92 : 1.055 * Math.pow(g, 1 / 2.4) - 0.055, | ||
b < 0.0031308 ? rgb[2] * 12.92 : 1.055 * Math.pow(b, 1 / 2.4) - 0.055, | ||
rgb[3], // alpha | ||
]; | ||
} | ||
/** Linear sRGB -> LMS */ | ||
export function linearRGBToLMS(lrgb) { | ||
return multiplyColorMatrix(lrgb, LINEAR_RGB_TO_LMS); | ||
const lms = multiplyColorMatrix(lrgb, LINEAR_RGB_TO_LMS); | ||
return [ | ||
Math.cbrt(lms[0]), | ||
Math.cbrt(lms[1]), | ||
Math.cbrt(lms[2]), | ||
lms[3], | ||
]; | ||
} | ||
/** LUV -> XYZ (D65) */ | ||
export function luvToXYZ(luv) { | ||
let [L, u, v, alpha] = luv; | ||
L *= 100; | ||
u *= 100; | ||
v *= 100; | ||
const y = L > ε * κ ? ((L + 16) / 116) ** 3 : L / κ; | ||
const a = ((52 * L) / (u + 13 * L * D65_U_REF) - 1) / 3 || 0; | ||
const b = -5 * y || 0; | ||
const c = -1 / 3; | ||
const d = y * ((39 * L) / (v + 13 * L * D65_V_REF) - 5) || 0; | ||
const x = (d - b) / (a - c) || 0; | ||
const z = x * a + b; | ||
return [x, y, z, alpha]; | ||
/** Linear sRGB -> XYZ (D65) */ | ||
export function linearRGBToXYZ(rgb) { | ||
return multiplyColorMatrix(rgb, LINEAR_RGB_TO_XYZ_D65); | ||
} | ||
/** LUV -> sRGB */ | ||
export function luvTosRGB(luv) { | ||
return xyzTosRGB(luvToXYZ(luv)); | ||
} | ||
// export function luvTosRGB(luv: LUV): sRGB { | ||
// return linearRGBTosRGB(xyzToLinearRGB(luvToXYZ(luv))); | ||
// } | ||
/** LUV -> XYZ (D65) */ | ||
// export function luvToXYZ(luv: LUV, ε = 216 / 24389): XYZ_D65 { | ||
// let [L, u, v, alpha] = luv; | ||
// L; | ||
// u; | ||
// v; | ||
// const y = L > ε * D65_κ ? ((L + 16) / 116) ** 3 : L / D65_κ; | ||
// const a = ((52 * L) / (u + 13 * L * D65_U_REF) - 1) / 3 || 0; | ||
// const b = -5 * y || 0; | ||
// const c = -1 / 3; | ||
// const d = y * ((39 * L) / (v + 13 * L * D65_V_REF) - 5) || 0; | ||
// const x = (d - b) / (a - c) || 0; | ||
// const z = x * a + b; | ||
// return [x, y, z, alpha]; | ||
// } | ||
/** Oklab -> LMS */ | ||
export function oklabToLMS(oklab) { | ||
const lms = multiplyColorMatrix(oklab, OKLAB_TO_LMS); | ||
return [ | ||
lms[0] ** 3, | ||
lms[1] ** 3, | ||
lms[2] ** 3, | ||
lms[3], // alpha | ||
]; | ||
return multiplyColorMatrix(oklab, OKLAB_TO_LMS); | ||
} | ||
/** Oklab -> sRGB */ | ||
export function oklabTosRGB(oklab) { | ||
return linearRGBTosRGB(lmsToLinearRGB(oklabToLMS(oklab))); | ||
const rgb = linearRGBTosRGB(lmsToLinearRGB(oklabToLMS(oklab))); | ||
if (rgb[0] > 1.001 || rgb[0] < -0.001 || rgb[1] > 1.001 || rgb[1] < -0.001 || rgb[2] > 1.001 || rgb[2] < -0.001) { | ||
// “Preserve light, clamp Chroma” method from https://bottosson.github.io/posts/gamutclipping/ | ||
const ε = 0.00001; | ||
const [L, a, b, alpha] = oklab; | ||
const C = Math.max(ε, Math.sqrt(a ** 2 + b ** 2)); | ||
const Lgamut = clamp(L, 0, 1); | ||
const aNorm = a / C; | ||
const bNorm = b / C; | ||
const t = findGamutIntersection(aNorm, bNorm, L, C, Lgamut); | ||
return linearRGBTosRGB(lmsToLinearRGB(oklabToLMS([ | ||
Lgamut * (1 - t) + t * L, | ||
aNorm * (t * C), | ||
bNorm * (t * C), | ||
alpha, | ||
]))); | ||
} | ||
return rgb; | ||
} | ||
@@ -191,17 +179,18 @@ /** Oklch -> sRGB */ | ||
} | ||
/** sRGB -> Linear RGB */ | ||
/** sRGB -> Linear sRGB */ | ||
export function sRGBToLinearRGB(rgb) { | ||
return rgb.map((value, n) => { | ||
if (n === 3) | ||
return value; // alpha | ||
if (value <= 0.04045) | ||
return value / 12.92; | ||
else | ||
return ((value + 0.055) / 1.055) ** 2.4; | ||
}); | ||
const r = Math.abs(rgb[0]); | ||
const g = Math.abs(rgb[1]); | ||
const b = Math.abs(rgb[2]); | ||
return [ | ||
r < 0.04045 ? rgb[0] / 12.92 : ((r + 0.055) / 1.055) ** 2.4, | ||
g < 0.04045 ? rgb[1] / 12.92 : ((g + 0.055) / 1.055) ** 2.4, | ||
b < 0.04045 ? rgb[2] / 12.92 : ((b + 0.055) / 1.055) ** 2.4, | ||
rgb[3], // alpha | ||
]; | ||
} | ||
/** sRGB -> Luv */ | ||
export function sRGBToLuv(rgb) { | ||
return xyzToLuv(sRGBToXYZ(rgb)); | ||
} | ||
/** Linear sRGB -> Luv */ | ||
// export function sRGBToLuv(rgb: LinearRGB): LUV { | ||
// return xyzToLuv(linearRGBToXYZ(sRGBToLinearRGB(rgb))); | ||
// } | ||
/** sRGB -> Oklab */ | ||
@@ -215,20 +204,16 @@ export function sRGBToOklab(rgb) { | ||
} | ||
/** sRGB -> XYZ (D65) */ | ||
export function sRGBToXYZ(rgb) { | ||
return multiplyColorMatrix(sRGBToLinearRGB(rgb), LINEAR_RGB_TO_XYZ_D65); | ||
/** XYZ (D65) -> Linear sRGB */ | ||
export function xyzToLinearRGB(xyz) { | ||
return multiplyColorMatrix(xyz, XYZ_D65_TO_LINEAR_RGB); | ||
} | ||
/** XYZ (D65) -> sRGB */ | ||
export function xyzTosRGB(xyz) { | ||
return linearRGBTosRGB(multiplyColorMatrix(xyz, XYZ_D65_TO_LINEAR_RGB)); | ||
} | ||
/** XYZ (D65) -> Luv */ | ||
export function xyzToLuv(xyz) { | ||
const [x, y, z, alpha] = xyz; | ||
const denominator = x + 15 * y + 3 * z; | ||
const _u = (4 * x) / denominator || 0; | ||
const _v = (9 * y) / denominator || 0; | ||
const L = x > ε ? 116 * Math.pow(y, 1 / 3) - 16 : κ * y; | ||
const u = 13 * L * (_u - D65_U_REF) || 0; // `|| 0` fixes -0 | ||
const v = 13 * L * (_v - D65_V_REF) || 0; | ||
return [L / 100, u / 100, v / 100, alpha]; | ||
} | ||
// export function xyzToLuv(xyz: XYZ_D65, ε = 216 / 24389): LUV { | ||
// const [x, y, z, alpha] = xyz; | ||
// const denominator = x + 15 * y + 3 * z; | ||
// const _u = (4 * x) / denominator || 0; | ||
// const _v = (9 * y) / denominator || 0; | ||
// const L = x > ε ? 116 * Math.pow(y, 1 / 3) - 16 : D65_κ * y; | ||
// const u = 13 * L * (_u - D65_U_REF) || 0; // `|| 0` fixes -0 | ||
// const v = 13 * L * (_v - D65_V_REF) || 0; | ||
// return [L, u, v, alpha]; | ||
// } |
@@ -8,3 +8,3 @@ export type { ColorMatrix, HSL, HWB, LMS, LinearRGB, LUV, Oklab, Oklch, sRGB, XYZ_D65 } from './colorspace.js'; | ||
export { from, lightness } from './parse.js'; | ||
export { clamp, colorFn, leftPad, round } from './utils.js'; | ||
export { clamp, colorFn, degToRad, leftPad, multiplyColorMatrix, radToDeg, round } from './utils.js'; | ||
import { darken, lighten } from './lighten-darken.js'; | ||
@@ -11,0 +11,0 @@ import { mix } from './mix.js'; |
export { darken, lighten } from './lighten-darken.js'; | ||
export { mix } from './mix.js'; | ||
export { from, lightness } from './parse.js'; | ||
export { clamp, colorFn, leftPad, round } from './utils.js'; | ||
export { clamp, colorFn, degToRad, leftPad, multiplyColorMatrix, radToDeg, round } from './utils.js'; | ||
import { darken, lighten } from './lighten-darken.js'; | ||
@@ -6,0 +6,0 @@ import { mix } from './mix.js'; |
@@ -1,1 +0,1 @@ | ||
function m(t,e=2){let o=t;for(;o.length<e;)o=`0${o}`;return o}function X(t){return t*(Math.PI/180)}function U(t){return t*(180/Math.PI)}function l(t,e,o){return Math.min(Math.max(t,e),o)}function h(t,e){let[o,n,a,r]=e,f=r<1?`/${u(r,5)}`:"";switch(t){case"rgb":case"rgba":return r<1?`rgba(${Math.round(o*255)}, ${Math.round(n*255)}, ${Math.round(a*255)}, ${u(r,5)})`:`rgb(${Math.round(o*255)}, ${Math.round(n*255)}, ${Math.round(a*255)})`;case"oklab":case"oklch":return`color(${t} ${u(o*100,5)}% ${u(n,5)} ${u(a,5)}${f})`;default:return`color(${t} ${u(o,5)} ${u(n,5)} ${u(a,5)}${f})`}}function p(t,e){let o=[...t];for(let n=0;n<e.length;n++){let a=0;for(let r=0;r<e[0].length;r++)a+=t[r]*e[n][r];o[n]=a}return o}function u(t,e=2){let o=10**e;return Math.round(t*o)/o}var Y=216/24389,z=24389/27,M=[95.47,100,108.33],Z=M[0]+15*M[1]+3*M[2],H=4*M[0]/Z,K=9*M[1]/Z,oe=[[.2104542553,.793617785,-.0040720468],[1.9779984951,-2.428592205,.4505937099],[.0259040371,.7827717662,-.808675766]],ne=[[4.0767416621,-3.3077115913,.2309699292],[-1.2684380046,2.6097574011,-.3413193965],[-.0041960863,-.7034186147,1.707614701]],ae=[[.4124,.3576,.1805],[.2126,.7152,.0722],[.0193,.1192,.9505]],fe=[[.4122214708,.5363325363,.0514459929],[.2119034982,.6806995451,.1073969566],[.0883024619,.2817188376,.6299787005]],se=[[1,.3963377774,.2158037573],[1,-.1055613458,-.0638541728],[1,-.0894841775,-1.291485548]],le=[[3.2406,-1.5372,-.4986],[-.9689,1.8758,.0415],[.0557,-.204,1.057]];function q(t){let[e,o,n,a]=t;e=Math.abs(e%360);let r=o*(1-Math.abs(2*n-1)),f=r*(1-Math.abs(e/60%2-1)),s=0,c=0,i=0;0<=e&&e<60?(s=r,c=f):60<=e&&e<120?(s=f,c=r):120<=e&&e<180?(c=r,i=f):180<=e&&e<240?(c=f,i=r):240<=e&&e<300?(s=f,i=r):300<=e&&e<360&&(s=r,i=f);let x=n-r/2;return[s+x,c+x,i+x,a]}function j(t){let[e,o,n,a]=t;if(o+n>=1){let f=o/(o+n);return[f,f,f,a]}let r=q([e,100,50,a]);for(let f=0;f<3;f++)r[f]*=1-o-n,r[f]+=o;return r}function ce(t){let[e,o,n,a]=t,r=o===0&&n===0?0:U(Math.atan2(n,o));return[e,Math.sqrt(o**2+n**2),r<0?r+360:r,a]}function ie(t){let[e,o,n,a]=t,r=X(n);return[e,o*Math.cos(r),o*Math.sin(r),a]}function xe(t){let e=Math.cbrt(t[0]),o=Math.cbrt(t[1]),n=Math.cbrt(t[2]),a=t[3];return p([e,o,n,a],oe)}function ue(t){let[e,o,n,a]=p(t,ne);return[Math.max(0,e),Math.max(0,o),Math.max(0,n),Math.max(0,a)]}function k(t){return t.map((e,o)=>o===3?l(e,0,1):e<=.0031308?l(e*12.92,0,1):l(1.055*Math.pow(e,1/2.4)-.055,0,1))}function de(t){return p(t,fe)}function he(t){let[e,o,n,a]=t;e*=100,o*=100,n*=100;let r=e>Y*z?((e+16)/116)**3:e/z,f=(52*e/(o+13*e*H)-1)/3||0,s=-5*r||0,c=-1/3,x=((r*(39*e/(n+13*e*K)-5)||0)-s)/(f-c)||0,g=x*f+s;return[x,r,g,a]}function $(t){return F(he(t))}function be(t){let e=p(t,se);return[e[0]**3,e[1]**3,e[2]**3,e[3]]}function w(t){return k(ue(be(t)))}function E(t){return w(ie(t))}function R(t){return t.map((e,o)=>o===3?e:e<=.04045?e/12.92:((e+.055)/1.055)**2.4)}function v(t){return ge(O(t))}function y(t){return xe(de(R(t)))}function _(t){return ce(y(t))}function O(t){return p(R(t),ae)}function F(t){return k(p(t,le))}function ge(t){let[e,o,n,a]=t,r=e+15*o+3*n,f=4*e/r||0,s=9*o/r||0,c=e>Y?116*Math.pow(o,1/3)-16:z*o,i=13*c*(f-H)||0,x=13*c*(s-K)||0;return[c/100,i/100,x/100,a]}var pe={black:0,silver:12632256,gray:8421504,white:16777215,maroon:8388608,red:16711680,purple:8388736,fuchsia:16711935,green:32768,lime:65280,olive:8421376,yellow:16776960,navy:128,blue:255,teal:32896,aqua:65535,orange:16753920,aliceblue:15792383,antiquewhite:16444375,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,blanchedalmond:16772045,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,limegreen:3329330,linen:16445670,magenta:16711935,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,oldlace:16643558,olivedrab:7048739,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,whitesmoke:16119285,yellowgreen:10145074,rebeccapurple:6697881},N=pe;var me=/-?[0-9.]+%?/g,ke=/^#?[0-9a-f]{3,8}$/i,Re=16**6,D=16**4,S=16**2;function d(t){let e=ye(t),o={get hex(){let n="#";return n+=m(Math.round(e[0]*255).toString(16),2),n+=m(Math.round(e[1]*255).toString(16),2),n+=m(Math.round(e[2]*255).toString(16),2),e[3]<1&&(n+=m(Math.round(e[3]*255).toString(16),2)),n},get hexVal(){e[3]<1&&console.warn(`hexVal converted a semi-transparent color (${e[3]*100}%) to fully opaque`);let n=Math.round(e[0]*255),a=Math.round(e[1]*255),r=Math.round(e[2]*255);return n*D+a*S+r},get luv(){return h("luv",v(e))},get luvVal(){return v(e)},get rgb(){return h("rgb",e)},rgbVal:e,get rgba(){return h("rgb",e)},rgbaVal:e,get linearRGB(){return R(e)},get p3(){return h("display-p3",e)},p3Val:e,get oklab(){return h("oklab",y(e))},get oklabVal(){return y(e)},get oklch(){return h("oklch",_(e))},get oklchVal(){return _(e)},get xyz(){return h("xyz-d65",O(e))},get xyzVal(){return O(e)}};return o.toString=()=>o.hex,o}function W(t){if(t>Re)throw new Error("better-color-tools can\u2019t parse hex numbers with alpha (0x0000000 is indistinguishable from 0x00000000). Please use hex string, or another color method");let e=t,o=Math.floor(e/D);e-=o*D;let n=Math.floor(e/S);e-=n*S;let a=e;return[o/255,n/255,a/255,1]}function b(t,e){let o=t.match(me);if(!o)throw new Error(`Unexpected color format: ${t}`);let n=[0,0,0,1];return o.forEach((a,r)=>{a.includes("%")?n[r]=parseFloat(a)/100:e[r]===1/0?n[r]=parseFloat(a):n[r]=parseFloat(a)/(e[r]||1)}),n}function ye(t){if(Array.isArray(t)){if(t.some(e=>typeof e!="number"))throw new Error(`Color array must be numbers, received ${t}`);if(t.length<3||t.length>4)throw new Error(`Expected [R, G, B, A?], received ${t}`);return[l(t[0],0,1),l(t[1],0,1),l(t[2],0,1),typeof t[3]=="number"?l(t[3],0,1):1]}if(typeof t=="number")return W(t);if(typeof t=="string"){let e=t.trim();if(!e)throw new Error("Expected color, received empty string");let o=e.toLowerCase();if(typeof N[o]=="number")return W(N[o]);if(ke.test(e)){let r=e.replace("#",""),f=[0,0,0,1];if(r.length>=6)for(let s=0;s<r.length/2;s++){let c=s*2,i=c+2,x=r.substring(c,i);f[s]=parseInt(x,16)/255}else for(let s=0;s<r.length;s++){let c=r.charAt(s);f[s]=parseInt(`${c}${c}`,16)/255}return f}let[n,a]=e.split("(");if(n==="color"){let r=a.indexOf(" ");n=a.substring(0,r),a=a.substring(r)}switch(n){case"rgb":case"rgba":case"srgb":{let[r,f,s,c]=b(a,[255,255,255,1]);return[l(r,0,1),l(f,0,1),l(s,0,1),l(c,0,1)]}case"srgb-linear":{let[r,f,s,c]=b(a,[255,255,255,1]);return k([l(r,0,1),l(f,0,1),l(s,0,1),l(c,0,1)])}case"hsl":case"hsla":{let[r,f,s,c]=b(a,[1/0,1,1,1]);return q([r,l(f,0,1),l(s,0,1),l(c,0,1)])}case"hwb":case"hwba":{let[r,f,s,c]=b(a,[1/0,1,1,1]);return j([r,l(f,0,1),l(s,0,1),l(c,0,1)])}case"p3":case"display-p3":{let[r,f,s,c]=b(a,[1,1,1,1]);return[l(r,0,1),l(f,0,1),l(s,0,1),l(c,0,1)]}case"luv":{let r=b(a,[1,1,1,1]);return $(r)}case"oklab":return w(b(a,[1,1,1,1]));case"oklch":return E(b(a,[1,1,1/0,1]));case"xyz":case"xyz-d65":return F(b(a,[1,1,1,1]))}}throw new Error(`Unable to parse color "${t}"`)}function P(t){return u(d(t).oklabVal[0],5)}function B(t,e,o=.5,n="oklab"){let a=l(o,0,1);if(o===0)return d(t);if(o===1)return d(e);let r=1-a,f=a,s={oklch:_,oklab:y,luv:v,linearRGB:R,sRGB:V=>V},c={oklch:E,oklab:w,luv:$,linearRGB:k,sRGB:V=>V},i=s[n],x=c[n];if(!i)throw new Error(`Unknown color space "${n}", try "oklab", "oklch", "linearRGB", or "sRGB"`);let g=d(t).rgbVal,G=d(e).rgbVal;n==="oklch"&&(g[0]===g[1]&&g[1]===g[2]||G[0]===G[1]&&G[1]===G[2])&&(i=s.oklab,x=c.oklab);let[J,Q,L,C]=i(g),[ee,te,T,re]=i(G);return n==="oklch"&&Math.abs(T-L)>180&&(Math.max(L,T)===T?T-=360:L-=360),d(x([J*r+ee*f,Q*r+te*f,L*r+T*f,C*r+re*f]))}function A(t,e,o="oklab"){let n=l(e,-1,1);return n>=0?B(t,"black",n,o):I(t,-n)}function I(t,e,o="oklab"){let n=l(e,-1,1);return n>=0?B(t,"white",n,o):A(t,-n)}var Se={darken:A,from:d,lighten:I,lightness:P,mix:B};export{l as clamp,h as colorFn,A as darken,Se as default,d as from,m as leftPad,I as lighten,P as lightness,B as mix,u as round}; | ||
function v(e,t=2){let o=e;for(;o.length<t;)o=`0${o}`;return o}function K(e){return e*(Math.PI/180)}function H(e){return e*(180/Math.PI)}function l(e,t,o){return Math.min(Math.max(e,t),o)}function R(e,t){let[o,r,a,n]=t,s=n<1?`/${g(n,5)}`:"";switch(e){case"rgb":case"rgba":return n<1?`rgba(${Math.round(o*255)}, ${Math.round(r*255)}, ${Math.round(a*255)}, ${g(n,5)})`:`rgb(${Math.round(o*255)}, ${Math.round(r*255)}, ${Math.round(a*255)})`;case"oklab":case"oklch":return`${e}(${g(o*100,6)}% ${g(r,6)} ${g(a,6)}${s})`;default:return`color(${e} ${g(o,6)} ${g(r,6)} ${g(a,6)}${s})`}}function _(e,t){let o=[...e];for(let r=0;r<t.length;r++){let a=0;for(let n=0;n<t[r].length;n++)a+=e[n]*t[r][n];o[r]=a}return o}function g(e,t=2){let o=10**t;return Math.round(e*o)/o}var a0=[[.4123907992659593,.357584339383878,.1804807884018343],[.2126390058715102,.715168678767756,.0721923153607337],[.0193308187155918,.11919477979462,.9505321522496607]],s0=[[3.240969941904522,-1.537383177570094,-.4986107602930034],[-.9692436362808793,1.8759675015077202,.0415550574071756],[.0556300796969937,-.2039769588889766,1.0569715142428782]],f0=[[.2104542553,.793617785,-.0040720468],[1.9779984951,-2.428592205,.4505937099],[.0259040371,.7827717662,-.808675766]],x=[[4.0767416621,-3.3077115913,.2309699292],[-1.2684380046,2.6097574011,-.3413193965],[-.0041960863,-.7034186147,1.707614701]],c0=[[.4122214708,.5363325363,.0514459929],[.2119034982,.6806995451,.1073969566],[.0883024619,.2817188376,.6299787005]],l0=[[1,.39633779217376774,.2158037580607588],[1,-.10556134232365633,-.0638541747717059],[1,-.08948418209496574,-1.2914855378640917]];function k0(e,t){let o=[1/0,1/0,1/0,1/0,1/0],r=1/0,a=1/0,n=1/0;-1.88170328*e-.80936493*t>1?(o=[1.19086277,1.76576728,.59662641,.75515197,.56771245],r=4.0767416621,a=-3.3077115913,n=.2309699292):1.81444104*e-1.19445276*t>1?(o=[.73956515,-.45954404,.08285427,.1254107,.14503204],r=-1.2684380046,a=2.6097574011,n=-.3413193965):(o=[1.35733652,-.00915799,-1.1513021,-.50559606,.00692167],r=-.0041960863,a=-.7034186147,n=1.707614701);let s=o[0]+o[1]*e+o[2]*t+o[3]*e*e+o[4]*e*t,f=.3963377774*e+.2158037573*t,c=-.1055613458*e-.0638541728*t,u=-.0894841775*e-1.291485548*t;{let d=1+s*f,h=1+s*c,b=1+s*u,$=d**3,I=h**3,k=b**3,T=3*f*d**2,w=3*c*h**2,L=3*u*b**2,m=6*f**2*d,i=6*c**2*h,p=6*u**2*b,O=r*$+a*I+n*k,N=r*T+a*w+n*L,F=r*m+a*i+n*p;s=s-O*N/(N*N-.5*O*F)}return s}function y0(e,t){let o=k0(e,t),r=S(z([1,o*e,o*t,1])),a=Math.cbrt(1/Math.max((r[0],r[1],r[3]))),n=a*o;return{L:a,C:n}}function i0(e,t,o,r,a){let n=y0(e,t);if((o-a)*n.C-(n.L-a)*r<=0)return n.C*a/(r*n.L+n.C*(a-o));let s=n.C*(a-1)/(r*(n.L-1)+n.C*(a-o)),f=o-a,c=r,u=.3963377774*e+.2158037573*t,d=-.1055613458*e-.0638541728*t,h=-.0894841775*e-1.291485548*t,b=f+c*u,$=f+c*d,I=f+c*h,k=a*(1-s)+s*o,T=s*r,w=k+T*u,L=k+T*d,m=k+T*h,i=[[w**3,L**3,m**3],[3*b*w**2,3*$*L**2,3*I*m**2],[6*b**2*w,6*$**2*L,6*I**2*m]],p=x[0][0]*i[0][0]+x[0][1]*i[0][1]+x[0][2]*i[0][2]-1,O=x[0][0]*i[1][0]+x[0][1]*i[1][1]+x[0][2]*i[1][2],N=x[0][0]*i[2][0]+x[0][1]*i[2][1]+x[0][2]*i[2][2],F=O/(O*O-.5*p*N),h0=F>=0?-p*F:1/0,t0=x[1][0]*i[0][0]+x[1][1]*i[0][1]+x[1][2]*i[0][2]-1,Z=x[1][0]*i[1][0]+x[1][1]*i[1][1]+x[1][2]*i[1][2],b0=x[1][0]*i[2][0]+x[1][1]*i[2][1]+x[1][2]*i[2][2],r0=Z/(Z*Z-.5*t0*b0),p0=r0>=0?-t0*r0:1/0,n0=x[2][0]*i[0][0]+x[2][1]*i[0][1]+x[2][2]*i[0][2]-1,P=x[2][0]*i[1][0]+x[2][1]*i[1][1]+x[2][2]*i[1][2],m0=x[2][0]*i[2][0]+x[2][1]*i[2][1]+x[2][2]*i[2][2],o0=P/(P*P-.5*n0*m0),g0=o0>=0?-n0*o0:1/0;return Math.min(h0,p0,g0)}function U(e){let[t,o,r,a]=e;t=Math.abs(t%360);let n=o*(1-Math.abs(2*r-1)),s=n*(1-Math.abs(t/60%2-1)),f=0,c=0,u=0;0<=t&&t<60?(f=n,c=s):60<=t&&t<120?(f=s,c=n):120<=t&&t<180?(c=n,u=s):180<=t&&t<240?(c=s,u=n):240<=t&&t<300?(f=s,u=n):300<=t&&t<360&&(f=n,u=s);let d=r-n/2;return[f+d,c+d,u+d,a]}function x0(e){let[t,o,r,a]=e;if(o+r>=1){let s=o/(o+r);return[s,s,s,a]}let n=U([t,100,50,a]);for(let s=0;s<3;s++)n[s]*=1-o-r,n[s]+=o;return n}function R0(e,t=2e-4){let[o,r,a,n]=e,s=Math.abs(r)<t&&Math.abs(a)<t?0:H(Math.atan2(a,r));for(;s<0;)s+=360;for(;s>=360;)s-=360;return[o,Math.sqrt(r**2+a**2),s,n]}function _0(e){let[t,o,r,a]=e;if(t===0)return[0,0,0,e[3]];for(;r<0;)r+=360;for(;r>=360;)r-=360;let n=K(r);return[t,Math.cos(n)*o,Math.sin(n)*o,a]}function M0(e){return _(e,f0)}function S(e){let[t,o,r,a]=_([e[0]**3,e[1]**3,e[2]**3,e[3]],x);return[t,o,r,a]}function M(e){let t=Math.abs(e[0]),o=Math.abs(e[1]),r=Math.abs(e[2]);return[t<.0031308?e[0]*12.92:1.055*Math.pow(t,1/2.4)-.055,o<.0031308?e[1]*12.92:1.055*Math.pow(o,1/2.4)-.055,r<.0031308?e[2]*12.92:1.055*Math.pow(r,1/2.4)-.055,e[3]]}function j(e){let t=_(e,c0);return[Math.cbrt(t[0]),Math.cbrt(t[1]),Math.cbrt(t[2]),t[3]]}function C(e){return _(e,a0)}function z(e){return _(e,l0)}function V(e){let t=M(S(z(e)));if(t[0]>1.001||t[0]<-.001||t[1]>1.001||t[1]<-.001||t[2]>1.001||t[2]<-.001){let[r,a,n,s]=e,f=Math.max(1e-5,Math.sqrt(a**2+n**2)),c=l(r,0,1),u=a/f,d=n/f,h=i0(u,d,r,f,c);return M(S(z([c*(1-h)+h*r,u*(h*f),d*(h*f),s])))}return t}function X(e){return V(_0(e))}function B(e){let t=Math.abs(e[0]),o=Math.abs(e[1]),r=Math.abs(e[2]);return[t<.04045?e[0]/12.92:((t+.055)/1.055)**2.4,o<.04045?e[1]/12.92:((o+.055)/1.055)**2.4,r<.04045?e[2]/12.92:((r+.055)/1.055)**2.4,e[3]]}function A(e){return M0(j(B(e)))}function q(e){return R0(A(e))}function u0(e){return _(e,s0)}var B0={black:0,silver:12632256,gray:8421504,white:16777215,maroon:8388608,red:16711680,purple:8388736,fuchsia:16711935,green:32768,lime:65280,olive:8421376,yellow:16776960,navy:128,blue:255,teal:32896,aqua:65535,orange:16753920,aliceblue:15792383,antiquewhite:16444375,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,blanchedalmond:16772045,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,limegreen:3329330,linen:16445670,magenta:16711935,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,oldlace:16643558,olivedrab:7048739,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,whitesmoke:16119285,yellowgreen:10145074,rebeccapurple:6697881},J=B0;var G0=/-?[0-9.]+%?/g,T0=/^#?[0-9a-f]{3,8}$/i,w0=16**6,Q=16**4,W=16**2;function y(e){let t=L0(e),o={get hex(){let r="#";return r+=v(Math.round(l(t[0]*255,0,255)).toString(16),2),r+=v(Math.round(l(t[1]*255,0,255)).toString(16),2),r+=v(Math.round(l(t[2]*255,0,255)).toString(16),2),t[3]<1&&(r+=v(Math.round(t[3]*255).toString(16),2)),r},get hexVal(){t[3]<1&&console.warn(`hexVal converted a semi-transparent color (${t[3]*100}%) to fully opaque`);let r=Math.round(l(t[0]*255,0,255)),a=Math.round(l(t[1]*255,0,255)),n=Math.round(l(t[2]*255,0,255));return r*Q+a*W+n},get rgb(){return R("rgb",t)},rgbVal:t,get rgba(){return R("rgb",t)},rgbaVal:t,get linearRGB(){return B(t)},get p3(){return R("display-p3",t)},p3Val:t,get oklab(){return R("oklab",A(t))},get oklabVal(){return A(t)},get oklch(){return R("oklch",q(t))},get oklchVal(){return q(t)},get xyz(){return R("xyz-d65",C(B(t)))},get xyzVal(){return C(B(t))}};return o.toString=()=>o.hex,o}function d0(e){if(e>w0)throw new Error("8-digit hex values (with transparency) aren\u2019t supported");let t=e,o=Math.floor(t/Q);t-=o*Q;let r=Math.floor(t/W);t-=r*W;let a=t;return[o/255,r/255,a/255,1]}function G(e,t){let o=e.match(G0);if(!o)throw new Error(`Unexpected color format: ${e}`);let r=[0,0,0,1];return o.forEach((a,n)=>{a.includes("%")?r[n]=parseFloat(a)/100:t[n]===1/0||t[n]===0||t[n]===1?r[n]=parseFloat(a):r[n]=parseFloat(a)/t[n]}),r}function L0(e){if(Array.isArray(e)){if(typeof e[0]!="number"||typeof e[1]!="number"||typeof e[2]!="number")throw new Error(`Color array must be numbers, received ${e}`);if(e.length<3||e.length>4)throw new Error(`Expected [R, G, B, A?], received ${e}`);return[l(e[0],0,1),l(e[1],0,1),l(e[2],0,1),typeof e[3]=="number"?l(e[3],0,1):1]}if(typeof e=="number")return d0(e);if(typeof e=="string"){let t=e.trim();if(!t)throw new Error("Expected color, received empty string");let o=t.toLowerCase();if(typeof J[o]=="number")return d0(J[o]);if(T0.test(t)){let n=t.replace("#",""),s=[0,0,0,1];if(n.length>=6)for(let f=0;f<n.length/2;f++){let c=f*2,u=c+2,d=n.substring(c,u);s[f]=parseInt(d,16)/255}else for(let f=0;f<n.length;f++){let c=n.charAt(f);s[f]=parseInt(`${c}${c}`,16)/255}return s}let[r,a]=t.split("(");if(r==="color"){let n=a.indexOf(" ");r=a.substring(0,n),a=a.substring(n)}switch(r){case"rgb":case"rgba":case"srgb":{let[n,s,f,c]=G(a,[255,255,255,1]);return[l(n,0,1),l(s,0,1),l(f,0,1),l(c,0,1)]}case"linear-rgb":case"linear-srgb":case"rgb-linear":case"srgb-linear":{let n=G(a,[255,255,255,1]);return M(n)}case"hsl":case"hsla":{let[n,s,f,c]=G(a,[1,1,1,1]);return U([n,l(s,0,1),l(f,0,1),l(c,0,1)])}case"hwb":case"hwba":{let[n,s,f,c]=G(a,[1,1,1,1]);return x0([n,l(s,0,1),l(f,0,1),l(c,0,1)])}case"p3":case"display-p3":{let[n,s,f,c]=G(a,[1,1,1,1]);return[l(n,0,1),l(s,0,1),l(f,0,1),l(c,0,1)]}case"oklab":return V(G(a,[1,1,1,1]));case"oklch":return X(G(a,[1,1,1,1]));case"xyz":case"xyz-d65":return M(u0(G(a,[1,1,1,1])))}}throw new Error(`Unable to parse color "${e}"`)}function e0(e){return g(y(e).oklabVal[0],5)}function E(e,t,o=.5,r="oklab"){let a=l(o,0,1);if(o===0)return y(e);if(o===1)return y(t);let n=1-a,s=a,f={oklch:q,oklab:A,lms:p=>B(j(p)),linearRGB:B,sRGB:p=>p},c={oklch:X,oklab:V,lms:p=>S(M(p)),linearRGB:M,sRGB:p=>p},u=f[r],d=c[r];if(!u)throw new Error(`Unknown color space "${r}", try "oklab", "oklch", "linearRGB", or "sRGB"`);let h=y(e).rgbVal,b=y(t).rgbVal;r==="oklch"&&(h[0]===h[1]&&h[1]===h[2]||b[0]===b[1]&&b[1]===b[2])&&(u=f.oklab,d=c.oklab);let[$,I,k,T]=u(h),[w,L,m,i]=u(b);return r==="oklch"&&Math.abs(m-k)>180&&(Math.max(k,m)===m?m-=360:k-=360),y(d([$*n+w*s,I*n+L*s,k*n+m*s,T*n+i*s]))}function D(e,t,o="oklab"){let r=l(t,-1,1);return r>=0?E(e,"black",r,o):Y(e,-r)}function Y(e,t,o="oklab"){let r=l(t,-1,1);return r>=0?E(e,"white",r,o):D(e,-r)}var J0={darken:D,from:y,lighten:Y,lightness:e0,mix:E};export{l as clamp,R as colorFn,D as darken,J0 as default,K as degToRad,y as from,v as leftPad,Y as lighten,e0 as lightness,E as mix,_ as multiplyColorMatrix,H as radToDeg,g as round}; |
@@ -1,6 +0,6 @@ | ||
import type { Color } from './colorspace.js'; | ||
import { Color } from './colorspace.js'; | ||
import type { ColorOutput } from './parse.js'; | ||
export declare type MixColorSpace = 'oklab' | 'oklch' | 'luv' | 'linearRGB' | 'sRGB'; | ||
export declare type MixColorSpace = 'oklab' | 'oklch' | 'lms' | 'linearRGB' | 'sRGB'; | ||
/** | ||
* Mix colors via Oklch (better for preserving hue than Oklab) | ||
* Mix colors via LMS (better for preserving hue than Oklab) | ||
* @param {Color} color1 | ||
@@ -7,0 +7,0 @@ * @param {Color} color2 |
@@ -1,6 +0,7 @@ | ||
import { linearRGBTosRGB, luvTosRGB, sRGBToLuv, sRGBToOklab, sRGBToOklch, sRGBToLinearRGB, oklabTosRGB, oklchTosRGB } from './colorspace.js'; | ||
import { linearRGBToLMS, lmsToLinearRGB } from './colorspace.js'; | ||
import { linearRGBTosRGB, sRGBToOklab, sRGBToOklch, sRGBToLinearRGB, oklabTosRGB, oklchTosRGB } from './colorspace.js'; | ||
import { from } from './parse.js'; | ||
import { clamp } from './utils.js'; | ||
/** | ||
* Mix colors via Oklch (better for preserving hue than Oklab) | ||
* Mix colors via LMS (better for preserving hue than Oklab) | ||
* @param {Color} color1 | ||
@@ -23,3 +24,3 @@ * @param {Color} color2 | ||
oklab: sRGBToOklab, | ||
luv: sRGBToLuv, | ||
lms: (c) => sRGBToLinearRGB(linearRGBToLMS(c)), | ||
linearRGB: sRGBToLinearRGB, | ||
@@ -32,3 +33,3 @@ sRGB: (c) => c, | ||
oklab: oklabTosRGB, | ||
luv: luvTosRGB, | ||
lms: (c) => lmsToLinearRGB(linearRGBTosRGB(c)), | ||
linearRGB: linearRGBTosRGB, | ||
@@ -35,0 +36,0 @@ sRGB: (c) => c, |
@@ -1,2 +0,2 @@ | ||
import { Color, LinearRGB, LUV, Oklab, Oklch, sRGB, XYZ_D65 } from './colorspace.js'; | ||
import type { Color, LinearRGB, Oklab, Oklch, sRGB, XYZ_D65 } from './colorspace.js'; | ||
export interface ColorOutput { | ||
@@ -18,5 +18,3 @@ /** `#000000` */ | ||
/** `color(luv 0 0 0/1)` */ | ||
luv: string; | ||
/** [L, u, v, alpha] */ | ||
luvVal: LUV; | ||
/** `color(display-p3 0 0 0/1)` */ | ||
@@ -23,0 +21,0 @@ p3: string; |
@@ -1,3 +0,2 @@ | ||
import { sRGBToXYZ, xyzTosRGB } from './colorspace.js'; | ||
import { hslTosRGB, hwbTosRGB, linearRGBTosRGB, luvTosRGB, oklabTosRGB, oklchTosRGB, sRGBToLinearRGB, sRGBToLuv, sRGBToOklab, sRGBToOklch } from './colorspace.js'; | ||
import { hslTosRGB, hwbTosRGB, linearRGBTosRGB, linearRGBToXYZ, oklabTosRGB, oklchTosRGB, sRGBToLinearRGB, sRGBToOklab, sRGBToOklch, xyzToLinearRGB } from './colorspace.js'; | ||
import cssNames from './css-names.js'; | ||
@@ -30,5 +29,5 @@ import { clamp, colorFn, leftPad, round } from './utils.js'; | ||
let hexString = '#'; | ||
hexString += leftPad(Math.round(color[0] * 255).toString(16), 2); // r | ||
hexString += leftPad(Math.round(color[1] * 255).toString(16), 2); // g | ||
hexString += leftPad(Math.round(color[2] * 255).toString(16), 2); // b | ||
hexString += leftPad(Math.round(clamp(color[0] * 255, 0, 255)).toString(16), 2); // r | ||
hexString += leftPad(Math.round(clamp(color[1] * 255, 0, 255)).toString(16), 2); // g | ||
hexString += leftPad(Math.round(clamp(color[2] * 255, 0, 255)).toString(16), 2); // b | ||
if (color[3] < 1) | ||
@@ -41,13 +40,13 @@ hexString += leftPad(Math.round(color[3] * 255).toString(16), 2); // a | ||
console.warn(`hexVal converted a semi-transparent color (${color[3] * 100}%) to fully opaque`); // eslint-disable-line no-console | ||
const r = Math.round(color[0] * 255); | ||
const g = Math.round(color[1] * 255); | ||
const b = Math.round(color[2] * 255); | ||
const r = Math.round(clamp(color[0] * 255, 0, 255)); | ||
const g = Math.round(clamp(color[1] * 255, 0, 255)); | ||
const b = Math.round(clamp(color[2] * 255, 0, 255)); | ||
return r * R_FACTOR + g * G_FACTOR + b; | ||
}, | ||
get luv() { | ||
return colorFn('luv', sRGBToLuv(color)); | ||
}, | ||
get luvVal() { | ||
return sRGBToLuv(color); | ||
}, | ||
// get luv(): string { | ||
// return colorFn('luv', sRGBToLuv(color)); | ||
// }, | ||
// get luvVal(): LUV { | ||
// return sRGBToLuv(color); | ||
// }, | ||
get rgb() { | ||
@@ -81,6 +80,6 @@ return colorFn('rgb', color); | ||
get xyz() { | ||
return colorFn('xyz-d65', sRGBToXYZ(color)); | ||
return colorFn('xyz-d65', linearRGBToXYZ(sRGBToLinearRGB(color))); | ||
}, | ||
get xyzVal() { | ||
return sRGBToXYZ(color); | ||
return linearRGBToXYZ(sRGBToLinearRGB(color)); | ||
}, | ||
@@ -98,3 +97,3 @@ }; | ||
if (hex > RGB_RANGE) | ||
throw new Error('better-color-tools can’t parse hex numbers with alpha (0x0000000 is indistinguishable from 0x00000000). Please use hex string, or another color method'); | ||
throw new Error('8-digit hex values (with transparency) aren’t supported'); | ||
let remaining = hex; | ||
@@ -119,7 +118,7 @@ const r = Math.floor(remaining / R_FACTOR); // Math.floor gets rid of G + B | ||
// unbounded | ||
else if (normalize[n] === Infinity) | ||
else if (normalize[n] === Infinity || normalize[n] === 0 || normalize[n] === 1) | ||
values[n] = parseFloat(value); | ||
// bounded | ||
else | ||
values[n] = parseFloat(value) / (normalize[n] || 1); | ||
values[n] = parseFloat(value) / normalize[n]; | ||
}); | ||
@@ -132,3 +131,3 @@ return values; | ||
if (Array.isArray(rawColor)) { | ||
if (rawColor.some((val) => typeof val != 'number')) | ||
if (typeof rawColor[0] != 'number' || typeof rawColor[1] != 'number' || typeof rawColor[2] != 'number') | ||
throw new Error(`Color array must be numbers, received ${rawColor}`); | ||
@@ -195,9 +194,12 @@ if (rawColor.length < 3 || rawColor.length > 4) | ||
} | ||
case 'linear-rgb': | ||
case 'linear-srgb': | ||
case 'rgb-linear': | ||
case 'srgb-linear': { | ||
const [r, g, b, a] = parseValueStr(valueStr, [255, 255, 255, 1]); | ||
return linearRGBTosRGB([clamp(r, 0, 1), clamp(g, 0, 1), clamp(b, 0, 1), clamp(a, 0, 1)]); | ||
const rgb = parseValueStr(valueStr, [255, 255, 255, 1]); | ||
return linearRGBTosRGB(rgb); | ||
} | ||
case 'hsl': | ||
case 'hsla': { | ||
const [h, s, l, a] = parseValueStr(valueStr, [Infinity, 1, 1, 1]); | ||
const [h, s, l, a] = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
return hslTosRGB([h, clamp(s, 0, 1), clamp(l, 0, 1), clamp(a, 0, 1)]); | ||
@@ -207,3 +209,3 @@ } | ||
case 'hwba': { | ||
const [h, w, b, a] = parseValueStr(valueStr, [Infinity, 1, 1, 1]); | ||
const [h, w, b, a] = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
return hwbTosRGB([h, clamp(w, 0, 1), clamp(b, 0, 1), clamp(a, 0, 1)]); | ||
@@ -216,6 +218,6 @@ } | ||
} | ||
case 'luv': { | ||
const luv = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
return luvTosRGB(luv); | ||
} | ||
// case 'luv': { | ||
// const luv = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
// return luvTosRGB(luv); | ||
// } | ||
case 'oklab': { | ||
@@ -225,7 +227,7 @@ return oklabTosRGB(parseValueStr(valueStr, [1, 1, 1, 1])); | ||
case 'oklch': { | ||
return oklchTosRGB(parseValueStr(valueStr, [1, 1, Infinity, 1])); | ||
return oklchTosRGB(parseValueStr(valueStr, [1, 1, 1, 1])); | ||
} | ||
case 'xyz': | ||
case 'xyz-d65': { | ||
return xyzTosRGB(parseValueStr(valueStr, [1, 1, 1, 1])); | ||
return linearRGBTosRGB(xyzToLinearRGB(parseValueStr(valueStr, [1, 1, 1, 1]))); | ||
} | ||
@@ -232,0 +234,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import type { sRGB } from './index'; | ||
import type { ColorMatrix, sRGB } from './index'; | ||
/** you know it, you love it */ | ||
@@ -10,3 +10,3 @@ export declare function leftPad(input: string, min?: number): string; | ||
/** multiply 3x1 color matrix with colorspace */ | ||
export declare function multiplyColorMatrix(color: sRGB, matrix: number[][]): sRGB; | ||
export declare function multiplyColorMatrix(color: sRGB, matrix: ColorMatrix): sRGB; | ||
export declare function round(number: number, precision?: number): number; |
@@ -22,2 +22,3 @@ /** you know it, you love it */ | ||
const alphaSlash = alpha < 1 ? `/${round(alpha, 5)}` : ''; | ||
// note: JavaScript abbreviates anything > 6 decimal places as 1e-7, etc. | ||
switch (colorSpace) { | ||
@@ -31,12 +32,11 @@ case 'rgb': | ||
} | ||
// color(oklab 54.0% -0.10 -0.02) | ||
// oklab(54.0% -0.10 -0.02) | ||
case 'oklab': | ||
case 'oklch': { | ||
return `color(${colorSpace} ${round(x * 100, 5)}% ${round(y, 5)} ${round(z, 5)}${alphaSlash})`; | ||
return `${colorSpace}(${round(x * 100, 6)}% ${round(y, 6)} ${round(z, 6)}${alphaSlash})`; | ||
} | ||
// color(display-p3 0.4 0.2 0.6) | ||
// color(luv 0.4 0.2 0.6) | ||
// color(xyz-d65 0.4 0.2 0.6) | ||
default: | ||
return `color(${colorSpace} ${round(x, 5)} ${round(y, 5)} ${round(z, 5)}${alphaSlash})`; | ||
return `color(${colorSpace} ${round(x, 6)} ${round(y, 6)} ${round(z, 6)}${alphaSlash})`; | ||
} | ||
@@ -47,8 +47,8 @@ } | ||
const product = [...color]; | ||
for (let row = 0; row < matrix.length; row++) { | ||
for (let y = 0; y < matrix.length; y++) { | ||
let sum = 0; | ||
for (let col = 0; col < matrix[0].length; col++) { | ||
sum += color[col] * matrix[row][col]; | ||
for (let x = 0; x < matrix[y].length; x++) { | ||
sum += color[x] * matrix[y][x]; | ||
} | ||
product[row] = sum; | ||
product[y] = sum; | ||
} | ||
@@ -55,0 +55,0 @@ return product; |
{ | ||
"name": "better-color-tools", | ||
"description": "Fast, minimal color conversion and tools for JS/Sass. Supports sRGB, Oklab, Oklch, Display P3, and more.", | ||
"version": "0.7.3", | ||
"version": "0.8.0", | ||
"author": { | ||
@@ -29,10 +29,10 @@ "name": "Drew Powers", | ||
"devDependencies": { | ||
"@changesets/cli": "^2.22.0", | ||
"@types/node": "^17.0.36", | ||
"@typescript-eslint/eslint-plugin": "^5.27.0", | ||
"@typescript-eslint/parser": "^5.27.0", | ||
"@changesets/cli": "^2.23.0", | ||
"@types/node": "^17.0.42", | ||
"@typescript-eslint/eslint-plugin": "^5.27.1", | ||
"@typescript-eslint/parser": "^5.27.1", | ||
"chai": "^4.3.6", | ||
"del-cli": "^4.0.1", | ||
"esbuild": "^0.14.42", | ||
"eslint": "^8.16.0", | ||
"esbuild": "^0.14.43", | ||
"eslint": "^8.17.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
@@ -43,4 +43,4 @@ "eslint-plugin-prettier": "^4.0.0", | ||
"prettier": "^2.6.2", | ||
"sass": "^1.52.1", | ||
"typescript": "^4.7.2" | ||
"sass": "^1.52.3", | ||
"typescript": "^4.7.3" | ||
}, | ||
@@ -54,5 +54,5 @@ "scripts": { | ||
"prepublish": "npm run build", | ||
"test": "mocha --parallel", | ||
"test": "mocha", | ||
"test:benchmark": "mocha -t 10000" | ||
} | ||
} |
196
README.md
@@ -5,3 +5,3 @@ # better-color-tools | ||
The JS version of this libray is fast (`> 225k` ops/s), lightweight (`4.3 kB` gzip), and dependency-free. The Sass version… is Sass (which has no runtime). | ||
The JS version of this libray is fast (`~200k` ops/s), lightweight (`5 kB` gzip), and dependency-free. The Sass version… is Sass (which has no runtime). | ||
@@ -20,52 +20,80 @@ 👉 **Playground**: https://better-color-tools.pages.dev/ | ||
**Importing** | ||
```js | ||
import better from 'better-color-utils'; | ||
import better from 'better-color-tools'; | ||
``` | ||
better.from('rebeccapurple').hex; // #663399 | ||
better.from('rebeccapurple').p3; // color(display-p3 0.4 0.2 0.6) | ||
better.from('rebeccapurple').oklch; // color(oklch 0.44027 0.1603 303.37299) | ||
**Parse** | ||
```js | ||
better.from('#b3f6e6'); // hex string | ||
better.from(0xb3f6e6); // hex integer (note: only mode that doesn’t support transparency) | ||
better.from('rebeccapurple'); // CSS keyword | ||
better.from('rgb(136, 48, 62)'); // CSS RGB | ||
better.from('hsl(210, 85%, 37%)'); // CSS HSL | ||
better.from('oklab(48.56949% -0.03971 -0.14459)'); // CSS Oklab | ||
better.from('oklch(83.11253% 0.22612 147.35276)'); // CSS Oklch | ||
``` | ||
#### Quick guide | ||
This library understands **any CSS-valid color**, including [CSS Color Module 4](https://www.w3.org/TR/css-color-4/) (but if some aspect isn’t implemented yet, please request it!). | ||
| Code | Description | | ||
| :-------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| `better.from('red')` | Parse any valid CSS color (including [color()][css-color]) | | ||
| `better.from('red').[colorspace]` | Convert color to [another colorspace](#supported-colorspaces) | | ||
| `better.mix('red', 'lime', 0.35)` | Mix `red` and `lime` 35%, i.e. more red. Uses Oklab for better color mixing. | | ||
| `better.lighten('red', 0.5)` | Lighten color by 50%, i.e. halfway to white (100% is white; 0% is original color). Better than Sass’ builtin. | | ||
| `better.darken('red', 0.5)` | Darken color by 50%, i.e. halfway to black (100% is black; 0% is original color). Better than Sass’ builtin. | | ||
| `better.lightness('red', 0.5)` | Get the human-perceived value of lightness from `0` (pure black) to `1` (pure white). Alias for `better.from().oklabVal`’s lightness (first value) | | ||
**Convert** | ||
#### Supported colorspaces | ||
This library converts to most web-compatible formats¹, with an emphasis on usefulness over completeness: | ||
| Colorspace | Example | Result | | ||
| :------------------------------------- | :------------------------ | :----------------------------------------- | | ||
| sRGB (hex) | `better.from(…).hex` | `'#ff0000'` | | ||
| sRGB (hex int) | `better.from(…).hexVal` | `0xff0000` | | ||
| sRGB (RGB) | `better.from(…).rgb` | `'rgb(255, 0, 0)'` | | ||
| sRGB (RGB array) | `better.from(…).rgbVal` | `[1, 0, 0, 1]` | | ||
| [P3][p3] ([Color Module 4][cm4]) | `better.from(…).p3` | `'color(display-p3 0.4 0.2 0.6)'` | | ||
| [P3][p3] (array) | `better.from(…).p3Val` | (alias for `rgbVal`) | | ||
| [Oklab][oklab] ([Color Module 4][cm4]) | `better.from(…).oklab` | `'color(oklab 0.44027 0.08818 -0.13386)'` | | ||
| [Oklab][oklab] (array) | `better.from(…).oklabVal` | `[0.44027, 0.08818, -0.13386, 1]` | | ||
| [Oklch][oklab] ([Color Module 4][cm4]) | `better.from(…).oklch` | `'color(oklch 0.44027 0.1603 303.37299)'` | | ||
| [Oklch][oklab] (array) | `better.from(…).oklchVal` | `[0.44027, 0.1603, 303.37299, 1]` | | ||
| [XYZ D65][xyz] ([Color Module 4][cm4]) | `better.from(…).xyz` | `'color(xyz-d65 0.12413 0.07492 0.30929)'` | | ||
| [XYZ D65][xyz] (array) | `better.from(…).xyzVal` | `[0.12413, 0.07492, 0.30929, 1]` | | ||
| [Luv][luv] ([Color Module 4][cm4]) | `better.from(…).luv` | `'color(luv 0.53241 1.75015 0.2979)'` | | ||
| [Luv][luv] (array) | `better.from(…).luvVal` | `[0.53241, 1.75015, 0.2979, 1]` | | ||
- **sRGB** (hex): `better.from('…').hex` / `better.from('…').hexVal` | ||
- **sRGB** (RGB): `better.from('…').rgb` / `better.from('–').rgbVal` | ||
- **Oklab**: `better.from('…').oklab` / `better.from('…').oklabVal` | ||
- **Oklch**: `better.from('…').oklch` / `better.from('…').oklchVal` | ||
- **XYZ**: `better.from('…').xyz` / `better.from('…').xyzVal` | ||
[cm4]: https://www.w3.org/TR/css-color-4/ | ||
[luv]: https://en.wikipedia.org/wiki/CIELUV | ||
[oklab]: https://bottosson.github.io/posts/oklab/ | ||
[p3]: https://webkit.org/blog/10042/wide-gamut-color-in-css-with-display-p3/ | ||
[xyz]: https://en.wikipedia.org/wiki/CIE_1931_color_space | ||
_¹The following formats aren’t supported as outputs:_ | ||
- **Oklch** (and Oklab) are always preferred. Oklch works like HSL (1 value for lightness, 1 value for chroma/saturation, and 1 value for hue degrees) but is vastly superior. | ||
- All values are normalized to `1` besides hue degrees (e.g. Oklch). Values < 0 and > 1 are still valid; they just represent a value darker or brigher than the display is able to reproduce | ||
- **Luv** is [a close runner-up to Oklch/Oklab](https://gist.github.com/Myndex/47c793f8a054041bd2b52caa7ad5271c) | ||
- HSL and HWB can be parsed but not output (and [you shouldn’t use either anyway](https://better-color-tools.pages.dev/terminology#hsl)) | ||
- All colorspaces use the [CIE standard 2°, D65 white point observer](https://en.wikipedia.org/wiki/Illuminant_D65). | ||
- HSL isn’t supported because [you shouldn’t use it](https://pow.rs/blog/dont-use-hsl-for-anything/) | ||
- HWB isn’t supported because it’s another form of HSL | ||
- HSV is a great colorspace, but on no standards track for the web currently | ||
- CIE L\*a\*/CIE L\*C\*h aren’t supported because Oklab/Oklch [are superior](https://bottosson.github.io/posts/oklab/) | ||
For a comprehensive color conversion library, see [culori](https://github.com/Evercoder/culori). | ||
**Mix** | ||
```js | ||
better.mix('red', 'lime', 0.35); // Mix `red` and `lime` 35%, i.e. more red | ||
better.mix('blue', 'magenta', 0.5, 'srgb-linear'); // Mix `blue` and `magenta` 50% using srgb-linear colorspace | ||
``` | ||
This uses Oklab (best) by default, but also supports `oklch`, `lms`, `sRGB`, and `linearRGB` colorspaces for mixing. | ||
**Lighten / Darken** | ||
```js | ||
better.lighten('red', 0.5); // Lighten color by 50% | ||
better.darken('red', 0.5); // Darken color by 50% | ||
``` | ||
This takes hue and human perception into account for visually-accurate results. Also, fun fact: both functions accept negative values. | ||
**Lightness** | ||
Get the human-perceived lightness of any color (identical to the first value of `.oklabVal`) | ||
```js | ||
better.lightness('#fc7030'); // 0.7063999 | ||
``` | ||
**Manipulation** | ||
```js | ||
import better, { colorFn } from 'better-color-tools'; | ||
let [L, C, h] = better.from('#5a00a6').oklchVal; | ||
h += 5; // rotate hue by 5° | ||
C += 0.01; // increase Chroma by 1% | ||
better.from(colorFn('oklch', [L, C, h])).hex; // #6f00ca | ||
``` | ||
Manipulation is best done in a space like [Oklch](https://oklch.evilmartians.io/#70,0.1,17,100) which is optimized for manual tweaking. | ||
### Sass | ||
@@ -75,2 +103,4 @@ | ||
**Importing** | ||
```scss | ||
@@ -80,15 +110,77 @@ @use 'better-color-tools' as better; | ||
#### Quick guide | ||
**Mix** | ||
| Code | Description | | ||
| :-------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | ||
| `better.p3(#f00)` | Convert RGB color to [P3][p3] (`color(display-p3 …)`) ([CSS Module 5][css-color]) | | ||
| `better.rgbToOklab(#f00)` | Convert RGB to Oklab `color(oklab …)` ([CSS Module 5][css-color]) | | ||
| `better.oklabToRGB(('l': 0.87421, 'a': -0.19121, 'b': 0.1174))` | Convert Oklab map of `l`, `a`, `b` to Sass color (with all values normalized to `1`). | | ||
| `better.fallback('color', better.p3(#f00), #f00)` | Easy fallback constructor (meant for color, but may be used for anything). | | ||
| `better.mix(red, lime, 0.35)` | Mix `red` and `lime` 35%, i.e. more red. Uses Oklab for improved mixing. | | ||
| `better.lighten(#f00, 0.5)` | Lighten color by 50%, i.e. halfway to white (`1` is white; `0` is original color) | | ||
| `better.darken(#f00, 0.5)` | Darken color by 50%, i.e. halfway to black (`1` is black; `0` is original color) | | ||
| `better.lightness(#f00, 0.5)` | Get the human-perceived value of lightness from `0` (pure black) to `1` (pure white). | | ||
```scss | ||
.foo { | ||
color: better.mix('red', 'lime', 0.35); | ||
} | ||
``` | ||
Uses Oklab for best results (which yields much better results than Sass’ [mix](https://sass-lang.com/documentation/modules/color#mix)). | ||
**Lighten / Darken** | ||
```scss | ||
.foo:hover { | ||
color: better.lighten('blue', 0.2); | ||
border-color: better.darken('blue', 0.15); | ||
} | ||
``` | ||
Lightens (or darkens) color via Oklab for human-perceived lightness value (which yields much better results than Sass’ [lighten](https://sass-lang.com/documentation/modules/color#lighten)/[darken](https://sass-lang.com/documentation/modules/color#darken): | ||
**P3** | ||
```scss | ||
.foo { | ||
color: better.p3(#f00); // color(display-p3 1 0 0) | ||
} | ||
``` | ||
Convert any Sass-readable color to [P3][p3]. | ||
**Fallback** | ||
```scss | ||
.foo { | ||
@include better.fallback('color', better.p3(#f00), #f00); | ||
// color: #f00; | ||
// color: color(display-p3 1 0 0); | ||
} | ||
``` | ||
Mixin for adding CSS fallbacks. Can take infinite arguments. Specify desired format first, followed by fallbacks. | ||
**Oklab** | ||
```scss | ||
$oklab: better.rgbToOklab(#f00); // (l: 0.6279554, a: 0.22486, b: 0.12585) | ||
$rgb: better.oklabToRGB($oklab); // #f00 | ||
``` | ||
Converts any Sass-readable color to an Oklab [map](https://sass-lang.com/documentation/modules/map) of `(l: …, a: …, b: –)`. The Sass map can either be used to make a CSS string: | ||
```scss | ||
@use 'sass:map'; | ||
.foo { | ||
color: oklab(#{map.get($oklab, 'l')} #{map.get($oklab, 'a')} #{map.get($oklab, 'b')}); | ||
color: better.oklabToRGB($oklab); // fallback | ||
} | ||
``` | ||
Or for color manipulation: | ||
```scss | ||
$oklab-lighter: map.set($oklab, 'l', 0.8); | ||
``` | ||
**Lightness** | ||
Get the human-perceived lightness of any color (identical to the first value of `.oklabVal`): | ||
```scss | ||
$lightness: better.lightness(#f00); | ||
``` | ||
## Project summary | ||
@@ -95,0 +187,0 @@ |
@@ -1,3 +0,1 @@ | ||
// note: these types are all interchangeable, but are kept separate for readability | ||
// all color spaces’ 4th number is alpha | ||
export type HSL = [number, number, number, number]; | ||
@@ -17,9 +15,11 @@ export type HWB = [number, number, number, number]; | ||
import { clamp, degToRad, multiplyColorMatrix, radToDeg } from './utils.js'; | ||
import { LMS_TO_OKLAB, LMS_TO_LINEAR_RGB, LINEAR_RGB_TO_LMS, OKLAB_TO_LMS, LINEAR_RGB_TO_XYZ_D65, XYZ_D65_TO_LINEAR_RGB, findGamutIntersection } from './lib.js'; | ||
const ε = 216 / 24389; | ||
const κ = 24389 / 27; | ||
const D65_WHITEPOINT = [95.47, 100, 108.33]; | ||
const D65_LUV_DENOMINATOR = D65_WHITEPOINT[0] + 15 * D65_WHITEPOINT[1] + 3 * D65_WHITEPOINT[2]; | ||
const D65_U_REF = (4 * D65_WHITEPOINT[0]) / D65_LUV_DENOMINATOR; | ||
const D65_V_REF = (9 * D65_WHITEPOINT[1]) / D65_LUV_DENOMINATOR; | ||
// const D65_κ = 24389 / 27; | ||
// const D65_X = 0.950489; | ||
// const D65_Y = 1; | ||
// const D65_Z = 1.08884; | ||
// const D65_LUV_DENOMINATOR = D65_X + 0.15 * D65_Y + 0.03 * D65_Z; | ||
// const D65_U_REF = (4 * D65_X) / D65_LUV_DENOMINATOR; | ||
// const D65_V_REF = (9 * D65_Y) / D65_LUV_DENOMINATOR; | ||
@@ -29,44 +29,2 @@ type MatrixRow = [number, number, number]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const LMS_TO_OKLAB: ColorMatrix = [ | ||
[0.2104542553, 0.793617785, -0.0040720468], | ||
[1.9779984951, -2.428592205, 0.4505937099], | ||
[0.0259040371, 0.7827717662, -0.808675766], | ||
]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const LMS_TO_LINEAR_RGB: ColorMatrix = [ | ||
[4.0767416621, -3.3077115913, 0.2309699292], | ||
[-1.2684380046, 2.6097574011, -0.3413193965], | ||
[-0.0041960863, -0.7034186147, 1.707614701], | ||
]; | ||
// https://github.com/muak/ColorMinePortable/ | ||
export const LINEAR_RGB_TO_XYZ_D65: ColorMatrix = [ | ||
[0.4124, 0.3576, 0.1805], | ||
[0.2126, 0.7152, 0.0722], | ||
[0.0193, 0.1192, 0.9505], | ||
]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const LINEAR_RGB_TO_LMS: ColorMatrix = [ | ||
[0.4122214708, 0.5363325363, 0.0514459929], | ||
[0.2119034982, 0.6806995451, 0.1073969566], | ||
[0.0883024619, 0.2817188376, 0.6299787005], | ||
]; | ||
// https://bottosson.github.io/posts/oklab/ | ||
export const OKLAB_TO_LMS: ColorMatrix = [ | ||
[1, 0.3963377774, 0.2158037573], | ||
[1, -0.1055613458, -0.0638541728], | ||
[1, -0.0894841775, -1.291485548], | ||
]; | ||
// http://www.easyrgb.com/ | ||
export const XYZ_D65_TO_LINEAR_RGB: ColorMatrix = [ | ||
[3.2406, -1.5372, -0.4986], | ||
[-0.9689, 1.8758, 0.0415], | ||
[0.0557, -0.204, 1.057], | ||
]; | ||
/** HSL -> sRGB */ | ||
@@ -124,10 +82,13 @@ export function hslTosRGB(hsl: HSL): sRGB { | ||
/** Lab -> LCh / Oklab -> Oklch) */ | ||
export function labToLCH(lab: LAB): LCH { | ||
export function labToLCH(lab: LAB, ε = 0.0002): LCH { | ||
const [L, a, b, alpha] = lab; | ||
const h = a === 0 && b === 0 ? 0 : radToDeg(Math.atan2(b, a)); // if desaturated, set hue to 0 | ||
let h = Math.abs(a) < ε && Math.abs(b) < ε ? 0 : radToDeg(Math.atan2(b, a)); // if desaturated, set hue to 0 | ||
while (h < 0) h += 360; | ||
while (h >= 360) h -= 360; | ||
return [ | ||
L, // L | ||
Math.sqrt(a ** 2 + b ** 2), // C | ||
h < 0 ? h + 360 : h, // h (enforce 0–360° hue) | ||
alpha, // alpha | ||
h, | ||
alpha, // alpha; | ||
]; | ||
@@ -138,8 +99,14 @@ } | ||
export function lchToLAB(lch: LCH): LAB { | ||
const [L, C, h, alpha] = lch; | ||
let [L, C, h, alpha] = lch; | ||
// treat L === 0 as pure black | ||
if (L === 0) { | ||
return [0, 0, 0, lch[3]]; | ||
} | ||
while (h < 0) h += 360; | ||
while (h >= 360) h -= 360; | ||
const h2 = degToRad(h); | ||
return [ | ||
L, // l | ||
C * Math.cos(h2), // a | ||
C * Math.sin(h2), // b, | ||
Math.cos(h2) * C, // a | ||
Math.sin(h2) * C, // b, | ||
alpha, // alpha | ||
@@ -151,7 +118,3 @@ ]; | ||
export function lmsToOklab(lms: LMS): Oklab { | ||
let l2 = Math.cbrt(lms[0]); | ||
let m2 = Math.cbrt(lms[1]); | ||
let s2 = Math.cbrt(lms[2]); | ||
let alpha = lms[3]; | ||
return multiplyColorMatrix([l2, m2, s2, alpha], LMS_TO_OKLAB); | ||
return multiplyColorMatrix(lms, LMS_TO_OKLAB); | ||
} | ||
@@ -161,9 +124,8 @@ | ||
export function lmsToLinearRGB(lms: LMS): LinearRGB { | ||
const [r, g, b, a] = multiplyColorMatrix(lms, LMS_TO_LINEAR_RGB); | ||
// bump any negative #s to 0 | ||
const [r, g, b, a] = multiplyColorMatrix([lms[0] ** 3, lms[1] ** 3, lms[2] ** 3, lms[3]], LMS_TO_LINEAR_RGB); | ||
return [ | ||
Math.max(0, r), // r | ||
Math.max(0, g), // g | ||
Math.max(0, b), // b | ||
Math.max(0, a), // a | ||
r, // r | ||
g, // g | ||
b, // b | ||
a, // a | ||
]; | ||
@@ -174,8 +136,11 @@ } | ||
export function linearRGBTosRGB(rgb: LinearRGB): sRGB { | ||
return rgb.map((value, n) => { | ||
if (n === 3) return clamp(value, 0, 1); // alpha | ||
if (value <= 0.0031308) return clamp(value * 12.92, 0, 1); | ||
else return clamp(1.055 * Math.pow(value, 1 / 2.4) - 0.055, 0, 1); | ||
}) as sRGB; | ||
const r = Math.abs(rgb[0]); | ||
const g = Math.abs(rgb[1]); | ||
const b = Math.abs(rgb[2]); | ||
return [ | ||
r < 0.0031308 ? rgb[0] * 12.92 : 1.055 * Math.pow(r, 1 / 2.4) - 0.055, // r | ||
g < 0.0031308 ? rgb[1] * 12.92 : 1.055 * Math.pow(g, 1 / 2.4) - 0.055, // g | ||
b < 0.0031308 ? rgb[2] * 12.92 : 1.055 * Math.pow(b, 1 / 2.4) - 0.055, // b | ||
rgb[3], // alpha | ||
]; | ||
} | ||
@@ -185,37 +150,42 @@ | ||
export function linearRGBToLMS(lrgb: LinearRGB): LMS { | ||
return multiplyColorMatrix(lrgb, LINEAR_RGB_TO_LMS); | ||
const lms = multiplyColorMatrix(lrgb, LINEAR_RGB_TO_LMS); | ||
return [ | ||
Math.cbrt(lms[0]), // L | ||
Math.cbrt(lms[1]), // M | ||
Math.cbrt(lms[2]), // S | ||
lms[3], | ||
]; | ||
} | ||
/** Linear sRGB -> XYZ (D65) */ | ||
export function linearRGBToXYZ(rgb: LinearRGB): XYZ_D65 { | ||
return multiplyColorMatrix(rgb, LINEAR_RGB_TO_XYZ_D65); | ||
} | ||
/** LUV -> sRGB */ | ||
// export function luvTosRGB(luv: LUV): sRGB { | ||
// return linearRGBTosRGB(xyzToLinearRGB(luvToXYZ(luv))); | ||
// } | ||
/** LUV -> XYZ (D65) */ | ||
export function luvToXYZ(luv: LUV): XYZ_D65 { | ||
let [L, u, v, alpha] = luv; | ||
// export function luvToXYZ(luv: LUV, ε = 216 / 24389): XYZ_D65 { | ||
// let [L, u, v, alpha] = luv; | ||
L *= 100; | ||
u *= 100; | ||
v *= 100; | ||
const y = L > ε * κ ? ((L + 16) / 116) ** 3 : L / κ; | ||
const a = ((52 * L) / (u + 13 * L * D65_U_REF) - 1) / 3 || 0; | ||
const b = -5 * y || 0; | ||
const c = -1 / 3; | ||
const d = y * ((39 * L) / (v + 13 * L * D65_V_REF) - 5) || 0; | ||
const x = (d - b) / (a - c) || 0; | ||
const z = x * a + b; | ||
// L; | ||
// u; | ||
// v; | ||
// const y = L > ε * D65_κ ? ((L + 16) / 116) ** 3 : L / D65_κ; | ||
// const a = ((52 * L) / (u + 13 * L * D65_U_REF) - 1) / 3 || 0; | ||
// const b = -5 * y || 0; | ||
// const c = -1 / 3; | ||
// const d = y * ((39 * L) / (v + 13 * L * D65_V_REF) - 5) || 0; | ||
// const x = (d - b) / (a - c) || 0; | ||
// const z = x * a + b; | ||
return [x, y, z, alpha]; | ||
} | ||
// return [x, y, z, alpha]; | ||
// } | ||
/** LUV -> sRGB */ | ||
export function luvTosRGB(luv: LUV): sRGB { | ||
return xyzTosRGB(luvToXYZ(luv)); | ||
} | ||
/** Oklab -> LMS */ | ||
export function oklabToLMS(oklab: Oklab): sRGB { | ||
const lms = multiplyColorMatrix(oklab, OKLAB_TO_LMS); | ||
return [ | ||
lms[0] ** 3, // l | ||
lms[1] ** 3, // m | ||
lms[2] ** 3, // s | ||
lms[3], // alpha | ||
]; | ||
return multiplyColorMatrix(oklab, OKLAB_TO_LMS); | ||
} | ||
@@ -225,3 +195,27 @@ | ||
export function oklabTosRGB(oklab: Oklab): sRGB { | ||
return linearRGBTosRGB(lmsToLinearRGB(oklabToLMS(oklab))); | ||
const rgb = linearRGBTosRGB(lmsToLinearRGB(oklabToLMS(oklab))); | ||
if (rgb[0] > 1.001 || rgb[0] < -0.001 || rgb[1] > 1.001 || rgb[1] < -0.001 || rgb[2] > 1.001 || rgb[2] < -0.001) { | ||
// “Preserve light, clamp Chroma” method from https://bottosson.github.io/posts/gamutclipping/ | ||
const ε = 0.00001; | ||
const [L, a, b, alpha] = oklab; | ||
const C = Math.max(ε, Math.sqrt(a ** 2 + b ** 2)); | ||
const Lgamut = clamp(L, 0, 1); | ||
const aNorm = a / C; | ||
const bNorm = b / C; | ||
const t = findGamutIntersection(aNorm, bNorm, L, C, Lgamut); | ||
return linearRGBTosRGB( | ||
lmsToLinearRGB( | ||
oklabToLMS([ | ||
Lgamut * (1 - t) + t * L, // L | ||
aNorm * (t * C), // a | ||
bNorm * (t * C), // b | ||
alpha, | ||
]) | ||
) | ||
); | ||
} | ||
return rgb; | ||
} | ||
@@ -234,16 +228,19 @@ | ||
/** sRGB -> Linear RGB */ | ||
/** sRGB -> Linear sRGB */ | ||
export function sRGBToLinearRGB(rgb: sRGB): LinearRGB { | ||
return rgb.map((value, n) => { | ||
if (n === 3) return value; // alpha | ||
if (value <= 0.04045) return value / 12.92; | ||
else return ((value + 0.055) / 1.055) ** 2.4; | ||
}) as LinearRGB; | ||
const r = Math.abs(rgb[0]); | ||
const g = Math.abs(rgb[1]); | ||
const b = Math.abs(rgb[2]); | ||
return [ | ||
r < 0.04045 ? rgb[0] / 12.92 : ((r + 0.055) / 1.055) ** 2.4, // r | ||
g < 0.04045 ? rgb[1] / 12.92 : ((g + 0.055) / 1.055) ** 2.4, // g | ||
b < 0.04045 ? rgb[2] / 12.92 : ((b + 0.055) / 1.055) ** 2.4, // b | ||
rgb[3], // alpha | ||
]; | ||
} | ||
/** sRGB -> Luv */ | ||
export function sRGBToLuv(rgb: sRGB): LUV { | ||
return xyzToLuv(sRGBToXYZ(rgb)); | ||
} | ||
/** Linear sRGB -> Luv */ | ||
// export function sRGBToLuv(rgb: LinearRGB): LUV { | ||
// return xyzToLuv(linearRGBToXYZ(sRGBToLinearRGB(rgb))); | ||
// } | ||
@@ -260,24 +257,19 @@ /** sRGB -> Oklab */ | ||
/** sRGB -> XYZ (D65) */ | ||
export function sRGBToXYZ(rgb: sRGB): XYZ_D65 { | ||
return multiplyColorMatrix(sRGBToLinearRGB(rgb), LINEAR_RGB_TO_XYZ_D65); | ||
/** XYZ (D65) -> Linear sRGB */ | ||
export function xyzToLinearRGB(xyz: XYZ_D65): sRGB { | ||
return multiplyColorMatrix(xyz, XYZ_D65_TO_LINEAR_RGB); | ||
} | ||
/** XYZ (D65) -> sRGB */ | ||
export function xyzTosRGB(xyz: XYZ_D65): LinearRGB { | ||
return linearRGBTosRGB(multiplyColorMatrix(xyz, XYZ_D65_TO_LINEAR_RGB)); | ||
} | ||
/** XYZ (D65) -> Luv */ | ||
export function xyzToLuv(xyz: XYZ_D65): LUV { | ||
const [x, y, z, alpha] = xyz; | ||
// export function xyzToLuv(xyz: XYZ_D65, ε = 216 / 24389): LUV { | ||
// const [x, y, z, alpha] = xyz; | ||
const denominator = x + 15 * y + 3 * z; | ||
const _u = (4 * x) / denominator || 0; | ||
const _v = (9 * y) / denominator || 0; | ||
const L = x > ε ? 116 * Math.pow(y, 1 / 3) - 16 : κ * y; | ||
const u = 13 * L * (_u - D65_U_REF) || 0; // `|| 0` fixes -0 | ||
const v = 13 * L * (_v - D65_V_REF) || 0; | ||
// const denominator = x + 15 * y + 3 * z; | ||
// const _u = (4 * x) / denominator || 0; | ||
// const _v = (9 * y) / denominator || 0; | ||
// const L = x > ε ? 116 * Math.pow(y, 1 / 3) - 16 : D65_κ * y; | ||
// const u = 13 * L * (_u - D65_U_REF) || 0; // `|| 0` fixes -0 | ||
// const v = 13 * L * (_v - D65_V_REF) || 0; | ||
return [L / 100, u / 100, v / 100, alpha]; | ||
} | ||
// return [L, u, v, alpha]; | ||
// } |
@@ -9,3 +9,3 @@ export type { ColorMatrix, HSL, HWB, LMS, LinearRGB, LUV, Oklab, Oklch, sRGB, XYZ_D65 } from './colorspace.js'; | ||
export { from, lightness } from './parse.js'; | ||
export { clamp, colorFn, leftPad, round } from './utils.js'; | ||
export { clamp, colorFn, degToRad, leftPad, multiplyColorMatrix, radToDeg, round } from './utils.js'; | ||
@@ -12,0 +12,0 @@ import { darken, lighten } from './lighten-darken.js'; |
@@ -1,12 +0,12 @@ | ||
import type { Color, sRGB } from './colorspace.js'; | ||
import { Color, linearRGBToLMS, lmsToLinearRGB, sRGB } from './colorspace.js'; | ||
import type { ColorOutput } from './parse.js'; | ||
import { linearRGBTosRGB, luvTosRGB, sRGBToLuv, sRGBToOklab, sRGBToOklch, sRGBToLinearRGB, oklabTosRGB, oklchTosRGB } from './colorspace.js'; | ||
import { linearRGBTosRGB, sRGBToOklab, sRGBToOklch, sRGBToLinearRGB, oklabTosRGB, oklchTosRGB } from './colorspace.js'; | ||
import { from } from './parse.js'; | ||
import { clamp } from './utils.js'; | ||
export type MixColorSpace = 'oklab' | 'oklch' | 'luv' | 'linearRGB' | 'sRGB'; | ||
export type MixColorSpace = 'oklab' | 'oklch' | 'lms' | 'linearRGB' | 'sRGB'; | ||
/** | ||
* Mix colors via Oklch (better for preserving hue than Oklab) | ||
* Mix colors via LMS (better for preserving hue than Oklab) | ||
* @param {Color} color1 | ||
@@ -29,3 +29,3 @@ * @param {Color} color2 | ||
oklab: sRGBToOklab, | ||
luv: sRGBToLuv, | ||
lms: (c) => sRGBToLinearRGB(linearRGBToLMS(c)), | ||
linearRGB: sRGBToLinearRGB, | ||
@@ -38,3 +38,3 @@ sRGB: (c) => c, | ||
oklab: oklabTosRGB, | ||
luv: luvTosRGB, | ||
lms: (c) => lmsToLinearRGB(linearRGBTosRGB(c)), | ||
linearRGB: linearRGBTosRGB, | ||
@@ -41,0 +41,0 @@ sRGB: (c) => c, |
@@ -1,4 +0,4 @@ | ||
import { Color, LinearRGB, LUV, Oklab, Oklch, sRGB, sRGBToXYZ, xyzTosRGB, XYZ_D65 } from './colorspace.js'; | ||
import type { Color, LinearRGB, Oklab, Oklch, sRGB, XYZ_D65 } from './colorspace.js'; | ||
import { hslTosRGB, hwbTosRGB, linearRGBTosRGB, luvTosRGB, oklabTosRGB, oklchTosRGB, sRGBToLinearRGB, sRGBToLuv, sRGBToOklab, sRGBToOklch } from './colorspace.js'; | ||
import { hslTosRGB, hwbTosRGB, linearRGBTosRGB, linearRGBToXYZ, oklabTosRGB, oklchTosRGB, sRGBToLinearRGB, sRGBToOklab, sRGBToOklch, xyzToLinearRGB } from './colorspace.js'; | ||
import cssNames from './css-names.js'; | ||
@@ -23,5 +23,5 @@ import { clamp, colorFn, leftPad, round } from './utils.js'; | ||
/** `color(luv 0 0 0/1)` */ | ||
luv: string; // TODO: fix bug | ||
// luv: string; | ||
/** [L, u, v, alpha] */ | ||
luvVal: LUV; | ||
// luvVal: LUV; | ||
/** `color(display-p3 0 0 0/1)` */ | ||
@@ -73,5 +73,5 @@ p3: string; | ||
let hexString = '#'; | ||
hexString += leftPad(Math.round(color[0] * 255).toString(16), 2); // r | ||
hexString += leftPad(Math.round(color[1] * 255).toString(16), 2); // g | ||
hexString += leftPad(Math.round(color[2] * 255).toString(16), 2); // b | ||
hexString += leftPad(Math.round(clamp(color[0] * 255, 0, 255)).toString(16), 2); // r | ||
hexString += leftPad(Math.round(clamp(color[1] * 255, 0, 255)).toString(16), 2); // g | ||
hexString += leftPad(Math.round(clamp(color[2] * 255, 0, 255)).toString(16), 2); // b | ||
if (color[3] < 1) hexString += leftPad(Math.round(color[3] * 255).toString(16), 2); // a | ||
@@ -82,13 +82,13 @@ return hexString; | ||
if (color[3] < 1) console.warn(`hexVal converted a semi-transparent color (${color[3] * 100}%) to fully opaque`); // eslint-disable-line no-console | ||
const r = Math.round(color[0] * 255); | ||
const g = Math.round(color[1] * 255); | ||
const b = Math.round(color[2] * 255); | ||
const r = Math.round(clamp(color[0] * 255, 0, 255)); | ||
const g = Math.round(clamp(color[1] * 255, 0, 255)); | ||
const b = Math.round(clamp(color[2] * 255, 0, 255)); | ||
return r * R_FACTOR + g * G_FACTOR + b; | ||
}, | ||
get luv(): string { | ||
return colorFn('luv', sRGBToLuv(color)); | ||
}, | ||
get luvVal(): LUV { | ||
return sRGBToLuv(color); | ||
}, | ||
// get luv(): string { | ||
// return colorFn('luv', sRGBToLuv(color)); | ||
// }, | ||
// get luvVal(): LUV { | ||
// return sRGBToLuv(color); | ||
// }, | ||
get rgb(): string { | ||
@@ -122,6 +122,6 @@ return colorFn('rgb', color); | ||
get xyz(): string { | ||
return colorFn('xyz-d65', sRGBToXYZ(color)); | ||
return colorFn('xyz-d65', linearRGBToXYZ(sRGBToLinearRGB(color))); | ||
}, | ||
get xyzVal(): XYZ_D65 { | ||
return sRGBToXYZ(color); | ||
return linearRGBToXYZ(sRGBToLinearRGB(color)); | ||
}, | ||
@@ -141,3 +141,3 @@ }; | ||
export function hexNumTosRGB(hex: number): sRGB { | ||
if (hex > RGB_RANGE) throw new Error('better-color-tools can’t parse hex numbers with alpha (0x0000000 is indistinguishable from 0x00000000). Please use hex string, or another color method'); | ||
if (hex > RGB_RANGE) throw new Error('8-digit hex values (with transparency) aren’t supported'); | ||
let remaining = hex; | ||
@@ -161,5 +161,5 @@ const r = Math.floor(remaining / R_FACTOR); // Math.floor gets rid of G + B | ||
// unbounded | ||
else if (normalize[n] === Infinity) values[n] = parseFloat(value); | ||
else if (normalize[n] === Infinity || normalize[n] === 0 || normalize[n] === 1) values[n] = parseFloat(value); | ||
// bounded | ||
else values[n] = parseFloat(value) / (normalize[n] || 1); | ||
else values[n] = parseFloat(value) / normalize[n]; | ||
}); | ||
@@ -173,3 +173,3 @@ return values; | ||
if (Array.isArray(rawColor)) { | ||
if (rawColor.some((val) => typeof val != 'number')) throw new Error(`Color array must be numbers, received ${rawColor}`); | ||
if (typeof rawColor[0] != 'number' || typeof rawColor[1] != 'number' || typeof rawColor[2] != 'number') throw new Error(`Color array must be numbers, received ${rawColor}`); | ||
if (rawColor.length < 3 || rawColor.length > 4) throw new Error(`Expected [R, G, B, A?], received ${rawColor}`); | ||
@@ -238,9 +238,12 @@ return [ | ||
} | ||
case 'linear-rgb': | ||
case 'linear-srgb': | ||
case 'rgb-linear': | ||
case 'srgb-linear': { | ||
const [r, g, b, a] = parseValueStr(valueStr, [255, 255, 255, 1]); | ||
return linearRGBTosRGB([clamp(r, 0, 1), clamp(g, 0, 1), clamp(b, 0, 1), clamp(a, 0, 1)]); | ||
const rgb = parseValueStr(valueStr, [255, 255, 255, 1]); | ||
return linearRGBTosRGB(rgb); | ||
} | ||
case 'hsl': | ||
case 'hsla': { | ||
const [h, s, l, a] = parseValueStr(valueStr, [Infinity, 1, 1, 1]); | ||
const [h, s, l, a] = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
return hslTosRGB([h, clamp(s, 0, 1), clamp(l, 0, 1), clamp(a, 0, 1)]); | ||
@@ -250,3 +253,3 @@ } | ||
case 'hwba': { | ||
const [h, w, b, a] = parseValueStr(valueStr, [Infinity, 1, 1, 1]); | ||
const [h, w, b, a] = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
return hwbTosRGB([h, clamp(w, 0, 1), clamp(b, 0, 1), clamp(a, 0, 1)]); | ||
@@ -259,6 +262,6 @@ } | ||
} | ||
case 'luv': { | ||
const luv = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
return luvTosRGB(luv); | ||
} | ||
// case 'luv': { | ||
// const luv = parseValueStr(valueStr, [1, 1, 1, 1]); | ||
// return luvTosRGB(luv); | ||
// } | ||
case 'oklab': { | ||
@@ -268,7 +271,7 @@ return oklabTosRGB(parseValueStr(valueStr, [1, 1, 1, 1])); | ||
case 'oklch': { | ||
return oklchTosRGB(parseValueStr(valueStr, [1, 1, Infinity, 1])); | ||
return oklchTosRGB(parseValueStr(valueStr, [1, 1, 1, 1])); | ||
} | ||
case 'xyz': | ||
case 'xyz-d65': { | ||
return xyzTosRGB(parseValueStr(valueStr, [1, 1, 1, 1])); | ||
return linearRGBTosRGB(xyzToLinearRGB(parseValueStr(valueStr, [1, 1, 1, 1]))); | ||
} | ||
@@ -275,0 +278,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import type { sRGB } from './index'; | ||
import type { ColorMatrix, sRGB } from './index'; | ||
@@ -29,2 +29,4 @@ /** you know it, you love it */ | ||
// note: JavaScript abbreviates anything > 6 decimal places as 1e-7, etc. | ||
switch (colorSpace) { | ||
@@ -38,12 +40,11 @@ case 'rgb': | ||
} | ||
// color(oklab 54.0% -0.10 -0.02) | ||
// oklab(54.0% -0.10 -0.02) | ||
case 'oklab': | ||
case 'oklch': { | ||
return `color(${colorSpace} ${round(x * 100, 5)}% ${round(y, 5)} ${round(z, 5)}${alphaSlash})`; | ||
return `${colorSpace}(${round(x * 100, 6)}% ${round(y, 6)} ${round(z, 6)}${alphaSlash})`; | ||
} | ||
// color(display-p3 0.4 0.2 0.6) | ||
// color(luv 0.4 0.2 0.6) | ||
// color(xyz-d65 0.4 0.2 0.6) | ||
default: | ||
return `color(${colorSpace} ${round(x, 5)} ${round(y, 5)} ${round(z, 5)}${alphaSlash})`; | ||
return `color(${colorSpace} ${round(x, 6)} ${round(y, 6)} ${round(z, 6)}${alphaSlash})`; | ||
} | ||
@@ -53,10 +54,10 @@ } | ||
/** multiply 3x1 color matrix with colorspace */ | ||
export function multiplyColorMatrix(color: sRGB, matrix: number[][]): sRGB { | ||
export function multiplyColorMatrix(color: sRGB, matrix: ColorMatrix): sRGB { | ||
const product: sRGB = [...color]; | ||
for (let row = 0; row < matrix.length; row++) { | ||
for (let y = 0; y < matrix.length; y++) { | ||
let sum = 0; | ||
for (let col = 0; col < matrix[0].length; col++) { | ||
sum += color[col] * matrix[row][col]; | ||
for (let x = 0; x < matrix[y].length; x++) { | ||
sum += color[x] * matrix[y][x]; | ||
} | ||
product[row] = sum; | ||
product[y] = sum; | ||
} | ||
@@ -63,0 +64,0 @@ return product; |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
225341
34
2194
194