use core::f64::consts::PI;
use core::fmt;
use std::sync::LazyLock;
pub(crate) const fn low_len(sample_len: usize) -> usize {
sample_len.div_ceil(2)
}
pub(crate) const fn high_len(sample_len: usize) -> usize {
sample_len / 2
}
pub(crate) fn idct8_basis(sample_idx: usize, freq: usize) -> f64 {
debug_assert!(sample_idx < 8);
debug_assert!(freq < 8);
idct8_basis_table()[sample_idx][freq]
}
pub(crate) fn idct8_basis_table() -> &'static [[f64; 8]; 8] {
static BASIS: LazyLock<[[f64; 8]; 8]> = LazyLock::new(|| {
let mut basis = [[0.0; 8]; 8];
for (sample_idx, row) in basis.iter_mut().enumerate() {
for (freq, value) in row.iter_mut().enumerate() {
*value = idct8_basis_uncached(sample_idx, freq);
}
}
basis
});
&BASIS
}
#[expect(
clippy::cast_precision_loss,
reason = "DCT basis indices are debug-asserted below eight and exactly representable in f64"
)]
fn idct8_basis_uncached(sample_idx: usize, freq: usize) -> f64 {
let scale = if freq == 0 {
(1.0_f64 / 8.0).sqrt()
} else {
(2.0_f64 / 8.0).sqrt()
};
scale * (((sample_idx as f64 + 0.5) * freq as f64 * PI) / 8.0).cos()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DctGridError {
block_count: usize,
block_cols: usize,
block_rows: usize,
width: usize,
height: usize,
}
impl DctGridError {
const fn new(
block_count: usize,
block_cols: usize,
block_rows: usize,
width: usize,
height: usize,
) -> Self {
Self {
block_count,
block_cols,
block_rows,
width,
height,
}
}
#[must_use]
pub const fn block_count(self) -> usize {
self.block_count
}
#[must_use]
pub const fn block_cols(self) -> usize {
self.block_cols
}
#[must_use]
pub const fn block_rows(self) -> usize {
self.block_rows
}
#[must_use]
pub const fn width(self) -> usize {
self.width
}
#[must_use]
pub const fn height(self) -> usize {
self.height
}
}
impl fmt::Display for DctGridError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"DCT grid has {} blocks for {}x{} grid covering requested {}x{} samples",
self.block_count, self.block_cols, self.block_rows, self.width, self.height
)
}
}
impl std::error::Error for DctGridError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DctTransformError {
Grid(DctGridError),
InvalidSamplePlaneDimensions {
width: usize,
height: usize,
},
SamplePlaneLengthMismatch {
sample_count: usize,
width: usize,
height: usize,
},
SymbolicWeightIndexOutOfRange {
sample_len: usize,
output_index: usize,
high_pass: bool,
},
MemoryCapExceeded {
requested: usize,
cap: usize,
},
HostAllocationFailed {
bytes: usize,
},
}
impl From<DctGridError> for DctTransformError {
fn from(value: DctGridError) -> Self {
Self::Grid(value)
}
}
impl fmt::Display for DctTransformError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Grid(error) => error.fmt(f),
Self::InvalidSamplePlaneDimensions { width, height } => {
write!(
f,
"sample plane dimensions must be non-zero, got {width}x{height}"
)
}
Self::SamplePlaneLengthMismatch {
sample_count,
width,
height,
} => write!(
f,
"sample plane has {sample_count} values for declared {width}x{height} dimensions"
),
Self::SymbolicWeightIndexOutOfRange {
sample_len,
output_index,
high_pass,
} => write!(
f,
"symbolic 5/3 row {output_index} is outside the {}-pass extent for {sample_len} samples",
if *high_pass { "high" } else { "low" }
),
Self::MemoryCapExceeded { requested, cap } => write!(
f,
"DCT transform workspace requires {requested} bytes, exceeding the {cap}-byte cap"
),
Self::HostAllocationFailed { bytes } => {
write!(f, "DCT transform host allocation failed for {bytes} bytes")
}
}
}
}
impl std::error::Error for DctTransformError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Grid(error) => Some(error),
Self::InvalidSamplePlaneDimensions { .. }
| Self::SamplePlaneLengthMismatch { .. }
| Self::SymbolicWeightIndexOutOfRange { .. }
| Self::MemoryCapExceeded { .. }
| Self::HostAllocationFailed { .. } => None,
}
}
}
pub(crate) fn validate_dct_block_grid(
block_count: usize,
block_cols: usize,
block_rows: usize,
width: usize,
height: usize,
) -> Result<(), DctGridError> {
let expected_blocks = block_cols
.checked_mul(block_rows)
.ok_or_else(|| DctGridError::new(block_count, block_cols, block_rows, width, height))?;
let covered_width = block_cols
.checked_mul(8)
.ok_or_else(|| DctGridError::new(block_count, block_cols, block_rows, width, height))?;
let covered_height = block_rows
.checked_mul(8)
.ok_or_else(|| DctGridError::new(block_count, block_cols, block_rows, width, height))?;
if block_count != expected_blocks
|| width == 0
|| height == 0
|| width > covered_width
|| height > covered_height
{
return Err(DctGridError::new(
block_count,
block_cols,
block_rows,
width,
height,
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::error::Error as _;
use super::{
high_len, idct8_basis, low_len, validate_dct_block_grid, DctGridError, DctTransformError,
};
#[test]
fn band_lengths_split_even_and_odd_samples() {
assert_eq!((low_len(0), high_len(0)), (0, 0));
assert_eq!((low_len(1), high_len(1)), (1, 0));
assert_eq!((low_len(8), high_len(8)), (4, 4));
assert_eq!((low_len(9), high_len(9)), (5, 4));
}
#[test]
fn idct8_basis_is_orthonormal_for_dc_and_first_ac() {
assert!((idct8_basis(0, 0) - (1.0_f64 / 8.0).sqrt()).abs() < 1e-12);
assert!((idct8_basis(0, 1) - 0.490_392_640_201_615_2).abs() < 1e-12);
}
#[test]
fn validates_non_empty_grid_covered_by_blocks() {
assert_eq!(validate_dct_block_grid(6, 3, 2, 24, 16), Ok(()));
}
#[test]
fn rejects_overflowing_grid_dimensions() {
let err = validate_dct_block_grid(1, usize::MAX, usize::MAX, 8, 8)
.expect_err("overflowing dimensions must fail");
assert_eq!(err.block_count(), 1);
assert_eq!(err.block_cols(), usize::MAX);
assert_eq!(err.block_rows(), usize::MAX);
}
#[test]
fn rejects_zero_image_extent() {
assert!(validate_dct_block_grid(1, 1, 1, 0, 8).is_err());
assert!(validate_dct_block_grid(1, 1, 1, 8, 0).is_err());
}
#[test]
fn transform_error_conversion_display_and_sources_preserve_typed_context() {
let grid =
validate_dct_block_grid(1, 2, 1, 16, 8).expect_err("block count mismatch must fail");
let cases = [
(
DctTransformError::from(grid),
"DCT grid has 1 blocks for 2x1 grid covering requested 16x8 samples",
),
(
DctTransformError::InvalidSamplePlaneDimensions {
width: 0,
height: 8,
},
"sample plane dimensions must be non-zero, got 0x8",
),
(
DctTransformError::SamplePlaneLengthMismatch {
sample_count: 7,
width: 2,
height: 4,
},
"sample plane has 7 values for declared 2x4 dimensions",
),
(
DctTransformError::SymbolicWeightIndexOutOfRange {
sample_len: 5,
output_index: 3,
high_pass: true,
},
"symbolic 5/3 row 3 is outside the high-pass extent for 5 samples",
),
(
DctTransformError::MemoryCapExceeded {
requested: 17,
cap: 16,
},
"DCT transform workspace requires 17 bytes, exceeding the 16-byte cap",
),
(
DctTransformError::HostAllocationFailed { bytes: 32 },
"DCT transform host allocation failed for 32 bytes",
),
];
for (index, (error, expected)) in cases.into_iter().enumerate() {
assert_eq!(error.to_string(), expected);
if index == 0 {
assert_eq!(
error
.source()
.and_then(|source| source.downcast_ref::<DctGridError>()),
Some(&grid)
);
} else {
assert!(error.source().is_none());
}
}
}
}