arqoii_types/
lib.rs

1#![no_std]
2
3/// The byte sequence beginning the **Qoi F**ormat Header
4pub const QOI_MAGIC: [u8; 4] = *b"qoif";
5
6/// The byte sequence marking the end of a Qoi File
7pub const QOI_FOOTER: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1];
8
9/// A single RGB/RGBA pixel
10///
11/// In case of RGB the alpha value should always be 255
12///
13/// For RGBA the values should be un-premultiplied alpha
14#[derive(PartialEq, Eq, Debug, Clone)]
15pub struct Pixel {
16    pub r: u8,
17    pub g: u8,
18    pub b: u8,
19    pub a: u8,
20}
21
22impl Pixel {
23    /// A Pixel with all channels set to 0
24    pub const ZERO: Self = Pixel {
25        r: 0,
26        g: 0,
27        b: 0,
28        a: 0,
29    };
30
31    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
32        Self::rgba(r, g, b, 255)
33    }
34
35    pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
36        Self { r, g, b, a }
37    }
38
39    /// Calculate the Pixel Hash as described by the Qoi Specification
40    pub fn pixel_hash(&self) -> u8 {
41        (((self.r as usize) * 3
42            + (self.g as usize) * 5
43            + (self.b as usize) * 7
44            + (self.a as usize) * 11)
45            % 64) as u8
46    }
47}
48
49/// The internal state of a Qoi{De,En}coder
50pub struct CoderState {
51    pub previous: Pixel,
52    pub index: [Pixel; 64],
53    pub run: u8,
54}
55
56impl Default for CoderState {
57    fn default() -> Self {
58        Self {
59            previous: Pixel::rgba(0, 0, 0, 255),
60            index: [Pixel::ZERO; 64],
61            run: 0,
62        }
63    }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
67#[repr(u8)]
68pub enum QoiChannels {
69    Rgb = 3,
70    Rgba = 4,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
74#[repr(u8)]
75pub enum QoiColorSpace {
76    SRgbWithLinearAlpha = 0,
77    AllChannelsLinear = 1,
78}
79
80/// A struct representing the Qoi Format File Header
81#[derive(Debug, PartialEq, Eq)]
82pub struct QoiHeader {
83    pub width: u32,
84    pub height: u32,
85    pub channels: QoiChannels,
86    pub color_space: QoiColorSpace,
87}
88
89impl QoiHeader {
90    pub fn new(width: u32, height: u32, channels: QoiChannels, color_space: QoiColorSpace) -> Self {
91        Self {
92            width,
93            height,
94            channels,
95            color_space,
96        }
97    }
98
99    pub fn to_bytes(&self) -> [u8; 14] {
100        let mut bytes = [0; 14];
101
102        for (i, &b) in QOI_MAGIC.iter().enumerate() {
103            bytes[i] = b;
104        }
105
106        for (i, b) in self.width.to_be_bytes().into_iter().enumerate() {
107            bytes[i + QOI_MAGIC.len()] = b;
108        }
109
110        for (i, b) in self.height.to_be_bytes().into_iter().enumerate() {
111            bytes[i + QOI_MAGIC.len() + (u32::BITS / 8) as usize] = b;
112        }
113
114        bytes[QOI_MAGIC.len() + 2 * (u32::BITS / 8) as usize] = self.channels.clone() as u8;
115        bytes[QOI_MAGIC.len() + 2 * (u32::BITS / 8) as usize + 1] = self.color_space.clone() as u8;
116
117        bytes
118    }
119}
120
121/// An individual Chunk,
122/// representing between 1 and 62 pixel
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub enum QoiChunk {
125    #[non_exhaustive]
126    Rgb { r: u8, g: u8, b: u8 },
127    #[non_exhaustive]
128    Rgba { r: u8, g: u8, b: u8, a: u8 },
129    #[non_exhaustive]
130    Index { idx: u8 /* u6 0..=63 */ },
131    #[non_exhaustive]
132    Diff {
133        dr: i8, /* i2 -2..=1 */
134        dg: i8, /* i2 -2..=1 */
135        db: i8, /* i2 -2..=1 */
136    },
137    #[non_exhaustive]
138    Luma {
139        dg: i8,    /* i6 -32..=31 */
140        dr_dg: i8, /* i4 -8..=7 */
141        db_dg: i8, /* i4 -8..=7 */
142    },
143    #[non_exhaustive]
144    Run { run: u8 /* u6, 1..=62 */ },
145}
146
147impl QoiChunk {
148    /// Create a new Run Chunk, run needs to be in the range 0..=62
149    pub fn new_run(run: u8) -> Self {
150        debug_assert!(0 < run && run <= 62);
151        Self::Run { run }
152    }
153
154    // Create a new Index Chunk, index needs to be at most 63
155    pub fn new_index(idx: u8) -> Self {
156        debug_assert!(idx <= 63);
157        Self::Index { idx }
158    }
159
160    // Create a new Diff Chunk, all arguments need to be in the range -1..=1
161    pub fn new_diff(dr: i8, dg: i8, db: i8) -> Self {
162        debug_assert!((-2..=1).contains(&dr));
163        debug_assert!((-2..=1).contains(&dg));
164        debug_assert!((-2..=1).contains(&db));
165
166        Self::Diff { dr, dg, db }
167    }
168
169    // Create a new Luma Chunk, dg needs to be in the range -32..=31, dr_dg and db_dg need to be in the range -8..=7
170    pub fn new_luma(dg: i8, dr_dg: i8, db_dg: i8) -> Self {
171        debug_assert!((-32..=31).contains(&dg));
172        debug_assert!((-8..=7).contains(&dr_dg));
173        debug_assert!((-8..=7).contains(&db_dg));
174
175        Self::Luma { dg, dr_dg, db_dg }
176    }
177
178    // Creates a new RGB Chunk
179    pub fn new_rgb(r: u8, g: u8, b: u8) -> Self {
180        Self::Rgb { r, g, b }
181    }
182
183    // Creates a new RGBA Chunk
184    pub fn new_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
185        Self::Rgba { r, g, b, a }
186    }
187
188    /// Write the Chunk into the provided ChunkBuf
189    fn write_to_chunk_buffer(&self, buf: &mut ChunkBuf) {
190        match self.clone() {
191            QoiChunk::Rgb { r, g, b } => {
192                // [0b11111110] r g b
193                buf.set([0b11111110, r, g, b])
194            }
195            QoiChunk::Rgba { r, g, b, a } => {
196                // [0b11111111] r g b a
197                buf.set([0b11111111, r, g, b, a])
198            }
199            QoiChunk::Index { idx } => {
200                // [ 0 0  idx idx idx idx idx idx]
201                buf.set([0b00111111 & idx])
202            }
203            QoiChunk::Diff { dr, dg, db } => {
204                // [ 0 1 dr dr dg dg db db]
205                buf.set([0b01000000
206                    | (0b00111111
207                        & ((0b11 & (dr + 2) as u8) << 4
208                            | (0b11 & (dg + 2) as u8) << 2
209                            | (0b11 & (db + 2) as u8)))])
210            }
211            QoiChunk::Luma { dg, dr_dg, db_dg } => {
212                // [ 1 0 dg dg dg dg dg dg] [ dr_dg dr_dg dr_dg dr_dg db_dg db_dg db_dg db_dg ]
213                buf.set([
214                    0b10000000 | (0b00111111 & (dg + 32) as u8),
215                    (0b1111 & (dr_dg + 8) as u8) << 4 | (0b1111 & (db_dg + 8) as u8),
216                ])
217            }
218            QoiChunk::Run { run } => {
219                // [ 1 1 run run run run run run ]
220                // Note: [ 1 1 1 1 1 1 1 1 ] & [ 1 1 1 1 1 1 1 0 ] are invalid here
221                debug_assert!(run <= 62);
222                buf.set([0b11000000 | (run - 1)]);
223            }
224        }
225    }
226}
227
228impl IntoIterator for QoiChunk {
229    type Item = u8;
230
231    type IntoIter = ChunkBuf;
232
233    fn into_iter(self) -> Self::IntoIter {
234        let mut buf = ChunkBuf::new();
235        self.write_to_chunk_buffer(&mut buf);
236        buf
237    }
238}
239
240/// A buffer for the bytes of a single Chunk
241///
242/// used to iterate over the bytes of a Chunk
243pub struct ChunkBuf {
244    data: [u8; 5],
245    len: u8,
246    offset: u8,
247}
248
249trait ChunkData {}
250
251impl ChunkData for [u8; 1] {}
252impl ChunkData for [u8; 2] {}
253impl ChunkData for [u8; 3] {}
254impl ChunkData for [u8; 4] {}
255impl ChunkData for [u8; 5] {}
256
257impl ChunkBuf {
258    /// Create a new empty ChunkBuf
259    pub fn new() -> Self {
260        ChunkBuf {
261            data: [0; 5],
262            len: 0,
263            offset: 0,
264        }
265    }
266
267    /// Set the content of the ChunkBuf
268    fn set<const N: usize>(&mut self, data: [u8; N])
269    where
270        [u8; N]: ChunkData,
271    {
272        (0..N).for_each(|i| {
273            self.data[i] = data[i];
274        });
275        self.offset = 0;
276        self.len = N as u8;
277    }
278
279    /// Get the data of the last written Chunk, this includes already popped bytes
280    pub fn as_slice(&self) -> &[u8] {
281        &self.data[0..self.len as usize]
282    }
283}
284
285impl Iterator for ChunkBuf {
286    type Item = u8;
287
288    fn next(&mut self) -> Option<Self::Item> {
289        if self.offset < self.len {
290            let res = self.data[self.offset as usize];
291            self.offset += 1;
292            Some(res)
293        } else {
294            None
295        }
296    }
297}
298
299impl Default for ChunkBuf {
300    fn default() -> Self {
301        Self::new()
302    }
303}