use super::{NativeEncodePipelineError, NativeEncodePipelineResult, SubBandType};
pub(super) fn subband_origin(
origin: (u32, u32),
depth: u8,
band: SubBandType,
) -> NativeEncodePipelineResult<(u32, u32)> {
j2k_types::encode_geometry::encode_subband_origin(
origin,
depth,
super::public_sub_band_type(band),
)
.ok_or_else(|| {
NativeEncodePipelineError::internal_invariant(
"subband origin exceeds supported decomposition depth",
)
})
}
pub(super) fn block_count(origin: u32, extent: u32, span: u32) -> NativeEncodePipelineResult<u32> {
j2k_types::encode_geometry::encode_grid_cell_count(origin, extent, span)
.ok_or_else(|| NativeEncodePipelineError::internal_invariant("invalid code-block grid"))
}
pub(super) fn block_span(
origin: u32,
extent: u32,
span: u32,
index: u32,
) -> NativeEncodePipelineResult<(u32, u32)> {
j2k_types::encode_geometry::encode_grid_cell_span(origin, extent, span, index).ok_or_else(
|| NativeEncodePipelineError::internal_invariant("code-block index exceeds subband grid"),
)
}
pub(super) fn tile_bounds(
width: u32,
height: u32,
origin: (u32, u32),
) -> NativeEncodePipelineResult<j2k_types::J2kRect> {
Ok(j2k_types::J2kRect {
x0: origin.0,
y0: origin.1,
x1: origin
.0
.checked_add(width)
.ok_or_else(|| NativeEncodePipelineError::arithmetic_overflow("tile x endpoint"))?,
y1: origin
.1
.checked_add(height)
.ok_or_else(|| NativeEncodePipelineError::arithmetic_overflow("tile y endpoint"))?,
})
}
const DEFAULT_PRECINCT_EXPONENT: u8 = 15;
#[cfg(test)]
pub(super) const MISALIGNED_TILE_GRID: &str =
"multi-tile encode requires tile origins on the wavelet, code-block, and precinct grids";
pub(super) fn component_tile_axis_is_aligned(
origin: u32,
end: u32,
levels: u8,
code_block_exponent: u8,
precinct_exponent: impl Fn(u8) -> u8,
) -> bool {
if origin == 0 {
return true;
}
let (origin, end) = (u64::from(origin), u64::from(end));
let Some(level_grid) = 1_u64.checked_shl(u32::from(levels)) else {
return false;
};
if !origin.is_multiple_of(level_grid) {
return false;
}
let code_block = 1_u64 << code_block_exponent;
(0..=levels).all(|resolution| {
let resolution_shift = levels - resolution;
let band_shift = if resolution == 0 {
resolution_shift
} else {
resolution_shift + 1
};
let precinct = 1_u64 << precinct_exponent(resolution).min(DEFAULT_PRECINCT_EXPONENT);
range_respects_grid(
origin >> resolution_shift,
end.div_ceil(1 << resolution_shift),
precinct,
) && range_respects_grid(
origin >> band_shift,
end.div_ceil(1 << band_shift),
code_block,
)
})
}
fn range_respects_grid(start: u64, end: u64, grid: u64) -> bool {
start.is_multiple_of(grid) || (start / grid + 1) * grid >= end
}
#[cfg(test)]
pub(super) fn tile_axis_is_aligned(
image_extent: u32,
tile_extent: u32,
sampling: u32,
levels: u8,
code_block_exponent: u8,
precinct_exponent: impl Fn(u8) -> u8 + Copy,
) -> bool {
let component_extent = image_extent.div_ceil(sampling);
let mut tile_origin = tile_extent;
while tile_origin < image_extent {
let tile_end = tile_origin.saturating_add(tile_extent).min(image_extent);
let origin = tile_origin.div_ceil(sampling);
let end = tile_end.div_ceil(sampling).min(component_extent);
if !component_tile_axis_is_aligned(
origin,
end,
levels,
code_block_exponent,
precinct_exponent,
) {
return false;
}
tile_origin = tile_end;
}
true
}
#[cfg(test)]
pub(super) fn validate_tile_grid(
image: (u32, u32),
tile: (u32, u32),
sampling: impl IntoIterator<Item = (u32, u32)>,
levels: u8,
code_block_exponents: (u8, u8),
precinct_exponents: &[(u8, u8)],
) -> Result<(), &'static str> {
let horizontal = |resolution: u8| {
precinct_exponents
.get(usize::from(resolution))
.map_or(DEFAULT_PRECINCT_EXPONENT, |exponents| exponents.0)
};
let vertical = |resolution: u8| {
precinct_exponents
.get(usize::from(resolution))
.map_or(DEFAULT_PRECINCT_EXPONENT, |exponents| exponents.1)
};
let code_block_width = code_block_exponents.0.saturating_add(2);
let code_block_height = code_block_exponents.1.saturating_add(2);
for (x_sampling, y_sampling) in sampling {
if !tile_axis_is_aligned(
image.0,
tile.0,
x_sampling.max(1),
levels,
code_block_width,
horizontal,
) || !tile_axis_is_aligned(
image.1,
tile.1,
y_sampling.max(1),
levels,
code_block_height,
vertical,
) {
return Err(MISALIGNED_TILE_GRID);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::{tile_axis_is_aligned, validate_tile_grid};
const NO_PRECINCTS: fn(u8) -> u8 = |_| 15;
fn axis(image: u32, tile: u32, levels: u8) -> bool {
tile_axis_is_aligned(image, tile, 1, levels, 6, NO_PRECINCTS)
}
#[test]
fn accepts_power_of_two_tiles_and_single_tiles() {
assert!(axis(301, 64, 1));
assert!(axis(301, 64, 5));
assert!(axis(16_384, 1024, 5));
assert!(axis(16_384, 256, 5));
assert!(axis(301, 301, 5));
assert!(axis(301, 512, 5));
}
#[test]
fn rejects_tile_origins_off_the_wavelet_grid() {
assert!(!axis(30, 15, 1));
assert!(!axis(40, 18, 2));
assert!(axis(40, 20, 2));
assert!(!axis(6000, 1000, 5));
}
#[test]
fn rejects_sub_bands_crossing_a_code_block_boundary() {
assert!(!axis(203, 48, 1));
assert!(!axis(301, 48, 1));
assert!(!axis(301, 96, 0));
assert!(axis(96, 48, 1));
}
#[test]
fn rejects_resolutions_crossing_a_precinct_boundary() {
assert!(!tile_axis_is_aligned(600, 192, 1, 0, 6, |_| 7));
assert!(tile_axis_is_aligned(600, 192, 1, 0, 6, NO_PRECINCTS));
assert!(!axis(40_000, 20_480, 0));
assert!(axis(32_768, 20_480, 0));
}
#[test]
fn applies_component_sampling_to_tile_origins() {
assert!(!tile_axis_is_aligned(301, 64, 3, 1, 6, NO_PRECINCTS));
assert!(tile_axis_is_aligned(301, 64, 2, 1, 6, NO_PRECINCTS));
}
#[test]
fn validates_both_axes_for_every_component() {
assert_eq!(
validate_tile_grid((301, 203), (64, 64), [(1, 1)], 1, (4, 4), &[]),
Ok(())
);
assert!(validate_tile_grid((301, 203), (64, 48), [(1, 1)], 1, (4, 4), &[]).is_err());
assert!(validate_tile_grid((301, 203), (48, 64), [(1, 1)], 1, (4, 4), &[]).is_err());
assert!(
validate_tile_grid((301, 203), (64, 64), [(1, 1), (3, 1)], 1, (4, 4), &[]).is_err()
);
}
}