gistools/parsers/image/
rgba.rs

1use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub};
2use libm::pow;
3use s2json::{GetM, MValue, MValueCompatible, ValuePrimitive, ValueType};
4use serde::{Deserialize, Serialize};
5
6use crate::parsers::Reader;
7
8/// Gamma correction
9const GAMMA: f64 = 2.2;
10
11/// Convert from u8 sRGB (gamma-encoded) to linear space
12pub fn gamma_to_linear(n: f64) -> f64 {
13    pow(n / 255., 1. / GAMMA)
14}
15
16/// Convert from linear space to u8 sRGB (gamma-encoded)
17pub fn linear_to_gamma(n: f64) -> f64 {
18    pow(n, GAMMA) * 255.
19}
20
21/// # Red Green Blue Alpha container
22///
23/// ## Description
24/// RGBA data in 0->1 range floats
25///
26/// These values remove gamma-corrected values so that you can apply maths on them
27///
28/// This means the RGBA values are in linear space
29///
30/// ## Usage
31///
32/// Methods that are available:
33/// - [`RGBA::default`]: returns a new RGBA value with RGB set to 0.0 and alpha set to 1.0
34/// - [`RGBA::new`]: returns a new RGBA value
35/// - [`RGBA::from_gamma`]: Create a new RGBA value from gamma-corrected values
36/// - [`RGBA::to_gamma`]: Convert RGBA to gamma-corrected values
37/// - [`RGBA::from_reader`]: returns a new RGBA value from a reader
38/// - [`RGBA::from_u8s`]: returns a new RGBA value from 4 u8 values
39/// - [`RGBA::to_u8s`]: returns a new RGBA value from 4 u8 values
40/// - [`RGBA::from_u16s`]: returns a new RGBA value from 4 u16 values
41/// - [`RGBA::to_u16s`]: returns a new RGBA value from 4 u16 values
42/// - [`RGBA::from_u32`]: returns a new RGBA value from 4 u32 values
43/// - [`RGBA::to_u32s`]: returns a new RGBA value from 4 u32 values
44/// - [`RGBA::from_u64`]: returns a new RGBA value from 4 u64 values
45/// - [`RGBA::to_u64s`]: returns a new RGBA value from 4 u64 values
46/// - [`RGBA::from_hex`]: Create an RGBA color from a hex string
47#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ValuePrimitive)]
48pub struct RGBA {
49    /// Gamma corrected Red between 0 and 1
50    pub r: f64,
51    /// Gamma corrected Green between 0 and 1
52    pub g: f64,
53    /// Gamma corrected Blue between 0 and 1
54    pub b: f64,
55    /// Opacity between 0 and 1 (not gamma corrected as opacity is linear)
56    pub a: f64,
57}
58impl Default for RGBA {
59    fn default() -> Self {
60        Self { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }
61    }
62}
63impl RGBA {
64    /// Create a new RGBA value
65    pub fn new(r: f64, g: f64, b: f64, a: f64) -> Self {
66        Self { r, g, b, a }
67    }
68
69    /// Create a new RGBA value from gamma-corrected values
70    pub fn from_gamma(r: f64, g: f64, b: f64, a: f64) -> Self {
71        Self::new(pow(r, 1. / GAMMA), pow(g, 1. / GAMMA), pow(b, 1. / GAMMA), a)
72    }
73
74    /// Convert RGBA to gamma-corrected values
75    pub fn to_gamma(self) -> (f64, f64, f64, f64) {
76        (pow(self.r, GAMMA), pow(self.g, GAMMA), pow(self.b, GAMMA), self.a)
77    }
78
79    /// Create a new RGBA value from 4 u8 values
80    pub fn from_u8s(r: u8, g: u8, b: u8, a: u8) -> Self {
81        let max_u8 = u8::MAX as f64;
82        // store the gamma corrected value
83        let r = (r as f64) / max_u8;
84        let g = (g as f64) / max_u8;
85        let b = (b as f64) / max_u8;
86        let a = (a as f64) / max_u8;
87        Self::from_gamma(r, g, b, a)
88    }
89
90    /// Convert RGBA to 4 u8 values in Gamma corrected 0->255 range
91    pub fn to_u8s(self) -> (u8, u8, u8, u8) {
92        let (r, g, b, a) = self.to_gamma();
93        let max_u8 = u8::MAX as f64;
94        (
95            (r * max_u8).round() as u8,
96            (g * max_u8).round() as u8,
97            (b * max_u8).round() as u8,
98            (a * max_u8).round() as u8,
99        )
100    }
101
102    /// Create a new RGBA value from 4 u16 values
103    pub fn from_reader<R: Reader>(reader: &R, offset: Option<u64>) -> Self {
104        let offset = offset.unwrap_or(reader.tell());
105        let r = reader.uint16_le(Some(offset));
106        let g = reader.uint16_le(Some(offset + 2));
107        let b = reader.uint16_le(Some(offset + 4));
108        RGBA::from_u16s(r, g, b, u16::MAX)
109    }
110
111    /// Create a new RGBA value from 4 u16 values
112    pub fn from_u16s(r: u16, g: u16, b: u16, a: u16) -> Self {
113        let max_u16 = u16::MAX as f64;
114        let r = (r as f64) / max_u16;
115        let g = (g as f64) / max_u16;
116        let b = (b as f64) / max_u16;
117        let a = (a as f64) / max_u16;
118        Self::from_gamma(r, g, b, a)
119    }
120
121    /// Convert RGBA to 4 u16 values in Gamma corrected 0->65,535 range
122    pub fn to_u16s(&self) -> (u16, u16, u16, u16) {
123        let (r, g, b, a) = self.to_gamma();
124        let max_u16 = u16::MAX as f64;
125        (
126            (r * max_u16).round() as u16,
127            (g * max_u16).round() as u16,
128            (b * max_u16).round() as u16,
129            (a * max_u16).round() as u16,
130        )
131    }
132
133    /// Convert a 32-bit integer to RGBA
134    pub fn from_u32(value: u32) -> Self {
135        let r = ((value >> 24) & 0xFF) as u8;
136        let g = ((value >> 16) & 0xFF) as u8;
137        let b = ((value >> 8) & 0xFF) as u8;
138        let a = (value & 0xFF) as u8;
139        Self::from_u8s(r, g, b, a)
140    }
141
142    /// Convert RGBA to a 32-bit integer (Big-endian: 0xRRGGBBAA)
143    /// big-endian is more common in graphics formats like BMP, PNG, etc.
144    pub fn to_u32s(&self) -> u32 {
145        let max_u8 = u8::MAX as f64;
146        let (r, g, b, a) = self.to_gamma();
147        ((r * max_u8).round() as u32) << 24
148            | ((g * max_u8).round() as u32) << 16
149            | ((b * max_u8).round() as u32) << 8
150            | ((a * max_u8).round() as u32)
151    }
152
153    /// Convert an unsigned 64-bit integer to RGBA
154    pub fn from_u64(value: u64) -> Self {
155        let r = ((value >> 48) & 0xFFFF) as u16;
156        let g = ((value >> 32) & 0xFFFF) as u16;
157        let b = ((value >> 16) & 0xFFFF) as u16;
158        let a = (value & 0xFFFF) as u16;
159        Self::from_u16s(r, g, b, a)
160    }
161
162    /// Convert RGBA to an unsigned 64-bit integer
163    pub fn to_u64s(&self) -> u64 {
164        let max_u16 = u16::MAX as f64;
165        let (r, g, b, a) = self.to_gamma();
166        ((r * max_u16).round() as u64) << 48
167            | ((g * max_u16).round() as u64) << 32
168            | ((b * max_u16).round() as u64) << 16
169            | ((a * max_u16).round() as u64)
170    }
171
172    /// Create an RGBA color from a hex string.
173    /// Accepts formats: "#RRGGBB", "#RRGGBBAA", "RRGGBB", "RRGGBBAA" (case-insensitive).
174    /// Panics on invalid input.
175    pub fn from_hex(hex: &str) -> Self {
176        let hex = hex.trim_start_matches('#');
177        let len = hex.len();
178
179        let parse_byte = |i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap();
180
181        match len {
182            6 => {
183                let r = parse_byte(0);
184                let g = parse_byte(2);
185                let b = parse_byte(4);
186                Self::from_u8s(r, g, b, 255)
187            }
188            8 => {
189                let r = parse_byte(0);
190                let g = parse_byte(2);
191                let b = parse_byte(4);
192                let a = parse_byte(6);
193                Self::from_u8s(r, g, b, a)
194            }
195            _ => panic!("Invalid hex color: expected 6 or 8 hex digits, got {}", len),
196        }
197    }
198}
199impl GetM<RGBA> for RGBA {
200    fn m(&self) -> Option<&RGBA> {
201        Some(self)
202    }
203}
204impl Add<RGBA> for RGBA {
205    type Output = RGBA;
206    fn add(self, rhs: RGBA) -> Self::Output {
207        RGBA::new(self.r + rhs.r, self.g + rhs.g, self.b + rhs.b, self.a + rhs.a)
208    }
209}
210impl AddAssign<RGBA> for RGBA {
211    fn add_assign(&mut self, rhs: RGBA) {
212        self.r += rhs.r;
213        self.g += rhs.g;
214        self.b += rhs.b;
215        self.a += rhs.a;
216    }
217}
218impl AddAssign<f64> for RGBA {
219    fn add_assign(&mut self, rhs: f64) {
220        self.r += rhs;
221        self.g += rhs;
222        self.b += rhs;
223        self.a += rhs;
224    }
225}
226impl Sub<RGBA> for RGBA {
227    type Output = RGBA;
228    fn sub(self, rhs: RGBA) -> Self::Output {
229        RGBA::new(self.r - rhs.r, self.g - rhs.g, self.b - rhs.b, self.a - rhs.a)
230    }
231}
232impl Mul<RGBA> for RGBA {
233    type Output = RGBA;
234    fn mul(self, rhs: RGBA) -> Self::Output {
235        RGBA::new(self.r * rhs.r, self.g * rhs.g, self.b * rhs.b, self.a * rhs.a)
236    }
237}
238impl MulAssign<RGBA> for RGBA {
239    fn mul_assign(&mut self, rhs: RGBA) {
240        self.r *= rhs.r;
241        self.g *= rhs.g;
242        self.b *= rhs.b;
243        self.a *= rhs.a;
244    }
245}
246impl MulAssign<f64> for RGBA {
247    fn mul_assign(&mut self, rhs: f64) {
248        self.r *= rhs;
249        self.g *= rhs;
250        self.b *= rhs;
251        self.a *= rhs;
252    }
253}
254impl Div<RGBA> for RGBA {
255    type Output = RGBA;
256    fn div(self, rhs: RGBA) -> Self::Output {
257        RGBA::new(self.r / rhs.r, self.g / rhs.g, self.b / rhs.b, self.a / rhs.a)
258    }
259}
260impl DivAssign<RGBA> for RGBA {
261    fn div_assign(&mut self, rhs: RGBA) {
262        self.r /= rhs.r;
263        self.g /= rhs.g;
264        self.b /= rhs.b;
265        self.a /= rhs.a;
266    }
267}
268impl DivAssign<f64> for RGBA {
269    fn div_assign(&mut self, rhs: f64) {
270        self.r /= rhs;
271        self.g /= rhs;
272        self.b /= rhs;
273        self.a /= rhs;
274    }
275}
276impl PartialEq<f64> for RGBA {
277    fn eq(&self, rhs: &f64) -> bool {
278        self.r == *rhs || self.g == *rhs || self.b == *rhs || self.a == *rhs
279    }
280}
281impl MValueCompatible for RGBA {}
282impl From<MValue> for RGBA {
283    fn from(mvalue: MValue) -> Self {
284        let r = mvalue.get("r").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
285        let g = mvalue.get("g").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
286        let b = mvalue.get("b").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
287        let a = mvalue.get("a").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
288        RGBA::from_u8s(r, g, b, a)
289    }
290}
291impl From<&MValue> for RGBA {
292    fn from(mvalue: &MValue) -> Self {
293        let r = mvalue.get("r").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
294        let g = mvalue.get("g").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
295        let b = mvalue.get("b").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
296        let a = mvalue.get("a").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
297        RGBA::from_u8s(r, g, b, a)
298    }
299}
300impl From<RGBA> for MValue {
301    fn from(value: RGBA) -> Self {
302        let (r, g, b, a) = value.to_u8s();
303        MValue::from([
304            ("r".into(), (r as u64).into()),
305            ("g".into(), (g as u64).into()),
306            ("b".into(), (b as u64).into()),
307            ("a".into(), (a as u64).into()),
308        ])
309    }
310}
311impl From<RGBA> for ValueType {
312    fn from(value: RGBA) -> Self {
313        let (r, g, b, a) = value.to_u8s();
314        ValueType::Nested(MValue::from([
315            ("r".into(), (r as u64).into()),
316            ("g".into(), (g as u64).into()),
317            ("b".into(), (b as u64).into()),
318            ("a".into(), (a as u64).into()),
319        ]))
320    }
321}
322impl From<&ValueType> for RGBA {
323    fn from(value: &ValueType) -> Self {
324        let ValueType::Nested(mvalue) = value else {
325            panic!("Expected nested value type");
326        };
327        let r = mvalue.get("r").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
328        let g = mvalue.get("g").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
329        let b = mvalue.get("b").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
330        let a = mvalue.get("a").unwrap().to_prim().unwrap().to_u64().unwrap() as u8;
331        RGBA::from_u8s(r, g, b, a)
332    }
333}