Implement support for Qoi format

This commit is contained in:
Alex K 2022-08-31 17:56:40 -06:00
parent a1ce569afd
commit bbd53eb960
6 changed files with 125 additions and 2 deletions

View file

@ -46,6 +46,7 @@ dav1d = { version = "0.6.0", optional = true }
dcv-color-primitives = { version = "0.4.0", optional = true }
color_quant = "1.1"
exr = { version = "1.5.0", optional = true }
qoi = { version = "0.4", optional = true }
[dev-dependencies]
crc32fast = "1.2.0"
@ -59,7 +60,7 @@ jpeg = { package = "jpeg-decoder", version = "0.2.1", default-features = false,
[features]
# TODO: Add "avif" to this list while preparing for 0.24.0
default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr"]
default = ["gif", "jpeg", "ico", "png", "pnm", "tga", "tiff", "webp", "bmp", "hdr", "dxt", "dds", "farbfeld", "jpeg_rayon", "openexr", "qoi"]
ico = ["bmp", "png"]
pnm = []

96
src/codecs/qoi.rs Normal file
View file

@ -0,0 +1,96 @@
use crate::{
error::{DecodingError, EncodingError},
ColorType, ImageDecoder, ImageEncoder, ImageError, ImageFormat, ImageResult,
};
use std::io::{Cursor, Read, Write};
/// QOI decoder
pub struct QoiDecoder<R> {
decoder: qoi::Decoder<R>,
}
impl<R> QoiDecoder<R>
where
R: Read,
{
/// Creates a new decoder that decodes from the stream ```reader```
pub fn new(reader: R) -> ImageResult<Self> {
let decoder = qoi::Decoder::from_stream(reader).map_err(decoding_error)?;
Ok(Self { decoder })
}
}
impl<'a, R: Read + 'a> ImageDecoder<'a> for QoiDecoder<R> {
type Reader = Cursor<Vec<u8>>;
fn dimensions(&self) -> (u32, u32) {
(self.decoder.header().width, self.decoder.header().height)
}
fn color_type(&self) -> ColorType {
match self.decoder.header().channels {
qoi::Channels::Rgb => ColorType::Rgb8,
qoi::Channels::Rgba => ColorType::Rgba8,
}
}
fn into_reader(mut self) -> ImageResult<Self::Reader> {
let buffer = self.decoder.decode_to_vec().map_err(decoding_error)?;
Ok(Cursor::new(buffer))
}
}
fn decoding_error(error: qoi::Error) -> ImageError {
ImageError::Decoding(DecodingError::new(ImageFormat::Qoi.into(), error))
}
/// QOI encoder
pub struct QoiEncoder<W> {
writer: W,
}
impl<W: Write> QoiEncoder<W> {
/// Creates a new encoder that writes its output to ```writer```
pub fn new(writer: W) -> Self {
Self { writer }
}
}
impl<W: Write> ImageEncoder for QoiEncoder<W> {
fn write_image(
mut self,
buf: &[u8],
width: u32,
height: u32,
_color_type: ColorType,
) -> ImageResult<()> {
// Encode data in QOI
let data = qoi::encode_to_vec(&buf, width, height)
.map_err(|e| ImageError::Encoding(EncodingError::new(ImageFormat::Qoi.into(), e)))?;
// Write data to buffer
{
self.writer.write_all(&data[..])?;
self.writer.flush()?;
Ok(())
}
.map_err(ImageError::IoError)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
#[test]
fn decode_test_image() {
let decoder = QoiDecoder::new(File::open("tests/images/qoi/basic-test.qoi").unwrap())
.expect("Unable to read QOI file");
assert_eq!((5, 5), decoder.dimensions());
assert_eq!(ColorType::Rgba8, decoder.color_type());
}
}

View file

@ -67,6 +67,9 @@ pub enum ImageFormat {
/// An Image in AVIF format.
Avif,
/// An Image in QOI format.
Qoi,
}
impl ImageFormat {
@ -104,6 +107,7 @@ impl ImageFormat {
"exr" => ImageFormat::OpenExr,
"pbm" | "pam" | "ppm" | "pgm" => ImageFormat::Pnm,
"ff" | "farbfeld" => ImageFormat::Farbfeld,
"qoi" => ImageFormat::Qoi,
_ => return None,
})
}
@ -176,6 +180,9 @@ impl ImageFormat {
| "image/x-portable-graymap"
| "image/x-portable-pixmap"
| "image/x-portable-anymap" => Some(ImageFormat::Pnm),
// Qoi's MIME type is being worked on.
// See: https://github.com/phoboslab/qoi/issues/167
"image/x-qoi" => Some(ImageFormat::Qoi),
_ => None,
}
}
@ -199,6 +206,7 @@ impl ImageFormat {
ImageFormat::Pnm => true,
ImageFormat::Farbfeld => true,
ImageFormat::Avif => true,
ImageFormat::Qoi => true,
}
}
@ -221,6 +229,7 @@ impl ImageFormat {
ImageFormat::Hdr => false,
ImageFormat::OpenExr => true,
ImageFormat::Dds => false,
ImageFormat::Qoi => true,
}
}
@ -250,6 +259,7 @@ impl ImageFormat {
ImageFormat::Farbfeld => &["ff"],
// According to: https://aomediacodec.github.io/av1-avif/#mime-registration
ImageFormat::Avif => &["avif"],
ImageFormat::Qoi => &["qoi"],
}
}
}
@ -302,6 +312,10 @@ pub enum ImageOutputFormat {
/// An image in AVIF Format
Avif,
#[cfg(feature = "qoi")]
/// An image in QOI Format
Qoi,
/// A value for signalling an error: An unsupported format was requested
// Note: When TryFrom is stabilized, this value should not be needed, and
// a TryInto<ImageOutputFormat> should be used instead of an Into<ImageOutputFormat>.
@ -335,6 +349,9 @@ impl From<ImageFormat> for ImageOutputFormat {
#[cfg(feature = "avif-encoder")]
ImageFormat::Avif => ImageOutputFormat::Avif,
#[cfg(feature = "qoi")]
ImageFormat::Qoi => ImageOutputFormat::Qoi,
f => ImageOutputFormat::Unsupported(format!("{:?}", f)),
}
}

