qoi/
encode.rs

1#[cfg(any(feature = "std", feature = "alloc"))]
2use alloc::{vec, vec::Vec};
3#[cfg(feature = "std")]
4use std::io::Write;
5
6use bytemuck::Pod;
7
8use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
9use crate::error::{Error, Result};
10use crate::header::Header;
11use crate::pixel::{Pixel, SupportedChannels};
12use crate::types::{Channels, ColorSpace, RawChannels};
13#[cfg(feature = "std")]
14use crate::utils::GenericWriter;
15use crate::utils::{unlikely, BytesMut, Writer};
16
17#[allow(clippy::cast_possible_truncation, unused_assignments, unused_variables)]
18fn encode_impl<W: Writer, const N: usize, const R: usize>(
19    mut buf: W, data: &[u8], width: usize, height: usize, stride: usize,
20    read_px: impl Fn(&mut Pixel<N>, &[u8]),
21) -> Result<usize>
22where
23    Pixel<N>: SupportedChannels,
24    [u8; N]: Pod,
25{
26    let cap = buf.capacity();
27
28    let mut index = [Pixel::new(); 256];
29    let mut px_prev = Pixel::new().with_a(0xff);
30    let mut hash_prev = px_prev.hash_index();
31    let mut run = 0_u8;
32    let mut px = Pixel::<N>::new().with_a(0xff);
33    let mut index_allowed = false;
34
35    let n_pixels = width * height;
36
37    let mut i = 0;
38    for row in data.chunks(stride).take(height) {
39        let pixel_row = &row[..width * R];
40        for chunk in pixel_row.chunks_exact(R) {
41            read_px(&mut px, chunk);
42            if px == px_prev {
43                run += 1;
44                if run == 62 || unlikely(i == n_pixels - 1) {
45                    buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
46                    run = 0;
47                }
48            } else {
49                if run != 0 {
50                    #[cfg(not(feature = "reference"))]
51                    {
52                        // credits for the original idea: @zakarumych (had to be fixed though)
53                        buf = buf.write_one(if run == 1 && index_allowed {
54                            QOI_OP_INDEX | hash_prev
55                        } else {
56                            QOI_OP_RUN | (run - 1)
57                        })?;
58                    }
59                    #[cfg(feature = "reference")]
60                    {
61                        buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
62                    }
63                    run = 0;
64                }
65                index_allowed = true;
66                let px_rgba = px.as_rgba(0xff);
67                hash_prev = px_rgba.hash_index();
68                let index_px = &mut index[hash_prev as usize];
69                if *index_px == px_rgba {
70                    buf = buf.write_one(QOI_OP_INDEX | hash_prev)?;
71                } else {
72                    *index_px = px_rgba;
73                    buf = px.encode_into(px_prev, buf)?;
74                }
75                px_prev = px;
76            }
77            i += 1;
78        }
79    }
80
81    assert_eq!(i, n_pixels);
82    buf = buf.write_many(&QOI_PADDING)?;
83    Ok(cap.saturating_sub(buf.capacity()))
84}
85
86/// The maximum number of bytes the encoded image will take.
87///
88/// Can be used to pre-allocate the buffer to encode the image into.
89#[inline]
90pub fn encode_max_len(width: u32, height: u32, channels: impl Into<u8>) -> usize {
91    let (width, height) = (width as usize, height as usize);
92    let n_pixels = width.saturating_mul(height);
93    QOI_HEADER_SIZE
94        + n_pixels.saturating_mul(channels.into() as usize)
95        + n_pixels
96        + QOI_PADDING_SIZE
97}
98
99/// Encode the image into a pre-allocated buffer.
100///
101/// Returns the total number of bytes written.
102#[inline]
103pub fn encode_to_buf(
104    buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
105) -> Result<usize> {
106    Encoder::new(&data, width, height)?.encode_to_buf(buf)
107}
108
109/// Encode the image into a newly allocated vector.
110#[cfg(any(feature = "alloc", feature = "std"))]
111#[inline]
112pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
113    Encoder::new(&data, width, height)?.encode_to_vec()
114}
115
116pub struct EncoderBuilder<'a> {
117    data: &'a [u8],
118    width: u32,
119    height: u32,
120    stride: Option<usize>,
121    raw_channels: Option<RawChannels>,
122    colorspace: Option<ColorSpace>,
123}
124
125impl<'a> EncoderBuilder<'a> {
126    /// Creates a new encoder builder from a given array of pixel data and image dimensions.
127    pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Self {
128        Self {
129            data: data.as_ref(),
130            width,
131            height,
132            stride: None,
133            raw_channels: None,
134            colorspace: None,
135        }
136    }
137
138    /// Set the stride of the pixel data.
139    pub const fn stride(mut self, stride: usize) -> Self {
140        self.stride = Some(stride);
141        self
142    }
143
144    /// Set the input format of the pixel data.
145    pub const fn raw_channels(mut self, raw_channels: RawChannels) -> Self {
146        self.raw_channels = Some(raw_channels);
147        self
148    }
149
150    /// Set the colorspace.
151    pub const fn colorspace(mut self, colorspace: ColorSpace) -> Self {
152        self.colorspace = Some(colorspace);
153        self
154    }
155
156    /// Build the encoder.
157    pub fn build(self) -> Result<Encoder<'a>> {
158        let EncoderBuilder { data, width, height, stride, raw_channels, colorspace } = self;
159
160        let size = data.len();
161        let no_stride = stride.is_none();
162        let stride = stride.unwrap_or(
163            size.checked_div(height as usize)
164                .ok_or(Error::InvalidImageDimensions { width, height })?,
165        );
166        let raw_channels = raw_channels.unwrap_or(if stride == width as usize * 3 {
167            RawChannels::Rgb
168        } else {
169            RawChannels::Rgba
170        });
171
172        if stride < width as usize * raw_channels.bytes_per_pixel() {
173            return Err(Error::InvalidImageLength { size, width, height });
174        }
175        if stride * (height - 1) as usize + width as usize * raw_channels.bytes_per_pixel() < size {
176            return Err(Error::InvalidImageLength { size, width, height });
177        }
178        if no_stride && size != width as usize * height as usize * raw_channels.bytes_per_pixel() {
179            return Err(Error::InvalidImageLength { size, width, height });
180        }
181
182        let channels = raw_channels.into();
183        let colorspace = colorspace.unwrap_or_default();
184
185        Ok(Encoder {
186            data,
187            stride,
188            raw_channels,
189            header: Header::try_new(self.width, self.height, channels, colorspace)?,
190        })
191    }
192}
193
194/// Encode QOI images into buffers or into streams.
195pub struct Encoder<'a> {
196    data: &'a [u8],
197    stride: usize,
198    raw_channels: RawChannels,
199    header: Header,
200}
201
202impl<'a> Encoder<'a> {
203    /// Creates a new encoder from a given array of pixel data and image dimensions.
204    /// The data must be in RGB(A) order, without fill borders (extra stride).
205    ///
206    /// The number of channels will be inferred automatically (the valid values
207    /// are 3 or 4). The color space will be set to sRGB by default.
208    #[inline]
209    #[allow(clippy::cast_possible_truncation)]
210    pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
211        EncoderBuilder::new(data, width, height).build()
212    }
213
214    /// Returns a new encoder with modified color space.
215    ///
216    /// Note: the color space doesn't affect encoding or decoding in any way, it's
217    /// a purely informative field that's stored in the image header.
218    #[inline]
219    pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
220        self.header = self.header.with_colorspace(colorspace);
221        self
222    }
223
224    /// Returns the inferred number of channels.
225    #[inline]
226    pub const fn channels(&self) -> Channels {
227        self.header.channels
228    }
229
230    /// Returns the header that will be stored in the encoded image.
231    #[inline]
232    pub const fn header(&self) -> &Header {
233        &self.header
234    }
235
236    /// The maximum number of bytes the encoded image will take.
237    ///
238    /// Can be used to pre-allocate the buffer to encode the image into.
239    #[inline]
240    pub fn required_buf_len(&self) -> usize {
241        self.header.encode_max_len()
242    }
243
244    /// Encodes the image to a pre-allocated buffer and returns the number of bytes written.
245    ///
246    /// The minimum size of the buffer can be found via [`Encoder::required_buf_len`].
247    #[inline]
248    pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
249        let buf = buf.as_mut();
250        let size_required = self.required_buf_len();
251        if unlikely(buf.len() < size_required) {
252            return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
253        }
254        let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); // can't panic
255        head.copy_from_slice(&self.header.encode());
256        let n_written = self.encode_impl_all(BytesMut::new(tail))?;
257        Ok(QOI_HEADER_SIZE + n_written)
258    }
259
260    /// Encodes the image into a newly allocated vector of bytes and returns it.
261    #[cfg(any(feature = "alloc", feature = "std"))]
262    #[inline]
263    pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
264        let mut out = vec![0_u8; self.required_buf_len()];
265        let size = self.encode_to_buf(&mut out)?;
266        out.truncate(size);
267        Ok(out)
268    }
269
270    /// Encodes the image directly to a generic writer that implements [`Write`](std::io::Write).
271    ///
272    /// Note: while it's possible to pass a `&mut [u8]` slice here since it implements `Write`,
273    /// it would more effficient to use a specialized method instead: [`Encoder::encode_to_buf`].
274    #[cfg(feature = "std")]
275    #[inline]
276    pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {
277        writer.write_all(&self.header.encode())?;
278        let n_written = self.encode_impl_all(GenericWriter::new(writer))?;
279        Ok(n_written + QOI_HEADER_SIZE)
280    }
281
282    #[inline]
283    fn encode_impl_all<W: Writer>(&self, out: W) -> Result<usize> {
284        let width = self.header.width as usize;
285        let height = self.header.height as usize;
286        let stride = self.stride;
287        match self.raw_channels {
288            RawChannels::Rgb => {
289                encode_impl::<_, 3, 3>(out, self.data, width, height, stride, Pixel::read)
290            }
291            RawChannels::Bgr => {
292                encode_impl::<_, 3, 3>(out, self.data, width, height, stride, |px, c| {
293                    px.update_rgb(c[2], c[1], c[0]);
294                })
295            }
296            RawChannels::Rgba => {
297                encode_impl::<_, 4, 4>(out, self.data, width, height, stride, Pixel::read)
298            }
299            RawChannels::Argb => {
300                encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
301                    px.update_rgba(c[1], c[2], c[3], c[0]);
302                })
303            }
304            RawChannels::Rgbx => {
305                encode_impl::<_, 3, 4>(out, self.data, width, height, stride, |px, c| {
306                    px.read(&c[..3]);
307                })
308            }
309            RawChannels::Xrgb => {
310                encode_impl::<_, 3, 4>(out, self.data, width, height, stride, |px, c| {
311                    px.update_rgb(c[1], c[2], c[3]);
312                })
313            }
314            RawChannels::Bgra => {
315                encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
316                    px.update_rgba(c[2], c[1], c[0], c[3]);
317                })
318            }
319            RawChannels::Abgr => {
320                encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
321                    px.update_rgba(c[3], c[2], c[1], c[0]);
322                })
323            }
324            RawChannels::Bgrx => {
325                encode_impl::<_, 3, 4>(out, self.data, width, height, stride, |px, c| {
326                    px.update_rgb(c[2], c[1], c[0]);
327                })
328            }
329            RawChannels::Xbgr => {
330                encode_impl::<_, 4, 4>(out, self.data, width, height, stride, |px, c| {
331                    px.update_rgb(c[3], c[2], c[1]);
332                })
333            }
334        }
335    }
336}