Skip to main content

rapid_qoi/
decode.rs

1use core::convert::TryInto;
2
3use super::*;
4
5#[cfg(feature = "alloc")]
6use alloc::{vec, vec::Vec};
7
8/// Errros that may occur during image decoding.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub enum DecodeError {
11    /// Buffer does not contain enough encoded data.
12    NotEnoughData,
13
14    /// Encoded header contains invalid magic value.\
15    /// First four bytes must contain `b"qoif"`.\
16    /// This usually indicates that buffer does not contain QOI image.
17    InvalidMagic,
18
19    /// Encoded header contains invalud channels number.\
20    /// QOI supports only images with `3` or `4` channels.\
21    /// Any other value cannot be produced by valid encoder.
22    InvalidChannelsValue,
23
24    /// Encoded header contains invalud color space value.'
25    /// QOI supports only images with SRGB color channels and linear alpha (if present) denoted by `0` and all linear channels denoted by `1`.\
26    /// Any other value cannot be produced by valid encoder.
27    InvalidColorSpaceValue,
28
29    /// Output buffer is too small to fit decoded image.
30    OutputIsTooSmall,
31}
32
33impl Display for DecodeError {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        match self {
36            DecodeError::NotEnoughData => {
37                f.write_str("Buffer does not contain enough encoded data")
38            }
39            DecodeError::InvalidMagic => f.write_str("Encoded header contains invalid magic value"),
40            DecodeError::InvalidChannelsValue => {
41                f.write_str("Encoded header contains invalud channels number. Must be 3 or 4")
42            }
43            DecodeError::InvalidColorSpaceValue => {
44                f.write_str("Encoded header contains invalud color space value. Must be 0 or 1")
45            }
46            DecodeError::OutputIsTooSmall => {
47                f.write_str("Output buffer is too small to fit decoded image")
48            }
49        }
50    }
51}
52
53#[cfg(feature = "std")]
54impl std::error::Error for DecodeError {}
55
56impl Qoi {
57    /// Returns bytes size for the decoded image.
58    #[inline]
59    pub fn decoded_size(&self) -> usize {
60        self.width as usize * self.height as usize * self.colors.channels()
61    }
62
63    /// Reads header from encoded QOI image.\
64    /// Returned header can be analyzed before proceeding parsing with [`Qoi::decode_skip_header`].
65    pub fn decode_header(bytes: &[u8]) -> Result<Self, DecodeError> {
66        if bytes.len() < QOI_HEADER_SIZE {
67            return Err(DecodeError::NotEnoughData);
68        }
69
70        let magic = u32::from_be_bytes(bytes[0..4].try_into().unwrap());
71        if magic != QOI_MAGIC {
72            return Err(DecodeError::InvalidMagic);
73        }
74
75        let w = u32::from_be_bytes(bytes[4..8].try_into().unwrap());
76        let h = u32::from_be_bytes(bytes[8..12].try_into().unwrap());
77
78        let channels = bytes[12];
79        let colors = bytes[13];
80
81        Ok(Qoi {
82            width: w,
83            height: h,
84            colors: match (channels, colors) {
85                (3, 0) => Colors::Srgb,
86                (4, 0) => Colors::SrgbLinA,
87                (3, 1) => Colors::Rgb,
88                (4, 1) => Colors::Rgba,
89                (_, 0 | 1) => return Err(DecodeError::InvalidChannelsValue),
90                (_, _) => return Err(DecodeError::InvalidColorSpaceValue),
91            },
92        })
93    }
94
95    /// Decode a QOI image from bytes slice.\
96    /// Decoded raw RGB or RGBA pixels are written into `output` slice.
97    ///
98    /// On success this function returns `Ok(qoi)` with `qoi` describing image dimensions and color space.\
99    /// On failure this function returns `Err(err)` with `err` describing cause of the error.
100    #[inline]
101    pub fn decode(bytes: &[u8], output: &mut [u8]) -> Result<Self, DecodeError> {
102        let qoi = Self::decode_header(bytes)?;
103        qoi.decode_skip_header(&bytes[QOI_HEADER_SIZE..], output)?;
104        Ok(qoi)
105    }
106
107    /// Decode a QOI image from bytes slice.\
108    /// `bytes` does not include QOI header. Uses provided `Qoi` value instead.\
109    /// Decoded raw RGB or RGBA (depending on `self.colors` value) pixels are written into `output` slice.
110    ///
111    /// On success this function returns `Ok(())`.\
112    /// On failure this function returns `Err(err)` with `err` describing cause of the error.
113    #[inline]
114    pub fn decode_skip_header(&self, bytes: &[u8], output: &mut [u8]) -> Result<(), DecodeError> {
115        if self.width == 0 || self.height == 0 {
116            return Ok(());
117        }
118
119        let px_len = self.decoded_size();
120
121        let output = match output.get_mut(..px_len) {
122            None => return Err(DecodeError::OutputIsTooSmall),
123            Some(output) => output,
124        };
125
126        match self.colors.has_alpha() {
127            true => {
128                Self::decode_range::<4>(
129                    &mut [Pixel::new(); 64],
130                    &mut Pixel::new_opaque(),
131                    &mut 0,
132                    bytes,
133                    output,
134                )?;
135            }
136            false => {
137                Self::decode_range::<3>(
138                    &mut [Pixel::new(); 64],
139                    &mut Pixel::new_opaque(),
140                    &mut 0,
141                    bytes,
142                    output,
143                )?;
144            }
145        }
146        Ok(())
147    }
148
149    /// Decode range of pixels into pixels slice.
150    #[inline]
151    pub fn decode_range<const N: usize>(
152        index: &mut [[u8; N]; 64],
153        ppx: &mut [u8; N],
154        prun: &mut usize,
155        bytes: &[u8],
156        pixels: &mut [u8],
157    ) -> Result<usize, DecodeError>
158    where
159        [u8; N]: Pixel,
160    {
161        assert_eq!(pixels.len() % N, 0);
162
163        // let (mut pixels, rem) = pixels.as_chunks_mut::<N>();
164        // let mut pixels = pixels.chunks_exact_mut(N).map(cast_pixel::<N>);
165
166        let mut pixels = bytemuck::cast_slice_mut(pixels);
167
168        // assert!(rem.is_empty());
169
170        let mut px = *ppx;
171
172        if *prun > 0 {
173            // let len = pixels.len();
174
175            let (head, tail) = pixels.split_at_mut((*prun).min(pixels.len()));
176
177            // pixels.by_ref().take(*prun).for_each(|pixel| *pixel = px);
178
179            pixels = tail;
180            head.fill(px);
181
182            if pixels.is_empty() {
183                cold();
184                *prun -= head.len();
185                return Ok(0);
186            } else {
187                *prun = 0;
188            }
189        }
190
191        let mut rest = bytes;
192
193        loop {
194            match pixels {
195                [out, tail @ ..] => {
196                    // Some(out) => {
197                    pixels = tail;
198                    match rest {
199                        [b1 @ 0b00000000..=0b00111111, tail @ ..] => {
200                            px = index[*b1 as usize];
201                            *out = px;
202
203                            rest = tail;
204                            continue;
205                        }
206                        [b1 @ 0b01000000..=0b01111111, tail @ ..] => {
207                            let vr = ((b1 >> 4) & 0x03).wrapping_sub(2);
208                            let vg = ((b1 >> 2) & 0x03).wrapping_sub(2);
209                            let vb = (b1 & 0x03).wrapping_sub(2);
210                            px.add_rgb(vr, vg, vb);
211
212                            rest = tail;
213                        }
214                        [b1 @ 0b10000000..=0b10111111, b2, tail @ ..] => {
215                            let vg = (b1 & 0x3f).wrapping_sub(32);
216                            let vr = ((b2 >> 4) & 0x0f).wrapping_sub(8).wrapping_add(vg);
217                            let vb = (b2 & 0x0f).wrapping_sub(8).wrapping_add(vg);
218                            px.add_rgb(vr, vg, vb);
219
220                            rest = tail;
221                        }
222                        [0b11111110, b2, b3, b4, tail @ ..] => {
223                            px.set_rgb(*b2, *b3, *b4);
224                            // px[0] = *b2;
225                            // px[1] = *b3;
226                            // px[2] = *b4;
227
228                            rest = tail;
229                        }
230                        [0b11111111, b2, b3, b4, _b5, tail @ ..] if N == 3 => {
231                            cold();
232                            px.set_rgb(*b2, *b3, *b4);
233                            // px[0] = *b2;
234                            // px[1] = *b3;
235                            // px[2] = *b4;
236
237                            rest = tail;
238                        }
239                        [0b11111111, b2, b3, b4, b5, tail @ ..] => {
240                            px.set_rgba(*b2, *b3, *b4, *b5);
241
242                            // px[0] = *b2;
243                            // px[1] = *b3;
244                            // px[2] = *b4;
245                            // px[3] = *b5;
246
247                            rest = tail;
248                        }
249                        [b1 @ 0b11000000..=0b11111101, dtail @ ..] => {
250                            *out = px;
251                            let run = *b1 as usize & 0x3f;
252                            let (head, tail) = pixels.split_at_mut(run);
253                            head.fill(px);
254                            pixels = tail;
255                            rest = dtail;
256
257                            // let len = pixels.len();
258                            // pixels.by_ref().take(run.min(len)).for_each(|out| *out = px);
259
260                            if unlikely(pixels.is_empty()) {
261                                *prun = run - head.len();
262                                break;
263                            }
264
265                            continue;
266                        }
267                        _ => {
268                            // if unlikely(rest.len() < QOI_PADDING) {
269                            return Err(DecodeError::NotEnoughData);
270                            // }
271                            // Unreachable arm due to length check above.
272                            // unreachable();
273                        }
274                    }
275                    //     }
276                    // }
277
278                    index[px.hash() as usize] = px;
279
280                    // px.write(chunk);
281                    *out = px;
282                    // output = px.write_head(output);
283                }
284                [] => {
285                    // None => {
286                    // None => {
287                    cold();
288                    break;
289                }
290            }
291        }
292
293        *ppx = px;
294
295        Ok(bytes.len() - rest.len())
296    }
297
298    /// Decode a QOI image from bytes slice.\
299    /// Decoded raw RGB or RGBA pixels are written into allocated `Vec`.
300    ///
301    /// On success this function returns `Ok((qoi, vec))` with `qoi` describing image dimensions and color space and `vec` containing raw pixels data.\
302    /// On failure this function returns `Err(err)` with `err` describing cause of the error.
303    #[cfg(feature = "alloc")]
304    #[inline]
305    pub fn decode_alloc(bytes: &[u8]) -> Result<(Self, Vec<u8>), DecodeError> {
306        let qoi = Self::decode_header(bytes)?;
307
308        let size = qoi.decoded_size();
309        let mut output = vec![0; size];
310        let qoi = Self::decode(bytes, &mut output)?;
311        Ok((qoi, output))
312    }
313}