diff options
author | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
---|---|---|
committer | Valentin Popov <valentin@popov.link> | 2024-01-08 00:21:28 +0300 |
commit | 1b6a04ca5504955c571d1c97504fb45ea0befee4 (patch) | |
tree | 7579f518b23313e8a9748a88ab6173d5e030b227 /vendor/qoi/src | |
parent | 5ecd8cf2cba827454317368b68571df0d13d7842 (diff) | |
download | fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.tar.xz fparkan-1b6a04ca5504955c571d1c97504fb45ea0befee4.zip |
Initial vendor packages
Signed-off-by: Valentin Popov <valentin@popov.link>
Diffstat (limited to 'vendor/qoi/src')
-rw-r--r-- | vendor/qoi/src/consts.rs | 17 | ||||
-rw-r--r-- | vendor/qoi/src/decode.rs | 396 | ||||
-rw-r--r-- | vendor/qoi/src/encode.rs | 210 | ||||
-rw-r--r-- | vendor/qoi/src/error.rs | 82 | ||||
-rw-r--r-- | vendor/qoi/src/header.rs | 120 | ||||
-rw-r--r-- | vendor/qoi/src/lib.rs | 95 | ||||
-rw-r--r-- | vendor/qoi/src/pixel.rs | 183 | ||||
-rw-r--r-- | vendor/qoi/src/types.rs | 113 | ||||
-rw-r--r-- | vendor/qoi/src/utils.rs | 107 |
9 files changed, 1323 insertions, 0 deletions
diff --git a/vendor/qoi/src/consts.rs b/vendor/qoi/src/consts.rs new file mode 100644 index 0000000..2281c24 --- /dev/null +++ b/vendor/qoi/src/consts.rs @@ -0,0 +1,17 @@ +pub const QOI_OP_INDEX: u8 = 0x00; // 00xxxxxx +pub const QOI_OP_DIFF: u8 = 0x40; // 01xxxxxx +pub const QOI_OP_LUMA: u8 = 0x80; // 10xxxxxx +pub const QOI_OP_RUN: u8 = 0xc0; // 11xxxxxx +pub const QOI_OP_RGB: u8 = 0xfe; // 11111110 +pub const QOI_OP_RGBA: u8 = 0xff; // 11111111 + +pub const QOI_MASK_2: u8 = 0xc0; // (11)000000 + +pub const QOI_HEADER_SIZE: usize = 14; + +pub const QOI_PADDING: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0x01]; // 7 zeros and one 0x01 marker +pub const QOI_PADDING_SIZE: usize = 8; + +pub const QOI_MAGIC: u32 = u32::from_be_bytes(*b"qoif"); + +pub const QOI_PIXELS_MAX: usize = 400_000_000; diff --git a/vendor/qoi/src/decode.rs b/vendor/qoi/src/decode.rs new file mode 100644 index 0000000..019dac2 --- /dev/null +++ b/vendor/qoi/src/decode.rs @@ -0,0 +1,396 @@ +#[cfg(any(feature = "std", feature = "alloc"))] +use alloc::{vec, vec::Vec}; +#[cfg(feature = "std")] +use std::io::Read; + +// TODO: can be removed once https://github.com/rust-lang/rust/issues/74985 is stable +use bytemuck::{cast_slice_mut, Pod}; + +use crate::consts::{ + QOI_HEADER_SIZE, QOI_OP_DIFF, QOI_OP_INDEX, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA, 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; +use crate::utils::{cold, unlikely}; + +const QOI_OP_INDEX_END: u8 = QOI_OP_INDEX | 0x3f; +const QOI_OP_RUN_END: u8 = QOI_OP_RUN | 0x3d; // <- note, 0x3d (not 0x3f) +const QOI_OP_DIFF_END: u8 = QOI_OP_DIFF | 0x3f; +const QOI_OP_LUMA_END: u8 = QOI_OP_LUMA | 0x3f; + +#[inline] +fn decode_impl_slice<const N: usize, const RGBA: bool>(data: &[u8], out: &mut [u8]) -> Result<usize> +where + Pixel<N>: SupportedChannels, + [u8; N]: Pod, +{ + let mut pixels = cast_slice_mut::<_, [u8; N]>(out); + let data_len = data.len(); + let mut data = data; + + let mut index = [Pixel::<4>::new(); 256]; + let mut px = Pixel::<N>::new().with_a(0xff); + let mut px_rgba: Pixel<4>; + + while let [px_out, ptail @ ..] = pixels { + pixels = ptail; + match data { + [b1 @ QOI_OP_INDEX..=QOI_OP_INDEX_END, dtail @ ..] => { + px_rgba = index[*b1 as usize]; + px.update(px_rgba); + *px_out = px.into(); + data = dtail; + continue; + } + [QOI_OP_RGB, r, g, b, dtail @ ..] => { + px.update_rgb(*r, *g, *b); + data = dtail; + } + [QOI_OP_RGBA, r, g, b, a, dtail @ ..] if RGBA => { + px.update_rgba(*r, *g, *b, *a); + data = dtail; + } + [b1 @ QOI_OP_RUN..=QOI_OP_RUN_END, dtail @ ..] => { + *px_out = px.into(); + let run = ((b1 & 0x3f) as usize).min(pixels.len()); + let (phead, ptail) = pixels.split_at_mut(run); // can't panic + phead.fill(px.into()); + pixels = ptail; + data = dtail; + continue; + } + [b1 @ QOI_OP_DIFF..=QOI_OP_DIFF_END, dtail @ ..] => { + px.update_diff(*b1); + data = dtail; + } + [b1 @ QOI_OP_LUMA..=QOI_OP_LUMA_END, b2, dtail @ ..] => { + px.update_luma(*b1, *b2); + data = dtail; + } + _ => { + cold(); + if unlikely(data.len() < QOI_PADDING_SIZE) { + return Err(Error::UnexpectedBufferEnd); + } + } + } + + px_rgba = px.as_rgba(0xff); + index[px_rgba.hash_index() as usize] = px_rgba; + *px_out = px.into(); + } + + if unlikely(data.len() < QOI_PADDING_SIZE) { + return Err(Error::UnexpectedBufferEnd); + } else if unlikely(data[..QOI_PADDING_SIZE] != QOI_PADDING) { + return Err(Error::InvalidPadding); + } + + Ok(data_len.saturating_sub(data.len()).saturating_sub(QOI_PADDING_SIZE)) +} + +#[inline] +fn decode_impl_slice_all( + data: &[u8], out: &mut [u8], channels: u8, src_channels: u8, +) -> Result<usize> { + match (channels, src_channels) { + (3, 3) => decode_impl_slice::<3, false>(data, out), + (3, 4) => decode_impl_slice::<3, true>(data, out), + (4, 3) => decode_impl_slice::<4, false>(data, out), + (4, 4) => decode_impl_slice::<4, true>(data, out), + _ => { + cold(); + Err(Error::InvalidChannels { channels }) + } + } +} + +/// Decode the image into a pre-allocated buffer. +/// +/// Note: the resulting number of channels will match the header. In order to change +/// the number of channels, use [`Decoder::with_channels`]. +#[inline] +pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> { + let mut decoder = Decoder::new(&data)?; + decoder.decode_to_buf(buf)?; + Ok(*decoder.header()) +} + +/// Decode the image into a newly allocated vector. +/// +/// Note: the resulting number of channels will match the header. In order to change +/// the number of channels, use [`Decoder::with_channels`]. +#[cfg(any(feature = "std", feature = "alloc"))] +#[inline] +pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> { + let mut decoder = Decoder::new(&data)?; + let out = decoder.decode_to_vec()?; + Ok((*decoder.header(), out)) +} + +/// Decode the image header from a slice of bytes. +#[inline] +pub fn decode_header(data: impl AsRef<[u8]>) -> Result<Header> { + Header::decode(data) +} + +#[cfg(any(feature = "std"))] +#[inline] +fn decode_impl_stream<R: Read, const N: usize, const RGBA: bool>( + data: &mut R, out: &mut [u8], +) -> Result<()> +where + Pixel<N>: SupportedChannels, + [u8; N]: Pod, +{ + let mut pixels = cast_slice_mut::<_, [u8; N]>(out); + + let mut index = [Pixel::<N>::new(); 256]; + let mut px = Pixel::<N>::new().with_a(0xff); + + while let [px_out, ptail @ ..] = pixels { + pixels = ptail; + let mut p = [0]; + data.read_exact(&mut p)?; + let [b1] = p; + match b1 { + QOI_OP_INDEX..=QOI_OP_INDEX_END => { + px = index[b1 as usize]; + *px_out = px.into(); + continue; + } + QOI_OP_RGB => { + let mut p = [0; 3]; + data.read_exact(&mut p)?; + px.update_rgb(p[0], p[1], p[2]); + } + QOI_OP_RGBA if RGBA => { + let mut p = [0; 4]; + data.read_exact(&mut p)?; + px.update_rgba(p[0], p[1], p[2], p[3]); + } + QOI_OP_RUN..=QOI_OP_RUN_END => { + *px_out = px.into(); + let run = ((b1 & 0x3f) as usize).min(pixels.len()); + let (phead, ptail) = pixels.split_at_mut(run); // can't panic + phead.fill(px.into()); + pixels = ptail; + continue; + } + QOI_OP_DIFF..=QOI_OP_DIFF_END => { + px.update_diff(b1); + } + QOI_OP_LUMA..=QOI_OP_LUMA_END => { + let mut p = [0]; + data.read_exact(&mut p)?; + let [b2] = p; + px.update_luma(b1, b2); + } + _ => { + cold(); + } + } + + index[px.hash_index() as usize] = px; + *px_out = px.into(); + } + + let mut p = [0_u8; QOI_PADDING_SIZE]; + data.read_exact(&mut p)?; + if unlikely(p != QOI_PADDING) { + return Err(Error::InvalidPadding); + } + + Ok(()) +} + +#[cfg(feature = "std")] +#[inline] +fn decode_impl_stream_all<R: Read>( + data: &mut R, out: &mut [u8], channels: u8, src_channels: u8, +) -> Result<()> { + match (channels, src_channels) { + (3, 3) => decode_impl_stream::<_, 3, false>(data, out), + (3, 4) => decode_impl_stream::<_, 3, true>(data, out), + (4, 3) => decode_impl_stream::<_, 4, false>(data, out), + (4, 4) => decode_impl_stream::<_, 4, true>(data, out), + _ => { + cold(); + Err(Error::InvalidChannels { channels }) + } + } +} + +#[doc(hidden)] +pub trait Reader: Sized { + fn decode_header(&mut self) -> Result<Header>; + fn decode_image(&mut self, out: &mut [u8], channels: u8, src_channels: u8) -> Result<()>; +} + +pub struct Bytes<'a>(&'a [u8]); + +impl<'a> Bytes<'a> { + #[inline] + pub const fn new(buf: &'a [u8]) -> Self { + Self(buf) + } + + #[inline] + pub const fn as_slice(&self) -> &[u8] { + self.0 + } +} + +impl<'a> Reader for Bytes<'a> { + #[inline] + fn decode_header(&mut self) -> Result<Header> { + let header = Header::decode(self.0)?; + self.0 = &self.0[QOI_HEADER_SIZE..]; // can't panic + Ok(header) + } + + #[inline] + fn decode_image(&mut self, out: &mut [u8], channels: u8, src_channels: u8) -> Result<()> { + let n_read = decode_impl_slice_all(self.0, out, channels, src_channels)?; + self.0 = &self.0[n_read..]; + Ok(()) + } +} + +#[cfg(feature = "std")] +impl<R: Read> Reader for R { + #[inline] + fn decode_header(&mut self) -> Result<Header> { + let mut b = [0; QOI_HEADER_SIZE]; + self.read_exact(&mut b)?; + Header::decode(b) + } + + #[inline] + fn decode_image(&mut self, out: &mut [u8], channels: u8, src_channels: u8) -> Result<()> { + decode_impl_stream_all(self, out, channels, src_channels) + } +} + +/// Decode QOI images from slices or from streams. +#[derive(Clone)] +pub struct Decoder<R> { + reader: R, + header: Header, + channels: Channels, +} + +impl<'a> Decoder<Bytes<'a>> { + /// Creates a new decoder from a slice of bytes. + /// + /// The header will be decoded immediately upon construction. + /// + /// Note: this provides the most efficient decoding, but requires the source data to + /// be loaded in memory in order to decode it. In order to decode from a generic + /// stream, use [`Decoder::from_stream`] instead. + #[inline] + pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> { + Self::new_impl(Bytes::new(data.as_ref())) + } + + /// Returns the undecoded tail of the input slice of bytes. + #[inline] + pub const fn data(&self) -> &[u8] { + self.reader.as_slice() + } +} + +#[cfg(feature = "std")] +impl<R: Read> Decoder<R> { + /// Creates a new decoder from a generic reader that implements [`Read`](std::io::Read). + /// + /// The header will be decoded immediately upon construction. + /// + /// Note: while it's possible to pass a `&[u8]` slice here since it implements `Read`, it + /// would be more efficient to use a specialized constructor instead: [`Decoder::new`]. + #[inline] + pub fn from_stream(reader: R) -> Result<Self> { + Self::new_impl(reader) + } + + /// Returns an immutable reference to the underlying reader. + #[inline] + pub const fn reader(&self) -> &R { + &self.reader + } + + /// Consumes the decoder and returns the underlying reader back. + #[inline] + #[allow(clippy::missing_const_for_fn)] + pub fn into_reader(self) -> R { + self.reader + } +} + +impl<R: Reader> Decoder<R> { + #[inline] + fn new_impl(mut reader: R) -> Result<Self> { + let header = reader.decode_header()?; + Ok(Self { reader, header, channels: header.channels }) + } + + /// Returns a new decoder with modified number of channels. + /// + /// By default, the number of channels in the decoded image will be equal + /// to whatever is specified in the header. However, it is also possible + /// to decode RGB into RGBA (in which case the alpha channel will be set + /// to 255), and vice versa (in which case the alpha channel will be ignored). + #[inline] + pub const fn with_channels(mut self, channels: Channels) -> Self { + self.channels = channels; + self + } + + /// Returns the number of channels in the decoded image. + /// + /// Note: this may differ from the number of channels specified in the header. + #[inline] + pub const fn channels(&self) -> Channels { + self.channels + } + + /// Returns the decoded image header. + #[inline] + pub const fn header(&self) -> &Header { + &self.header + } + + /// The number of bytes the decoded image will take. + /// + /// Can be used to pre-allocate the buffer to decode the image into. + #[inline] + pub const fn required_buf_len(&self) -> usize { + self.header.n_pixels().saturating_mul(self.channels.as_u8() as usize) + } + + /// Decodes the image to a pre-allocated buffer and returns the number of bytes written. + /// + /// The minimum size of the buffer can be found via [`Decoder::required_buf_len`]. + #[inline] + pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<usize> { + let buf = buf.as_mut(); + let size = self.required_buf_len(); + if unlikely(buf.len() < size) { + return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size }); + } + self.reader.decode_image(buf, self.channels.as_u8(), self.header.channels.as_u8())?; + Ok(size) + } + + /// Decodes the image into a newly allocated vector of bytes and returns it. + #[cfg(any(feature = "std", feature = "alloc"))] + #[inline] + pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> { + let mut out = vec![0; self.header.n_pixels() * self.channels.as_u8() as usize]; + let _ = self.decode_to_buf(&mut out)?; + Ok(out) + } +} 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) + } +} diff --git a/vendor/qoi/src/error.rs b/vendor/qoi/src/error.rs new file mode 100644 index 0000000..2b90636 --- /dev/null +++ b/vendor/qoi/src/error.rs @@ -0,0 +1,82 @@ +use core::convert::Infallible; +use core::fmt::{self, Display}; + +use crate::consts::QOI_MAGIC; + +/// Errors that can occur during encoding or decoding. +#[derive(Debug)] +pub enum Error { + /// Leading 4 magic bytes don't match when decoding + InvalidMagic { magic: u32 }, + /// Invalid number of channels: expected 3 or 4 + InvalidChannels { channels: u8 }, + /// Invalid color space: expected 0 or 1 + InvalidColorSpace { colorspace: u8 }, + /// Invalid image dimensions: can't be empty or larger than 400Mp + InvalidImageDimensions { width: u32, height: u32 }, + /// Image dimensions are inconsistent with image buffer length + InvalidImageLength { size: usize, width: u32, height: u32 }, + /// Output buffer is too small to fit encoded/decoded image + OutputBufferTooSmall { size: usize, required: usize }, + /// Input buffer ended unexpectedly before decoding was finished + UnexpectedBufferEnd, + /// Invalid stream end marker encountered when decoding + InvalidPadding, + #[cfg(feature = "std")] + /// Generic I/O error from the wrapped reader/writer + IoError(std::io::Error), +} + +/// Alias for [`Result`](std::result::Result) with the error type of [`Error`]. +pub type Result<T> = core::result::Result<T, Error>; + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::InvalidMagic { magic } => { + write!(f, "invalid magic: expected {:?}, got {:?}", QOI_MAGIC, magic.to_be_bytes()) + } + Self::InvalidChannels { channels } => { + write!(f, "invalid number of channels: {}", channels) + } + Self::InvalidColorSpace { colorspace } => { + write!(f, "invalid color space: {} (expected 0 or 1)", colorspace) + } + Self::InvalidImageDimensions { width, height } => { + write!(f, "invalid image dimensions: {}x{}", width, height) + } + Self::InvalidImageLength { size, width, height } => { + write!(f, "invalid image length: {} bytes for {}x{}", size, width, height) + } + Self::OutputBufferTooSmall { size, required } => { + write!(f, "output buffer size too small: {} (required: {})", size, required) + } + Self::UnexpectedBufferEnd => { + write!(f, "unexpected input buffer end while decoding") + } + Self::InvalidPadding => { + write!(f, "invalid padding (stream end marker mismatch)") + } + #[cfg(feature = "std")] + Self::IoError(ref err) => { + write!(f, "i/o error: {}", err) + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} + +impl From<Infallible> for Error { + fn from(_: Infallible) -> Self { + unreachable!() + } +} + +#[cfg(feature = "std")] +impl From<std::io::Error> for Error { + fn from(err: std::io::Error) -> Self { + Self::IoError(err) + } +} diff --git a/vendor/qoi/src/header.rs b/vendor/qoi/src/header.rs new file mode 100644 index 0000000..ccdb1cc --- /dev/null +++ b/vendor/qoi/src/header.rs @@ -0,0 +1,120 @@ +use core::convert::TryInto; + +use bytemuck::cast_slice; + +use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX}; +use crate::encode_max_len; +use crate::error::{Error, Result}; +use crate::types::{Channels, ColorSpace}; +use crate::utils::unlikely; + +/// Image header: dimensions, channels, color space. +/// +/// ### Notes +/// A valid image header must satisfy the following conditions: +/// * Both width and height must be non-zero. +/// * Maximum number of pixels is 400Mp (=4e8 pixels). +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Header { + /// Image width in pixels + pub width: u32, + /// Image height in pixels + pub height: u32, + /// Number of 8-bit channels per pixel + pub channels: Channels, + /// Color space (informative field, doesn't affect encoding) + pub colorspace: ColorSpace, +} + +impl Default for Header { + #[inline] + fn default() -> Self { + Self { + width: 1, + height: 1, + channels: Channels::default(), + colorspace: ColorSpace::default(), + } + } +} + +impl Header { + /// Creates a new header and validates image dimensions. + #[inline] + pub const fn try_new( + width: u32, height: u32, channels: Channels, colorspace: ColorSpace, + ) -> Result<Self> { + let n_pixels = (width as usize).saturating_mul(height as usize); + if unlikely(n_pixels == 0 || n_pixels > QOI_PIXELS_MAX) { + return Err(Error::InvalidImageDimensions { width, height }); + } + Ok(Self { width, height, channels, colorspace }) + } + + /// Creates a new header with modified channels. + #[inline] + pub const fn with_channels(mut self, channels: Channels) -> Self { + self.channels = channels; + self + } + + /// Creates a new header with modified color space. + #[inline] + pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self { + self.colorspace = colorspace; + self + } + + /// Serializes the header into a bytes array. + #[inline] + pub(crate) fn encode(&self) -> [u8; QOI_HEADER_SIZE] { + let mut out = [0; QOI_HEADER_SIZE]; + out[..4].copy_from_slice(&QOI_MAGIC.to_be_bytes()); + out[4..8].copy_from_slice(&self.width.to_be_bytes()); + out[8..12].copy_from_slice(&self.height.to_be_bytes()); + out[12] = self.channels.into(); + out[13] = self.colorspace.into(); + out + } + + /// Deserializes the header from a byte array. + #[inline] + pub(crate) fn decode(data: impl AsRef<[u8]>) -> Result<Self> { + let data = data.as_ref(); + if unlikely(data.len() < QOI_HEADER_SIZE) { + return Err(Error::UnexpectedBufferEnd); + } + let v = cast_slice::<_, [u8; 4]>(&data[..12]); + let magic = u32::from_be_bytes(v[0]); + let width = u32::from_be_bytes(v[1]); + let height = u32::from_be_bytes(v[2]); + let channels = data[12].try_into()?; + let colorspace = data[13].try_into()?; + if unlikely(magic != QOI_MAGIC) { + return Err(Error::InvalidMagic { magic }); + } + Self::try_new(width, height, channels, colorspace) + } + + /// Returns a number of pixels in the image. + #[inline] + pub const fn n_pixels(&self) -> usize { + (self.width as usize).saturating_mul(self.height as usize) + } + + /// Returns the total number of bytes in the raw pixel array. + /// + /// This may come useful when pre-allocating a buffer to decode the image into. + #[inline] + pub const fn n_bytes(&self) -> usize { + self.n_pixels() * self.channels.as_u8() as usize + } + + /// 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(&self) -> usize { + encode_max_len(self.width, self.height, self.channels) + } +} diff --git a/vendor/qoi/src/lib.rs b/vendor/qoi/src/lib.rs new file mode 100644 index 0000000..f77506b --- /dev/null +++ b/vendor/qoi/src/lib.rs @@ -0,0 +1,95 @@ +//! Fast encoder/decoder for [QOI image format](https://qoiformat.org/), implemented in pure and safe Rust. +//! +//! - One of the [fastest](#benchmarks) QOI encoders/decoders out there. +//! - Compliant with the [latest](https://qoiformat.org/qoi-specification.pdf) QOI format specification. +//! - Zero unsafe code. +//! - Supports decoding from / encoding to `std::io` streams directly. +//! - `no_std` support. +//! - Roundtrip-tested vs the reference C implementation; fuzz-tested. +//! +//! ### Examples +//! +//! ```rust +//! use qoi::{encode_to_vec, decode_to_vec}; +//! +//! let encoded = encode_to_vec(&pixels, width, height)?; +//! let (header, decoded) = decode_to_vec(&encoded)?; +//! +//! assert_eq!(header.width, width); +//! assert_eq!(header.height, height); +//! assert_eq!(decoded, pixels); +//! ``` +//! +//! ### Benchmarks +//! +//! ``` +//! decode:Mp/s encode:Mp/s decode:MB/s encode:MB/s +//! qoi.h 282.9 225.3 978.3 778.9 +//! qoi-rust 427.4 290.0 1477.7 1002.9 +//! ``` +//! +//! - Reference C implementation: +//! [phoboslab/qoi@00e34217](https://github.com/phoboslab/qoi/commit/00e34217). +//! - Benchmark timings were collected on an Apple M1 laptop. +//! - 2846 images from the suite provided upstream +//! ([tarball](https://phoboslab.org/files/qoibench/qoi_benchmark_suite.tar)): +//! all pngs except two with broken checksums. +//! - 1.32 GPixels in total with 4.46 GB of raw pixel data. +//! +//! Benchmarks have also been run for all of the other Rust implementations +//! of QOI for comparison purposes and, at the time of writing this document, +//! this library proved to be the fastest one by a noticeable margin. +//! +//! ### Rust version +//! +//! The minimum supported Rust version is 1.51.0 (any changes to this would be +//! considered to be a breaking change). +//! +//! ### `no_std` +//! +//! This crate supports `no_std` mode. By default, std is enabled via the `std` +//! feature. You can deactivate the `default-features` to target core instead. +//! In that case anything related to `std::io`, `std::error::Error` and heap +//! allocations is disabled. There is an additional `alloc` feature that can +//! be activated to bring back the support for heap allocations. + +#![forbid(unsafe_code)] +#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)] +#![allow( + clippy::inline_always, + clippy::similar_names, + clippy::missing_errors_doc, + clippy::must_use_candidate, + clippy::module_name_repetitions, + clippy::cargo_common_metadata, + clippy::doc_markdown, + clippy::return_self_not_must_use, +)] +#![cfg_attr(not(any(feature = "std", test)), no_std)] +#[cfg(all(feature = "alloc", not(any(feature = "std", test))))] +extern crate alloc; +#[cfg(any(feature = "std", test))] +extern crate std as alloc; + +mod decode; +mod encode; +mod error; +mod header; +mod pixel; +mod types; +mod utils; + +#[doc(hidden)] +pub mod consts; + +#[cfg(any(feature = "alloc", feature = "std"))] +pub use crate::decode::decode_to_vec; +pub use crate::decode::{decode_header, decode_to_buf, Decoder}; + +#[cfg(any(feature = "alloc", feature = "std"))] +pub use crate::encode::encode_to_vec; +pub use crate::encode::{encode_max_len, encode_to_buf, Encoder}; + +pub use crate::error::{Error, Result}; +pub use crate::header::Header; +pub use crate::types::{Channels, ColorSpace}; diff --git a/vendor/qoi/src/pixel.rs b/vendor/qoi/src/pixel.rs new file mode 100644 index 0000000..d103494 --- /dev/null +++ b/vendor/qoi/src/pixel.rs @@ -0,0 +1,183 @@ +use crate::consts::{QOI_OP_DIFF, QOI_OP_LUMA, QOI_OP_RGB, QOI_OP_RGBA}; +use crate::error::Result; +use crate::utils::Writer; +use bytemuck::{cast, Pod}; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(transparent)] +pub struct Pixel<const N: usize>([u8; N]); + +impl<const N: usize> Pixel<N> { + #[inline] + pub const fn new() -> Self { + Self([0; N]) + } + + #[inline] + pub fn read(&mut self, s: &[u8]) { + if s.len() == N { + let mut i = 0; + while i < N { + self.0[i] = s[i]; + i += 1; + } + } else { + unreachable!(); + } + } + + #[inline] + pub fn update<const M: usize>(&mut self, px: Pixel<M>) { + let mut i = 0; + while i < M && i < N { + self.0[i] = px.0[i]; + i += 1; + } + } + + #[inline] + pub fn update_rgb(&mut self, r: u8, g: u8, b: u8) { + self.0[0] = r; + self.0[1] = g; + self.0[2] = b; + } + + #[inline] + pub fn update_rgba(&mut self, r: u8, g: u8, b: u8, a: u8) { + self.0[0] = r; + self.0[1] = g; + self.0[2] = b; + if N >= 4 { + self.0[3] = a; + } + } + + #[inline] + pub fn update_diff(&mut self, b1: u8) { + self.0[0] = self.0[0].wrapping_add((b1 >> 4) & 0x03).wrapping_sub(2); + self.0[1] = self.0[1].wrapping_add((b1 >> 2) & 0x03).wrapping_sub(2); + self.0[2] = self.0[2].wrapping_add(b1 & 0x03).wrapping_sub(2); + } + + #[inline] + pub fn update_luma(&mut self, b1: u8, b2: u8) { + let vg = (b1 & 0x3f).wrapping_sub(32); + let vg_8 = vg.wrapping_sub(8); + let vr = vg_8.wrapping_add((b2 >> 4) & 0x0f); + let vb = vg_8.wrapping_add(b2 & 0x0f); + self.0[0] = self.0[0].wrapping_add(vr); + self.0[1] = self.0[1].wrapping_add(vg); + self.0[2] = self.0[2].wrapping_add(vb); + } + + #[inline] + pub const fn as_rgba(self, with_a: u8) -> Pixel<4> { + let mut i = 0; + let mut out = Pixel::new(); + while i < N { + out.0[i] = self.0[i]; + i += 1; + } + if N < 4 { + out.0[3] = with_a; + } + out + } + + #[inline] + pub const fn r(self) -> u8 { + self.0[0] + } + + #[inline] + pub const fn g(self) -> u8 { + self.0[1] + } + + #[inline] + pub const fn b(self) -> u8 { + self.0[2] + } + + #[inline] + pub const fn with_a(mut self, value: u8) -> Self { + if N >= 4 { + self.0[3] = value; + } + self + } + + #[inline] + pub const fn a_or(self, value: u8) -> u8 { + if N < 4 { + value + } else { + self.0[3] + } + } + + #[inline] + #[allow(clippy::cast_lossless, clippy::cast_possible_truncation)] + pub fn hash_index(self) -> u8 + where + [u8; N]: Pod, + { + // credits for the initial idea: @zakarumych + let v = if N == 4 { + u32::from_ne_bytes(cast(self.0)) + } else { + u32::from_ne_bytes([self.0[0], self.0[1], self.0[2], 0xff]) + } as u64; + let s = ((v & 0xff00_ff00) << 32) | (v & 0x00ff_00ff); + s.wrapping_mul(0x0300_0700_0005_000b_u64).to_le().swap_bytes() as u8 & 63 + } + + #[inline] + pub fn rgb_add(&mut self, r: u8, g: u8, b: u8) { + self.0[0] = self.0[0].wrapping_add(r); + self.0[1] = self.0[1].wrapping_add(g); + self.0[2] = self.0[2].wrapping_add(b); + } + + #[inline] + pub fn encode_into<W: Writer>(&self, px_prev: Self, buf: W) -> Result<W> { + if N == 3 || self.a_or(0) == px_prev.a_or(0) { + let vg = self.g().wrapping_sub(px_prev.g()); + let vg_32 = vg.wrapping_add(32); + if vg_32 | 63 == 63 { + let vr = self.r().wrapping_sub(px_prev.r()); + let vb = self.b().wrapping_sub(px_prev.b()); + let vg_r = vr.wrapping_sub(vg); + let vg_b = vb.wrapping_sub(vg); + let (vr_2, vg_2, vb_2) = + (vr.wrapping_add(2), vg.wrapping_add(2), vb.wrapping_add(2)); + if vr_2 | vg_2 | vb_2 | 3 == 3 { + buf.write_one(QOI_OP_DIFF | vr_2 << 4 | vg_2 << 2 | vb_2) + } else { + let (vg_r_8, vg_b_8) = (vg_r.wrapping_add(8), vg_b.wrapping_add(8)); + if vg_r_8 | vg_b_8 | 15 == 15 { + buf.write_many(&[QOI_OP_LUMA | vg_32, vg_r_8 << 4 | vg_b_8]) + } else { + buf.write_many(&[QOI_OP_RGB, self.r(), self.g(), self.b()]) + } + } + } else { + buf.write_many(&[QOI_OP_RGB, self.r(), self.g(), self.b()]) + } + } else { + buf.write_many(&[QOI_OP_RGBA, self.r(), self.g(), self.b(), self.a_or(0xff)]) + } + } +} + +impl<const N: usize> From<Pixel<N>> for [u8; N] { + #[inline(always)] + fn from(px: Pixel<N>) -> Self { + px.0 + } +} + +pub trait SupportedChannels {} + +impl SupportedChannels for Pixel<3> {} +impl SupportedChannels for Pixel<4> {} diff --git a/vendor/qoi/src/types.rs b/vendor/qoi/src/types.rs new file mode 100644 index 0000000..81229d4 --- /dev/null +++ b/vendor/qoi/src/types.rs @@ -0,0 +1,113 @@ +use core::convert::TryFrom; + +use crate::error::{Error, Result}; +use crate::utils::unlikely; + +/// Image color space. +/// +/// Note: the color space is purely informative. Although it is saved to the +/// file header, it does not affect encoding/decoding in any way. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] +#[repr(u8)] +pub enum ColorSpace { + /// sRGB with linear alpha + Srgb = 0, + /// All channels are linear + Linear = 1, +} + +impl ColorSpace { + /// Returns true if the color space is sRGB with linear alpha. + pub const fn is_srgb(self) -> bool { + matches!(self, Self::Srgb) + } + + /// Returns true is all channels are linear. + pub const fn is_linear(self) -> bool { + matches!(self, Self::Linear) + } + + /// Converts to an integer (0 if sRGB, 1 if all linear). + pub const fn as_u8(self) -> u8 { + self as u8 + } +} + +impl Default for ColorSpace { + fn default() -> Self { + Self::Srgb + } +} + +impl From<ColorSpace> for u8 { + #[inline] + fn from(colorspace: ColorSpace) -> Self { + colorspace as Self + } +} + +impl TryFrom<u8> for ColorSpace { + type Error = Error; + + #[inline] + fn try_from(colorspace: u8) -> Result<Self> { + if unlikely(colorspace | 1 != 1) { + Err(Error::InvalidColorSpace { colorspace }) + } else { + Ok(if colorspace == 0 { Self::Srgb } else { Self::Linear }) + } + } +} + +/// Number of 8-bit channels in a pixel. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] +#[repr(u8)] +pub enum Channels { + /// Three 8-bit channels (RGB) + Rgb = 3, + /// Four 8-bit channels (RGBA) + Rgba = 4, +} + +impl Channels { + /// Returns true if there are 3 channels (RGB). + pub const fn is_rgb(self) -> bool { + matches!(self, Self::Rgb) + } + + /// Returns true if there are 4 channels (RGBA). + pub const fn is_rgba(self) -> bool { + matches!(self, Self::Rgba) + } + + /// Converts to an integer (3 if RGB, 4 if RGBA). + pub const fn as_u8(self) -> u8 { + self as u8 + } +} + +impl Default for Channels { + fn default() -> Self { + Self::Rgb + } +} + +impl From<Channels> for u8 { + #[inline] + fn from(channels: Channels) -> Self { + channels as Self + } +} + +impl TryFrom<u8> for Channels { + type Error = Error; + + #[inline] + fn try_from(channels: u8) -> Result<Self> { + if unlikely(channels != 3 && channels != 4) { + Err(Error::InvalidChannels { channels }) + } else { + Ok(if channels == 3 { Self::Rgb } else { Self::Rgba }) + } + } +} diff --git a/vendor/qoi/src/utils.rs b/vendor/qoi/src/utils.rs new file mode 100644 index 0000000..d0c37a6 --- /dev/null +++ b/vendor/qoi/src/utils.rs @@ -0,0 +1,107 @@ +#[cfg(feature = "std")] +use std::io::Write; + +use crate::error::Result; + +#[inline(always)] +#[cold] +pub const fn cold() {} + +#[inline(always)] +#[allow(unused)] +pub const fn likely(b: bool) -> bool { + if !b { + cold(); + } + b +} + +#[inline(always)] +pub const fn unlikely(b: bool) -> bool { + if b { + cold(); + } + b +} + +pub trait Writer: Sized { + fn write_one(self, v: u8) -> Result<Self>; + fn write_many(self, v: &[u8]) -> Result<Self>; + fn capacity(&self) -> usize; +} + +pub struct BytesMut<'a>(&'a mut [u8]); + +impl<'a> BytesMut<'a> { + pub fn new(buf: &'a mut [u8]) -> Self { + Self(buf) + } + + #[inline] + pub fn write_one(self, v: u8) -> Self { + if let Some((first, tail)) = self.0.split_first_mut() { + *first = v; + Self(tail) + } else { + unreachable!() + } + } + + #[inline] + pub fn write_many(self, v: &[u8]) -> Self { + if v.len() <= self.0.len() { + let (head, tail) = self.0.split_at_mut(v.len()); + head.copy_from_slice(v); + Self(tail) + } else { + unreachable!() + } + } +} + +impl<'a> Writer for BytesMut<'a> { + #[inline] + fn write_one(self, v: u8) -> Result<Self> { + Ok(BytesMut::write_one(self, v)) + } + + #[inline] + fn write_many(self, v: &[u8]) -> Result<Self> { + Ok(BytesMut::write_many(self, v)) + } + + #[inline] + fn capacity(&self) -> usize { + self.0.len() + } +} + +#[cfg(feature = "std")] +pub struct GenericWriter<W> { + writer: W, + n_written: usize, +} + +#[cfg(feature = "std")] +impl<W: Write> GenericWriter<W> { + pub const fn new(writer: W) -> Self { + Self { writer, n_written: 0 } + } +} + +#[cfg(feature = "std")] +impl<W: Write> Writer for GenericWriter<W> { + fn write_one(mut self, v: u8) -> Result<Self> { + self.n_written += 1; + self.writer.write_all(&[v]).map(|_| self).map_err(Into::into) + } + + fn write_many(mut self, v: &[u8]) -> Result<Self> { + self.n_written += v.len(); + self.writer.write_all(v).map(|_| self).map_err(Into::into) + } + + fn capacity(&self) -> usize { + usize::MAX - self.n_written + } +} |