//! Project-owned boundaries for JPEG 2000 codec types exposed by the C# API. use crate::{DEFAULT_MAX_PIXELS, Error, ManagedImage}; use std::any::Any; use std::fmt::Debug; use std::marker::PhantomData; /// A type-erased decoded image returned by an image creator. pub trait IImage: Any + Debug + Send + Sync { /// Returns the concrete image wrapper for checked downcasting. fn as_any(&self) -> &dyn Any; /// Returns the already-created managed image when this is a compatible /// decode target. fn as_managed_image(&self) -> Option<&ManagedImage> { None } } /// Marker corresponding to `CoreJ2K`'s image-creator boundary. pub trait IImageCreator: Send + Sync {} /// Typed marker corresponding to `CoreJ2K`'s generic image creator. #[derive(Clone, Copy, Debug, Default)] pub struct ImageCreator(pub PhantomData); /// One decoded JPEG 2000 component at its original integer precision. #[derive(Clone, Debug, Eq, PartialEq)] pub struct InterleavedComponent { precision: u8, signed: bool, alpha: bool, samples: Vec, } impl InterleavedComponent { /// Creates a checked component without reducing its sample precision. /// /// # Errors /// /// Returns [`Error::Argument`] for precision outside 1 through 31 bits or /// for samples outside the declared signed or unsigned range. pub fn new(precision: u8, signed: bool, alpha: bool, samples: Vec) -> Result { if !(1..=31).contains(&precision) { return Err(Error::Argument); } let (minimum, maximum) = component_range(precision, signed); if samples .iter() .any(|sample| i64::from(*sample) < minimum || i64::from(*sample) > maximum) { return Err(Error::Argument); } Ok(Self { precision, signed, alpha, samples, }) } /// Declared component precision in bits. #[must_use] pub const fn precision(&self) -> u8 { self.precision } /// Whether component samples use signed representation. #[must_use] pub const fn is_signed(&self) -> bool { self.signed } /// Whether the container marks this component as alpha. #[must_use] pub const fn is_alpha(&self) -> bool { self.alpha } /// Original integer samples, in top-left row-major order. #[must_use] pub fn samples(&self) -> &[i32] { &self.samples } } /// A bounded decoded image retaining component precision and order. /// /// The historical external type calls this representation interleaved, while /// its public conversion API is component-oriented. Keeping the components in /// separate owned planes avoids a second full-image allocation and retains the /// original JPEG 2000 integer samples until byte conversion is requested. #[derive(Clone, Debug, Eq, PartialEq)] pub struct InterleavedImage { width: i32, height: i32, components: Vec, } impl InterleavedImage { /// Creates a checked decoded component image. /// /// # Errors /// /// Returns [`Error::Argument`] for invalid/over-limit dimensions, zero or /// more than five components, or a plane whose size differs from /// `width * height`. pub fn new( width: i32, height: i32, components: Vec, ) -> Result { let pixels = checked_pixels(width, height)?; if components.is_empty() || components.len() > 5 || components .iter() .any(|component| component.samples.len() != pixels) { return Err(Error::Argument); } pixels .checked_mul(components.len()) .filter(|samples| *samples <= DEFAULT_MAX_PIXELS * 5) .ok_or(Error::Argument)?; Ok(Self { width, height, components, }) } /// Decoded width after any discard-level reduction. #[must_use] pub const fn width(&self) -> i32 { self.width } /// Decoded height after any discard-level reduction. #[must_use] pub const fn height(&self) -> i32 { self.height } /// Number of decoded components in codestream order. #[must_use] pub fn number_of_components(&self) -> usize { self.components.len() } /// Component metadata and original samples. #[must_use] pub fn components(&self) -> &[InterleavedComponent] { &self.components } /// Scales one component into an 8-bit plane using CoreJ2K-compatible full /// range conversion. /// /// # Errors /// /// Returns [`Error::IndexOutOfRange`] for an invalid component index and /// [`Error::Argument`] when `destination` is not exactly one image plane. pub fn to_component_bytes( &self, component_index: usize, destination: &mut [u8], ) -> Result<(), Error> { let component = self .components .get(component_index) .ok_or(Error::IndexOutOfRange)?; if destination.len() != component.samples.len() { return Err(Error::Argument); } for (destination, sample) in destination.iter_mut().zip(&component.samples) { *destination = scale_sample(*sample, component.precision, component.signed); } Ok(()) } } /// Encode-side sample source corresponding to `CoreJ2K`'s block image source. pub trait BlkImgDataSrc: Debug + Send + Sync { /// Image width. fn width(&self) -> i32; /// Image height. fn height(&self) -> i32; /// Number of presented components. fn number_of_components(&self) -> usize; /// Nominal range bits for one component. /// /// # Errors /// /// Returns [`Error::IndexOutOfRange`] for an invalid component index. fn nominal_range_bits(&self, component_index: usize) -> Result; /// Fixed-point fractional bits for one component. /// /// # Errors /// /// Returns [`Error::IndexOutOfRange`] for an invalid component index. fn fixed_point(&self, component_index: usize) -> Result; /// Whether one presented component is signed at the source. /// /// # Errors /// /// Returns [`Error::IndexOutOfRange`] for an invalid component index. fn is_original_signed(&self, component_index: usize) -> Result; /// Returns a checked top-left row-major rectangle with the source DC offset /// applied, exactly as `CoreJ2K` expects. /// /// # Errors /// /// Returns a typed index or argument error for an invalid component or /// rectangle. fn component_block( &self, component_index: usize, x: i32, y: i32, width: i32, height: i32, ) -> Result, Error>; } fn checked_pixels(width: i32, height: i32) -> Result { let width = usize::try_from(width).map_err(|_| Error::Argument)?; let height = usize::try_from(height).map_err(|_| Error::Argument)?; if width == 0 || height == 0 { return Err(Error::Argument); } width .checked_mul(height) .filter(|pixels| *pixels <= DEFAULT_MAX_PIXELS) .ok_or(Error::Argument) } fn component_range(precision: u8, signed: bool) -> (i64, i64) { if signed { let magnitude = 1_i64 << (precision - 1); (-magnitude, magnitude - 1) } else { (0, (1_i64 << precision) - 1) } } fn scale_sample(sample: i32, precision: u8, signed: bool) -> u8 { if signed { let old_max = 1_i64 << (precision - 1); let scaled = (i64::from(sample) * 128) / old_max + 128; u8::try_from(scaled.clamp(0, 255)).unwrap_or_default() } else { let old_max = (1_u64 << precision) - 1; let scaled = (u64::try_from(sample).unwrap_or_default() * 255) / old_max; u8::try_from(scaled).unwrap_or(u8::MAX) } } #[cfg(test)] mod tests { use super::*; #[test] fn component_conversion_preserves_precision_and_signed_range() { let unsigned = InterleavedComponent::new(16, false, false, vec![0, 32_768, 65_535]) .expect("16-bit component"); let signed = InterleavedComponent::new(8, true, false, vec![-128, 0, 127]) .expect("signed component"); let unsigned = InterleavedImage::new(3, 1, vec![unsigned]).expect("unsigned image"); let signed = InterleavedImage::new(3, 1, vec![signed]).expect("signed image"); let mut bytes = [0; 3]; unsigned .to_component_bytes(0, &mut bytes) .expect("unsigned bytes"); assert_eq!(bytes, [0, 127, 255]); signed .to_component_bytes(0, &mut bytes) .expect("signed bytes"); assert_eq!(bytes, [0, 128, 255]); } #[test] fn interleaved_image_rejects_bad_layouts_and_ranges() { assert_eq!( InterleavedComponent::new(0, false, false, vec![]), Err(Error::Argument) ); assert_eq!( InterleavedComponent::new(8, false, false, vec![256]), Err(Error::Argument) ); let component = InterleavedComponent::new(8, false, false, vec![0]).unwrap(); assert_eq!( InterleavedImage::new(2, 1, vec![component]), Err(Error::Argument) ); } }