1#[cfg(all(not(feature = "std"), feature = "libm"))]
2#[allow(unused_imports)]
3use crate::utils::no_std::FloatExt;
4use crate::{
5 color::{Argb, Lab},
6 hct::Hct,
7 utils::math::sanitize_degrees_double,
8 Map,
9};
10#[cfg(not(feature = "std"))]
11use alloc::{vec, vec::Vec};
12use core::cmp::Ordering;
13#[cfg(feature = "std")]
14use std::{vec, vec::Vec};
15
16pub struct TemperatureCache {
21 input: Hct,
22
23 _hcts_by_temp: Vec<Hct>,
24 _hcts_by_hue: Vec<Hct>,
25 _temps_by_hct: Map<Hct, f64>,
26 _input_relative_temperature: f64,
27 _complement: Option<Hct>,
28}
29
30impl TemperatureCache {
31 pub fn warmest(&mut self) -> Hct {
35 let hcts = self.hcts_by_temp();
36
37 return *hcts.last().unwrap();
38 }
39
40 pub fn coldest(&mut self) -> Hct {
44 let hcts = self.hcts_by_temp();
45
46 return *hcts.first().unwrap();
47 }
48
49 pub fn new(input: Hct) -> Self {
50 Self {
51 input,
52 _hcts_by_temp: vec![],
53 _hcts_by_hue: vec![],
54 _temps_by_hct: Map::default(),
55 _input_relative_temperature: -1.0,
56 _complement: None,
57 }
58 }
59
60 pub fn analogous(&mut self, count: Option<i32>, divisions: Option<i32>) -> Vec<Hct> {
72 let count = count.unwrap_or(5);
73 let divisions = divisions.unwrap_or(12);
74 let start_hue = self.input.get_hue().round() as i32;
75 let start_hct = &self.hcts_by_hue()[start_hue as usize];
76 let mut last_temp = self.relative_temperature(start_hct);
77 let mut all_colors = vec![*start_hct];
78
79 let mut absolute_total_temp_delta = 0.0;
80
81 for i in 0..360 {
82 let hue = sanitize_degrees_double((start_hue + i).into());
83 let hct = &self.hcts_by_hue()[hue as usize];
84 let temp = self.relative_temperature(hct);
85 let temp_delta = (temp - last_temp).abs();
86
87 last_temp = temp;
88 absolute_total_temp_delta += temp_delta;
89 }
90
91 let mut hue_addend = 1;
92 let temp_step = absolute_total_temp_delta / f64::from(divisions);
93
94 let mut total_temp_delta = 0.0;
95
96 last_temp = self.relative_temperature(start_hct);
97
98 while all_colors.len() < divisions as usize {
99 let hue = sanitize_degrees_double((start_hue + hue_addend).into());
100 let hct = &self.hcts_by_hue()[hue as usize];
101 let temp = self.relative_temperature(hct);
102 let temp_delta = (temp - last_temp).abs();
103
104 total_temp_delta += temp_delta;
105
106 let desired_total_temp_delta_for_index = all_colors.len() as f64 * temp_step;
107
108 let mut index_satisfied = total_temp_delta >= desired_total_temp_delta_for_index;
109 let mut index_addend = 1;
110
111 while index_satisfied && all_colors.len() < divisions as usize {
120 all_colors.push(*hct);
121
122 let desired_total_temp_delta_for_index =
123 (all_colors.len() + index_addend) as f64 * temp_step;
124
125 index_satisfied = total_temp_delta >= desired_total_temp_delta_for_index;
126 index_addend += 1;
127 }
128
129 last_temp = temp;
130 hue_addend += 1;
131
132 if hue_addend > 360 {
133 while all_colors.len() < divisions as usize {
134 all_colors.push(*hct);
135 }
136
137 break;
138 }
139 }
140
141 let mut answers = vec![self.input];
142
143 let increase_hue_count = ((f64::from(count) - 1.0) / 2.0).floor() as isize;
145
146 for i in 1..=increase_hue_count {
147 let mut index = 0_isize - i;
148
149 while index < 0 {
150 index += all_colors.len() as isize;
151 }
152
153 if index >= all_colors.len() as isize {
154 index %= all_colors.len() as isize;
155 }
156
157 answers.insert(0, all_colors[index as usize]);
158 }
159
160 let decrease_hue_count = (count - (increase_hue_count as i32) - 1) as isize;
162
163 for i in 1..=decrease_hue_count {
164 let mut index = i;
165
166 while index < 0 {
167 index += all_colors.len() as isize;
168 }
169
170 if index >= all_colors.len() as isize {
171 index %= all_colors.len() as isize;
172 }
173
174 answers.push(all_colors[index as usize]);
175 }
176
177 answers
178 }
179
180 pub fn complement(&mut self) -> Hct {
190 if let Some(_complement) = &self._complement {
191 return *_complement;
192 }
193
194 let coldest_hct = self.coldest();
195 let warmest_hct = self.warmest();
196
197 let temps_by_hct = self.temps_by_hct();
198
199 let coldest_hue = coldest_hct.get_hue();
200 let coldest_temp = temps_by_hct[&coldest_hct];
201
202 let warmest_hue = warmest_hct.get_hue();
203 let warmest_temp = temps_by_hct[&warmest_hct];
204
205 let range = warmest_temp - coldest_temp;
206 let start_hue_is_coldest_to_warmest =
207 Self::is_between(self.input.get_hue(), coldest_hue, warmest_hue);
208 let start_hue = if start_hue_is_coldest_to_warmest {
209 warmest_hue
210 } else {
211 coldest_hue
212 };
213 let end_hue = if start_hue_is_coldest_to_warmest {
214 coldest_hue
215 } else {
216 warmest_hue
217 };
218 let direction_of_rotation = 1.0_f64;
219 let mut smallest_error = 1000.0;
220 let mut answer = self.hcts_by_hue()[self.input.get_hue().round() as usize];
221
222 let complement_relative_temp = 1.0 - self.input_relative_temperature();
223
224 for hue_addend in 0..=360 {
227 let hue = sanitize_degrees_double(
228 direction_of_rotation.mul_add(f64::from(hue_addend), start_hue),
229 );
230
231 if !Self::is_between(hue, start_hue, end_hue) {
232 continue;
233 }
234
235 let possible_answer = &self.hcts_by_hue()[hue.round() as usize];
236 let relative_temp = (self._temps_by_hct[possible_answer] - coldest_temp) / range;
237 let error = (complement_relative_temp - relative_temp).abs();
238
239 if error < smallest_error {
240 smallest_error = error;
241 answer = *possible_answer;
242 }
243 }
244
245 self._complement = Some(answer);
246
247 self._complement.unwrap()
248 }
249
250 pub fn relative_temperature(&mut self, hct: &Hct) -> f64 {
253 let coldest = self.coldest();
254 let warmest = self.warmest();
255
256 let temps_by_hct = self.temps_by_hct();
257
258 let range = temps_by_hct[&warmest] - temps_by_hct[&coldest];
259 let difference_from_coldest = temps_by_hct[hct] - temps_by_hct[&coldest];
260
261 if range == 0.0 {
264 return 0.5;
265 }
266
267 difference_from_coldest / range
268 }
269
270 pub fn input_relative_temperature(&mut self) -> f64 {
274 if self._input_relative_temperature >= 0.0 {
275 return self._input_relative_temperature;
276 }
277
278 let coldest = self.coldest();
279 let warmest = self.warmest();
280 let input = self.input;
281
282 let temps_by_hct = self.temps_by_hct();
283
284 let coldest_temp = temps_by_hct[&coldest];
285
286 let range = temps_by_hct[&warmest] - coldest_temp;
287 let difference_from_coldest = temps_by_hct[&input] - coldest_temp;
288 let input_relative_temp = if range == 0.0 {
289 0.5
290 } else {
291 difference_from_coldest / range
292 };
293
294 self._input_relative_temperature = input_relative_temp;
295
296 self._input_relative_temperature
297 }
298
299 pub fn hcts_by_temp(&mut self) -> &[Hct] {
302 if !self._hcts_by_temp.is_empty() {
303 return &self._hcts_by_temp;
304 }
305
306 let mut hcts = self.hcts_by_hue();
307
308 hcts.push(self.input);
309 hcts.sort_by(|a, b| self.sort_by_temp(a, b));
310
311 self._hcts_by_temp = hcts;
312
313 &self._hcts_by_temp
314 }
315
316 fn sort_by_temp(&mut self, this: &Hct, that: &Hct) -> Ordering {
317 let a = self.temps_by_hct()[this];
318 let b = self.temps_by_hct()[that];
319
320 a.partial_cmp(&b).unwrap()
321 }
322
323 pub fn temps_by_hct(&mut self) -> &Map<Hct, f64> {
325 if !self._temps_by_hct.is_empty() {
326 return &self._temps_by_hct;
327 }
328
329 let mut all_hcts = self.hcts_by_hue();
330
331 all_hcts.push(self.input);
332
333 let mut temperatures_by_hct = Map::default();
334
335 for e in all_hcts {
336 temperatures_by_hct.insert(e, Self::raw_temperature(e));
337 }
338
339 self._temps_by_hct = temperatures_by_hct;
340
341 &self._temps_by_hct
342 }
343
344 pub fn hcts_by_hue(&mut self) -> Vec<Hct> {
347 if !self._hcts_by_hue.is_empty() {
348 return self._hcts_by_hue.clone();
349 }
350
351 let mut hcts = vec![];
352
353 for hue in 0..=360 {
354 let color_at_hue = Hct::from(
355 f64::from(hue),
356 self.input.get_chroma(),
357 self.input.get_tone(),
358 );
359
360 hcts.push(color_at_hue);
361 }
362
363 self._hcts_by_hue = hcts;
364
365 self._hcts_by_hue.clone()
366 }
367
368 pub fn is_between(angle: f64, a: f64, b: f64) -> bool {
370 if a < b {
371 a <= angle && angle <= b
372 } else {
373 a <= angle || angle <= b
374 }
375 }
376
377 pub fn raw_temperature(color: Hct) -> f64 {
395 let lab = Lab::from(Argb::from(color));
396 let hue = sanitize_degrees_double(lab.b.atan2(lab.a).to_degrees());
397
398 let chroma = lab.a.hypot(lab.b);
399
400 (0.02 * chroma.powf(1.07)).mul_add(
401 (sanitize_degrees_double(hue - 50.0).to_radians()).cos(),
402 -0.5,
403 )
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::TemperatureCache;
410 use crate::{color::Argb, hct::Hct};
411 use float_cmp::assert_approx_eq;
412
413 #[test]
414 fn test_raw_temperature() {
415 let blue_hct = Hct::new(Argb::from_u32(0xff0000ff));
416 let red_hct = Hct::new(Argb::from_u32(0xffff0000));
417 let green_hct = Hct::new(Argb::from_u32(0xff00ff00));
418 let white_hct = Hct::new(Argb::from_u32(0xffffffff));
419 let black_hct = Hct::new(Argb::from_u32(0xff000000));
420
421 let blue_temp = TemperatureCache::raw_temperature(blue_hct);
422 let red_temp = TemperatureCache::raw_temperature(red_hct);
423 let green_temp = TemperatureCache::raw_temperature(green_hct);
424 let white_temp = TemperatureCache::raw_temperature(white_hct);
425 let black_temp = TemperatureCache::raw_temperature(black_hct);
426
427 assert_approx_eq!(f64, -1.393, blue_temp, epsilon = 0.001);
428 assert_approx_eq!(f64, 2.351, red_temp, epsilon = 0.001);
429 assert_approx_eq!(f64, -0.267, green_temp, epsilon = 0.001);
430 assert_approx_eq!(f64, -0.5, white_temp, epsilon = 0.001);
431 assert_approx_eq!(f64, -0.5, black_temp, epsilon = 0.001);
432 }
433
434 #[test]
435 fn test_complement() {
436 let blue_complement: Argb = TemperatureCache::new(Hct::new(Argb::from_u32(0xff0000ff)))
437 .complement()
438 .into();
439 let red_complement: Argb = TemperatureCache::new(Hct::new(Argb::from_u32(0xffff0000)))
440 .complement()
441 .into();
442 let green_complement: Argb = TemperatureCache::new(Hct::new(Argb::from_u32(0xff00ff00)))
443 .complement()
444 .into();
445 let white_complement: Argb = TemperatureCache::new(Hct::new(Argb::from_u32(0xffffffff)))
446 .complement()
447 .into();
448 let black_complement: Argb = TemperatureCache::new(Hct::new(Argb::from_u32(0xff000000)))
449 .complement()
450 .into();
451
452 assert_eq!(Argb::from_u32(0xff9d0002), blue_complement);
453 assert_eq!(Argb::from_u32(0xff007bfc), red_complement);
454 assert_eq!(Argb::from_u32(0xffffd2c9), green_complement);
455 assert_eq!(Argb::from_u32(0xffffffff), white_complement);
456 assert_eq!(Argb::from_u32(0xff000000), black_complement);
457 }
458
459 #[test]
460 fn test_blue_analogous() {
461 let analogous =
462 TemperatureCache::new(Hct::new(Argb::from_u32(0xff0000ff))).analogous(None, None);
463
464 assert_eq!(Argb::from_u32(0xff00590c), analogous[0].into());
465 assert_eq!(Argb::from_u32(0xff00564e), analogous[1].into());
466 assert_eq!(Argb::from_u32(0xff0000ff), analogous[2].into());
467 assert_eq!(Argb::from_u32(0xff6700cc), analogous[3].into());
468 assert_eq!(Argb::from_u32(0xff81009f), analogous[4].into());
469 }
470
471 #[test]
472 fn test_red_analogous() {
473 let analogous =
474 TemperatureCache::new(Hct::new(Argb::from_u32(0xffff0000))).analogous(None, None);
475
476 assert_eq!(Argb::from_u32(0xfff60082), analogous[0].into());
477 assert_eq!(Argb::from_u32(0xfffc004c), analogous[1].into());
478 assert_eq!(Argb::from_u32(0xffff0000), analogous[2].into());
479 assert_eq!(Argb::from_u32(0xffd95500), analogous[3].into());
480 assert_eq!(Argb::from_u32(0xffaf7200), analogous[4].into());
481 }
482
483 #[test]
484 fn test_green_analogous() {
485 let green_analogous =
486 TemperatureCache::new(Hct::new(Argb::from_u32(0xff00ff00))).analogous(None, None);
487
488 assert_eq!(Argb::from_u32(0xffcee900), green_analogous[0].into());
489 assert_eq!(Argb::from_u32(0xff92f500), green_analogous[1].into());
490 assert_eq!(Argb::from_u32(0xff00ff00), green_analogous[2].into());
491 assert_eq!(Argb::from_u32(0xff00fd6f), green_analogous[3].into());
492 assert_eq!(Argb::from_u32(0xff00fab3), green_analogous[4].into());
493 }
494
495 #[test]
496 fn test_white_analogous() {
497 let analogous =
498 TemperatureCache::new(Hct::new(Argb::from_u32(0xffffffff))).analogous(None, None);
499
500 assert_eq!(Argb::from_u32(0xffffffff), analogous[0].into());
501 assert_eq!(Argb::from_u32(0xffffffff), analogous[1].into());
502 assert_eq!(Argb::from_u32(0xffffffff), analogous[2].into());
503 assert_eq!(Argb::from_u32(0xffffffff), analogous[3].into());
504 assert_eq!(Argb::from_u32(0xffffffff), analogous[4].into());
505 }
506
507 #[test]
508 fn test_black_analogous() {
509 let analogous =
510 TemperatureCache::new(Hct::new(Argb::from_u32(0xff000000))).analogous(None, None);
511
512 assert_eq!(Argb::from_u32(0xff000000), analogous[0].into());
513 assert_eq!(Argb::from_u32(0xff000000), analogous[1].into());
514 assert_eq!(Argb::from_u32(0xff000000), analogous[2].into());
515 assert_eq!(Argb::from_u32(0xff000000), analogous[3].into());
516 assert_eq!(Argb::from_u32(0xff000000), analogous[4].into());
517 }
518}