aboutsummaryrefslogtreecommitdiff
path: root/vendor/qoi/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/qoi/src')
-rw-r--r--vendor/qoi/src/consts.rs17
-rw-r--r--vendor/qoi/src/decode.rs396
-rw-r--r--vendor/qoi/src/encode.rs210
-rw-r--r--vendor/qoi/src/error.rs82
-rw-r--r--vendor/qoi/src/header.rs120
-rw-r--r--vendor/qoi/src/lib.rs95
-rw-r--r--vendor/qoi/src/pixel.rs183
-rw-r--r--vendor/qoi/src/types.rs113
-rw-r--r--vendor/qoi/src/utils.rs107
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
+ }
+}