1#![no_std]
2
3pub const QOI_MAGIC: [u8; 4] = *b"qoif";
5
6pub const QOI_FOOTER: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1];
8
9#[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 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 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
49pub 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#[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#[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 },
131 #[non_exhaustive]
132 Diff {
133 dr: i8, dg: i8, db: i8, },
137 #[non_exhaustive]
138 Luma {
139 dg: i8, dr_dg: i8, db_dg: i8, },
143 #[non_exhaustive]
144 Run { run: u8 },
145}
146
147impl QoiChunk {
148 pub fn new_run(run: u8) -> Self {
150 debug_assert!(0 < run && run <= 62);
151 Self::Run { run }
152 }
153
154 pub fn new_index(idx: u8) -> Self {
156 debug_assert!(idx <= 63);
157 Self::Index { idx }
158 }
159
160 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 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 pub fn new_rgb(r: u8, g: u8, b: u8) -> Self {
180 Self::Rgb { r, g, b }
181 }
182
183 pub fn new_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
185 Self::Rgba { r, g, b, a }
186 }
187
188 fn write_to_chunk_buffer(&self, buf: &mut ChunkBuf) {
190 match self.clone() {
191 QoiChunk::Rgb { r, g, b } => {
192 buf.set([0b11111110, r, g, b])
194 }
195 QoiChunk::Rgba { r, g, b, a } => {
196 buf.set([0b11111111, r, g, b, a])
198 }
199 QoiChunk::Index { idx } => {
200 buf.set([0b00111111 & idx])
202 }
203 QoiChunk::Diff { dr, dg, db } => {
204 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 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 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
240pub 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 pub fn new() -> Self {
260 ChunkBuf {
261 data: [0; 5],
262 len: 0,
263 offset: 0,
264 }
265 }
266
267 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 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}