1use crate::raster::Rgb;
6
7#[derive(Debug, Clone, Copy)]
8pub struct Oklch {
9 pub l: f64,
10 pub c: f64,
11 pub h: f64,
13}
14
15impl Oklch {
16 pub fn rgb(self) -> Rgb {
17 let hr = self.h.to_radians();
18 let (a, b) = (self.c * hr.cos(), self.c * hr.sin());
19 let l_ = self.l + 0.3963377774 * a + 0.2158037573 * b;
20 let m_ = self.l - 0.1055613458 * a - 0.0638541728 * b;
21 let s_ = self.l - 0.0894841775 * a - 1.2914855480 * b;
22 let (l, m, s) = (l_ * l_ * l_, m_ * m_ * m_, s_ * s_ * s_);
23 let r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
24 let g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
25 let bb = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s;
26 Rgb(gamma(r), gamma(g), gamma(bb))
27 }
28}
29
30fn gamma(c: f64) -> u8 {
31 let c = c.clamp(0.0, 1.0);
32 let v = if c <= 0.0031308 {
33 12.92 * c
34 } else {
35 1.055 * c.powf(1.0 / 2.4) - 0.055
36 };
37 (v * 255.0).round() as u8
38}
39
40fn mix(a: Oklch, b: Oklch, t: f64) -> Oklch {
41 let mut dh = b.h - a.h;
42 if dh > 180.0 {
43 dh -= 360.0;
44 }
45 if dh < -180.0 {
46 dh += 360.0;
47 }
48 Oklch {
49 l: a.l + (b.l - a.l) * t,
50 c: a.c + (b.c - a.c) * t,
51 h: a.h + dh * t,
52 }
53}
54
55#[derive(Debug, Clone)]
56pub struct Theme {
57 pub name: &'static str,
58 pub axis: Rgb,
59 pub title: Rgb,
60 pub accent: Rgb,
62 grad_ends: (Oklch, Oklch),
63 pub palette: Vec<Rgb>,
64}
65
66impl Theme {
67 pub fn grad(&self, t: f64) -> Rgb {
69 mix(self.grad_ends.0, self.grad_ends.1, t.clamp(0.0, 1.0)).rgb()
70 }
71
72 pub fn series(&self, i: usize) -> Rgb {
78 self.palette[i % self.palette.len()]
79 }
80}
81
82pub const THEME_NAMES: &[&str] = &["benday", "lichtenstein", "rotogravure"];
83
84pub fn by_name(name: &str) -> Option<Theme> {
85 match name {
86 "benday" => Some(Theme {
88 name: "benday",
89 axis: Rgb(106, 112, 122),
90 title: Rgb(222, 226, 232),
91 accent: Oklch {
92 l: 0.80,
93 c: 0.14,
94 h: 210.0,
95 }
96 .rgb(),
97 grad_ends: (
98 Oklch {
99 l: 0.70,
100 c: 0.12,
101 h: 235.0,
102 },
103 Oklch {
104 l: 0.86,
105 c: 0.15,
106 h: 75.0,
107 },
108 ),
109 palette: palette_from_hues(&[235.0, 155.0, 70.0, 330.0, 200.0, 20.0, 110.0, 285.0]),
110 }),
111 "lichtenstein" => Some(Theme {
113 name: "lichtenstein",
114 axis: Rgb(132, 120, 106),
115 title: Rgb(235, 228, 216),
116 accent: Oklch {
117 l: 0.62,
118 c: 0.22,
119 h: 28.0,
120 }
121 .rgb(),
122 grad_ends: (
123 Oklch {
124 l: 0.58,
125 c: 0.22,
126 h: 28.0,
127 },
128 Oklch {
129 l: 0.88,
130 c: 0.16,
131 h: 95.0,
132 },
133 ),
134 palette: vec![
135 Oklch {
136 l: 0.62,
137 c: 0.22,
138 h: 28.0,
139 }
140 .rgb(),
141 Oklch {
142 l: 0.88,
143 c: 0.16,
144 h: 95.0,
145 }
146 .rgb(),
147 Oklch {
148 l: 0.55,
149 c: 0.19,
150 h: 262.0,
151 }
152 .rgb(),
153 Oklch {
154 l: 0.42,
155 c: 0.08,
156 h: 262.0,
157 }
158 .rgb(),
159 ],
160 }),
161 "rotogravure" => Some(Theme {
163 name: "rotogravure",
164 axis: Rgb(110, 110, 116),
165 title: Rgb(228, 228, 232),
166 accent: Oklch {
167 l: 0.85,
168 c: 0.02,
169 h: 250.0,
170 }
171 .rgb(),
172 grad_ends: (
173 Oklch {
174 l: 0.42,
175 c: 0.02,
176 h: 250.0,
177 },
178 Oklch {
179 l: 0.95,
180 c: 0.02,
181 h: 250.0,
182 },
183 ),
184 palette: vec![
185 Oklch {
186 l: 0.90,
187 c: 0.02,
188 h: 250.0,
189 }
190 .rgb(),
191 Oklch {
192 l: 0.70,
193 c: 0.02,
194 h: 250.0,
195 }
196 .rgb(),
197 Oklch {
198 l: 0.52,
199 c: 0.02,
200 h: 250.0,
201 }
202 .rgb(),
203 Oklch {
204 l: 0.36,
205 c: 0.02,
206 h: 250.0,
207 }
208 .rgb(),
209 ],
210 }),
211 _ => None,
212 }
213}
214
215fn palette_from_hues(hues: &[f64]) -> Vec<Rgb> {
216 hues.iter()
217 .map(|&h| {
218 Oklch {
219 l: 0.78,
220 c: 0.13,
221 h,
222 }
223 .rgb()
224 })
225 .collect()
226}