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    pub fn to_double(a: u8) -> f64 {
352        a as f64 / Self::BASE_MASK as f64
353    }
354
355    pub fn from_double(a: f64) -> u8 {
356        uround(a * Self::BASE_MASK as f64) as u8
357    }
358
359    pub fn empty_value() -> u8 {
360        0
361    }
362
363    pub fn full_value() -> u8 {
364        Self::BASE_MASK as u8
365    }
366
367    pub fn is_transparent(&self) -> bool {
368        self.a == 0
369    }
370
371    pub fn is_opaque(&self) -> bool {
372        self.a == Self::BASE_MASK as u8
373    }
374
375    pub fn invert(x: u8) -> u8 {
376        Self::BASE_MASK as u8 - x
377    }
378
379    /// Fixed-point multiply, exact over u8.
380    /// `(a * b + 128) >> 8`, with rounding correction.
381    #[inline]
382    pub fn multiply(a: u8, b: u8) -> u8 {
383        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
384        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u8
385    }
386
387    /// Fixed-point demultiply.
388    #[inline]
389    pub fn demultiply_value(a: u8, b: u8) -> u8 {
390        if (a as u32) * (b as u32) == 0 {
391            0
392        } else if a >= b {
393            Self::BASE_MASK as u8
394        } else {
395            (a as u32 * Self::BASE_MASK + (b as u32 >> 1)) as u8 / b
396        }
397    }
398
399    /// Multiply a color component by a cover.
400    #[inline]
401    pub fn mult_cover(a: u8, b: CoverType) -> u8 {
402        Self::multiply(a, b)
403    }
404
405    /// Scale a cover by a value.
406    #[inline]
407    pub fn scale_cover(a: CoverType, b: u8) -> CoverType {
408        Self::multiply(b, a)
409    }
410
411    /// Interpolate p to q by a, assuming q is premultiplied by a.
412    #[inline]
413    pub fn prelerp(p: u8, q: u8, a: u8) -> u8 {
414        p.wrapping_add(q).wrapping_sub(Self::multiply(p, a))
415    }
416
417    /// Interpolate p to q by a.
418    #[inline]
419    pub fn lerp(p: u8, q: u8, a: u8) -> u8 {
420        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
421        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u8
422    }
423
424    pub fn clear(&mut self) -> &mut Self {
425        self.r = 0;
426        self.g = 0;
427        self.b = 0;
428        self.a = 0;
429        self
430    }
431
432    pub fn transparent(&mut self) -> &mut Self {
433        self.a = 0;
434        self
435    }
436
437    pub fn set_opacity(&mut self, a: f64) -> &mut Self {
438        if a < 0.0 {
439            self.a = 0;
440        } else if a > 1.0 {
441            self.a = 1;
442        } else {
443            self.a = uround(a * Self::BASE_MASK as f64) as u8;
444        }
445        self
446    }
447
448    pub fn opacity(&self) -> f64 {
449        self.a as f64 / Self::BASE_MASK as f64
450    }
451
452    pub fn premultiply(&mut self) -> &mut Self {
453        if self.a != Self::BASE_MASK as u8 {
454            if self.a == 0 {
455                self.r = 0;
456                self.g = 0;
457                self.b = 0;
458            } else {
459                self.r = Self::multiply(self.r, self.a);
460                self.g = Self::multiply(self.g, self.a);
461                self.b = Self::multiply(self.b, self.a);
462            }
463        }
464        self
465    }
466
467    pub fn premultiply_with_alpha(&mut self, a_: u32) -> &mut Self {
468        if self.a as u32 != Self::BASE_MASK || a_ < Self::BASE_MASK {
469            if self.a == 0 || a_ == 0 {
470                self.r = 0;
471                self.g = 0;
472                self.b = 0;
473                self.a = 0;
474            } else {
475                let r_ = (self.r as u32 * a_) / self.a as u32;
476                let g_ = (self.g as u32 * a_) / self.a as u32;
477                let b_ = (self.b as u32 * a_) / self.a as u32;
478                self.r = if r_ > a_ { a_ as u8 } else { r_ as u8 };
479                self.g = if g_ > a_ { a_ as u8 } else { g_ as u8 };
480                self.b = if b_ > a_ { a_ as u8 } else { b_ as u8 };
481                self.a = a_ as u8;
482            }
483        }
484        self
485    }
486
487    pub fn demultiply(&mut self) -> &mut Self {
488        if (self.a as u32) < Self::BASE_MASK {
489            if self.a == 0 {
490                self.r = 0;
491                self.g = 0;
492                self.b = 0;
493            } else {
494                let r_ = (self.r as u32 * Self::BASE_MASK) / self.a as u32;
495                let g_ = (self.g as u32 * Self::BASE_MASK) / self.a as u32;
496                let b_ = (self.b as u32 * Self::BASE_MASK) / self.a as u32;
497                self.r = r_.min(Self::BASE_MASK) as u8;
498                self.g = g_.min(Self::BASE_MASK) as u8;
499                self.b = b_.min(Self::BASE_MASK) as u8;
500            }
501        }
502        self
503    }
504
505    /// Interpolate between `self` and `c` by parameter `k` (0.0 to 1.0).
506    pub fn gradient(&self, c: &Rgba8, k: f64) -> Rgba8 {
507        let ik = uround(k * Self::BASE_MASK as f64) as u8;
508        Rgba8 {
509            r: Self::lerp(self.r, c.r, ik),
510            g: Self::lerp(self.g, c.g, ik),
511            b: Self::lerp(self.b, c.b, ik),
512            a: Self::lerp(self.a, c.a, ik),
513        }
514    }
515
516    /// Add color `c` with coverage `cover`.
517    pub fn add(&mut self, c: &Rgba8, cover: u32) {
518        let cr: u32;
519        let cg: u32;
520        let cb: u32;
521        let ca: u32;
522        if cover == COVER_MASK {
523            if c.a as u32 == Self::BASE_MASK {
524                *self = *c;
525                return;
526            } else {
527                cr = self.r as u32 + c.r as u32;
528                cg = self.g as u32 + c.g as u32;
529                cb = self.b as u32 + c.b as u32;
530                ca = self.a as u32 + c.a as u32;
531            }
532        } else {
533            cr = self.r as u32 + Self::mult_cover(c.r, cover as u8) as u32;
534            cg = self.g as u32 + Self::mult_cover(c.g, cover as u8) as u32;
535            cb = self.b as u32 + Self::mult_cover(c.b, cover as u8) as u32;
536            ca = self.a as u32 + Self::mult_cover(c.a, cover as u8) as u32;
537        }
538        self.r = cr.min(Self::BASE_MASK) as u8;
539        self.g = cg.min(Self::BASE_MASK) as u8;
540        self.b = cb.min(Self::BASE_MASK) as u8;
541        self.a = ca.min(Self::BASE_MASK) as u8;
542    }
543
544    /// Apply forward gamma correction.
545    pub fn apply_gamma_dir(&mut self, gamma: &crate::gamma::GammaLut) {
546        self.r = gamma.dir(self.r);
547        self.g = gamma.dir(self.g);
548        self.b = gamma.dir(self.b);
549    }
550
551    /// Apply inverse gamma correction.
552    pub fn apply_gamma_inv(&mut self, gamma: &crate::gamma::GammaLut) {
553        self.r = gamma.inv(self.r);
554        self.g = gamma.inv(self.g);
555        self.b = gamma.inv(self.b);
556    }
557
558    pub fn no_color() -> Self {
559        Self {
560            r: 0,
561            g: 0,
562            b: 0,
563            a: 0,
564        }
565    }
566
567    pub fn from_wavelength(wl: f64, gamma: f64) -> Self {
568        Self::from_rgba(&Rgba::from_wavelength(wl, gamma))
569    }
570}
571
572impl Default for Rgba8 {
573    fn default() -> Self {
574        Self::no_color()
575    }
576}
577
578/// Create an Rgba8 from a packed RGB value (0xRRGGBB).
579pub fn rgb8_packed(v: u32) -> Rgba8 {
580    Rgba8::new((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF, 255)
581}
582
583/// Create an Rgba8 from a packed BGR value (0xBBGGRR).
584pub fn bgr8_packed(v: u32) -> Rgba8 {
585    Rgba8::new(v & 0xFF, (v >> 8) & 0xFF, (v >> 16) & 0xFF, 255)
586}
587
588/// Create an Rgba8 from a packed ARGB value (0xAARRGGBB).
589pub fn argb8_packed(v: u32) -> Rgba8 {
590    Rgba8::new((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF, v >> 24)
591}
592
593// ============================================================================
594// Rgba16 (16-bit per channel)
595// ============================================================================
596
597/// RGBA color with u16 components.
598/// Port of C++ `rgba16`.
599#[derive(Debug, Clone, Copy, PartialEq, Eq)]
600pub struct Rgba16 {
601    pub r: u16,
602    pub g: u16,
603    pub b: u16,
604    pub a: u16,
605}
606
607impl Rgba16 {
608    pub const BASE_SHIFT: u32 = 16;
609    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
610    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
611    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
612
613    pub fn new(r: u32, g: u32, b: u32, a: u32) -> Self {
614        Self {
615            r: r as u16,
616            g: g as u16,
617            b: b as u16,
618            a: a as u16,
619        }
620    }
621
622    pub fn new_opaque(r: u32, g: u32, b: u32) -> Self {
623        Self::new(r, g, b, Self::BASE_MASK)
624    }
625
626    /// Convert from Rgba (f64).
627    pub fn from_rgba(c: &Rgba) -> Self {
628        Self {
629            r: uround(c.r * Self::BASE_MASK as f64) as u16,
630            g: uround(c.g * Self::BASE_MASK as f64) as u16,
631            b: uround(c.b * Self::BASE_MASK as f64) as u16,
632            a: uround(c.a * Self::BASE_MASK as f64) as u16,
633        }
634    }
635
636    /// Convert from Rgba8 (u8) by expanding 8-bit to 16-bit.
637    pub fn from_rgba8(c: &Rgba8) -> Self {
638        Self {
639            r: ((c.r as u16) << 8) | c.r as u16,
640            g: ((c.g as u16) << 8) | c.g as u16,
641            b: ((c.b as u16) << 8) | c.b as u16,
642            a: ((c.a as u16) << 8) | c.a as u16,
643        }
644    }
645
646    pub fn to_rgba(&self) -> Rgba {
647        Rgba {
648            r: self.r as f64 / 65535.0,
649            g: self.g as f64 / 65535.0,
650            b: self.b as f64 / 65535.0,
651            a: self.a as f64 / 65535.0,
652        }
653    }
654
655    pub fn to_rgba8(&self) -> Rgba8 {
656        Rgba8::new(
657            (self.r >> 8) as u32,
658            (self.g >> 8) as u32,
659            (self.b >> 8) as u32,
660            (self.a >> 8) as u32,
661        )
662    }
663
664    pub fn is_transparent(&self) -> bool {
665        self.a == 0
666    }
667
668    pub fn is_opaque(&self) -> bool {
669        self.a == Self::BASE_MASK as u16
670    }
671
672    pub fn invert(x: u16) -> u16 {
673        Self::BASE_MASK as u16 - x
674    }
675
676    /// Fixed-point multiply, exact over u16.
677    #[inline]
678    pub fn multiply(a: u16, b: u16) -> u16 {
679        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
680        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u16
681    }
682
683    /// Interpolate p to q by a.
684    #[inline]
685    pub fn lerp(p: u16, q: u16, a: u16) -> u16 {
686        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
687        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u16
688    }
689
690    /// Multiply a color component by a cover (8-bit).
691    #[inline]
692    pub fn mult_cover(a: u16, b: CoverType) -> u16 {
693        Self::multiply(a, (b as u16) << 8 | b as u16)
694    }
695
696    pub fn clear(&mut self) -> &mut Self {
697        self.r = 0;
698        self.g = 0;
699        self.b = 0;
700        self.a = 0;
701        self
702    }
703
704    pub fn premultiply(&mut self) -> &mut Self {
705        if self.a as u32 != Self::BASE_MASK {
706            if self.a == 0 {
707                self.r = 0;
708                self.g = 0;
709                self.b = 0;
710            } else {
711                self.r = Self::multiply(self.r, self.a);
712                self.g = Self::multiply(self.g, self.a);
713                self.b = Self::multiply(self.b, self.a);
714            }
715        }
716        self
717    }
718
719    pub fn demultiply(&mut self) -> &mut Self {
720        if (self.a as u32) < Self::BASE_MASK {
721            if self.a == 0 {
722                self.r = 0;
723                self.g = 0;
724                self.b = 0;
725            } else {
726                let r_ = (self.r as u32 * Self::BASE_MASK) / self.a as u32;
727                let g_ = (self.g as u32 * Self::BASE_MASK) / self.a as u32;
728                let b_ = (self.b as u32 * Self::BASE_MASK) / self.a as u32;
729                self.r = r_.min(Self::BASE_MASK) as u16;
730                self.g = g_.min(Self::BASE_MASK) as u16;
731                self.b = b_.min(Self::BASE_MASK) as u16;
732            }
733        }
734        self
735    }
736
737    pub fn gradient(&self, c: &Rgba16, k: f64) -> Rgba16 {
738        let ik = uround(k * Self::BASE_MASK as f64) as u16;
739        Rgba16 {
740            r: Self::lerp(self.r, c.r, ik),
741            g: Self::lerp(self.g, c.g, ik),
742            b: Self::lerp(self.b, c.b, ik),
743            a: Self::lerp(self.a, c.a, ik),
744        }
745    }
746
747    pub fn no_color() -> Self {
748        Self {
749            r: 0,
750            g: 0,
751            b: 0,
752            a: 0,
753        }
754    }
755
756    pub fn from_wavelength(wl: f64, gamma: f64) -> Self {
757        Self::from_rgba(&Rgba::from_wavelength(wl, gamma))
758    }
759}
760
761impl Default for Rgba16 {
762    fn default() -> Self {
763        Self::no_color()
764    }
765}
766
767// ============================================================================
768// Gray8 (8-bit grayscale)
769// ============================================================================
770
771/// Grayscale color with u8 components (value + alpha).
772/// Port of C++ `gray8T<linear>`.
773#[derive(Debug, Clone, Copy, PartialEq, Eq)]
774pub struct Gray8 {
775    pub v: u8,
776    pub a: u8,
777}
778
779impl Gray8 {
780    pub const BASE_SHIFT: u32 = 8;
781    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
782    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
783    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
784
785    pub fn new(v: u32, a: u32) -> Self {
786        Self {
787            v: v as u8,
788            a: a as u8,
789        }
790    }
791
792    pub fn new_opaque(v: u32) -> Self {
793        Self::new(v, Self::BASE_MASK)
794    }
795
796    /// Calculate luminance from linear RGB (ITU-R BT.709).
797    pub fn luminance_from_rgba(c: &Rgba) -> u8 {
798        uround((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * Self::BASE_MASK as f64) as u8
799    }
800
801    /// Calculate luminance from Rgba8 (ITU-R BT.709 with integer coefficients).
802    pub fn luminance_from_rgba8(c: &Rgba8) -> u8 {
803        ((55u32 * c.r as u32 + 184u32 * c.g as u32 + 18u32 * c.b as u32) >> 8) as u8
804    }
805
806    pub fn from_rgba(c: &Rgba) -> Self {
807        Self {
808            v: Self::luminance_from_rgba(c),
809            a: uround(c.a * Self::BASE_MASK as f64) as u8,
810        }
811    }
812
813    pub fn from_rgba8(c: &Rgba8) -> Self {
814        Self {
815            v: Self::luminance_from_rgba8(c),
816            a: c.a,
817        }
818    }
819
820    pub fn is_transparent(&self) -> bool {
821        self.a == 0
822    }
823
824    pub fn is_opaque(&self) -> bool {
825        self.a == Self::BASE_MASK as u8
826    }
827
828    #[inline]
829    pub fn multiply(a: u8, b: u8) -> u8 {
830        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
831        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u8
832    }
833
834    #[inline]
835    pub fn lerp(p: u8, q: u8, a: u8) -> u8 {
836        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
837        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u8
838    }
839
840    #[inline]
841    pub fn mult_cover(a: u8, b: CoverType) -> u8 {
842        Self::multiply(a, b)
843    }
844
845    pub fn clear(&mut self) -> &mut Self {
846        self.v = 0;
847        self.a = 0;
848        self
849    }
850
851    pub fn premultiply(&mut self) -> &mut Self {
852        if (self.a as u32) < Self::BASE_MASK {
853            if self.a == 0 {
854                self.v = 0;
855            } else {
856                self.v = Self::multiply(self.v, self.a);
857            }
858        }
859        self
860    }
861
862    pub fn demultiply(&mut self) -> &mut Self {
863        if (self.a as u32) < Self::BASE_MASK {
864            if self.a == 0 {
865                self.v = 0;
866            } else {
867                let v_ = (self.v as u32 * Self::BASE_MASK) / self.a as u32;
868                self.v = v_.min(Self::BASE_MASK) as u8;
869            }
870        }
871        self
872    }
873
874    pub fn gradient(&self, c: &Gray8, k: f64) -> Gray8 {
875        let ik = uround(k * Self::BASE_SCALE as f64) as u8;
876        Gray8 {
877            v: Self::lerp(self.v, c.v, ik),
878            a: Self::lerp(self.a, c.a, ik),
879        }
880    }
881
882    pub fn no_color() -> Self {
883        Self { v: 0, a: 0 }
884    }
885}
886
887impl Default for Gray8 {
888    fn default() -> Self {
889        Self::no_color()
890    }
891}
892
893// ============================================================================
894// Gray16 (16-bit grayscale)
895// ============================================================================
896
897/// Grayscale color with u16 components (value + alpha).
898/// Port of C++ `gray16`.
899#[derive(Debug, Clone, Copy, PartialEq, Eq)]
900pub struct Gray16 {
901    pub v: u16,
902    pub a: u16,
903}
904
905impl Gray16 {
906    pub const BASE_SHIFT: u32 = 16;
907    pub const BASE_SCALE: u32 = 1 << Self::BASE_SHIFT;
908    pub const BASE_MASK: u32 = Self::BASE_SCALE - 1;
909    pub const BASE_MSB: u32 = 1 << (Self::BASE_SHIFT - 1);
910
911    pub fn new(v: u32, a: u32) -> Self {
912        Self {
913            v: v as u16,
914            a: a as u16,
915        }
916    }
917
918    pub fn new_opaque(v: u32) -> Self {
919        Self::new(v, Self::BASE_MASK)
920    }
921
922    /// Calculate luminance from Rgba (ITU-R BT.709).
923    pub fn luminance_from_rgba(c: &Rgba) -> u16 {
924        uround((0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) * Self::BASE_MASK as f64) as u16
925    }
926
927    /// Calculate luminance from Rgba16 (ITU-R BT.709 with integer coefficients).
928    pub fn luminance_from_rgba16(c: &Rgba16) -> u16 {
929        ((13933u32 * c.r as u32 + 46872u32 * c.g as u32 + 4732u32 * c.b as u32) >> 16) as u16
930    }
931
932    pub fn from_rgba(c: &Rgba) -> Self {
933        Self {
934            v: Self::luminance_from_rgba(c),
935            a: uround(c.a * Self::BASE_MASK as f64) as u16,
936        }
937    }
938
939    pub fn from_rgba8(c: &Rgba8) -> Self {
940        Self::from_rgba16(&Rgba16::from_rgba8(c))
941    }
942
943    pub fn from_rgba16(c: &Rgba16) -> Self {
944        Self {
945            v: Self::luminance_from_rgba16(c),
946            a: c.a,
947        }
948    }
949
950    pub fn from_gray8(c: &Gray8) -> Self {
951        Self {
952            v: ((c.v as u16) << 8) | c.v as u16,
953            a: ((c.a as u16) << 8) | c.a as u16,
954        }
955    }
956
957    pub fn is_transparent(&self) -> bool {
958        self.a == 0
959    }
960
961    pub fn is_opaque(&self) -> bool {
962        self.a == Self::BASE_MASK as u16
963    }
964
965    #[inline]
966    pub fn multiply(a: u16, b: u16) -> u16 {
967        let t: u32 = a as u32 * b as u32 + Self::BASE_MSB;
968        (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT) as u16
969    }
970
971    #[inline]
972    pub fn lerp(p: u16, q: u16, a: u16) -> u16 {
973        let t = (q as i32 - p as i32) * a as i32 + Self::BASE_MSB as i32 - (p > q) as i32;
974        (p as i32 + (((t >> Self::BASE_SHIFT) + t) >> Self::BASE_SHIFT)) as u16
975    }
976
977    pub fn clear(&mut self) -> &mut Self {
978        self.v = 0;
979        self.a = 0;
980        self
981    }
982
983    pub fn premultiply(&mut self) -> &mut Self {
984        if (self.a as u32) < Self::BASE_MASK {
985            if self.a == 0 {
986                self.v = 0;
987            } else {
988                self.v = Self::multiply(self.v, self.a);
989            }
990        }
991        self
992    }
993
994    pub fn demultiply(&mut self) -> &mut Self {
995        if (self.a as u32) < Self::BASE_MASK {
996            if self.a == 0 {
997                self.v = 0;
998            } else {
999                let v_ = (self.v as u32 * Self::BASE_MASK) / self.a as u32;
1000                self.v = v_.min(Self::BASE_MASK) as u16;
1001            }
1002        }
1003        self
1004    }
1005
1006    pub fn gradient(&self, c: &Gray16, k: f64) -> Gray16 {
1007        let ik = uround(k * Self::BASE_SCALE as f64) as u16;
1008        Gray16 {
1009            v: Self::lerp(self.v, c.v, ik),
1010            a: Self::lerp(self.a, c.a, ik),
1011        }
1012    }
1013
1014    pub fn no_color() -> Self {
1015        Self { v: 0, a: 0 }
1016    }
1017}
1018
1019impl Default for Gray16 {
1020    fn default() -> Self {
1021        Self::no_color()
1022    }
1023}
1024
1025// ============================================================================
1026// Tests
1027// ============================================================================
1028
1029#[cfg(test)]
1030mod tests {
1031    use super::*;
1032
1033    #[test]
1034    fn test_rgba_new() {
1035        let c = Rgba::new(0.5, 0.6, 0.7, 0.8);
1036        assert_eq!(c.r, 0.5);
1037        assert_eq!(c.g, 0.6);
1038        assert_eq!(c.b, 0.7);
1039        assert_eq!(c.a, 0.8);
1040    }
1041
1042    #[test]
1043    fn test_rgba_premultiply_demultiply() {
1044        let mut c = Rgba::new(1.0, 0.5, 0.25, 0.5);
1045        c.premultiply();
1046        assert!((c.r - 0.5).abs() < 1e-10);
1047        assert!((c.g - 0.25).abs() < 1e-10);
1048        assert!((c.b - 0.125).abs() < 1e-10);
1049        assert!((c.a - 0.5).abs() < 1e-10);
1050
1051        c.demultiply();
1052        assert!((c.r - 1.0).abs() < 1e-10);
1053        assert!((c.g - 0.5).abs() < 1e-10);
1054        assert!((c.b - 0.25).abs() < 1e-10);
1055    }
1056
1057    #[test]
1058    fn test_rgba_gradient() {
1059        let c1 = Rgba::new(0.0, 0.0, 0.0, 1.0);
1060        let c2 = Rgba::new(1.0, 1.0, 1.0, 1.0);
1061        let mid = c1.gradient(&c2, 0.5);
1062        assert!((mid.r - 0.5).abs() < 1e-10);
1063        assert!((mid.g - 0.5).abs() < 1e-10);
1064        assert!((mid.b - 0.5).abs() < 1e-10);
1065    }
1066
1067    #[test]
1068    fn test_rgba_from_wavelength() {
1069        let c = Rgba::from_wavelength(550.0, 1.0);
1070        // 550nm is green-yellow region
1071        assert!(c.r > 0.0);
1072        assert!(c.g > 0.0);
1073        assert!(c.b == 0.0 || c.b < 0.01);
1074    }
1075
1076    #[test]
1077    fn test_rgba_operators() {
1078        let c1 = Rgba::new(0.1, 0.2, 0.3, 0.4);
1079        let c2 = Rgba::new(0.2, 0.3, 0.4, 0.5);
1080        let sum = c1 + c2;
1081        assert!((sum.r - 0.3).abs() < 1e-10);
1082        assert!((sum.g - 0.5).abs() < 1e-10);
1083
1084        let scaled = c1 * 2.0;
1085        assert!((scaled.r - 0.2).abs() < 1e-10);
1086    }
1087
1088    #[test]
1089    fn test_rgba8_new() {
1090        let c = Rgba8::new(128, 64, 32, 255);
1091        assert_eq!(c.r, 128);
1092        assert_eq!(c.g, 64);
1093        assert_eq!(c.b, 32);
1094        assert_eq!(c.a, 255);
1095    }
1096
1097    #[test]
1098    fn test_rgba8_multiply() {
1099        assert_eq!(Rgba8::multiply(255, 255), 255);
1100        assert_eq!(Rgba8::multiply(255, 0), 0);
1101        assert_eq!(Rgba8::multiply(0, 255), 0);
1102        assert_eq!(Rgba8::multiply(128, 255), 128);
1103    }
1104
1105    #[test]
1106    fn test_rgba8_lerp() {
1107        assert_eq!(Rgba8::lerp(0, 255, 128), 128);
1108        assert_eq!(Rgba8::lerp(0, 255, 0), 0);
1109        assert_eq!(Rgba8::lerp(0, 255, 255), 255);
1110        assert_eq!(Rgba8::lerp(100, 200, 128), 150);
1111    }
1112
1113    #[test]
1114    fn test_rgba8_premultiply() {
1115        let mut c = Rgba8::new(255, 128, 64, 128);
1116        c.premultiply();
1117        // With alpha=128 (≈0.502), components should be roughly halved
1118        assert!(c.r > 120 && c.r < 132);
1119        assert!(c.g > 60 && c.g < 68);
1120        assert!(c.b > 28 && c.b < 36);
1121    }
1122
1123    #[test]
1124    fn test_rgba8_demultiply() {
1125        let mut c = Rgba8::new(64, 32, 16, 128);
1126        c.demultiply();
1127        // After demultiplying by alpha=128, values should roughly double
1128        assert!(c.r > 124 && c.r < 132);
1129        assert!(c.g > 60 && c.g < 68);
1130    }
1131
1132    #[test]
1133    fn test_rgba8_from_rgba_roundtrip() {
1134        let orig = Rgba::new(0.5, 0.25, 0.75, 1.0);
1135        let c8 = Rgba8::from_rgba(&orig);
1136        let back = c8.to_rgba();
1137        assert!((orig.r - back.r).abs() < 0.01);
1138        assert!((orig.g - back.g).abs() < 0.01);
1139        assert!((orig.b - back.b).abs() < 0.01);
1140    }
1141
1142    #[test]
1143    fn test_rgba8_gradient() {
1144        let c1 = Rgba8::new(0, 0, 0, 255);
1145        let c2 = Rgba8::new(255, 255, 255, 255);
1146        let mid = c1.gradient(&c2, 0.5);
1147        assert!(mid.r > 125 && mid.r < 130);
1148        assert!(mid.g > 125 && mid.g < 130);
1149    }
1150
1151    #[test]
1152    fn test_rgba8_packed() {
1153        let c = rgb8_packed(0xFF8040);
1154        assert_eq!(c.r, 0xFF);
1155        assert_eq!(c.g, 0x80);
1156        assert_eq!(c.b, 0x40);
1157        assert_eq!(c.a, 255);
1158
1159        let c = bgr8_packed(0xFF8040);
1160        assert_eq!(c.r, 0x40);
1161        assert_eq!(c.g, 0x80);
1162        assert_eq!(c.b, 0xFF);
1163
1164        let c = argb8_packed(0x80FF8040);
1165        assert_eq!(c.a, 0x80);
1166        assert_eq!(c.r, 0xFF);
1167        assert_eq!(c.g, 0x80);
1168        assert_eq!(c.b, 0x40);
1169    }
1170
1171    #[test]
1172    fn test_rgba16_from_rgba8() {
1173        let c8 = Rgba8::new(128, 64, 32, 255);
1174        let c16 = Rgba16::from_rgba8(&c8);
1175        // 128 expanded to 16-bit: (128 << 8) | 128 = 32896
1176        assert_eq!(c16.r, (128 << 8) | 128);
1177        assert_eq!(c16.g, (64 << 8) | 64);
1178    }
1179
1180    #[test]
1181    fn test_rgba16_multiply() {
1182        assert_eq!(Rgba16::multiply(65535, 65535), 65535);
1183        assert_eq!(Rgba16::multiply(65535, 0), 0);
1184    }
1185
1186    #[test]
1187    fn test_gray8_luminance() {
1188        let white = Rgba8::new(255, 255, 255, 255);
1189        let lum = Gray8::luminance_from_rgba8(&white);
1190        // White should have luminance ≈ 255
1191        assert!(lum > 250);
1192
1193        let black = Rgba8::new(0, 0, 0, 255);
1194        let lum = Gray8::luminance_from_rgba8(&black);
1195        assert_eq!(lum, 0);
1196    }
1197
1198    #[test]
1199    fn test_gray8_premultiply() {
1200        let mut g = Gray8::new(200, 128);
1201        g.premultiply();
1202        // 200 * 128/255 ≈ 100
1203        assert!(g.v > 95 && g.v < 105);
1204    }
1205
1206    #[test]
1207    fn test_gray16_from_gray8() {
1208        let g8 = Gray8::new(128, 255);
1209        let g16 = Gray16::from_gray8(&g8);
1210        assert_eq!(g16.v, (128 << 8) | 128);
1211        assert_eq!(g16.a, (255 << 8) | 255);
1212    }
1213
1214    #[test]
1215    fn test_component_orders() {
1216        assert_eq!(OrderRgba::R, 0);
1217        assert_eq!(OrderRgba::G, 1);
1218        assert_eq!(OrderRgba::B, 2);
1219        assert_eq!(OrderRgba::A, 3);
1220        assert_eq!(OrderBgra::B, 0);
1221        assert_eq!(OrderBgra::G, 1);
1222        assert_eq!(OrderBgra::R, 2);
1223        assert_eq!(OrderBgra::A, 3);
1224    }
1225}