diff --git a/crates/canvas/src/interaction/ink.rs b/crates/canvas/src/interaction/ink.rs index ec8d621dfbd1fa456892bb6bcbc9365bd50b2499..510e2e2518bf365f43b6e7d6482901bbda2bae6a 100644 --- a/crates/canvas/src/interaction/ink.rs +++ b/crates/canvas/src/interaction/ink.rs @@ -20,7 +20,11 @@ use onestore::{ pub fn pens(section: [f32; 4]) -> [Pen; 15] { let [red, green, blue] = draw::srgb_bytes({ let [saturation, lightness] = draw::LIGHT_ACCENT; - draw::hsl(draw::hue(section), saturation, lightness) + draw::hsl( + draw::hue(section), + saturation * draw::vividness(section), + lightness, + ) }); let accent = Pen::new(35.0, Some(u32::from_le_bytes([red, green, blue, 0]))); std::array::from_fn(|place| place.checked_sub(1).map_or(accent, |at| FAVORITES[at])) diff --git a/crates/draw/src/render.rs b/crates/draw/src/render.rs index 2625eec830b1db3f9542c9784f26a8846988aaa6..93b5d79f5093fa48bfb98e5b4eb29f0b282fcb81 100644 --- a/crates/draw/src/render.rs +++ b/crates/draw/src/render.rs @@ -1762,6 +1762,15 @@ pub fn hue(color: [f32; 4]) -> f32 { to_hsl([color[0], color[1], color[2]].map(crate::encode))[0] } +/// How much of a shade's saturation a linear colour's hue takes, from 0 to 1: all of it from +/// a saturation of `VIVID` up, as OneNote 2010's bright section colours have, less as the +/// colour greys, so its Silver and mists stay grey rather than turn their faint hue vivid. +pub fn vividness(color: [f32; 4]) -> f32 { + const VIVID: f32 = 0.35; + let [_, saturation, _] = to_hsl([color[0], color[1], color[2]].map(crate::encode)); + (saturation / VIVID).min(1.0) +} + /// Hue in degrees, saturation and lightness of an encoded sRGB colour. fn to_hsl([r, g, b]: [f32; 3]) -> [f32; 3] { let [max, min] = [r.max(g).max(b), r.min(g).min(b)]; diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 52888a58df2b1097d0cc573efe0192b6935d9148..c71d1289916e3a775467f734046df6325ed8a9d1 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -290,10 +290,12 @@ impl Theme { } } - /// The colours a section takes from the hue of `color`, linear RGBA. + /// The colours a section takes from the hue of `color`, linear RGBA, as grey as it is. pub fn section(&self, color: [f32; 4]) -> Section { - let hue = hue(color); - let shade = |hue: f32, [saturation, lightness]: [f32; 2]| hsl(hue, saturation, lightness); + let (hue, vividness) = (hue(color), draw::vividness(color)); + let shade = |hue: f32, [saturation, lightness]: [f32; 2]| { + hsl(hue, saturation * vividness, lightness) + }; let Shades { frame, tab, @@ -326,6 +328,32 @@ mod tests { assert_eq!(hsl(240.0, 0.0, 1.0), srgb(0xff, 0xff, 0xff)); } + /// OneNote 2010's Silver (`corpus/section-color/native/colors.txt`) shades grey, as + /// faint in hue as it is, while its Blue takes the theme's full saturation. + #[test] + fn grey_sections_stay_grey() { + // HSL saturation, as sRGB shows it. + let saturation = |color: [f32; 4]| { + let [r, g, b] = draw::srgb_bytes(color).map(|byte| f32::from(byte) / 255.0); + let [max, min] = [r.max(g).max(b), r.min(g).min(b)]; + (max - min) / (1.0 - (max + min - 1.0).abs()) + }; + for theme in [Theme::light(), Theme::dark()] { + let silver = theme.section(srgb(0x95, 0x95, 0xaa)); + let blue = theme.section(srgb(0x8a, 0xa8, 0xe4)); + for (silver, blue) in [ + (silver.tab, blue.tab), + (silver.accent, blue.accent), + (silver.frame[0], blue.frame[0]), + ] { + assert!( + saturation(silver) < saturation(blue) * 0.4, + "{silver:?} {blue:?}" + ); + } + } + } + #[test] fn icon_badges_stand_clear_of_the_accent() { let badge = |accent_hue: f32| {