use crate::DecodeError;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DecodeOptions {
pub max_size: Option<[u32; 2]>,
pub mipmaps: bool,
pub limits: DecodeLimits,
}
impl DecodeOptions {
pub fn fit(&self, source: [u32; 2]) -> [u32; 2] {
match self.max_size {
Some(max) => fit_within(source, max),
None => source,
}
}
}
pub fn fit_within(source: [u32; 2], bounds: [u32; 2]) -> [u32; 2] {
if source[0] <= bounds[0] && source[1] <= bounds[1] {
return source;
}
let by_width = [bounds[0], scaled(source[1], source[0], bounds[0])];
if by_width[1] <= bounds[1] {
by_width
} else {
[scaled(source[0], source[1], bounds[1]), bounds[1]]
}
}
fn scaled(value: u32, from: u32, to: u32) -> u32 {
let from = u64::from(from.max(1));
let scaled = (u64::from(value) * u64::from(to) + from / 2) / from;
u32::try_from(scaled).unwrap_or(u32::MAX).max(1)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DecodeLimits {
pub max_encoded_bytes: usize,
pub max_pixels: u64,
pub max_frames: u32,
}
impl Default for DecodeLimits {
fn default() -> Self {
Self {
max_encoded_bytes: 256 * 1024 * 1024,
max_pixels: 64 * 1024 * 1024,
max_frames: 10_000,
}
}
}
impl DecodeLimits {
pub fn check_source_size(self, size: [u32; 2]) -> Result<(), DecodeError> {
if size.contains(&0) {
return Err(DecodeError::InvalidData("empty image".into()));
}
if u64::from(size[0]) * u64::from(size[1]) > self.max_pixels {
return Err(DecodeError::LimitExceeded("decoded pixels"));
}
Ok(())
}
pub fn check_frame_count(self, count: u32) -> Result<(), DecodeError> {
if count == 0 {
return Err(DecodeError::InvalidData("image has no frames".into()));
}
if count > self.max_frames {
return Err(DecodeError::LimitExceeded("animation frames"));
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ImageInfo {
pub size: [u32; 2],
pub frame_count: u32,
pub repetition: Repetition,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Repetition {
#[default]
Once,
Times(u32),
Forever,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fitting_keeps_aspect_and_never_upscales() {
assert_eq!(fit_within([400, 200], [800, 800]), [400, 200]);
assert_eq!(fit_within([400, 200], [100, 100]), [100, 50]);
assert_eq!(fit_within([200, 400], [100, 100]), [50, 100]);
assert_eq!(fit_within([4000, 1], [100, 100]), [100, 1]);
assert_eq!(fit_within([3, 3], [2, 1]), [1, 1]);
}
}