1use glam::{Vec3, Vec4};
8use std::f32::consts::PI;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct Rgba {
15 pub r: f32,
16 pub g: f32,
17 pub b: f32,
18 pub a: f32,
19}
20
21impl Rgba {
22 pub const WHITE: Rgba = Rgba { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
23 pub const BLACK: Rgba = Rgba { r: 0.0, g: 0.0, b: 0.0, a: 1.0 };
24 pub const RED: Rgba = Rgba { r: 1.0, g: 0.0, b: 0.0, a: 1.0 };
25 pub const GREEN: Rgba = Rgba { r: 0.0, g: 1.0, b: 0.0, a: 1.0 };
26 pub const BLUE: Rgba = Rgba { r: 0.0, g: 0.0, b: 1.0, a: 1.0 };
27 pub const YELLOW: Rgba = Rgba { r: 1.0, g: 1.0, b: 0.0, a: 1.0 };
28 pub const CYAN: Rgba = Rgba { r: 0.0, g: 1.0, b: 1.0, a: 1.0 };
29 pub const MAGENTA: Rgba = Rgba { r: 1.0, g: 0.0, b: 1.0, a: 1.0 };
30 pub const TRANSPARENT: Rgba = Rgba { r: 0.0, g: 0.0, b: 0.0, a: 0.0 };
31
32 pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } }
33 pub fn rgb(r: f32, g: f32, b: f32) -> Self { Self { r, g, b, a: 1.0 } }
34
35 pub fn from_vec4(v: Vec4) -> Self { Self { r: v.x, g: v.y, b: v.z, a: v.w } }
36 pub fn to_vec4(self) -> Vec4 { Vec4::new(self.r, self.g, self.b, self.a) }
37 pub fn to_vec3(self) -> Vec3 { Vec3::new(self.r, self.g, self.b) }
38
39 pub fn from_hex(hex: u32) -> Self {
41 let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
42 let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
43 let b = ( hex & 0xFF) as f32 / 255.0;
44 Self::rgb(r, g, b)
45 }
46
47 pub fn from_hex_alpha(hex: u32) -> Self {
49 let r = ((hex >> 24) & 0xFF) as f32 / 255.0;
50 let g = ((hex >> 16) & 0xFF) as f32 / 255.0;
51 let b = ((hex >> 8) & 0xFF) as f32 / 255.0;
52 let a = ( hex & 0xFF) as f32 / 255.0;
53 Self { r, g, b, a }
54 }
55
56 pub fn with_alpha(self, a: f32) -> Self { Self { a, ..self } }
57 pub fn lerp(self, other: Rgba, t: f32) -> Self {
58 Rgba {
59 r: self.r + (other.r - self.r) * t,
60 g: self.g + (other.g - self.g) * t,
61 b: self.b + (other.b - self.b) * t,
62 a: self.a + (other.a - self.a) * t,
63 }
64 }
65
66 pub fn over(self, other: Rgba) -> Rgba {
68 let ia = 1.0 - self.a;
69 Rgba {
70 r: self.r * self.a + other.r * ia,
71 g: self.g * self.a + other.g * ia,
72 b: self.b * self.a + other.b * ia,
73 a: self.a + other.a * ia,
74 }
75 }
76
77 pub fn luminance(self) -> f32 {
79 0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b
80 }
81
82 pub fn to_u8(self) -> [u8; 4] {
84 [
85 (self.r.clamp(0.0, 1.0) * 255.0) as u8,
86 (self.g.clamp(0.0, 1.0) * 255.0) as u8,
87 (self.b.clamp(0.0, 1.0) * 255.0) as u8,
88 (self.a.clamp(0.0, 1.0) * 255.0) as u8,
89 ]
90 }
91}
92
93impl From<Vec4> for Rgba {
94 fn from(v: Vec4) -> Self { Self::from_vec4(v) }
95}
96
97impl From<Rgba> for Vec4 {
98 fn from(c: Rgba) -> Self { c.to_vec4() }
99}
100
101#[inline]
105pub fn linear_to_srgb_channel(x: f32) -> f32 {
106 if x <= 0.003_130_8 {
107 x * 12.92
108 } else {
109 1.055 * x.powf(1.0 / 2.4) - 0.055
110 }
111}
112
113#[inline]
115pub fn srgb_to_linear_channel(x: f32) -> f32 {
116 if x <= 0.040_45 {
117 x / 12.92
118 } else {
119 ((x + 0.055) / 1.055).powf(2.4)
120 }
121}
122
123pub fn linear_to_srgb(c: Rgba) -> Rgba {
124 Rgba::new(
125 linear_to_srgb_channel(c.r),
126 linear_to_srgb_channel(c.g),
127 linear_to_srgb_channel(c.b),
128 c.a,
129 )
130}
131
132pub fn srgb_to_linear(c: Rgba) -> Rgba {
133 Rgba::new(
134 srgb_to_linear_channel(c.r),
135 srgb_to_linear_channel(c.g),
136 srgb_to_linear_channel(c.b),
137 c.a,
138 )
139}
140
141#[derive(Debug, Clone, Copy)]
145pub struct Hsv { pub h: f32, pub s: f32, pub v: f32 }
146
147impl Hsv {
148 pub fn new(h: f32, s: f32, v: f32) -> Self { Self { h, s, v } }
149
150 pub fn to_rgb(self) -> Rgba {
151 let (r, g, b) = hsv_to_rgb(self.h, self.s, self.v);
152 Rgba::rgb(r, g, b)
153 }
154
155 pub fn from_rgb(c: Rgba) -> Self {
156 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
157 Self { h, s, v }
158 }
159}
160
161pub fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
162 if s == 0.0 { return (v, v, v); }
163 let h = ((h % 360.0) + 360.0) % 360.0;
164 let i = (h / 60.0) as u32;
165 let f = h / 60.0 - i as f32;
166 let p = v * (1.0 - s);
167 let q = v * (1.0 - s * f);
168 let t = v * (1.0 - s * (1.0 - f));
169 match i {
170 0 => (v, t, p),
171 1 => (q, v, p),
172 2 => (p, v, t),
173 3 => (p, q, v),
174 4 => (t, p, v),
175 _ => (v, p, q),
176 }
177}
178
179pub fn rgb_to_hsv(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
180 let max = r.max(g).max(b);
181 let min = r.min(g).min(b);
182 let delta = max - min;
183
184 let v = max;
185 let s = if max < 1e-8 { 0.0 } else { delta / max };
186 let h = if delta < 1e-8 {
187 0.0
188 } else if max == r {
189 60.0 * (((g - b) / delta) % 6.0)
190 } else if max == g {
191 60.0 * ((b - r) / delta + 2.0)
192 } else {
193 60.0 * ((r - g) / delta + 4.0)
194 };
195 (((h % 360.0) + 360.0) % 360.0, s, v)
196}
197
198#[derive(Debug, Clone, Copy)]
202pub struct Hsl { pub h: f32, pub s: f32, pub l: f32 }
203
204impl Hsl {
205 pub fn new(h: f32, s: f32, l: f32) -> Self { Self { h, s, l } }
206
207 pub fn to_rgb(self) -> Rgba {
208 let (r, g, b) = hsl_to_rgb(self.h, self.s, self.l);
209 Rgba::rgb(r, g, b)
210 }
211}
212
213fn hue_to_rgb(p: f32, q: f32, t: f32) -> f32 {
214 let t = ((t % 1.0) + 1.0) % 1.0;
215 if t < 1.0 / 6.0 { return p + (q - p) * 6.0 * t; }
216 if t < 1.0 / 2.0 { return q; }
217 if t < 2.0 / 3.0 { return p + (q - p) * (2.0 / 3.0 - t) * 6.0; }
218 p
219}
220
221pub fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (f32, f32, f32) {
222 if s == 0.0 { return (l, l, l); }
223 let q = if l < 0.5 { l * (1.0 + s) } else { l + s - l * s };
224 let p = 2.0 * l - q;
225 let h = h / 360.0;
226 (
227 hue_to_rgb(p, q, h + 1.0 / 3.0),
228 hue_to_rgb(p, q, h),
229 hue_to_rgb(p, q, h - 1.0 / 3.0),
230 )
231}
232
233pub fn rgb_to_hsl(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
234 let max = r.max(g).max(b);
235 let min = r.min(g).min(b);
236 let l = (max + min) * 0.5;
237 let delta = max - min;
238
239 if delta < 1e-8 { return (0.0, 0.0, l); }
240
241 let s = if l < 0.5 { delta / (max + min) } else { delta / (2.0 - max - min) };
242 let h = if max == r {
243 60.0 * ((g - b) / delta + if g < b { 6.0 } else { 0.0 })
244 } else if max == g {
245 60.0 * ((b - r) / delta + 2.0)
246 } else {
247 60.0 * ((r - g) / delta + 4.0)
248 };
249 (h, s, l)
250}
251
252#[derive(Debug, Clone, Copy)]
257pub struct Oklab { pub l: f32, pub a: f32, pub b: f32 }
258
259impl Oklab {
260 pub fn from_linear_rgb(c: Rgba) -> Self {
261 let l = 0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b;
262 let m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b;
263 let s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b;
264
265 let l_ = l.cbrt();
266 let m_ = m.cbrt();
267 let s_ = s.cbrt();
268
269 Self {
270 l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
271 a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
272 b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
273 }
274 }
275
276 pub fn to_linear_rgb(self) -> Rgba {
277 let l_ = self.l + 0.3963377774 * self.a + 0.2158037573 * self.b;
278 let m_ = self.l - 0.1055613458 * self.a - 0.0638541728 * self.b;
279 let s_ = self.l - 0.0894841775 * self.a - 1.2914855480 * self.b;
280
281 let l = l_ * l_ * l_;
282 let m = m_ * m_ * m_;
283 let s = s_ * s_ * s_;
284
285 Rgba::rgb(
286 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
287 -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
288 -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
289 )
290 }
291
292 pub fn lerp(self, other: Oklab, t: f32) -> Oklab {
294 Oklab {
295 l: self.l + (other.l - self.l) * t,
296 a: self.a + (other.a - self.a) * t,
297 b: self.b + (other.b - self.b) * t,
298 }
299 }
300}
301
302#[derive(Debug, Clone, Copy)]
306pub struct Xyz { pub x: f32, pub y: f32, pub z: f32 }
307
308impl Xyz {
309 pub fn from_linear_rgb(c: Rgba) -> Self {
310 Self {
311 x: c.r * 0.4124 + c.g * 0.3576 + c.b * 0.1805,
312 y: c.r * 0.2126 + c.g * 0.7152 + c.b * 0.0722,
313 z: c.r * 0.0193 + c.g * 0.1192 + c.b * 0.9505,
314 }
315 }
316
317 pub fn to_linear_rgb(self) -> Rgba {
318 Rgba::rgb(
319 self.x * 3.2406 + self.y * -1.5372 + self.z * -0.4986,
320 self.x * -0.9689 + self.y * 1.8758 + self.z * 0.0415,
321 self.x * 0.0557 + self.y * -0.2040 + self.z * 1.0570,
322 )
323 }
324}
325
326#[derive(Debug, Clone, Copy)]
330pub struct Lab { pub l: f32, pub a: f32, pub b: f32 }
331
332const D65_X: f32 = 0.95047;
333const D65_Y: f32 = 1.00000;
334const D65_Z: f32 = 1.08883;
335
336fn xyz_to_lab_f(t: f32) -> f32 {
337 if t > 0.008856 { t.cbrt() } else { 7.787 * t + 16.0 / 116.0 }
338}
339
340impl Lab {
341 pub fn from_xyz(xyz: Xyz) -> Self {
342 let fx = xyz_to_lab_f(xyz.x / D65_X);
343 let fy = xyz_to_lab_f(xyz.y / D65_Y);
344 let fz = xyz_to_lab_f(xyz.z / D65_Z);
345 Self {
346 l: 116.0 * fy - 16.0,
347 a: 500.0 * (fx - fy),
348 b: 200.0 * (fy - fz),
349 }
350 }
351
352 pub fn to_xyz(self) -> Xyz {
353 let fy = (self.l + 16.0) / 116.0;
354 let fx = self.a / 500.0 + fy;
355 let fz = fy - self.b / 200.0;
356 let cube = |v: f32| if v > 0.2069 { v * v * v } else { (v - 16.0 / 116.0) / 7.787 };
357 Xyz { x: cube(fx) * D65_X, y: cube(fy) * D65_Y, z: cube(fz) * D65_Z }
358 }
359
360 pub fn from_rgb(c: Rgba) -> Self {
361 Self::from_xyz(Xyz::from_linear_rgb(c))
362 }
363
364 pub fn to_rgb(self) -> Rgba {
365 self.to_xyz().to_linear_rgb()
366 }
367
368 pub fn delta_e(&self, other: &Lab) -> f32 {
370 let dl = self.l - other.l;
371 let da = self.a - other.a;
372 let db = self.b - other.b;
373 (dl * dl + da * da + db * db).sqrt()
374 }
375}
376
377#[derive(Debug, Clone, Copy)]
379pub struct Lch { pub l: f32, pub c: f32, pub h: f32 }
380
381impl Lch {
382 pub fn from_lab(lab: Lab) -> Self {
383 let c = (lab.a * lab.a + lab.b * lab.b).sqrt();
384 let h = lab.b.atan2(lab.a).to_degrees();
385 let h = ((h % 360.0) + 360.0) % 360.0;
386 Self { l: lab.l, c, h }
387 }
388
389 pub fn to_lab(self) -> Lab {
390 let h_rad = self.h.to_radians();
391 Lab { l: self.l, a: self.c * h_rad.cos(), b: self.c * h_rad.sin() }
392 }
393
394 pub fn from_rgb(c: Rgba) -> Self { Self::from_lab(Lab::from_rgb(c)) }
395 pub fn to_rgb(self) -> Rgba { self.to_lab().to_rgb() }
396
397 pub fn lerp_hue(self, other: Lch, t: f32) -> Lch {
398 let mut dh = other.h - self.h;
400 if dh > 180.0 { dh -= 360.0; }
401 if dh < -180.0 { dh += 360.0; }
402 Lch {
403 l: self.l + (other.l - self.l) * t,
404 c: self.c + (other.c - self.c) * t,
405 h: self.h + dh * t,
406 }
407 }
408}
409
410#[derive(Debug, Clone, Copy, PartialEq)]
414pub enum GradientMode {
415 LinearRgb,
417 Oklab,
419 Lch,
421 Hsv,
423}
424
425#[derive(Debug, Clone, Copy)]
427pub struct ColorStop {
428 pub t: f32, pub color: Rgba,
430}
431
432#[derive(Debug, Clone)]
434pub struct Gradient {
435 pub stops: Vec<ColorStop>,
436 pub mode: GradientMode,
437}
438
439impl Gradient {
440 pub fn new(mode: GradientMode) -> Self {
441 Self { stops: Vec::new(), mode }
442 }
443
444 pub fn add_stop(mut self, t: f32, color: Rgba) -> Self {
445 self.stops.push(ColorStop { t: t.clamp(0.0, 1.0), color });
446 self.stops.sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap());
447 self
448 }
449
450 pub fn sample(&self, t: f32) -> Rgba {
452 if self.stops.is_empty() { return Rgba::BLACK; }
453 if self.stops.len() == 1 { return self.stops[0].color; }
454
455 let t = t.clamp(0.0, 1.0);
456
457 let i = self.stops.partition_point(|s| s.t <= t);
459 if i == 0 { return self.stops[0].color; }
460 if i >= self.stops.len() { return self.stops.last().unwrap().color; }
461
462 let lo = &self.stops[i - 1];
463 let hi = &self.stops[i];
464 let f = (t - lo.t) / (hi.t - lo.t).max(1e-8);
465
466 match self.mode {
467 GradientMode::LinearRgb => lo.color.lerp(hi.color, f),
468 GradientMode::Oklab => {
469 let a = Oklab::from_linear_rgb(lo.color);
470 let b = Oklab::from_linear_rgb(hi.color);
471 a.lerp(b, f).to_linear_rgb()
472 }
473 GradientMode::Lch => {
474 let a = Lch::from_rgb(lo.color);
475 let b = Lch::from_rgb(hi.color);
476 a.lerp_hue(b, f).to_rgb()
477 }
478 GradientMode::Hsv => {
479 let (ha, sa, va) = rgb_to_hsv(lo.color.r, lo.color.g, lo.color.b);
480 let (hb, sb, vb) = rgb_to_hsv(hi.color.r, hi.color.g, hi.color.b);
481 let mut dh = hb - ha;
482 if dh > 180.0 { dh -= 360.0; }
483 if dh < -180.0 { dh += 360.0; }
484 let h = ha + dh * f;
485 let s = sa + (sb - sa) * f;
486 let v = va + (vb - va) * f;
487 let (r, g, b) = hsv_to_rgb(h, s, v);
488 Rgba::rgb(r, g, b)
489 }
490 }
491 }
492
493 pub fn bake_lut(&self, n: usize) -> Vec<Rgba> {
495 (0..n).map(|i| self.sample(i as f32 / (n - 1) as f32)).collect()
496 }
497}
498
499pub fn gradient_plasma() -> Gradient {
502 Gradient::new(GradientMode::Oklab)
503 .add_stop(0.0, Rgba::from_hex(0x0d0887))
504 .add_stop(0.2, Rgba::from_hex(0x6a00a8))
505 .add_stop(0.4, Rgba::from_hex(0xb12a90))
506 .add_stop(0.6, Rgba::from_hex(0xe16462))
507 .add_stop(0.8, Rgba::from_hex(0xfca636))
508 .add_stop(1.0, Rgba::from_hex(0xf0f921))
509}
510
511pub fn gradient_inferno() -> Gradient {
512 Gradient::new(GradientMode::Oklab)
513 .add_stop(0.0, Rgba::from_hex(0x000004))
514 .add_stop(0.25, Rgba::from_hex(0x420a68))
515 .add_stop(0.5, Rgba::from_hex(0x932667))
516 .add_stop(0.75, Rgba::from_hex(0xdd513a))
517 .add_stop(0.9, Rgba::from_hex(0xfca50a))
518 .add_stop(1.0, Rgba::from_hex(0xfcffa4))
519}
520
521pub fn gradient_viridis() -> Gradient {
522 Gradient::new(GradientMode::Oklab)
523 .add_stop(0.0, Rgba::from_hex(0x440154))
524 .add_stop(0.25, Rgba::from_hex(0x31688e))
525 .add_stop(0.5, Rgba::from_hex(0x35b779))
526 .add_stop(0.75, Rgba::from_hex(0x90d743))
527 .add_stop(1.0, Rgba::from_hex(0xfde725))
528}
529
530pub fn gradient_fire() -> Gradient {
531 Gradient::new(GradientMode::LinearRgb)
532 .add_stop(0.0, Rgba::BLACK)
533 .add_stop(0.3, Rgba::rgb(0.5, 0.0, 0.0))
534 .add_stop(0.6, Rgba::rgb(1.0, 0.3, 0.0))
535 .add_stop(0.8, Rgba::rgb(1.0, 0.8, 0.0))
536 .add_stop(1.0, Rgba::WHITE)
537}
538
539pub fn gradient_ice() -> Gradient {
540 Gradient::new(GradientMode::Oklab)
541 .add_stop(0.0, Rgba::BLACK)
542 .add_stop(0.4, Rgba::rgb(0.0, 0.2, 0.5))
543 .add_stop(0.7, Rgba::rgb(0.2, 0.6, 1.0))
544 .add_stop(1.0, Rgba::WHITE)
545}
546
547pub fn gradient_neon() -> Gradient {
548 Gradient::new(GradientMode::Oklab)
549 .add_stop(0.0, Rgba::from_hex(0xff00ff))
550 .add_stop(0.5, Rgba::from_hex(0x00ffff))
551 .add_stop(1.0, Rgba::from_hex(0xff00ff))
552}
553
554pub fn gradient_health() -> Gradient {
555 Gradient::new(GradientMode::Oklab)
556 .add_stop(0.0, Rgba::rgb(1.0, 0.0, 0.0))
557 .add_stop(0.5, Rgba::rgb(1.0, 0.8, 0.0))
558 .add_stop(1.0, Rgba::rgb(0.0, 1.0, 0.2))
559}
560
561pub fn complementary(c: Rgba) -> Rgba {
565 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
566 let (r, g, b) = hsv_to_rgb((h + 180.0) % 360.0, s, v);
567 Rgba::rgb(r, g, b)
568}
569
570pub fn split_complementary(c: Rgba) -> (Rgba, Rgba) {
572 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
573 let mk = |dh: f32| {
574 let (r, g, b) = hsv_to_rgb((h + dh) % 360.0, s, v);
575 Rgba::rgb(r, g, b)
576 };
577 (mk(150.0), mk(210.0))
578}
579
580pub fn triadic(c: Rgba) -> (Rgba, Rgba) {
582 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
583 let mk = |dh: f32| {
584 let (r, g, b) = hsv_to_rgb((h + dh) % 360.0, s, v);
585 Rgba::rgb(r, g, b)
586 };
587 (mk(120.0), mk(240.0))
588}
589
590pub fn analogous(c: Rgba) -> (Rgba, Rgba) {
592 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
593 let mk = |dh: f32| {
594 let (r, g, b) = hsv_to_rgb((h + dh + 360.0) % 360.0, s, v);
595 Rgba::rgb(r, g, b)
596 };
597 (mk(-30.0), mk(30.0))
598}
599
600pub fn tetradic(c: Rgba) -> [Rgba; 4] {
602 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
603 std::array::from_fn(|i| {
604 let (r, g, b) = hsv_to_rgb((h + i as f32 * 90.0) % 360.0, s, v);
605 Rgba::rgb(r, g, b)
606 })
607}
608
609#[derive(Debug, Clone)]
613pub struct Palette {
614 pub name: String,
615 pub colors: Vec<Rgba>,
616}
617
618impl Palette {
619 pub fn new(name: impl Into<String>, colors: Vec<Rgba>) -> Self {
620 Self { name: name.into(), colors }
621 }
622
623 pub fn get(&self, i: usize) -> Rgba {
625 if self.colors.is_empty() { return Rgba::WHITE; }
626 self.colors[i % self.colors.len()]
627 }
628
629 pub fn sample(&self, t: f32) -> Rgba {
631 if self.colors.is_empty() { return Rgba::WHITE; }
632 if self.colors.len() == 1 { return self.colors[0]; }
633 let t = t.fract().abs();
634 let f = t * (self.colors.len() - 1) as f32;
635 let i = f as usize;
636 let j = (i + 1).min(self.colors.len() - 1);
637 self.colors[i].lerp(self.colors[j], f.fract())
638 }
639}
640
641pub fn palette_crt() -> Palette {
643 Palette::new("CRT", vec![
644 Rgba::from_hex(0x00ff00), Rgba::from_hex(0x00ffff), Rgba::from_hex(0xff6600), Rgba::from_hex(0xffffff), ])
649}
650
651pub fn palette_ansi16() -> Palette {
653 Palette::new("ANSI16", vec![
654 Rgba::from_hex(0x000000), Rgba::from_hex(0xaa0000),
655 Rgba::from_hex(0x00aa00), Rgba::from_hex(0xaa5500),
656 Rgba::from_hex(0x0000aa), Rgba::from_hex(0xaa00aa),
657 Rgba::from_hex(0x00aaaa), Rgba::from_hex(0xaaaaaa),
658 Rgba::from_hex(0x555555), Rgba::from_hex(0xff5555),
659 Rgba::from_hex(0x55ff55), Rgba::from_hex(0xffff55),
660 Rgba::from_hex(0x5555ff), Rgba::from_hex(0xff55ff),
661 Rgba::from_hex(0x55ffff), Rgba::from_hex(0xffffff),
662 ])
663}
664
665pub fn palette_chaos_elements() -> Palette {
667 Palette::new("ChaosElements", vec![
668 Rgba::from_hex(0xff4400), Rgba::from_hex(0x00aaff), Rgba::from_hex(0x44ff44), Rgba::from_hex(0xaa00ff), Rgba::from_hex(0xffcc00), Rgba::from_hex(0x22ffcc), Rgba::from_hex(0xff00aa), ])
676}
677
678pub fn tonemap_reinhard(c: Rgba) -> Rgba {
682 let map = |x: f32| x / (x + 1.0);
683 Rgba::new(map(c.r), map(c.g), map(c.b), c.a)
684}
685
686pub fn tonemap_aces(c: Rgba) -> Rgba {
688 let aces = |x: f32| -> f32 {
689 const A: f32 = 2.51;
690 const B: f32 = 0.03;
691 const C: f32 = 2.43;
692 const D: f32 = 0.59;
693 const E: f32 = 0.14;
694 ((x * (A * x + B)) / (x * (C * x + D) + E)).clamp(0.0, 1.0)
695 };
696 Rgba::new(aces(c.r), aces(c.g), aces(c.b), c.a)
697}
698
699pub fn tonemap_uncharted2(c: Rgba) -> Rgba {
701 fn partial(x: f32) -> f32 {
702 const A: f32 = 0.15; const B: f32 = 0.50;
703 const C: f32 = 0.10; const D: f32 = 0.20;
704 const E: f32 = 0.02; const F: f32 = 0.30;
705 ((x*(A*x+C*B)+D*E) / (x*(A*x+B)+D*F)) - E/F
706 }
707 let exposure_bias = 2.0_f32;
708 let curr = |x: f32| partial(x * exposure_bias);
709 let white = partial(11.2);
710 let scale = 1.0 / white;
711 Rgba::new(curr(c.r)*scale, curr(c.g)*scale, curr(c.b)*scale, c.a)
712}
713
714pub fn distance_rgb(a: Rgba, b: Rgba) -> f32 {
718 let dr = a.r - b.r; let dg = a.g - b.g; let db = a.b - b.b;
719 (dr*dr + dg*dg + db*db).sqrt()
720}
721
722pub fn distance_lab_e76(a: Rgba, b: Rgba) -> f32 {
724 Lab::from_rgb(a).delta_e(&Lab::from_rgb(b))
725}
726
727pub fn nearest_in_palette(color: Rgba, palette: &Palette) -> usize {
729 let lab = Lab::from_rgb(color);
730 palette.colors.iter()
731 .enumerate()
732 .min_by(|(_, &a), (_, &b)| {
733 let da = lab.delta_e(&Lab::from_rgb(a));
734 let db = lab.delta_e(&Lab::from_rgb(b));
735 da.partial_cmp(&db).unwrap()
736 })
737 .map(|(i, _)| i)
738 .unwrap_or(0)
739}
740
741pub fn adjust_hue(c: Rgba, delta: f32) -> Rgba {
745 let (h, s, v) = rgb_to_hsv(c.r, c.g, c.b);
746 let (r, g, b) = hsv_to_rgb((h + delta + 360.0) % 360.0, s, v);
747 Rgba::new(r, g, b, c.a)
748}
749
750pub fn adjust_saturation(c: Rgba, factor: f32) -> Rgba {
752 let lum = c.luminance();
753 Rgba::new(
754 lum + (c.r - lum) * factor,
755 lum + (c.g - lum) * factor,
756 lum + (c.b - lum) * factor,
757 c.a,
758 )
759}
760
761pub fn adjust_brightness(c: Rgba, delta: f32) -> Rgba {
763 Rgba::new((c.r + delta).clamp(0.0, 1.0),
764 (c.g + delta).clamp(0.0, 1.0),
765 (c.b + delta).clamp(0.0, 1.0),
766 c.a)
767}
768
769pub fn adjust_contrast(c: Rgba, factor: f32) -> Rgba {
771 let adj = |x: f32| ((x - 0.5) * factor + 0.5).clamp(0.0, 1.0);
772 Rgba::new(adj(c.r), adj(c.g), adj(c.b), c.a)
773}
774
775pub fn tint_white(c: Rgba, factor: f32) -> Rgba {
777 c.lerp(Rgba::WHITE, factor)
778}
779
780pub fn shade_black(c: Rgba, factor: f32) -> Rgba {
782 c.lerp(Rgba::BLACK, factor)
783}
784
785#[cfg(test)]
788mod tests {
789 use super::*;
790
791 fn approx_eq(a: f32, b: f32) -> bool { (a - b).abs() < 0.005 }
792
793 #[test]
794 fn hsv_roundtrip() {
795 let (h0, s0, v0) = (200.0f32, 0.7, 0.8);
796 let (r, g, b) = hsv_to_rgb(h0, s0, v0);
797 let (h1, s1, v1) = rgb_to_hsv(r, g, b);
798 assert!(approx_eq(h0, h1), "hue mismatch: {h0} vs {h1}");
799 assert!(approx_eq(s0, s1));
800 assert!(approx_eq(v0, v1));
801 }
802
803 #[test]
804 fn hsl_roundtrip() {
805 let (r, g, b) = hsl_to_rgb(120.0, 0.5, 0.5);
806 let (h, s, l) = rgb_to_hsl(r, g, b);
807 assert!(approx_eq(h, 120.0), "hue mismatch: {h}");
808 assert!(approx_eq(s, 0.5));
809 assert!(approx_eq(l, 0.5));
810 }
811
812 #[test]
813 fn oklab_roundtrip() {
814 let c = Rgba::from_hex(0x3a7bd5);
815 let oklab = Oklab::from_linear_rgb(c);
816 let back = oklab.to_linear_rgb();
817 assert!(approx_eq(c.r, back.r), "r mismatch: {} vs {}", c.r, back.r);
818 assert!(approx_eq(c.g, back.g));
819 assert!(approx_eq(c.b, back.b));
820 }
821
822 #[test]
823 fn gradient_endpoints() {
824 let g = gradient_fire();
825 let lo = g.sample(0.0);
826 let hi = g.sample(1.0);
827 assert!(lo.luminance() < 0.01);
828 assert!(hi.luminance() > 0.9);
829 }
830
831 #[test]
832 fn complementary_is_180_degrees() {
833 let c = Rgba::from_hex(0xff0000); let comp = complementary(c);
835 let (h, _, _) = rgb_to_hsv(comp.r, comp.g, comp.b);
836 assert!((h - 180.0).abs() < 2.0, "hue={h}");
838 }
839
840 #[test]
841 fn tonemap_aces_bounds() {
842 let bright = Rgba::rgb(10.0, 5.0, 2.0); let tm = tonemap_aces(bright);
844 assert!(tm.r <= 1.0);
845 assert!(tm.g <= 1.0);
846 assert!(tm.b <= 1.0);
847 }
848}