hct_cam16/
scheme.rs

1//! Material Design 3-ish Color Scheme Generation (ARGB output)
2
3use crate::palette::CorePalette;
4
5/// A complete color scheme (ARGB values).
6#[derive(Debug, Clone)]
7pub struct MaterialColorScheme {
8    // Primary
9    pub primary: u32,
10    pub on_primary: u32,
11    pub primary_container: u32,
12    pub on_primary_container: u32,
13
14    // Secondary
15    pub secondary: u32,
16    pub on_secondary: u32,
17    pub secondary_container: u32,
18    pub on_secondary_container: u32,
19
20    // Tertiary
21    pub tertiary: u32,
22    pub on_tertiary: u32,
23    pub tertiary_container: u32,
24    pub on_tertiary_container: u32,
25
26    // Error
27    pub error: u32,
28    pub on_error: u32,
29    pub error_container: u32,
30    pub on_error_container: u32,
31
32    // Surface
33    pub surface: u32,
34    pub surface_bright: u32,
35    pub surface_dim: u32,
36    pub on_surface: u32,
37    pub on_surface_variant: u32,
38
39    // Surface Containers
40    pub surface_container_lowest: u32,
41    pub surface_container_low: u32,
42    pub surface_container: u32,
43    pub surface_container_high: u32,
44    pub surface_container_highest: u32,
45
46    // Outline
47    pub outline: u32,
48    pub outline_variant: u32,
49
50    // Inverse
51    pub inverse_surface: u32,
52    pub inverse_on_surface: u32,
53    pub inverse_primary: u32,
54
55    // Fixed Accent
56    pub primary_fixed: u32,
57    pub primary_fixed_dim: u32,
58    pub on_primary_fixed: u32,
59    pub on_primary_fixed_variant: u32,
60    pub secondary_fixed: u32,
61    pub secondary_fixed_dim: u32,
62    pub on_secondary_fixed: u32,
63    pub on_secondary_fixed_variant: u32,
64    pub tertiary_fixed: u32,
65    pub tertiary_fixed_dim: u32,
66    pub on_tertiary_fixed: u32,
67    pub on_tertiary_fixed_variant: u32,
68
69    // Utility
70    pub scrim: u32,
71    pub shadow: u32,
72}
73
74impl Default for MaterialColorScheme {
75    fn default() -> Self {
76        Self::dark_from_argb(0xFF6750A4)
77    }
78}
79
80impl MaterialColorScheme {
81    pub fn dark_from_argb(seed: u32) -> Self {
82        let mut palette = CorePalette::from_argb(seed);
83        palette.cache_all();
84        Self::dark_from_palette(&mut palette)
85    }
86
87    pub fn light_from_argb(seed: u32) -> Self {
88        let mut palette = CorePalette::from_argb(seed);
89        palette.cache_all();
90        Self::light_from_palette(&mut palette)
91    }
92
93    pub fn dark_from_palette(p: &mut CorePalette) -> Self {
94        Self {
95            primary: p.primary.tone(80),
96            on_primary: p.primary.tone(20),
97            primary_container: p.primary.tone(30),
98            on_primary_container: p.primary.tone(90),
99
100            secondary: p.secondary.tone(80),
101            on_secondary: p.secondary.tone(20),
102            secondary_container: p.secondary.tone(30),
103            on_secondary_container: p.secondary.tone(90),
104
105            tertiary: p.tertiary.tone(80),
106            on_tertiary: p.tertiary.tone(20),
107            tertiary_container: p.tertiary.tone(30),
108            on_tertiary_container: p.tertiary.tone(90),
109
110            error: p.error.tone(80),
111            on_error: p.error.tone(20),
112            error_container: p.error.tone(30),
113            on_error_container: p.error.tone(90),
114
115            surface: p.neutral.tone(6),
116            surface_bright: p.neutral.tone(24),
117            surface_dim: p.neutral.tone(6),
118            on_surface: p.neutral.tone(90),
119            on_surface_variant: p.neutral_variant.tone(80),
120
121            surface_container_lowest: p.neutral.tone(4),
122            surface_container_low: p.neutral.tone(10),
123            surface_container: p.neutral.tone(12),
124            surface_container_high: p.neutral.tone(17),
125            surface_container_highest: p.neutral.tone(22),
126
127            outline: p.neutral_variant.tone(60),
128            outline_variant: p.neutral_variant.tone(30),
129
130            inverse_surface: p.neutral.tone(90),
131            inverse_on_surface: p.neutral.tone(20),
132            inverse_primary: p.primary.tone(40),
133
134            primary_fixed: p.primary.tone(90),
135            primary_fixed_dim: p.primary.tone(80),
136            on_primary_fixed: p.primary.tone(10),
137            on_primary_fixed_variant: p.primary.tone(30),
138            secondary_fixed: p.secondary.tone(90),
139            secondary_fixed_dim: p.secondary.tone(80),
140            on_secondary_fixed: p.secondary.tone(10),
141            on_secondary_fixed_variant: p.secondary.tone(30),
142            tertiary_fixed: p.tertiary.tone(90),
143            tertiary_fixed_dim: p.tertiary.tone(80),
144            on_tertiary_fixed: p.tertiary.tone(10),
145            on_tertiary_fixed_variant: p.tertiary.tone(30),
146
147            scrim: 0xFF000000,
148            shadow: 0xFF000000,
149        }
150    }
151
152    pub fn light_from_palette(p: &mut CorePalette) -> Self {
153        Self {
154            primary: p.primary.tone(40),
155            on_primary: p.primary.tone(100),
156            primary_container: p.primary.tone(90),
157            on_primary_container: p.primary.tone(10),
158
159            secondary: p.secondary.tone(40),
160            on_secondary: p.secondary.tone(100),
161            secondary_container: p.secondary.tone(90),
162            on_secondary_container: p.secondary.tone(10),
163
164            tertiary: p.tertiary.tone(40),
165            on_tertiary: p.tertiary.tone(100),
166            tertiary_container: p.tertiary.tone(90),
167            on_tertiary_container: p.tertiary.tone(10),
168
169            error: p.error.tone(40),
170            on_error: p.error.tone(100),
171            error_container: p.error.tone(90),
172            on_error_container: p.error.tone(10),
173
174            surface: p.neutral.tone(98),
175            surface_bright: p.neutral.tone(98),
176            surface_dim: p.neutral.tone(87),
177            on_surface: p.neutral.tone(10),
178            on_surface_variant: p.neutral_variant.tone(30),
179
180            surface_container_lowest: p.neutral.tone(100),
181            surface_container_low: p.neutral.tone(96),
182            surface_container: p.neutral.tone(94),
183            surface_container_high: p.neutral.tone(92),
184            surface_container_highest: p.neutral.tone(90),
185
186            outline: p.neutral_variant.tone(50),
187            outline_variant: p.neutral_variant.tone(80),
188
189            inverse_surface: p.neutral.tone(20),
190            inverse_on_surface: p.neutral.tone(95),
191            inverse_primary: p.primary.tone(80),
192
193            primary_fixed: p.primary.tone(90),
194            primary_fixed_dim: p.primary.tone(80),
195            on_primary_fixed: p.primary.tone(10),
196            on_primary_fixed_variant: p.primary.tone(30),
197            secondary_fixed: p.secondary.tone(90),
198            secondary_fixed_dim: p.secondary.tone(80),
199            on_secondary_fixed: p.secondary.tone(10),
200            on_secondary_fixed_variant: p.secondary.tone(30),
201            tertiary_fixed: p.tertiary.tone(90),
202            tertiary_fixed_dim: p.tertiary.tone(80),
203            on_tertiary_fixed: p.tertiary.tone(10),
204            on_tertiary_fixed_variant: p.tertiary.tone(30),
205
206            scrim: 0xFF000000,
207            shadow: 0xFF000000,
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    fn luminance_from_argb(argb: u32) -> f32 {
217        let r = ((argb >> 16) & 0xFF) as f32 / 255.0;
218        let g = ((argb >> 8) & 0xFF) as f32 / 255.0;
219        let b = (argb & 0xFF) as f32 / 255.0;
220        0.299 * r + 0.587 * g + 0.114 * b
221    }
222
223    #[test]
224    fn test_dark_scheme_generation() {
225        let scheme = MaterialColorScheme::dark_from_argb(0xFF6750A4);
226        assert!(luminance_from_argb(scheme.primary) > luminance_from_argb(scheme.on_primary));
227    }
228
229    #[test]
230    fn test_light_scheme_generation() {
231        let scheme = MaterialColorScheme::light_from_argb(0xFF6750A4);
232        assert!(luminance_from_argb(scheme.on_primary) > luminance_from_argb(scheme.primary));
233    }
234
235    #[test]
236    fn test_surface_hierarchy_dark() {
237        let scheme = MaterialColorScheme::dark_from_argb(0xFF6750A4);
238        assert!(
239            luminance_from_argb(scheme.surface_container_lowest)
240                < luminance_from_argb(scheme.surface_container_low)
241        );
242        assert!(
243            luminance_from_argb(scheme.surface_container_low)
244                < luminance_from_argb(scheme.surface_container)
245        );
246        assert!(
247            luminance_from_argb(scheme.surface_container)
248                < luminance_from_argb(scheme.surface_container_high)
249        );
250        assert!(
251            luminance_from_argb(scheme.surface_container_high)
252                < luminance_from_argb(scheme.surface_container_highest)
253        );
254    }
255
256    #[test]
257    fn test_error_colors() {
258        let scheme = MaterialColorScheme::dark_from_argb(0xFF6750A4);
259        let r = (scheme.error >> 16) & 0xFF;
260        let b = scheme.error & 0xFF;
261        assert!(r > b);
262    }
263}