summaryrefslogtreecommitdiff
path: root/vendor/qoi/src/encode.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/qoi/src/encode.rs')
-rw-r--r--vendor/qoi/src/encode.rs210
1 files changed, 210 insertions, 0 deletions
diff --git a/vendor/qoi/src/encode.rs b/vendor/qoi/src/encode.rs
new file mode 100644
index 0000000..0ed8476
--- /dev/null
+++ b/vendor/qoi/src/encode.rs
@@ -0,0 +1,210 @@
+#[cfg(any(feature = "std", feature = "alloc"))]
+use alloc::{vec, vec::Vec};
+use core::convert::TryFrom;
+#[cfg(feature = "std")]
+use std::io::Write;
+
+use bytemuck::Pod;
+
+use crate::consts::{QOI_HEADER_SIZE, QOI_OP_INDEX, QOI_OP_RUN, QOI_PADDING, QOI_PADDING_SIZE};
+use crate::error::{Error, Result};
+use crate::header::Header;
+use crate::pixel::{Pixel, SupportedChannels};
+use crate::types::{Channels, ColorSpace};
+#[cfg(feature = "std")]
+use crate::utils::GenericWriter;
+use crate::utils::{unlikely, BytesMut, Writer};
+
+#[allow(clippy::cast_possible_truncation, unused_assignments, unused_variables)]
+fn encode_impl<W: Writer, const N: usize>(mut buf: W, data: &[u8]) -> Result<usize>
+where
+ Pixel<N>: SupportedChannels,
+ [u8; N]: Pod,
+{
+ let cap = buf.capacity();
+
+ let mut index = [Pixel::new(); 256];
+ let mut px_prev = Pixel::new().with_a(0xff);
+ let mut hash_prev = px_prev.hash_index();
+ let mut run = 0_u8;
+ let mut px = Pixel::<N>::new().with_a(0xff);
+ let mut index_allowed = false;
+
+ let n_pixels = data.len() / N;
+
+ for (i, chunk) in data.chunks_exact(N).enumerate() {
+ px.read(chunk);
+ if px == px_prev {
+ run += 1;
+ if run == 62 || unlikely(i == n_pixels - 1) {
+ buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
+ run = 0;
+ }
+ } else {
+ if run != 0 {
+ #[cfg(not(feature = "reference"))]
+ {
+ // credits for the original idea: @zakarumych (had to be fixed though)
+ buf = buf.write_one(if run == 1 && index_allowed {
+ QOI_OP_INDEX | hash_prev
+ } else {
+ QOI_OP_RUN | (run - 1)
+ })?;
+ }
+ #[cfg(feature = "reference")]
+ {
+ buf = buf.write_one(QOI_OP_RUN | (run - 1))?;
+ }
+ run = 0;
+ }
+ index_allowed = true;
+ let px_rgba = px.as_rgba(0xff);
+ hash_prev = px_rgba.hash_index();
+ let index_px = &mut index[hash_prev as usize];
+ if *index_px == px_rgba {
+ buf = buf.write_one(QOI_OP_INDEX | hash_prev)?;
+ } else {
+ *index_px = px_rgba;
+ buf = px.encode_into(px_prev, buf)?;
+ }
+ px_prev = px;
+ }
+ }
+
+ buf = buf.write_many(&QOI_PADDING)?;
+ Ok(cap.saturating_sub(buf.capacity()))
+}
+
+#[inline]
+fn encode_impl_all<W: Writer>(out: W, data: &[u8], channels: Channels) -> Result<usize> {
+ match channels {
+ Channels::Rgb => encode_impl::<_, 3>(out, data),
+ Channels::Rgba => encode_impl::<_, 4>(out, data),
+ }
+}
+
+/// The maximum number of bytes the encoded image will take.
+///
+/// Can be used to pre-allocate the buffer to encode the image into.
+#[inline]
+pub fn encode_max_len(width: u32, height: u32, channels: impl Into<u8>) -> usize {
+ let (width, height) = (width as usize, height as usize);
+ let n_pixels = width.saturating_mul(height);
+ QOI_HEADER_SIZE
+ + n_pixels.saturating_mul(channels.into() as usize)
+ + n_pixels
+ + QOI_PADDING_SIZE
+}
+
+/// Encode the image into a pre-allocated buffer.
+///
+/// Returns the total number of bytes written.
+#[inline]
+pub fn encode_to_buf(
+ buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32,
+) -> Result<usize> {
+ Encoder::new(&data, width, height)?.encode_to_buf(buf)
+}
+
+/// Encode the image into a newly allocated vector.
+#[cfg(any(feature = "alloc", feature = "std"))]
+#[inline]
+pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
+ Encoder::new(&data, width, height)?.encode_to_vec()
+}
+
+/// Encode QOI images into buffers or into streams.
+pub struct Encoder<'a> {
+ data: &'a [u8],
+ header: Header,
+}
+
+impl<'a> Encoder<'a> {
+ /// Creates a new encoder from a given array of pixel data and image dimensions.
+ ///
+ /// The number of channels will be inferred automatically (the valid values
+ /// are 3 or 4). The color space will be set to sRGB by default.
+ #[inline]
+ #[allow(clippy::cast_possible_truncation)]
+ pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
+ let data = data.as_ref();
+ let mut header =
+ Header::try_new(width, height, Channels::default(), ColorSpace::default())?;
+ let size = data.len();
+ let n_channels = size / header.n_pixels();
+ if header.n_pixels() * n_channels != size {
+ return Err(Error::InvalidImageLength { size, width, height });
+ }
+ header.channels = Channels::try_from(n_channels.min(0xff) as u8)?;
+ Ok(Self { data, header })
+ }
+
+ /// Returns a new encoder with modified color space.
+ ///
+ /// Note: the color space doesn't affect encoding or decoding in any way, it's
+ /// a purely informative field that's stored in the image header.
+ #[inline]
+ pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
+ self.header = self.header.with_colorspace(colorspace);
+ self
+ }
+
+ /// Returns the inferred number of channels.
+ #[inline]
+ pub const fn channels(&self) -> Channels {
+ self.header.channels
+ }
+
+ /// Returns the header that will be stored in the encoded image.
+ #[inline]
+ pub const fn header(&self) -> &Header {
+ &self.header
+ }
+
+ /// The maximum number of bytes the encoded image will take.
+ ///
+ /// Can be used to pre-allocate the buffer to encode the image into.
+ #[inline]
+ pub fn required_buf_len(&self) -> usize {
+ self.header.encode_max_len()
+ }
+
+ /// Encodes the image to a pre-allocated buffer and returns the number of bytes written.
+ ///
+ /// The minimum size of the buffer can be found via [`Encoder::required_buf_len`].
+ #[inline]
+ pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
+ let buf = buf.as_mut();
+ let size_required = self.required_buf_len();
+ if unlikely(buf.len() < size_required) {
+ return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required });
+ }
+ let (head, tail) = buf.split_at_mut(QOI_HEADER_SIZE); // can't panic
+ head.copy_from_slice(&self.header.encode());
+ let n_written = encode_impl_all(BytesMut::new(tail), self.data, self.header.channels)?;
+ Ok(QOI_HEADER_SIZE + n_written)
+ }
+
+ /// Encodes the image into a newly allocated vector of bytes and returns it.
+ #[cfg(any(feature = "alloc", feature = "std"))]
+ #[inline]
+ pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
+ let mut out = vec![0_u8; self.required_buf_len()];
+ let size = self.encode_to_buf(&mut out)?;
+ out.truncate(size);
+ Ok(out)
+ }
+
+ /// Encodes the image directly to a generic writer that implements [`Write`](std::io::Write).
+ ///
+ /// Note: while it's possible to pass a `&mut [u8]` slice here since it implements `Write`,
+ /// it would more effficient to use a specialized method instead: [`Encoder::encode_to_buf`].
+ #[cfg(feature = "std")]
+ #[inline]
+ pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {
+ writer.write_all(&self.header.encode())?;
+ let n_written =
+ encode_impl_all(GenericWriter::new(writer), self.data, self.header.channels)?;
+ Ok(n_written + QOI_HEADER_SIZE)
+ }
+}