use core::mem::size_of;
use crate::{
HtOwnedCodeBlockBatchJob, J2kCodeBlockSegment, J2kDirectColorPlan, J2kDirectGrayscalePlan,
J2kDirectGrayscaleStep, J2kOwnedCodeBlockBatchJob, Result, ValidationError,
DEFAULT_MAX_DECODE_BYTES,
};
#[derive(Default)]
struct RetainedPlanBudget {
bytes: usize,
}
impl RetainedPlanBudget {
fn include_capacity<T>(&mut self, capacity: usize) -> Result<()> {
let bytes = capacity
.checked_mul(size_of::<T>())
.ok_or(ValidationError::ImageTooLarge)?;
self.include_bytes(bytes)
}
fn include_bytes(&mut self, bytes: usize) -> Result<()> {
self.bytes = self
.bytes
.checked_add(bytes)
.ok_or(ValidationError::ImageTooLarge)?;
if self.bytes > DEFAULT_MAX_DECODE_BYTES {
return Err(ValidationError::ImageTooLarge.into());
}
Ok(())
}
}
impl J2kDirectGrayscalePlan {
#[doc(hidden)]
pub fn retained_allocation_bytes(&self) -> Result<usize> {
let mut budget = RetainedPlanBudget::default();
include_grayscale_plan(&mut budget, self)?;
Ok(budget.bytes)
}
}
impl J2kDirectColorPlan {
#[doc(hidden)]
pub fn retained_allocation_bytes(&self) -> Result<usize> {
let mut budget = RetainedPlanBudget::default();
budget.include_capacity::<J2kDirectGrayscalePlan>(self.component_plans.capacity())?;
for component in &self.component_plans {
include_grayscale_plan(&mut budget, component)?;
}
Ok(budget.bytes)
}
}
fn include_grayscale_plan(
budget: &mut RetainedPlanBudget,
plan: &J2kDirectGrayscalePlan,
) -> Result<()> {
budget.include_capacity::<J2kDirectGrayscaleStep>(plan.steps.capacity())?;
for step in &plan.steps {
match step {
J2kDirectGrayscaleStep::ClassicSubBand(sub_band) => {
budget.include_capacity::<J2kOwnedCodeBlockBatchJob>(sub_band.jobs.capacity())?;
for job in &sub_band.jobs {
budget.include_capacity::<u8>(job.data.capacity())?;
budget.include_capacity::<J2kCodeBlockSegment>(job.segments.capacity())?;
}
}
J2kDirectGrayscaleStep::HtSubBand(sub_band) => {
budget.include_capacity::<HtOwnedCodeBlockBatchJob>(sub_band.jobs.capacity())?;
for job in &sub_band.jobs {
budget.include_capacity::<u8>(job.data.capacity())?;
}
}
J2kDirectGrayscaleStep::Idwt(_) | J2kDirectGrayscaleStep::Store(_) => {}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use super::*;
use crate::{HtOwnedSubBandPlan, J2kOwnedSubBandPlan, J2kRect};
#[test]
fn retained_bytes_use_nested_vector_capacities() {
let mut jobs = Vec::new();
jobs.try_reserve_exact(3).expect("job test capacity");
jobs.push(J2kOwnedCodeBlockBatchJob {
output_x: 0,
output_y: 0,
data: {
let mut data = Vec::new();
data.try_reserve_exact(11).expect("data test capacity");
data
},
segments: {
let mut segments = Vec::new();
segments
.try_reserve_exact(5)
.expect("segment test capacity");
segments
},
width: 1,
height: 1,
output_stride: 1,
missing_bit_planes: 0,
number_of_coding_passes: 0,
total_bitplanes: 0,
roi_shift: 0,
sub_band_type: crate::J2kSubBandType::LowLow,
style: crate::J2kCodeBlockStyle {
selective_arithmetic_coding_bypass: false,
reset_context_probabilities: false,
termination_on_each_pass: false,
vertically_causal_context: false,
segmentation_symbols: false,
},
strict: false,
dequantization_step: 1.0,
});
let mut steps = Vec::new();
steps.try_reserve_exact(4).expect("step test capacity");
steps.push(J2kDirectGrayscaleStep::ClassicSubBand(
J2kOwnedSubBandPlan {
band_id: 0,
rect: J2kRect {
x0: 0,
y0: 0,
x1: 1,
y1: 1,
},
width: 1,
height: 1,
jobs,
},
));
let plan = J2kDirectGrayscalePlan {
dimensions: (1, 1),
bit_depth: 8,
steps,
};
assert_eq!(
plan.retained_allocation_bytes().expect("retained bytes"),
4 * size_of::<J2kDirectGrayscaleStep>()
+ 3 * size_of::<J2kOwnedCodeBlockBatchJob>()
+ 11
+ 5 * size_of::<J2kCodeBlockSegment>()
);
}
#[test]
fn all_direct_plan_owner_types_are_move_only() {
fn assert_debug<T: core::fmt::Debug>() {}
assert_debug::<J2kDirectColorPlan>();
assert_debug::<J2kDirectGrayscalePlan>();
assert_debug::<J2kOwnedSubBandPlan>();
assert_debug::<HtOwnedSubBandPlan>();
assert_debug::<J2kOwnedCodeBlockBatchJob>();
assert_debug::<HtOwnedCodeBlockBatchJob>();
}
}