@@ -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,13 @@ 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 strong version of the base background color.
458460 pub strong : Pair ,
461+ /// A stronger version of the base background color.
462+ pub stronger : Pair ,
459463 /// The strongest version of the base background color.
460464 pub strongest : Pair ,
461465}
@@ -464,15 +468,19 @@ impl Background {
464468 /// Generates a set of [`Background`] colors from the base and text colors.
465469 pub fn new ( base : Color , text : Color ) -> Self {
466470 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 ) ) ;
471+ let weaker = deviate ( base, 0.07 ) ;
472+ let weak = deviate ( base, 0.1 ) ;
473+ let strong = deviate ( base, 0.15 ) ;
474+ let stronger = deviate ( base, 0.175 ) ;
475+ let strongest = deviate ( base, 0.20 ) ;
470476
471477 Self {
472478 base : Pair :: new ( base, text) ,
473479 weakest : Pair :: new ( weakest, text) ,
480+ weaker : Pair :: new ( weaker, text) ,
474481 weak : Pair :: new ( weak, text) ,
475482 strong : Pair :: new ( strong, text) ,
483+ stronger : Pair :: new ( stronger, text) ,
476484 strongest : Pair :: new ( strongest, text) ,
477485 }
478486 }
@@ -517,9 +525,9 @@ pub struct Secondary {
517525impl Secondary {
518526 /// Generates a set of [`Secondary`] colors from the base and text colors.
519527 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 ) ;
528+ let weak = mix ( deviate ( base, 0.1 ) , text, 0.4 ) ;
529+ let base = mix ( deviate ( base, 0.3 ) , text, 0.4 ) ;
530+ let strong = mix ( deviate ( base, 0.5 ) , text, 0.4 ) ;
523531
524532 Self {
525533 base : Pair :: new ( base, text) ,
@@ -604,53 +612,55 @@ impl Danger {
604612 }
605613}
606614
607- struct Hsl {
608- h : f32 ,
609- s : f32 ,
615+ struct Oklch {
610616 l : f32 ,
617+ c : f32 ,
618+ h : f32 ,
611619 a : f32 ,
612620}
613621
614622fn darken ( color : Color , amount : f32 ) -> Color {
615- let mut hsl = to_hsl ( color) ;
623+ let mut oklch = to_oklch ( color) ;
616624
617- hsl. l = if hsl. l - amount < 0.0 {
625+ // We try to bump the chroma a bit for more colorful palettes
626+ if oklch. c > 0.0 && oklch. c < ( 1.0 - oklch. l ) / 2.0 {
627+ // Formula empirically and cluelessly derived
628+ oklch. c *= 1.0 + ( 0.2 / oklch. c ) . min ( 100.0 ) * amount;
629+ }
630+
631+ oklch. l = if oklch. l - amount < 0.0 {
618632 0.0
619633 } else {
620- hsl . l - amount
634+ oklch . l - amount
621635 } ;
622636
623- from_hsl ( hsl )
637+ from_oklch ( oklch )
624638}
625639
626640fn lighten ( color : Color , amount : f32 ) -> Color {
627- let mut hsl = to_hsl ( color) ;
641+ let mut oklch = to_oklch ( color) ;
628642
629- hsl. l = if hsl. l + amount > 1.0 {
643+ // We try to bump the chroma a bit for more colorful palettes
644+ // Formula empirically and cluelessly derived
645+ oklch. c *= 1.0 + 2.0 * amount / oklch. l . max ( 0.05 ) ;
646+
647+ oklch. l = if oklch. l + amount > 1.0 {
630648 1.0
631649 } else {
632- hsl . l + amount
650+ oklch . l + amount
633651 } ;
634652
635- from_hsl ( hsl )
653+ from_oklch ( oklch )
636654}
637655
638656fn deviate ( color : Color , amount : f32 ) -> Color {
639657 if is_dark ( color) {
640658 lighten ( color, amount)
641659 } else {
642- darken ( color, amount * 0.8 )
660+ darken ( color, amount)
643661 }
644662}
645663
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-
654664fn mix ( a : Color , b : Color , factor : f32 ) -> Color {
655665 let b_amount = factor. clamp ( 0.0 , 1.0 ) ;
656666 let a_amount = 1.0 - b_amount;
@@ -680,6 +690,12 @@ fn readable(background: Color, text: Color) -> Color {
680690 return candidate;
681691 }
682692
693+ let candidate = improve ( text, 0.2 ) ;
694+
695+ if is_readable ( background, candidate) {
696+ return candidate;
697+ }
698+
683699 let white_contrast = relative_contrast ( background, Color :: WHITE ) ;
684700 let black_contrast = relative_contrast ( background, Color :: BLACK ) ;
685701
@@ -691,11 +707,11 @@ fn readable(background: Color, text: Color) -> Color {
691707}
692708
693709fn is_dark ( color : Color ) -> bool {
694- to_hsl ( color) . l < 0.6
710+ to_oklch ( color) . l < 0.6
695711}
696712
697713fn is_readable ( a : Color , b : Color ) -> bool {
698- relative_contrast ( a, b) >= 7 .0
714+ relative_contrast ( a, b) >= 6 .0
699715}
700716
701717// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
@@ -711,65 +727,57 @@ fn relative_luminance(color: Color) -> f32 {
711727 0.2126 * linear[ 0 ] + 0.7152 * linear[ 1 ] + 0.0722 * linear[ 2 ]
712728}
713729
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) ;
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 ( ) ;
720733
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- } ;
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;
731738
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- } ;
739+ // Nonlinear transform (cube root)
740+ let l_ = l. cbrt ( ) ;
741+ let m_ = m. cbrt ( ) ;
742+ let s_ = s. cbrt ( ) ;
737743
738- Hsl {
739- h,
740- s,
741- l,
742- a : color. a ,
743- }
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_;
748+
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