use super::super::coefficient_view::validate_tier1_code_block_geometry;
use super::writers::{MEL_SIZE, MS_SIZE, VLC_SIZE};
use crate::{EncodeError, EncodeResult, DEFAULT_MAX_CODEC_BYTES};
mod refinement;
use refinement::{ht_refinement_allocation, ht_worker_scratch};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct HtWorkerAllocation {
pub(crate) output_bytes: usize,
pub(crate) scratch_bytes: usize,
pub(super) cleanup_bytes: usize,
pub(super) refinement_bytes: usize,
pub(super) sigma_entries: usize,
pub(super) previous_sigma_entries: usize,
pub(super) sigprop_bytes: usize,
pub(super) magref_bits: usize,
pub(super) magref_bytes: usize,
}
impl HtWorkerAllocation {
pub(crate) fn total_bytes(self) -> EncodeResult<usize> {
checked_add(
self.output_bytes,
self.scratch_bytes,
"HTJ2K Tier-1 worker allocation",
)
}
}
pub(crate) fn ht_worker_allocation(
width: usize,
height: usize,
target_coding_passes: u8,
) -> EncodeResult<HtWorkerAllocation> {
if !(1..=3).contains(&target_coding_passes) {
return Err(EncodeError::InvalidInput {
what: "HTJ2K scalar target coding passes must be 1..=3",
});
}
let width_u32 = u32::try_from(width).map_err(|_| EncodeError::InvalidInput {
what: "HTJ2K code-block width exceeds u32",
})?;
let height_u32 = u32::try_from(height).map_err(|_| EncodeError::InvalidInput {
what: "HTJ2K code-block height exceeds u32",
})?;
let coefficients = validate_tier1_code_block_geometry(width, height)?;
let cleanup_bytes = checked_add(
checked_add(MEL_SIZE, VLC_SIZE, "HTJ2K cleanup reservoirs")?,
MS_SIZE,
"HTJ2K cleanup reservoirs",
)?;
let refinement =
ht_refinement_allocation(width_u32, height_u32, coefficients, target_coding_passes)?;
let output_bytes = checked_add(
cleanup_bytes,
refinement.refinement_bytes,
"HTJ2K retained block output",
)?;
let scratch_bytes = ht_worker_scratch(cleanup_bytes, refinement)?;
let allocation = HtWorkerAllocation {
output_bytes,
scratch_bytes,
cleanup_bytes,
refinement_bytes: refinement.refinement_bytes,
sigma_entries: refinement.sigma_entries,
previous_sigma_entries: refinement.previous_sigma_entries,
sigprop_bytes: refinement.sigprop_bytes,
magref_bits: refinement.magref_bits,
magref_bytes: refinement.magref_bytes,
};
let requested = allocation.total_bytes()?;
if requested > DEFAULT_MAX_CODEC_BYTES {
return Err(EncodeError::AllocationTooLarge {
what: "HTJ2K Tier-1 worker allocation",
requested,
cap: DEFAULT_MAX_CODEC_BYTES,
});
}
Ok(allocation)
}
fn checked_add(left: usize, right: usize, what: &'static str) -> EncodeResult<usize> {
left.checked_add(right)
.ok_or(EncodeError::ArithmeticOverflow { what })
}
fn checked_mul(left: usize, right: usize, what: &'static str) -> EncodeResult<usize> {
left.checked_mul(right)
.ok_or(EncodeError::ArithmeticOverflow { what })
}
#[cfg(test)]
mod tests {
use super::ht_worker_allocation;
use crate::EncodeError;
#[test]
fn refinement_plan_adds_only_requested_pass_workspaces() {
let cleanup = ht_worker_allocation(64, 64, 1).expect("cleanup plan");
let sigprop = ht_worker_allocation(64, 64, 2).expect("SigProp plan");
let magref = ht_worker_allocation(64, 64, 3).expect("MagRef plan");
assert_eq!(cleanup.refinement_bytes, 0);
assert!(sigprop.refinement_bytes > 0);
assert!(magref.refinement_bytes > sigprop.refinement_bytes);
assert!(cleanup.scratch_bytes < sigprop.scratch_bytes);
assert!(sigprop.scratch_bytes < magref.scratch_bytes);
}
#[test]
fn invalid_pass_count_is_typed() {
assert_eq!(
ht_worker_allocation(1, 1, 0),
Err(EncodeError::InvalidInput {
what: "HTJ2K scalar target coding passes must be 1..=3",
})
);
}
}