299 lines
9.3 KiB
Rust
299 lines
9.3 KiB
Rust
//! 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<T>(pub PhantomData<T>);
|
|
|
|
/// 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<i32>,
|
|
}
|
|
|
|
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<i32>) -> Result<Self, Error> {
|
|
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<InterleavedComponent>,
|
|
}
|
|
|
|
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<InterleavedComponent>,
|
|
) -> Result<Self, Error> {
|
|
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<u8, Error>;
|
|
/// Fixed-point fractional bits for one component.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns [`Error::IndexOutOfRange`] for an invalid component index.
|
|
fn fixed_point(&self, component_index: usize) -> Result<u8, Error>;
|
|
/// 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<bool, Error>;
|
|
/// 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<Vec<i32>, Error>;
|
|
}
|
|
|
|
fn checked_pixels(width: i32, height: i32) -> Result<usize, Error> {
|
|
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)
|
|
);
|
|
}
|
|
}
|