use alloc::vec::Vec;
use crate::{
AlphaMode, BandPresence, ByteRange, ChromaSampling, ColorFormat, DecodeScale, ImageInfo,
JxrError, JxrErrorKind, OverlapMode, Rect,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PredictionMode {
None,
FromLeft,
FromTop,
FromTopLeft,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct QuantizerSet {
pub dc: u32,
pub low_pass: u32,
pub high_pass: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct TileEdgeFlags(u8);
impl TileEdgeFlags {
pub const LEFT: Self = Self(1 << 0);
pub const TOP: Self = Self(1 << 1);
pub const RIGHT: Self = Self(1 << 2);
pub const BOTTOM: Self = Self(1 << 3);
pub const HARD_TILE: Self = Self(1 << 4);
#[must_use]
pub const fn from_bits(bits: u8) -> Option<Self> {
if bits & !0x1f == 0 {
Some(Self(bits))
} else {
None
}
}
#[must_use]
pub const fn bits(self) -> u8 {
self.0
}
#[must_use]
pub const fn contains(self, flag: Self) -> bool {
self.0 & flag.0 == flag.0
}
#[must_use]
pub const fn union(self, flag: Self) -> Self {
Self(self.0 | flag.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct MacroblockMetadata {
pub coefficient_offsets: Vec<u32>,
pub quantizers: Vec<QuantizerSet>,
pub bands: Vec<BandPresence>,
pub predictions: Vec<PredictionMode>,
pub hp_predictions: Vec<PredictionMode>,
pub tile_edges: Vec<TileEdgeFlags>,
pub coded_x: Vec<u32>,
pub coded_y: Vec<u32>,
pub output_x: Vec<u32>,
pub output_y: Vec<u32>,
}
impl MacroblockMetadata {
#[must_use]
pub fn len(&self) -> usize {
self.coefficient_offsets.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.coefficient_offsets.is_empty()
}
pub fn validate(&self, coefficient_count: usize) -> Result<(), JxrError> {
let expected = self.len();
let lengths = [
self.quantizers.len(),
self.bands.len(),
self.predictions.len(),
self.hp_predictions.len(),
self.tile_edges.len(),
self.coded_x.len(),
self.coded_y.len(),
self.output_x.len(),
self.output_y.len(),
];
if lengths.iter().any(|&length| length != expected) {
return Err(JxrError::new(
JxrErrorKind::InternalInvariant,
"macroblock metadata lengths",
));
}
if self.coefficient_offsets.iter().any(|&offset| {
usize::try_from(offset).map_or(true, |offset| offset > coefficient_count)
}) {
return Err(JxrError::new(
JxrErrorKind::InvalidSyntax,
"coefficient offset",
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CoefficientPlane {
pub coefficient_offset: usize,
pub coefficient_count: usize,
pub macroblock_offset: usize,
pub macroblock_count: usize,
pub block_columns: u8,
pub block_rows: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CoefficientArena {
pub coefficients: Vec<i32>,
pub macroblocks: MacroblockMetadata,
pub planes: Vec<CoefficientPlane>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct CoefficientArenaDescriptor {
pub coefficient_count: usize,
pub macroblocks: MacroblockMetadata,
pub planes: Vec<CoefficientPlane>,
}
impl CoefficientArenaDescriptor {
pub fn validate(&self) -> Result<(), JxrError> {
validate_coefficient_contract(self.coefficient_count, &self.macroblocks, &self.planes)
}
}
impl CoefficientArena {
pub fn validate(&self) -> Result<(), JxrError> {
validate_coefficient_contract(self.coefficients.len(), &self.macroblocks, &self.planes)
}
}
fn validate_coefficient_contract(
coefficient_count: usize,
macroblocks: &MacroblockMetadata,
planes: &[CoefficientPlane],
) -> Result<(), JxrError> {
macroblocks.validate(coefficient_count)?;
for plane in planes {
if !(1..=4).contains(&plane.block_columns) || !(1..=4).contains(&plane.block_rows) {
return Err(JxrError::new(
JxrErrorKind::InternalInvariant,
"coefficient plane block geometry",
));
}
check_range(
plane.coefficient_offset,
plane.coefficient_count,
coefficient_count,
"coefficient plane range",
)?;
check_range(
plane.macroblock_offset,
plane.macroblock_count,
macroblocks.len(),
"macroblock plane range",
)?;
}
Ok(())
}
fn check_range(
offset: usize,
length: usize,
available: usize,
operation: &'static str,
) -> Result<(), JxrError> {
let end = offset
.checked_add(length)
.ok_or_else(|| JxrError::arithmetic(operation))?;
if end > available {
Err(JxrError::new(JxrErrorKind::InternalInvariant, operation))
} else {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlanePlan {
pub width: u32,
pub height: u32,
pub macroblocks_x: u32,
pub macroblocks_y: u32,
pub overlap: OverlapMode,
pub coefficient_plane: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TilePlan {
pub packet_range: ByteRange,
pub output_region: Rect,
pub macroblock_start: u32,
pub macroblock_count: u32,
pub hard_boundaries: bool,
pub required_for_reconstruction: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PreparedPlan {
pub info: ImageInfo,
pub codestream_range: ByteRange,
pub primary: PlanePlan,
pub alpha: Option<PlanePlan>,
pub tiles: Vec<TilePlan>,
pub reconstruction_region: Rect,
pub output_region: Rect,
pub decoded_region: Rect,
pub scale: DecodeScale,
pub coefficient_bytes: usize,
}
impl PreparedPlan {
pub fn reconstructed_coefficients(&self) -> Result<u64, JxrError> {
let macroblocks = self
.tiles
.iter()
.filter(|tile| tile.required_for_reconstruction)
.try_fold(0_u64, |total, tile| {
total
.checked_add(u64::from(tile.macroblock_count))
.ok_or_else(|| JxrError::arithmetic("reconstruction macroblock count"))
})?;
let primary = match self.info.primary.color_format {
ColorFormat::Luma => 256,
ColorFormat::Yuv(ChromaSampling::Cs420) => 384,
ColorFormat::Yuv(ChromaSampling::Cs422) => 512,
ColorFormat::Yuv(ChromaSampling::Cs444) | ColorFormat::Rgb | ColorFormat::Rgbe => 768,
ColorFormat::Cmyk | ColorFormat::CmykDirect | ColorFormat::YuvK => 1_024,
ColorFormat::NComponent(components) => u64::from(components)
.checked_mul(256)
.ok_or_else(|| JxrError::arithmetic("N-component reconstruction work"))?,
};
let alpha = match self.info.alpha_mode {
AlphaMode::Integrated => 256,
AlphaMode::Separate if self.alpha.is_some() => 256,
AlphaMode::None | AlphaMode::Separate => 0,
};
macroblocks
.checked_mul(primary + alpha)
.ok_or_else(|| JxrError::arithmetic("reconstructed coefficient count"))
}
}