Skip to main content

steel_utils/
color.rs

1//! Packed color values used by Vanilla codecs and network payloads.
2
3use std::io::{Cursor, Result, Write};
4
5use crate::serial::{ReadFrom, WriteTo};
6
7/// A packed color interpreted through its red, green, and blue channels.
8///
9/// The upper byte is preserved because Vanilla's RGB codecs do not normalize
10/// integer inputs, even though RGB consumers ignore that byte.
11#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
12#[repr(transparent)]
13pub struct RgbColor(i32);
14
15impl RgbColor {
16    /// Preserves a raw Vanilla packed integer as an RGB-semantic color.
17    #[must_use]
18    pub const fn new(raw: i32) -> Self {
19        Self(raw)
20    }
21
22    /// Returns the unchanged packed integer.
23    #[must_use]
24    pub const fn raw(self) -> i32 {
25        self.0
26    }
27
28    /// Returns the red channel.
29    #[must_use]
30    pub const fn red(self) -> u8 {
31        (self.0 as u32 >> 16) as u8
32    }
33
34    /// Returns the green channel.
35    #[must_use]
36    pub const fn green(self) -> u8 {
37        (self.0 as u32 >> 8) as u8
38    }
39
40    /// Returns the blue channel.
41    #[must_use]
42    pub const fn blue(self) -> u8 {
43        self.0 as u8
44    }
45
46    /// Replaces the ignored upper byte with an alpha channel.
47    #[must_use]
48    pub const fn with_alpha(self, alpha: u8) -> ArgbColor {
49        ArgbColor::new(((alpha as u32) << 24 | (self.0 as u32 & 0x00ff_ffff)) as i32)
50    }
51}
52
53impl WriteTo for RgbColor {
54    fn write(&self, writer: &mut impl Write) -> Result<()> {
55        self.0.write(writer)
56    }
57}
58
59impl ReadFrom for RgbColor {
60    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
61        Ok(Self::new(i32::read(data)?))
62    }
63}
64
65/// A packed color interpreted through its alpha, red, green, and blue channels.
66#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
67#[repr(transparent)]
68pub struct ArgbColor(i32);
69
70impl ArgbColor {
71    /// Preserves a raw Vanilla packed integer as an ARGB-semantic color.
72    #[must_use]
73    pub const fn new(raw: i32) -> Self {
74        Self(raw)
75    }
76
77    /// Returns the unchanged packed integer.
78    #[must_use]
79    pub const fn raw(self) -> i32 {
80        self.0
81    }
82
83    /// Returns the alpha channel.
84    #[must_use]
85    pub const fn alpha(self) -> u8 {
86        (self.0 as u32 >> 24) as u8
87    }
88
89    /// Returns the red channel.
90    #[must_use]
91    pub const fn red(self) -> u8 {
92        (self.0 as u32 >> 16) as u8
93    }
94
95    /// Returns the green channel.
96    #[must_use]
97    pub const fn green(self) -> u8 {
98        (self.0 as u32 >> 8) as u8
99    }
100
101    /// Returns the blue channel.
102    #[must_use]
103    pub const fn blue(self) -> u8 {
104        self.0 as u8
105    }
106
107    /// Returns the same packed bits with RGB semantics.
108    #[must_use]
109    pub const fn rgb(self) -> RgbColor {
110        RgbColor::new(self.0)
111    }
112}
113
114impl WriteTo for ArgbColor {
115    fn write(&self, writer: &mut impl Write) -> Result<()> {
116        self.0.write(writer)
117    }
118}
119
120impl ReadFrom for ArgbColor {
121    fn read(data: &mut Cursor<&[u8]>) -> Result<Self> {
122        Ok(Self::new(i32::read(data)?))
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn rgb_preserves_ignored_upper_byte() {
132        let color = RgbColor::new(0x7f12_3456);
133
134        assert_eq!(color.raw(), 0x7f12_3456);
135        assert_eq!(
136            (color.red(), color.green(), color.blue()),
137            (0x12, 0x34, 0x56)
138        );
139        assert_eq!(color.with_alpha(0xaa).raw(), 0xaa12_3456_u32 as i32);
140    }
141
142    #[test]
143    fn argb_exposes_all_channels() {
144        let color = ArgbColor::new(0xaabb_ccdd_u32 as i32);
145
146        assert_eq!(
147            (color.alpha(), color.red(), color.green(), color.blue()),
148            (0xaa, 0xbb, 0xcc, 0xdd)
149        );
150    }
151}