use crate::{
backend::{
image as backend_image, inspect_info, inspect_info_from_image, DecodeSettings, Image,
},
decode::{
decode_image_into_with_native_context, decode_image_region_into_with_native_context,
decode_warnings_for_settings, validate_buffer, validate_region, J2kDecodeOutcome,
J2kDecodedComponents, J2kDecodedNativeComponents,
},
parse::{parse_image_info, parse_info},
scratch::J2kScratchPool,
CpuDecodeParallelism, J2kError, J2kSupportInfo,
};
use j2k_core::{
CompressedPayloadKind, CompressedTransferSyntax, Downscale, Info, PassthroughCandidate,
PixelFormat, Rect,
};
mod rows;
mod traits;
pub struct J2kView<'a> {
bytes: &'a [u8],
info: Info,
support_info: Option<J2kSupportInfo>,
image: Option<Image<'a>>,
passthrough: Option<(CompressedTransferSyntax, CompressedPayloadKind)>,
}
impl<'a> J2kView<'a> {
pub fn parse(input: &'a [u8]) -> Result<Self, J2kError> {
let (info, support_info, passthrough) = match parse_image_info(input) {
Ok(parsed) => {
let support_info = parsed.into_support_info();
let passthrough = Some((support_info.transfer_syntax, support_info.payload_kind));
(support_info.info.clone(), Some(support_info), passthrough)
}
Err(error) if should_retry_with_backend(&error) => (inspect_info(input)?, None, None),
Err(error) => return Err(error),
};
let image = Some(backend_image(input, DecodeSettings::default())?);
Ok(Self {
bytes: input,
info,
support_info,
image,
passthrough,
})
}
pub fn info(&self) -> &Info {
&self.info
}
pub fn support_info(&self) -> Option<&J2kSupportInfo> {
self.support_info.as_ref()
}
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
pub fn passthrough_candidate(&self) -> Option<PassthroughCandidate<'a>> {
self.passthrough.map(|(transfer_syntax, payload_kind)| {
PassthroughCandidate::new(self.bytes, transfer_syntax, payload_kind, self.info.clone())
})
}
}
pub struct J2kDecoder<'a> {
bytes: &'a [u8],
info: Info,
image: Option<Image<'a>>,
native_context: j2k_native::DecoderContext<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct J2kRowDecodeOptions {
max_rows_per_stripe: u32,
max_stripe_bytes: usize,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct J2kCodec;
impl<'a> J2kDecoder<'a> {
pub fn inspect(input: &'a [u8]) -> Result<Info, J2kError> {
match parse_info(input) {
Ok(info) => Ok(info),
Err(error) if should_retry_with_backend(&error) => inspect_info(input),
Err(error) => Err(error),
}
}
pub fn inspect_support(input: &'a [u8]) -> Result<J2kSupportInfo, J2kError> {
parse_image_info(input).map(crate::parse::ParsedImageInfo::into_support_info)
}
pub fn new(input: &'a [u8]) -> Result<Self, J2kError> {
Self::from_view(J2kView::parse(input)?)
}
pub fn from_view(view: J2kView<'a>) -> Result<Self, J2kError> {
Ok(Self {
bytes: view.bytes,
info: view.info,
image: view.image,
native_context: j2k_native::DecoderContext::default(),
})
}
pub fn info(&self) -> &Info {
&self.info
}
pub fn decode_components(&mut self) -> Result<J2kDecodedComponents<'_>, J2kError> {
self.ensure_image()?;
let (Some(image), native_context) = (self.image.as_ref(), &mut self.native_context) else {
return Err(J2kError::internal_backend("internal image cache missing"));
};
let retained_image_bytes = component_handoff_image_bytes(image)?;
let decoded = image
.decode_components_with_context(native_context)
.map_err(J2kError::from_native_decode_error)?;
J2kDecodedComponents::try_from_native(decoded, retained_image_bytes)
}
pub fn decode_region_components(
&mut self,
roi: Rect,
) -> Result<J2kDecodedComponents<'_>, J2kError> {
validate_region(roi, self.info.dimensions)?;
self.ensure_image()?;
let (Some(image), native_context) = (self.image.as_ref(), &mut self.native_context) else {
return Err(J2kError::internal_backend("internal image cache missing"));
};
let retained_image_bytes = component_handoff_image_bytes(image)?;
let decoded = image
.decode_region_components_with_context((roi.x, roi.y, roi.w, roi.h), native_context)
.map_err(J2kError::from_native_decode_error)?;
J2kDecodedComponents::try_from_native(decoded, retained_image_bytes)
}
pub fn decode_native_components(&mut self) -> Result<J2kDecodedNativeComponents, J2kError> {
self.ensure_image()?;
let (Some(image), native_context) = (self.image.as_ref(), &mut self.native_context) else {
return Err(J2kError::internal_backend("internal image cache missing"));
};
let retained_image_bytes = component_handoff_image_bytes(image)?;
let decoded = image
.decode_native_components_with_context(native_context)
.map_err(J2kError::from_native_decode_error)?;
J2kDecodedNativeComponents::try_from_native(decoded, retained_image_bytes)
}
pub fn decode_native_region_components(
&mut self,
roi: Rect,
) -> Result<J2kDecodedNativeComponents, J2kError> {
validate_region(roi, self.info.dimensions)?;
self.ensure_image()?;
let (Some(image), native_context) = (self.image.as_ref(), &mut self.native_context) else {
return Err(J2kError::internal_backend("internal image cache missing"));
};
let retained_image_bytes = component_handoff_image_bytes(image)?;
let decoded = image
.decode_native_region_components_with_context(
(roi.x, roi.y, roi.w, roi.h),
native_context,
)
.map_err(J2kError::from_native_decode_error)?;
J2kDecodedNativeComponents::try_from_native(decoded, retained_image_bytes)
}
pub fn cpu_decode_parallelism(&self) -> CpuDecodeParallelism {
CpuDecodeParallelism::from_native(self.native_context.cpu_decode_parallelism())
}
pub fn set_cpu_decode_parallelism(&mut self, parallelism: CpuDecodeParallelism) {
self.native_context
.set_cpu_decode_parallelism(parallelism.to_native());
}
pub fn decode_into(
&mut self,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
) -> Result<J2kDecodeOutcome, J2kError> {
self.decode_into_cached(out, stride, fmt)
}
pub fn decode_into_with_scratch(
&mut self,
_pool: &mut J2kScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
) -> Result<J2kDecodeOutcome, J2kError> {
self.decode_into_cached(out, stride, fmt)
}
fn decode_into_cached(
&mut self,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
) -> Result<J2kDecodeOutcome, J2kError> {
validate_buffer(self.info.dimensions, out.len(), stride, fmt)?;
self.ensure_image()?;
let (Some(image), native_context) = (self.image.as_ref(), &mut self.native_context) else {
return Err(J2kError::internal_backend("internal image cache missing"));
};
decode_image_into_with_native_context(image, native_context, out, stride, fmt)?;
Ok(j2k_core::DecodeOutcome::new(
Rect::full(self.info.dimensions),
decode_warnings_for_settings(DecodeSettings::default()),
))
}
pub fn decode_region_into(
&mut self,
_pool: &mut J2kScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
roi: Rect,
) -> Result<J2kDecodeOutcome, J2kError> {
self.decode_region_into_cached(out, stride, fmt, roi)
}
fn decode_region_into_cached(
&mut self,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
roi: Rect,
) -> Result<J2kDecodeOutcome, J2kError> {
validate_region(roi, self.info.dimensions)?;
validate_buffer((roi.w, roi.h), out.len(), stride, fmt)?;
self.ensure_image()?;
let (Some(image), native_context) = (self.image.as_ref(), &mut self.native_context) else {
return Err(J2kError::internal_backend("internal image cache missing"));
};
decode_image_region_into_with_native_context(image, native_context, out, stride, fmt, roi)?;
Ok(j2k_core::DecodeOutcome::new(
roi,
decode_warnings_for_settings(DecodeSettings::default()),
))
}
pub fn decode_scaled_into(
&mut self,
pool: &mut J2kScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
scale: Downscale,
) -> Result<J2kDecodeOutcome, J2kError> {
if scale == Downscale::None {
return self.decode_into_with_scratch(pool, out, stride, fmt);
}
let settings = DecodeSettings {
target_resolution: Some(self.scaled_target_dims(scale)),
..DecodeSettings::default()
};
let warnings = decode_warnings_for_settings(settings);
let image = backend_image(self.bytes, settings)?;
let image_dims = (image.width(), image.height());
validate_buffer(image_dims, out.len(), stride, fmt)?;
let mut native_context = self.scaled_decode_native_context();
decode_image_into_with_native_context(&image, &mut native_context, out, stride, fmt)?;
Ok(j2k_core::DecodeOutcome::new(
Rect::full(image_dims),
warnings,
))
}
pub fn decode_region_scaled_into(
&mut self,
pool: &mut J2kScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
roi: Rect,
scale: Downscale,
) -> Result<J2kDecodeOutcome, J2kError> {
if scale == Downscale::None {
return self.decode_region_into(pool, out, stride, fmt, roi);
}
validate_region(roi, self.info.dimensions)?;
let scaled_roi = roi.scaled_covering(scale);
validate_buffer((scaled_roi.w, scaled_roi.h), out.len(), stride, fmt)?;
let settings = DecodeSettings {
target_resolution: Some(self.scaled_target_dims(scale)),
..DecodeSettings::default()
};
let warnings = decode_warnings_for_settings(settings);
let image = backend_image(self.bytes, settings)?;
let image_dims = (image.width(), image.height());
validate_region(scaled_roi, image_dims)?;
let mut native_context = self.scaled_decode_native_context();
decode_image_region_into_with_native_context(
&image,
&mut native_context,
out,
stride,
fmt,
scaled_roi,
)?;
Ok(j2k_core::DecodeOutcome::new(scaled_roi, warnings))
}
fn ensure_image(&mut self) -> Result<(), J2kError> {
if self.image.is_none() {
self.image = Some(backend_image(self.bytes, DecodeSettings::default())?);
if self.info.tile_layout.is_none() {
self.info = inspect_info_from_image(self.cached_image()?);
}
}
Ok(())
}
fn cached_image(&self) -> Result<&Image<'a>, J2kError> {
self.image
.as_ref()
.ok_or_else(|| J2kError::internal_backend("internal image cache missing"))
}
fn scaled_target_dims(&self, scale: Downscale) -> (u32, u32) {
(
self.info.dimensions.0.div_ceil(scale.denominator()),
self.info.dimensions.1.div_ceil(scale.denominator()),
)
}
fn scaled_decode_native_context(&self) -> j2k_native::DecoderContext<'a> {
let mut native_context = j2k_native::DecoderContext::default();
native_context.set_cpu_decode_parallelism(self.native_context.cpu_decode_parallelism());
native_context
}
}
fn component_handoff_image_bytes(image: &Image<'_>) -> Result<usize, J2kError> {
image
.retained_allocation_bytes()
.map_err(J2kError::from_native_decode_error)
}
fn should_retry_with_backend(error: &J2kError) -> bool {
matches!(
error,
J2kError::InvalidMarker {
marker: 0x50
| 0x53
| 0x55
| 0x57
| 0x58
| 0x59
| 0x5C
| 0x5D
| 0x5E
| 0x5F
| 0x60
| 0x61
| 0x63
| 0x64
| 0x91
| 0x92,
..
}
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scaled_decode_native_context_preserves_configured_parallelism() {
let mut decoder = J2kDecoder {
bytes: &[],
info: Info {
dimensions: (1, 1),
components: 1,
colorspace: j2k_core::Colorspace::SGray,
bit_depth: 8,
tile_layout: None,
coded_unit_layout: None,
restart_interval: None,
resolution_levels: 1,
},
image: None,
native_context: j2k_native::DecoderContext::default(),
};
decoder.set_cpu_decode_parallelism(CpuDecodeParallelism::Serial);
let native_context = decoder.scaled_decode_native_context();
assert_eq!(
native_context.cpu_decode_parallelism(),
CpuDecodeParallelism::Serial.to_native()
);
}
}