use crate::container::anim::FrameMeta;
use crate::error::Result;
use crate::image::{Dimensions, PixelLayout};
use crate::prelude::*;
#[derive(Clone, Copy, Debug)]
pub struct FramePayload<'a> {
pub vp8l: Option<&'a [u8]>,
pub vp8: Option<&'a [u8]>,
pub alph: Option<&'a [u8]>,
pub dims: Dimensions,
}
#[derive(Clone, Debug)]
pub struct DecodedFrame {
pub argb: Vec<u32>,
pub alpha_used: bool,
}
pub trait FrameDecoder: core::fmt::Debug + Sync {
fn decode_frame(
&self,
frame: FramePayload<'_>,
options: &DecodeOptions,
) -> Result<DecodedFrame>;
}
pub const DEFAULT_MAX_PIXELS: u64 = 100_000_000;
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct DecodeOptions {
pub(crate) layout: PixelLayout,
pub(crate) max_pixels: Option<u64>,
pub(crate) read_metadata: bool,
}
impl Default for DecodeOptions {
fn default() -> Self {
Self {
layout: PixelLayout::Rgba8,
max_pixels: Some(DEFAULT_MAX_PIXELS),
read_metadata: true,
}
}
}
impl DecodeOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn layout(mut self, layout: PixelLayout) -> Self {
self.layout = layout;
self
}
#[must_use]
pub const fn max_pixels(mut self, max: u64) -> Self {
self.max_pixels = Some(max);
self
}
#[must_use]
pub const fn unbounded(mut self) -> Self {
self.max_pixels = None;
self
}
#[must_use]
pub const fn read_metadata(mut self, read: bool) -> Self {
self.read_metadata = read;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct ImageInfo {
pub dimensions: Dimensions,
pub has_alpha: bool,
pub has_metadata: bool,
pub is_animated: bool,
}
impl ImageInfo {
#[must_use]
pub const fn new(
dimensions: Dimensions,
has_alpha: bool,
has_metadata: bool,
is_animated: bool,
) -> Self {
Self {
dimensions,
has_alpha,
has_metadata,
is_animated,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Progress {
NeedMoreInput,
HeaderReady(ImageInfo),
RowsDecoded {
first_row: u32,
count: u32,
},
FrameComplete(FrameMeta),
Finished,
}
#[derive(Clone, Copy, Debug)]
pub struct RowDrain<'a> {
pub first_row: u32,
pub rows: u32,
pub width: u32,
pub layout: PixelLayout,
bytes: &'a [u8],
}
impl<'a> RowDrain<'a> {
#[must_use]
pub const fn new(
first_row: u32,
rows: u32,
width: u32,
layout: PixelLayout,
bytes: &'a [u8],
) -> Self {
Self {
first_row,
rows,
width,
layout,
bytes,
}
}
}
impl RowDrain<'_> {
#[must_use]
pub const fn as_bytes(&self) -> &[u8] {
self.bytes
}
#[must_use]
pub fn row(&self, i: u32) -> &[u8] {
let row_bytes = self.width as usize * 4;
let start = i as usize * row_bytes;
&self.bytes[start..start + row_bytes]
}
}
#[cfg(test)]
mod tests {
use super::{DEFAULT_MAX_PIXELS, DecodeOptions, ImageInfo};
use crate::image::{Dimensions, PixelLayout};
#[test]
fn image_info_new_is_the_construction_path() {
let dims = Dimensions::new(3, 4).unwrap();
let info = ImageInfo::new(dims, true, false, true);
assert_eq!(info.dimensions, dims);
assert!(info.has_alpha);
assert!(!info.has_metadata);
assert!(info.is_animated);
}
#[test]
fn decode_options_builder_round_trips_and_defaults_to_the_safety_cap() {
assert_eq!(
DecodeOptions::default().max_pixels,
Some(DEFAULT_MAX_PIXELS)
);
let opts = DecodeOptions::new()
.layout(PixelLayout::Bgra8)
.max_pixels(1024)
.read_metadata(false);
assert_eq!(opts.layout, PixelLayout::Bgra8);
assert_eq!(opts.max_pixels, Some(1024));
assert!(!opts.read_metadata);
}
#[test]
fn unbounded_removes_the_default_cap() {
assert_eq!(
DecodeOptions::default().max_pixels,
Some(DEFAULT_MAX_PIXELS)
);
assert_eq!(DecodeOptions::new().unbounded().max_pixels, None);
assert_eq!(
DecodeOptions::new().unbounded().max_pixels(64).max_pixels,
Some(64)
);
}
#[test]
fn row_drain_row_slices_by_width_times_four() {
use super::RowDrain;
let bytes: Vec<u8> = (0..24).collect();
let drain = RowDrain::new(5, 2, 3, PixelLayout::Rgba8, &bytes);
assert_eq!(drain.row(0), &(0u8..12).collect::<Vec<u8>>()[..]);
assert_eq!(drain.row(1), &(12u8..24).collect::<Vec<u8>>()[..]);
assert_eq!(drain.first_row, 5);
assert_eq!(drain.rows, 2);
}
}