View file

@ -78,6 +78,8 @@ pub(crate) fn load_decoder<R: BufRead + Seek, V: DecoderVisitor>(
image::ImageFormat::Pnm => visitor.visit_decoder(pnm::PnmDecoder::new(r)?),
#[cfg(feature = "farbfeld")]
image::ImageFormat::Farbfeld => visitor.visit_decoder(farbfeld::FarbfeldDecoder::new(r)?),
#[cfg(feature = "qoi")]
image::ImageFormat::Qoi => visitor.visit_decoder(qoi::QoiDecoder::new(r)?),
_ => Err(ImageError::Unsupported(
ImageFormatHint::Exact(format).into(),
)),
@ -242,6 +244,10 @@ pub(crate) fn write_buffer_impl<W: std::io::Write + Seek>(
ImageOutputFormat::Avif => {
avif::AvifEncoder::new(buffered_write).write_image(buf, width, height, color)
}
#[cfg(feature = "qoi")]
ImageOutputFormat::Qoi => {
qoi::QoiEncoder::new(buffered_write).write_image(buf, width, height, color)
}
image::ImageOutputFormat::Unsupported(msg) => Err(ImageError::Unsupported(
UnsupportedError::from_format_and_kind(
@ -252,7 +258,7 @@ pub(crate) fn write_buffer_impl<W: std::io::Write + Seek>(
}
}
static MAGIC_BYTES: [(&[u8], ImageFormat); 22] = [
static MAGIC_BYTES: [(&[u8], ImageFormat); 23] = [
(b"\x89PNG\r\n\x1a\n", ImageFormat::Png),
(&[0xff, 0xd8, 0xff], ImageFormat::Jpeg),
(b"GIF89a", ImageFormat::Gif),
@ -275,6 +281,7 @@ static MAGIC_BYTES: [(&[u8], ImageFormat); 22] = [
(b"\0\0\0 ftypavif", ImageFormat::Avif),
(b"\0\0\0\x1cftypavif", ImageFormat::Avif),
(&[0x76, 0x2f, 0x31, 0x01], ImageFormat::OpenExr), // = &exr::meta::magic_number::BYTES
(b"qoif", ImageFormat::Qoi),
];
/// Guess image format from memory block

View file

@ -246,6 +246,8 @@ pub mod codecs {
pub mod tiff;
#[cfg(feature = "webp")]
pub mod webp;
#[cfg(feature = "qoi")]
pub mod qoi;
}
mod animation;

Binary file not shown.