@@ -28,7 +28,7 @@ impl Palette {
2828 text : Color :: BLACK ,
2929 primary : color ! ( 0x5865F2 ) ,
3030 success : color ! ( 0x12664f ) ,
31- warning : color ! ( 0xffc14e ) ,
31+ warning : color ! ( 0xb77e33 ) ,
3232 danger : color ! ( 0xc3423f ) ,
3333 } ;
3434
@@ -453,9 +453,15 @@ pub struct Background {
453453 /// The weakest version of the base background color.
454454 pub weakest : Pair ,
455455 /// A weaker version of the base background color.
456+ pub weaker : Pair ,
457+ /// A weak version of the base background color.
456458 pub weak : Pair ,
457- /// A stronger version of the base background color.
459+ /// A neutral version of the base background color, between weak and strong.
460+ pub neutral : Pair ,
461+ /// A strong version of the base background color.
458462 pub strong : Pair ,
463+ /// A stronger version of the base background color.
464+ pub stronger : Pair ,
459465 /// The strongest version of the base background color.
460466 pub strongest : Pair ,
461467}
@@ -464,15 +470,21 @@ impl Background {
464470 /// Generates a set of [`Background`] colors from the base and text colors.
465471 pub fn new ( base : Color , text : Color ) -> Self {
466472 let weakest = deviate ( base, 0.03 ) ;
467- let weak = muted ( deviate ( base, 0.1 ) ) ;
468- let strong = muted ( deviate ( base, 0.2 ) ) ;
469- let strongest = muted ( deviate ( base, 0.3 ) ) ;
473+ let weaker = deviate ( base, 0.07 ) ;
474+ let weak = deviate ( base, 0.1 ) ;
475+ let neutral = deviate ( base, 0.125 ) ;
476+ let strong = deviate ( base, 0.15 ) ;
477+ let stronger = deviate ( base, 0.175 ) ;
478+ let strongest = deviate ( base, 0.20 ) ;
470479
471480 Self {
472481 base : Pair :: new ( base, text) ,
473482 weakest : Pair :: new ( weakest, text) ,
483+ weaker : Pair :: new ( weaker, text) ,
474484 weak : Pair :: new ( weak, text) ,
485+ neutral : Pair :: new ( neutral, text) ,
475486 strong : Pair :: new ( strong, text) ,
487+ stronger : Pair :: new ( stronger, text) ,
476488 strongest : Pair :: new ( strongest, text) ,
477489 }
478490 }
@@ -517,9 +529,11 @@ pub struct Secondary {
517529impl Secondary {
518530 /// Generates a set of [`Secondary`] colors from the base and text colors.
519531 pub fn generate ( base : Color , text : Color ) -> Self {
520- let base = mix ( base, text, 0.2 ) ;
521- let weak = mix ( base, text, 0.1 ) ;
522- let strong = mix ( base, text, 0.3 ) ;
532+ let factor = if is_dark ( base) { 0.2 } else { 0.4 } ;
533+
534+ let weak = mix ( deviate ( base, 0.1 ) , text, factor) ;
535+ let strong = mix ( deviate ( base, 0.3 ) , text, factor) ;
536+ let base = mix ( deviate ( base, 0.2 ) , text, factor) ;
523537
524538 Self {
525539 base : Pair :: new ( base, text) ,
@@ -604,53 +618,55 @@ impl Danger {
604618 }
605619}
606620
607- struct Hsl {
608- h : f32 ,
609- s : f32 ,
621+ struct Oklch {
610622 l : f32 ,
623+ c : f32 ,
624+ h : f32 ,
611625 a : f32 ,
612626}
613627
614628fn darken ( color : Color , amount : f32 ) -> Color {
615- let mut hsl = to_hsl ( color) ;
629+ let mut oklch = to_oklch ( color) ;
630+
631+ // We try to bump the chroma a bit for more colorful palettes
632+ if oklch. c > 0.0 && oklch. c < ( 1.0 - oklch. l ) / 2.0 {
633+ // Formula empirically and cluelessly derived
634+ oklch. c *= 1.0 + ( 0.2 / oklch. c ) . min ( 100.0 ) * amount;
635+ }
616636
617- hsl . l = if hsl . l - amount < 0.0 {
637+ oklch . l = if oklch . l - amount < 0.0 {
618638 0.0
619639 } else {
620- hsl . l - amount
640+ oklch . l - amount
621641 } ;
622642
623- from_hsl ( hsl )
643+ from_oklch ( oklch )
624644}
625645
626646fn lighten ( color : Color , amount : f32 ) -> Color {
627- let mut hsl = to_hsl ( color) ;
647+ let mut oklch = to_oklch ( color) ;
648+
649+ // We try to bump the chroma a bit for more colorful palettes
650+ // Formula empirically and cluelessly derived
651+ oklch. c *= 1.0 + 2.0 * amount / oklch. l . max ( 0.05 ) ;
628652
629- hsl . l = if hsl . l + amount > 1.0 {
653+ oklch . l = if oklch . l + amount > 1.0 {
630654 1.0
631655 } else {
632- hsl . l + amount
656+ oklch . l + amount
633657 } ;
634658
635- from_hsl ( hsl )
659+ from_oklch ( oklch )
636660}
637661
638662fn deviate ( color : Color , amount : f32 ) -> Color {
639663 if is_dark ( color) {
640664 lighten ( color, amount)
641665 } else {
642- darken ( color, amount * 0.8 )
666+ darken ( color, amount)
643667 }
644668}
645669
646- fn muted ( color : Color ) -> Color {
647- let mut hsl = to_hsl ( color) ;
648-
649- hsl. s = hsl. s . min ( 0.5 ) ;
650-
651- from_hsl ( hsl)
652- }
653-
654670fn mix ( a : Color , b : Color , factor : f32 ) -> Color {
655671 let b_amount = factor. clamp ( 0.0 , 1.0 ) ;
656672 let a_amount = 1.0 - b_amount;
@@ -680,6 +696,12 @@ fn readable(background: Color, text: Color) -> Color {
680696 return candidate;
681697 }
682698
699+ let candidate = improve ( text, 0.2 ) ;
700+
701+ if is_readable ( background, candidate) {
702+ return candidate;
703+ }
704+
683705 let white_contrast = relative_contrast ( background, Color :: WHITE ) ;
684706 let black_contrast = relative_contrast ( background, Color :: BLACK ) ;
685707
@@ -691,85 +713,71 @@ fn readable(background: Color, text: Color) -> Color {
691713}
692714
693715fn is_dark ( color : Color ) -> bool {
694- to_hsl ( color) . l < 0.6
716+ to_oklch ( color) . l < 0.6
695717}
696718
697719fn is_readable ( a : Color , b : Color ) -> bool {
698- relative_contrast ( a, b) >= 7 .0
720+ relative_contrast ( a, b) >= 6 .0
699721}
700722
701723// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
702724fn relative_contrast ( a : Color , b : Color ) -> f32 {
703- let lum_a = relative_luminance ( a ) ;
704- let lum_b = relative_luminance ( b ) ;
725+ let lum_a = a . relative_luminance ( ) ;
726+ let lum_b = b . relative_luminance ( ) ;
705727 ( lum_a. max ( lum_b) + 0.05 ) / ( lum_a. min ( lum_b) + 0.05 )
706728}
707729
708- // https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
709- fn relative_luminance ( color : Color ) -> f32 {
710- let linear = color. into_linear ( ) ;
711- 0.2126 * linear[ 0 ] + 0.7152 * linear[ 1 ] + 0.0722 * linear[ 2 ]
712- }
730+ // https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
731+ fn to_oklch ( color : Color ) -> Oklch {
732+ let [ r, g, b, alpha] = color. into_linear ( ) ;
713733
714- // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
715- fn to_hsl ( color : Color ) -> Hsl {
716- let x_max = color. r . max ( color. g ) . max ( color. b ) ;
717- let x_min = color. r . min ( color. g ) . min ( color. b ) ;
718- let c = x_max - x_min;
719- let l = x_max. midpoint ( x_min) ;
734+ // linear RGB → LMS
735+ let l = 0.41222146 * r + 0.53633255 * g + 0.051445995 * b;
736+ let m = 0.2119035 * r + 0.6806995 * g + 0.10739696 * b;
737+ let s = 0.08830246 * r + 0.28171885 * g + 0.6299787 * b;
720738
721- let h = if c == 0.0 {
722- 0.0
723- } else if x_max == color. r {
724- 60.0 * ( ( color. g - color. b ) / c) . rem_euclid ( 6.0 )
725- } else if x_max == color. g {
726- 60.0 * ( ( ( color. b - color. r ) / c) + 2.0 )
727- } else {
728- // x_max == color.b
729- 60.0 * ( ( ( color. r - color. g ) / c) + 4.0 )
730- } ;
739+ // Nonlinear transform (cube root)
740+ let l_ = l. cbrt ( ) ;
741+ let m_ = m. cbrt ( ) ;
742+ let s_ = s. cbrt ( ) ;
731743
732- let s = if l == 0.0 || l == 1.0 {
733- 0.0
734- } else {
735- ( x_max - l) / l. min ( 1.0 - l)
736- } ;
744+ // LMS → Oklab
745+ let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
746+ let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
747+ let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
737748
738- Hsl {
739- h,
740- s,
741- l,
742- a : color. a ,
743- }
749+ // Oklab → Oklch
750+ let c = ( a * a + b * b) . sqrt ( ) ;
751+ let h = b. atan2 ( a) ; // radians
752+
753+ Oklch { l, c, h, a : alpha }
744754}
745755
746- // https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
747- fn from_hsl ( hsl : Hsl ) -> Color {
748- let c = ( 1.0 - ( 2.0 * hsl. l - 1.0 ) . abs ( ) ) * hsl. s ;
749- let h = hsl. h / 60.0 ;
750- let x = c * ( 1.0 - ( h. rem_euclid ( 2.0 ) - 1.0 ) . abs ( ) ) ;
751-
752- let ( r1, g1, b1) = if h < 1.0 {
753- ( c, x, 0.0 )
754- } else if h < 2.0 {
755- ( x, c, 0.0 )
756- } else if h < 3.0 {
757- ( 0.0 , c, x)
758- } else if h < 4.0 {
759- ( 0.0 , x, c)
760- } else if h < 5.0 {
761- ( x, 0.0 , c)
762- } else {
763- // h < 6.0
764- ( c, 0.0 , x)
765- } ;
756+ // https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
757+ fn from_oklch ( oklch : Oklch ) -> Color {
758+ let Oklch { l, c, h, a : alpha } = oklch;
766759
767- let m = hsl. l - ( c / 2.0 ) ;
760+ let a = c * h. cos ( ) ;
761+ let b = c * h. sin ( ) ;
768762
769- Color {
770- r : r1 + m,
771- g : g1 + m,
772- b : b1 + m,
773- a : hsl. a ,
774- }
763+ // Oklab → LMS (nonlinear)
764+ let l_ = l + 0.39633778 * a + 0.21580376 * b;
765+ let m_ = l - 0.105561346 * a - 0.06385417 * b;
766+ let s_ = l - 0.08948418 * a - 1.2914855 * b;
767+
768+ // Cubing back
769+ let l = l_ * l_ * l_;
770+ let m = m_ * m_ * m_;
771+ let s = s_ * s_ * s_;
772+
773+ let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
774+ let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
775+ let b = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
776+
777+ Color :: from_linear_rgba (
778+ r. clamp ( 0.0 , 1.0 ) ,
779+ g. clamp ( 0.0 , 1.0 ) ,
780+ b. clamp ( 0.0 , 1.0 ) ,
781+ alpha,
782+ )
775783}
0 commit comments