Skip to main content

j2k_native/
lib.rs

1/*!
2Internal pure-Rust JPEG 2000 codec engine for `j2k`.
3
4This module tree was imported from the `dicom-toolkit-jpeg2000` 0.5.0 crate and
5adapted in-repo so `j2k` no longer depends on an external production decoder crate.
6
7`dicom-toolkit-jpeg2000` is the JPEG 2000 engine used by `dicom-toolkit-rs`.
8It is a maintained fork of the original `hayro-jpeg2000` project with
9DICOM-focused extensions, including native-bit-depth decode for 8/12/16-bit
10images and pure-Rust JPEG 2000 encoding.
11
12The crate can decode raw JPEG 2000 codestreams (`.j2c`) and still-image JP2/JPH
13wrappers. It implements the JPEG 2000 core coding system (ISO/IEC 15444-1) and
14HTJ2K block coding (ISO/IEC 15444-15) through the support boundary documented in
15`docs/public-support.md`. The remaining declared gaps are tracked there.
16
17The crate offers both a high-level 8-bit decode path for general image use and
18a native-bit-depth decode path for integrations such as DICOM, plus encoder APIs
19for emitting raw JPEG 2000 and HTJ2K codestreams.
20
21# Example
22```rust,no_run
23use j2k_native::{DecodeSettings, Image};
24
25let data = std::fs::read("image.jp2").unwrap();
26let image = Image::new(&data, &DecodeSettings::default()).unwrap();
27
28println!(
29    "{}x{} image in {:?} with alpha={}",
30    image.width(),
31    image.height(),
32    image.color_space(),
33    image.has_alpha(),
34);
35
36let bitmap = image.decode().unwrap();
37```
38
39If you want to see a more comprehensive example, please take a look
40at the example in [GitHub](https://github.com/knopkem/dicom-toolkit-rs/blob/main/crates/dicom-toolkit-jpeg2000/examples/png.rs),
41which shows the main steps needed to convert a JPEG 2000 image into PNG.
42
43# Testing
44The decoder has been tested against 20.000+ images scraped from random PDFs
45on the internet and also passes a large part of the `OpenJPEG` test suite. So you
46can expect the crate to perform decently in terms of decoding correctness.
47
48# Performance
49A decent amount of effort has already been put into optimizing this crate
50(both raw throughput and memory allocations), with remaining optimization work planned.
51
52Overall, you should expect this crate to have worse performance than `OpenJPEG`,
53but the difference gap should not be too large.
54
55# Safety
56By default, the crate has the `simd` feature enabled, which uses the
57[`fearless_simd`](https://github.com/linebender/fearless_simd) crate to accelerate
58important parts of the pipeline. If you want to eliminate any usage of unsafe
59in this crate as well as its dependencies, you can simply disable this
60feature, at the cost of worse decoding performance. Unsafe code is forbidden
61via a crate-level attribute.
62
63The crate is `no_std` compatible but requires an allocator to be available.
64*/
65
66#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
67#![forbid(unsafe_code)]
68#![forbid(missing_docs)]
69extern crate alloc;
70
71#[cfg(test)]
72use alloc::vec;
73use alloc::vec::Vec;
74
75use crate::error::bail;
76#[cfg(test)]
77use crate::jp2::colr::CieLab;
78
79macro_rules! define_ht_code_block_job {
80    (
81        $(#[$meta:meta])*
82        pub struct $name:ident $(<$lt:lifetime>)? {
83            $($prefix:tt)*
84        }
85    ) => {
86        $(#[$meta])*
87        pub struct $name $(<$lt>)? {
88            $($prefix)*
89            /// Cleanup segment length in bytes.
90            pub cleanup_length: u32,
91            /// Refinement segment length in bytes.
92            pub refinement_length: u32,
93            /// Code-block width in samples.
94            pub width: u32,
95            /// Code-block height in samples.
96            pub height: u32,
97            /// Output row stride, in samples, for the target sub-band storage.
98            pub output_stride: usize,
99            /// Missing most-significant bit planes for this code block.
100            pub missing_bit_planes: u8,
101            /// Number of coding passes present for this code block.
102            pub number_of_coding_passes: u8,
103            /// Total coded bitplanes for the parent sub-band.
104            pub num_bitplanes: u8,
105            /// Region-of-interest maxshift value from RGN marker metadata.
106            pub roi_shift: u8,
107            /// Whether vertically causal context was enabled.
108            pub stripe_causal: bool,
109            /// Whether strict decode validation is enabled for the parent image.
110            pub strict: bool,
111            /// Dequantization step to apply to decoded coefficients.
112            pub dequantization_step: f32,
113        }
114    };
115}
116
117#[doc(hidden)]
118#[macro_export]
119macro_rules! __j2k_component_plane_metadata_accessors {
120    () => {
121        /// Width and height of this decoded plane in output samples.
122        #[must_use]
123        pub fn dimensions(&self) -> (u32, u32) {
124            self.dimensions
125        }
126
127        /// Horizontal and vertical SIZ sampling factors (`XRsiz`, `YRsiz`).
128        #[must_use]
129        pub fn sampling(&self) -> (u8, u8) {
130            self.sampling
131        }
132
133        /// Bit depth of this component plane.
134        #[must_use]
135        pub fn bit_depth(&self) -> u8 {
136            self.bit_depth
137        }
138
139        /// Whether this component plane stores signed sample values.
140        #[must_use]
141        pub fn signed(&self) -> bool {
142            self.signed
143        }
144    };
145}
146mod backend;
147mod color;
148mod error;
149mod ht_adapter;
150mod inspect;
151#[macro_use]
152pub(crate) mod log;
153mod direct_cpu;
154mod direct_plan;
155mod direct_roi;
156pub(crate) mod math;
157mod move_only;
158#[doc(hidden)]
159pub mod packet_math;
160pub(crate) mod profile;
161mod roi;
162pub(crate) mod writer;
163#[cfg(test)]
164use crate::math::{dispatch, Level};
165#[cfg(test)]
166use color::cielab_to_rgb;
167pub(crate) use color::{
168    convert_color_space, interleave_and_convert_after_mct, interleave_and_convert_region,
169    resolve_alpha_and_color_space, resolve_palette_indices, validate_and_reorder_channels,
170    validate_interleaved_output_buffer,
171};
172pub use color::{
173    Bitmap, ColorSpace, ComponentPlane, DecodedComponents, DecodedNativeComponents,
174    NativeComponentPlane, RawBitmap,
175};
176#[doc(hidden)]
177pub use color::{ComponentPlaneParts, ComponentRowSink, NativeComponentPlaneParts};
178#[doc(hidden)]
179pub use color::{DecodedComponentsParts, DecodedNativeComponentsParts};
180#[doc(hidden)]
181pub use direct_cpu::{
182    execute_direct_color_plan_rgb8_into, execute_direct_color_plan_rgba8_into,
183    execute_referenced_classic_entropy_job, execute_referenced_classic_plan,
184    execute_referenced_classic_plan_from_payloads, execute_referenced_htj2k_entropy_job,
185    execute_referenced_htj2k_plan, execute_referenced_htj2k_plan_from_payloads,
186    finish_referenced_classic_staged, finish_referenced_classic_tile_staged,
187    finish_referenced_htj2k_staged, finish_referenced_htj2k_tile_staged,
188    prepare_referenced_classic_entropy_workspace, prepare_referenced_classic_staged,
189    prepare_referenced_classic_tile_staged, prepare_referenced_htj2k_entropy_workspace,
190    prepare_referenced_htj2k_staged, prepare_referenced_htj2k_tile_staged, J2kDirectCodeBlockIndex,
191    J2kDirectCpuEntropyWorkspace, J2kDirectCpuScratch, J2kDirectDecodedComponents,
192    J2kDirectDecodedPlane,
193};
194#[doc(hidden)]
195pub use direct_plan::{
196    HtCodeBlockPayloadRanges, HtOwnedCodeBlockBatchJob, HtOwnedSubBandPlan,
197    J2kClassicCodeBlockPayload, J2kCodestreamRange, J2kDirectBandId, J2kDirectColorPlan,
198    J2kDirectGrayscalePlan, J2kDirectGrayscaleStep, J2kDirectIdwtStep, J2kDirectRgbaPlan,
199    J2kDirectStoreStep, J2kOwnedCodeBlockBatchJob, J2kOwnedSubBandPlan, J2kReferencedClassicPlan,
200    J2kReferencedHtj2kPlan, J2kReferencedImageGeometry, J2kReferencedPayloadRecordSpan,
201    J2kReferencedTileGeometry, J2kReferencedTilePlan,
202};
203#[doc(hidden)]
204pub use direct_roi::{
205    idwt_required_input_window_for_rects, idwt_required_input_windows, idwt_required_output_margin,
206    window_referenced_classic_plan_to_stores, window_referenced_htj2k_plan_to_stores,
207    J2kIdwtRequiredInputWindows, J2kRequiredBandRegion,
208};
209pub use inspect::{
210    inspect_htj2k_capabilities, inspect_j2k_codestream_header, looks_like_j2k_codestream,
211    J2kCodestreamComponentHeader, J2kCodestreamHeaderError, J2kCodestreamHeaderMetadata,
212};
213#[doc(hidden)]
214pub use j2c::capabilities::required_magnitude_bound as htj2k_required_magnitude_bound;
215pub use j2c::capabilities::{Htj2kCapabilities, Htj2kCapabilityMode, J2kCorrespondingProfile};
216#[doc(hidden)]
217pub use jp2::{
218    extract_jp2_codestream_payload, inspect_jp2_container, Jp2ChannelAssociation,
219    Jp2ChannelDefinition, Jp2ChannelType, Jp2ColorSpec, Jp2ComponentMapping,
220    Jp2ComponentMappingType, Jp2ComponentMetadata, Jp2Container, Jp2FileKind, Jp2FileMetadata,
221    Jp2ImageHeaderMetadata, Jp2PaletteColumn, Jp2PaletteMetadata,
222};
223#[doc(hidden)]
224pub use roi::idwt_band_index;
225pub(crate) use roi::{add_roi_shift_to_bitplanes, apply_roi_maxshift_inverse_i64, validate_roi};
226#[cfg(test)]
227pub(crate) use roi::{apply_roi_maxshift_inverse_f32, apply_roi_maxshift_inverse_i32};
228
229pub use error::{
230    ColorError, DecodeError, DecodeErrorClass, DecodingError, DirectPlanUnsupportedReason,
231    EncodeError, EncodeResult, FormatError, MarkerError, Result, TileError, ValidationError,
232};
233#[cfg(test)]
234pub(crate) use j2c::encode::NativeEncodeRetainedInput;
235pub use j2c::encode::{
236    encode, encode_component_planes_53, encode_htj2k, encode_htj2k_with_qfactor,
237    encode_htj2k_with_qfactor_and_accelerator, encode_precomputed_htj2k_53,
238    encode_precomputed_htj2k_53_with_accelerator,
239    encode_precomputed_htj2k_53_with_accelerator_and_max_host_bytes,
240    encode_precomputed_htj2k_53_with_mct, encode_precomputed_htj2k_53_with_mct_and_accelerator,
241    encode_precomputed_htj2k_97, encode_precomputed_htj2k_97_batch_owned_with_accelerator,
242    encode_precomputed_htj2k_97_batch_owned_with_accelerator_and_max_host_bytes,
243    encode_precomputed_htj2k_97_batch_with_accelerator,
244    encode_precomputed_htj2k_97_with_accelerator,
245    encode_precomputed_htj2k_97_with_accelerator_and_max_host_bytes, encode_precomputed_j2k_53,
246    encode_precomputed_j2k_53_with_accelerator, encode_precomputed_j2k_53_with_mct,
247    encode_precomputed_j2k_53_with_mct_and_accelerator, encode_preencoded_htj2k_97,
248    encode_preencoded_htj2k_97_compact_owned_with_accelerator,
249    encode_preencoded_htj2k_97_compact_owned_with_accelerator_and_max_host_bytes,
250    encode_preencoded_htj2k_97_compact_owned_with_accelerator_and_max_host_bytes_and_required_magnitude_bound,
251    encode_preencoded_htj2k_97_owned_with_accelerator,
252    encode_preencoded_htj2k_97_owned_with_accelerator_and_max_host_bytes,
253    encode_preencoded_htj2k_97_owned_with_accelerator_and_max_host_bytes_and_required_magnitude_bound,
254    encode_preencoded_htj2k_97_with_accelerator, encode_prequantized_htj2k_97,
255    encode_prequantized_htj2k_97_with_accelerator,
256    encode_prequantized_htj2k_97_with_accelerator_and_max_host_bytes,
257    encode_resident_htj2k_with_accelerator, encode_typed_component_planes_53,
258    encode_with_accelerator, encode_with_accelerator_and_roi_regions, encode_with_roi_regions,
259    irreversible_quantization_profile_scale_ceiling, irreversible_quantization_scale_bounds,
260    irreversible_quantization_step_for_subband, EncodeComponentPlane, EncodeOptions,
261    EncodeProgressionOrder, EncodeRoiRegion, EncodeTypedComponentPlane, IrreversibleScaleEncoder,
262    ResidentHtj2kEncodeError,
263};
264pub use j2c::{
265    CpuDecodeParallelism, DecoderContext, DecoderWorkspace, DecoderWorkspaceStats,
266    Reversible53CoefficientImage,
267};
268#[doc(hidden)]
269pub use j2k_types::{
270    sort_packet_descriptors_for_progression, CpuOnlyJ2kEncodeStageAccelerator,
271    EncodedHtJ2kCodeBlock, EncodedHtJ2kCodeBlockSet, EncodedJ2kCodeBlock,
272    IrreversibleQuantizationStep, IrreversibleQuantizationSubbandScales, J2kCodeBlockSegment,
273    J2kCodeBlockStyle, J2kDeinterleaveMctToF32Job, J2kDeinterleaveToF32Job, J2kEncodeContext,
274    J2kEncodeDispatchReport, J2kEncodeStageAccelerator, J2kEncodeStageError,
275    J2kEncodeStageErrorKind, J2kEncodeStageResult, J2kForwardDwt53Job, J2kForwardDwt53Level,
276    J2kForwardDwt53Output, J2kForwardDwt97Job, J2kForwardDwt97Level, J2kForwardDwt97Output,
277    J2kForwardIctJob, J2kForwardRctJob, J2kHtCodeBlockEncodeJob, J2kHtCodeBlockSetEncodeJob,
278    J2kHtSubbandEncodeJob, J2kHtj2kTileEncodeJob, J2kPacketizationBlockCodingMode,
279    J2kPacketizationCodeBlock, J2kPacketizationEncodeJob, J2kPacketizationPacketDescriptor,
280    J2kPacketizationProgressionOrder, J2kPacketizationResolution, J2kPacketizationSubband,
281    J2kQuantizeSubbandJob, J2kResidentEncodeInput, J2kResidentEncodeInputError,
282    J2kResidentHtj2kTileEncodeJob, J2kSubBandType, J2kTier1CodeBlockEncodeJob,
283    PrecomputedHtj2k53Component, PrecomputedHtj2k53Image, PrecomputedHtj2k97Component,
284    PrecomputedHtj2k97Image, PreencodedHtj2k97CodeBlock, PreencodedHtj2k97CompactCodeBlock,
285    PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
286    PreencodedHtj2k97CompactResolution, PreencodedHtj2k97CompactSubband,
287    PreencodedHtj2k97Component, PreencodedHtj2k97Image, PreencodedHtj2k97Resolution,
288    PreencodedHtj2k97Subband, PrequantizedHtj2k97CodeBlock, PrequantizedHtj2k97Component,
289    PrequantizedHtj2k97Image, PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
290};
291pub use j2k_types::{
292    WaveletBand, WaveletBandKind, WaveletComponent, WaveletImage, WaveletRect, WaveletSamples,
293    WaveletSelection, WaveletTile, WaveletTransform,
294};
295
296mod j2c;
297mod jp2;
298pub(crate) mod reader;
299#[doc(hidden)]
300pub use j2c::ht_encode_tables::HtUvlcTableEntry;
301
302const MAX_CLASSIC_DECODE_BITPLANES: u8 = j2c::MAX_BITPLANE_COUNT;
303const MAX_DEINTERLEAVE_REFERENCE_BIT_DEPTH: u8 = j2k_types::MAX_JPEG2000_PART1_SAMPLE_BIT_DEPTH;
304pub(crate) use j2k_types::MAX_JPEG2000_PART1_COMPONENTS as MAX_J2K_SPEC_COMPONENTS;
305pub(crate) const MAX_J2K_TILE_COUNT: u64 = u16::MAX as u64 + 1;
306#[doc(hidden)]
307pub use j2k_types::{DEFAULT_MAX_CODEC_BYTES, DEFAULT_MAX_DECODE_BYTES};
308
309#[inline]
310pub(crate) fn checked_decode_usize_product2(left: usize, right: usize) -> Result<usize> {
311    left.checked_mul(right)
312        .ok_or(ValidationError::ImageTooLarge.into())
313}
314
315#[inline]
316fn checked_decode_byte_cap(len: usize) -> Result<usize> {
317    if len > DEFAULT_MAX_DECODE_BYTES {
318        bail!(ValidationError::ImageTooLarge);
319    }
320    Ok(len)
321}
322
323#[inline]
324pub(crate) fn checked_decode_byte_len2(left: usize, right: usize) -> Result<usize> {
325    checked_decode_byte_cap(checked_decode_usize_product2(left, right)?)
326}
327
328#[inline]
329pub(crate) fn checked_decode_byte_len3(first: usize, second: usize, third: usize) -> Result<usize> {
330    let partial = checked_decode_usize_product2(first, second)?;
331    checked_decode_byte_cap(checked_decode_usize_product2(partial, third)?)
332}
333
334#[inline]
335pub(crate) fn checked_decode_byte_len4(
336    first: usize,
337    second: usize,
338    third: usize,
339    fourth: usize,
340) -> Result<usize> {
341    let partial = checked_decode_usize_product2(first, second)?;
342    let partial = checked_decode_usize_product2(partial, third)?;
343    checked_decode_byte_cap(checked_decode_usize_product2(partial, fourth)?)
344}
345
346#[inline]
347pub(crate) fn try_reserve_decode_elements<T>(values: &mut Vec<T>, target_len: usize) -> Result<()> {
348    checked_decode_byte_len2(target_len, core::mem::size_of::<T>())?;
349    if target_len > values.capacity() {
350        values
351            .try_reserve_exact(target_len - values.len())
352            .map_err(|_| DecodingError::HostAllocationFailed)?;
353    }
354    Ok(())
355}
356
357#[inline]
358pub(crate) fn try_resize_decode_elements<T: Clone>(
359    values: &mut Vec<T>,
360    target_len: usize,
361    value: T,
362) -> Result<()> {
363    try_reserve_decode_elements(values, target_len)?;
364    values.resize(target_len, value);
365    Ok(())
366}
367
368#[inline]
369pub(crate) fn checked_decode_sample_count(width: u32, height: u32) -> Result<usize> {
370    #[cfg(target_pointer_width = "64")]
371    {
372        usize::try_from(u64::from(width) * u64::from(height))
373            .map_err(|_| ValidationError::ImageTooLarge.into())
374    }
375
376    #[cfg(not(target_pointer_width = "64"))]
377    {
378        checked_decode_usize_product2(width as usize, height as usize)
379    }
380}
381
382#[inline]
383fn native_bytes_per_sample(bit_depth: u8) -> Result<usize> {
384    if bit_depth == 0 || bit_depth > 63 {
385        bail!(ValidationError::ImageTooLarge);
386    }
387    Ok(usize::from(bit_depth).div_ceil(8).max(1))
388}
389
390#[doc(hidden)]
391pub use backend::{
392    HtCleanupEncodeDistribution, HtCodeBlockBatchJob, HtCodeBlockDecodeJob,
393    HtCodeBlockDecodePhaseLimit, HtCodeBlockDecoder, HtSubBandDecodeJob, J2kClassicReconstruction,
394    J2kCodeBlockBatchJob, J2kCodeBlockDecodeJob, J2kIdwtBand, J2kIdwtNormalization,
395    J2kInverseMctJob, J2kRect, J2kSingleDecompositionIdwtJob, J2kStoreComponentJob,
396    J2kSubBandDecodeJob, J2kTier1TokenSegment, J2kWaveletTransform,
397};
398#[doc(hidden)]
399pub use ht_adapter::{
400    decode_ht_sigprop_benchmark_state, ht_uvlc_encode_table, ht_uvlc_encode_table_bytes,
401    ht_uvlc_table0, ht_uvlc_table1, ht_vlc_encode_table0, ht_vlc_encode_table1, ht_vlc_table0,
402    ht_vlc_table1, prepare_ht_sigprop_benchmark_state, HtSigPropBenchmarkState,
403};
404
405mod scalar;
406#[doc(hidden)]
407pub use scalar::{
408    collect_ht_cleanup_encode_distribution, decode_ht_code_block_scalar,
409    decode_ht_code_block_scalar_until_phase, decode_ht_code_block_scalar_with_workspace,
410    decode_ht_code_block_scalar_with_workspace_midpoint,
411    decode_ht_code_block_scalar_with_workspace_midpoint_profiled,
412    decode_ht_code_block_scalar_with_workspace_profiled, decode_j2k_code_block_scalar,
413    decode_j2k_code_block_scalar_profiled, decode_j2k_code_block_scalar_with_workspace,
414    decode_j2k_code_block_scalar_with_workspace_midpoint,
415    decode_j2k_code_block_scalar_with_workspace_midpoint_profiled,
416    decode_j2k_code_block_scalar_with_workspace_profiled, decode_j2k_sub_band_scalar,
417    encode_ht_code_block_scalar, encode_ht_code_block_scalar_with_passes,
418    encode_ht_code_block_scalar_with_passes_and_workspace, encode_j2k_code_block_scalar_with_style,
419    encode_j2k_packetization_scalar, forward_dwt53_reference, forward_dwt97_reference,
420    forward_ict_reference, forward_rct_reference, inverse_dwt97_reference, inverse_ict_reference,
421    pack_j2k_code_block_scalar_from_tier1_tokens, quantize_reversible_reference,
422    quantize_subband_reference, try_deinterleave_reference, HtCodeBlockDecodeProfile,
423    HtCodeBlockDecodeWorkspace, HtCodeBlockEncodeWorkspace, J2kCodeBlockDecodeProfile,
424    J2kCodeBlockDecodeWorkspace,
425};
426
427/// JP2 signature box: 00 00 00 0C 6A 50 20 20
428pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
429/// Codestream signature: FF 4F FF 51 (SOC + SIZ markers)
430pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
431
432mod image;
433pub use image::{DecodeSettings, Image, PreparedRegionDecoder};
434
435#[cfg(test)]
436mod tests;