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)]
69
70extern crate alloc;
71
72#[cfg(test)]
73use alloc::vec;
74use alloc::vec::Vec;
75
76use crate::error::bail;
77#[cfg(test)]
78use crate::jp2::colr::CieLab;
79
80macro_rules! define_ht_code_block_job {
81    (
82        $(#[$meta:meta])*
83        pub struct $name:ident $(<$lt:lifetime>)? {
84            $($prefix:tt)*
85        }
86    ) => {
87        $(#[$meta])*
88        pub struct $name $(<$lt>)? {
89            $($prefix)*
90            /// Cleanup segment length in bytes.
91            pub cleanup_length: u32,
92            /// Refinement segment length in bytes.
93            pub refinement_length: u32,
94            /// Code-block width in samples.
95            pub width: u32,
96            /// Code-block height in samples.
97            pub height: u32,
98            /// Output row stride, in samples, for the target sub-band storage.
99            pub output_stride: usize,
100            /// Missing most-significant bit planes for this code block.
101            pub missing_bit_planes: u8,
102            /// Number of coding passes present for this code block.
103            pub number_of_coding_passes: u8,
104            /// Total coded bitplanes for the parent sub-band.
105            pub num_bitplanes: u8,
106            /// Region-of-interest maxshift value from RGN marker metadata.
107            pub roi_shift: u8,
108            /// Whether vertically causal context was enabled.
109            pub stripe_causal: bool,
110            /// Whether strict decode validation is enabled for the parent image.
111            pub strict: bool,
112            /// Dequantization step to apply to decoded coefficients.
113            pub dequantization_step: f32,
114        }
115    };
116}
117
118#[doc(hidden)]
119#[macro_export]
120macro_rules! __j2k_component_plane_metadata_accessors {
121    () => {
122        /// Width and height of this decoded plane in output samples.
123        #[must_use]
124        pub fn dimensions(&self) -> (u32, u32) {
125            self.dimensions
126        }
127
128        /// Horizontal and vertical SIZ sampling factors (`XRsiz`, `YRsiz`).
129        #[must_use]
130        pub fn sampling(&self) -> (u8, u8) {
131            self.sampling
132        }
133
134        /// Bit depth of this component plane.
135        #[must_use]
136        pub fn bit_depth(&self) -> u8 {
137            self.bit_depth
138        }
139
140        /// Whether this component plane stores signed sample values.
141        #[must_use]
142        pub fn signed(&self) -> bool {
143            self.signed
144        }
145    };
146}
147
148mod backend;
149mod color;
150mod error;
151mod ht_adapter;
152mod inspect;
153#[macro_use]
154pub(crate) mod log;
155mod direct_cpu;
156mod direct_plan;
157mod direct_roi;
158pub(crate) mod math;
159mod move_only;
160#[doc(hidden)]
161pub mod packet_math;
162pub(crate) mod profile;
163mod roi;
164pub(crate) mod writer;
165
166#[cfg(test)]
167use crate::math::{dispatch, Level};
168#[cfg(test)]
169use color::cielab_to_rgb;
170pub(crate) use color::{
171    convert_color_space, interleave_and_convert, interleave_and_convert_region,
172    resolve_alpha_and_color_space, resolve_palette_indices, validate_and_reorder_channels,
173    validate_interleaved_output_buffer,
174};
175pub use color::{
176    Bitmap, ColorSpace, ComponentPlane, DecodedComponents, DecodedNativeComponents,
177    NativeComponentPlane, RawBitmap,
178};
179#[doc(hidden)]
180pub use color::{ComponentPlaneParts, NativeComponentPlaneParts};
181#[doc(hidden)]
182pub use direct_cpu::{
183    execute_direct_color_plan_rgb8_into, execute_direct_color_plan_rgba8_into,
184    execute_referenced_classic_entropy_job, execute_referenced_classic_plan,
185    execute_referenced_classic_plan_from_payloads, execute_referenced_htj2k_entropy_job,
186    execute_referenced_htj2k_plan, execute_referenced_htj2k_plan_from_payloads,
187    finish_referenced_classic_staged, finish_referenced_classic_tile_staged,
188    finish_referenced_htj2k_staged, finish_referenced_htj2k_tile_staged,
189    prepare_referenced_classic_entropy_workspace, prepare_referenced_classic_staged,
190    prepare_referenced_classic_tile_staged, prepare_referenced_htj2k_entropy_workspace,
191    prepare_referenced_htj2k_staged, prepare_referenced_htj2k_tile_staged, J2kDirectCodeBlockIndex,
192    J2kDirectCpuEntropyWorkspace, J2kDirectCpuScratch, J2kDirectDecodedComponents,
193    J2kDirectDecodedPlane,
194};
195#[doc(hidden)]
196pub use direct_plan::{
197    HtCodeBlockPayloadRanges, HtOwnedCodeBlockBatchJob, HtOwnedSubBandPlan,
198    J2kClassicCodeBlockPayload, J2kCodestreamRange, J2kDirectBandId, J2kDirectColorPlan,
199    J2kDirectGrayscalePlan, J2kDirectGrayscaleStep, J2kDirectIdwtStep, J2kDirectRgbaPlan,
200    J2kDirectStoreStep, J2kOwnedCodeBlockBatchJob, J2kOwnedSubBandPlan, J2kReferencedClassicPlan,
201    J2kReferencedHtj2kPlan, J2kReferencedPayloadRecordSpan, J2kReferencedTileGeometry,
202    J2kReferencedTilePlan,
203};
204#[doc(hidden)]
205pub use direct_roi::{
206    idwt_required_input_window_for_rects, idwt_required_input_windows, idwt_required_output_margin,
207    J2kIdwtRequiredInputWindows, J2kRequiredBandRegion,
208};
209pub use inspect::{
210    inspect_j2k_codestream_header, looks_like_j2k_codestream, J2kCodestreamComponentHeader,
211    J2kCodestreamHeaderError, J2kCodestreamHeaderMetadata,
212};
213#[doc(hidden)]
214pub use jp2::{
215    extract_jp2_codestream_payload, inspect_jp2_container, Jp2ChannelAssociation,
216    Jp2ChannelDefinition, Jp2ChannelType, Jp2ColorSpec, Jp2ComponentMapping,
217    Jp2ComponentMappingType, Jp2ComponentMetadata, Jp2Container, Jp2FileKind, Jp2FileMetadata,
218    Jp2ImageHeaderMetadata, Jp2PaletteColumn, Jp2PaletteMetadata,
219};
220#[doc(hidden)]
221pub use roi::idwt_band_index;
222pub(crate) use roi::{
223    add_roi_shift_to_bitplanes, apply_roi_maxshift_inverse_i32, apply_roi_maxshift_inverse_i64,
224    validate_roi,
225};
226
227pub use error::{
228    ColorError, DecodeError, DecodeErrorClass, DecodingError, DirectPlanUnsupportedReason,
229    EncodeError, EncodeResult, FormatError, MarkerError, Result, TileError, ValidationError,
230};
231#[cfg(test)]
232pub(crate) use j2c::encode::NativeEncodeRetainedInput;
233pub use j2c::encode::{
234    encode, encode_component_planes_53, encode_htj2k, encode_precomputed_htj2k_53,
235    encode_precomputed_htj2k_53_with_accelerator,
236    encode_precomputed_htj2k_53_with_accelerator_and_max_host_bytes,
237    encode_precomputed_htj2k_53_with_mct, encode_precomputed_htj2k_53_with_mct_and_accelerator,
238    encode_precomputed_htj2k_97, encode_precomputed_htj2k_97_batch_owned_with_accelerator,
239    encode_precomputed_htj2k_97_batch_owned_with_accelerator_and_max_host_bytes,
240    encode_precomputed_htj2k_97_batch_with_accelerator,
241    encode_precomputed_htj2k_97_with_accelerator,
242    encode_precomputed_htj2k_97_with_accelerator_and_max_host_bytes, encode_precomputed_j2k_53,
243    encode_precomputed_j2k_53_with_accelerator, encode_precomputed_j2k_53_with_mct,
244    encode_precomputed_j2k_53_with_mct_and_accelerator, encode_preencoded_htj2k_97,
245    encode_preencoded_htj2k_97_compact_owned_with_accelerator,
246    encode_preencoded_htj2k_97_compact_owned_with_accelerator_and_max_host_bytes,
247    encode_preencoded_htj2k_97_owned_with_accelerator,
248    encode_preencoded_htj2k_97_owned_with_accelerator_and_max_host_bytes,
249    encode_preencoded_htj2k_97_with_accelerator, encode_prequantized_htj2k_97,
250    encode_prequantized_htj2k_97_with_accelerator,
251    encode_prequantized_htj2k_97_with_accelerator_and_max_host_bytes,
252    encode_resident_htj2k_with_accelerator, encode_typed_component_planes_53,
253    encode_with_accelerator, encode_with_accelerator_and_roi_regions, encode_with_roi_regions,
254    irreversible_quantization_step_for_subband, EncodeComponentPlane, EncodeOptions,
255    EncodeProgressionOrder, EncodeRoiRegion, EncodeTypedComponentPlane, ResidentHtj2kEncodeError,
256};
257pub use j2c::{
258    CpuDecodeParallelism, DecoderContext, DecoderWorkspace, DecoderWorkspaceStats,
259    Reversible53CoefficientImage,
260};
261#[doc(hidden)]
262pub use j2k_types::{
263    sort_packet_descriptors_for_progression, CpuOnlyJ2kEncodeStageAccelerator,
264    EncodedHtJ2kCodeBlock, EncodedJ2kCodeBlock, IrreversibleQuantizationStep,
265    IrreversibleQuantizationSubbandScales, J2kCodeBlockSegment, J2kCodeBlockStyle,
266    J2kDeinterleaveToF32Job, J2kEncodeDispatchReport, J2kEncodeStageAccelerator,
267    J2kEncodeStageError, J2kEncodeStageErrorKind, J2kEncodeStageResult, J2kForwardDwt53Job,
268    J2kForwardDwt53Level, J2kForwardDwt53Output, J2kForwardDwt97Job, J2kForwardDwt97Level,
269    J2kForwardDwt97Output, J2kForwardIctJob, J2kForwardRctJob, J2kHtCodeBlockEncodeJob,
270    J2kHtSubbandEncodeJob, J2kHtj2kTileEncodeJob, J2kPacketizationBlockCodingMode,
271    J2kPacketizationCodeBlock, J2kPacketizationEncodeJob, J2kPacketizationPacketDescriptor,
272    J2kPacketizationProgressionOrder, J2kPacketizationResolution, J2kPacketizationSubband,
273    J2kQuantizeSubbandJob, J2kResidentEncodeInput, J2kResidentEncodeInputError,
274    J2kResidentHtj2kTileEncodeJob, J2kSubBandType, J2kTier1CodeBlockEncodeJob,
275    PrecomputedHtj2k53Component, PrecomputedHtj2k53Image, PrecomputedHtj2k97Component,
276    PrecomputedHtj2k97Image, PreencodedHtj2k97CodeBlock, PreencodedHtj2k97CompactCodeBlock,
277    PreencodedHtj2k97CompactComponent, PreencodedHtj2k97CompactImage,
278    PreencodedHtj2k97CompactResolution, PreencodedHtj2k97CompactSubband,
279    PreencodedHtj2k97Component, PreencodedHtj2k97Image, PreencodedHtj2k97Resolution,
280    PreencodedHtj2k97Subband, PrequantizedHtj2k97CodeBlock, PrequantizedHtj2k97Component,
281    PrequantizedHtj2k97Image, PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
282};
283
284mod j2c;
285mod jp2;
286pub(crate) mod reader;
287#[doc(hidden)]
288pub use j2c::ht_encode_tables::HtUvlcTableEntry;
289
290const MAX_CLASSIC_DECODE_BITPLANES: u8 = j2c::MAX_BITPLANE_COUNT;
291const MAX_DEINTERLEAVE_REFERENCE_BIT_DEPTH: u8 = j2k_types::MAX_JPEG2000_PART1_SAMPLE_BIT_DEPTH;
292pub(crate) use j2k_types::MAX_JPEG2000_PART1_COMPONENTS as MAX_J2K_SPEC_COMPONENTS;
293pub(crate) const MAX_J2K_IMAGE_DIMENSION: u32 = 60_000;
294pub(crate) const MAX_J2K_TILE_COUNT: u64 = u16::MAX as u64 + 1;
295/// Authoritative worst-case host allocation allowance for one native codec operation.
296#[doc(hidden)]
297pub const DEFAULT_MAX_CODEC_BYTES: usize = 512 * 1024 * 1024;
298/// Backward-compatible decode name for the authoritative native codec allowance.
299#[doc(hidden)]
300pub const DEFAULT_MAX_DECODE_BYTES: usize = DEFAULT_MAX_CODEC_BYTES;
301
302#[inline]
303pub(crate) fn checked_decode_usize_product2(left: usize, right: usize) -> Result<usize> {
304    left.checked_mul(right)
305        .ok_or(ValidationError::ImageTooLarge.into())
306}
307
308#[inline]
309fn checked_decode_byte_cap(len: usize) -> Result<usize> {
310    if len > DEFAULT_MAX_DECODE_BYTES {
311        bail!(ValidationError::ImageTooLarge);
312    }
313    Ok(len)
314}
315
316#[inline]
317pub(crate) fn checked_decode_byte_len2(left: usize, right: usize) -> Result<usize> {
318    checked_decode_byte_cap(checked_decode_usize_product2(left, right)?)
319}
320
321#[inline]
322pub(crate) fn checked_decode_byte_len3(first: usize, second: usize, third: usize) -> Result<usize> {
323    let partial = checked_decode_usize_product2(first, second)?;
324    checked_decode_byte_cap(checked_decode_usize_product2(partial, third)?)
325}
326
327#[inline]
328pub(crate) fn checked_decode_byte_len4(
329    first: usize,
330    second: usize,
331    third: usize,
332    fourth: usize,
333) -> Result<usize> {
334    let partial = checked_decode_usize_product2(first, second)?;
335    let partial = checked_decode_usize_product2(partial, third)?;
336    checked_decode_byte_cap(checked_decode_usize_product2(partial, fourth)?)
337}
338
339#[inline]
340pub(crate) fn try_reserve_decode_elements<T>(values: &mut Vec<T>, target_len: usize) -> Result<()> {
341    checked_decode_byte_len2(target_len, core::mem::size_of::<T>())?;
342    if target_len > values.capacity() {
343        values
344            .try_reserve_exact(target_len - values.len())
345            .map_err(|_| DecodingError::HostAllocationFailed)?;
346    }
347    Ok(())
348}
349
350#[inline]
351pub(crate) fn try_resize_decode_elements<T: Clone>(
352    values: &mut Vec<T>,
353    target_len: usize,
354    value: T,
355) -> Result<()> {
356    try_reserve_decode_elements(values, target_len)?;
357    values.resize(target_len, value);
358    Ok(())
359}
360
361#[inline]
362pub(crate) fn checked_decode_sample_count(width: u32, height: u32) -> Result<usize> {
363    #[cfg(target_pointer_width = "64")]
364    {
365        usize::try_from(u64::from(width) * u64::from(height))
366            .map_err(|_| ValidationError::ImageTooLarge.into())
367    }
368
369    #[cfg(not(target_pointer_width = "64"))]
370    {
371        checked_decode_usize_product2(width as usize, height as usize)
372    }
373}
374
375#[inline]
376fn native_bytes_per_sample(bit_depth: u8) -> Result<usize> {
377    if bit_depth == 0 || bit_depth > 63 {
378        bail!(ValidationError::ImageTooLarge);
379    }
380    Ok(usize::from(bit_depth).div_ceil(8).max(1))
381}
382
383#[doc(hidden)]
384pub use backend::{
385    HtCleanupEncodeDistribution, HtCodeBlockBatchJob, HtCodeBlockDecodeJob,
386    HtCodeBlockDecodePhaseLimit, HtCodeBlockDecoder, HtSubBandDecodeJob, J2kCodeBlockBatchJob,
387    J2kCodeBlockDecodeJob, J2kIdwtBand, J2kInverseMctJob, J2kRect, J2kSingleDecompositionIdwtJob,
388    J2kStoreComponentJob, J2kSubBandDecodeJob, J2kTier1TokenSegment, J2kWaveletTransform,
389};
390#[doc(hidden)]
391pub use ht_adapter::{
392    decode_ht_sigprop_benchmark_state, ht_uvlc_encode_table, ht_uvlc_encode_table_bytes,
393    ht_uvlc_table0, ht_uvlc_table1, ht_vlc_encode_table0, ht_vlc_encode_table1, ht_vlc_table0,
394    ht_vlc_table1, prepare_ht_sigprop_benchmark_state, HtSigPropBenchmarkState,
395};
396
397mod scalar;
398#[doc(hidden)]
399pub use scalar::{
400    collect_ht_cleanup_encode_distribution, decode_ht_code_block_scalar,
401    decode_ht_code_block_scalar_until_phase, decode_ht_code_block_scalar_with_workspace,
402    decode_ht_code_block_scalar_with_workspace_profiled, decode_j2k_code_block_scalar,
403    decode_j2k_code_block_scalar_profiled, decode_j2k_code_block_scalar_with_workspace,
404    decode_j2k_code_block_scalar_with_workspace_profiled, decode_j2k_sub_band_scalar,
405    encode_ht_code_block_scalar, encode_ht_code_block_scalar_with_passes,
406    encode_j2k_code_block_scalar_with_style, encode_j2k_packetization_scalar,
407    forward_dwt53_reference, forward_dwt97_reference, forward_ict_reference, forward_rct_reference,
408    pack_j2k_code_block_scalar_from_tier1_tokens, quantize_reversible_reference,
409    quantize_subband_reference, try_deinterleave_reference, HtCodeBlockDecodeProfile,
410    HtCodeBlockDecodeWorkspace, J2kCodeBlockDecodeProfile, J2kCodeBlockDecodeWorkspace,
411};
412
413/// JP2 signature box: 00 00 00 0C 6A 50 20 20
414pub(crate) const JP2_MAGIC: &[u8] = b"\x00\x00\x00\x0C\x6A\x50\x20\x20";
415/// Codestream signature: FF 4F FF 51 (SOC + SIZ markers)
416pub(crate) const CODESTREAM_MAGIC: &[u8] = b"\xFF\x4F\xFF\x51";
417
418mod image;
419pub use image::{DecodeSettings, Image};
420
421#[cfg(test)]
422mod tests;