Skip to main content

agg_rust/
color.rs

1//! Color types and operations.
2//!
3//! Port of `agg_color_rgba.h`, `agg_color_rgba.cpp`, and `agg_color_gray.h`.
4//!
5//! Provides RGBA and grayscale color types at different precisions:
6//! - `Rgba` — f64 components (linear working space)
7//! - `Rgba8` — u8 components (8-bit per channel)
8//! - `Rgba16` — u16 components (16-bit per channel)
9//! - `Gray8` — u8 grayscale + alpha
10//! - `Gray16` — u16 grayscale + alpha
11//!
12//! Note: sRGB colorspace variants (`srgba8`, `sgray8`) from the C++ code are
13//! not included in Phase 1. They will be added when needed (they require sRGB
14//! lookup table infrastructure).
15
16use crate::basics::{uround, CoverType, COVER_MASK};
17
18// ============================================================================
19// Component orders (for pixel format layer)
20// ============================================================================
21
22/// RGB component order: R=0, G=1, B=2
23pub struct OrderRgb;
24impl OrderRgb {
25    pub const R: usize = 0;
26    pub const G: usize = 1;
27    pub const B: usize = 2;
28    pub const N: usize = 3;
29}
30
31/// BGR component order: B=0, G=1, R=2
32pub struct OrderBgr;
33impl OrderBgr {
34    pub const B: usize = 0;
35    pub const G: usize = 1;
36    pub const R: usize = 2;
37    pub const N: usize = 3;
38}
39
40/// RGBA component order: R=0, G=1, B=2, A=3
41pub struct OrderRgba;
42impl OrderRgba {
43    pub const R: usize = 0;
44    pub const G: usize = 1;
45    pub const B: usize = 2;
46    pub const A: usize = 3;
47    pub const N: usize = 4;
48}
49
50/// ARGB component order: A=0, R=1, G=2, B=3
51pub struct OrderArgb;
52impl OrderArgb {
53    pub const A: usize = 0;
54    pub const R: usize = 1;
55    pub const G: usize = 2;
56    pub const B: usize = 3;
57    pub const N: usize = 4;
58}
59
60/// ABGR component order: A=0, B=1, G=2, R=3
61pub struct OrderAbgr;
62impl OrderAbgr {
63    pub const A: usize = 0;
64    pub const B: usize = 1;
65    pub const G: usize = 2;
66    pub const R: usize = 3;
67    pub const N: usize = 4;
68}
69
70/// BGRA component order: B=0, G=1, R=2, A=3
71pub struct OrderBgra;
72impl OrderBgra {
73    pub const B: usize = 0;
74    pub const G: usize = 1;
75    pub const R: usize = 2;
76    pub const A: usize = 3;
77    pub const N: usize = 4;
78}
79
80// ============================================================================
81// Rgba (f64 precision color)
82// ============================================================================
83
84/// RGBA color with f64 components in range [0, 1].
85/// Port of C++ `rgba`.
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub struct Rgba {
88    pub r: f64,
89    pub g: f64,
90    pub b: f64,
91    pub a: f64,
92}
93
94impl Rgba {
95    pub fn new(r: f64, g: f64, b: f64, a: f64) -> Self {
96        Self { r, g, b, a }
97    }
98
99    pub fn new_rgb(r: f64, g: f64, b: f64) -> Self {
100        Self { r, g, b, a: 1.0 }
101    }
102
103    pub fn with_opacity(c: &Rgba, a: f64) -> Self {
104        Self {
105            r: c.r,
106            g: c.g,
107            b: c.b,
108            a,
109        }
110    }
111
112    pub fn clear(&mut self) -> &mut Self {
113        self.r = 0.0;
114        self.g = 0.0;
115        self.b = 0.0;
116        self.a = 0.0;
117        self
118    }
119
120    pub fn transparent(&mut self) -> &mut Self {
121        self.a = 0.0;
122        self
123    }
124
125    pub fn set_opacity(&mut self, a: f64) -> &mut Self {
126        if a < 0.0 {
127            self.a = 0.0;
128        } else if a > 1.0 {
129            self.a = 1.0;
130        } else {
131            self.a = a;
132        }
133        self
134    }
135
136    pub fn opacity(&self) -> f64 {
137        self.a
138    }
139
140    pub fn premultiply(&mut self) -> &mut Self {
141        self.r *= self.a;
142        self.g *= self.a;
143        self.b *= self.a;
144        self
145    }
146
147    pub fn premultiply_with_alpha(&mut self, a: f64) -> &mut Self {
148        if self.a <= 0.0 || a <= 0.0 {
149            self.r = 0.0;
150            self.g = 0.0;
151            self.b = 0.0;
152            self.a = 0.0;
153        } else {
154            let scale = a / self.a;
155            self.r *= scale;
156            self.g *= scale;
157            self.b *= scale;
158            self.a = scale;
159        }
160        self
161    }
162
163    pub fn demultiply(&mut self) -> &mut Self {
164        if self.a == 0.0 {
165            self.r = 0.0;
166            self.g = 0.0;
167            self.b = 0.0;
168        } else {
169            let inv_a = 1.0 / self.a;
170            self.r *= inv_a;
171            self.g *= inv_a;
172            self.b *= inv_a;
173        }
174        self
175    }
176
177    /// Interpolate between `self` and `c` by parameter `k`.
178    pub fn gradient(&self, c: &Rgba, k: f64) -> Rgba {
179        Rgba {
180            r: self.r + (c.r - self.r) * k,
181            g: self.g + (c.g - self.g) * k,
182            b: self.b + (c.b - self.b) * k,
183            a: self.a + (c.a - self.a) * k,
184        }
185    }
186
187    pub fn no_color() -> Self {
188        Self {
189            r: 0.0,
190            g: 0.0,
191            b: 0.0,
192            a: 0.0,
193        }
194    }
195
196    /// Create a color from a visible light wavelength (380–780 nm).
197    pub fn from_wavelength(wl: f64, gamma: f64) -> Self {
198        let mut t = Rgba::new(0.0, 0.0, 0.0, 1.0);
199
200        if (380.0..=440.0).contains(&wl) {
201            t.r = -(wl - 440.0) / (440.0 - 380.0);
202            t.b = 1.0;
203        } else if (440.0..=490.0).contains(&wl) {
204            t.g = (wl - 440.0) / (490.0 - 440.0);
205            t.b = 1.0;
206        } else if (490.0..=510.0).contains(&wl) {
207            t.g = 1.0;
208            t.b = -(wl - 510.0) / (510.0 - 490.0);
209        } else if (510.0..=580.0).contains(&wl) {
210            t.r = (wl - 510.0) / (580.0 - 510.0);
211            t.g = 1.0;
212        } else if (580.0..=645.0).contains(&wl) {
213            t.r = 1.0;
214            t.g = -(wl - 645.0) / (645.0 - 580.0);
215        } else if (645.0..=780.0).contains(&wl) {
216            t.r = 1.0;
217        }
218
219        let s = if wl > 700.0 {
220            0.3 + 0.7 * (780.0 - wl) / (780.0 - 700.0)
221        } else if wl < 420.0 {
222            0.3 + 0.7 * (wl - 380.0) / (420.0 - 380.0)
223        } else {
224            1.0
225        };
226
227        t.r = (t.r * s).powf(gamma);
228        t.g = (t.g * s).powf(gamma);
229        t.b = (t.b * s).powf(gamma);
230        t
231    }
232}
233
234impl Default for Rgba {
235    fn default() -> Self {
236        Self::no_color()
237    }
238}
239
240impl core::ops::Add for Rgba {
241    type Output = Self;
242    fn add(self, rhs: Self) -> Self {
243        Self {
244            r: self.r + rhs.r,
245            g: self.g + rhs.g,
246            b: self.b + rhs.b,
247            a: self.a + rhs.a,
248        }
249    }
250}
251
252impl core::ops::AddAssign for Rgba {
253    fn add_assign(&mut self, rhs: Self) {
254        self.r += rhs.r;
255        self.g += rhs.g;
256        self.b += rhs.b;
257        self.a += rhs.a;
258    }
259}
260
261impl core::ops::Mul<f64> for Rgba {
262    type Output = Self;
263    fn mul(self, k: f64) -> Self {
264        Self {
265            r: self.r * k,
266            g: self.g * k,
267            b: self.b * k,
268            a: self.a * k,
269        }
270    }
271}
272
273impl core::ops::MulAssign<f64> for Rgba {
274    fn mul_assign(&mut self, k: f64) {
275        self.r *= k;
276        self.g *= k;
277        self.b *= k;
278        self.a *= k;
279    }
280}
281
282/// Create a pre-multiplied Rgba color.
283pub fn rgba_pre(r: f64, g: f64, b: f64, a: f64) -> Rgba {
284    let mut c = Rgba::new(r, g, b, a);
285    c.premultiply();
286    c
287}
288
289// ============================================================================
290// Rgba8 (8-bit per channel)
291// ============================================================================
292
293/// RGBA color with u8 components.
294/// Port of C++ `rgba8T<linear>` (linear colorspace variant).
295#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub struct Rgba8 {
297    pub r: u8,
298    pub g: u8,
299    pub b: u8,
300    pub a: u8,
301}
302
303impl Rgba8 {
304    pub const BASE_SHIFT: u32 = 8;
305    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
306    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
307    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
308
309    pub fn new(r: u32, g: u32, b: u32, a: u32) -> Self {
310        Self {
311            r: r as u8,
312            g: g as u8,
313            b: b as u8,
314            a: a as u8,
315        }
316    }
317
318    pub fn new_opaque(r: u32, g: u32, b: u32) -> Self {
319        Self::new(r, g, b, Self::BASE_MASK)
320    }
321
322    pub fn with_opacity(c: &Rgba8, a: u32) -> Self {
323        Self {
324            r: c.r,
325            g: c.g,
326            b: c.b,
327            a: a as u8,
328        }
329    }
330
331    /// Convert from `Rgba` (f64) to `Rgba8` (u8).
332    pub fn from_rgba(c: &Rgba) -> Self {
333        Self {
334            r: uround(c.r * Self::BASE_MASK as f64) as u8,
335            g: uround(c.g * Self::BASE_MASK as f64) as u8,
336            b: uround(c.b * Self::BASE_MASK as f64) as u8,
337            a: uround(c.a * Self::BASE_MASK as f64) as u8,
338        }
339    }
340
341    /// Convert to `Rgba` (f64).
342    pub fn to_rgba(&self) -> Rgba {
343        Rgba {
344            r: self.r as f64 / 255.0,
345            g: self.g as f64 / 255.0,
346            b: self.b as f64 / 255.0,
347            a: self.a as f64 / 255.0,
348        }
349    }
350
351    #[inline]
352    pub fn to_double(a: u8) -> f64 {
353        a as f64 / Self::BASE_MASK as f64
354    }
355
356    #[inline]
357    pub fn from_double(a: f64) -> u8 {
358        uround(a * Self::BASE_MASK as f64) as u8
359    }
360
361    pub fn empty_value() -> u8 {
362        0
363    }
364
365    pub fn full_value() -> u8 {
366        Self::BASE_MASK as u8
367    }
368
369    pub fn is_transparent(&self) -> bool {
370        self.a == 0
371    }
372
373    pub fn is_opaque(&self) -> bool {
374        self.a == Self::BASE_MASK as u8
375    }
376
377    pub fn invert(x: u8) -> u8 {
378        Self::BASE_MASK as u8 - x
379    }
380
381    /// Fixed-point multiply, exact over u8.
382    /// `(a * b + 128) >> 8`, with rounding correction.
383    #[inline]
384    pub fn multiply(a: u8, b: u8) -> u8 {
385        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
386        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u8
387    }
388
389    /// Fixed-point demultiply.
390    #[inline]
391    pub fn demultiply_value(a: u8, b: u8) -> u8 {
392        if (a as u32) * (b as u32) == 0 {
393            0
394        } else if a >= b {
395            Self::BASE_MASK as u8
396        } else {
397            (a as u32 * Self::BASE_MASK + (b as u32 >> 1)) as u8 / b
398        }
399    }
400
401    /// Multiply a color component by a cover.
402    #[inline]
403    pub fn mult_cover(a: u8, b: CoverType) -> u8 {
404        Self::multiply(a, b)
405    }
406
407    /// Scale a cover by a value.
408    #[inline]
409    pub fn scale_cover(a: CoverType, b: u8) -> CoverType {
410        Self::multiply(b, a)
411    }
412
413    /// Interpolate p to q by a, assuming q is premultiplied by a.
414    #[inline]
415    pub fn prelerp(p: u8, q: u8, a: u8) -> u8 {
416        p.wrapping_add(q).wrapping_sub(Self::multiply(p, a))
417    }
418
419    /// Interpolate p to q by a.
420    #[inline]
421    pub fn lerp(p: u8, q: u8, a: u8) -> u8 {
422        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
423        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u8
424    }
425
426    pub fn clear(&mut self) -> &mut Self {
427        self.r = 0;
428        self.g = 0;
429        self.b = 0;
430        self.a = 0;
431        self
432    }
433
434    pub fn transparent(&mut self) -> &mut Self {
435        self.a = 0;
436        self
437    }
438
439    pub fn set_opacity(&mut self, a: f64) -> &mut Self {
440        if a < 0.0 {
441            self.a = 0;
442        } else if a > 1.0 {
443            self.a = 1;
444        } else {
445            self.a = uround(a * Self::BASE_MASK as f64) as u8;
446        }
447        self
448    }
449
450    pub fn opacity(&self) -> f64 {
451        self.a as f64 / Self::BASE_MASK as f64
452    }
453
454    pub fn premultiply(&mut self) -> &mut Self {
455        if self.a != Self::BASE_MASK as u8 {
456            if self.a == 0 {
457                self.r = 0;
458                self.g = 0;
459                self.b = 0;
460            } else {
461                self.r = Self::multiply(self.r, self.a);
462                self.g = Self::multiply(self.g, self.a);
463                self.b = Self::multiply(self.b, self.a);
464            }
465        }
466        self
467    }
468
469    pub fn premultiply_with_alpha(&mut self, a_: u32) -> &mut Self {
470        if self.a as u32 != Self::BASE_MASK || a_ < Self::BASE_MASK {
471            if self.a == 0 || a_ == 0 {
472                self.r = 0;
473                self.g = 0;
474                self.b = 0;
475                self.a = 0;
476            } else {
477                let r_ = (self.r as u32 * a_) / self.a as u32;
478                let g_ = (self.g as u32 * a_) / self.a as u32;
479                let b_ = (self.b as u32 * a_) / self.a as u32;
480                self.r = if r_ > a_ { a_ as u8 } else { r_ as u8 };
481                self.g = if g_ > a_ { a_ as u8 } else { g_ as u8 };
482                self.b = if b_ > a_ { a_ as u8 } else { b_ as u8 };
483                self.a = a_ as u8;
484            }
485        }
486        self
487    }
488
489    pub fn demultiply(&mut self) -> &mut Self {
490        if (self.a as u32) < Self::BASE_MASK {
491            if self.a == 0 {
492                self.r = 0;
493                self.g = 0;
494                self.b = 0;
495            } else {
496                let r_ = (self.r as u32 * Self::BASE_MASK) / self.a as u32;
497                let g_ = (self.g as u32 * Self::BASE_MASK) / self.a as u32;
498                let b_ = (self.b as u32 * Self::BASE_MASK) / self.a as u32;
499                self.r = r_.min(Self::BASE_MASK) as u8;
500                self.g = g_.min(Self::BASE_MASK) as u8;
501                self.b = b_.min(Self::BASE_MASK) as u8;
502            }
503        }
504        self
505    }
506
507    /// Interpolate between `self` and `c` by parameter `k` (0.0 to 1.0).
508    pub fn gradient(&self, c: &Rgba8, k: f64) -> Rgba8 {
509        let ik = uround(k * Self::BASE_MASK as f64) as u8;
510        Rgba8 {
511            r: Self::lerp(self.r, c.r, ik),
512            g: Self::lerp(self.g, c.g, ik),
513            b: Self::lerp(self.b, c.b, ik),
514            a: Self::lerp(self.a, c.a, ik),
515        }
516    }
517
518    /// Add color `c` with coverage `cover`.
519    pub fn add(&mut self, c: &Rgba8, cover: u32) {
520        let cr: u32;
521        let cg: u32;
522        let cb: u32;
523        let ca: u32;
524        if cover == COVER_MASK {
525            if c.a as u32 == Self::BASE_MASK {
526                *self = *c;
527                return;
528            } else {
529                cr = self.r as u32 + c.r as u32;
530                cg = self.g as u32 + c.g as u32;
531                cb = self.b as u32 + c.b as u32;
532                ca = self.a as u32 + c.a as u32;
533            }
534        } else {
535            cr = self.r as u32 + Self::mult_cover(c.r, cover as u8) as u32;
536            cg = self.g as u32 + Self::mult_cover(c.g, cover as u8) as u32;
537            cb = self.b as u32 + Self::mult_cover(c.b, cover as u8) as u32;
538            ca = self.a as u32 + Self::mult_cover(c.a, cover as u8) as u32;
539        }
540        self.r = cr.min(Self::BASE_MASK) as u8;
541        self.g = cg.min(Self::BASE_MASK) as u8;
542        self.b = cb.min(Self::BASE_MASK) as u8;
543        self.a = ca.min(Self::BASE_MASK) as u8;
544    }
545
546    /// Apply forward gamma correction.
547    pub fn apply_gamma_dir(&mut self, gamma: &crate::gamma::GammaLut) {
548        self.r = gamma.dir(self.r);
549        self.g = gamma.dir(self.g);
550        self.b = gamma.dir(self.b);
551    }
552
553    /// Apply inverse gamma correction.
554    pub fn apply_gamma_inv(&mut self, gamma: &crate::gamma::GammaLut) {
555        self.r = gamma.inv(self.r);
556        self.g = gamma.inv(self.g);
557        self.b = gamma.inv(self.b);
558    }
559
560    pub fn no_color() -> Self {
561        Self {
562            r: 0,
563            g: 0,
564            b: 0,
565            a: 0,
566        }
567    }
568
569    pub fn from_wavelength(wl: f64, gamma: f64) -> Self {
570        Self::from_rgba(&Rgba::from_wavelength(wl, gamma))
571    }
572}
573
574impl Default for Rgba8 {
575    fn default() -> Self {
576        Self::no_color()
577    }
578}
579
580/// Create an Rgba8 from a packed RGB value (0xRRGGBB).
581pub fn rgb8_packed(v: u32) -> Rgba8 {
582    Rgba8::new((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF, 255)
583}
584
585/// Create an Rgba8 from a packed BGR value (0xBBGGRR).
586pub fn bgr8_packed(v: u32) -> Rgba8 {
587    Rgba8::new(v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, 255)
588}
589
590/// Create an Rgba8 from a packed ARGB value (0xAARRGGBB).
591pub fn argb8_packed(v: u32) -> Rgba8 {
592    Rgba8::new((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF, v >> 24)
593}
594
595// ============================================================================
596// Rgba16 (16-bit per channel)
597// ============================================================================
598
599/// RGBA color with u16 components.
600/// Port of C++ `rgba16`.
601#[derive(Debug, Clone, Copy, PartialEq, Eq)]
602pub struct Rgba16 {
603    pub r: u16,
604    pub g: u16,
605    pub b: u16,
606    pub a: u16,
607}
608
609impl Rgba16 {
610    pub const BASE_SHIFT: u32 = 16;
611    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
612    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
613    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
614
615    pub fn new(r: u32, g: u32, b: u32, a: u32) -> Self {
616        Self {
617            r: r as u16,
618            g: g as u16,
619            b: b as u16,
620            a: a as u16,
621        }
622    }
623
624    pub fn new_opaque(r: u32, g: u32, b: u32) -> Self {
625        Self::new(r, g, b, Self::BASE_MASK)
626    }
627
628    /// Convert from Rgba (f64).
629    pub fn from_rgba(c: &Rgba) -> Self {
630        Self {
631            r: uround(c.r * Self::BASE_MASK as f64) as u16,
632            g: uround(c.g * Self::BASE_MASK as f64) as u16,
633            b: uround(c.b * Self::BASE_MASK as f64) as u16,
634            a: uround(c.a * Self::BASE_MASK as f64) as u16,
635        }
636    }
637
638    /// Convert from Rgba8 (u8) by expanding 8-bit to 16-bit.
639    pub fn from_rgba8(c: &Rgba8) -> Self {
640        Self {
641            r: ((c.r as u16) << 8) | c.r as u16,
642            g: ((c.g as u16) << 8) | c.g as u16,
643            b: ((c.b as u16) << 8) | c.b as u16,
644            a: ((c.a as u16) << 8) | c.a as u16,
645        }
646    }
647
648    pub fn to_rgba(&self) -> Rgba {
649        Rgba {
650            r: self.r as f64 / 65535.0,
651            g: self.g as f64 / 65535.0,
652            b: self.b as f64 / 65535.0,
653            a: self.a as f64 / 65535.0,
654        }
655    }
656
657    pub fn to_rgba8(&self) -> Rgba8 {
658        Rgba8::new(
659            (self.r >> 8) as u32,
660            (self.g >> 8) as u32,
661            (self.b >> 8) as u32,
662            (self.a >> 8) as u32,
663        )
664    }
665
666    pub fn is_transparent(&self) -> bool {
667        self.a == 0
668    }
669
670    pub fn is_opaque(&self) -> bool {
671        self.a == Self::BASE_MASK as u16
672    }
673
674    pub fn invert(x: u16) -> u16 {
675        Self::BASE_MASK as u16 - x
676    }
677
678    /// Fixed-point multiply, exact over u16.
679    #[inline]
680    pub fn multiply(a: u16, b: u16) -> u16 {
681        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
682        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u16
683    }
684
685    /// Interpolate p to q by a.
686    #[inline]
687    pub fn lerp(p: u16, q: u16, a: u16) -> u16 {
688        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
689        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u16
690    }
691
692    /// Multiply a color component by a cover (8-bit).
693    #[inline]
694    pub fn mult_cover(a: u16, b: CoverType) -> u16 {
695        Self::multiply(a, (b as u16) << 8 | b as u16)
696    }
697
698    pub fn clear(&mut self) -> &mut Self {
699        self.r = 0;
700        self.g = 0;
701        self.b = 0;
702        self.a = 0;
703        self
704    }
705
706    pub fn premultiply(&mut self) -> &mut Self {
707        if self.a as u32 != Self::BASE_MASK {
708            if self.a == 0 {
709                self.r = 0;
710                self.g = 0;
711                self.b = 0;
712            } else {
713                self.r = Self::multiply(self.r, self.a);
714                self.g = Self::multiply(self.g, self.a);
715                self.b = Self::multiply(self.b, self.a);
716            }
717        }
718        self
719    }
720
721    pub fn demultiply(&mut self) -> &mut Self {
722        if (self.a as u32) < Self::BASE_MASK {
723            if self.a == 0 {
724                self.r = 0;
725                self.g = 0;
726                self.b = 0;
727            } else {
728                let r_ = (self.r as u32 * Self::BASE_MASK) / self.a as u32;
729                let g_ = (self.g as u32 * Self::BASE_MASK) / self.a as u32;
730                let b_ = (self.b as u32 * Self::BASE_MASK) / self.a as u32;
731                self.r = r_.min(Self::BASE_MASK) as u16;
732                self.g = g_.min(Self::BASE_MASK) as u16;
733                self.b = b_.min(Self::BASE_MASK) as u16;
734            }
735        }
736        self
737    }
738
739    pub fn gradient(&self, c: &Rgba16, k: f64) -> Rgba16 {
740        let ik = uround(k * Self::BASE_MASK as f64) as u16;
741        Rgba16 {
742            r: Self::lerp(self.r, c.r, ik),
743            g: Self::lerp(self.g, c.g, ik),
744            b: Self::lerp(self.b, c.b, ik),
745            a: Self::lerp(self.a, c.a, ik),
746        }
747    }
748
749    pub fn no_color() -> Self {
750        Self {
751            r: 0,
752            g: 0,
753            b: 0,
754            a: 0,
755        }
756    }
757
758    pub fn from_wavelength(wl: f64, gamma: f64) -> Self {
759        Self::from_rgba(&Rgba::from_wavelength(wl, gamma))
760    }
761}
762
763impl Default for Rgba16 {
764    fn default() -> Self {
765        Self::no_color()
766    }
767}
768
769// ============================================================================
770// Gray8 (8-bit grayscale)
771// ============================================================================
772
773/// Grayscale color with u8 components (value + alpha).
774/// Port of C++ `gray8T<linear>`.
775#[derive(Debug, Clone, Copy, PartialEq, Eq)]
776pub struct Gray8 {
777    pub v: u8,
778    pub a: u8,
779}
780
781impl Gray8 {
782    pub const BASE_SHIFT: u32 = 8;
783    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
784    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
785    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
786
787    pub fn new(v: u32, a: u32) -> Self {
788        Self {
789            v: v as u8,
790            a: a as u8,
791        }
792    }
793
794    pub fn new_opaque(v: u32) -> Self {
795        Self::new(v, Self::BASE_MASK)
796    }
797
798    /// Calculate luminance from linear RGB (ITU-R BT.709).
799    pub fn luminance_from_rgba(c: &Rgba) -> u8 {
800        uround((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * Self::BASE_MASK as f64) as u8
801    }
802
803    /// Calculate luminance from Rgba8 (ITU-R BT.709 with integer coefficients).
804    pub fn luminance_from_rgba8(c: &Rgba8) -> u8 {
805        ((55u32 * c.r as u32 + 184u32 * c.g as u32 + 18u32 * c.b as u32) >> 8) as u8
806    }
807
808    pub fn from_rgba(c: &Rgba) -> Self {
809        Self {
810            v: Self::luminance_from_rgba(c),
811            a: uround(c.a * Self::BASE_MASK as f64) as u8,
812        }
813    }
814
815    pub fn from_rgba8(c: &Rgba8) -> Self {
816        Self {
817            v: Self::luminance_from_rgba8(c),
818            a: c.a,
819        }
820    }
821
822    pub fn is_transparent(&self) -> bool {
823        self.a == 0
824    }
825
826    pub fn is_opaque(&self) -> bool {
827        self.a == Self::BASE_MASK as u8
828    }
829
830    #[inline]
831    pub fn multiply(a: u8, b: u8) -> u8 {
832        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
833        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u8
834    }
835
836    #[inline]
837    pub fn lerp(p: u8, q: u8, a: u8) -> u8 {
838        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
839        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u8
840    }
841
842    #[inline]
843    pub fn mult_cover(a: u8, b: CoverType) -> u8 {
844        Self::multiply(a, b)
845    }
846
847    pub fn clear(&mut self) -> &mut Self {
848        self.v = 0;
849        self.a = 0;
850        self
851    }
852
853    pub fn premultiply(&mut self) -> &mut Self {
854        if (self.a as u32) < Self::BASE_MASK {
855            if self.a == 0 {
856                self.v = 0;
857            } else {
858                self.v = Self::multiply(self.v, self.a);
859            }
860        }
861        self
862    }
863
864    pub fn demultiply(&mut self) -> &mut Self {
865        if (self.a as u32) < Self::BASE_MASK {
866            if self.a == 0 {
867                self.v = 0;
868            } else {
869                let v_ = (self.v as u32 * Self::BASE_MASK) / self.a as u32;
870                self.v = v_.min(Self::BASE_MASK) as u8;
871            }
872        }
873        self
874    }
875
876    pub fn gradient(&self, c: &Gray8, k: f64) -> Gray8 {
877        let ik = uround(k * Self::BASE_SCALE as f64) as u8;
878        Gray8 {
879            v: Self::lerp(self.v, c.v, ik),
880            a: Self::lerp(self.a, c.a, ik),
881        }
882    }
883
884    pub fn no_color() -> Self {
885        Self { v: 0, a: 0 }
886    }
887}
888
889impl Default for Gray8 {
890    fn default() -> Self {
891        Self::no_color()
892    }
893}
894
895// ============================================================================
896// Gray16 (16-bit grayscale)
897// ============================================================================
898
899/// Grayscale color with u16 components (value + alpha).
900/// Port of C++ `gray16`.
901#[derive(Debug, Clone, Copy, PartialEq, Eq)]
902pub struct Gray16 {
903    pub v: u16,
904    pub a: u16,
905}
906
907impl Gray16 {
908    pub const BASE_SHIFT: u32 = 16;
909    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
910    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
911    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
912
913    pub fn new(v: u32, a: u32) -> Self {
914        Self {
915            v: v as u16,
916            a: a as u16,
917        }
918    }
919
920    pub fn new_opaque(v: u32) -> Self {
921        Self::new(v, Self::BASE_MASK)
922    }
923
924    /// Calculate luminance from Rgba (ITU-R BT.709).
925    pub fn luminance_from_rgba(c: &Rgba) -> u16 {
926        uround((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * Self::BASE_MASK as f64) as u16
927    }
928
929    /// Calculate luminance from Rgba16 (ITU-R BT.709 with integer coefficients).
930    pub fn luminance_from_rgba16(c: &Rgba16) -> u16 {
931        ((13933u32 * c.r as u32 + 46872u32 * c.g as u32 + 4732u32 * c.b as u32) >> 16) as u16
932    }
933
934    pub fn from_rgba(c: &Rgba) -> Self {
935        Self {
936            v: Self::luminance_from_rgba(c),
937            a: uround(c.a * Self::BASE_MASK as f64) as u16,
938        }
939    }
940
941    pub fn from_rgba8(c: &Rgba8) -> Self {
942        Self::from_rgba16(&Rgba16::from_rgba8(c))
943    }
944
945    pub fn from_rgba16(c: &Rgba16) -> Self {
946        Self {
947            v: Self::luminance_from_rgba16(c),
948            a: c.a,
949        }
950    }
951
952    pub fn from_gray8(c: &Gray8) -> Self {
953        Self {
954            v: ((c.v as u16) << 8) | c.v as u16,
955            a: ((c.a as u16) << 8) | c.a as u16,
956        }
957    }
958
959    pub fn is_transparent(&self) -> bool {
960        self.a == 0
961    }
962
963    pub fn is_opaque(&self) -> bool {
964        self.a == Self::BASE_MASK as u16
965    }
966
967    #[inline]
968    pub fn multiply(a: u16, b: u16) -> u16 {
969        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
970        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u16
971    }
972
973    #[inline]
974    pub fn lerp(p: u16, q: u16, a: u16) -> u16 {
975        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
976        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u16
977    }
978
979    pub fn clear(&mut self) -> &mut Self {
980        self.v = 0;
981        self.a = 0;
982        self
983    }
984
985    pub fn premultiply(&mut self) -> &mut Self {
986        if (self.a as u32) < Self::BASE_MASK {
987            if self.a == 0 {
988                self.v = 0;
989            } else {
990                self.v = Self::multiply(self.v, self.a);
991            }
992        }
993        self
994    }
995
996    pub fn demultiply(&mut self) -> &mut Self {
997        if (self.a as u32) < Self::BASE_MASK {
998            if self.a == 0 {
999                self.v = 0;
1000            } else {
1001                let v_ = (self.v as u32 * Self::BASE_MASK) / self.a as u32;
1002                self.v = v_.min(Self::BASE_MASK) as u16;
1003            }
1004        }
1005        self
1006    }
1007
1008    pub fn gradient(&self, c: &Gray16, k: f64) -> Gray16 {
1009        let ik = uround(k * Self::BASE_SCALE as f64) as u16;
1010        Gray16 {
1011            v: Self::lerp(self.v, c.v, ik),
1012            a: Self::lerp(self.a, c.a, ik),
1013        }
1014    }
1015
1016    pub fn no_color() -> Self {
1017        Self { v: 0, a: 0 }
1018    }
1019}
1020
1021impl Default for Gray16 {
1022    fn default() -> Self {
1023        Self::no_color()
1024    }
1025}
1026
1027// ============================================================================
1028// Tests
1029// ============================================================================
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034
1035    #[test]
1036    fn test_rgba_new() {
1037        let c = Rgba::new(0.5, 0.6, 0.7, 0.8);
1038        assert_eq!(c.r, 0.5);
1039        assert_eq!(c.g, 0.6);
1040        assert_eq!(c.b, 0.7);
1041        assert_eq!(c.a, 0.8);
1042    }
1043
1044    #[test]
1045    fn test_rgba_premultiply_demultiply() {
1046        let mut c = Rgba::new(1.0, 0.5, 0.25, 0.5);
1047        c.premultiply();
1048        assert!((c.r - 0.5).abs() < 1e-10);
1049        assert!((c.g - 0.25).abs() < 1e-10);
1050        assert!((c.b - 0.125).abs() < 1e-10);
1051        assert!((c.a - 0.5).abs() < 1e-10);
1052
1053        c.demultiply();
1054        assert!((c.r - 1.0).abs() < 1e-10);
1055        assert!((c.g - 0.5).abs() < 1e-10);
1056        assert!((c.b - 0.25).abs() < 1e-10);
1057    }
1058
1059    #[test]
1060    fn test_rgba_gradient() {
1061        let c1 = Rgba::new(0.0, 0.0, 0.0, 1.0);
1062        let c2 = Rgba::new(1.0, 1.0, 1.0, 1.0);
1063        let mid = c1.gradient(&c2, 0.5);
1064        assert!((mid.r - 0.5).abs() < 1e-10);
1065        assert!((mid.g - 0.5).abs() < 1e-10);
1066        assert!((mid.b - 0.5).abs() < 1e-10);
1067    }
1068
1069    #[test]
1070    fn test_rgba_from_wavelength() {
1071        let c = Rgba::from_wavelength(550.0, 1.0);
1072        // 550nm is green-yellow region
1073        assert!(c.r > 0.0);
1074        assert!(c.g > 0.0);
1075        assert!(c.b == 0.0 || c.b < 0.01);
1076    }
1077
1078    #[test]
1079    fn test_rgba_operators() {
1080        let c1 = Rgba::new(0.1, 0.2, 0.3, 0.4);
1081        let c2 = Rgba::new(0.2, 0.3, 0.4, 0.5);
1082        let sum = c1 + c2;
1083        assert!((sum.r - 0.3).abs() < 1e-10);
1084        assert!((sum.g - 0.5).abs() < 1e-10);
1085
1086        let scaled = c1 * 2.0;
1087        assert!((scaled.r - 0.2).abs() < 1e-10);
1088    }
1089
1090    #[test]
1091    fn test_rgba8_new() {
1092        let c = Rgba8::new(128, 64, 32, 255);
1093        assert_eq!(c.r, 128);
1094        assert_eq!(c.g, 64);
1095        assert_eq!(c.b, 32);
1096        assert_eq!(c.a, 255);
1097    }
1098
1099    #[test]
1100    fn test_rgba8_multiply() {
1101        assert_eq!(Rgba8::multiply(255, 255), 255);
1102        assert_eq!(Rgba8::multiply(255, 0), 0);
1103        assert_eq!(Rgba8::multiply(0, 255), 0);
1104        assert_eq!(Rgba8::multiply(128, 255), 128);
1105    }
1106
1107    #[test]
1108    fn test_rgba8_lerp() {
1109        assert_eq!(Rgba8::lerp(0, 255, 128), 128);
1110        assert_eq!(Rgba8::lerp(0, 255, 0), 0);
1111        assert_eq!(Rgba8::lerp(0, 255, 255), 255);
1112        assert_eq!(Rgba8::lerp(100, 200, 128), 150);
1113    }
1114
1115    #[test]
1116    fn test_rgba8_premultiply() {
1117        let mut c = Rgba8::new(255, 128, 64, 128);
1118        c.premultiply();
1119        // With alpha=128 (≈0.502), components should be roughly halved
1120        assert!(c.r > 120 && c.r < 132);
1121        assert!(c.g > 60 && c.g < 68);
1122        assert!(c.b > 28 && c.b < 36);
1123    }
1124
1125    #[test]
1126    fn test_rgba8_demultiply() {
1127        let mut c = Rgba8::new(64, 32, 16, 128);
1128        c.demultiply();
1129        // After demultiplying by alpha=128, values should roughly double
1130        assert!(c.r > 124 && c.r < 132);
1131        assert!(c.g > 60 && c.g < 68);
1132    }
1133
1134    #[test]
1135    fn test_rgba8_from_rgba_roundtrip() {
1136        let orig = Rgba::new(0.5, 0.25, 0.75, 1.0);
1137        let c8 = Rgba8::from_rgba(&orig);
1138        let back = c8.to_rgba();
1139        assert!((orig.r - back.r).abs() < 0.01);
1140        assert!((orig.g - back.g).abs() < 0.01);
1141        assert!((orig.b - back.b).abs() < 0.01);
1142    }
1143
1144    #[test]
1145    fn test_rgba8_gradient() {
1146        let c1 = Rgba8::new(0, 0, 0, 255);
1147        let c2 = Rgba8::new(255, 255, 255, 255);
1148        let mid = c1.gradient(&c2, 0.5);
1149        assert!(mid.r > 125 && mid.r < 130);
1150        assert!(mid.g > 125 && mid.g < 130);
1151    }
1152
1153    #[test]
1154    fn test_rgba8_packed() {
1155        let c = rgb8_packed(0xFF8040);
1156        assert_eq!(c.r, 0xFF);
1157        assert_eq!(c.g, 0x80);
1158        assert_eq!(c.b, 0x40);
1159        assert_eq!(c.a, 255);
1160
1161        let c = bgr8_packed(0xFF8040);
1162        assert_eq!(c.r, 0x40);
1163        assert_eq!(c.g, 0x80);
1164        assert_eq!(c.b, 0xFF);
1165
1166        let c = argb8_packed(0x80FF8040);
1167        assert_eq!(c.a, 0x80);
1168        assert_eq!(c.r, 0xFF);
1169        assert_eq!(c.g, 0x80);
1170        assert_eq!(c.b, 0x40);
1171    }
1172
1173    #[test]
1174    fn test_rgba16_from_rgba8() {
1175        let c8 = Rgba8::new(128, 64, 32, 255);
1176        let c16 = Rgba16::from_rgba8(&c8);
1177        // 128 expanded to 16-bit: (128 << 8) | 128 = 32896
1178        assert_eq!(c16.r, (128 << 8) | 128);
1179        assert_eq!(c16.g, (64 << 8) | 64);
1180    }
1181
1182    #[test]
1183    fn test_rgba16_multiply() {
1184        assert_eq!(Rgba16::multiply(65535, 65535), 65535);
1185        assert_eq!(Rgba16::multiply(65535, 0), 0);
1186    }
1187
1188    #[test]
1189    fn test_gray8_luminance() {
1190        let white = Rgba8::new(255, 255, 255, 255);
1191        let lum = Gray8::luminance_from_rgba8(&white);
1192        // White should have luminance ≈ 255
1193        assert!(lum > 250);
1194
1195        let black = Rgba8::new(0, 0, 0, 255);
1196        let lum = Gray8::luminance_from_rgba8(&black);
1197        assert_eq!(lum, 0);
1198    }
1199
1200    #[test]
1201    fn test_gray8_premultiply() {
1202        let mut g = Gray8::new(200, 128);
1203        g.premultiply();
1204        // 200 * 128/255 ≈ 100
1205        assert!(g.v > 95 && g.v < 105);
1206    }
1207
1208    #[test]
1209    fn test_gray16_from_gray8() {
1210        let g8 = Gray8::new(128, 255);
1211        let g16 = Gray16::from_gray8(&g8);
1212        assert_eq!(g16.v, (128 << 8) | 128);
1213        assert_eq!(g16.a, (255 << 8) | 255);
1214    }
1215
1216    #[test]
1217    fn test_component_orders() {
1218        assert_eq!(OrderRgba::R, 0);
1219        assert_eq!(OrderRgba::G, 1);
1220        assert_eq!(OrderRgba::B, 2);
1221        assert_eq!(OrderRgba::A, 3);
1222        assert_eq!(OrderBgra::B, 0);
1223        assert_eq!(OrderBgra::G, 1);
1224        assert_eq!(OrderBgra::R, 2);
1225        assert_eq!(OrderBgra::A, 3);
1226    }
1227}