use crate::Error;
use crate::pixel::{
IntegralPixel, IntegralSquaredPixel, Mono32, Mono64, MonoF64, Rgb32, Rgb64, RgbF64,
};
pub(super) trait IntegralCapacity: Copy {
fn channel_capacity_u128() -> u128;
fn pixel_to_per_channel_u128(value: Self) -> u128;
}
pub(super) trait IntegralSquaredCapacity: Copy {
fn channel_capacity_u128() -> u128;
fn pixel_to_per_channel_u128(value: Self) -> u128;
}
const F64_INTEGER_CAPACITY: u128 = 1u128 << 53;
impl IntegralCapacity for Mono32 {
#[inline]
fn channel_capacity_u128() -> u128 {
u32::MAX as u128
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
value.value() as u128
}
}
impl IntegralCapacity for Mono64 {
#[inline]
fn channel_capacity_u128() -> u128 {
u64::MAX as u128
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
value.value() as u128
}
}
impl IntegralCapacity for MonoF64 {
#[inline]
fn channel_capacity_u128() -> u128 {
F64_INTEGER_CAPACITY
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
value.value().max(0.0) as u128
}
}
impl IntegralSquaredCapacity for Mono64 {
#[inline]
fn channel_capacity_u128() -> u128 {
u64::MAX as u128
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
value.value() as u128
}
}
impl IntegralSquaredCapacity for MonoF64 {
#[inline]
fn channel_capacity_u128() -> u128 {
F64_INTEGER_CAPACITY
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
value.value().max(0.0) as u128
}
}
impl IntegralCapacity for Rgb32 {
#[inline]
fn channel_capacity_u128() -> u128 {
u32::MAX as u128
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
let r = value.r.0 as u128;
let g = value.g.0 as u128;
let b = value.b.0 as u128;
r.max(g).max(b)
}
}
impl IntegralCapacity for Rgb64 {
#[inline]
fn channel_capacity_u128() -> u128 {
u64::MAX as u128
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
let r = value.r.0 as u128;
let g = value.g.0 as u128;
let b = value.b.0 as u128;
r.max(g).max(b)
}
}
impl IntegralCapacity for RgbF64 {
#[inline]
fn channel_capacity_u128() -> u128 {
F64_INTEGER_CAPACITY
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
let r = value.r.max(0.0) as u128;
let g = value.g.max(0.0) as u128;
let b = value.b.max(0.0) as u128;
r.max(g).max(b)
}
}
impl IntegralSquaredCapacity for Rgb64 {
#[inline]
fn channel_capacity_u128() -> u128 {
u64::MAX as u128
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
let r = value.r.0 as u128;
let g = value.g.0 as u128;
let b = value.b.0 as u128;
r.max(g).max(b)
}
}
impl IntegralSquaredCapacity for RgbF64 {
#[inline]
fn channel_capacity_u128() -> u128 {
F64_INTEGER_CAPACITY
}
#[inline]
fn pixel_to_per_channel_u128(value: Self) -> u128 {
let r = value.r.max(0.0) as u128;
let g = value.g.max(0.0) as u128;
let b = value.b.max(0.0) as u128;
r.max(g).max(b)
}
}
#[inline]
pub(super) fn check<P, A>(width: usize, height: usize) -> Result<(), Error>
where
P: IntegralPixel<A>,
A: Copy + IntegralCapacity,
{
let per_channel = A::pixel_to_per_channel_u128(P::max_integral_value());
let cap = A::channel_capacity_u128();
verify(per_channel, width, height, cap)
}
#[inline]
pub(super) fn check_squared<P, A>(width: usize, height: usize) -> Result<(), Error>
where
P: IntegralSquaredPixel<A>,
A: Copy + IntegralSquaredCapacity,
{
let per_channel = A::pixel_to_per_channel_u128(P::max_integral_squared_value());
let cap = A::channel_capacity_u128();
verify(per_channel, width, height, cap)
}
#[inline]
fn verify(per_channel: u128, width: usize, height: usize, cap: u128) -> Result<(), Error> {
let required = per_channel
.checked_mul(width as u128)
.and_then(|v| v.checked_mul(height as u128))
.ok_or(Error::AccumulatorOverflow {
required_capacity: u128::MAX,
accumulator_capacity: cap,
})?;
if required <= cap {
Ok(())
} else {
Err(Error::AccumulatorOverflow {
required_capacity: required,
accumulator_capacity: cap,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pixel::{Mono8, Mono16, MonoF32, Rgb8};
#[test]
fn mono8_to_mono32_passes_for_small_image() {
assert!(check::<Mono8, Mono32>(1024, 1024).is_ok());
}
#[test]
fn mono8_to_mono32_fails_at_4k_squared() {
let w = 5000;
let h = 5000;
let err = check::<Mono8, Mono32>(w, h).unwrap_err();
let Error::AccumulatorOverflow {
required_capacity,
accumulator_capacity,
} = err
else {
panic!("expected AccumulatorOverflow, got {:?}", err);
};
assert_eq!(
required_capacity,
255u128 * (w as u128) * (h as u128),
"required_capacity should be 255 × W × H",
);
assert_eq!(accumulator_capacity, u32::MAX as u128);
}
#[test]
fn mono8_to_mono64_passes_at_huge_size() {
assert!(check::<Mono8, Mono64>(1_000_000, 1_000_000).is_ok());
}
#[test]
fn mono16_to_mono64_passes() {
assert!(check::<Mono16, Mono64>(65_536, 65_536).is_ok());
}
#[test]
fn monof32_to_monof64_passes_for_megapixel_image() {
assert!(check::<MonoF32, MonoF64>(1024, 1024).is_ok());
}
#[test]
fn rgb8_to_rgb32_passes_for_small_image() {
assert!(check::<Rgb8, Rgb32>(1024, 1024).is_ok());
}
#[test]
fn rgb8_to_rgb32_fails_for_huge_image() {
let w = 5000;
let h = 5000;
let err = check::<Rgb8, Rgb32>(w, h).unwrap_err();
let Error::AccumulatorOverflow {
required_capacity,
accumulator_capacity,
} = err
else {
panic!("expected AccumulatorOverflow, got {:?}", err);
};
assert_eq!(required_capacity, 255u128 * (w as u128) * (h as u128));
assert_eq!(accumulator_capacity, u32::MAX as u128);
}
#[test]
fn check_squared_mono8_to_mono64() {
assert!(check_squared::<Mono8, Mono64>(1024, 1024).is_ok());
}
#[test]
fn check_squared_mono16_to_monof64_passes_for_megapixel_image() {
assert!(check_squared::<Mono16, MonoF64>(1024, 1024).is_ok());
}
#[test]
fn check_squared_mono16_to_monof64_fails_at_large_image() {
let err = check_squared::<Mono16, MonoF64>(4096, 4096).unwrap_err();
let Error::AccumulatorOverflow {
required_capacity,
accumulator_capacity,
} = err
else {
panic!("expected AccumulatorOverflow");
};
assert!(required_capacity > accumulator_capacity);
assert_eq!(accumulator_capacity, F64_INTEGER_CAPACITY);
}
#[test]
fn zero_sized_image_passes() {
assert!(check::<Mono8, Mono32>(0, 0).is_ok());
assert!(check::<Mono8, Mono32>(1024, 0).is_ok());
assert!(check::<Mono8, Mono32>(0, 1024).is_ok());
}
}