diff --git a/Cargo.toml b/Cargo.toml index eac840f..6a5ebe3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "qoi-fast" version = "0.2.0" -description = "VERY fast encoder/decoder for QOI (Quite Okay Image) format" +description = "Fast encoder/decoder for QOI (Quite Okay Image) format" authors = ["Ivan Smirnov "] edition = "2018" readme = "README.md" diff --git a/src/decode.rs b/src/decode.rs index 0f4e660..7d062b2 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -106,6 +106,10 @@ fn decode_impl_slice_all( } } +/// 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
{ let mut decoder = Decoder::new(&data)?; @@ -113,6 +117,10 @@ pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result) -> Result<(Header, Vec)> { @@ -121,6 +129,7 @@ pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, 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::decode(data) @@ -264,6 +273,7 @@ impl Reader for R { } } +/// Decode QOI images from slices or from streams. #[derive(Clone)] pub struct Decoder { reader: R, @@ -272,11 +282,19 @@ pub struct Decoder { } impl<'a> Decoder> { + /// 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::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() @@ -285,16 +303,24 @@ impl<'a> Decoder> { #[cfg(feature = "std")] impl Decoder { + /// 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::new_impl(reader) } + /// Returns an immutable reference to the underlying reader. #[inline] pub fn reader(&self) -> &R { &self.reader } + /// Consumes the decoder and returns the underlying reader back. #[inline] pub fn into_reader(self) -> R { self.reader @@ -308,25 +334,47 @@ impl Decoder { 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 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 fn channels(&self) -> Channels { self.channels } + /// Returns the decoded image header. #[inline] pub 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 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 { let buf = buf.as_mut(); - let size = self.header.n_pixels() * self.channels.as_u8() as usize; + let size = self.required_buf_len(); if unlikely(buf.len() < size) { return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size }); } @@ -334,6 +382,7 @@ impl Decoder { 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> { diff --git a/src/encode.rs b/src/encode.rs index 1e80cc0..ec66b74 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -43,6 +43,7 @@ where if run != 0 { #[cfg(not(feature = "reference"))] { + // credits for the original idea: @zakarumych buf = buf.write_one(if run == 1 && i != 1 { QOI_OP_INDEX | (hash_prev as u8) } else { @@ -80,8 +81,11 @@ fn encode_impl_all(out: W, data: &[u8], channels: Channels) -> Result } } +/// 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_size_limit(width: u32, height: u32, channels: impl Into) -> usize { +pub fn encode_max_len(width: u32, height: u32, channels: impl Into) -> usize { let (width, height) = (width as usize, height as usize); let n_pixels = width.saturating_mul(height); QOI_HEADER_SIZE @@ -90,6 +94,9 @@ pub fn encode_size_limit(width: u32, height: u32, channels: impl Into) -> us + 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, @@ -97,18 +104,24 @@ pub fn encode_to_buf( 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> { 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 { @@ -124,31 +137,43 @@ impl<'a> Encoder<'a> { 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 encode_size_limit(&self) -> usize { - self.header.encode_size_limit() + 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 { let buf = buf.as_mut(); - let size_required = self.encode_size_limit(); + let size_required = self.required_buf_len(); if unlikely(buf.len() < size_required) { return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size_required }); } @@ -158,15 +183,20 @@ impl<'a> Encoder<'a> { 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> { - let mut out = vec![0_u8; self.encode_size_limit()]; + 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(&self, writer: &mut W) -> Result { diff --git a/src/error.rs b/src/error.rs index 6fe6e33..2b90636 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum Error { IoError(std::io::Error), } -/// Alias for `Result` with the error type `qoi_fast::Error`. +/// Alias for [`Result`](std::result::Result) with the error type of [`Error`]. pub type Result = core::result::Result; impl Display for Error { diff --git a/src/header.rs b/src/header.rs index 6295805..ccdb1cc 100644 --- a/src/header.rs +++ b/src/header.rs @@ -3,7 +3,7 @@ use core::convert::TryInto; use bytemuck::cast_slice; use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX}; -use crate::encode_size_limit; +use crate::encode_max_len; use crate::error::{Error, Result}; use crate::types::{Channels, ColorSpace}; use crate::utils::unlikely; @@ -102,17 +102,19 @@ impl Header { (self.width as usize).saturating_mul(self.height as usize) } - /// Returns the total number of bytes in the image. + /// 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 } - /// Returns the maximum number of bytes the image can possibly occupy when QOI-encoded. + /// The maximum number of bytes the encoded image will take. /// - /// This comes useful when pre-allocating a buffer to encode the image into. + /// Can be used to pre-allocate the buffer to encode the image into. #[inline] - pub fn encode_size_limit(&self) -> usize { - encode_size_limit(self.width, self.height, self.channels) + pub fn encode_max_len(&self) -> usize { + encode_max_len(self.width, self.height, self.channels) } } diff --git a/src/lib.rs b/src/lib.rs index 91d8c55..9b23f0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ 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_size_limit, encode_to_buf, Encoder}; +pub use crate::encode::{encode_max_len, encode_to_buf, Encoder}; pub use crate::error::{Error, Result}; pub use crate::header::Header;