#![warn(missing_docs)]
#[cfg(animation)]
mod animation;
mod color;
#[cfg(any(feature = "png", feature = "jpeg", feature = "webp"))]
mod encode;
mod probe;
#[cfg(animation)]
use animation::FrameStream;
#[cfg(any(feature = "png", feature = "jpeg", feature = "webp"))]
pub use encode::{encode, EncodeError, Encoding};
use color::ColorSpace;
use image::DynamicImage;
use probe::Probe;
use std::io::Cursor;
use std::sync::Arc;
use std::time::Duration;
use valo::{AlphaType, PixelBuffer, PixelFormat, PixelLayout};
use valo_codec::{
DecodeError, DecodedFrame, Decoder, Decoding, FramePixels, FrameReader, ImageInfo, OpenError,
OpenRequest,
};
#[derive(Clone, Copy, Debug, Default)]
pub struct SoftwareDecoder;
impl Decoder for SoftwareDecoder {
fn name(&self) -> &'static str {
"software"
}
fn open<'a>(
&'a self,
request: &'a OpenRequest,
) -> Decoding<'a, Result<Box<dyn FrameReader>, OpenError>> {
Box::pin(std::future::ready(
SoftwareReader::open(request).map(|reader| Box::new(reader) as Box<dyn FrameReader>),
))
}
}
struct SoftwareReader {
encoded: Arc<[u8]>,
probe: Probe,
color: ColorSpace,
limits: image::Limits,
output_size: [u32; 2],
#[cfg(animation)]
animation: Option<FrameStream>,
next: u32,
}
impl SoftwareReader {
fn open(request: &OpenRequest) -> Result<Self, OpenError> {
let limits = request.options.limits;
let probe = probe::probe(&request.encoded, limits)?;
let color = ColorSpace::detect(&probe, &request.encoded)?;
let image_limits = image_limits(limits);
#[cfg(animation)]
let animation = probe
.animated
.then(|| FrameStream::new(request.encoded.clone(), probe.format, image_limits.clone()));
Ok(Self {
output_size: request.options.fit(probe.oriented_size),
encoded: request.encoded.clone(),
probe,
color,
limits: image_limits,
#[cfg(animation)]
animation,
next: 0,
})
}
fn decode_still(&self) -> Result<DynamicImage, DecodeError> {
let mut reader =
image::ImageReader::with_format(Cursor::new(self.encoded.clone()), self.probe.format);
reader.limits(self.limits.clone());
reader.decode().map_err(decode_error)
}
#[cfg(animation)]
fn decoded(&mut self, index: u32) -> Result<(DynamicImage, Duration), DecodeError> {
match &mut self.animation {
Some(stream) => {
let (frame, duration) = stream.frame(index)?;
Ok((DynamicImage::ImageRgba8(frame), duration))
}
None => Ok((self.decode_still()?, Duration::ZERO)),
}
}
#[cfg(not(animation))]
fn decoded(&mut self, _index: u32) -> Result<(DynamicImage, Duration), DecodeError> {
Ok((self.decode_still()?, Duration::ZERO))
}
fn finish(&self, mut image: DynamicImage) -> Result<PixelBuffer, DecodeError> {
image.apply_orientation(self.probe.orientation);
if [image.width(), image.height()] != self.output_size {
let [width, height] = self.output_size;
image = image.resize_exact(width, height, image::imageops::FilterType::Triangle);
}
let rgba = self.color.to_srgb_rgba8(image)?;
let layout = PixelLayout::packed(self.output_size, PixelFormat::Rgba8, AlphaType::Straight);
Ok(PixelBuffer::new(layout, rgba)?)
}
}
impl FrameReader for SoftwareReader {
fn info(&self) -> ImageInfo {
ImageInfo {
size: self.output_size,
frame_count: self.probe.frame_count,
repetition: self.probe.repetition,
}
}
fn next_frame(&mut self) -> Decoding<'_, Result<DecodedFrame, DecodeError>> {
Box::pin(std::future::ready(self.decode_next()))
}
}
impl SoftwareReader {
fn decode_next(&mut self) -> Result<DecodedFrame, DecodeError> {
let index = self.next;
self.next = (index + 1) % self.probe.frame_count;
let (image, duration) = self.decoded(index)?;
Ok(DecodedFrame {
pixels: FramePixels::Cpu(self.finish(image)?),
duration,
})
}
}
fn image_limits(limits: valo_codec::DecodeLimits) -> image::Limits {
let mut image_limits = image::Limits::default();
image_limits.max_alloc = Some(limits.max_pixels.saturating_mul(16));
image_limits
}
pub(crate) fn decode_error(error: image::ImageError) -> DecodeError {
use image::ImageError;
match error {
ImageError::Decoding(error) => DecodeError::InvalidData(error.to_string()),
ImageError::Limits(_) => DecodeError::LimitExceeded("software decoder allocation"),
ImageError::Unsupported(error) => DecodeError::Failed(error.to_string()),
other => DecodeError::Failed(other.to_string()),
}
}