Tons of docstrings + some renames

This commit is contained in:
Ivan Smirnov 2022-01-04 02:57:42 +03:00
parent 86d5462958
commit f3120d5df3
6 changed files with 96 additions and 15 deletions

View file

@ -1,7 +1,7 @@
[package] [package]
name = "qoi-fast" name = "qoi-fast"
version = "0.2.0" 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 <rust@ivan.smirnov.ie>"] authors = ["Ivan Smirnov <rust@ivan.smirnov.ie>"]
edition = "2018" edition = "2018"
readme = "README.md" readme = "README.md"

View file

@ -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] #[inline]
pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> { pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<Header> {
let mut decoder = Decoder::new(&data)?; let mut decoder = Decoder::new(&data)?;
@ -113,6 +117,10 @@ pub fn decode_to_buf(buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>) -> Result<He
Ok(*decoder.header()) 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"))] #[cfg(any(feature = "std", feature = "alloc"))]
#[inline] #[inline]
pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> { pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> {
@ -121,6 +129,7 @@ pub fn decode_to_vec(data: impl AsRef<[u8]>) -> Result<(Header, Vec<u8>)> {
Ok((*decoder.header(), out)) Ok((*decoder.header(), out))
} }
/// Decode the image header from a slice of bytes.
#[inline] #[inline]
pub fn decode_header(data: impl AsRef<[u8]>) -> Result<Header> { pub fn decode_header(data: impl AsRef<[u8]>) -> Result<Header> {
Header::decode(data) Header::decode(data)
@ -264,6 +273,7 @@ impl<R: Read> Reader for R {
} }
} }
/// Decode QOI images from slices or from streams.
#[derive(Clone)] #[derive(Clone)]
pub struct Decoder<R> { pub struct Decoder<R> {
reader: R, reader: R,
@ -272,11 +282,19 @@ pub struct Decoder<R> {
} }
impl<'a> Decoder<Bytes<'a>> { 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] #[inline]
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> { pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized)) -> Result<Self> {
Self::new_impl(Bytes::new(data.as_ref())) Self::new_impl(Bytes::new(data.as_ref()))
} }
/// Returns the undecoded tail of the input slice of bytes.
#[inline] #[inline]
pub const fn data(&self) -> &[u8] { pub const fn data(&self) -> &[u8] {
self.reader.as_slice() self.reader.as_slice()
@ -285,16 +303,24 @@ impl<'a> Decoder<Bytes<'a>> {
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<R: Read> Decoder<R> { 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] #[inline]
pub fn from_stream(reader: R) -> Result<Self> { pub fn from_stream(reader: R) -> Result<Self> {
Self::new_impl(reader) Self::new_impl(reader)
} }
/// Returns an immutable reference to the underlying reader.
#[inline] #[inline]
pub fn reader(&self) -> &R { pub fn reader(&self) -> &R {
&self.reader &self.reader
} }
/// Consumes the decoder and returns the underlying reader back.
#[inline] #[inline]
pub fn into_reader(self) -> R { pub fn into_reader(self) -> R {
self.reader self.reader
@ -308,25 +334,47 @@ impl<R: Reader> Decoder<R> {
Ok(Self { reader, header, channels: header.channels }) 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 { pub fn with_channels(mut self, channels: Channels) -> Self {
self.channels = channels; self.channels = channels;
self self
} }
/// Returns the number of channels in the decoded image.
///
/// Note: this may differ from the number of channels specified in the header.
#[inline] #[inline]
pub fn channels(&self) -> Channels { pub fn channels(&self) -> Channels {
self.channels self.channels
} }
/// Returns the decoded image header.
#[inline] #[inline]
pub fn header(&self) -> &Header { pub fn header(&self) -> &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] #[inline]
pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<usize> { pub fn decode_to_buf(&mut self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
let buf = buf.as_mut(); 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) { if unlikely(buf.len() < size) {
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size }); return Err(Error::OutputBufferTooSmall { size: buf.len(), required: size });
} }
@ -334,6 +382,7 @@ impl<R: Reader> Decoder<R> {
Ok(size) Ok(size)
} }
/// Decodes the image into a newly allocated vector of bytes and returns it.
#[cfg(any(feature = "std", feature = "alloc"))] #[cfg(any(feature = "std", feature = "alloc"))]
#[inline] #[inline]
pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> { pub fn decode_to_vec(&mut self) -> Result<Vec<u8>> {

View file

@ -43,6 +43,7 @@ where
if run != 0 { if run != 0 {
#[cfg(not(feature = "reference"))] #[cfg(not(feature = "reference"))]
{ {
// credits for the original idea: @zakarumych
buf = buf.write_one(if run == 1 && i != 1 { buf = buf.write_one(if run == 1 && i != 1 {
QOI_OP_INDEX | (hash_prev as u8) QOI_OP_INDEX | (hash_prev as u8)
} else { } else {
@ -80,8 +81,11 @@ fn encode_impl_all<W: Writer>(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] #[inline]
pub fn encode_size_limit(width: u32, height: u32, channels: impl Into<u8>) -> usize { pub fn encode_max_len(width: u32, height: u32, channels: impl Into<u8>) -> usize {
let (width, height) = (width as usize, height as usize); let (width, height) = (width as usize, height as usize);
let n_pixels = width.saturating_mul(height); let n_pixels = width.saturating_mul(height);
QOI_HEADER_SIZE QOI_HEADER_SIZE
@ -90,6 +94,9 @@ pub fn encode_size_limit(width: u32, height: u32, channels: impl Into<u8>) -> us
+ QOI_PADDING_SIZE + QOI_PADDING_SIZE
} }
/// Encode the image into a pre-allocated buffer.
///
/// Returns the total number of bytes written.
#[inline] #[inline]
pub fn encode_to_buf( pub fn encode_to_buf(
buf: impl AsMut<[u8]>, data: impl AsRef<[u8]>, width: u32, height: u32, 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) Encoder::new(&data, width, height)?.encode_to_buf(buf)
} }
/// Encode the image into a newly allocated vector.
#[cfg(any(feature = "alloc", feature = "std"))] #[cfg(any(feature = "alloc", feature = "std"))]
#[inline] #[inline]
pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> { pub fn encode_to_vec(data: impl AsRef<[u8]>, width: u32, height: u32) -> Result<Vec<u8>> {
Encoder::new(&data, width, height)?.encode_to_vec() Encoder::new(&data, width, height)?.encode_to_vec()
} }
/// Encode QOI images into buffers or into streams.
pub struct Encoder<'a> { pub struct Encoder<'a> {
data: &'a [u8], data: &'a [u8],
header: Header, header: Header,
} }
impl<'a> Encoder<'a> { 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] #[inline]
#[allow(clippy::cast_possible_truncation)] #[allow(clippy::cast_possible_truncation)]
pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> { pub fn new(data: &'a (impl AsRef<[u8]> + ?Sized), width: u32, height: u32) -> Result<Self> {
@ -124,31 +137,43 @@ impl<'a> Encoder<'a> {
Ok(Self { data, header }) 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] #[inline]
pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self { pub const fn with_colorspace(mut self, colorspace: ColorSpace) -> Self {
self.header = self.header.with_colorspace(colorspace); self.header = self.header.with_colorspace(colorspace);
self self
} }
/// Returns the inferred number of channels.
#[inline] #[inline]
pub const fn channels(&self) -> Channels { pub const fn channels(&self) -> Channels {
self.header.channels self.header.channels
} }
/// Returns the header that will be stored in the encoded image.
#[inline] #[inline]
pub const fn header(&self) -> &Header { pub const fn header(&self) -> &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] #[inline]
pub fn encode_size_limit(&self) -> usize { pub fn required_buf_len(&self) -> usize {
self.header.encode_size_limit() 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] #[inline]
pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> { pub fn encode_to_buf(&self, mut buf: impl AsMut<[u8]>) -> Result<usize> {
let buf = buf.as_mut(); 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) { if unlikely(buf.len() < size_required) {
return Err(Error::OutputBufferTooSmall { size: buf.len(), required: 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) 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"))] #[cfg(any(feature = "alloc", feature = "std"))]
#[inline] #[inline]
pub fn encode_to_vec(&self) -> Result<Vec<u8>> { pub fn encode_to_vec(&self) -> Result<Vec<u8>> {
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)?; let size = self.encode_to_buf(&mut out)?;
out.truncate(size); out.truncate(size);
Ok(out) 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")] #[cfg(feature = "std")]
#[inline] #[inline]
pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> { pub fn encode_to_stream<W: Write>(&self, writer: &mut W) -> Result<usize> {

View file

@ -27,7 +27,7 @@ pub enum Error {
IoError(std::io::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<T> = core::result::Result<T, Error>; pub type Result<T> = core::result::Result<T, Error>;
impl Display for Error { impl Display for Error {

View file

@ -3,7 +3,7 @@ use core::convert::TryInto;
use bytemuck::cast_slice; use bytemuck::cast_slice;
use crate::consts::{QOI_HEADER_SIZE, QOI_MAGIC, QOI_PIXELS_MAX}; 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::error::{Error, Result};
use crate::types::{Channels, ColorSpace}; use crate::types::{Channels, ColorSpace};
use crate::utils::unlikely; use crate::utils::unlikely;
@ -102,17 +102,19 @@ impl Header {
(self.width as usize).saturating_mul(self.height as usize) (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] #[inline]
pub const fn n_bytes(&self) -> usize { pub const fn n_bytes(&self) -> usize {
self.n_pixels() * self.channels.as_u8() as 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] #[inline]
pub fn encode_size_limit(&self) -> usize { pub fn encode_max_len(&self) -> usize {
encode_size_limit(self.width, self.height, self.channels) encode_max_len(self.width, self.height, self.channels)
} }
} }

View file

@ -31,7 +31,7 @@ pub use crate::decode::{decode_header, decode_to_buf, Decoder};
#[cfg(any(feature = "alloc", feature = "std"))] #[cfg(any(feature = "alloc", feature = "std"))]
pub use crate::encode::encode_to_vec; 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::error::{Error, Result};
pub use crate::header::Header; pub use crate::header::Header;