From d9dd97d6c5cd772ae68cf5f424b94db486078997 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 24 Jan 2026 18:59:38 +0100 Subject: [PATCH 1/2] update color-helpers --- packages/color-helpers/CHANGELOG.md | 11 + packages/color-helpers/dist/index.mjs | 30 +- packages/color-helpers/scripts/hashes.json | 6 +- packages/color-helpers/src/conversions.js | 504 ++++++++++++++++++ .../color-helpers/src/conversions/gam-2020.ts | 9 +- .../src/conversions/lab-to-lch.ts | 18 +- .../color-helpers/src/conversions/lin-2020.ts | 9 +- .../src/conversions/oklab-to-oklch.ts | 17 +- .../src/conversions/srgb-to-hsl.ts | 5 + .../src/conversions/xyz/index.ts | 11 +- .../test/basic/color-function.mjs | 6 +- .../test/basic/color-mix-function.mjs | 10 +- plugins/postcss-color-function/test/basic.css | 4 +- .../test/basic.expect.css | 2 +- .../test/basic.preserve-true.expect.css | 6 +- .../test/basic.with-cloned-rules.expect.css | 6 +- 16 files changed, 594 insertions(+), 60 deletions(-) create mode 100644 packages/color-helpers/src/conversions.js diff --git a/packages/color-helpers/CHANGELOG.md b/packages/color-helpers/CHANGELOG.md index 0ead14023..907406e90 100644 --- a/packages/color-helpers/CHANGELOG.md +++ b/packages/color-helpers/CHANGELOG.md @@ -1,5 +1,16 @@ # Changes to Color Helpers +### Unreleased (patch) + +- Match latest changes made in `csswg-drafts` for: + - `gam_2020` + - `Lab_to_LCH` + - `lin_2020` + - `OKLab_to_OKLCH` + - `sRGB_to_HSL` + - `XYZ_D50_to_HWB` + + ### 6.0.0 _January 14, 2026_ diff --git a/packages/color-helpers/dist/index.mjs b/packages/color-helpers/dist/index.mjs index 7ca812eb6..904e6caa1 100644 --- a/packages/color-helpers/dist/index.mjs +++ b/packages/color-helpers/dist/index.mjs @@ -38,7 +38,7 @@ function multiplyMatrices(t,n){return[t[0]*n[0]+t[1]*n[1]+t[2]*n[2],t[3]*n[0]+t[ /** * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */function Lab_to_LCH(t){const n=180*Math.atan2(t[2],t[1])/Math.PI;return[t[0],Math.sqrt(Math.pow(t[1],2)+Math.pow(t[2],2)),n>=0?n:n+360]}const _=[.3457/.3585,1,.2958/.3585]; + */function Lab_to_LCH(t){const n=Math.sqrt(Math.pow(t[1],2)+Math.pow(t[2],2));let _=180*Math.atan2(t[2],t[1])/Math.PI;return _<0&&(_+=360),n<=.0015&&(_=NaN),[t[0],n,_]}const _=[.3457/.3585,1,.2958/.3585]; /** * Convert Lab to D50-adapted XYZ * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document @@ -54,7 +54,7 @@ function multiplyMatrices(t,n){return[t[0]*n[0]+t[1]*n[1]+t[2]*n[2],t[3]*n[0]+t[ * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). * @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js - */function OKLab_to_OKLCH(t){const n=180*Math.atan2(t[2],t[1])/Math.PI;return[t[0],Math.sqrt(t[1]**2+t[2]**2),n>=0?n:n+360]}const o=[1.2268798758459243,-.5578149944602171,.2813910456659647,-.0405757452148008,1.112286803280317,-.0717110580655164,-.0763729366746601,-.4214933324022432,1.5869240198367816],e=[1,.3963377773761749,.2158037573099136,1,-.1055613458156586,-.0638541728258133,1,-.0894841775298119,-1.2914855480194092]; + */function OKLab_to_OKLCH(t){const n=Math.sqrt(t[1]**2+t[2]**2);let _=180*Math.atan2(t[2],t[1])/Math.PI;return _<0&&(_+=360),n<=4e-6&&(_=NaN),[t[0],n,_]}const o=[1.2268798758459243,-.5578149944602171,.2813910456659647,-.0405757452148008,1.112286803280317,-.0717110580655164,-.0763729366746601,-.4214933324022432,1.5869240198367816],e=[1,.3963377773761749,.2158037573099136,1,-.1055613458156586,-.0638541728258133,1,-.0894841775298119,-1.2914855480194092]; /** * Given OKLab, convert to XYZ relative to D65 * @@ -113,7 +113,7 @@ function XYZ_to_OKLab(t){const n=multiplyMatrices(l,t);return multiplyMatrices(i * * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */const D=1.09929682680944,b=.018053968510807;function gam_2020_channel(t){const n=t<0?-1:1,_=Math.abs(t);return _>b?n*(D*Math.pow(_,.45)-(D-1)):4.5*t} + */function gam_2020_channel(t){const n=t<0?-1:1,_=Math.abs(t);return n*Math.pow(_,1/2.4)} /** * Convert an array of linear-light sRGB values in the range 0.0-1.0 to gamma corrected form * Extended transfer function: @@ -138,7 +138,7 @@ function XYZ_to_OKLab(t){const n=multiplyMatrices(l,t);return multiplyMatrices(i * * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */const g=1/512;function gam_ProPhoto_channel(t){const n=t<0?-1:1,_=Math.abs(t);return _>=g?n*Math.pow(_,1/1.8):16*t} + */const D=1/512;function gam_ProPhoto_channel(t){const n=t<0?-1:1,_=Math.abs(t);return _>=D?n*Math.pow(_,1/1.8):16*t} /** * Convert an array of linear-light a98-rgb in the range 0.0-1.0 * to gamma corrected form. Negative values are also now accepted @@ -153,7 +153,7 @@ function XYZ_to_OKLab(t){const n=multiplyMatrices(l,t);return multiplyMatrices(i * * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */const X=1.09929682680944,Y=.018053968510807;function lin_2020_channel(t){const n=t<0?-1:1,_=Math.abs(t);return _<4.5*Y?t/4.5:n*Math.pow((_+X-1)/X,1/.45)}const Z=[63426534/99577255,20160776/139408157,47086771/278816314,26158966/99577255,.677998071518871,8267143/139408157,0,19567812/697040785,1.0609850577107909]; + */function lin_2020_channel(t){const n=t<0?-1:1,_=Math.abs(t);return n*Math.pow(_,2.4)}const b=[63426534/99577255,20160776/139408157,47086771/278816314,26158966/99577255,.677998071518871,8267143/139408157,0,19567812/697040785,1.0609850577107909]; /** * Convert an array of linear-light rec2020 values to CIE XYZ * using D65 (no chromatic adaptation) @@ -180,7 +180,7 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */function lin_P3(t){return lin_sRGB(t)}const f=[608311/1250200,189793/714400,198249/1000160,35783/156275,247089/357200,198249/2500400,0,32229/714400,5220557/5000800]; + */function lin_P3(t){return lin_sRGB(t)}const g=[608311/1250200,189793/714400,198249/1000160,35783/156275,247089/357200,198249/2500400,0,32229/714400,5220557/5000800]; /** * Convert an array of linear-light display-p3 values to CIE XYZ * using D65 (no chromatic adaptation) @@ -188,7 +188,7 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). * @see http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - */function lin_P3_to_XYZ(t){return multiplyMatrices(f,t)} + */function lin_P3_to_XYZ(t){return multiplyMatrices(g,t)} /** * Convert an array of prophoto-rgb values where in-gamut Colors are in the * range [0.0 - 1.0] to linear light (un-companded) form. Transfer curve is @@ -196,7 +196,7 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */const M=16/512;function lin_ProPhoto_channel(t){const n=t<0?-1:1,_=Math.abs(t);return _<=M?t/16:n*Math.pow(_,1.8)}const p=[.7977666449006423,.13518129740053308,.0313477341283922,.2880748288194013,.711835234241873,8993693872564e-17,0,0,.8251046025104602]; + */const X=16/512;function lin_ProPhoto_channel(t){const n=t<0?-1:1,_=Math.abs(t);return _<=X?t/16:n*Math.pow(_,1.8)}const Y=[.7977666449006423,.13518129740053308,.0313477341283922,.2880748288194013,.711835234241873,8993693872564e-17,0,0,.8251046025104602]; /** * Convert an array of linear-light prophoto-rgb values to CIE D50 XYZ. * Matrix cannot be expressed in rational form, but is calculated to 64 bit accuracy. @@ -204,7 +204,7 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). * @see see https://github.com/w3c/csswg-drafts/issues/7675 - */function lin_a98rgb_channel(t){const n=t<0?-1:1,_=Math.abs(t);return n*Math.pow(_,563/256)}const d=[573536/994567,263643/1420810,187206/994567,591459/1989134,6239551/9945670,374412/4972835,53769/1989134,351524/4972835,4929758/4972835]; + */function lin_a98rgb_channel(t){const n=t<0?-1:1,_=Math.abs(t);return n*Math.pow(_,563/256)}const Z=[573536/994567,263643/1420810,187206/994567,591459/1989134,6239551/9945670,374412/4972835,53769/1989134,351524/4972835,4929758/4972835]; /** * Convert an array of linear-light a98-rgb values to CIE XYZ * http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html @@ -218,14 +218,14 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * @see http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html * @see https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf * @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/matrixmaker.html - */const L=[506752/1228815,87881/245763,12673/70218,87098/409605,175762/245763,12673/175545,7918/409605,87881/737289,1001167/1053270]; + */const f=[506752/1228815,87881/245763,12673/70218,87098/409605,175762/245763,12673/175545,7918/409605,87881/737289,1001167/1053270]; /** * Convert an array of linear-light sRGB values to CIE XYZ * using sRGB's own white, D65 (no chromatic adaptation) * * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). - */function lin_sRGB_to_XYZ(t){return multiplyMatrices(L,t)} + */function lin_sRGB_to_XYZ(t){return multiplyMatrices(f,t)} /** * Convert an array of gamma-corrected sRGB values in the 0.0 to 1.0 range to HSL. * @@ -239,7 +239,7 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/utilities.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). * * @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/better-rgbToHsl.js - */function sRGB_to_HSL(t){const n=t[0],_=t[1],o=t[2],e=Math.max(n,_,o),a=Math.min(n,_,o),r=(a+e)/2,l=e-a;let i=Number.NaN,c=0;if(0!==Math.round(1e5*l)){const t=Math.round(1e5*r);switch(c=0===t||1e5===t?0:(e-r)/Math.min(r,1-r),e){case n:i=(_-o)/l+(_=360&&(i-=360),[i,100*c,100*r]}function sRGB_to_Hue(t){const n=t[0],_=t[1],o=t[2],e=Math.max(n,_,o),a=Math.min(n,_,o);let r=Number.NaN;const l=e-a;if(0!==l){switch(e){case n:r=(_-o)/l+(_=360&&(r-=360),r}function sRGB_to_XYZ_D50(t){let n=t;return n=lin_sRGB(n),n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_sRGB(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n),n=gam_sRGB(n),n}function HSL_to_XYZ_D50(t){let n=t;return n=HSL_to_sRGB(n),n=lin_sRGB(n),n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_HSL(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n),n=gam_sRGB(n),n=sRGB_to_HSL(n),n}function HWB_to_XYZ_D50(t){let n=t;return n=HWB_to_sRGB(n),n=lin_sRGB(n),n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_HWB(t){let n=t;n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n);const _=gam_sRGB(n),o=Math.min(_[0],_[1],_[2]),e=1-Math.max(_[0],_[1],_[2]);return[sRGB_to_Hue(_),100*o,100*e]}function Lab_to_XYZ_D50(t){let n=t;return n=Lab_to_XYZ(n),n}function XYZ_D50_to_Lab(t){let n=t;return n=XYZ_to_Lab(n),n}function LCH_to_XYZ_D50(t){let n=t;return n=LCH_to_Lab(n),n=Lab_to_XYZ(n),n}function XYZ_D50_to_LCH(t){let n=t;return n=XYZ_to_Lab(n),n=Lab_to_LCH(n),n}function OKLab_to_XYZ_D50(t){let n=t;return n=OKLab_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_OKLab(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_OKLab(n),n}function OKLCH_to_XYZ_D50(t){let n=t;return n=OKLCH_to_OKLab(n),n=OKLab_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_OKLCH(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_OKLab(n),n=OKLab_to_OKLCH(n),n}function lin_sRGB_to_XYZ_D50(t){let n=t;return n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_lin_sRGB(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n),n}function a98_RGB_to_XYZ_D50(t){let n=t; + */function sRGB_to_HSL(t){const n=t[0],_=t[1],o=t[2],e=Math.max(n,_,o),a=Math.min(n,_,o),r=(a+e)/2,l=e-a;let i=Number.NaN,c=0;if(0!==Math.round(1e5*l)){const t=Math.round(1e5*r);switch(c=0===t||1e5===t?0:(e-r)/Math.min(r,1-r),e){case n:i=(_-o)/l+(_=360&&(i-=360);return c<=1e-5&&(i=NaN),[i,100*c,100*r]}function sRGB_to_Hue(t){const n=t[0],_=t[1],o=t[2],e=Math.max(n,_,o),a=Math.min(n,_,o);let r=Number.NaN;const l=e-a;if(0!==l){switch(e){case n:r=(_-o)/l+(_=360&&(r-=360),r}function sRGB_to_XYZ_D50(t){let n=t;return n=lin_sRGB(n),n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_sRGB(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n),n=gam_sRGB(n),n}function HSL_to_XYZ_D50(t){let n=t;return n=HSL_to_sRGB(n),n=lin_sRGB(n),n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_HSL(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n),n=gam_sRGB(n),n=sRGB_to_HSL(n),n}function HWB_to_XYZ_D50(t){let n=t;return n=HWB_to_sRGB(n),n=lin_sRGB(n),n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_HWB(t){let n=t;n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n);const _=gam_sRGB(n),o=Math.min(_[0],_[1],_[2]),e=1-Math.max(_[0],_[1],_[2]);let a=sRGB_to_Hue(_);return o+e>=.99999&&(a=NaN),[a,100*o,100*e]}function Lab_to_XYZ_D50(t){let n=t;return n=Lab_to_XYZ(n),n}function XYZ_D50_to_Lab(t){let n=t;return n=XYZ_to_Lab(n),n}function LCH_to_XYZ_D50(t){let n=t;return n=LCH_to_Lab(n),n=Lab_to_XYZ(n),n}function XYZ_D50_to_LCH(t){let n=t;return n=XYZ_to_Lab(n),n=Lab_to_LCH(n),n}function OKLab_to_XYZ_D50(t){let n=t;return n=OKLab_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_OKLab(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_OKLab(n),n}function OKLCH_to_XYZ_D50(t){let n=t;return n=OKLCH_to_OKLab(n),n=OKLab_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_OKLCH(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_OKLab(n),n=OKLab_to_OKLCH(n),n}function lin_sRGB_to_XYZ_D50(t){let n=t;return n=lin_sRGB_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_lin_sRGB(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_sRGB(n),n}function a98_RGB_to_XYZ_D50(t){let n=t; /** * Convert an array of a98-rgb values in the range 0.0 - 1.0 * to linear light (un-companded) form. Negative values are also now accepted @@ -247,7 +247,7 @@ function lin_sRGB(t){return[lin_sRGB_channel(t[0]),lin_sRGB_channel(t[1]),lin_sR * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). */ -var _;return n=[lin_a98rgb_channel((_=n)[0]),lin_a98rgb_channel(_[1]),lin_a98rgb_channel(_[2])],n=multiplyMatrices(d,n),n=D65_to_D50(n),n}function XYZ_D50_to_a98_RGB(t){let n=t;var _;return n=D50_to_D65(n),n=multiplyMatrices(h,n),n=[gam_a98rgb_channel((_=n)[0]),gam_a98rgb_channel(_[1]),gam_a98rgb_channel(_[2])],n}function P3_to_XYZ_D50(t){let n=t;return n=lin_P3(n),n=lin_P3_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_P3(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_P3(n),n=gam_P3(n),n}function lin_P3_to_XYZ_D50(t){let n=t;return n=lin_P3_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_lin_P3(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_P3(n),n}function rec_2020_to_XYZ_D50(t){let n=t;var _;return n=[lin_2020_channel((_=n)[0]),lin_2020_channel(_[1]),lin_2020_channel(_[2])],n=multiplyMatrices(Z,n),n=D65_to_D50(n),n}function XYZ_D50_to_rec_2020(t){let n=t;var _;return n=D50_to_D65(n),n=multiplyMatrices(c,n),n=[gam_2020_channel((_=n)[0]),gam_2020_channel(_[1]),gam_2020_channel(_[2])],n}function ProPhoto_RGB_to_XYZ_D50(t){let n=t;var _;return n=[lin_ProPhoto_channel((_=n)[0]),lin_ProPhoto_channel(_[1]),lin_ProPhoto_channel(_[2])],n=multiplyMatrices(p,n),n}function XYZ_D50_to_ProPhoto(t){let n=t;var _;return n=multiplyMatrices(s,n),n=[gam_ProPhoto_channel((_=n)[0]),gam_ProPhoto_channel(_[1]),gam_ProPhoto_channel(_[2])],n}function XYZ_D65_to_XYZ_D50(t){let n=t;return n=D65_to_D50(n),n}function XYZ_D50_to_XYZ_D65(t){let n=t;return n=D50_to_D65(n),n}function XYZ_D50_to_XYZ_D50(t){return t}function inGamut(t){return t[0]>=-1e-4&&t[0]<=1.0001&&t[1]>=-1e-4&&t[1]<=1.0001&&t[2]>=-1e-4&&t[2]<=1.0001}function clip(t){return[t[0]<0?0:t[0]>1?1:t[0],t[1]<0?0:t[1]>1?1:t[1],t[2]<0?0:t[2]>1?1:t[2]]} +var _;return n=[lin_a98rgb_channel((_=n)[0]),lin_a98rgb_channel(_[1]),lin_a98rgb_channel(_[2])],n=multiplyMatrices(Z,n),n=D65_to_D50(n),n}function XYZ_D50_to_a98_RGB(t){let n=t;var _;return n=D50_to_D65(n),n=multiplyMatrices(h,n),n=[gam_a98rgb_channel((_=n)[0]),gam_a98rgb_channel(_[1]),gam_a98rgb_channel(_[2])],n}function P3_to_XYZ_D50(t){let n=t;return n=lin_P3(n),n=lin_P3_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_P3(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_P3(n),n=gam_P3(n),n}function lin_P3_to_XYZ_D50(t){let n=t;return n=lin_P3_to_XYZ(n),n=D65_to_D50(n),n}function XYZ_D50_to_lin_P3(t){let n=t;return n=D50_to_D65(n),n=XYZ_to_lin_P3(n),n}function rec_2020_to_XYZ_D50(t){let n=t;var _;return n=[lin_2020_channel((_=n)[0]),lin_2020_channel(_[1]),lin_2020_channel(_[2])],n=multiplyMatrices(b,n),n=D65_to_D50(n),n}function XYZ_D50_to_rec_2020(t){let n=t;var _;return n=D50_to_D65(n),n=multiplyMatrices(c,n),n=[gam_2020_channel((_=n)[0]),gam_2020_channel(_[1]),gam_2020_channel(_[2])],n}function ProPhoto_RGB_to_XYZ_D50(t){let n=t;var _;return n=[lin_ProPhoto_channel((_=n)[0]),lin_ProPhoto_channel(_[1]),lin_ProPhoto_channel(_[2])],n=multiplyMatrices(Y,n),n}function XYZ_D50_to_ProPhoto(t){let n=t;var _;return n=multiplyMatrices(s,n),n=[gam_ProPhoto_channel((_=n)[0]),gam_ProPhoto_channel(_[1]),gam_ProPhoto_channel(_[2])],n}function XYZ_D65_to_XYZ_D50(t){let n=t;return n=D65_to_D50(n),n}function XYZ_D50_to_XYZ_D65(t){let n=t;return n=D50_to_D65(n),n}function XYZ_D50_to_XYZ_D50(t){return t}function inGamut(t){return t[0]>=-1e-4&&t[0]<=1.0001&&t[1]>=-1e-4&&t[1]<=1.0001&&t[2]>=-1e-4&&t[2]<=1.0001}function clip(t){return[t[0]<0?0:t[0]>1?1:t[0],t[1]<0?0:t[1]>1?1:t[1],t[2]<0?0:t[2]>1?1:t[2]]} /** * @description Calculate deltaE OK which is the simple root sum of squares * @param {number[]} reference - Array of OKLab values: L as 0..1, a and b as -1..1 @@ -257,7 +257,7 @@ var _;return n=[lin_a98rgb_channel((_=n)[0]),lin_a98rgb_channel(_[1]),lin_a98rgb * @license W3C https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/deltaEOK.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). * @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/deltaEOK.js - */function deltaEOK(t,n){const[_,o,e]=t,[a,r,l]=n,i=_-a,c=o-r,u=e-l;return Math.sqrt(i**2+c**2+u**2)}const B=.02,G=1e-4;function mapGamut(t,n,_){const o=t;let e=clip(n(o)),a=deltaEOK(OKLCH_to_OKLab(_(e)),OKLCH_to_OKLab(o));if(aG;){const t=(r+l)/2;if(o[1]=t,i&&inGamut(n(o)))r=t;else if(e=clip(n(o)),a=deltaEOK(OKLCH_to_OKLab(_(e)),OKLCH_to_OKLab(o)),ap;){const t=(r+l)/2;if(o[1]=t,i&&inGamut(n(o)))r=t;else if(e=clip(n(o)),a=deltaEOK(OKLCH_to_OKLab(_(e)),OKLCH_to_OKLab(o)),a0){const t=_(a);t[0]=o,t[2]=e,a=n(t)}const l=rayTraceBox(r,a);if(!l)break;a=l}return clip(a)}function rayTraceBox(t,n){let _=1/0,o=-1/0;const e=[0,0,0];for(let a=0;a<3;a++){const r=t[a],l=n[a]-r;e[a]=l;const i=0,c=1;if(l){const t=1/l,n=(i-r)*t,e=(c-r)*t;o=Math.max(Math.min(n,e),o),_=Math.min(Math.max(n,e),_)}else if(rc)return!1}return!(o>_||_<0)&&(o<0&&(o=_),!!isFinite(o)&&[t[0]+e[0]*o,t[1]+e[1]*o,t[2]+e[2]*o])}const R={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};function luminance(t){const[n,_,o]=t.map(t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*n+.7152*_+.0722*o}function contrast_ratio_wcag_2_1(t,n){const _=luminance(t),o=luminance(n);return(Math.max(_,o)+.05)/(Math.min(_,o)+.05)}export{HSL_to_XYZ_D50,HWB_to_XYZ_D50,LCH_to_XYZ_D50,Lab_to_XYZ_D50,OKLCH_to_OKLab,OKLCH_to_XYZ_D50,OKLab_to_OKLCH,OKLab_to_XYZ,OKLab_to_XYZ_D50,P3_to_XYZ_D50,ProPhoto_RGB_to_XYZ_D50,XYZ_D50_to_HSL,XYZ_D50_to_HWB,XYZ_D50_to_LCH,XYZ_D50_to_Lab,XYZ_D50_to_OKLCH,XYZ_D50_to_OKLab,XYZ_D50_to_P3,XYZ_D50_to_ProPhoto,XYZ_D50_to_XYZ_D50,XYZ_D50_to_XYZ_D65,XYZ_D50_to_a98_RGB,XYZ_D50_to_lin_P3,XYZ_D50_to_lin_sRGB,XYZ_D50_to_rec_2020,XYZ_D50_to_sRGB,XYZ_D65_to_XYZ_D50,XYZ_to_OKLab,XYZ_to_lin_P3,XYZ_to_lin_sRGB,a98_RGB_to_XYZ_D50,clip,contrast_ratio_wcag_2_1,gam_P3,gam_sRGB,inGamut,lin_P3,lin_P3_to_XYZ,lin_P3_to_XYZ_D50,lin_sRGB,lin_sRGB_to_XYZ,lin_sRGB_to_XYZ_D50,mapGamut,mapGamutRayTrace,R as namedColors,rec_2020_to_XYZ_D50,sRGB_to_XYZ_D50}; + */function mapGamutRayTrace(t,n,_){const o=t[0],e=t[2];let a=n(t);const r=n([o,0,e]);for(let t=0;t<4;t++){if(t>0){const t=_(a);t[0]=o,t[2]=e,a=n(t)}const l=rayTraceBox(r,a);if(!l)break;a=l}return clip(a)}function rayTraceBox(t,n){let _=1/0,o=-1/0;const e=[0,0,0];for(let a=0;a<3;a++){const r=t[a],l=n[a]-r;e[a]=l;const i=0,c=1;if(l){const t=1/l,n=(i-r)*t,e=(c-r)*t;o=Math.max(Math.min(n,e),o),_=Math.min(Math.max(n,e),_)}else if(rc)return!1}return!(o>_||_<0)&&(o<0&&(o=_),!!isFinite(o)&&[t[0]+e[0]*o,t[1]+e[1]*o,t[2]+e[2]*o])}const d={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]};function luminance(t){const[n,_,o]=t.map(t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4));return.2126*n+.7152*_+.0722*o}function contrast_ratio_wcag_2_1(t,n){const _=luminance(t),o=luminance(n);return(Math.max(_,o)+.05)/(Math.min(_,o)+.05)}export{HSL_to_XYZ_D50,HWB_to_XYZ_D50,LCH_to_XYZ_D50,Lab_to_XYZ_D50,OKLCH_to_OKLab,OKLCH_to_XYZ_D50,OKLab_to_OKLCH,OKLab_to_XYZ,OKLab_to_XYZ_D50,P3_to_XYZ_D50,ProPhoto_RGB_to_XYZ_D50,XYZ_D50_to_HSL,XYZ_D50_to_HWB,XYZ_D50_to_LCH,XYZ_D50_to_Lab,XYZ_D50_to_OKLCH,XYZ_D50_to_OKLab,XYZ_D50_to_P3,XYZ_D50_to_ProPhoto,XYZ_D50_to_XYZ_D50,XYZ_D50_to_XYZ_D65,XYZ_D50_to_a98_RGB,XYZ_D50_to_lin_P3,XYZ_D50_to_lin_sRGB,XYZ_D50_to_rec_2020,XYZ_D50_to_sRGB,XYZ_D65_to_XYZ_D50,XYZ_to_OKLab,XYZ_to_lin_P3,XYZ_to_lin_sRGB,a98_RGB_to_XYZ_D50,clip,contrast_ratio_wcag_2_1,gam_P3,gam_sRGB,inGamut,lin_P3,lin_P3_to_XYZ,lin_P3_to_XYZ_D50,lin_sRGB,lin_sRGB_to_XYZ,lin_sRGB_to_XYZ_D50,mapGamut,mapGamutRayTrace,d as namedColors,rec_2020_to_XYZ_D50,sRGB_to_XYZ_D50}; diff --git a/packages/color-helpers/scripts/hashes.json b/packages/color-helpers/scripts/hashes.json index 2f8eabfee..521b4d3d0 100644 --- a/packages/color-helpers/scripts/hashes.json +++ b/packages/color-helpers/scripts/hashes.json @@ -1,10 +1,10 @@ [ "384d52db3fcd98381e18743e1eba590a", - "cd99f5e1c28fc9df18684e351049e7a6", + "a69bf3dc21cfcc876c80ee311ca85f90", "094c9859b0960c4e394947cc4832b54f", "c9e2f2a3b2cba543a01cb8aa5d77c04a", "7aba804ac76ce5d733956b4251194344", - "d575d036af8c4a813217192a15982021", + "38c990a8de107bb30600ef3288e1b391", "f912dea8d7d9813556f3ece0730f5392", - "45dd3fb5a146c0865a4ed06d4d230eb0" + "cf51b4aff9abf7a7eff417c62a3b126d" ] \ No newline at end of file diff --git a/packages/color-helpers/src/conversions.js b/packages/color-helpers/src/conversions.js new file mode 100644 index 000000000..45fde9b0e --- /dev/null +++ b/packages/color-helpers/src/conversions.js @@ -0,0 +1,504 @@ +// Sample code for color conversions +// Conversion can also be done using ICC profiles and a Color Management System +// For clarity, a library is used for matrix multiplication (multiply-matrices.js) + +// standard white points, defined by 4-figure CIE x,y chromaticities +const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; +const D65 = [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290]; + +// sRGB-related functions + +function lin_sRGB(RGB) { + // convert an array of sRGB values + // where in-gamut values are in the range [0 - 1] + // to linear light (un-companded) form. + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // for negative values, linear portion is extended on reflection of axis, + // then reflected power function is used. + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + + if (abs <= 0.04045) { + return val / 12.92; + } + + return sign * (Math.pow((abs + 0.055) / 1.055, 2.4)); + }); +} + +function gam_sRGB(RGB) { + // convert an array of linear-light sRGB values in the range 0.0-1.0 + // to gamma corrected form + // https://en.wikipedia.org/wiki/SRGB + // Extended transfer function: + // For negative values, linear portion extends on reflection + // of axis, then uses reflected pow below that + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + + if (abs > 0.0031308) { + return sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); + } + + return 12.92 * val; + }); +} + +function lin_sRGB_to_XYZ(rgb) { + // convert an array of linear-light sRGB values to CIE XYZ + // using sRGB's own white, D65 (no chromatic adaptation) + + var M = [ + [506752 / 1228815, 87881 / 245763, 12673 / 70218], + [87098 / 409605, 175762 / 245763, 12673 / 175545], + [7918 / 409605, 87881 / 737289, 1001167 / 1053270], + ]; + return multiplyMatrices(M, rgb); +} + +function XYZ_to_lin_sRGB(XYZ) { + // convert XYZ to linear-light sRGB + + var M = [ + [12831 / 3959, -329 / 214, -1974 / 3959], + [-851781 / 878810, 1648619 / 878810, 36519 / 878810], + [705 / 12673, -2585 / 12673, 705 / 667], + ]; + + return multiplyMatrices(M, XYZ); +} + +// display-p3-related functions + + +function lin_P3(RGB) { + // convert an array of display-p3 RGB values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + + return lin_sRGB(RGB); // same as sRGB +} + +function gam_P3(RGB) { + // convert an array of linear-light display-p3 RGB in the range 0.0-1.0 + // to gamma corrected form + + return gam_sRGB(RGB); // same as sRGB +} + +function lin_P3_to_XYZ(rgb) { + // convert an array of linear-light display-p3 values to CIE XYZ + // using D65 (no chromatic adaptation) + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + var M = [ + [608311 / 1250200, 189793 / 714400, 198249 / 1000160], + [35783 / 156275, 247089 / 357200, 198249 / 2500400], + [0 / 1, 32229 / 714400, 5220557 / 5000800], + ]; + + return multiplyMatrices(M, rgb); +} + +function XYZ_to_lin_P3(XYZ) { + // convert XYZ to linear-light P3 + var M = [ + [446124 / 178915, -333277 / 357830, -72051 / 178915], + [-14852 / 17905, 63121 / 35810, 423 / 17905], + [11844 / 330415, -50337 / 660830, 316169 / 330415], + ]; + + return multiplyMatrices(M, XYZ); +} + +// prophoto-rgb functions + +function lin_ProPhoto(RGB) { + // convert an array of prophoto-rgb values + // where in-gamut colors are in the range [0.0 - 1.0] + // to linear light (un-companded) form. + // Transfer curve is gamma 1.8 with a small linear portion + // Extended transfer function + const Et2 = 16 / 512; + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + + if (abs <= Et2) { + return val / 16; + } + + return sign * Math.pow(abs, 1.8); + }); +} + +function gam_ProPhoto(RGB) { + // convert an array of linear-light prophoto-rgb in the range 0.0-1.0 + // to gamma corrected form + // Transfer curve is gamma 1.8 with a small linear portion + // TODO for negative values, extend linear portion on reflection of axis, then add pow below that + const Et = 1 / 512; + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + + if (abs >= Et) { + return sign * Math.pow(abs, 1 / 1.8); + } + + return 16 * val; + }); +} + +function lin_ProPhoto_to_XYZ(rgb) { + // convert an array of linear-light prophoto-rgb values to CIE D50 XYZ + // matrix cannot be expressed in rational form, but is calculated to 64 bit accuracy + // see https://github.com/w3c/csswg-drafts/issues/7675 + var M = [ + [0.79776664490064230, 0.13518129740053308, 0.03134773412839220], + [0.28807482881940130, 0.71183523424187300, 0.00008993693872564], + [0.00000000000000000, 0.00000000000000000, 0.82510460251046020], + ]; + + return multiplyMatrices(M, rgb); +} + +function XYZ_to_lin_ProPhoto(XYZ) { + // convert D50 XYZ to linear-light prophoto-rgb + var M = [ + [1.34578688164715830, -0.25557208737979464, -0.05110186497554526], + [-0.54463070512490190, 1.50824774284514680, 0.02052744743642139], + [0.00000000000000000, 0.00000000000000000, 1.21196754563894520], + ]; + + return multiplyMatrices(M, XYZ); +} + +// a98-rgb functions + +function lin_a98rgb(RGB) { + // convert an array of a98-rgb values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // negative values are also now accepted + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + + return sign * Math.pow(abs, 563 / 256); + }); +} + +function gam_a98rgb(RGB) { + // convert an array of linear-light a98-rgb in the range 0.0-1.0 + // to gamma corrected form + // negative values are also now accepted + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + + return sign * Math.pow(abs, 256 / 563); + }); +} + +function lin_a98rgb_to_XYZ(rgb) { + // convert an array of linear-light a98-rgb values to CIE XYZ + // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html + // has greater numerical precision than section 4.3.5.3 of + // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf + // but the values below were calculated from first principles + // from the chromaticity coordinates of R G B W + // see matrixmaker.html + var M = [ + [573536 / 994567, 263643 / 1420810, 187206 / 994567], + [591459 / 1989134, 6239551 / 9945670, 374412 / 4972835], + [53769 / 1989134, 351524 / 4972835, 4929758 / 4972835], + ]; + + return multiplyMatrices(M, rgb); +} + +function XYZ_to_lin_a98rgb(XYZ) { + // convert XYZ to linear-light a98-rgb + var M = [ + [1829569 / 896150, -506331 / 896150, -308931 / 896150], + [-851781 / 878810, 1648619 / 878810, 36519 / 878810], + [16779 / 1248040, -147721 / 1248040, 1266979 / 1248040], + ]; + + return multiplyMatrices(M, XYZ); +} + +//Rec. 2020-related functions + +function lin_2020(RGB) { + // convert an array of rec2020 RGB values in the range 0.0 - 1.0 + // to linear light (un-companded) form. + // Reference electro-optical transfer function from Rec. ITU-R BT.1886 Annex 1 + // with b (black lift) = 0 and a (user gain) = 1 + // defined over the extended range, not clamped + + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + return sign * Math.pow(abs, 2.4); + }); +} + +function gam_2020(RGB) { + // convert an array of linear-light rec2020 RGB in the range 0.0-1.0 + // to gamma corrected form + // Reference electro-optical transfer function from Rec. ITU-R BT.1886 Annex 1 + // with b (black lift) = 0 and a (user gain) = 1 + // defined over the extended range, not clamped + + return RGB.map(function (val) { + let sign = val < 0 ? -1 : 1; + let abs = Math.abs(val); + return sign * Math.pow(abs, 1 / 2.4); + }); +} + +function lin_2020_to_XYZ(rgb) { + // convert an array of linear-light rec2020 values to CIE XYZ + // using D65 (no chromatic adaptation) + var M = [ + [63426534 / 99577255, 20160776 / 139408157, 47086771 / 278816314], + [26158966 / 99577255, 472592308 / 697040785, 8267143 / 139408157], + [0 / 1, 19567812 / 697040785, 295819943 / 278816314], + ]; + // 0 is actually calculated as 4.994106574466076e-17 + + return multiplyMatrices(M, rgb); +} + +function XYZ_to_lin_2020(XYZ) { + // convert XYZ to linear-light rec2020 + var M = [ + [30757411 / 17917100, -6372589 / 17917100, -4539589 / 17917100], + [-19765991 / 29648200, 47925759 / 29648200, 467509 / 29648200], + [792561 / 44930125, -1921689 / 44930125, 42328811 / 44930125], + ]; + + return multiplyMatrices(M, XYZ); +} + +// Chromatic adaptation + +function D65_to_D50(XYZ) { + // Bradford chromatic adaptation from D65 to D50 + // The matrix below is the result of three operations: + // - convert from XYZ to retinal cone domain + // - scale components from one reference white to another + // - convert back to XYZ + // see https://github.com/LeaVerou/color.js/pull/354/files + + var M = [ + [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], + [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], + [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371], + ]; + + return multiplyMatrices(M, XYZ); +} + +function D50_to_D65(XYZ) { + // Bradford chromatic adaptation from D50 to D65 + // See https://github.com/LeaVerou/color.js/pull/360/files + var M = [ + [0.955473421488075, -0.02309845494876471, 0.06325924320057072], + [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323], + [0.012314014864481998, -0.020507649298898964, 1.330365926242124], + ]; + + return multiplyMatrices(M, XYZ); +} + +// CIE Lab and LCH + +function XYZ_to_Lab(XYZ) { + // Assuming XYZ is relative to D50, convert to CIE Lab + // from CIE standard, which now defines these as a rational fraction + var ε = 216 / 24389; // 6^3/29^3 + var κ = 24389 / 27; // 29^3/3^3 + + // compute xyz, which is XYZ scaled relative to reference white + var xyz = XYZ.map((value, i) => value / D50[i]); + + // now compute f + var f = xyz.map(value => value > ε ? Math.cbrt(value) : (κ * value + 16) / 116); + + return [ + (116 * f[1]) - 16, // L + 500 * (f[0] - f[1]), // a + 200 * (f[1] - f[2]), // b + ]; + // L in range [0,100]. For use in CSS, add a percent +} + +function Lab_to_XYZ(Lab) { + // Convert Lab to D50-adapted XYZ + // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html + var κ = 24389 / 27; // 29^3/3^3 + var ε = 216 / 24389; // 6^3/29^3 + var f = []; + + // compute f, starting with the luminance-related term + f[1] = (Lab[0] + 16) / 116; + f[0] = Lab[1] / 500 + f[1]; + f[2] = f[1] - Lab[2] / 200; + + // compute xyz + var xyz = [ + Math.pow(f[0], 3) > ε ? Math.pow(f[0], 3) : (116 * f[0] - 16) / κ, + Lab[0] > κ * ε ? Math.pow((Lab[0] + 16) / 116, 3) : Lab[0] / κ, + Math.pow(f[2], 3) > ε ? Math.pow(f[2], 3) : (116 * f[2] - 16) / κ, + ]; + + // Compute XYZ by scaling xyz by reference white + return xyz.map((value, i) => value * D50[i]); +} + +function Lab_to_LCH(Lab) { + var epsilon = 0.0015; + var chroma = Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)); // Chroma + var hue = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI; + if (hue < 0) { + hue = hue + 360; + } + if (chroma <= epsilon) { + hue = NaN; + } + return [ + Lab[0], // L is still L + chroma, // Chroma + hue, // Hue, in degrees [0 to 360) + ]; +} + +function LCH_to_Lab(LCH) { + // Convert from polar form + return [ + LCH[0], // L is still L + LCH[1] * Math.cos(LCH[2] * Math.PI / 180), // a + LCH[1] * Math.sin(LCH[2] * Math.PI / 180), // b + ]; +} + +// OKLab and OKLCH +// https://bottosson.github.io/posts/oklab/ + +// XYZ <-> LMS matrices recalculated for consistent reference white +// see https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-943521484 +// recalculated for 64bit precision +// see https://github.com/color-js/color.js/pull/357 + +function XYZ_to_OKLab(XYZ) { + // Given XYZ relative to D65, convert to OKLab + var XYZtoLMS = [ + [0.8190224379967030, 0.3619062600528904, -0.1288737815209879], + [0.0329836539323885, 0.9292868615863434, 0.0361446663506424], + [0.0481771893596242, 0.2642395317527308, 0.6335478284694309], + ]; + var LMStoOKLab = [ + [0.2104542683093140, 0.7936177747023054, -0.0040720430116193], + [1.9779985324311684, -2.4285922420485799, 0.4505937096174110], + [0.0259040424655478, 0.7827717124575296, -0.8086757549230774], + ]; + + var LMS = multiplyMatrices(XYZtoLMS, XYZ); + // JavaScript Math.cbrt returns a sign-matched cube root + // beware if porting to other languages + // especially if tempted to use a general power function + return multiplyMatrices(LMStoOKLab, LMS.map(c => Math.cbrt(c))); + // L in range [0,1]. For use in CSS, multiply by 100 and add a percent +} + +function OKLab_to_XYZ(OKLab) { + // Given OKLab, convert to XYZ relative to D65 + var LMStoXYZ = [ + [1.2268798758459243, -0.5578149944602171, 0.2813910456659647], + [-0.0405757452148008, 1.1122868032803170, -0.0717110580655164], + [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816], + ]; + var OKLabtoLMS = [ + [1.0000000000000000, 0.3963377773761749, 0.2158037573099136], + [1.0000000000000000, -0.1055613458156586, -0.0638541728258133], + [1.0000000000000000, -0.0894841775298119, -1.2914855480194092], + ]; + + var LMSnl = multiplyMatrices(OKLabtoLMS, OKLab); + return multiplyMatrices(LMStoXYZ, LMSnl.map(c => c ** 3)); +} + +function OKLab_to_OKLCH(OKLab) { + var epsilon = 0.000004; + var hue = Math.atan2(OKLab[2], OKLab[1]) * 180 / Math.PI; + var chroma = Math.sqrt(OKLab[1] ** 2 + OKLab[2] ** 2); + if (hue < 0) { + hue = hue + 360; + } + if (chroma <= epsilon) { + hue = NaN; + } + return [ + OKLab[0], // L is still L + chroma, + hue, + ]; +} + +function OKLCH_to_OKLab(OKLCH) { + return [ + OKLCH[0], // L is still L + OKLCH[1] * Math.cos(OKLCH[2] * Math.PI / 180), // a + OKLCH[1] * Math.sin(OKLCH[2] * Math.PI / 180), // b + ]; +} + +// Premultiplied alpha conversions + +function rectangular_premultiply(color, alpha) { + // given a color in a rectangular orthogonal colorspace + // and an alpha value + // return the premultiplied form + return color.map((c) => c * alpha); +} + +function rectangular_un_premultiply(color, alpha) { + // given a premultiplied color in a rectangular orthogonal colorspace + // and an alpha value + // return the actual color + if (alpha === 0) { + return color; // avoid divide by zero + } + return color.map((c) => c / alpha); +} + +function polar_premultiply(color, alpha, hueIndex) { + // given a color in a cylindicalpolar colorspace + // and an alpha value + // return the premultiplied form. + // the index says which entry in the color array corresponds to hue angle + // for example, in OKLCH it would be 2 + // while in HSL it would be 0 + return color.map((c, i) => c * (hueIndex === i ? 1 : alpha)); +} + +function polar_un_premultiply(color, alpha, hueIndex) { + // given a color in a cylindicalpolar colorspace + // and an alpha value + // return the actual color. + // the hueIndex says which entry in the color array corresponds to hue angle + // for example, in OKLCH it would be 2 + // while in HSL it would be 0 + if (alpha === 0) { + return color; // avoid divide by zero + } + return color.map((c, i) => c / (hueIndex === i ? 1 : alpha)); +} + +// Convenience functions can easily be defined, such as +function hsl_premultiply(color, alpha) { + return polar_premultiply(color, alpha, 0); +} diff --git a/packages/color-helpers/src/conversions/gam-2020.ts b/packages/color-helpers/src/conversions/gam-2020.ts index c06d78747..743c5e0df 100644 --- a/packages/color-helpers/src/conversions/gam-2020.ts +++ b/packages/color-helpers/src/conversions/gam-2020.ts @@ -15,16 +15,9 @@ export function gam_2020(RGB: Color): Color { ] } -const α = 1.09929682680944; -const β = 0.018053968510807; - function gam_2020_channel(val: number): number { const sign = val < 0 ? -1 : 1; const abs = Math.abs(val); - if (abs > β) { - return sign * (α * Math.pow(abs, 0.45) - (α - 1)); - } - - return 4.5 * val; + return sign * Math.pow(abs, 1 / 2.4); } diff --git a/packages/color-helpers/src/conversions/lab-to-lch.ts b/packages/color-helpers/src/conversions/lab-to-lch.ts index a750ecfba..1cad9cdb8 100644 --- a/packages/color-helpers/src/conversions/lab-to-lch.ts +++ b/packages/color-helpers/src/conversions/lab-to-lch.ts @@ -5,11 +5,21 @@ import type { Color } from '../types/color'; * @copyright This software or document includes material copied from or derived from https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js. Copyright © 2022 W3C® (MIT, ERCIM, Keio, Beihang). */ export function Lab_to_LCH(Lab: Color): Color { - // Convert to polar form - const hue = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI; + const epsilon = 0.0015; + const chroma = Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)); // Chroma + + let hue = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI; + if (hue < 0) { + hue = hue + 360; + } + + if (chroma <= epsilon) { + hue = NaN; + } + return [ Lab[0], // L is still L - Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)), // Chroma - hue >= 0 ? hue : hue + 360, // Hue, in degrees [0 to 360) + chroma, // Chroma + hue // Hue, in degrees [0 to 360) ]; } diff --git a/packages/color-helpers/src/conversions/lin-2020.ts b/packages/color-helpers/src/conversions/lin-2020.ts index 853dfcb26..1dc960aaa 100644 --- a/packages/color-helpers/src/conversions/lin-2020.ts +++ b/packages/color-helpers/src/conversions/lin-2020.ts @@ -16,16 +16,9 @@ export function lin_2020(RGB: Color): Color { ] } -const α = 1.09929682680944; -const β = 0.018053968510807; - function lin_2020_channel(val: number): number { const sign = val < 0 ? -1 : 1; const abs = Math.abs(val); - if (abs < β * 4.5) { - return val / 4.5; - } - - return sign * (Math.pow((abs + α - 1) / α, 1 / 0.45)); + return sign * Math.pow(abs, 2.4); } diff --git a/packages/color-helpers/src/conversions/oklab-to-oklch.ts b/packages/color-helpers/src/conversions/oklab-to-oklch.ts index 536add344..36e4be681 100644 --- a/packages/color-helpers/src/conversions/oklab-to-oklch.ts +++ b/packages/color-helpers/src/conversions/oklab-to-oklch.ts @@ -6,10 +6,21 @@ import type { Color } from '../types/color'; * @see https://github.com/w3c/csswg-drafts/blob/main/css-color-4/conversions.js */ export function OKLab_to_OKLCH(OKLab: Color): Color { - const hue = Math.atan2(OKLab[2], OKLab[1]) * 180 / Math.PI; + const epsilon = 0.000004; + const chroma = Math.sqrt(OKLab[1] ** 2 + OKLab[2] ** 2); + + let hue = Math.atan2(OKLab[2], OKLab[1]) * 180 / Math.PI; + if (hue < 0) { + hue = hue + 360; + } + + if (chroma <= epsilon) { + hue = NaN; + } + return [ OKLab[0], // L is still L - Math.sqrt(OKLab[1] ** 2 + OKLab[2] ** 2), // Chroma - hue >= 0 ? hue : hue + 360, // Hue, in degrees [0 to 360) + chroma, + hue ]; } diff --git a/packages/color-helpers/src/conversions/srgb-to-hsl.ts b/packages/color-helpers/src/conversions/srgb-to-hsl.ts index 01da718e1..b42f76abc 100644 --- a/packages/color-helpers/src/conversions/srgb-to-hsl.ts +++ b/packages/color-helpers/src/conversions/srgb-to-hsl.ts @@ -63,6 +63,11 @@ export function sRGB_to_HSL(RGB: Color): Color { hue -= 360; } + const epsilon = 1 / 100_000; // max Sat is 1, in this code + if (sat <= epsilon) { + hue = NaN; + } + return [ hue, sat * 100, diff --git a/packages/color-helpers/src/conversions/xyz/index.ts b/packages/color-helpers/src/conversions/xyz/index.ts index 5ce2b3ee6..3c79170bf 100644 --- a/packages/color-helpers/src/conversions/xyz/index.ts +++ b/packages/color-helpers/src/conversions/xyz/index.ts @@ -135,8 +135,15 @@ export function XYZ_D50_to_HWB(x: Color): Color { const srgb = gam_sRGB(y); const white = Math.min(srgb[0], srgb[1], srgb[2]); - const black = 1 - Math.max(srgb[0], srgb[1], srgb[2]); - return [sRGB_to_Hue(srgb), white * 100, black * 100]; + const black = 1 - Math.max(srgb[0], srgb[1], srgb[2]) + + let hue = sRGB_to_Hue(srgb); + const epsilon = 1 / 100_000; // account for multiply by 100 + if (white + black >= 1 - epsilon) { + hue = NaN; + } + + return [hue, white * 100, black * 100]; } /** diff --git a/packages/css-color-parser/test/basic/color-function.mjs b/packages/css-color-parser/test/basic/color-function.mjs index f871d79db..2d34f1710 100644 --- a/packages/css-color-parser/test/basic/color-function.mjs +++ b/packages/css-color-parser/test/basic/color-function.mjs @@ -19,7 +19,7 @@ const tests = [ ['color(a98-rgb 0.565 1 0.234)', 'rgb(1, 255, 0)'], ['color(prophoto-rgb 0.5402796954751572 0.9275945561561767 0.30435477084804174)', 'rgb(1, 255, 0)'], ['color(display-p3 0.45844420720487417 0.9852652233445233 0.29798036139719497)', 'rgb(1, 255, 0)'], - ['color(rec2020 0.5675603321833232 0.9592792129938423 0.2686829491074993)', 'rgb(1, 255, 0)'], + ['color(rec2020 0.6296393406173314 0.9656623497977268 0.3632776716731966)', 'rgb(1, 255, 0)'], ['color(xyz-d50 0.3851458288094242 0.7168862873215868 0.09696013568183873)', 'rgb(1, 255, 0)'], ['color(xyz-d65 0.35757130434515494 0.7151655154354521 0.11903355225337156)', 'rgb(1, 255, 0)'], ['color(xyz 0.35757130434515494 0.7151655154354521 0.11903355225337156)', 'rgb(1, 255, 0)'], @@ -29,7 +29,7 @@ const tests = [ ['color(a98-rgb 0.265 0.5134 0.5344)', 'rgb(0, 131, 135)'], ['color(prophoto-rgb 0.28284813138491105 0.41695332740189284 0.4586239337463392)', 'rgb(0, 131, 135)'], ['color(display-p3 0.18049383596973329 0.5091259470889726 0.5339002129941044)', 'rgb(0, 131, 135)'], - ['color(rec2020 0.24657637908526203 0.44994210472189566 0.486194553499425)', 'rgb(0, 131, 135)'], + ['color(rec2020 0.3584374937329418 0.5234107856368522 0.548844528912945)', 'rgb(0, 131, 135)'], ['color(xyz-d50 0.11786343156307554 0.1771045882551784 0.2028294891298204)', 'rgb(0, 131, 135)'], ['color(xyz-d65 0.12135537506539545 0.1797988884168019 0.2676568254682071)', 'rgb(0, 131, 135)'], ['color(xyz 0.12135537506539545 0.1797988884168019 0.2676568254682071)', 'rgb(0, 131, 135)'], @@ -39,7 +39,7 @@ const tests = [ ['color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695)', 'rgb(229, 125, 245)'], ['color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222)', 'rgb(229, 125, 245)'], ['color(display-p3 0.843565234 0.509345345 0.9342344435)', 'rgb(229, 125, 245)'], - ['color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224)', 'rgb(229, 125, 245)'], + ['color(rec2020 0.8075367152908116 0.5641142016050468 0.9339114003184052)', 'rgb(229, 125, 245)'], ['color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199)', 'rgb(229, 125, 245)'], ['color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693)', 'rgb(229, 125, 245)'], ['color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693)', 'rgb(229, 125, 245)'], diff --git a/packages/css-color-parser/test/basic/color-mix-function.mjs b/packages/css-color-parser/test/basic/color-mix-function.mjs index 6403d1708..cfbf770e1 100644 --- a/packages/css-color-parser/test/basic/color-mix-function.mjs +++ b/packages/css-color-parser/test/basic/color-mix-function.mjs @@ -10,7 +10,7 @@ const tests = [ ['color-mix(in srgb, green 40%, color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695))', 'rgb(137, 126, 147)'], ['color-mix(in srgb, green 40%, color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222))', 'rgb(137, 126, 147)'], ['color-mix(in srgb, green 40%, color(display-p3 0.843565234 0.509345345 0.9342344435))', 'rgb(137, 126, 147)'], - ['color-mix(in srgb, green 40%, color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224))', 'rgb(137, 126, 147)'], + ['color-mix(in srgb, green 40%, color(rec2020 0.807076644727751 0.5627572708703388 0.9326528689276063))', 'rgb(137, 126, 147)'], ['color-mix(in srgb, green 40%, color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199))', 'rgb(137, 126, 147)'], ['color-mix(in srgb, green 40%, color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(137, 126, 147)'], ['color-mix(in srgb, green 40%, color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(137, 126, 147)'], @@ -22,7 +22,7 @@ const tests = [ ['color-mix(in srgb-linear, green 40%, color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695))', 'rgb(182, 126, 195)'], ['color-mix(in srgb-linear, green 40%, color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222))', 'rgb(182, 126, 195)'], ['color-mix(in srgb-linear, green 40%, color(display-p3 0.843565234 0.509345345 0.9342344435))', 'rgb(182, 126, 195)'], - ['color-mix(in srgb-linear, green 40%, color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224))', 'rgb(182, 126, 195)'], + ['color-mix(in srgb-linear, green 40%, color(rec2020 0.807076644727751 0.5627572708703388 0.9326528689276063))', 'rgb(182, 126, 195)'], ['color-mix(in srgb-linear, green 40%, color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199))', 'rgb(182, 126, 195)'], ['color-mix(in srgb-linear, green 40%, color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(182, 126, 195)'], ['color-mix(in srgb-linear, green 40%, color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(182, 126, 195)'], @@ -34,7 +34,7 @@ const tests = [ ['color-mix(in xyz-d50, green 40%, color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695))', 'rgb(182, 126, 195)'], ['color-mix(in xyz-d50, green 40%, color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222))', 'rgb(182, 126, 195)'], ['color-mix(in xyz-d50, green 40%, color(display-p3 0.843565234 0.509345345 0.9342344435))', 'rgb(182, 126, 195)'], - ['color-mix(in xyz-d50, green 40%, color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224))', 'rgb(182, 126, 195)'], + ['color-mix(in xyz-d50, green 40%, color(rec2020 0.807076644727751 0.5627572708703388 0.9326528689276063))', 'rgb(182, 126, 195)'], ['color-mix(in xyz-d50, green 40%, color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199))', 'rgb(182, 126, 195)'], ['color-mix(in xyz-d50, green 40%, color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(182, 126, 195)'], ['color-mix(in xyz-d50, green 40%, color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(182, 126, 195)'], @@ -46,7 +46,7 @@ const tests = [ ['color-mix(in hsl, green 40%, color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695))', 'rgb(28, 88, 245)'], ['color-mix(in hsl, green 40%, color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222))', 'rgb(28, 88, 245)'], ['color-mix(in hsl, green 40%, color(display-p3 0.843565234 0.509345345 0.9342344435))', 'rgb(28, 88, 245)'], - ['color-mix(in hsl, green 40%, color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224))', 'rgb(28, 88, 245)'], + ['color-mix(in hsl, green 40%, color(rec2020 0.807076644727751 0.5627572708703388 0.9326528689276063))', 'rgb(28, 88, 245)'], ['color-mix(in hsl, green 40%, color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199))', 'rgb(28, 88, 245)'], ['color-mix(in hsl, green 40%, color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(28, 88, 245)'], ['color-mix(in hsl, green 40%, color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(28, 88, 245)'], @@ -58,7 +58,7 @@ const tests = [ ['color-mix(in oklch, green 40%, color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695))', 'rgb(0, 148, 253)'], ['color-mix(in oklch, green 40%, color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222))', 'rgb(0, 148, 253)'], ['color-mix(in oklch, green 40%, color(display-p3 0.843565234 0.509345345 0.9342344435))', 'rgb(0, 148, 253)'], - ['color-mix(in oklch, green 40%, color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224))', 'rgb(0, 148, 253)'], + ['color-mix(in oklch, green 40%, color(rec2020 0.807076644727751 0.5627572708703388 0.9326528689276063))', 'rgb(0, 148, 253)'], ['color-mix(in oklch, green 40%, color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199))', 'rgb(0, 148, 253)'], ['color-mix(in oklch, green 40%, color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(0, 148, 253)'], ['color-mix(in oklch, green 40%, color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693))', 'rgb(0, 148, 253)'], diff --git a/plugins/postcss-color-function/test/basic.css b/plugins/postcss-color-function/test/basic.css index 65efd1e03..535ce48ca 100644 --- a/plugins/postcss-color-function/test/basic.css +++ b/plugins/postcss-color-function/test/basic.css @@ -193,7 +193,7 @@ color-3: color(a98-rgb 0.265 0.5134 0.5344); color-4: color(prophoto-rgb 0.28284813138491105 0.41695332740189284 0.4586239337463392); color-5: color(display-p3 0.18049383596973329 0.5091259470889726 0.5339002129941044); - color-6: color(rec2020 0.24657637908526203 0.44994210472189566 0.486194553499425); + color-6: color(rec2020 0.3584374937329418 0.5234107856368522 0.548844528912945); color-7: color(xyz-d50 0.11786343156307554 0.1771045882551784 0.2028294891298204); color-8: color(xyz-d65 0.12135537506539545 0.1797988884168019 0.2676568254682071); color-9: color(xyz 0.12135537506539545 0.1797988884168019 0.2676568254682071); @@ -205,7 +205,7 @@ color-3: color(a98-rgb 0.8035122804301492 0.484896415622613 0.9440692746539695); color-4: color(prophoto-rgb 0.7596595159204217 0.4934889951894072 0.8985832663171222); color-5: color(display-p3 0.843565234 0.509345345 0.9342344435); - color-6: color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224); + color-6: color(rec2020 0.8075367152908116 0.5641142016050468 0.9339114003184052); color-7: color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199); color-8: color(xyz-d65 0.5600582450343325 0.37782875858447507 0.904570025128693); color-9: color(xyz 0.5600582450343325 0.37782875858447507 0.904570025128693); diff --git a/plugins/postcss-color-function/test/basic.expect.css b/plugins/postcss-color-function/test/basic.expect.css index 70062d567..90a079d81 100644 --- a/plugins/postcss-color-function/test/basic.expect.css +++ b/plugins/postcss-color-function/test/basic.expect.css @@ -181,7 +181,7 @@ color-3: rgb(1, 255, 0); color-4: rgb(1, 255, 0); color-5: rgb(1, 255, 0); - color-6: rgb(1, 255, 0); + color-6: rgb(0, 248, 51); color-7: rgb(1, 255, 0); color-8: rgb(1, 255, 0); color-9: rgb(1, 255, 0); diff --git a/plugins/postcss-color-function/test/basic.preserve-true.expect.css b/plugins/postcss-color-function/test/basic.preserve-true.expect.css index df3fea1e9..e776c2a9c 100644 --- a/plugins/postcss-color-function/test/basic.preserve-true.expect.css +++ b/plugins/postcss-color-function/test/basic.preserve-true.expect.css @@ -261,7 +261,7 @@ color-4: color(prophoto-rgb 0.5402796954751572 0.9275945561561767 0.30435477084804174); color-5: rgb(1, 255, 0); color-5: color(display-p3 0.45844420720487417 0.9852652233445233 0.29798036139719497); - color-6: rgb(1, 255, 0); + color-6: rgb(0, 248, 51); color-6: color(rec2020 0.5675603321833232 0.9592792129938423 0.2686829491074993); color-7: rgb(1, 255, 0); color-7: color(xyz-d50 0.3851458288094242 0.7168862873215868 0.09696013568183873); @@ -283,7 +283,7 @@ color-5: rgb(0, 131, 135); color-5: color(display-p3 0.18049383596973329 0.5091259470889726 0.5339002129941044); color-6: rgb(0, 131, 135); - color-6: color(rec2020 0.24657637908526203 0.44994210472189566 0.486194553499425); + color-6: color(rec2020 0.3584374937329418 0.5234107856368522 0.548844528912945); color-7: rgb(0, 131, 135); color-7: color(xyz-d50 0.11786343156307554 0.1771045882551784 0.2028294891298204); color-8: rgb(0, 131, 135); @@ -304,7 +304,7 @@ color-5: rgb(229, 125, 245); color-5: color(display-p3 0.843565234 0.509345345 0.9342344435); color-6: rgb(229, 125, 245); - color-6: color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224); + color-6: color(rec2020 0.8075367152908116 0.5641142016050468 0.9339114003184052); color-7: rgb(229, 125, 245); color-7: color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199); color-8: rgb(229, 125, 245); diff --git a/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css b/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css index 563602fd1..cfd566f5e 100644 --- a/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css +++ b/plugins/postcss-color-function/test/basic.with-cloned-rules.expect.css @@ -261,7 +261,7 @@ color-4: color(prophoto-rgb 0.5402796954751572 0.9275945561561767 0.30435477084804174); color-5: rgb(1, 255, 0); color-5: color(display-p3 0.45844420720487417 0.9852652233445233 0.29798036139719497); - color-6: rgb(1, 255, 0); + color-6: rgb(0, 248, 51); color-6: color(rec2020 0.5675603321833232 0.9592792129938423 0.2686829491074993); color-7: rgb(1, 255, 0); color-7: color(xyz-d50 0.3851458288094242 0.7168862873215868 0.09696013568183873); @@ -283,7 +283,7 @@ color-5: rgb(0, 131, 135); color-5: color(display-p3 0.18049383596973329 0.5091259470889726 0.5339002129941044); color-6: rgb(0, 131, 135); - color-6: color(rec2020 0.24657637908526203 0.44994210472189566 0.486194553499425); + color-6: color(rec2020 0.3584374937329418 0.5234107856368522 0.548844528912945); color-7: rgb(0, 131, 135); color-7: color(xyz-d50 0.11786343156307554 0.1771045882551784 0.2028294891298204); color-8: rgb(0, 131, 135); @@ -304,7 +304,7 @@ color-5: rgb(229, 125, 245); color-5: color(display-p3 0.843565234 0.509345345 0.9342344435); color-6: rgb(229, 125, 245); - color-6: color(rec2020 0.7728366085950608 0.49153213847089583 0.9202627474826224); + color-6: color(rec2020 0.8075367152908116 0.5641142016050468 0.9339114003184052); color-7: rgb(229, 125, 245); color-7: color(xyz-d50 0.5501693084815327 0.37536346388820246 0.6806345611398199); color-8: rgb(229, 125, 245); From 86fbd5551fec079f13fc4b87274af7c78b281dd2 Mon Sep 17 00:00:00 2001 From: Romain Menke Date: Sat, 24 Jan 2026 19:07:58 +0100 Subject: [PATCH 2/2] cleanup --- packages/color-helpers/src/conversions.js | 504 ---------------------- 1 file changed, 504 deletions(-) delete mode 100644 packages/color-helpers/src/conversions.js diff --git a/packages/color-helpers/src/conversions.js b/packages/color-helpers/src/conversions.js deleted file mode 100644 index 45fde9b0e..000000000 --- a/packages/color-helpers/src/conversions.js +++ /dev/null @@ -1,504 +0,0 @@ -// Sample code for color conversions -// Conversion can also be done using ICC profiles and a Color Management System -// For clarity, a library is used for matrix multiplication (multiply-matrices.js) - -// standard white points, defined by 4-figure CIE x,y chromaticities -const D50 = [0.3457 / 0.3585, 1.00000, (1.0 - 0.3457 - 0.3585) / 0.3585]; -const D65 = [0.3127 / 0.3290, 1.00000, (1.0 - 0.3127 - 0.3290) / 0.3290]; - -// sRGB-related functions - -function lin_sRGB(RGB) { - // convert an array of sRGB values - // where in-gamut values are in the range [0 - 1] - // to linear light (un-companded) form. - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // for negative values, linear portion is extended on reflection of axis, - // then reflected power function is used. - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - - if (abs <= 0.04045) { - return val / 12.92; - } - - return sign * (Math.pow((abs + 0.055) / 1.055, 2.4)); - }); -} - -function gam_sRGB(RGB) { - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - - if (abs > 0.0031308) { - return sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055); - } - - return 12.92 * val; - }); -} - -function lin_sRGB_to_XYZ(rgb) { - // convert an array of linear-light sRGB values to CIE XYZ - // using sRGB's own white, D65 (no chromatic adaptation) - - var M = [ - [506752 / 1228815, 87881 / 245763, 12673 / 70218], - [87098 / 409605, 175762 / 245763, 12673 / 175545], - [7918 / 409605, 87881 / 737289, 1001167 / 1053270], - ]; - return multiplyMatrices(M, rgb); -} - -function XYZ_to_lin_sRGB(XYZ) { - // convert XYZ to linear-light sRGB - - var M = [ - [12831 / 3959, -329 / 214, -1974 / 3959], - [-851781 / 878810, 1648619 / 878810, 36519 / 878810], - [705 / 12673, -2585 / 12673, 705 / 667], - ]; - - return multiplyMatrices(M, XYZ); -} - -// display-p3-related functions - - -function lin_P3(RGB) { - // convert an array of display-p3 RGB values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - - return lin_sRGB(RGB); // same as sRGB -} - -function gam_P3(RGB) { - // convert an array of linear-light display-p3 RGB in the range 0.0-1.0 - // to gamma corrected form - - return gam_sRGB(RGB); // same as sRGB -} - -function lin_P3_to_XYZ(rgb) { - // convert an array of linear-light display-p3 values to CIE XYZ - // using D65 (no chromatic adaptation) - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - var M = [ - [608311 / 1250200, 189793 / 714400, 198249 / 1000160], - [35783 / 156275, 247089 / 357200, 198249 / 2500400], - [0 / 1, 32229 / 714400, 5220557 / 5000800], - ]; - - return multiplyMatrices(M, rgb); -} - -function XYZ_to_lin_P3(XYZ) { - // convert XYZ to linear-light P3 - var M = [ - [446124 / 178915, -333277 / 357830, -72051 / 178915], - [-14852 / 17905, 63121 / 35810, 423 / 17905], - [11844 / 330415, -50337 / 660830, 316169 / 330415], - ]; - - return multiplyMatrices(M, XYZ); -} - -// prophoto-rgb functions - -function lin_ProPhoto(RGB) { - // convert an array of prophoto-rgb values - // where in-gamut colors are in the range [0.0 - 1.0] - // to linear light (un-companded) form. - // Transfer curve is gamma 1.8 with a small linear portion - // Extended transfer function - const Et2 = 16 / 512; - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - - if (abs <= Et2) { - return val / 16; - } - - return sign * Math.pow(abs, 1.8); - }); -} - -function gam_ProPhoto(RGB) { - // convert an array of linear-light prophoto-rgb in the range 0.0-1.0 - // to gamma corrected form - // Transfer curve is gamma 1.8 with a small linear portion - // TODO for negative values, extend linear portion on reflection of axis, then add pow below that - const Et = 1 / 512; - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - - if (abs >= Et) { - return sign * Math.pow(abs, 1 / 1.8); - } - - return 16 * val; - }); -} - -function lin_ProPhoto_to_XYZ(rgb) { - // convert an array of linear-light prophoto-rgb values to CIE D50 XYZ - // matrix cannot be expressed in rational form, but is calculated to 64 bit accuracy - // see https://github.com/w3c/csswg-drafts/issues/7675 - var M = [ - [0.79776664490064230, 0.13518129740053308, 0.03134773412839220], - [0.28807482881940130, 0.71183523424187300, 0.00008993693872564], - [0.00000000000000000, 0.00000000000000000, 0.82510460251046020], - ]; - - return multiplyMatrices(M, rgb); -} - -function XYZ_to_lin_ProPhoto(XYZ) { - // convert D50 XYZ to linear-light prophoto-rgb - var M = [ - [1.34578688164715830, -0.25557208737979464, -0.05110186497554526], - [-0.54463070512490190, 1.50824774284514680, 0.02052744743642139], - [0.00000000000000000, 0.00000000000000000, 1.21196754563894520], - ]; - - return multiplyMatrices(M, XYZ); -} - -// a98-rgb functions - -function lin_a98rgb(RGB) { - // convert an array of a98-rgb values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // negative values are also now accepted - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - - return sign * Math.pow(abs, 563 / 256); - }); -} - -function gam_a98rgb(RGB) { - // convert an array of linear-light a98-rgb in the range 0.0-1.0 - // to gamma corrected form - // negative values are also now accepted - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - - return sign * Math.pow(abs, 256 / 563); - }); -} - -function lin_a98rgb_to_XYZ(rgb) { - // convert an array of linear-light a98-rgb values to CIE XYZ - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - // has greater numerical precision than section 4.3.5.3 of - // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf - // but the values below were calculated from first principles - // from the chromaticity coordinates of R G B W - // see matrixmaker.html - var M = [ - [573536 / 994567, 263643 / 1420810, 187206 / 994567], - [591459 / 1989134, 6239551 / 9945670, 374412 / 4972835], - [53769 / 1989134, 351524 / 4972835, 4929758 / 4972835], - ]; - - return multiplyMatrices(M, rgb); -} - -function XYZ_to_lin_a98rgb(XYZ) { - // convert XYZ to linear-light a98-rgb - var M = [ - [1829569 / 896150, -506331 / 896150, -308931 / 896150], - [-851781 / 878810, 1648619 / 878810, 36519 / 878810], - [16779 / 1248040, -147721 / 1248040, 1266979 / 1248040], - ]; - - return multiplyMatrices(M, XYZ); -} - -//Rec. 2020-related functions - -function lin_2020(RGB) { - // convert an array of rec2020 RGB values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // Reference electro-optical transfer function from Rec. ITU-R BT.1886 Annex 1 - // with b (black lift) = 0 and a (user gain) = 1 - // defined over the extended range, not clamped - - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - return sign * Math.pow(abs, 2.4); - }); -} - -function gam_2020(RGB) { - // convert an array of linear-light rec2020 RGB in the range 0.0-1.0 - // to gamma corrected form - // Reference electro-optical transfer function from Rec. ITU-R BT.1886 Annex 1 - // with b (black lift) = 0 and a (user gain) = 1 - // defined over the extended range, not clamped - - return RGB.map(function (val) { - let sign = val < 0 ? -1 : 1; - let abs = Math.abs(val); - return sign * Math.pow(abs, 1 / 2.4); - }); -} - -function lin_2020_to_XYZ(rgb) { - // convert an array of linear-light rec2020 values to CIE XYZ - // using D65 (no chromatic adaptation) - var M = [ - [63426534 / 99577255, 20160776 / 139408157, 47086771 / 278816314], - [26158966 / 99577255, 472592308 / 697040785, 8267143 / 139408157], - [0 / 1, 19567812 / 697040785, 295819943 / 278816314], - ]; - // 0 is actually calculated as 4.994106574466076e-17 - - return multiplyMatrices(M, rgb); -} - -function XYZ_to_lin_2020(XYZ) { - // convert XYZ to linear-light rec2020 - var M = [ - [30757411 / 17917100, -6372589 / 17917100, -4539589 / 17917100], - [-19765991 / 29648200, 47925759 / 29648200, 467509 / 29648200], - [792561 / 44930125, -1921689 / 44930125, 42328811 / 44930125], - ]; - - return multiplyMatrices(M, XYZ); -} - -// Chromatic adaptation - -function D65_to_D50(XYZ) { - // Bradford chromatic adaptation from D65 to D50 - // The matrix below is the result of three operations: - // - convert from XYZ to retinal cone domain - // - scale components from one reference white to another - // - convert back to XYZ - // see https://github.com/LeaVerou/color.js/pull/354/files - - var M = [ - [1.0479297925449969, 0.022946870601609652, -0.05019226628920524], - [0.02962780877005599, 0.9904344267538799, -0.017073799063418826], - [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371], - ]; - - return multiplyMatrices(M, XYZ); -} - -function D50_to_D65(XYZ) { - // Bradford chromatic adaptation from D50 to D65 - // See https://github.com/LeaVerou/color.js/pull/360/files - var M = [ - [0.955473421488075, -0.02309845494876471, 0.06325924320057072], - [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323], - [0.012314014864481998, -0.020507649298898964, 1.330365926242124], - ]; - - return multiplyMatrices(M, XYZ); -} - -// CIE Lab and LCH - -function XYZ_to_Lab(XYZ) { - // Assuming XYZ is relative to D50, convert to CIE Lab - // from CIE standard, which now defines these as a rational fraction - var ε = 216 / 24389; // 6^3/29^3 - var κ = 24389 / 27; // 29^3/3^3 - - // compute xyz, which is XYZ scaled relative to reference white - var xyz = XYZ.map((value, i) => value / D50[i]); - - // now compute f - var f = xyz.map(value => value > ε ? Math.cbrt(value) : (κ * value + 16) / 116); - - return [ - (116 * f[1]) - 16, // L - 500 * (f[0] - f[1]), // a - 200 * (f[1] - f[2]), // b - ]; - // L in range [0,100]. For use in CSS, add a percent -} - -function Lab_to_XYZ(Lab) { - // Convert Lab to D50-adapted XYZ - // http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html - var κ = 24389 / 27; // 29^3/3^3 - var ε = 216 / 24389; // 6^3/29^3 - var f = []; - - // compute f, starting with the luminance-related term - f[1] = (Lab[0] + 16) / 116; - f[0] = Lab[1] / 500 + f[1]; - f[2] = f[1] - Lab[2] / 200; - - // compute xyz - var xyz = [ - Math.pow(f[0], 3) > ε ? Math.pow(f[0], 3) : (116 * f[0] - 16) / κ, - Lab[0] > κ * ε ? Math.pow((Lab[0] + 16) / 116, 3) : Lab[0] / κ, - Math.pow(f[2], 3) > ε ? Math.pow(f[2], 3) : (116 * f[2] - 16) / κ, - ]; - - // Compute XYZ by scaling xyz by reference white - return xyz.map((value, i) => value * D50[i]); -} - -function Lab_to_LCH(Lab) { - var epsilon = 0.0015; - var chroma = Math.sqrt(Math.pow(Lab[1], 2) + Math.pow(Lab[2], 2)); // Chroma - var hue = Math.atan2(Lab[2], Lab[1]) * 180 / Math.PI; - if (hue < 0) { - hue = hue + 360; - } - if (chroma <= epsilon) { - hue = NaN; - } - return [ - Lab[0], // L is still L - chroma, // Chroma - hue, // Hue, in degrees [0 to 360) - ]; -} - -function LCH_to_Lab(LCH) { - // Convert from polar form - return [ - LCH[0], // L is still L - LCH[1] * Math.cos(LCH[2] * Math.PI / 180), // a - LCH[1] * Math.sin(LCH[2] * Math.PI / 180), // b - ]; -} - -// OKLab and OKLCH -// https://bottosson.github.io/posts/oklab/ - -// XYZ <-> LMS matrices recalculated for consistent reference white -// see https://github.com/w3c/csswg-drafts/issues/6642#issuecomment-943521484 -// recalculated for 64bit precision -// see https://github.com/color-js/color.js/pull/357 - -function XYZ_to_OKLab(XYZ) { - // Given XYZ relative to D65, convert to OKLab - var XYZtoLMS = [ - [0.8190224379967030, 0.3619062600528904, -0.1288737815209879], - [0.0329836539323885, 0.9292868615863434, 0.0361446663506424], - [0.0481771893596242, 0.2642395317527308, 0.6335478284694309], - ]; - var LMStoOKLab = [ - [0.2104542683093140, 0.7936177747023054, -0.0040720430116193], - [1.9779985324311684, -2.4285922420485799, 0.4505937096174110], - [0.0259040424655478, 0.7827717124575296, -0.8086757549230774], - ]; - - var LMS = multiplyMatrices(XYZtoLMS, XYZ); - // JavaScript Math.cbrt returns a sign-matched cube root - // beware if porting to other languages - // especially if tempted to use a general power function - return multiplyMatrices(LMStoOKLab, LMS.map(c => Math.cbrt(c))); - // L in range [0,1]. For use in CSS, multiply by 100 and add a percent -} - -function OKLab_to_XYZ(OKLab) { - // Given OKLab, convert to XYZ relative to D65 - var LMStoXYZ = [ - [1.2268798758459243, -0.5578149944602171, 0.2813910456659647], - [-0.0405757452148008, 1.1122868032803170, -0.0717110580655164], - [-0.0763729366746601, -0.4214933324022432, 1.5869240198367816], - ]; - var OKLabtoLMS = [ - [1.0000000000000000, 0.3963377773761749, 0.2158037573099136], - [1.0000000000000000, -0.1055613458156586, -0.0638541728258133], - [1.0000000000000000, -0.0894841775298119, -1.2914855480194092], - ]; - - var LMSnl = multiplyMatrices(OKLabtoLMS, OKLab); - return multiplyMatrices(LMStoXYZ, LMSnl.map(c => c ** 3)); -} - -function OKLab_to_OKLCH(OKLab) { - var epsilon = 0.000004; - var hue = Math.atan2(OKLab[2], OKLab[1]) * 180 / Math.PI; - var chroma = Math.sqrt(OKLab[1] ** 2 + OKLab[2] ** 2); - if (hue < 0) { - hue = hue + 360; - } - if (chroma <= epsilon) { - hue = NaN; - } - return [ - OKLab[0], // L is still L - chroma, - hue, - ]; -} - -function OKLCH_to_OKLab(OKLCH) { - return [ - OKLCH[0], // L is still L - OKLCH[1] * Math.cos(OKLCH[2] * Math.PI / 180), // a - OKLCH[1] * Math.sin(OKLCH[2] * Math.PI / 180), // b - ]; -} - -// Premultiplied alpha conversions - -function rectangular_premultiply(color, alpha) { - // given a color in a rectangular orthogonal colorspace - // and an alpha value - // return the premultiplied form - return color.map((c) => c * alpha); -} - -function rectangular_un_premultiply(color, alpha) { - // given a premultiplied color in a rectangular orthogonal colorspace - // and an alpha value - // return the actual color - if (alpha === 0) { - return color; // avoid divide by zero - } - return color.map((c) => c / alpha); -} - -function polar_premultiply(color, alpha, hueIndex) { - // given a color in a cylindicalpolar colorspace - // and an alpha value - // return the premultiplied form. - // the index says which entry in the color array corresponds to hue angle - // for example, in OKLCH it would be 2 - // while in HSL it would be 0 - return color.map((c, i) => c * (hueIndex === i ? 1 : alpha)); -} - -function polar_un_premultiply(color, alpha, hueIndex) { - // given a color in a cylindicalpolar colorspace - // and an alpha value - // return the actual color. - // the hueIndex says which entry in the color array corresponds to hue angle - // for example, in OKLCH it would be 2 - // while in HSL it would be 0 - if (alpha === 0) { - return color; // avoid divide by zero - } - return color.map((c, i) => c / (hueIndex === i ? 1 : alpha)); -} - -// Convenience functions can easily be defined, such as -function hsl_premultiply(color, alpha) { - return polar_premultiply(color, alpha, 0); -}