retrofire_core/util/
pixfmt.rs

1use crate::math::{Color3, Color4};
2
3// TODO Do we need the trait at all?
4// pub trait PixelFmt {}
5
6pub trait IntoPixel<T, F>: Sized {
7    /// Converts `self` to `T` in format `F`.
8    fn into_pixel(self) -> T;
9
10    /// Converts `self` to `T`, taking an `F` to help type inference.
11    ///
12    /// This can be used to avoid the awkward fully-qualified syntax
13    /// `IntoPixel::<_, F>::into_pixel(self)`.
14    fn into_pixel_fmt(self, _: F) -> T {
15        self.into_pixel()
16    }
17}
18
19/// Eight-bit channels in R,G,B order.
20#[derive(Copy, Clone, Default)]
21pub struct Rgb888;
22/// 5,6,5-bit channels in R,G,B order.
23#[derive(Copy, Clone, Default)]
24pub struct Rgb565;
25
26/// Eight-bit channels in X,R,G,B order, where X is unused.
27#[derive(Copy, Clone, Default)]
28pub struct Xrgb8888;
29/// Eight-bit channels in R,G,B,A order.
30#[derive(Copy, Clone, Default)]
31pub struct Rgba8888;
32/// Eight-bit channels in A,R,G,B order.
33#[derive(Copy, Clone, Default)]
34pub struct Argb8888;
35/// Eight-bit channels in B,G,R,A order.
36#[derive(Copy, Clone, Default)]
37pub struct Bgra8888;
38
39/// Four-bit channels in R,G,B,A order.
40#[derive(Copy, Clone, Default)]
41pub struct Rgba4444;
42
43// Impls for Color3
44
45impl IntoPixel<u32, Rgb888> for Color3 {
46    fn into_pixel(self) -> u32 {
47        let [r, g, b] = self.0;
48        // [0x00, 0xRR, 0xGG, 0xBB] -> 0x00_RR_GG_BB
49        u32::from_be_bytes([0, r, g, b])
50    }
51}
52impl IntoPixel<[u8; 3], Rgb888> for Color3 {
53    fn into_pixel(self) -> [u8; 3] {
54        self.0
55    }
56}
57
58impl IntoPixel<u16, Rgb565> for Color3 {
59    fn into_pixel(self) -> u16 {
60        let [r, g, b] = self.0;
61        (r as u16 >> 3 & 0x1F) << 11
62            | (g as u16 >> 2 & 0x3F) << 5
63            | (b as u16 >> 3 & 0x1F)
64    }
65}
66
67impl IntoPixel<[u8; 2], Rgb565> for Color3 {
68    fn into_pixel(self) -> [u8; 2] {
69        let c: u16 = self.into_pixel();
70        c.to_ne_bytes()
71    }
72}
73
74// Impls for Color4
75
76impl<F> IntoPixel<u32, F> for Color4
77where
78    Self: IntoPixel<[u8; 4], F>,
79{
80    fn into_pixel(self) -> u32 {
81        // From [0xAA, 0xBB, 0xCC, 0xDD] to 0xAA_BB_CC_DD -> big-endian!
82        u32::from_be_bytes(self.into_pixel())
83    }
84}
85
86impl IntoPixel<u32, Xrgb8888> for Color4 {
87    fn into_pixel(self) -> u32 {
88        let [r, g, b, _] = self.0;
89        // From [0x00, 0xRR, 0xGG, 0xBB] to 0x00_RR_GG_BB -> big-endian!
90        u32::from_be_bytes([0, r, g, b])
91    }
92}
93impl IntoPixel<[u8; 4], Rgba8888> for Color4 {
94    fn into_pixel(self) -> [u8; 4] {
95        self.0
96    }
97}
98impl IntoPixel<[u8; 4], Argb8888> for Color4 {
99    fn into_pixel(self) -> [u8; 4] {
100        let [r, g, b, a] = self.0;
101        [a, r, g, b]
102    }
103}
104impl IntoPixel<[u8; 4], Bgra8888> for Color4 {
105    fn into_pixel(self) -> [u8; 4] {
106        let [r, g, b, a] = self.0;
107        [b, g, r, a]
108    }
109}
110impl IntoPixel<[u8; 3], Rgb888> for Color4 {
111    fn into_pixel(self) -> [u8; 3] {
112        [self.r(), self.g(), self.b()]
113    }
114}
115impl IntoPixel<[u8; 2], Rgba4444> for Color4 {
116    fn into_pixel(self) -> [u8; 2] {
117        let c: u16 = self.into_pixel_fmt(Rgba4444);
118        c.to_ne_bytes()
119    }
120}
121impl IntoPixel<u16, Rgba4444> for Color4 {
122    fn into_pixel(self) -> u16 {
123        let [r, g, b, a] = self.0.map(|c| c as u16 >> 4);
124        // [0xBA, 0xRG] in little-endian
125        r << 12 | g << 8 | b << 4 | a
126    }
127}
128impl IntoPixel<u16, Rgb565> for Color4 {
129    fn into_pixel(self) -> u16 {
130        self.to_rgb().into_pixel()
131    }
132}
133impl IntoPixel<[u8; 2], Rgb565> for Color4 {
134    fn into_pixel(self) -> [u8; 2] {
135        let c: u16 = self.into_pixel_fmt(Rgb565);
136        c.to_ne_bytes()
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::math::{rgb, rgba};
143
144    use super::*;
145
146    const COL3: Color3 = rgb(0x11u8, 0x22, 0x33);
147
148    #[test]
149    fn color3_to_rgb888() {
150        let pix: u32 = COL3.into_pixel_fmt(Rgb888);
151        assert_eq!(pix, 0x00_11_22_33);
152    }
153
154    #[test]
155    fn color3_to_rgb565() {
156        let pix: u16 = rgb(0x40, 0x20, 0x10).into_pixel();
157        assert_eq!(pix, 0b01000_001000_00010_u16);
158
159        let pix: [u8; 2] = rgb(0x40u8, 0x20, 0x10).into_pixel_fmt(Rgb565);
160        assert_eq!(pix, [0b000_00010, 0b01000_001]);
161    }
162
163    #[test]
164    fn color4_to_rgba8888() {
165        let col = rgba(0x11u8, 0x22, 0x33, 0x44);
166
167        let pix: u32 = col.into_pixel_fmt(Rgba8888);
168        assert_eq!(pix, 0x11_22_33_44);
169
170        let pix: [u8; 4] = col.into_pixel_fmt(Rgba8888);
171        assert_eq!(pix, [0x11, 0x22, 0x33, 0x44]);
172    }
173
174    const COL4: Color4 = rgba(0x11u8, 0x22, 0x33, 0x44);
175
176    #[test]
177    fn color4_to_argb8888() {
178        let pix: u32 = COL4.into_pixel_fmt(Argb8888);
179        assert_eq!(pix, 0x44_11_22_33);
180
181        let pix: [u8; 4] = COL4.into_pixel_fmt(Argb8888);
182        assert_eq!(pix, [0x44, 0x11, 0x22, 0x33]);
183    }
184
185    #[test]
186    fn color4_to_bgra8888() {
187        let pix: u32 = COL4.into_pixel_fmt(Bgra8888);
188        assert_eq!(pix, 0x33_22_11_44);
189
190        let pix: [u8; 4] = COL4.into_pixel_fmt(Bgra8888);
191        assert_eq!(pix, [0x33, 0x22, 0x11, 0x44]);
192    }
193
194    #[test]
195    fn color4_to_rgba4444() {
196        let pix: [u8; 2] = COL4.into_pixel_fmt(Rgba4444);
197        assert_eq!(pix, [0x34, 0x12]);
198
199        let pix: u16 = COL4.into_pixel_fmt(Rgba4444);
200        assert_eq!(pix, 0x1234);
201    }
202}