@@ -132,9 +132,34 @@ export const colorFunctions = [
132132 insertText : 'color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})' ,
133133 desc : l10n . t ( 'Mix two colors together in a polar color space.' )
134134 } ,
135+ {
136+ label : 'lab' ,
137+ func : 'lab($lightness $channel_a $channel_b $alpha)' ,
138+ insertText : 'lab(${1:lightness} ${2:a} ${3:b} ${4:alpha})' ,
139+ desc : l10n . t ( 'css.builtin.lab' , 'Creates a Color from Lightness, Channel a, Channel b and alpha values.' )
140+ } ,
141+ {
142+ label : 'lab relative' ,
143+ func : 'lab(from $color $lightness $channel_a $channel_b $alpha)' ,
144+ insertText : 'lab(from ${1:color} ${2:lightness} ${3:channel_a} ${4:channel_b} ${5:alpha})' ,
145+ desc : l10n . t ( 'css.builtin.lab' , 'Creates a Color from Lightness, Channel a, Channel b and alpha values of another Color.' )
146+ } ,
147+ {
148+ label : 'lch' ,
149+ func : 'lch($lightness $chrome $hue $alpha)' ,
150+ insertText : 'lch(${1:lightness} ${2:chrome} ${3:hue} ${4:alpha})' ,
151+ desc : l10n . t ( 'css.builtin.lab' , 'Creates a Color from Lightness, Chroma, Hue and alpha values.' )
152+ } ,
153+ {
154+ label : 'lch relative' ,
155+ func : 'lch(from $color $lightness $chrome $hue $alpha)' ,
156+ insertText : 'lch(from ${1:color} ${2:lightness} ${3:chrome} ${4:hue} ${5:alpha})' ,
157+ desc : l10n . t ( 'css.builtin.lab' , 'Creates a Color from Lightness, Chroma, Hue and alpha values of another Color.' )
158+ }
159+
135160] ;
136161
137- const colorFunctionNameRegExp = / ^ ( r g b | r g b a | h s l | h s l a | h w b ) $ / i;
162+ const colorFunctionNameRegExp = / ^ ( r g b | r g b a | h s l | h s l a | h w b | l a b | l c h ) $ / i;
138163
139164export const colors : { [ name : string ] : string } = {
140165 aliceblue : '#f0f8ff' ,
@@ -296,15 +321,15 @@ export const colorKeywords: { [name: string]: string } = {
296321
297322const colorKeywordsRegExp = new RegExp ( `^(${ Object . keys ( colorKeywords ) . join ( '|' ) } )$` , "i" ) ;
298323
299- function getNumericValue ( node : nodes . Node , factor : number ) {
324+ function getNumericValue ( node : nodes . Node , factor : number , lowerLimit : number = 0 , upperLimit : number = 1 ) {
300325 const val = node . getText ( ) ;
301326 const m = val . match ( / ^ ( [ - + ] ? [ 0 - 9 ] * \. ? [ 0 - 9 ] + ) ( % ? ) $ / ) ;
302327 if ( m ) {
303328 if ( m [ 2 ] ) {
304329 factor = 100.0 ;
305330 }
306331 const result = parseFloat ( m [ 1 ] ) / factor ;
307- if ( result >= 0 && result <= 1 ) {
332+ if ( result >= lowerLimit && result <= upperLimit ) {
308333 return result ;
309334 }
310335 }
@@ -533,6 +558,186 @@ export function hwbFromColor(rgba: Color): HWBA {
533558 } ;
534559}
535560
561+ export interface XYZ { x : number ; y : number ; z : number ; alpha : number ; }
562+
563+ export interface RGB { r : number ; g : number ; b : number ; alpha : number ; }
564+
565+ export function xyzFromLAB ( lab : LAB ) : XYZ {
566+ const xyz : XYZ = {
567+ x : 0 ,
568+ y : 0 ,
569+ z : 0 ,
570+ alpha : lab . alpha ?? 1
571+ } ;
572+ xyz . y = ( lab . l + 16.0 ) / 116.0 ;
573+ xyz . x = ( lab . a / 500.0 ) + xyz . y ;
574+ xyz . z = xyz . y - ( lab . b / 200.0 ) ;
575+ let key : keyof XYZ ;
576+
577+ for ( key in xyz ) {
578+ let pow = xyz [ key ] * xyz [ key ] * xyz [ key ] ;
579+ if ( pow > 0.008856 ) {
580+ xyz [ key ] = pow ;
581+ } else {
582+ xyz [ key ] = ( xyz [ key ] - 16.0 / 116.0 ) / 7.787 ;
583+ }
584+ }
585+
586+ xyz . x = xyz . x * 95.047 ;
587+ xyz . y = xyz . y * 100.0 ;
588+ xyz . z = xyz . z * 108.883 ;
589+ return xyz ;
590+ }
591+
592+ export function xyzToRGB ( xyz : XYZ ) : Color {
593+ const x = xyz . x / 100 ;
594+ const y = xyz . y / 100 ;
595+ const z = xyz . z / 100 ;
596+
597+ const r = 3.2406254773200533 * x - 1.5372079722103187 * y - 0.4986285986982479 * z ;
598+ const g = - 0.9689307147293197 * x + 1.8757560608852415 * y + 0.041517523842953964 * z ;
599+ const b = 0.055710120445510616 * x + - 0.2040210505984867 * y + 1.0569959422543882 * z ;
600+
601+ const compand = ( c : number ) => {
602+ return c <= 0.0031308 ?
603+ 12.92 * c :
604+ Math . min ( 1.055 * Math . pow ( c , 1 / 2.4 ) - 0.055 , 1 ) ;
605+ }
606+
607+ return {
608+ red : Math . round ( compand ( r ) * 255.0 ) ,
609+ blue : Math . round ( compand ( b ) * 255.0 ) ,
610+ green : Math . round ( compand ( g ) * 255.0 ) ,
611+ alpha : xyz . alpha
612+ } ;
613+ }
614+
615+ export function RGBtoXYZ ( rgba : Color ) : XYZ {
616+ let r : number = rgba . red ,
617+ g : number = rgba . green ,
618+ b : number = rgba . blue ;
619+
620+ if ( r > 0.04045 ) {
621+ r = Math . pow ( ( r + 0.055 ) / 1.055 , 2.4 ) ;
622+ } else {
623+ r = r / 12.92 ;
624+ }
625+ if ( g > 0.04045 ) {
626+ g = Math . pow ( ( g + 0.055 ) / 1.055 , 2.4 ) ;
627+ } else {
628+ g = g / 12.92 ;
629+ }
630+ if ( b > 0.04045 ) {
631+ b = Math . pow ( ( b + 0.055 ) / 1.055 , 2.4 ) ;
632+ } else {
633+ b = b / 12.92 ;
634+ }
635+ r = r * 100 ;
636+ g = g * 100 ;
637+ b = b * 100 ;
638+
639+ //Observer = 2°, Illuminant = D65
640+ const x = r * 0.4124 + g * 0.3576 + b * 0.1805 ;
641+ const y = r * 0.2126 + g * 0.7152 + b * 0.0722 ;
642+ const z = r * 0.0193 + g * 0.1192 + b * 0.9505 ;
643+ return { x, y, z, alpha : rgba . alpha } ;
644+ }
645+
646+ export function XYZtoLAB ( xyz : XYZ , round : Boolean = true ) : LAB {
647+ const ref_X = 95.047 , ref_Y = 100.000 , ref_Z = 108.883 ;
648+
649+ let x : number = xyz . x / ref_X ,
650+ y : number = xyz . y / ref_Y ,
651+ z : number = xyz . z / ref_Z ;
652+
653+ if ( x > 0.008856 ) {
654+ x = Math . pow ( x , 1 / 3 ) ;
655+ } else {
656+ x = ( 7.787 * x ) + ( 16 / 116 ) ;
657+ }
658+ if ( y > 0.008856 ) {
659+ y = Math . pow ( y , 1 / 3 ) ;
660+ } else {
661+ y = ( 7.787 * y ) + ( 16 / 116 ) ;
662+ }
663+ if ( z > 0.008856 ) {
664+ z = Math . pow ( z , 1 / 3 ) ;
665+ } else {
666+ z = ( 7.787 * z ) + ( 16 / 116 ) ;
667+ }
668+ const l : number = ( 116 * y ) - 16 ,
669+ a : number = 500 * ( x - y ) ,
670+ b : number = 200 * ( y - z ) ;
671+ if ( round ) {
672+ return {
673+ l : Math . round ( ( l + Number . EPSILON ) * 100 ) / 100 ,
674+ a : Math . round ( ( a + Number . EPSILON ) * 100 ) / 100 ,
675+ b : Math . round ( ( b + Number . EPSILON ) * 100 ) / 100 ,
676+ alpha : xyz . alpha
677+ } ;
678+ } else {
679+ return {
680+ l, a, b,
681+ alpha : xyz . alpha
682+ } ;
683+ }
684+ }
685+
686+ export function labFromColor ( rgba : Color , round : Boolean = true ) : LAB {
687+ const xyz : XYZ = RGBtoXYZ ( rgba ) ;
688+ const lab : LAB = XYZtoLAB ( xyz , round ) ;
689+ return lab ;
690+ }
691+ export function lchFromColor ( rgba : Color ) : LCH {
692+ const lab : LAB = labFromColor ( rgba , false ) ;
693+ const c : number = Math . sqrt ( Math . pow ( lab . a , 2 ) + Math . pow ( lab . b , 2 ) ) ;
694+ let h : number = Math . atan2 ( lab . b , lab . a ) * ( 180 / Math . PI ) ;
695+ while ( h < 0 ) {
696+ h = h + 360 ;
697+ }
698+ return {
699+ l : Math . round ( ( lab . l + Number . EPSILON ) * 100 ) / 100 ,
700+ c : Math . round ( ( c + Number . EPSILON ) * 100 ) / 100 ,
701+ h : Math . round ( ( h + Number . EPSILON ) * 100 ) / 100 ,
702+ alpha : lab . alpha
703+ } ;
704+ }
705+
706+ export function colorFromLAB ( l : number , a : number , b : number , alpha : number = 1.0 ) : Color {
707+ const lab : LAB = {
708+ l,
709+ a,
710+ b,
711+ alpha
712+ } ;
713+ const xyz = xyzFromLAB ( lab ) ;
714+ const rgb = xyzToRGB ( xyz ) ;
715+ return {
716+ red : ( rgb . red >= 0 ? ( rgb . red <= 255 ? rgb . red : 255 ) : 0 ) / 255.0 ,
717+ green : ( rgb . green >= 0 ? ( rgb . green <= 255 ? rgb . green : 255 ) : 0 ) / 255.0 ,
718+ blue : ( rgb . blue >= 0 ? ( rgb . blue <= 255 ? rgb . blue : 255 ) : 0 ) / 255.0 ,
719+ alpha
720+ } ;
721+ }
722+
723+ export interface LAB { l : number ; a : number ; b : number ; alpha ?: number ; }
724+
725+ export function labFromLCH ( l : number , c : number , h : number , alpha : number = 1.0 ) : LAB {
726+ return {
727+ l : l ,
728+ a : c * Math . cos ( h * ( Math . PI / 180 ) ) ,
729+ b : c * Math . sin ( h * ( Math . PI / 180 ) ) ,
730+ alpha : alpha
731+ } ;
732+ }
733+
734+ export function colorFromLCH ( l : number , c : number , h : number , alpha : number = 1.0 ) : Color {
735+ const lab : LAB = labFromLCH ( l , c , h , alpha ) ;
736+ return colorFromLAB ( lab . l , lab . a , lab . b , alpha ) ;
737+ }
738+
739+ export interface LCH { l : number ; c : number ; h : number ; alpha ?: number ; }
740+
536741export function getColorValue ( node : nodes . Node ) : Color | null {
537742 if ( node . type === nodes . NodeType . HexColorValue ) {
538743 const text = node . getText ( ) ;
@@ -578,6 +783,18 @@ export function getColorValue(node: nodes.Node): Color | null {
578783 const w = getNumericValue ( colorValues [ 1 ] , 100.0 ) ;
579784 const b = getNumericValue ( colorValues [ 2 ] , 100.0 ) ;
580785 return colorFromHWB ( h , w , b , alpha ) ;
786+ } else if ( name === 'lab' ) {
787+ // Reference: https://mina86.com/2021/srgb-lab-lchab-conversions/
788+ const l = getNumericValue ( colorValues [ 0 ] , 100.0 ) ;
789+ // Since these two values can be negative, a lower limit of -1 has been added
790+ const a = getNumericValue ( colorValues [ 1 ] , 125.0 , - 1 ) ;
791+ const b = getNumericValue ( colorValues [ 2 ] , 125.0 , - 1 ) ;
792+ return colorFromLAB ( l * 100 , a * 125 , b * 125 , alpha ) ;
793+ } else if ( name === 'lch' ) {
794+ const l = getNumericValue ( colorValues [ 0 ] , 100.0 ) ;
795+ const c = getNumericValue ( colorValues [ 1 ] , 230.0 ) ;
796+ const h = getAngle ( colorValues [ 2 ] ) ;
797+ return colorFromLCH ( l * 100 , c * 230 , h , alpha ) ;
581798 }
582799 } catch ( e ) {
583800 // parse error on numeric value
0 commit comments