From 437a2826eaa540e8d9efb32c8842fae96751eef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 20 Nov 2025 23:37:21 +0100 Subject: [PATCH 1/2] Make color helpers in `palette` module public Co-authored-by: Vincent Hanquez --- core/src/theme/palette.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/core/src/theme/palette.rs b/core/src/theme/palette.rs index 88c843b0..92186d80 100644 --- a/core/src/theme/palette.rs +++ b/core/src/theme/palette.rs @@ -625,7 +625,8 @@ struct Oklch { a: f32, } -fn darken(color: Color, amount: f32) -> Color { +/// Darkens a [`Color`] by the given factor. +pub fn darken(color: Color, amount: f32) -> Color { let mut oklch = to_oklch(color); // We try to bump the chroma a bit for more colorful palettes @@ -643,7 +644,8 @@ fn darken(color: Color, amount: f32) -> Color { from_oklch(oklch) } -fn lighten(color: Color, amount: f32) -> Color { +/// Lightens a [`Color`] by the given factor. +pub fn lighten(color: Color, amount: f32) -> Color { let mut oklch = to_oklch(color); // We try to bump the chroma a bit for more colorful palettes @@ -659,7 +661,9 @@ fn lighten(color: Color, amount: f32) -> Color { from_oklch(oklch) } -fn deviate(color: Color, amount: f32) -> Color { +/// Deviates a [`Color`] by the given factor. Lightens if the [`Color`] is +/// dark, darkens otherwise. +pub fn deviate(color: Color, amount: f32) -> Color { if is_dark(color) { lighten(color, amount) } else { @@ -667,7 +671,8 @@ fn deviate(color: Color, amount: f32) -> Color { } } -fn mix(a: Color, b: Color, factor: f32) -> Color { +/// Mixes two colors by the given factor. +pub fn mix(a: Color, b: Color, factor: f32) -> Color { let b_amount = factor.clamp(0.0, 1.0); let a_amount = 1.0 - b_amount; @@ -682,7 +687,9 @@ fn mix(a: Color, b: Color, factor: f32) -> Color { ) } -fn readable(background: Color, text: Color) -> Color { +/// Computes a [`Color`] from the given text color that is +/// readable on top of the given background color. +pub fn readable(background: Color, text: Color) -> Color { if is_readable(background, text) { return text; } @@ -712,18 +719,24 @@ fn readable(background: Color, text: Color) -> Color { } } -fn is_dark(color: Color) -> bool { +/// Returns true if the [`Color`] is dark. +pub fn is_dark(color: Color) -> bool { to_oklch(color).l < 0.6 } -fn is_readable(a: Color, b: Color) -> bool { - relative_contrast(a, b) >= 6.0 +/// Returns true if text with the given [`Color`] is readable on top +/// of the given background [`Color`]. +pub fn is_readable(background: Color, text: Color) -> bool { + relative_contrast(background, text) >= 6.0 } -// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio -fn relative_contrast(a: Color, b: Color) -> f32 { +/// Returns the [relative contrast ratio] of two colors. +/// +/// [relative contrast ratio]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio +pub fn relative_contrast(a: Color, b: Color) -> f32 { let lum_a = a.relative_luminance(); let lum_b = b.relative_luminance(); + (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05) } From 5510675f15de28bd0700834bb0a36e9fb38713eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 20 Nov 2025 23:46:37 +0100 Subject: [PATCH 2/2] Move some accessibility helpers to `Color` --- core/src/color.rs | 16 ++++++++++++++++ core/src/theme/palette.rs | 26 +++++--------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 954aad66..ff1e18b0 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -202,6 +202,22 @@ impl Color { let linear = self.into_linear(); 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2] } + + /// Returns the [relative contrast ratio] of the [`Color`] against another one. + /// + /// [relative contrast ratio]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio + pub fn relative_contrast(self, b: Color) -> f32 { + let lum_a = self.relative_luminance(); + let lum_b = b.relative_luminance(); + + (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05) + } + + /// Returns true if the current [`Color`] is readable on top + /// of the given background [`Color`]. + pub fn is_readable_on(self, background: Color) -> bool { + background.relative_contrast(self) >= 6.0 + } } impl From<[f32; 3]> for Color { diff --git a/core/src/theme/palette.rs b/core/src/theme/palette.rs index 92186d80..4cd66c9e 100644 --- a/core/src/theme/palette.rs +++ b/core/src/theme/palette.rs @@ -690,7 +690,7 @@ pub fn mix(a: Color, b: Color, factor: f32) -> Color { /// Computes a [`Color`] from the given text color that is /// readable on top of the given background color. pub fn readable(background: Color, text: Color) -> Color { - if is_readable(background, text) { + if text.is_readable_on(background) { return text; } @@ -699,18 +699,18 @@ pub fn readable(background: Color, text: Color) -> Color { // TODO: Compute factor from relative contrast value let candidate = improve(text, 0.1); - if is_readable(background, candidate) { + if candidate.is_readable_on(background) { return candidate; } let candidate = improve(text, 0.2); - if is_readable(background, candidate) { + if candidate.is_readable_on(background) { return candidate; } - let white_contrast = relative_contrast(background, Color::WHITE); - let black_contrast = relative_contrast(background, Color::BLACK); + let white_contrast = background.relative_contrast(Color::WHITE); + let black_contrast = background.relative_contrast(Color::BLACK); if white_contrast >= black_contrast { mix(Color::WHITE, background, 0.05) @@ -724,22 +724,6 @@ pub fn is_dark(color: Color) -> bool { to_oklch(color).l < 0.6 } -/// Returns true if text with the given [`Color`] is readable on top -/// of the given background [`Color`]. -pub fn is_readable(background: Color, text: Color) -> bool { - relative_contrast(background, text) >= 6.0 -} - -/// Returns the [relative contrast ratio] of two colors. -/// -/// [relative contrast ratio]: https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio -pub fn relative_contrast(a: Color, b: Color) -> f32 { - let lum_a = a.relative_luminance(); - let lum_b = b.relative_luminance(); - - (lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05) -} - // https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces fn to_oklch(color: Color) -> Oklch { let [r, g, b, alpha] = color.into_linear();