@@ -107,6 +107,8 @@ pub struct Framebuffer {
107107 /// of the palette as [dark, light], unless the palette is recognized
108108 /// as a light them, in which case it swaps them.
109109 auto_colors : [ StraightRgba ; 2 ] ,
110+ /// Above this lightness value, we consider a color to be "light".
111+ auto_color_threshold : f32 ,
110112 /// A cache table for previously contrasted colors.
111113 /// See: <https://fgiesen.wordpress.com/2019/02/11/cache-tables/>
112114 contrast_colors : [ Cell < ( StraightRgba , StraightRgba ) > ; CACHE_TABLE_SIZE ] ,
@@ -125,6 +127,7 @@ impl Framebuffer {
125127 DEFAULT_THEME [ IndexedColor :: Black as usize ] ,
126128 DEFAULT_THEME [ IndexedColor :: BrightWhite as usize ] ,
127129 ] ,
130+ auto_color_threshold : 0.5 ,
128131 contrast_colors : [ const { Cell :: new ( ( StraightRgba :: zero ( ) , StraightRgba :: zero ( ) ) ) } ;
129132 CACHE_TABLE_SIZE ] ,
130133 background_fill : DEFAULT_THEME [ IndexedColor :: Background as usize ] ,
@@ -145,7 +148,17 @@ impl Framebuffer {
145148 self . indexed_colors [ IndexedColor :: Black as usize ] ,
146149 self . indexed_colors [ IndexedColor :: BrightWhite as usize ] ,
147150 ] ;
148- if !Self :: is_dark ( self . auto_colors [ 0 ] ) {
151+
152+ // It's not guaranteed that Black is actually dark and BrightWhite light (vice versa for a light theme).
153+ // Such is the case with macOS 26's "Clear Dark" theme (and probably a lot other themes).
154+ // Its black is #35424C (l=0.3716; oof!) and bright white is #E5EFF5 (l=0.9464).
155+ // If we have a color such as #43698A (l=0.5065), which is l>0.5 ("light") and need a contrasting color,
156+ // we need that to be #E5EFF5, even though that's also l>0.5. With a midpoint of 0.659, we get that right.
157+ let lightness = self . auto_colors . map ( |c| c. as_oklab ( ) . lightness ( ) ) ;
158+ self . auto_color_threshold = ( lightness[ 0 ] + lightness[ 1 ] ) * 0.5 ;
159+
160+ // Ensure [0] is dark and [1] is light.
161+ if lightness[ 0 ] > lightness[ 1 ] {
149162 self . auto_colors . swap ( 0 , 1 ) ;
150163 }
151164 }
@@ -346,15 +359,12 @@ impl Framebuffer {
346359 #[ cold]
347360 fn contrasted_slow ( & self , color : StraightRgba ) -> StraightRgba {
348361 let idx = ( color. to_ne ( ) as usize ) . wrapping_mul ( HASH_MULTIPLIER ) >> CACHE_TABLE_SHIFT ;
349- let contrast = self . auto_colors [ Self :: is_dark ( color) as usize ] ;
362+ let is_dark = color. as_oklab ( ) . lightness ( ) < self . auto_color_threshold ;
363+ let contrast = self . auto_colors [ is_dark as usize ] ;
350364 self . contrast_colors [ idx] . set ( ( color, contrast) ) ;
351365 contrast
352366 }
353367
354- fn is_dark ( color : StraightRgba ) -> bool {
355- color. as_oklab ( ) . lightness ( ) < 0.5
356- }
357-
358368 /// Blends the given sRGB color onto the background bitmap.
359369 ///
360370 /// TODO: The current approach blends foreground/background independently,
0 commit comments