mirror of
https://github.com/image-rs/image
synced 2024-10-18 16:52:23 +00:00
Implement support for Qoi format
This commit is contained in:
parent
a1ce569afd
commit
bbd53eb960
|
@ -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
96
src/codecs/qoi.rs
Normal 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());
|
||||
}
|
||||
}
|
17
src/image.rs
17
src/image.rs
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -246,6 +246,8 @@ pub mod codecs {
|
|||
pub mod tiff;
|
||||
#[cfg(feature = "webp")]
|
||||
pub mod webp;
|
||||
#[cfg(feature = "qoi")]
|
||||
pub mod qoi;
|
||||
}
|
||||
|
||||
mod animation;
|
||||
|
|
BIN
tests/images/qoi/basic-test.qoi
Normal file
BIN
tests/images/qoi/basic-test.qoi
Normal file
Binary file not shown.
Loading…
Reference in a new issue