pub mod avif;
pub mod jpeg;
pub mod jxl;
pub mod png;
pub mod qoi;
pub mod webp;
use crate::error::Result;
use crate::format::Format;
#[derive(Debug, Clone)]
pub struct ImageData {
pub width: u32,
pub height: u32,
pub data: Vec<u8>,
}
impl ImageData {
pub fn new(width: u32, height: u32, data: Vec<u8>) -> Self {
debug_assert_eq!(
data.len(),
(width as usize) * (height as usize) * 4,
"ImageData: expected {} bytes ({}x{}x4), got {}",
(width as usize) * (height as usize) * 4,
width,
height,
data.len(),
);
Self {
width,
height,
data,
}
}
pub fn to_rgb(&self) -> Vec<u8> {
self.data
.chunks_exact(4)
.flat_map(|px| &px[..3])
.copied()
.collect()
}
}
#[derive(Debug, Clone, Copy)]
pub struct EncodeOptions {
pub quality: u8,
}
impl Default for EncodeOptions {
fn default() -> Self {
Self { quality: 80 }
}
}
pub trait Codec {
fn format(&self) -> Format;
fn decode(&self, data: &[u8]) -> Result<ImageData>;
fn encode(&self, image: &ImageData, options: &EncodeOptions) -> Result<Vec<u8>>;
}
pub fn get_codec(format: Format) -> Box<dyn Codec> {
match format {
Format::Jpeg => Box::new(jpeg::JpegCodec),
Format::Png => Box::new(png::PngCodec),
Format::WebP => Box::new(webp::WebPCodec),
Format::Avif => Box::new(avif::AvifCodec),
Format::Jxl => Box::new(jxl::JxlCodec),
Format::Qoi => Box::new(qoi::QoiCodec),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn image_data_to_rgb() {
let data = vec![255, 0, 0, 255, 0, 255, 0, 128];
let img = ImageData::new(2, 1, data);
let rgb = img.to_rgb();
assert_eq!(rgb, vec![255, 0, 0, 0, 255, 0]);
}
#[test]
fn encode_options_default() {
let opts = EncodeOptions::default();
assert_eq!(opts.quality, 80);
}
#[test]
fn image_data_dimensions() {
let data = vec![0u8; 4 * 3 * 2]; let img = ImageData::new(3, 2, data);
assert_eq!(img.width, 3);
assert_eq!(img.height, 2);
assert_eq!(img.data.len(), 24);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "ImageData: expected")]
fn image_data_wrong_size_panics_in_debug() {
let data = vec![0u8; 10]; let _ = ImageData::new(2, 2, data);
}
}