gpui_kit_material_color/dynamic/
temperature.rs1use super::hct::Hct;
9use super::utils::{lab_from_argb, sanitize_degrees_double, sanitize_degrees_int};
10use std::f64::consts::PI;
11
12#[derive(Clone, Debug)]
14pub struct TemperatureCache {
15 input: Hct,
16 by_hue: Vec<Hct>,
18 coldest: (Hct, f64),
19 warmest: (Hct, f64),
20}
21
22impl TemperatureCache {
23 pub fn new(input: Hct) -> Self {
25 let by_hue: Vec<Hct> = (0..=360)
26 .map(|hue| Hct::from_hct(f64::from(hue), input.chroma(), input.tone()))
27 .collect();
28 let mut coldest = (input, f64::INFINITY);
32 let mut warmest = (input, f64::NEG_INFINITY);
33 for hct in by_hue.iter().copied().chain([input]) {
34 let temperature = raw_temperature(hct);
35 if temperature < coldest.1 {
36 coldest = (hct, temperature);
37 }
38 if temperature >= warmest.1 {
39 warmest = (hct, temperature);
40 }
41 }
42 Self {
43 input,
44 by_hue,
45 coldest,
46 warmest,
47 }
48 }
49
50 pub fn warmest(&self) -> Hct {
52 self.warmest.0
53 }
54
55 pub fn coldest(&self) -> Hct {
57 self.coldest.0
58 }
59
60 pub fn analogous(&self, count: usize, divisions: usize) -> Vec<Hct> {
65 if count == 0 || divisions == 0 {
66 return vec![self.input];
67 }
68 let start_hue = self.input.hue().round() as i32;
70 let start = self.by_hue[start_hue as usize];
71 let mut last_temp = self.relative_temperature(start);
72 let mut all = vec![start];
73
74 let mut total_delta = 0.;
75 for i in 0..360 {
76 let hct = self.by_hue[sanitize_degrees_int(start_hue + i) as usize];
77 let temp = self.relative_temperature(hct);
78 total_delta += (temp - last_temp).abs();
79 last_temp = temp;
80 }
81 let mut hue_addend = 1;
82 let step = total_delta / divisions as f64;
83 let mut delta_so_far = 0.;
84 last_temp = self.relative_temperature(start);
85 while all.len() < divisions {
86 let hct = self.by_hue[sanitize_degrees_int(start_hue + hue_addend) as usize];
87 let temp = self.relative_temperature(hct);
88 delta_so_far += (temp - last_temp).abs();
89 let mut satisfied = delta_so_far >= all.len() as f64 * step;
90 let mut index_addend = 1;
91 while satisfied && all.len() < divisions {
95 all.push(hct);
96 satisfied = delta_so_far >= (all.len() + index_addend) as f64 * step;
97 index_addend += 1;
98 }
99 last_temp = temp;
100 hue_addend += 1;
101 if hue_addend > 360 {
102 while all.len() < divisions {
103 all.push(hct);
104 }
105 break;
106 }
107 }
108
109 let mut answers = vec![self.input];
110 let increase = (count - 1) / 2;
111 for i in 1..=increase {
112 answers.insert(0, all[(all.len() - i % all.len()) % all.len()]);
113 }
114 for i in 1..=count - increase - 1 {
115 answers.push(all[i % all.len()]);
116 }
117 answers
118 }
119
120 pub fn complement(&self) -> Hct {
123 let (coldest_hue, coldest_temp) = (self.coldest.0.hue(), self.coldest.1);
124 let (warmest_hue, warmest_temp) = (self.warmest.0.hue(), self.warmest.1);
125 let range = warmest_temp - coldest_temp;
126 let cold_to_warm = is_between(self.input.hue(), coldest_hue, warmest_hue);
127 let (start_hue, end_hue) = if cold_to_warm {
128 (warmest_hue, coldest_hue)
129 } else {
130 (coldest_hue, warmest_hue)
131 };
132 let mut smallest_error = 1000.;
133 let mut answer = self.by_hue[self.input.hue().round() as usize];
134 let complement_relative = 1. - self.input_relative_temperature();
135 for addend in 0..=360 {
137 let hue = sanitize_degrees_double(start_hue + f64::from(addend));
138 if !is_between(hue, start_hue, end_hue) {
139 continue;
140 }
141 let possible = self.by_hue[hue.round() as usize];
142 let relative = (raw_temperature(possible) - coldest_temp) / range;
143 let error = (complement_relative - relative).abs();
144 if error < smallest_error {
145 smallest_error = error;
146 answer = possible;
147 }
148 }
149 answer
150 }
151
152 pub fn relative_temperature(&self, hct: Hct) -> f64 {
155 let range = self.warmest.1 - self.coldest.1;
156 if range == 0. {
157 return 0.5;
158 }
159 (raw_temperature(hct) - self.coldest.1) / range
160 }
161
162 pub fn input_relative_temperature(&self) -> f64 {
164 self.relative_temperature(self.input)
165 }
166}
167
168fn is_between(angle: f64, a: f64, b: f64) -> bool {
170 if a < b {
171 a <= angle && angle <= b
172 } else {
173 a <= angle || angle <= b
174 }
175}
176
177pub fn raw_temperature(color: Hct) -> f64 {
180 let [_, a, b] = lab_from_argb(color.argb());
181 let hue = sanitize_degrees_double(b.atan2(a) * 180. / PI);
182 let chroma = (a * a + b * b).sqrt();
183 -0.5 + 0.02 * chroma.powf(1.07) * (sanitize_degrees_double(hue - 50.) * PI / 180.).cos()
184}