From bc1ed6539955df087046f679fe55cfe7f84d3653 Mon Sep 17 00:00:00 2001 From: Aiving Date: Sun, 18 Aug 2024 20:54:02 +0500 Subject: [PATCH] see CHANGELOG.md --- CHANGELOG.md | 15 + src/color.rs | 20 +- src/dynamic_color/dynamic_scheme.rs | 66 +- src/dynamic_color/material_dynamic_colors.rs | 1529 +++++++++++++----- src/dynamic_color/mod.rs | 29 +- src/hct/mod.rs | 10 +- src/palette/core.rs | 38 +- src/palette/mod.rs | 3 +- src/palette/tonal.rs | 162 +- src/scheme/mod.rs | 94 +- src/scheme/variant/content.rs | 233 ++- src/scheme/variant/expressive.rs | 212 ++- src/scheme/variant/fidelity.rs | 257 ++- src/scheme/variant/fruit_salad.rs | 312 +++- src/scheme/variant/monochrome.rs | 427 ++++- src/scheme/variant/neutral.rs | 201 ++- src/scheme/variant/rainbow.rs | 244 ++- src/scheme/variant/tonal_spot.rs | 3 +- src/scheme/variant/vibrant.rs | 208 ++- src/temperature.rs | 249 ++- src/theme.rs | 5 + src/utils/mod.rs | 4 + 22 files changed, 3556 insertions(+), 765 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01d2748..56916fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +- **added**: Add `struct:CorePalettes` +- **added**: Add unit tests for `struct:SchemeContent` +- **added**: Add unit tests for `struct:SchemeExpressive` +- **added**: Add unit tests for `struct:SchemeFidelity` +- **added**: Add unit tests for `struct:SchemeFruitSalad` +- **added**: Add unit tests for `struct:SchemeMonochrome` +- **added**: Add unit tests for `struct:SchemeNeutral` +- **added**: Add unit tests for `struct:SchemeRainbow` +- **added**: Add unit tests for `struct:SchemeVibrant` +- **changed**: Deprecate `struct:CorePalette` +- **changed**: Update `struct:MaterialDynamicColors` to use the expressive on-colors spec +- **changed**: Update `struct:TonalPalette` to use new key color algorithm + ## 0.4.2 (Apr 8th, 2024) - **fixed**: Fix markdown in `README` diff --git a/src/color.rs b/src/color.rs index 98096d8..0420b13 100644 --- a/src/color.rs +++ b/src/color.rs @@ -433,7 +433,7 @@ fn lab_invf(ft: f64) -> f64 { #[cfg(test)] mod tests { use super::Lab; - use crate::color::{delinearized, linearized, lstar_from_y, y_from_lstar, Argb, Xyz}; + use crate::color::{delinearized, linearized, lstar_from_y, y_from_lstar, Argb, Rgb, Xyz}; #[cfg(not(feature = "std"))] use alloc::vec::Vec; use float_cmp::assert_approx_eq; @@ -473,6 +473,24 @@ mod tests { } } + #[test] + fn test_argb_from_rgb_returns_correct_value_for_black() { + assert_eq!(Argb::from(Rgb::new(0, 0, 0)), Argb::from_u32(0xff000000)); + assert_eq!(Argb::from(Rgb::new(0, 0, 0)), Argb::from_u32(4278190080)); + } + + #[test] + fn test_argb_from_rgb_returns_correct_value_for_white() { + assert_eq!(Argb::from(Rgb::new(255, 255, 255)), Argb::from_u32(0xffffffff)); + assert_eq!(Argb::from(Rgb::new(255, 255, 255)), Argb::from_u32(4294967295)); + } + + #[test] + fn test_argb_from_rgb_returns_correct_value_for_random_color() { + assert_eq!(Argb::from(Rgb::new(50, 150, 250)), Argb::from_u32(0xff3296fa)); + assert_eq!(Argb::from(Rgb::new(50, 150, 250)), Argb::from_u32(4281505530)); + } + #[test] fn test_yto_lstar_to_y() { for y in _range(0.0, 100.0, 1001) { diff --git a/src/dynamic_color/dynamic_scheme.rs b/src/dynamic_color/dynamic_scheme.rs index 7cffa28..97d1260 100644 --- a/src/dynamic_color/dynamic_scheme.rs +++ b/src/dynamic_color/dynamic_scheme.rs @@ -23,9 +23,6 @@ use core::{ /// [`DynamicColor`]: super::DynamicColor #[derive(Clone, PartialOrd)] pub struct DynamicScheme { - /// The source color of the theme as an Argb integer. - pub source_color_argb: Argb, - /// The source color of the theme in HCT. pub source_color_hct: Hct, @@ -68,8 +65,7 @@ pub struct DynamicScheme { impl DynamicScheme { pub fn new( - source_color_argb: Argb, - source_color_hct: Option, + source_color_hct: Hct, variant: Variant, is_dark: bool, contrast_level: Option, @@ -81,8 +77,7 @@ impl DynamicScheme { error_palette: Option, ) -> Self { Self { - source_color_argb, - source_color_hct: source_color_hct.unwrap_or_else(|| source_color_argb.into()), + source_color_hct, variant, is_dark, contrast_level: contrast_level.unwrap_or(0.0), @@ -158,162 +153,215 @@ impl DynamicScheme { pub fn primary_palette_key_color(&self) -> Argb { MaterialDynamicColors::primary_palette_key_color().get_argb(self) } + pub fn secondary_palette_key_color(&self) -> Argb { MaterialDynamicColors::secondary_palette_key_color().get_argb(self) } + pub fn tertiary_palette_key_color(&self) -> Argb { MaterialDynamicColors::tertiary_palette_key_color().get_argb(self) } + pub fn neutral_palette_key_color(&self) -> Argb { MaterialDynamicColors::neutral_palette_key_color().get_argb(self) } + pub fn neutral_variant_palette_key_color(&self) -> Argb { MaterialDynamicColors::neutral_palette_key_color().get_argb(self) } + pub fn background(&self) -> Argb { MaterialDynamicColors::background().get_argb(self) } + pub fn on_background(&self) -> Argb { MaterialDynamicColors::on_background().get_argb(self) } + pub fn surface(&self) -> Argb { MaterialDynamicColors::surface().get_argb(self) } + pub fn surface_dim(&self) -> Argb { MaterialDynamicColors::surface_dim().get_argb(self) } + pub fn surface_bright(&self) -> Argb { MaterialDynamicColors::surface_bright().get_argb(self) } + pub fn surface_container_lowest(&self) -> Argb { MaterialDynamicColors::surface_container_lowest().get_argb(self) } + pub fn surface_container_low(&self) -> Argb { MaterialDynamicColors::surface_container_low().get_argb(self) } + pub fn surface_container(&self) -> Argb { MaterialDynamicColors::surface_container().get_argb(self) } + pub fn surface_container_high(&self) -> Argb { MaterialDynamicColors::surface_container_high().get_argb(self) } + pub fn surface_container_highest(&self) -> Argb { MaterialDynamicColors::surface_container_highest().get_argb(self) } + pub fn on_surface(&self) -> Argb { MaterialDynamicColors::on_surface().get_argb(self) } + pub fn surface_variant(&self) -> Argb { MaterialDynamicColors::surface_variant().get_argb(self) } + pub fn on_surface_variant(&self) -> Argb { MaterialDynamicColors::on_surface_variant().get_argb(self) } + pub fn inverse_surface(&self) -> Argb { MaterialDynamicColors::inverse_surface().get_argb(self) } + pub fn inverse_on_surface(&self) -> Argb { MaterialDynamicColors::inverse_on_surface().get_argb(self) } + pub fn outline(&self) -> Argb { MaterialDynamicColors::outline().get_argb(self) } + pub fn outline_variant(&self) -> Argb { MaterialDynamicColors::outline_variant().get_argb(self) } + pub fn shadow(&self) -> Argb { MaterialDynamicColors::shadow().get_argb(self) } + pub fn scrim(&self) -> Argb { MaterialDynamicColors::scrim().get_argb(self) } + pub fn surface_tint(&self) -> Argb { MaterialDynamicColors::surface_tint().get_argb(self) } + pub fn primary(&self) -> Argb { MaterialDynamicColors::primary().get_argb(self) } + pub fn on_primary(&self) -> Argb { MaterialDynamicColors::on_primary().get_argb(self) } + pub fn primary_container(&self) -> Argb { MaterialDynamicColors::primary_container().get_argb(self) } + pub fn on_primary_container(&self) -> Argb { MaterialDynamicColors::on_primary_container().get_argb(self) } + pub fn inverse_primary(&self) -> Argb { MaterialDynamicColors::inverse_primary().get_argb(self) } + pub fn secondary(&self) -> Argb { MaterialDynamicColors::secondary().get_argb(self) } + pub fn on_secondary(&self) -> Argb { MaterialDynamicColors::on_secondary().get_argb(self) } + pub fn secondary_container(&self) -> Argb { MaterialDynamicColors::secondary_container().get_argb(self) } + pub fn on_secondary_container(&self) -> Argb { MaterialDynamicColors::on_secondary_container().get_argb(self) } + pub fn tertiary(&self) -> Argb { MaterialDynamicColors::tertiary().get_argb(self) } + pub fn on_tertiary(&self) -> Argb { MaterialDynamicColors::on_tertiary().get_argb(self) } + pub fn tertiary_container(&self) -> Argb { MaterialDynamicColors::tertiary_container().get_argb(self) } + pub fn on_tertiary_container(&self) -> Argb { MaterialDynamicColors::on_tertiary_container().get_argb(self) } + pub fn error(&self) -> Argb { MaterialDynamicColors::error().get_argb(self) } + pub fn on_error(&self) -> Argb { MaterialDynamicColors::on_error().get_argb(self) } + pub fn error_container(&self) -> Argb { MaterialDynamicColors::error_container().get_argb(self) } + pub fn on_error_container(&self) -> Argb { MaterialDynamicColors::on_error_container().get_argb(self) } + pub fn primary_fixed(&self) -> Argb { MaterialDynamicColors::primary_fixed().get_argb(self) } + pub fn primary_fixed_dim(&self) -> Argb { MaterialDynamicColors::primary_fixed_dim().get_argb(self) } + pub fn on_primary_fixed(&self) -> Argb { MaterialDynamicColors::on_primary_fixed().get_argb(self) } + pub fn on_primary_fixed_variant(&self) -> Argb { MaterialDynamicColors::on_primary_fixed_variant().get_argb(self) } + pub fn secondary_fixed(&self) -> Argb { MaterialDynamicColors::secondary_fixed().get_argb(self) } + pub fn secondary_fixed_dim(&self) -> Argb { MaterialDynamicColors::secondary_fixed_dim().get_argb(self) } + pub fn on_secondary_fixed(&self) -> Argb { MaterialDynamicColors::on_secondary_fixed().get_argb(self) } + pub fn on_secondary_fixed_variant(&self) -> Argb { MaterialDynamicColors::on_secondary_fixed_variant().get_argb(self) } + pub fn tertiary_fixed(&self) -> Argb { MaterialDynamicColors::tertiary_fixed().get_argb(self) } + pub fn tertiary_fixed_dim(&self) -> Argb { MaterialDynamicColors::tertiary_fixed_dim().get_argb(self) } + pub fn on_tertiary_fixed(&self) -> Argb { MaterialDynamicColors::on_tertiary_fixed().get_argb(self) } + pub fn on_tertiary_fixed_variant(&self) -> Argb { MaterialDynamicColors::on_tertiary_fixed_variant().get_argb(self) } @@ -327,8 +375,7 @@ impl Ord for DynamicScheme { impl PartialEq for DynamicScheme { fn eq(&self, other: &Self) -> bool { - self.source_color_argb == other.source_color_argb - && self.source_color_hct == other.source_color_hct + self.source_color_hct == other.source_color_hct && self.variant == other.variant && self.is_dark == other.is_dark && self.contrast_level == other.contrast_level @@ -345,7 +392,6 @@ impl Eq for DynamicScheme {} impl Hash for DynamicScheme { fn hash(&self, state: &mut H) { - self.source_color_argb.hash(state); self.source_color_hct.hash(state); self.variant.hash(state); self.is_dark.hash(state); diff --git a/src/dynamic_color/material_dynamic_colors.rs b/src/dynamic_color/material_dynamic_colors.rs index 2ef9beb..c5aef5e 100644 --- a/src/dynamic_color/material_dynamic_colors.rs +++ b/src/dynamic_color/material_dynamic_colors.rs @@ -12,113 +12,6 @@ const fn _is_monochrome(scheme: &DynamicScheme) -> bool { matches!(scheme.variant, Variant::Monochrome) } -macro_rules! define_key { - ($name:ident => $palette:ident; [tone, $scheme_argument:ident] => $tone:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$scheme_argument| $tone, - false, - None, - None, - None, - None, - ) - } - }; - - ($name:ident => $palette:ident; [tone, $tone_scheme_argument:ident] => $tone:expr; [background, $background_scheme_argument:ident] => $background:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$tone_scheme_argument| $tone, - false, - Some(|$background_scheme_argument| $background), - None, - None, - None, - ) - } - }; - - ($name:ident => $palette:ident; [tone, $tone_scheme_argument:ident] => $tone:expr; [background, $background_scheme_argument:ident] => $background:expr; [contrast_curve] => $contrast_curve:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$tone_scheme_argument| $tone, - false, - Some(|$background_scheme_argument| $background), - None, - Some($contrast_curve), - None, - ) - } - }; - - ($name:ident => $palette:ident; [tone, $tone_scheme_argument:ident] => $tone:expr; [background, $background_scheme_argument:ident] => $background:expr; [second_background, $second_background_scheme_argument:ident] => $second_background:expr; [contrast_curve] => $contrast_curve:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$tone_scheme_argument| $tone, - false, - Some(|$background_scheme_argument| $background), - Some(|$second_background_scheme_argument| $second_background), - Some($contrast_curve), - None, - ) - } - }; - - ($name:ident => $palette:ident; [tone, $tone_scheme_argument:ident] => $tone:expr; [background, $background_scheme_argument:ident] => $background:expr; [contrast_curve] => $contrast_curve:expr; [tone_delta_pair, $tone_delta_pair_argument:ident] => $tone_delta_pair:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$tone_scheme_argument| $tone, - false, - Some(|$background_scheme_argument| $background), - None, - Some($contrast_curve), - Some(|$tone_delta_pair_argument| $tone_delta_pair), - ) - } - }; - - (background $name:ident => $palette:ident; [tone, $tone_scheme_argument:ident] => $tone:expr; [background, $background_scheme_argument:ident] => $background:expr; [contrast_curve] => $contrast_curve:expr; [tone_delta_pair, $tone_delta_pair_argument:ident] => $tone_delta_pair:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$tone_scheme_argument| $tone, - true, - Some(|$background_scheme_argument| $background), - None, - Some($contrast_curve), - Some(|$tone_delta_pair_argument| $tone_delta_pair), - ) - } - }; - - (background $name:ident => $palette:ident; [tone, $scheme_argument:ident] => $tone:expr;) => { - pub fn $name() -> DynamicColor { - DynamicColor::new( - stringify!($name), - |scheme| &scheme.$palette, - |$scheme_argument| $tone, - true, - None, - None, - None, - None, - ) - } - }; -} - /// Tokens, or named colors, in the Material Design system. pub struct MaterialDynamicColors; @@ -133,376 +26,1236 @@ impl MaterialDynamicColors { } } - define_key! { - primary_palette_key_color => primary_palette; - [tone, scheme] => scheme.primary_palette.key_color().get_tone(); + pub fn primary_palette_key_color() -> DynamicColor { + DynamicColor::from_palette( + "primary_palette_key_color", + |scheme| &scheme.primary_palette, + |scheme| scheme.primary_palette.key_color().get_tone(), + ) } - define_key! { - secondary_palette_key_color => secondary_palette; - [tone, scheme] => scheme.secondary_palette.key_color().get_tone(); + pub fn secondary_palette_key_color() -> DynamicColor { + DynamicColor::from_palette( + "secondary_palette_key_color", + |scheme| &scheme.secondary_palette, + |scheme| scheme.secondary_palette.key_color().get_tone(), + ) } - define_key! { - tertiary_palette_key_color => tertiary_palette; - [tone, scheme] => scheme.tertiary_palette.key_color().get_tone(); + pub fn tertiary_palette_key_color() -> DynamicColor { + DynamicColor::from_palette( + "tertiary_palette_key_color", + |scheme| &scheme.tertiary_palette, + |scheme| scheme.tertiary_palette.key_color().get_tone(), + ) } - define_key! { - neutral_palette_key_color => neutral_palette; - [tone, scheme] => scheme.neutral_palette.key_color().get_tone(); + pub fn neutral_palette_key_color() -> DynamicColor { + DynamicColor::from_palette( + "neutral_palette_key_color", + |scheme| &scheme.neutral_palette, + |scheme| scheme.neutral_palette.key_color().get_tone(), + ) } - define_key! { - neutral_variant_palette_key_color => neutral_variant_palette; - [tone, scheme] => scheme.neutral_variant_palette.key_color().get_tone(); + pub fn neutral_variant_palette_key_color() -> DynamicColor { + DynamicColor::from_palette( + "neutral_variant_palette_key_color", + |scheme| &scheme.neutral_variant_palette, + |scheme| scheme.neutral_variant_palette.key_color().get_tone(), + ) } - define_key! { - background background => neutral_palette; - [tone, scheme] => if scheme.is_dark { 6.0 } else { 98.0 }; + pub fn background() -> DynamicColor { + DynamicColor::new( + "background", + |scheme| &scheme.neutral_palette, + |scheme| if scheme.is_dark { 6.0 } else { 98.0 }, + true, + None, + None, + None, + None, + ) } - define_key! { - on_background => neutral_palette; - [tone, scheme] => if scheme.is_dark { 90.0 } else { 10.0 }; - [background, _scheme] => Self::background(); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 3.0, medium: 4.5, high: 7.0 }; + pub fn on_background() -> DynamicColor { + DynamicColor::new( + "on_background", + |scheme| &scheme.neutral_palette, + |scheme| if scheme.is_dark { 90.0 } else { 10.0 }, + false, + Some(|_scheme| Self::background()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 3.0, + medium: 4.5, + high: 7.0, + }), + None, + ) } - define_key! { - background surface => neutral_palette; - [tone, scheme] => if scheme.is_dark { 6.0 } else { 98.0 }; + pub fn surface() -> DynamicColor { + DynamicColor::new( + "surface", + |scheme| &scheme.neutral_palette, + |scheme| if scheme.is_dark { 6.0 } else { 98.0 }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_dim => neutral_palette; - [tone, scheme] => if scheme.is_dark { 6.0 } else { ContrastCurve { low: 87.0, normal: 87.0, medium: 80.0, high: 75.0 }.get(scheme.contrast_level) }; + pub fn surface_dim() -> DynamicColor { + DynamicColor::new( + "surface_dim", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + 6.0 + } else { + ContrastCurve { + low: 87.0, + normal: 87.0, + medium: 80.0, + high: 75.0, + } + .get(scheme.contrast_level) + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_bright => neutral_palette; - [tone, scheme] => if scheme.is_dark { ContrastCurve { low: 24.0, normal: 24.0, medium: 29.0, high: 34.0 }.get(scheme.contrast_level) } else { 98.0 }; + pub fn surface_bright() -> DynamicColor { + DynamicColor::new( + "surface_bright", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + ContrastCurve { + low: 24.0, + normal: 24.0, + medium: 29.0, + high: 34.0, + } + .get(scheme.contrast_level) + } else { + 98.0 + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_container_lowest => neutral_palette; - [tone, scheme] => if scheme.is_dark { ContrastCurve { low: 4.0, normal: 4.0, medium: 2.0, high: 0.0 }.get(scheme.contrast_level) } else { 100.0 }; + pub fn surface_container_lowest() -> DynamicColor { + DynamicColor::new( + "surface_container_lowest", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + ContrastCurve { + low: 4.0, + normal: 4.0, + medium: 2.0, + high: 0.0, + } + .get(scheme.contrast_level) + } else { + 100.0 + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_container_low => neutral_palette; - [tone, scheme] => if scheme.is_dark { ContrastCurve { low: 10.0, normal: 10.0, medium: 11.0, high: 12.0 }.get(scheme.contrast_level) } else { ContrastCurve { low: 96.0, normal: 96.0, medium: 96.0, high: 95.0 }.get(scheme.contrast_level) }; + pub fn surface_container_low() -> DynamicColor { + DynamicColor::new( + "surface_container_low", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + ContrastCurve { + low: 10.0, + normal: 10.0, + medium: 11.0, + high: 12.0, + } + .get(scheme.contrast_level) + } else { + ContrastCurve { + low: 96.0, + normal: 96.0, + medium: 96.0, + high: 95.0, + } + .get(scheme.contrast_level) + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_container => neutral_palette; - [tone, scheme] => if scheme.is_dark { ContrastCurve { low: 12.0, normal: 12.0, medium: 16.0, high: 20.0 }.get(scheme.contrast_level) } else { ContrastCurve { low: 94.0, normal: 94.0, medium: 92.0, high: 90.0 }.get(scheme.contrast_level) }; + pub fn surface_container() -> DynamicColor { + DynamicColor::new( + "surface_container", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + ContrastCurve { + low: 12.0, + normal: 12.0, + medium: 16.0, + high: 20.0, + } + .get(scheme.contrast_level) + } else { + ContrastCurve { + low: 94.0, + normal: 94.0, + medium: 92.0, + high: 90.0, + } + .get(scheme.contrast_level) + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_container_high => neutral_palette; - [tone, scheme] => if scheme.is_dark { ContrastCurve { low: 17.0, normal: 17.0, medium: 21.0, high: 25.0 }.get(scheme.contrast_level) } else { ContrastCurve { low: 92.0, normal: 92.0, medium: 88.0, high: 85.0 }.get(scheme.contrast_level) }; + pub fn surface_container_high() -> DynamicColor { + DynamicColor::new( + "surface_container_high", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + ContrastCurve { + low: 17.0, + normal: 17.0, + medium: 21.0, + high: 25.0, + } + .get(scheme.contrast_level) + } else { + ContrastCurve { + low: 92.0, + normal: 92.0, + medium: 88.0, + high: 85.0, + } + .get(scheme.contrast_level) + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - background surface_container_highest => neutral_palette; - [tone, scheme] => if scheme.is_dark { ContrastCurve { low: 22.0, normal: 22.0, medium: 26.0, high: 30.0 }.get(scheme.contrast_level) } else { ContrastCurve { low: 90.0, normal: 90.0, medium: 84.0, high: 80.0 }.get(scheme.contrast_level) }; + pub fn surface_container_highest() -> DynamicColor { + DynamicColor::new( + "surface_container_highest", + |scheme| &scheme.neutral_palette, + |scheme| { + if scheme.is_dark { + ContrastCurve { + low: 22.0, + normal: 22.0, + medium: 26.0, + high: 30.0, + } + .get(scheme.contrast_level) + } else { + ContrastCurve { + low: 90.0, + normal: 90.0, + medium: 84.0, + high: 80.0, + } + .get(scheme.contrast_level) + } + }, + true, + None, + None, + None, + None, + ) } - define_key! { - on_surface => neutral_palette; - [tone, scheme] => if scheme.is_dark { 90.0 } else { 10.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn on_surface() -> DynamicColor { + DynamicColor::new( + "on_surface", + |scheme| &scheme.neutral_palette, + |scheme| if scheme.is_dark { 90.0 } else { 10.0 }, + false, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) } - define_key! { - background surface_variant => neutral_variant_palette; - [tone, scheme] => if scheme.is_dark { 30.0 } else { 90.0 }; + pub fn surface_variant() -> DynamicColor { + DynamicColor::new( + "surface_variant", + |scheme| &scheme.neutral_variant_palette, + |scheme| if scheme.is_dark { 30.0 } else { 90.0 }, + true, + None, + None, + None, + None, + ) } - define_key! { - on_surface_variant => neutral_variant_palette; - [tone, scheme] => if scheme.is_dark { 80.0 } else { 30.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 11.0 }; + pub fn on_surface_variant() -> DynamicColor { + DynamicColor::new( + "on_surface_variant", + |scheme| &scheme.neutral_variant_palette, + |scheme| if scheme.is_dark { 80.0 } else { 30.0 }, + false, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) } - define_key! { - inverse_surface => neutral_palette; - [tone, scheme] => if scheme.is_dark { 90.0 } else { 20.0 }; + pub fn inverse_surface() -> DynamicColor { + DynamicColor::new( + "inverse_surface", + |scheme| &scheme.neutral_palette, + |scheme| if scheme.is_dark { 90.0 } else { 20.0 }, + false, + None, + None, + None, + None, + ) } - define_key! { - inverse_on_surface => neutral_palette; - [tone, scheme] => if scheme.is_dark { 20.0 } else { 95.0 }; - [background, _scheme] => Self::inverse_surface(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn inverse_on_surface() -> DynamicColor { + DynamicColor::new( + "inverse_on_surface", + |scheme| &scheme.neutral_palette, + |scheme| if scheme.is_dark { 20.0 } else { 95.0 }, + false, + Some(|_scheme| Self::inverse_surface()), + None, + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) } - define_key! { - outline => neutral_variant_palette; - [tone, scheme] => if scheme.is_dark { 60.0 } else { 50.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.5, normal: 3.0, medium: 4.5, high: 7.0 }; + pub fn outline() -> DynamicColor { + DynamicColor::new( + "outline", + |scheme| &scheme.neutral_variant_palette, + |scheme| if scheme.is_dark { 60.0 } else { 50.0 }, + false, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.5, + normal: 3.0, + medium: 4.5, + high: 7.0, + }), + None, + ) } - define_key! { - outline_variant => neutral_variant_palette; - [tone, scheme] => if scheme.is_dark { 30.0 } else { 80.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; + pub fn outline_variant() -> DynamicColor { + DynamicColor::new( + "outline_variant", + |scheme| &scheme.neutral_variant_palette, + |scheme| if scheme.is_dark { 30.0 } else { 80.0 }, + false, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + None, + ) } - define_key! { - shadow => neutral_palette; - [tone, _scheme] => 0.0; + pub fn shadow() -> DynamicColor { + DynamicColor::new( + "shadow", + |scheme| &scheme.neutral_palette, + |_scheme| 0.0, + false, + None, + None, + None, + None, + ) } - define_key! { - scrim => neutral_palette; - [tone, _scheme] => 0.0; + pub fn scrim() -> DynamicColor { + DynamicColor::new( + "scrim", + |scheme| &scheme.neutral_palette, + |_scheme| 0.0, + false, + None, + None, + None, + None, + ) } - define_key! { - background surface_tint => primary_palette; - [tone, scheme] => if scheme.is_dark { 80.0 } else { 40.0 }; + pub fn surface_tint() -> DynamicColor { + DynamicColor::new( + "surface_tint", + |scheme| &scheme.primary_palette, + |scheme| if scheme.is_dark { 80.0 } else { 40.0 }, + true, + None, + None, + None, + None, + ) } - define_key! { - background primary => primary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 100.0 } else { 0.0 } } else if scheme.is_dark { 80.0 } else { 40.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 7.0 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::primary_container(), Self::primary(), 10.0, TonePolarity::Nearer, false); + pub fn primary() -> DynamicColor { + DynamicColor::new( + "primary", + |scheme| &scheme.primary_palette, + |scheme| { + if _is_monochrome(scheme) { + if scheme.is_dark { + 100.0 + } else { + 0.0 + } + } else if scheme.is_dark { + 80.0 + } else { + 40.0 + } + }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 7.0, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::primary_container(), + Self::primary(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) } - define_key! { - on_primary => primary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 10.0 } else { 90.0 } } else if scheme.is_dark { 20.0 } else { 100.0 }; - [background, _scheme] => Self::primary(); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn on_primary() -> DynamicColor { + DynamicColor::new( + "on_primary", + |scheme| &scheme.primary_palette, + |scheme| { + if _is_monochrome(scheme) { + if scheme.is_dark { + 10.0 + } else { + 90.0 + } + } else if scheme.is_dark { + 20.0 + } else { + 100.0 + } + }, + false, + Some(|_scheme| Self::primary()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) } - define_key! { - background primary_container => primary_palette; - [tone, scheme] => if _is_fidelity(scheme) { scheme.source_color_hct.get_tone() } else if _is_monochrome(scheme) { if scheme.is_dark { 85.0 } else { 25.0 } } else if scheme.is_dark { 30.0 } else { 90.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::primary_container(), Self::primary(), 10.0, TonePolarity::Nearer, false); + pub fn primary_container() -> DynamicColor { + DynamicColor::new( + "primary_container", + |scheme| &scheme.primary_palette, + |scheme| { + if _is_fidelity(scheme) { + scheme.source_color_hct.get_tone() + } else if _is_monochrome(scheme) { + if scheme.is_dark { + 85.0 + } else { + 25.0 + } + } else if scheme.is_dark { + 30.0 + } else { + 90.0 + } + }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::primary_container(), + Self::primary(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) } - define_key! { - on_primary_container => primary_palette; - [tone, scheme] => if _is_fidelity(scheme) { DynamicColor::foreground_tone(Self::primary_container().get_tone(scheme), 4.5) } else if _is_monochrome(scheme) { if scheme.is_dark { 0.0 } else { 100.0 } } else if scheme.is_dark { 90.0 } else { 10.0 }; - [background, _scheme] => Self::primary_container(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn on_primary_container() -> DynamicColor { + DynamicColor::new( + "on_primary_container", + |scheme| &scheme.primary_palette, + |scheme| { + if _is_fidelity(scheme) { + DynamicColor::foreground_tone(Self::primary_container().get_tone(scheme), 4.5) + } else if _is_monochrome(scheme) { + if scheme.is_dark { + 0.0 + } else { + 100.0 + } + } else if scheme.is_dark { + 90.0 + } else { + 30.0 + } + }, + false, + Some(|_scheme| Self::primary_container()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) } - define_key! { - inverse_primary => primary_palette; - [tone, scheme] => if scheme.is_dark { 40.0 } else { 80.0 }; - [background, _scheme] => Self::inverse_surface(); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 7.0 }; + pub fn inverse_primary() -> DynamicColor { + DynamicColor::new( + "inverse_primary", + |scheme| &scheme.primary_palette, + |scheme| if scheme.is_dark { 40.0 } else { 80.0 }, + false, + Some(|_scheme| Self::inverse_surface()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 7.0, + }), + None, + ) } - define_key! { - background secondary => secondary_palette; - [tone, scheme] => if scheme.is_dark { 80.0 } else { 40.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 7.0 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::secondary_container(), Self::secondary(), 10.0, TonePolarity::Nearer, false); + pub fn secondary() -> DynamicColor { + DynamicColor::new( + "secondary", + |scheme| &scheme.secondary_palette, + |scheme| if scheme.is_dark { 80.0 } else { 40.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 7.0, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::secondary_container(), + Self::secondary(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) } - define_key! { - on_secondary => secondary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 10.0 } else { 100.0 } } else if scheme.is_dark { 20.0 } else { 100.0 }; - [background, _scheme] => Self::secondary(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn on_secondary() -> DynamicColor { + DynamicColor::new( + "on_secondary", + |scheme| &scheme.secondary_palette, + |scheme| { + if _is_monochrome(scheme) { + if scheme.is_dark { + 10.0 + } else { + 100.0 + } + } else if scheme.is_dark { + 20.0 + } else { + 100.0 + } + }, + false, + Some(|_scheme| Self::secondary()), + None, + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) } - define_key! { - background secondary_container => secondary_palette; - [tone, scheme] => { - let initial_tone = if scheme.is_dark { 30.0 } else { 90.0 }; + pub fn secondary_container() -> DynamicColor { + DynamicColor::new( + "secondary_container", + |scheme| &scheme.secondary_palette, + |scheme| { + let initial_tone = if scheme.is_dark { 30.0 } else { 90.0 }; + + if _is_monochrome(scheme) { + if scheme.is_dark { + 30.0 + } else { + 90.0 + } + } else if !_is_fidelity(scheme) { + initial_tone + } else { + Self::_find_desired_chroma_by_tone( + scheme.secondary_palette.hue(), + scheme.secondary_palette.chroma(), + initial_tone, + !scheme.is_dark, + ) + } + }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::secondary_container(), + Self::secondary(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) + } - if _is_monochrome(scheme) { if scheme.is_dark { 30.0 } else { 90.0 } } - else if !_is_fidelity(scheme) { initial_tone } - else { Self::_find_desired_chroma_by_tone(scheme.secondary_palette.hue(), scheme.secondary_palette.chroma(), initial_tone, !scheme.is_dark) } - }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::secondary_container(), Self::secondary(), 10.0, TonePolarity::Nearer, false); + pub fn on_secondary_container() -> DynamicColor { + DynamicColor::new( + "on_secondary_container", + |scheme| &scheme.secondary_palette, + |scheme| { + if _is_fidelity(scheme) { + DynamicColor::foreground_tone((Self::secondary_container().tone)(scheme), 4.5) + } else if scheme.is_dark { + 90.0 + } else if _is_monochrome(scheme) { + 30.0 + } else { + 10.0 + } + }, + false, + Some(|_scheme| Self::secondary_container()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) + } + + pub fn tertiary() -> DynamicColor { + DynamicColor::new( + "tertiary", + |scheme| &scheme.tertiary_palette, + |scheme| { + if _is_monochrome(scheme) { + if scheme.is_dark { + 90.0 + } else { + 25.0 + } + } else if scheme.is_dark { + 80.0 + } else { + 40.0 + } + }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 7.0, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::tertiary_container(), + Self::tertiary(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) + } + + pub fn on_tertiary() -> DynamicColor { + DynamicColor::new( + "on_tertiary", + |scheme| &scheme.tertiary_palette, + |scheme| { + if _is_monochrome(scheme) { + if scheme.is_dark { + 10.0 + } else { + 90.0 + } + } else if scheme.is_dark { + 20.0 + } else { + 100.0 + } + }, + false, + Some(|_scheme| Self::tertiary()), + None, + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) + } + + pub fn tertiary_container() -> DynamicColor { + DynamicColor::new( + "tertiary_container", + |scheme| &scheme.tertiary_palette, + |scheme| { + if _is_monochrome(scheme) { + if scheme.is_dark { + 60.0 + } else { + 49.0 + } + } else if !_is_fidelity(scheme) { + if scheme.is_dark { + 30.0 + } else { + 90.0 + } + } else { + fix_if_disliked( + scheme + .tertiary_palette + .get_hct(scheme.source_color_hct.get_tone()), + ) + .get_tone() + } + }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::tertiary_container(), + Self::tertiary(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) + } + + pub fn on_tertiary_container() -> DynamicColor { + DynamicColor::new( + "on_tertiary_container", + |scheme| &scheme.tertiary_palette, + |scheme| { + if _is_fidelity(scheme) { + DynamicColor::foreground_tone(Self::tertiary_container().get_tone(scheme), 4.5) + } else if _is_monochrome(scheme) { + if scheme.is_dark { + 0.0 + } else { + 100.0 + } + } else if scheme.is_dark { + 90.0 + } else { + 30.0 + } + }, + false, + Some(|_scheme| Self::tertiary_container()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) } - define_key! { - on_secondary_container => secondary_palette; - [tone, scheme] => if _is_fidelity(scheme) { - DynamicColor::foreground_tone((Self::secondary_container().tone)(scheme), 4.5) - } else if scheme.is_dark { - 90.0 - } else { - 10.0 - }; - [background, _scheme] => Self::secondary_container(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn error() -> DynamicColor { + DynamicColor::new( + "error", + |scheme| &scheme.error_palette, + |scheme| if scheme.is_dark { 80.0 } else { 40.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 7.0, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::error_container(), + Self::error(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) } - define_key! { - background tertiary => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 90.0 } else { 25.0 } } else if scheme.is_dark { 80.0 } else { 40.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 7.0 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::tertiary_container(), Self::tertiary(), 10.0, TonePolarity::Nearer, false); + pub fn on_error() -> DynamicColor { + DynamicColor::new( + "on_error", + |scheme| &scheme.error_palette, + |scheme| if scheme.is_dark { 20.0 } else { 100.0 }, + false, + Some(|_scheme| Self::error()), + None, + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) } - define_key! { - on_tertiary => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 10.0 } else { 90.0 } } else if scheme.is_dark { 20.0 } else { 100.0 }; - [background, _scheme] => Self::tertiary(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn error_container() -> DynamicColor { + DynamicColor::new( + "error_container", + |scheme| &scheme.error_palette, + |scheme| if scheme.is_dark { 30.0 } else { 90.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::error_container(), + Self::error(), + 10.0, + TonePolarity::Nearer, + false, + ) + }), + ) } - define_key! { - background tertiary_container => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 60.0 } else { 49.0 } } else if !_is_fidelity(scheme) { if scheme.is_dark { 30.0 } else { 90.0 } } else { fix_if_disliked(scheme.tertiary_palette.get_hct(scheme.source_color_hct.get_tone())).get_tone() }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::tertiary_container(), Self::tertiary(), 10.0, TonePolarity::Nearer, false); - } + pub fn on_error_container() -> DynamicColor { + DynamicColor::new( + "on_error_container", + |scheme| &scheme.error_palette, + |scheme| { + if scheme.is_dark { + 90.0 + } else if _is_monochrome(scheme) { + 10.0 + } else { + 30.0 + } + }, + false, + Some(|_scheme| Self::error_container()), + None, + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) + } - define_key! { - on_tertiary_container => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { if scheme.is_dark { 0.0 } else { 100.0 } } else if !_is_fidelity(scheme) { if scheme.is_dark { 90.0 } else { 10.0 } } else { DynamicColor::foreground_tone(Self::tertiary_container().get_tone(scheme), 4.5) }; - [background, _scheme] => Self::tertiary_container(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn primary_fixed() -> DynamicColor { + DynamicColor::new( + "primary_fixed", + |scheme| &scheme.primary_palette, + |scheme| if _is_monochrome(scheme) { 40.0 } else { 90.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::primary_fixed(), + Self::primary_fixed_dim(), + 10.0, + TonePolarity::Lighter, + true, + ) + }), + ) } - - define_key! { - background error => error_palette; - [tone, scheme] => if scheme.is_dark { 80.0 } else { 40.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 7.0 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::error_container(), Self::error(), 10.0, TonePolarity::Nearer, false); - } - - define_key! { - on_error => error_palette; - [tone, scheme] => if scheme.is_dark { 20.0 } else { 100.0 }; - [background, _scheme] => Self::error(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; - } - - define_key! { - background error_container => error_palette; - [tone, scheme] => if scheme.is_dark { 30.0 } else { 90.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::error_container(), Self::error(), 10.0, TonePolarity::Nearer, false); - } - - define_key! { - on_error_container => error_palette; - [tone, scheme] => if scheme.is_dark { 90.0 } else { 10.0 }; - [background, _scheme] => Self::error_container(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; - } - - define_key! { - background primary_fixed => primary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 40.0 } else { 90.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::primary_fixed(), Self::primary_fixed_dim(), 10.0, TonePolarity::Lighter, true); - } - - define_key! { - background primary_fixed_dim => primary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 30.0 } else { 80.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::primary_fixed(), Self::primary_fixed_dim(), 10.0, TonePolarity::Lighter, true); - } - - define_key! { - on_primary_fixed => primary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 100.0 } else { 10.0 }; - [background, _scheme] => Self::primary_fixed_dim(); - [second_background, _scheme] => Self::primary_fixed(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + + pub fn primary_fixed_dim() -> DynamicColor { + DynamicColor::new( + "primary_fixed_dim", + |scheme| &scheme.primary_palette, + |scheme| if _is_monochrome(scheme) { 30.0 } else { 80.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::primary_fixed(), + Self::primary_fixed_dim(), + 10.0, + TonePolarity::Lighter, + true, + ) + }), + ) } - define_key! { - on_primary_fixed_variant => primary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 90.0 } else { 30.0 }; - [background, _scheme] => Self::primary_fixed_dim(); - [second_background, _scheme] => Self::primary_fixed(); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 11.0 }; - } + pub fn on_primary_fixed() -> DynamicColor { + DynamicColor::new( + "on_primary_fixed", + |scheme| &scheme.primary_palette, + |scheme| if _is_monochrome(scheme) { 100.0 } else { 10.0 }, + false, + Some(|_scheme| Self::primary_fixed_dim()), + Some(|_scheme| Self::primary_fixed()), + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) + } - define_key! { - background secondary_fixed => secondary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 80.0 } else { 90.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::secondary_fixed(), Self::secondary_fixed_dim(), 10.0, TonePolarity::Lighter, true); + pub fn on_primary_fixed_variant() -> DynamicColor { + DynamicColor::new( + "on_primary_fixed_variant", + |scheme| &scheme.primary_palette, + |scheme| if _is_monochrome(scheme) { 90.0 } else { 30.0 }, + false, + Some(|_scheme| Self::primary_fixed_dim()), + Some(|_scheme| Self::primary_fixed()), + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) } - define_key! { - background secondary_fixed_dim => secondary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 70.0 } else { 80.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::secondary_fixed(), Self::secondary_fixed_dim(), 10.0, TonePolarity::Lighter, true); + pub fn secondary_fixed() -> DynamicColor { + DynamicColor::new( + "secondary_fixed", + |scheme| &scheme.secondary_palette, + |scheme| if _is_monochrome(scheme) { 80.0 } else { 90.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::secondary_fixed(), + Self::secondary_fixed_dim(), + 10.0, + TonePolarity::Lighter, + true, + ) + }), + ) } - define_key! { - on_secondary_fixed => secondary_palette; - [tone, _scheme] => 10.0; - [background, _scheme] => Self::secondary_fixed_dim(); - [second_background, _scheme] => Self::secondary_fixed(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; + pub fn secondary_fixed_dim() -> DynamicColor { + DynamicColor::new( + "secondary_fixed_dim", + |scheme| &scheme.secondary_palette, + |scheme| if _is_monochrome(scheme) { 70.0 } else { 80.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::secondary_fixed(), + Self::secondary_fixed_dim(), + 10.0, + TonePolarity::Lighter, + true, + ) + }), + ) } - define_key! { - on_secondary_fixed_variant => secondary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 25.0 } else { 30.0 }; - [background, _scheme] => Self::secondary_fixed_dim(); - [second_background, _scheme] => Self::secondary_fixed(); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 11.0 }; + pub fn on_secondary_fixed() -> DynamicColor { + DynamicColor::new( + "on_secondary_fixed", + |scheme| &scheme.secondary_palette, + |_scheme| 10.0, + false, + Some(|_scheme| Self::secondary_fixed_dim()), + Some(|_scheme| Self::secondary_fixed()), + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) } - define_key! { - background tertiary_fixed => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 40.0 } else { 90.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::tertiary_fixed(), Self::tertiary_fixed_dim(), 10.0, TonePolarity::Lighter, true); - } + pub fn on_secondary_fixed_variant() -> DynamicColor { + DynamicColor::new( + "on_secondary_fixed_variant", + |scheme| &scheme.secondary_palette, + |scheme| if _is_monochrome(scheme) { 25.0 } else { 30.0 }, + false, + Some(|_scheme| Self::secondary_fixed_dim()), + Some(|_scheme| Self::secondary_fixed()), + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) + } - define_key! { - background tertiary_fixed_dim => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 30.0 } else { 80.0 }; - [background, scheme] => Self::highest_surface(scheme); - [contrast_curve] => ContrastCurve { low: 1.0, normal: 1.0, medium: 3.0, high: 4.5 }; - [tone_delta_pair, _scheme] => ToneDeltaPair::new(Self::tertiary_fixed(), Self::tertiary_fixed_dim(), 10.0, TonePolarity::Lighter, true); - } + pub fn tertiary_fixed() -> DynamicColor { + DynamicColor::new( + "tertiary_fixed", + |scheme| &scheme.tertiary_palette, + |scheme| if _is_monochrome(scheme) { 40.0 } else { 90.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::tertiary_fixed(), + Self::tertiary_fixed_dim(), + 10.0, + TonePolarity::Lighter, + true, + ) + }), + ) + } - define_key! { - on_tertiary_fixed => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 100.0 } else { 10.0 }; - [background, _scheme] => Self::tertiary_fixed_dim(); - [second_background, _scheme] => Self::tertiary_fixed(); - [contrast_curve] => ContrastCurve { low: 4.5, normal: 7.0, medium: 11.0, high: 21.0 }; - } + pub fn tertiary_fixed_dim() -> DynamicColor { + DynamicColor::new( + "tertiary_fixed_dim", + |scheme| &scheme.tertiary_palette, + |scheme| if _is_monochrome(scheme) { 30.0 } else { 80.0 }, + true, + Some(Self::highest_surface), + None, + Some(ContrastCurve { + low: 1.0, + normal: 1.0, + medium: 3.0, + high: 4.5, + }), + Some(|_scheme| { + ToneDeltaPair::new( + Self::tertiary_fixed(), + Self::tertiary_fixed_dim(), + 10.0, + TonePolarity::Lighter, + true, + ) + }), + ) + } + + pub fn on_tertiary_fixed() -> DynamicColor { + DynamicColor::new( + "on_tertiary_fixed", + |scheme| &scheme.tertiary_palette, + |scheme| if _is_monochrome(scheme) { 100.0 } else { 10.0 }, + false, + Some(|_scheme| Self::tertiary_fixed_dim()), + Some(|_scheme| Self::tertiary_fixed()), + Some(ContrastCurve { + low: 4.5, + normal: 7.0, + medium: 11.0, + high: 21.0, + }), + None, + ) + } - define_key! { - on_tertiary_fixed_variant => tertiary_palette; - [tone, scheme] => if _is_monochrome(scheme) { 90.0 } else { 30.0 }; - [background, _scheme] => Self::tertiary_fixed_dim(); - [second_background, _scheme] => Self::tertiary_fixed(); - [contrast_curve] => ContrastCurve { low: 3.0, normal: 4.5, medium: 7.0, high: 11.0 }; + pub fn on_tertiary_fixed_variant() -> DynamicColor { + DynamicColor::new( + "on_tertiary_fixed_variant", + |scheme| &scheme.tertiary_palette, + |scheme| if _is_monochrome(scheme) { 90.0 } else { 30.0 }, + false, + Some(|_scheme| Self::tertiary_fixed_dim()), + Some(|_scheme| Self::tertiary_fixed()), + Some(ContrastCurve { + low: 3.0, + normal: 4.5, + medium: 7.0, + high: 11.0, + }), + None, + ) } fn _find_desired_chroma_by_tone( diff --git a/src/dynamic_color/mod.rs b/src/dynamic_color/mod.rs index 6603db3..9f6af0e 100644 --- a/src/dynamic_color/mod.rs +++ b/src/dynamic_color/mod.rs @@ -8,7 +8,6 @@ use crate::{ contrast::{darker, darker_unsafe, lighter, lighter_unsafe, ratio_of_tones}, hct::Hct, palette::TonalPalette, - Map, }; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, string::String, vec, vec::Vec}; @@ -58,7 +57,6 @@ pub struct DynamicColor { second_background: Option>>, contrast_curve: Option, tone_delta_pair: Option>>, - _hct_cache: Map, } impl DynamicColor { @@ -116,16 +114,23 @@ impl DynamicColor { second_background: second_background.map(Box::new), contrast_curve, tone_delta_pair: tone_delta_pair.map(Box::new), - _hct_cache: Map::default(), } } + pub fn from_palette>( + name: T, + palette: fn(&DynamicScheme) -> &TonalPalette, + tone: fn(&DynamicScheme) -> f64, + ) -> Self { + Self::new(name, palette, tone, false, None, None, None, None) + } + /// Return a Argb integer (i.e. a hex code). /// /// - Parameter scheme: Defines the conditions of the user interface, for example, /// whether or not it is dark mode or light mode, and what the desired contrast level is. /// - Returns: The color as an integer (Argb). - pub fn get_argb(&mut self, scheme: &DynamicScheme) -> Argb { + pub fn get_argb(&self, scheme: &DynamicScheme) -> Argb { self.get_hct(scheme).into() } @@ -134,20 +139,8 @@ impl DynamicColor { /// contrast level is. /// - Returns: a color, expressed in the HCT color space, that this /// `DynamicColor` is under the conditions in `scheme`. - pub fn get_hct(&mut self, scheme: &DynamicScheme) -> Hct { - if let Some(cached_answer) = self._hct_cache.get(scheme) { - return *cached_answer; - } - - let answer = (self.palette)(scheme).get_hct(self.get_tone(scheme)); - - if self._hct_cache.len() > 4 { - self._hct_cache.clear(); - } - - self._hct_cache.insert(scheme.clone(), answer); - - answer + pub fn get_hct(&self, scheme: &DynamicScheme) -> Hct { + (self.palette)(scheme).get_hct(self.get_tone(scheme)) } /// - Parameter scheme: Defines the conditions of the user interface, for example, diff --git a/src/hct/mod.rs b/src/hct/mod.rs index e675a52..5537f3f 100644 --- a/src/hct/mod.rs +++ b/src/hct/mod.rs @@ -1,4 +1,4 @@ -use crate::color::{lstar_from_y, Argb}; +use crate::{color::{lstar_from_y, Argb}, utils::FromRef}; #[cfg(all(not(feature = "std"), feature = "libm"))] #[allow(unused_imports)] use crate::utils::no_std::FloatExt; @@ -15,7 +15,7 @@ pub mod cam16; pub mod solver; pub mod viewing_conditions; -#[derive(Clone, Copy, Debug, PartialOrd)] +#[derive(Default, Clone, Copy, Debug, PartialOrd)] #[cfg_attr(feature = "serde", derive(Serialize))] pub struct Hct { _hue: f64, @@ -213,6 +213,12 @@ impl From for Argb { } } +impl FromRef for Argb { + fn from_ref(value: &Hct) -> Self { + value._argb + } +} + #[cfg(test)] mod tests { use super::{Cam16, Hct, ViewingConditions}; diff --git a/src/palette/core.rs b/src/palette/core.rs index 989d6f2..55bc911 100644 --- a/src/palette/core.rs +++ b/src/palette/core.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use super::TonalPalette; use crate::{color::Argb, hct::Cam16}; use core::fmt; @@ -6,6 +8,7 @@ use core::fmt; /// color scheme. 5 tonal palettes are generated, all except one use the same /// hue as the key color, and all vary in chroma. #[derive(Debug, Hash, PartialEq, Eq)] +#[deprecated = "Use `DynamicScheme` for color scheme generation. Use `CorePalettes` for core palettes container class"] pub struct CorePalette { pub primary: TonalPalette, pub secondary: TonalPalette, @@ -16,9 +19,6 @@ pub struct CorePalette { } impl CorePalette { - /// The number of generated tonal palettes. - pub const SIZE: usize = 5; - pub fn new( primary: TonalPalette, secondary: TonalPalette, @@ -40,11 +40,8 @@ impl CorePalette { /// Create a [`CorePalette`] from a source Argb color. pub fn of(argb: Argb) -> Self { let cam = Cam16::from(argb); + let (hue, chroma) = (cam.hue, cam.chroma); - Self::_of(cam.hue, cam.chroma) - } - - fn _of(hue: f64, chroma: f64) -> Self { Self::new( TonalPalette::of(hue, 48.0_f64.max(chroma)), TonalPalette::of(hue, 16.0), @@ -58,11 +55,8 @@ impl CorePalette { /// Create a content [`CorePalette`] from a source Argb color. pub fn content_of(argb: Argb) -> Self { let cam = Cam16::from(argb); + let (hue, chroma) = (cam.hue, cam.chroma); - Self::_content_of(cam.hue, cam.chroma) - } - - fn _content_of(hue: f64, chroma: f64) -> Self { Self::new( TonalPalette::of(hue, chroma), TonalPalette::of(hue, chroma / 3.0), @@ -78,21 +72,23 @@ impl fmt::Display for CorePalette { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "primary{} secondary{} tertiary{} neutral{} neutralVariant{}", + "primary{} secondary{} tertiary{} neutral{} neutral_variant{}", self.primary, self.secondary, self.tertiary, self.neutral, self.neutral_variant ) } } -// Returns a partition from a list. -// -// For example, given a list with 2 partitions of size 3. -// range = [1, 2, 3, 4, 5, 6]; -// -// range.getPartition(0, 3) // [1, 2, 3] -// range.getPartition(1, 3) // [4, 5, 6] -fn _get_partition(list: &[i32], partition_number: usize, partition_size: usize) -> &[i32] { - &list[(partition_number * partition_size)..((partition_number + 1) * partition_size)] +/// Comprises foundational palettes to build a color scheme. Generated from a +/// source color, these palettes will then be part of a [`DynamicScheme`] together +/// with appearance preferences. +/// +/// [`DynamicScheme`]: [crate::dynamic_color::dynamic_scheme::DynamicScheme] +pub struct CorePalettes { + pub primary: TonalPalette, + pub secondary: TonalPalette, + pub tertiary: TonalPalette, + pub neutral: TonalPalette, + pub neutral_variant: TonalPalette, } #[cfg(test)] diff --git a/src/palette/mod.rs b/src/palette/mod.rs index fdb3561..5858b3d 100644 --- a/src/palette/mod.rs +++ b/src/palette/mod.rs @@ -1,4 +1,5 @@ -pub use self::core::CorePalette; +#[allow(deprecated)] +pub use self::core::{CorePalette, CorePalettes}; pub use tonal::TonalPalette; mod core; diff --git a/src/palette/tonal.rs b/src/palette/tonal.rs index 23c9454..f299ce3 100644 --- a/src/palette/tonal.rs +++ b/src/palette/tonal.rs @@ -10,7 +10,10 @@ use crate::{ SchemeContent, SchemeExpressive, SchemeFidelity, SchemeFruitSalad, SchemeMonochrome, SchemeNeutral, SchemeRainbow, SchemeTonalSpot, SchemeVibrant, }, + Map, }; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; use core::{ cmp::Ordering, fmt, @@ -78,7 +81,7 @@ impl TonalPalette { /// Create a Tonal Palette from `hue` and `chroma`, which generates a key color. pub fn from_hue_and_chroma(hue: f64, chroma: f64) -> Self { - Self::new(hue, chroma, Self::create_key_color(hue, chroma)) + Self::new(hue, chroma, KeyColor::new(hue, chroma).create()) } /// Create colors using `hue` and `chroma`. @@ -86,48 +89,6 @@ impl TonalPalette { Self::from_hue_and_chroma(hue, chroma) } - /// Creates a key color from a `hue` and a `chroma`. - /// The key color is the first tone, starting from T50, matching the given `hue` and `chroma`. - pub fn create_key_color(hue: f64, chroma: f64) -> Hct { - let start_tone = 50.0; - let mut smallest_delta_hct = Hct::from(hue, chroma, start_tone); - let mut smallest_delta = (smallest_delta_hct.get_chroma() - chroma).abs(); - - // Starting from T50, check T+/-delta to see if they match the requested - // chroma. - // - // Starts from T50 because T50 has the most chroma available, on - // average. Thus it is most likely to have a direct answer and minimize - // iteration. - for delta in 1..=49 { - // Termination condition rounding instead of minimizing delta to avoid - // case where requested chroma is 16.51, and the closest chroma is 16.49. - // Error is minimized, but when rounded and displayed, requested chroma - // is 17, key color's chroma is 16. - if (chroma.round() - smallest_delta_hct.get_chroma().round()).abs() < f64::EPSILON { - return smallest_delta_hct; - } - - let hct_add = Hct::from(hue, chroma, start_tone + f64::from(delta)); - let hct_add_delta = (hct_add.get_chroma() - chroma).abs(); - - if hct_add_delta < smallest_delta { - smallest_delta = hct_add_delta; - smallest_delta_hct = hct_add; - } - - let hct_subtract = Hct::from(hue, chroma, start_tone - f64::from(delta)); - let hct_subtract_delta = (hct_subtract.get_chroma() - chroma).abs(); - - if hct_subtract_delta < smallest_delta { - smallest_delta = hct_subtract_delta; - smallest_delta_hct = hct_subtract; - } - } - - smallest_delta_hct - } - /// Returns the Argb representation of an HCT color. /// /// If the class was instantiated from `_hue` and `_chroma`, will return the @@ -171,10 +132,125 @@ impl fmt::Display for TonalPalette { } } +/// Key color is a color that represents the hue and chroma of a tonal palette +pub struct KeyColor { + hue: f64, + requested_chroma: f64, + /// Cache that maps tone to max chroma to avoid duplicated HCT calculation. + chroma_cache: Map, +} + +impl KeyColor { + const MAX_CHROMA_VALUE: f64 = 200.0; + + pub fn new(hue: f64, requested_chroma: f64) -> Self { + Self { + hue, + requested_chroma, + chroma_cache: Map::default(), + } + } + + /// Creates a key color from a [`hue`] and a [`chroma`]. + /// The key color is the first tone, starting from T50, matching the given hue + /// and chroma. + /// + /// Returns key color in [`Hct`]. + pub fn create(&mut self) -> Hct { + // Pivot around T50 because T50 has the most chroma available, on average. Thus it is most + // likely to have a direct answer. + let pivot_tone = 50; + let tone_step_size = 1; + // Epsilon to accept values slightly higher than the requested chroma. + let epsilon = 0.01; + + // Binary search to find the tone that can provide a chroma that is closest + // to the requested chroma. + let mut lower_tone = 0; + let mut upper_tone = 100; + + while lower_tone < upper_tone { + let mid_tone = (lower_tone + upper_tone) / 2; + let is_ascending = + self.max_chroma(mid_tone) < self.max_chroma(mid_tone + tone_step_size); + let sufficient_chroma = self.max_chroma(mid_tone) >= self.requested_chroma - epsilon; + + if sufficient_chroma { + // Either range [lowerTone, midTone] or [midTone, upperTone] has answer, so search in the + // range that is closer the pivot tone. + if (lower_tone - pivot_tone).abs() < (upper_tone - pivot_tone).abs() { + upper_tone = mid_tone; + } else if lower_tone == mid_tone { + return Hct::from(self.hue, self.requested_chroma, f64::from(lower_tone)); + } else { + lower_tone = mid_tone; + } + } else if is_ascending { + // As there is no sufficient chroma in the midTone, follow the direction to the chroma + // peak. + lower_tone = mid_tone + tone_step_size; + } else { + // Keep midTone for potential chroma peak. + upper_tone = mid_tone; + } + } + + Hct::from(self.hue, self.requested_chroma, f64::from(lower_tone)) + } + + fn max_chroma(&mut self, tone: i32) -> f64 { + if let Some(chroma) = self.chroma_cache.get(&tone) { + *chroma + } else { + let chroma = Hct::from(self.hue, Self::MAX_CHROMA_VALUE, f64::from(tone)).get_chroma(); + + self.chroma_cache.insert(tone, chroma); + + chroma + } + } +} + #[cfg(test)] mod tests { + use float_cmp::assert_approx_eq; + use crate::{color::Argb, hct::Hct, palette::TonalPalette}; + #[test] + fn test_exact_chroma_available() { + let palette = TonalPalette::of(50.0, 60.0); + let result = palette.key_color(); + + assert_approx_eq!(f64, result.get_hue(), 50.0, epsilon = 10.0); + assert_approx_eq!(f64, result.get_chroma(), 60.0, epsilon = 0.5); + + assert!(result.get_tone() > 0.0); + assert!(result.get_tone() < 100.0); + } + + #[test] + fn test_unusually_high_chroma() { + let palette = TonalPalette::of(149.0, 200.0); + let result = palette.key_color(); + + assert_approx_eq!(f64, result.get_hue(), 149.0, epsilon = 10.0); + + assert!(result.get_chroma() > 89.0); + assert!(result.get_tone() > 0.0); + assert!(result.get_tone() < 100.0); + } + + #[test] + fn test_unusually_low_chroma() { + let palette = TonalPalette::of(50.0, 3.0); + let result = palette.key_color(); + + assert_approx_eq!(f64, result.get_hue(), 50.0, epsilon = 10.0); + assert_approx_eq!(f64, result.get_chroma(), 3.0, epsilon = 0.5); + assert_approx_eq!(f64, result.get_tone(), 50.0, epsilon = 0.5); + } + #[test] fn test_of_tones_of_blue() { let hct: Hct = Argb::from_u32(0xff0000ff).into(); diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs index 48ad677..ae3eeb0 100644 --- a/src/scheme/mod.rs +++ b/src/scheme/mod.rs @@ -1,4 +1,4 @@ -#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_arguments, deprecated)] use crate::{color::Argb, dynamic_color::DynamicScheme, palette::CorePalette, Map}; #[cfg(not(feature = "std"))] use alloc::string::String; @@ -66,60 +66,7 @@ pub struct Scheme { impl fmt::Display for Scheme { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Scheme") - .field("primary", &self.primary) - .field("on_primary", &self.on_primary) - .field("primary_container", &self.primary_container) - .field("on_primary_container", &self.on_primary_container) - .field("inverse_primary", &self.inverse_primary) - .field("primary_fixed", &self.primary_fixed) - .field("primary_fixed_dim", &self.primary_fixed_dim) - .field("on_primary_fixed", &self.on_primary_fixed) - .field("on_primary_fixed_variant", &self.on_primary_fixed_variant) - .field("secondary", &self.secondary) - .field("on_secondary", &self.on_secondary) - .field("secondary_container", &self.secondary_container) - .field("on_secondary_container", &self.on_secondary_container) - .field("secondary_fixed", &self.secondary_fixed) - .field("secondary_fixed_dim", &self.secondary_fixed_dim) - .field("on_secondary_fixed", &self.on_secondary_fixed) - .field( - "on_secondary_fixed_variant", - &self.on_secondary_fixed_variant, - ) - .field("tertiary", &self.tertiary) - .field("on_tertiary", &self.on_tertiary) - .field("tertiary_container", &self.tertiary_container) - .field("on_tertiary_container", &self.on_tertiary_container) - .field("tertiary_fixed", &self.tertiary_fixed) - .field("tertiary_fixed_dim", &self.tertiary_fixed_dim) - .field("on_tertiary_fixed", &self.on_tertiary_fixed) - .field("on_tertiary_fixed_variant", &self.on_tertiary_fixed_variant) - .field("error", &self.error) - .field("on_error", &self.on_error) - .field("error_container", &self.error_container) - .field("on_error_container", &self.on_error_container) - .field("surface_dim", &self.surface_dim) - .field("surface", &self.surface) - .field("surface_tint", &self.surface_tint) - .field("surface_bright", &self.surface_bright) - .field("surface_container_lowest", &self.surface_container_lowest) - .field("surface_container_low", &self.surface_container_low) - .field("surface_container", &self.surface_container) - .field("surface_container_high", &self.surface_container_high) - .field("surface_container_highest", &self.surface_container_highest) - .field("on_surface", &self.on_surface) - .field("on_surface_variant", &self.on_surface_variant) - .field("outline", &self.outline) - .field("outline_variant", &self.outline_variant) - .field("inverse_surface", &self.inverse_surface) - .field("inverse_on_surface", &self.inverse_on_surface) - .field("surface_variant", &self.surface_variant) - .field("background", &self.background) - .field("on_background", &self.on_background) - .field("shadow", &self.shadow) - .field("scrim", &self.scrim) - .finish() + fmt::Debug::fmt(&self, f) } } @@ -497,44 +444,9 @@ impl SchemeFromPalette { } } -impl PartialEq for SchemeFromPalette { - fn eq(&self, other: &Scheme) -> bool { - self.primary == other.primary - && self.on_primary == other.on_primary - && self.primary_container == other.primary_container - && self.on_primary_container == other.on_primary_container - && self.secondary == other.secondary - && self.on_secondary == other.on_secondary - && self.secondary_container == other.secondary_container - && self.on_secondary_container == other.on_secondary_container - && self.tertiary == other.tertiary - && self.on_tertiary == other.on_tertiary - && self.tertiary_container == other.tertiary_container - && self.on_tertiary_container == other.on_tertiary_container - && self.error == other.error - && self.on_error == other.on_error - && self.error_container == other.error_container - && self.on_error_container == other.on_error_container - && self.surface == other.surface - && self.on_surface == other.on_surface - && self.surface_variant == other.surface_variant - && self.on_surface_variant == other.on_surface_variant - && self.outline == other.outline - && self.outline_variant == other.outline_variant - && self.background == other.background - && self.on_background == other.on_background - && self.shadow == other.shadow - && self.scrim == other.scrim - && self.inverse_surface == other.inverse_surface - && self.inverse_on_surface == other.inverse_on_surface - && self.inverse_primary == other.inverse_primary - } -} - #[cfg(test)] mod tests { - use crate::color::Argb; - use crate::scheme::SchemeFromPalette; + use crate::{color::Argb, scheme::SchemeFromPalette}; use float_cmp::assert_approx_eq; #[test] diff --git a/src/scheme/variant/content.rs b/src/scheme/variant/content.rs index 36ef083..6b38a01 100644 --- a/src/scheme/variant/content.rs +++ b/src/scheme/variant/content.rs @@ -14,8 +14,7 @@ impl SchemeContent { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - Some(source_color_hct), + source_color_hct, Variant::Content, is_dark, contrast_level, @@ -51,7 +50,7 @@ impl SchemeContent { ), Palette::NeutralVariant => TonalPalette::of( source_color_hct.get_hue(), - (source_color_hct.get_chroma() / 8.0) + 4.0, + source_color_hct.get_chroma() / 8.0 + 4.0, ), } } @@ -60,38 +59,238 @@ impl SchemeContent { #[cfg(test)] mod tests { use crate::color::Argb; - use crate::dynamic_color::MaterialDynamicColors; - use super::SchemeContent; + #[test] + fn test_key_colors() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff080cff) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff656dd3) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff81009f) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff767684) + ); + // assert_eq!( + // scheme.neutral_variant_palette_key_color(), + // Argb::from_u32(0xff757589) + // ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff5660ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff0001bb)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff00019f)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd5d6ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000ff)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000f6)); + } + + #[test] + fn test_light_theme_min_contrast_tertiary_container() { + let scheme = + SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xfffac9ff)); + } + + #[test] + fn test_light_theme_standard_contrast_tertiary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff81009f)); + } + + #[test] + fn test_light_theme_max_contrast_tertiary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff7d009a)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff5e68ff)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffb3b7ff)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff7c84ff)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffbec2ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xfff0eeff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0001c9)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffbabdff)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff6b75ff)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffb3b7ff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff00003d)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffc254de)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xfff09fff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff1a0022)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12121d)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12121d)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = SchemeContent::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12121d)); + } + #[test] fn test_light_theme_min_contrast_objectionabe_tertiary_container_lightens() { let scheme = SchemeContent::new(Argb::from_u32(0xff850096).into(), false, Some(-1.0)).scheme; - assert_eq!( - MaterialDynamicColors::tertiary_container().get_argb(&scheme), - Argb::from_u32(0xffffccd7) - ); + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffffccd7)); } #[test] fn test_light_theme_standard_contrast_objectionabe_tertiary_container_lightens() { let scheme = SchemeContent::new(Argb::from_u32(0xff850096).into(), false, Some(0.0)).scheme; - assert_eq!( - MaterialDynamicColors::tertiary_container().get_argb(&scheme), - Argb::from_u32(0xff980249) - ); + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff980249)); } #[test] fn test_light_theme_max_contrast_objectionabe_tertiary_container_darkens() { let scheme = SchemeContent::new(Argb::from_u32(0xff850096).into(), false, Some(1.0)).scheme; - assert_eq!( - MaterialDynamicColors::tertiary_container().get_argb(&scheme), - Argb::from_u32(0xff930046) - ); + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff930046)); } } diff --git a/src/scheme/variant/expressive.rs b/src/scheme/variant/expressive.rs index a0cd3e5..b9d388f 100644 --- a/src/scheme/variant/expressive.rs +++ b/src/scheme/variant/expressive.rs @@ -13,23 +13,20 @@ pub struct SchemeExpressive { impl SchemeExpressive { /// Hues used at breakpoints such that designers can specify a hue rotation /// that occurs at a given break point. - pub const HUES: [f64; 9] = [0.0, 21.0, 51.0, 121.0, 151.0, 191.0, 271.0, 321.0, 360.0]; + const HUES: [f64; 9] = [0.0, 21.0, 51.0, 121.0, 151.0, 191.0, 271.0, 321.0, 360.0]; /// Hue rotations of the Secondary [`TonalPalette`], corresponding to the /// breakpoints in `hues`. - pub const SECONDARY_ROTATIONS: [f64; 9] = - [45.0, 95.0, 45.0, 20.0, 45.0, 90.0, 45.0, 45.0, 45.0]; + const SECONDARY_ROTATIONS: [f64; 9] = [45.0, 95.0, 45.0, 20.0, 45.0, 90.0, 45.0, 45.0, 45.0]; /// Hue rotations of the Tertiary [`TonalPalette`], corresponding to the /// breakpoints in `hues`. - pub const TERTIARY_ROTATIONS: [f64; 9] = - [120.0, 120.0, 20.0, 45.0, 20.0, 15.0, 20.0, 120.0, 120.0]; + const TERTIARY_ROTATIONS: [f64; 9] = [120.0, 120.0, 20.0, 45.0, 20.0, 15.0, 20.0, 120.0, 120.0]; pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::Expressive, is_dark, contrast_level, @@ -71,3 +68,204 @@ impl SchemeExpressive { } } } + +#[cfg(test)] +mod tests { + use super::SchemeExpressive; + use crate::color::Argb; + + #[test] + fn test_key_colors() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff35855f) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff8c6d8c) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff806ea1) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff79757f) + ); + // assert_eq!( + // scheme.neutral_variant_palette_key_color(), + // Argb::from_u32(0xff7a7585) + // ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff32835d)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff146c48)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff00341f)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff99eabd)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffa2f4c6)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff005436)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff388862)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff005234)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffdf7ff)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffdf7ff)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffdf7ff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff51a078)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff87d7ab)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffbbffd7)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff00432a)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff005234)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff83d3a8)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff43936c)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffa2f4c6)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff000e06)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff14121a)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff14121a)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = + SchemeExpressive::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff14121a)); + } +} diff --git a/src/scheme/variant/fidelity.rs b/src/scheme/variant/fidelity.rs index 1138994..8383e1f 100644 --- a/src/scheme/variant/fidelity.rs +++ b/src/scheme/variant/fidelity.rs @@ -14,8 +14,7 @@ impl SchemeFidelity { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::Fidelity, is_dark, contrast_level, @@ -53,3 +52,257 @@ impl SchemeFidelity { } } } + +#[cfg(test)] +mod tests { + use super::SchemeFidelity; + use crate::color::Argb; + + #[test] + fn test_key_colors() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff080cff) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff656dd3) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff9d0002) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff767684) + ); + // assert_eq!( + // scheme.neutral_variant_palette_key_color(), + // Argb::from_u32(0xff757589) + // ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff5660ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff0001bb)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff00019f)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd5d6ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000ff)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000f6)); + } + + #[test] + fn test_light_theme_min_contrast_tertiary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffffcdc6)); + } + + #[test] + fn test_light_theme_standard_contrast_tertiary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff9d0002)); + } + + #[test] + fn test_light_theme_max_contrast_tertiary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff980002)); + } + + #[test] + fn test_light_theme_min_contrast_objectionable_tertiary_container_lightens() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff850096).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffebd982)); + } + + #[test] + fn test_light_theme_standard_contrast_objectionable_tertiary_container_lightens() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff850096).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffbcac5a)); + } + + #[test] + fn test_light_theme_max_contrast_objectionable_tertiary_container_darkens() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff850096).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff544900)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff5e68ff)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffb3b7ff)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff7c84ff)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffbec2ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xfff0eeff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0001c9)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffbabdff)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff6b75ff)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffb3b7ff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff00003d)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffef4635)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffffa598)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff220000)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = + SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12121d)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12121d)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = SchemeFidelity::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12121d)); + } +} diff --git a/src/scheme/variant/fruit_salad.rs b/src/scheme/variant/fruit_salad.rs index 1ebdb55..17b77db 100644 --- a/src/scheme/variant/fruit_salad.rs +++ b/src/scheme/variant/fruit_salad.rs @@ -13,8 +13,7 @@ impl SchemeFruitSalad { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::FruitSalad, is_dark, contrast_level, @@ -45,3 +44,312 @@ impl SchemeFruitSalad { } } } + +#[cfg(test)] +mod tests { + use super::SchemeFruitSalad; + use crate::color::Argb; + + #[test] + fn test_key_colors() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff0393c3) + ); + + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff3a7e9e) + ); + + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff6e72ac) + ); + + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff777682) + ); + + // assert_eq!( + // scheme.neutral_variant_palette_key_color(), + // Argb::from_u32(0xff75758b) + // ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + + assert_eq!(scheme.primary(), Argb::from_u32(0xff007ea7)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.primary(), Argb::from_u32(0xff006688)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + + assert_eq!(scheme.primary(), Argb::from_u32(0xff003042)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffaae0ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffc2e8ff)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff004f6b)); + } + + #[test] + fn test_light_theme_min_contrast_tertiary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffd5d6ff)); + } + + #[test] + fn test_light_theme_standard_contrast_tertiary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffe0e0ff)); + } + + #[test] + fn test_light_theme_max_contrast_tertiary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff40447b)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff0083ae)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff004d67)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_standard_contrast_secondary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.secondary(), Argb::from_u32(0xff196584)); + } + + #[test] + fn test_light_theme_standard_contrast_secondary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!(scheme.secondary_container(), Argb::from_u32(0xffc2e8ff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + + assert_eq!(scheme.primary(), Argb::from_u32(0xff1e9bcb)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.primary(), Argb::from_u32(0xff76d1ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + + assert_eq!(scheme.primary(), Argb::from_u32(0xffe0f3ff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff003f56)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.primary_container(), Argb::from_u32(0xFF004D67)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff68ceff)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff008ebc)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffc2e8ff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff000d15)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff7b7fbb)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffe0e0ff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff00003c)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + + assert_eq!(scheme.surface(), Argb::from_u32(0xff12131c)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.surface(), Argb::from_u32(0xff12131c)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + + assert_eq!(scheme.surface(), Argb::from_u32(0xff12131c)); + } + + #[test] + fn test_dark_theme_standard_contrast_secondary() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.secondary(), Argb::from_u32(0xff8ecff2)); + } + + #[test] + fn test_dark_theme_standard_contrast_secondary_container() { + let scheme = + SchemeFruitSalad::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_eq!(scheme.secondary_container(), Argb::from_u32(0xff004d67)); + } +} diff --git a/src/scheme/variant/monochrome.rs b/src/scheme/variant/monochrome.rs index 6a2f327..c9623c3 100644 --- a/src/scheme/variant/monochrome.rs +++ b/src/scheme/variant/monochrome.rs @@ -12,8 +12,7 @@ impl SchemeMonochrome { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::Monochrome, is_dark, contrast_level, @@ -38,3 +37,427 @@ impl SchemeMonochrome { } } } + +#[cfg(test)] +mod tests { + use super::SchemeMonochrome; + use crate::{color::Argb, dynamic_color::MaterialDynamicColors}; + use float_cmp::assert_approx_eq; + + #[test] + fn test_key_colors() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff777777) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff777777) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff777777) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff777777) + ); + assert_eq!( + scheme.neutral_variant_palette_key_color(), + Argb::from_u32(0xff777777) + ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff747474)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff000000)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff000000)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd9d9d9)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff3b3b3b)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff3b3b3b)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff7a7a7a)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfff9f9f9)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfff9f9f9)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfff9f9f9)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff919191)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff3a3a3a)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd4d4d4)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd4d4d4)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff848484)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff000000)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff000000)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff848484)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff000000)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff000000)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131313)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131313)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131313)); + } + + #[test] + fn test_dark_theme_monochrome_spec() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + + assert_approx_eq!( + f64, + MaterialDynamicColors::primary().get_hct(&scheme).get_tone(), + 100.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_primary() + .get_hct(&scheme) + .get_tone(), + 10.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::primary_container() + .get_hct(&scheme) + .get_tone(), + 85.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_primary_container() + .get_hct(&scheme) + .get_tone(), + 0.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::secondary() + .get_hct(&scheme) + .get_tone(), + 80.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_secondary() + .get_hct(&scheme) + .get_tone(), + 10.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::secondary_container() + .get_hct(&scheme) + .get_tone(), + 30.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_secondary_container() + .get_hct(&scheme) + .get_tone(), + 90.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::tertiary() + .get_hct(&scheme) + .get_tone(), + 90.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_tertiary() + .get_hct(&scheme) + .get_tone(), + 10.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::tertiary_container() + .get_hct(&scheme) + .get_tone(), + 60.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_tertiary_container() + .get_hct(&scheme) + .get_tone(), + 0.0, + epsilon = 0.3 + ); + } + + fn _test_light_theme_monochrome_spec() { + let scheme = + SchemeMonochrome::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_approx_eq!( + f64, + MaterialDynamicColors::primary().get_hct(&scheme).get_tone(), + 0.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_primary() + .get_hct(&scheme) + .get_tone(), + 90.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::primary_container() + .get_hct(&scheme) + .get_tone(), + 25.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_primary_container() + .get_hct(&scheme) + .get_tone(), + 100.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::secondary() + .get_hct(&scheme) + .get_tone(), + 40.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_secondary() + .get_hct(&scheme) + .get_tone(), + 100.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::secondary_container() + .get_hct(&scheme) + .get_tone(), + 85.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_secondary_container() + .get_hct(&scheme) + .get_tone(), + 10.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::tertiary() + .get_hct(&scheme) + .get_tone(), + 25.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_tertiary() + .get_hct(&scheme) + .get_tone(), + 90.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::tertiary_container() + .get_hct(&scheme) + .get_tone(), + 49.0, + epsilon = 0.3 + ); + assert_approx_eq!( + f64, + MaterialDynamicColors::on_tertiary_container() + .get_hct(&scheme) + .get_tone(), + 100.0, + epsilon = 0.3 + ); + } +} diff --git a/src/scheme/variant/neutral.rs b/src/scheme/variant/neutral.rs index 2a751ba..f87fbde 100644 --- a/src/scheme/variant/neutral.rs +++ b/src/scheme/variant/neutral.rs @@ -12,8 +12,7 @@ impl SchemeNeutral { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::Neutral, is_dark, contrast_level, @@ -39,3 +38,201 @@ impl SchemeNeutral { } } } + +#[cfg(test)] +mod tests { + use super::SchemeNeutral; + use crate::color::Argb; + + #[test] + fn test_key_colors() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff767685) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff777680) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff75758b) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff787678) + ); + assert_eq!( + scheme.neutral_variant_palette_key_color(), + Argb::from_u32(0xff787678) + ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff737383)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff5d5d6c)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff2b2b38)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd9d7e9)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffe2e1f3)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff484856)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff797888)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff454654)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffcf8fa)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffcf8fa)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffcf8fa)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff908f9f)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffc6c5d6)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xfff0eeff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff393947)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff454654)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffc2c1d2)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff838393)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffe2e1f3)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff090a16)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff828299)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffe1e0f9)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff080a1b)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131315)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131315)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = SchemeNeutral::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131315)); + } +} diff --git a/src/scheme/variant/rainbow.rs b/src/scheme/variant/rainbow.rs index 41e51c6..9080031 100644 --- a/src/scheme/variant/rainbow.rs +++ b/src/scheme/variant/rainbow.rs @@ -13,8 +13,7 @@ impl SchemeRainbow { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::Rainbow, is_dark, contrast_level, @@ -43,3 +42,244 @@ impl SchemeRainbow { } } } + +#[cfg(test)] +mod tests { + use super::SchemeRainbow; + use crate::color::Argb; + + #[test] + fn test_key_colors() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff696fc4) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff75758b) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff936b84) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff777777) + ); + assert_eq!( + scheme.neutral_variant_palette_key_color(), + Argb::from_u32(0xff777777) + ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff676dc1)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff5056a9)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff1b2074)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd5d6ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffe0e0ff)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff3a4092)); + } + + #[test] + fn test_light_theme_min_contrast_tertiary_container() { + let scheme = + SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xfffbcbe7)); + } + + #[test] + fn test_light_theme_standard_contrast_tertiary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xffffd8ee)); + } + + #[test] + fn test_light_theme_max_contrast_tertiary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.tertiary_container(), Argb::from_u32(0xff613e55)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff6c72c7)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff383e8f)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfff9f9f9)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfff9f9f9)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfff9f9f9)); + } + + #[test] + fn test_light_theme_standard_contrast_secondary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.secondary(), Argb::from_u32(0xff5c5d72)); + } + + #[test] + fn test_light_theme_standard_contrast_secondary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.secondary_container(), Argb::from_u32(0xffe1e0f9)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff8389e0)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffbec2ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xfff0eeff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff2a3082)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff383e8f)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffbabdff)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff767cd2)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffe0e0ff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff00003d)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffa17891)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xffffd8ee)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff1b0315)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131313)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131313)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff131313)); + } + + #[test] + fn test_dark_theme_standard_contrast_secondary() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.secondary(), Argb::from_u32(0xffc5c4dd)); + } + + #[test] + fn test_dark_theme_standard_contrast_secondary_container() { + let scheme = SchemeRainbow::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.secondary_container(), Argb::from_u32(0xff444559)); + } +} diff --git a/src/scheme/variant/tonal_spot.rs b/src/scheme/variant/tonal_spot.rs index f788ca4..d58f071 100644 --- a/src/scheme/variant/tonal_spot.rs +++ b/src/scheme/variant/tonal_spot.rs @@ -13,8 +13,7 @@ impl SchemeTonalSpot { pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::TonalSpot, is_dark, contrast_level, diff --git a/src/scheme/variant/vibrant.rs b/src/scheme/variant/vibrant.rs index f58af0c..2372b67 100644 --- a/src/scheme/variant/vibrant.rs +++ b/src/scheme/variant/vibrant.rs @@ -12,22 +12,20 @@ pub struct SchemeVibrant { impl SchemeVibrant { /// Hues used at breakpoints such that designers can specify a hue rotation /// that occurs at a given break point. - pub const HUES: [f64; 9] = [0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0]; + const HUES: [f64; 9] = [0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0]; /// Hue rotations of the Secondary [`TonalPalette`], corresponding to the /// breakpoints in `hues`. - pub const SECONDARY_ROTATIONS: [f64; 9] = - [18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0]; + const SECONDARY_ROTATIONS: [f64; 9] = [18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0]; /// Hue rotations of the Tertiary [`TonalPalette`], corresponding to the /// breakpoints in `hues`. - pub const TERTIARY_ROTATIONS: [f64; 9] = [35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0]; + const TERTIARY_ROTATIONS: [f64; 9] = [35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0]; pub fn new(source_color_hct: Hct, is_dark: bool, contrast_level: Option) -> Self { Self { scheme: DynamicScheme::new( - source_color_hct.into(), - None, + source_color_hct, Variant::Vibrant, is_dark, contrast_level, @@ -67,3 +65,201 @@ impl SchemeVibrant { } } } + +#[cfg(test)] +mod tests { + use crate::color::Argb; + use super::SchemeVibrant; + + #[test] + fn test_key_colors() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + + assert_eq!( + scheme.primary_palette_key_color(), + Argb::from_u32(0xff080cff) + ); + assert_eq!( + scheme.secondary_palette_key_color(), + Argb::from_u32(0xff7b7296) + ); + assert_eq!( + scheme.tertiary_palette_key_color(), + Argb::from_u32(0xff886c9d) + ); + assert_eq!( + scheme.neutral_palette_key_color(), + Argb::from_u32(0xff777682) + ); + // assert_eq!( + // scheme.neutral_variant_palette_key_color(), + // Argb::from_u32(0xff767685) + // ); + } + + #[test] + fn test_light_theme_min_contrast_primary() { + let scheme = + SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff5660ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff343dff)); + } + + #[test] + fn test_light_theme_max_contrast_primary() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff00019f)); + } + + #[test] + fn test_light_theme_min_contrast_primary_container() { + let scheme = + SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffd5d6ff)); + } + + #[test] + fn test_light_theme_standard_contrast_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffe0e0ff)); + } + + #[test] + fn test_light_theme_max_contrast_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000f6)); + } + + #[test] + fn test_light_theme_min_contrast_on_primary_container() { + let scheme = + SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff5e68ff)); + } + + #[test] + fn test_light_theme_standard_contrast_on_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff0000ef)); + } + + #[test] + fn test_light_theme_max_contrast_on_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffffffff)); + } + + #[test] + fn test_light_theme_min_contrast_surface() { + let scheme = + SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_standard_contrast_surface() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_light_theme_max_contrast_surface() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), false, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xfffbf8ff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xff7c84ff)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xffbec2ff)); + } + + #[test] + fn test_dark_theme_max_contrast_primary() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary(), Argb::from_u32(0xfff0eeff)); + } + + #[test] + fn test_dark_theme_min_contrast_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0001c9)); + } + + #[test] + fn test_dark_theme_standard_contrast_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xff0000ef)); + } + + #[test] + fn test_dark_theme_max_contrast_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.primary_container(), Argb::from_u32(0xffbabdff)); + } + + #[test] + fn test_dark_theme_min_contrast_on_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff6b75ff)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xffe0e0ff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_primary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_primary_container(), Argb::from_u32(0xff00003d)); + } + + #[test] + fn test_dark_theme_min_contrast_on_tertiary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff9679ab)); + } + + #[test] + fn test_dark_theme_standard_contrast_on_tertiary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xfff2daff)); + } + + #[test] + fn test_dark_theme_max_contrast_on_tertiary_container() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.on_tertiary_container(), Argb::from_u32(0xff16002a)); + } + + #[test] + fn test_dark_theme_min_contrast_surface() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(-1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12131c)); + } + + #[test] + fn test_dark_theme_standard_contrast_surface() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(0.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12131c)); + } + + #[test] + fn test_dark_theme_max_contrast_surface() { + let scheme = SchemeVibrant::new(Argb::from_u32(0xff0000ff).into(), true, Some(1.0)).scheme; + assert_eq!(scheme.surface(), Argb::from_u32(0xff12131c)); + } +} diff --git a/src/temperature.rs b/src/temperature.rs index 52cd6c2..4de4896 100644 --- a/src/temperature.rs +++ b/src/temperature.rs @@ -4,7 +4,7 @@ use crate::utils::no_std::FloatExt; use crate::{ color::{Argb, Lab}, hct::Hct, - utils::math::sanitize_degrees_double, + utils::{math::sanitize_degrees_double, FromRef}, Map, }; #[cfg(not(feature = "std"))] @@ -20,10 +20,18 @@ use std::{vec, vec::Vec}; pub struct TemperatureCache { input: Hct, - _hcts_by_temp: Vec, - _hcts_by_hue: Vec, - _temps_by_hct: Map, - _input_relative_temperature: f64, + /// HCTs for all hues, with the same chroma/tone as the input. + /// Sorted from coldest first to warmest last. + hcts_by_temp: [Hct; 362], + /// HCTs for all hues, with the same chroma/tone as the input. + /// Sorted ascending, hue 0 to 360. + hcts_by_hue: [Hct; 362], + /// A Map with keys of HCTs in `hcts_by_temp`, values of raw temperature. + temps_by_hct: Map, + /// Relative temperature of the input color. See [`relative_temperature`]. + /// + /// [`relative_temperature`]: Self::relative_temperature + input_relative_temperature: f64, _complement: Option, } @@ -31,30 +39,65 @@ impl TemperatureCache { /// # Panics /// /// Will panic if there is no warmest HCT - pub fn warmest(&mut self) -> Hct { - let hcts = self.hcts_by_temp(); - - return *hcts.last().unwrap(); + pub const fn warmest(&self) -> &Hct { + &self.hcts_by_temp[361] } /// # Panics /// /// Will panic if there is no coldest HCT - pub fn coldest(&mut self) -> Hct { - let hcts = self.hcts_by_temp(); - - return *hcts.first().unwrap(); + pub const fn coldest(&self) -> &Hct { + &self.hcts_by_temp[0] } pub fn new(input: Hct) -> Self { - Self { + let chroma = input.get_chroma(); + let tone = input.get_tone(); + + let hcts_by_hue = core::array::from_fn(|index| { + if index == 361 { + input + } else { + Hct::from(f64::from(index as i32), chroma, tone) + } + }); + + let temps_by_hct = hcts_by_hue + .iter() + .map(|e| (*e, Self::raw_temperature(e))) + .collect(); + + let mut hcts_by_temp = hcts_by_hue; + + hcts_by_temp.sort_by(|a, b| Self::sort_by_temp(&temps_by_hct, a, b)); + + let mut cache = Self { input, - _hcts_by_temp: vec![], - _hcts_by_hue: vec![], - _temps_by_hct: Map::default(), - _input_relative_temperature: -1.0, + hcts_by_temp, + hcts_by_hue, + temps_by_hct, + input_relative_temperature: -1.0, _complement: None, - } + }; + + cache.input_relative_temperature = { + let coldest = cache.coldest(); + let warmest = cache.warmest(); + let input = &cache.input; + + let coldest_temp = cache.temps_by_hct[coldest]; + + let range = cache.temps_by_hct[warmest] - coldest_temp; + let difference_from_coldest = cache.temps_by_hct[input] - coldest_temp; + + if range == 0.0 { + 0.5 + } else { + difference_from_coldest / range + } + }; + + cache } /// A set of colors with differing hues, equidistant in temperature. @@ -68,20 +111,21 @@ impl TemperatureCache { /// /// - `count`: The number of colors to return, includes the input color. /// - `divisions`: The number of divisions on the color wheel. - pub fn analogous(&mut self, count: Option, divisions: Option) -> Vec { + pub fn analogous(&self, count: Option, divisions: Option) -> Vec { let count = count.unwrap_or(5); let divisions = divisions.unwrap_or(12); let start_hue = self.input.get_hue().round() as i32; - let start_hct = &self.hcts_by_hue()[start_hue as usize]; - let mut last_temp = self.relative_temperature(start_hct); - let mut all_colors = vec![*start_hct]; + + let start_hct = self.hcts_by_hue[start_hue as usize]; + let mut last_temp = self.relative_temperature(&start_hct); + let mut all_colors = vec![start_hct]; let mut absolute_total_temp_delta = 0.0; for i in 0..360 { let hue = sanitize_degrees_double((start_hue + i).into()); - let hct = &self.hcts_by_hue()[hue as usize]; - let temp = self.relative_temperature(hct); + let hct = self.hcts_by_hue[hue as usize]; + let temp = self.relative_temperature(&hct); let temp_delta = (temp - last_temp).abs(); last_temp = temp; @@ -93,12 +137,12 @@ impl TemperatureCache { let mut total_temp_delta = 0.0; - last_temp = self.relative_temperature(start_hct); + last_temp = self.relative_temperature(&start_hct); while all_colors.len() < divisions as usize { let hue = sanitize_degrees_double((start_hue + hue_addend).into()); - let hct = &self.hcts_by_hue()[hue as usize]; - let temp = self.relative_temperature(hct); + let hct = self.hcts_by_hue[hue as usize]; + let temp = self.relative_temperature(&hct); let temp_delta = (temp - last_temp).abs(); total_temp_delta += temp_delta; @@ -117,7 +161,7 @@ impl TemperatureCache { // colors at T100/T0. Therefore, they should just be added to the array // as answers. while index_satisfied && all_colors.len() < divisions as usize { - all_colors.push(*hct); + all_colors.push(hct); let desired_total_temp_delta_for_index = (all_colors.len() + index_addend) as f64 * temp_step; @@ -131,7 +175,7 @@ impl TemperatureCache { if hue_addend > 360 { while all_colors.len() < divisions as usize { - all_colors.push(*hct); + all_colors.push(hct); } break; @@ -187,39 +231,41 @@ impl TemperatureCache { /// /// Will panic if there is no coldest or warmest HCT pub fn complement(&mut self) -> Hct { - if let Some(_complement) = &self._complement { - return *_complement; + if let Some(complement) = self._complement { + return complement; } let coldest_hct = self.coldest(); let warmest_hct = self.warmest(); - let temps_by_hct = self.temps_by_hct(); - let coldest_hue = coldest_hct.get_hue(); - let coldest_temp = temps_by_hct[&coldest_hct]; + let coldest_temp = self.temps_by_hct[coldest_hct]; let warmest_hue = warmest_hct.get_hue(); - let warmest_temp = temps_by_hct[&warmest_hct]; + let warmest_temp = self.temps_by_hct[warmest_hct]; let range = warmest_temp - coldest_temp; let start_hue_is_coldest_to_warmest = Self::is_between(self.input.get_hue(), coldest_hue, warmest_hue); + let start_hue = if start_hue_is_coldest_to_warmest { warmest_hue } else { coldest_hue }; + let end_hue = if start_hue_is_coldest_to_warmest { coldest_hue } else { warmest_hue }; + let direction_of_rotation = 1.0_f64; let mut smallest_error = 1000.0; - let mut answer = self.hcts_by_hue()[self.input.get_hue().round() as usize]; + let hue = self.input.get_hue().round(); + let mut answer = self.hcts_by_hue[hue as usize]; - let complement_relative_temp = 1.0 - self.input_relative_temperature(); + let complement_relative_temp = 1.0 - self.input_relative_temperature; // Find the color in the other section, closest to the inverse percentile // of the input color. This is the complement. @@ -232,8 +278,8 @@ impl TemperatureCache { continue; } - let possible_answer = &self.hcts_by_hue()[hue.round() as usize]; - let relative_temp = (self._temps_by_hct[possible_answer] - coldest_temp) / range; + let possible_answer = &self.hcts_by_hue[hue.round() as usize]; + let relative_temp = (self.temps_by_hct[possible_answer] - coldest_temp) / range; let error = (complement_relative_temp - relative_temp).abs(); if error < smallest_error { @@ -244,125 +290,32 @@ impl TemperatureCache { self._complement = Some(answer); - self._complement.unwrap() + answer } /// Temperature relative to all colors with the same chroma and tone. /// Value on a scale from 0 to 1. - pub fn relative_temperature(&mut self, hct: &Hct) -> f64 { + pub fn relative_temperature(&self, hct: &Hct) -> f64 { let coldest = self.coldest(); let warmest = self.warmest(); - let temps_by_hct = self.temps_by_hct(); - - let range = temps_by_hct[&warmest] - temps_by_hct[&coldest]; - let difference_from_coldest = temps_by_hct[hct] - temps_by_hct[&coldest]; + let range = self.temps_by_hct[warmest] - self.temps_by_hct[coldest]; + let difference_from_coldest = self.temps_by_hct[hct] - self.temps_by_hct[coldest]; // Handle when there's no difference in temperature between warmest and // coldest: for example, at T100, only one color is available, white. if range == 0.0 { - return 0.5; - } - - difference_from_coldest / range - } - - /// Relative temperature of the input color. See [`relative_temperature`]. - /// - /// [`relative_temperature`]: Self::relative_temperature - pub fn input_relative_temperature(&mut self) -> f64 { - if self._input_relative_temperature >= 0.0 { - return self._input_relative_temperature; - } - - let coldest = self.coldest(); - let warmest = self.warmest(); - let input = self.input; - - let temps_by_hct = self.temps_by_hct(); - - let coldest_temp = temps_by_hct[&coldest]; - - let range = temps_by_hct[&warmest] - coldest_temp; - let difference_from_coldest = temps_by_hct[&input] - coldest_temp; - let input_relative_temp = if range == 0.0 { 0.5 } else { difference_from_coldest / range - }; - - self._input_relative_temperature = input_relative_temp; - - self._input_relative_temperature - } - - /// HCTs for all hues, with the same chroma/tone as the input. - /// Sorted from coldest first to warmest last. - pub fn hcts_by_temp(&mut self) -> &[Hct] { - if !self._hcts_by_temp.is_empty() { - return &self._hcts_by_temp; } - - let mut hcts = self.hcts_by_hue(); - - hcts.push(self.input); - hcts.sort_by(|a, b| self.sort_by_temp(a, b)); - - self._hcts_by_temp = hcts; - - &self._hcts_by_temp } - fn sort_by_temp(&mut self, this: &Hct, that: &Hct) -> Ordering { - let a = self.temps_by_hct()[this]; - let b = self.temps_by_hct()[that]; - - a.partial_cmp(&b).unwrap() - } - - /// A Map with keys of HCTs in `hcts_by_temp`, values of raw temperature. - pub fn temps_by_hct(&mut self) -> &Map { - if !self._temps_by_hct.is_empty() { - return &self._temps_by_hct; - } - - let mut all_hcts = self.hcts_by_hue(); - - all_hcts.push(self.input); - - let mut temperatures_by_hct = Map::default(); - - for e in all_hcts { - temperatures_by_hct.insert(e, Self::raw_temperature(e)); - } - - self._temps_by_hct = temperatures_by_hct; - - &self._temps_by_hct - } - - /// HCTs for all hues, with the same chroma/tone as the input. - /// Sorted ascending, hue 0 to 360. - pub fn hcts_by_hue(&mut self) -> Vec { - if !self._hcts_by_hue.is_empty() { - return self._hcts_by_hue.clone(); - } - - let mut hcts = vec![]; - - for hue in 0..=360 { - let color_at_hue = Hct::from( - f64::from(hue), - self.input.get_chroma(), - self.input.get_tone(), - ); - - hcts.push(color_at_hue); - } - - self._hcts_by_hue = hcts; + fn sort_by_temp(temps_by_hct: &Map, this: &Hct, that: &Hct) -> Ordering { + let a = &temps_by_hct[this]; + let b = &temps_by_hct[that]; - self._hcts_by_hue.clone() + a.partial_cmp(b).unwrap() } /// Determines if an angle is between two other angles, rotating clockwise. @@ -391,8 +344,8 @@ impl TemperatureCache { /// Assuming max of 130 chroma, -9.66. /// - Upper bound: -0.52 + (chroma ^ 1.07 / 20). L*a*b* chroma is infinite. /// Assuming max of 130 chroma, 8.61. - pub fn raw_temperature(color: Hct) -> f64 { - let lab = Lab::from(Argb::from(color)); + pub fn raw_temperature(color: &Hct) -> f64 { + let lab = Lab::from(Argb::from_ref(color)); let hue = sanitize_degrees_double(lab.b.atan2(lab.a).to_degrees()); let chroma = lab.a.hypot(lab.b); @@ -418,11 +371,11 @@ mod tests { let white_hct = Hct::new(Argb::from_u32(0xffffffff)); let black_hct = Hct::new(Argb::from_u32(0xff000000)); - let blue_temp = TemperatureCache::raw_temperature(blue_hct); - let red_temp = TemperatureCache::raw_temperature(red_hct); - let green_temp = TemperatureCache::raw_temperature(green_hct); - let white_temp = TemperatureCache::raw_temperature(white_hct); - let black_temp = TemperatureCache::raw_temperature(black_hct); + let blue_temp = TemperatureCache::raw_temperature(&blue_hct); + let red_temp = TemperatureCache::raw_temperature(&red_hct); + let green_temp = TemperatureCache::raw_temperature(&green_hct); + let white_temp = TemperatureCache::raw_temperature(&white_hct); + let black_temp = TemperatureCache::raw_temperature(&black_hct); assert_approx_eq!(f64, -1.393, blue_temp, epsilon = 0.001); assert_approx_eq!(f64, 2.351, red_temp, epsilon = 0.001); diff --git a/src/theme.rs b/src/theme.rs index 819a225..96e1b82 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -1,3 +1,4 @@ +#[allow(deprecated)] use crate::{ blend::harmonize, color::Argb, @@ -52,7 +53,9 @@ impl CustomColorGroup { value = harmonize(value, source); } + #[allow(deprecated)] let palette = CorePalette::of(value); + #[allow(deprecated)] let tones = palette.primary; Self { @@ -198,6 +201,7 @@ impl ThemeBuilder { #[must_use] pub fn build(mut self) -> Theme { + #[allow(deprecated)] let palette = CorePalette::of(self.source); if self.color_match { @@ -258,6 +262,7 @@ impl ThemeBuilder { light: light.into(), dark: dark.into(), }, + #[allow(deprecated)] palettes: Palettes { primary: palette.primary, secondary: palette.secondary, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 26b9cce..c44abe2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,3 +2,7 @@ pub mod math; #[cfg(all(not(feature = "std"), feature = "libm"))] pub mod no_std; pub mod random; + +pub trait FromRef { + fn from_ref(value: &T) -> Self; +}