Skip to main content

j2k_native/j2c/
encode.rs

1//! Top-level JPEG 2000 encode orchestration.
2//!
3//! Coordinates the full encoding pipeline:
4//!   pixels → MCT → DWT → quantize → EBCOT T1 → T2 → codestream
5//!
6//! Supports both lossless (5-3 reversible) and lossy (9-7 irreversible) encoding.
7
8use alloc::vec::Vec;
9use core::cmp::Ordering;
10use core::ops::Range;
11use j2k_types::encode_geometry::maximum_decomposition_levels;
12
13use super::bitplane_encode;
14use super::build::SubBandType;
15use super::codestream::CodeBlockStyle;
16use super::codestream_write::{self, BlockCodingMode, EncodeComponentSampleInfo, EncodeParams};
17use super::fdwt::{self, DwtDecomposition};
18use super::forward_mct;
19use super::ht_block_encode;
20use super::packet_encode::{self, CodeBlockPacketData, ResolutionPacket, SubbandPrecinct};
21#[doc(hidden)]
22pub use super::quantize::irreversible_quantization_step_for_subband;
23use super::quantize::{self, QuantStepSize};
24use crate::profile;
25pub(crate) use crate::J2kSubBandType;
26use crate::{
27    CpuOnlyJ2kEncodeStageAccelerator, EncodedHtJ2kCodeBlock, EncodedJ2kCodeBlock,
28    J2kDeinterleaveMctToF32Job, J2kDeinterleaveToF32Job, J2kEncodeContext,
29    J2kEncodeStageAccelerator, J2kForwardDwt53Job, J2kForwardDwt53Level, J2kForwardDwt53Output,
30    J2kForwardDwt97Job, J2kForwardDwt97Level, J2kForwardDwt97Output, J2kForwardIctJob,
31    J2kForwardRctJob, J2kHtSubbandEncodeJob, J2kHtj2kTileEncodeJob,
32    J2kPacketizationBlockCodingMode, J2kPacketizationCodeBlock, J2kPacketizationEncodeJob,
33    J2kPacketizationPacketDescriptor, J2kPacketizationResolution, J2kPacketizationSubband,
34    J2kQuantizeSubbandJob, J2kResidentEncodeInput, J2kResidentHtj2kTileEncodeJob,
35    J2kTier1CodeBlockEncodeJob, PrecomputedHtj2k53Component, PrecomputedHtj2k53Image,
36    PrecomputedHtj2k97Component, PrecomputedHtj2k97Image, PreencodedHtj2k97CodeBlock,
37    PreencodedHtj2k97CompactCodeBlock, PreencodedHtj2k97CompactComponent,
38    PreencodedHtj2k97CompactImage, PreencodedHtj2k97CompactResolution,
39    PreencodedHtj2k97CompactSubband, PreencodedHtj2k97Component, PreencodedHtj2k97Image,
40    PreencodedHtj2k97Resolution, PreencodedHtj2k97Subband, PrequantizedHtj2k97Component,
41    PrequantizedHtj2k97Image, PrequantizedHtj2k97Resolution, PrequantizedHtj2k97Subband,
42    MAX_J2K_SPEC_COMPONENTS,
43};
44
45const HT_CPU_PARALLEL_FALLBACK_MIN_JOBS: usize = 4;
46const MAX_RAW_PIXEL_ENCODE_BIT_DEPTH: u8 = 24;
47const MAX_PART1_SAMPLE_BIT_DEPTH: u8 = j2k_types::MAX_JPEG2000_PART1_SAMPLE_BIT_DEPTH;
48const MAX_REVERSIBLE_NO_QUANT_EXPONENT: u16 = 31;
49const MAX_REVERSIBLE_NO_QUANT_GUARD_BITS: u8 = 7;
50const MAX_CLASSIC_REVERSIBLE_MARKER_BITPLANES: u16 =
51    MAX_REVERSIBLE_NO_QUANT_GUARD_BITS as u16 + MAX_REVERSIBLE_NO_QUANT_EXPONENT - 1;
52// Classic packet headers can signal at most 164 coding passes, i.e.
53// 1 cleanup pass for the first bitplane plus 3 passes for each additional
54// bitplane: 1 + 3 * (55 - 1) = 163.
55const MAX_CLASSIC_ROI_CODED_BITPLANES: u8 = 55;
56const MAX_HT_ROI_CODED_BITPLANES: u8 = 31;
57
58mod api_helpers;
59pub(crate) use self::api_helpers::try_deinterleave_to_f32;
60use self::api_helpers::{
61    default_public_code_block_style, internal_sub_band_type, public_sub_band_type,
62};
63#[cfg(test)]
64pub(crate) use self::api_helpers::{deinterleave_rgb8_unsigned_to_f32, deinterleave_to_f32};
65pub(crate) mod allocation;
66mod code_block_metadata;
67use self::allocation::{checked_add_bytes, checked_element_bytes, host_allocation_failed};
68mod retained_api;
69pub(crate) use self::retained_api::encode_with_accelerator_and_retained_input;
70mod retained_input;
71pub(crate) use self::retained_input::NativeEncodeRetainedInput;
72use self::retained_input::{
73    NativeEncodePhase, NativeEncodePipelineError, NativeEncodePipelineResult, NativeEncodeSession,
74};
75mod options;
76use self::options::{
77    validate_code_block_geometry, validate_irreversible_quantization_profile,
78    validate_precinct_exponents_for_options, CodeBlockGeometry,
79};
80pub use self::options::{
81    EncodeComponentPlane, EncodeOptions, EncodeProgressionOrder, EncodeRoiRegion,
82    EncodeTypedComponentPlane,
83};
84mod resident_contract;
85#[doc(hidden)]
86pub use self::resident_contract::ResidentHtj2kEncodeError;
87mod exact;
88use self::exact::{
89    forward_rct_i64, validate_htj2k_codestream, validate_reversible_i64_encode_options,
90};
91mod i64_packetize;
92mod magnitude;
93use self::i64_packetize::{packetize_i64_component_resolution_packets, I64PacketizeRequest};
94mod multitile;
95mod tile_grid;
96mod tile_parts;
97use self::tile_parts::{
98    validate_packet_header_marker_payloads, write_single_tile_packetized_codestream_for_session,
99};
100mod precomputed;
101use self::precomputed::encode_precomputed_53_with_component_sample_info_for_session;
102pub(in crate::j2c) use self::precomputed::encode_precomputed_htj2k_53_with_mct_and_retained_owner;
103#[cfg(test)]
104use self::precomputed::prepared_subband_from_preencoded_owned_for_tests as prepared_subband_from_preencoded_owned;
105pub use self::precomputed::{
106    encode_precomputed_htj2k_53, encode_precomputed_htj2k_53_with_accelerator,
107    encode_precomputed_htj2k_53_with_accelerator_and_max_host_bytes,
108    encode_precomputed_htj2k_53_with_mct, encode_precomputed_htj2k_53_with_mct_and_accelerator,
109    encode_precomputed_htj2k_97, encode_precomputed_htj2k_97_batch_owned_with_accelerator,
110    encode_precomputed_htj2k_97_batch_owned_with_accelerator_and_max_host_bytes,
111    encode_precomputed_htj2k_97_batch_with_accelerator,
112    encode_precomputed_htj2k_97_with_accelerator,
113    encode_precomputed_htj2k_97_with_accelerator_and_max_host_bytes, encode_precomputed_j2k_53,
114    encode_precomputed_j2k_53_with_accelerator, encode_precomputed_j2k_53_with_mct,
115    encode_precomputed_j2k_53_with_mct_and_accelerator, encode_preencoded_htj2k_97,
116    encode_preencoded_htj2k_97_compact_owned_with_accelerator,
117    encode_preencoded_htj2k_97_compact_owned_with_accelerator_and_max_host_bytes,
118    encode_preencoded_htj2k_97_compact_owned_with_accelerator_and_max_host_bytes_and_required_magnitude_bound,
119    encode_preencoded_htj2k_97_owned_with_accelerator,
120    encode_preencoded_htj2k_97_owned_with_accelerator_and_max_host_bytes,
121    encode_preencoded_htj2k_97_owned_with_accelerator_and_max_host_bytes_and_required_magnitude_bound,
122    encode_preencoded_htj2k_97_with_accelerator, encode_prequantized_htj2k_97,
123    encode_prequantized_htj2k_97_with_accelerator,
124    encode_prequantized_htj2k_97_with_accelerator_and_max_host_bytes,
125};
126#[cfg(test)]
127use self::precomputed::{validate_precomputed_dwt97_geometry, validate_precomputed_dwt_geometry};
128mod precomputed_batch;
129use self::precomputed_batch::prepare_precomputed_htj2k97_image_for_batch;
130#[cfg(test)]
131use self::precomputed_batch::{copy_code_block_coefficients, downcast_i64_coefficients_to_i32};
132mod prepared_packets;
133use self::prepared_packets::{
134    encode_prepared_resolution_packets_for_session,
135    encode_prepared_resolution_packets_layered_for_session,
136};
137mod packet_plan;
138use self::packet_plan::{
139    count_compact_code_blocks, ordered_prepared_resolution_packets_for_session,
140    packet_descriptors_for_session, packetization_requires_scalar,
141    packetize_resolution_packets_with_options_for_session,
142    split_component_resolution_packets_by_precinct_for_session,
143};
144mod rate_control;
145#[cfg(test)]
146use self::rate_control::{
147    assign_classic_segment_layers_by_slope, assign_ht_segment_layers_by_budget,
148    ht_layer_contributions,
149};
150use self::rate_control::{
151    assign_classic_segment_layers_by_slope_accounted, assign_ht_segment_layers_by_budget_accounted,
152    classic_layer_contributions_accounted, classic_multilayer_code_block_style,
153    classic_unbudgeted_segment_layers_accounted, enforce_classic_segment_layer_monotonicity,
154    enforce_ht_segment_layer_monotonicity, ht_layer_contributions_accounted, ht_segment_count,
155    ht_segment_rate, ht_unbudgeted_segment_layers_accounted, ClassicSegmentAssignmentCandidate,
156    ClassicSegmentLocation, HtSegmentAssignmentCandidate, HtSegmentLocation, LayeredPreparedBlock,
157    LayeredPreparedPacket, LayeredPreparedSubband,
158};
159mod roi_plan;
160use self::roi_plan::{
161    max_total_bitplanes_for_components, roi_subband_scale,
162    validate_roi_encode_options_nonallocating, ComponentRoiEncodePlan, ComponentRoiEncodeRegion,
163};
164mod samples;
165use self::samples::{
166    native_samples_equal, raw_pixel_bytes_per_sample, read_le_sample_value, sign_extend_sample,
167};
168mod single_tile;
169use self::single_tile::ownership::{cpu_dwt_transient_bytes, dwt_decompositions_retained_bytes};
170#[doc(hidden)]
171pub use self::single_tile::IrreversibleScaleEncoder;
172use self::single_tile::{
173    encode_impl, encode_precomputed_53_single_tile, encode_precomputed_97_single_tile,
174    encode_resident_impl,
175};
176mod subband;
177#[cfg(test)]
178use self::subband::prepare_subband;
179use self::subband::{
180    prepare_subband_for_session, F32SubbandEncodeRequest, I64SubbandEncodeSettings, SubbandSamples,
181};
182mod tier1_allocation;
183mod tier1_driver;
184use self::tier1_driver::encode_prepared_subbands_for_session;
185#[cfg(test)]
186use self::tier1_driver::{
187    encode_all_ht_code_blocks_parallel, encode_all_ht_code_blocks_serial_cpu,
188    encode_prepared_subbands,
189};
190mod transform;
191#[cfg(test)]
192use self::transform::forward_dwt53_output_from_decomposition;
193use self::transform::{
194    adjust_component_step_sizes_for_guard_delta, adjust_reversible_step_sizes_for_guard_delta,
195    encode_forward_dwt, forward_dwt53_output_retained_bytes,
196    reversible_guard_bits_for_marker_limit, try_component_plane_to_f32_for_session,
197    try_encode_forward_ict, try_encode_forward_rct, try_forward_dwt53_output_from_decomposition,
198    validate_band_len, validate_component_sample_info, validate_deinterleaved_components,
199    ForwardDwtRequest,
200};
201mod typed_i64;
202use self::typed_i64::encode_typed_component_planes_53_i64;
203mod typed_components;
204use self::typed_components::encode_typed_component_planes_53_for_session;
205
206/// Encode pixel data into a JPEG 2000 codestream.
207///
208/// # Arguments
209/// * `pixels` — Raw pixel data. For 8-bit: one byte per sample. For >8-bit: two bytes per sample (little-endian u16).
210/// * `width` — Image width in pixels.
211/// * `height` — Image height in pixels.
212/// * `num_components` — Number of components (1 for grayscale, 3 for RGB).
213/// * `bit_depth` — Bits per sample (e.g., 8, 12, 16).
214/// * `signed` — Whether samples are signed.
215/// * `options` — Encoding parameters.
216///
217/// # Returns
218/// The encoded JPEG 2000 codestream bytes (`.j2c` format).
219///
220/// # Errors
221///
222/// Returns an error when dimensions, sample data, component metadata, or
223/// encoding options are invalid, or when a codec stage cannot encode them.
224pub fn encode(
225    pixels: &[u8],
226    width: u32,
227    height: u32,
228    num_components: u16,
229    bit_depth: u8,
230    signed: bool,
231    options: &EncodeOptions,
232) -> crate::EncodeResult<Vec<u8>> {
233    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
234    encode_with_accelerator(
235        pixels,
236        width,
237        height,
238        num_components,
239        bit_depth,
240        signed,
241        options,
242        &mut accelerator,
243    )
244}
245
246/// Encode an irreversible HTJ2K codestream using OpenHTJ2K-compatible
247/// Qfactor quantization.
248///
249/// Qfactor is an opt-in visual quantization profile for grayscale or
250/// three-component RGB input. It changes the expounded QCD/QCC step tuples;
251/// it is not a byte-rate target and is not signaled separately in the
252/// codestream.
253///
254/// # Errors
255///
256/// Returns an error unless `qfactor` is in `1..=100`, irreversible HT block
257/// coding is selected, and the input is grayscale or three-component RGB
258/// with the multi-component transform enabled.
259#[expect(
260    clippy::too_many_arguments,
261    reason = "this codec boundary keeps geometry, sample representation, and quantization policy explicit"
262)]
263pub fn encode_htj2k_with_qfactor(
264    pixels: &[u8],
265    width: u32,
266    height: u32,
267    num_components: u16,
268    bit_depth: u8,
269    signed: bool,
270    qfactor: u8,
271    options: &EncodeOptions,
272) -> crate::EncodeResult<Vec<u8>> {
273    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
274    encode_htj2k_with_qfactor_and_accelerator(
275        pixels,
276        width,
277        height,
278        num_components,
279        bit_depth,
280        signed,
281        qfactor,
282        options,
283        &mut accelerator,
284    )
285}
286
287/// Accelerator-aware counterpart to [`encode_htj2k_with_qfactor`].
288#[doc(hidden)]
289#[expect(
290    clippy::too_many_arguments,
291    reason = "this codec boundary keeps geometry, sample representation, quantization policy, and accelerator explicit"
292)]
293pub fn encode_htj2k_with_qfactor_and_accelerator(
294    pixels: &[u8],
295    width: u32,
296    height: u32,
297    num_components: u16,
298    bit_depth: u8,
299    signed: bool,
300    qfactor: u8,
301    options: &EncodeOptions,
302    accelerator: &mut impl J2kEncodeStageAccelerator,
303) -> crate::EncodeResult<Vec<u8>> {
304    validate_openhtj2k_qfactor_request(qfactor, num_components, options)?;
305    let session = NativeEncodeSession::try_new_with_openhtj2k_qfactor(
306        NativeEncodeRetainedInput::none(),
307        qfactor,
308    )?;
309    let component_sample_info = [EncodeComponentSampleInfo { bit_depth, signed }; 3];
310    encode_with_accelerator_and_component_sample_info_for_session(
311        pixels,
312        width,
313        height,
314        num_components,
315        bit_depth,
316        signed,
317        options,
318        &component_sample_info[..usize::from(num_components)],
319        &session,
320        accelerator,
321    )
322}
323
324fn validate_openhtj2k_qfactor_request(
325    qfactor: u8,
326    num_components: u16,
327    options: &EncodeOptions,
328) -> crate::EncodeResult<()> {
329    if !(1..=100).contains(&qfactor) {
330        return Err(crate::EncodeError::InvalidInput {
331            what: "OpenHTJ2K Qfactor must be in 1..=100",
332        });
333    }
334    if options.reversible || !options.use_ht_block_coding {
335        return Err(crate::EncodeError::InvalidInput {
336            what: "OpenHTJ2K Qfactor requires irreversible HT block coding",
337        });
338    }
339    if options.guard_bits != 1 {
340        return Err(crate::EncodeError::InvalidInput {
341            what: "OpenHTJ2K Qfactor requires one quantization guard bit",
342        });
343    }
344    if !matches!(num_components, 1 | 3) || (num_components == 3 && !options.use_mct) {
345        return Err(crate::EncodeError::InvalidInput {
346            what: "OpenHTJ2K Qfactor requires grayscale or three-component RGB with MCT",
347        });
348    }
349    Ok(())
350}
351
352/// Encode pixel data into a JPEG 2000 codestream using optional encode-stage hooks.
353///
354/// Stage hooks may accelerate forward RCT, forward 5/3 DWT, Tier-1 code-block
355/// encode, and packetization. Returning fallback from a hook preserves the CPU
356/// baseline for that stage.
357#[doc(hidden)]
358#[expect(
359    clippy::too_many_arguments,
360    reason = "this codec boundary keeps geometry, state buffers, and validated options explicit without allocation or indirection"
361)]
362pub fn encode_with_accelerator(
363    pixels: &[u8],
364    width: u32,
365    height: u32,
366    num_components: u16,
367    bit_depth: u8,
368    signed: bool,
369    options: &EncodeOptions,
370    accelerator: &mut impl J2kEncodeStageAccelerator,
371) -> crate::EncodeResult<Vec<u8>> {
372    encode_with_accelerator_and_retained_input(
373        pixels,
374        width,
375        height,
376        num_components,
377        bit_depth,
378        signed,
379        options,
380        NativeEncodeRetainedInput::none(),
381        accelerator,
382    )
383}
384
385/// Encode a complete HTJ2K tile whose input pixels remain backend-resident.
386///
387/// This implementation-facing entry point reuses native request planning and
388/// codestream finalization, but has no CPU fallback because no host samples are
389/// present. A declined resident hook is returned as an explicit error.
390#[doc(hidden)]
391pub fn encode_resident_htj2k_with_accelerator(
392    input: J2kResidentEncodeInput,
393    options: &EncodeOptions,
394    accelerator: &mut impl J2kEncodeStageAccelerator,
395) -> Result<Vec<u8>, ResidentHtj2kEncodeError> {
396    encode_resident_impl(input, options, block_coding_mode(options), accelerator)
397}
398
399#[expect(
400    clippy::too_many_arguments,
401    reason = "this codec boundary keeps geometry, state buffers, and validated options explicit without allocation or indirection"
402)]
403fn encode_with_accelerator_and_component_sample_info_for_session(
404    pixels: &[u8],
405    width: u32,
406    height: u32,
407    num_components: u16,
408    bit_depth: u8,
409    signed: bool,
410    options: &EncodeOptions,
411    component_sample_info: &[EncodeComponentSampleInfo],
412    session: &NativeEncodeSession<'_>,
413    accelerator: &mut impl J2kEncodeStageAccelerator,
414) -> crate::EncodeResult<Vec<u8>> {
415    let block_coding_mode = block_coding_mode(options);
416    encode_with_accelerator_and_mode_for_session(
417        pixels,
418        width,
419        height,
420        num_components,
421        bit_depth,
422        signed,
423        options,
424        component_sample_info,
425        block_coding_mode,
426        session,
427        accelerator,
428    )
429}
430
431#[expect(
432    clippy::too_many_arguments,
433    reason = "this internal mode boundary keeps caller geometry and validated coding policy explicit"
434)]
435fn encode_with_accelerator_and_mode_for_session(
436    pixels: &[u8],
437    width: u32,
438    height: u32,
439    num_components: u16,
440    bit_depth: u8,
441    signed: bool,
442    options: &EncodeOptions,
443    component_sample_info: &[EncodeComponentSampleInfo],
444    block_coding_mode: BlockCodingMode,
445    session: &NativeEncodeSession<'_>,
446    accelerator: &mut impl J2kEncodeStageAccelerator,
447) -> crate::EncodeResult<Vec<u8>> {
448    let codestream = encode_impl(
449        pixels,
450        width,
451        height,
452        num_components,
453        bit_depth,
454        signed,
455        options,
456        block_coding_mode,
457        &[],
458        component_sample_info,
459        session,
460        accelerator,
461    )
462    .map_err(NativeEncodePipelineError::into_encode_error)?;
463
464    if block_coding_mode == BlockCodingMode::HighThroughput
465        && options.validate_high_throughput_codestream
466    {
467        validate_htj2k_codestream(
468            &codestream,
469            codestream.capacity(),
470            pixels,
471            width,
472            height,
473            num_components,
474            bit_depth,
475            signed,
476            options.reversible,
477        )?;
478    }
479
480    Ok(codestream)
481}
482
483/// Encode pixel data into a JPEG 2000 codestream with rectangular ROI maxshift.
484///
485/// This uses the normal native encoder pipeline. Non-empty `roi_regions`
486/// produce RGN markers and shift selected quantized coefficients before
487/// code-block encoding.
488///
489/// # Errors
490///
491/// Returns an error for invalid image/sample metadata, invalid ROI regions or
492/// options, or a failure in any codec stage.
493#[expect(
494    clippy::too_many_arguments,
495    reason = "this codec boundary keeps geometry, state buffers, and validated options explicit without allocation or indirection"
496)]
497pub fn encode_with_roi_regions(
498    pixels: &[u8],
499    width: u32,
500    height: u32,
501    num_components: u16,
502    bit_depth: u8,
503    signed: bool,
504    options: &EncodeOptions,
505    roi_regions: &[EncodeRoiRegion],
506) -> crate::EncodeResult<Vec<u8>> {
507    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
508    encode_with_accelerator_and_roi_regions(
509        pixels,
510        width,
511        height,
512        num_components,
513        bit_depth,
514        signed,
515        options,
516        roi_regions,
517        &mut accelerator,
518    )
519}
520
521/// Encode pixel data with rectangular ROI maxshift and optional stage hooks.
522#[doc(hidden)]
523#[expect(
524    clippy::too_many_arguments,
525    reason = "this codec boundary keeps geometry, state buffers, and validated options explicit without allocation or indirection"
526)]
527pub fn encode_with_accelerator_and_roi_regions(
528    pixels: &[u8],
529    width: u32,
530    height: u32,
531    num_components: u16,
532    bit_depth: u8,
533    signed: bool,
534    options: &EncodeOptions,
535    roi_regions: &[EncodeRoiRegion],
536    accelerator: &mut impl J2kEncodeStageAccelerator,
537) -> crate::EncodeResult<Vec<u8>> {
538    let session = NativeEncodeSession::try_new(NativeEncodeRetainedInput::none())?;
539    let block_coding_mode = block_coding_mode(options);
540    let codestream = encode_impl(
541        pixels,
542        width,
543        height,
544        num_components,
545        bit_depth,
546        signed,
547        options,
548        block_coding_mode,
549        roi_regions,
550        &[],
551        &session,
552        accelerator,
553    )
554    .map_err(NativeEncodePipelineError::into_encode_error)?;
555
556    if block_coding_mode == BlockCodingMode::HighThroughput
557        && options.validate_high_throughput_codestream
558    {
559        validate_htj2k_codestream(
560            &codestream,
561            codestream.capacity(),
562            pixels,
563            width,
564            height,
565            num_components,
566            bit_depth,
567            signed,
568            options.reversible,
569        )?;
570    }
571
572    Ok(codestream)
573}
574
575/// Encode pixel data into an HTJ2K codestream.
576///
577/// Lossless HTJ2K output is self-validated before it is returned.
578///
579/// # Errors
580///
581/// Returns an error when the input or options are invalid, encoding fails, or
582/// the requested output fails HTJ2K self-validation.
583pub fn encode_htj2k(
584    pixels: &[u8],
585    width: u32,
586    height: u32,
587    num_components: u16,
588    bit_depth: u8,
589    signed: bool,
590    options: &EncodeOptions,
591) -> crate::EncodeResult<Vec<u8>> {
592    let session = NativeEncodeSession::try_new(NativeEncodeRetainedInput::none())?;
593    let mut accelerator = CpuOnlyJ2kEncodeStageAccelerator;
594    encode_with_accelerator_and_mode_for_session(
595        pixels,
596        width,
597        height,
598        num_components,
599        bit_depth,
600        signed,
601        options,
602        &[],
603        BlockCodingMode::HighThroughput,
604        &session,
605        &mut accelerator,
606    )
607}
608
609/// Encode reversible 5/3 component planes into a classic J2K or HTJ2K
610/// codestream.
611///
612/// Plane buffers are supplied at each component's own SIZ sampling grid. Set
613/// [`EncodeOptions::use_ht_block_coding`] to select HTJ2K block coding; the
614/// default writes classic Part 1 block coding.
615///
616/// # Errors
617///
618/// Returns an error for invalid component geometry, sampling, sample buffers,
619/// or options, or when a codec stage fails.
620pub fn encode_component_planes_53(
621    planes: &[EncodeComponentPlane<'_>],
622    width: u32,
623    height: u32,
624    bit_depth: u8,
625    signed: bool,
626    options: &EncodeOptions,
627) -> crate::EncodeResult<Vec<u8>> {
628    let session = NativeEncodeSession::try_new(NativeEncodeRetainedInput::none())?;
629    let requested_bytes = checked_element_bytes::<EncodeTypedComponentPlane<'_>>(
630        planes.len(),
631        "component-plane typed descriptor owners",
632    )?;
633    session.checked_phase(requested_bytes, "component-plane typed descriptor owners")?;
634    let mut typed_planes = Vec::new();
635    typed_planes.try_reserve_exact(planes.len()).map_err(|_| {
636        host_allocation_failed("component-plane typed descriptor owners", requested_bytes)
637    })?;
638    typed_planes.extend(planes.iter().map(|plane| EncodeTypedComponentPlane {
639        data: plane.data,
640        x_rsiz: plane.x_rsiz,
641        y_rsiz: plane.y_rsiz,
642        bit_depth,
643        signed,
644    }));
645    let actual_bytes = checked_element_bytes::<EncodeTypedComponentPlane<'_>>(
646        typed_planes.capacity(),
647        "component-plane typed descriptor owners",
648    )?;
649    let typed_session = session.checked_child_session(
650        &typed_planes,
651        actual_bytes,
652        "component-plane typed descriptor owners",
653    )?;
654    encode_typed_component_planes_53_for_session(
655        &typed_planes,
656        width,
657        height,
658        options,
659        &typed_session,
660    )
661    .map_err(NativeEncodePipelineError::into_encode_error)
662}
663
664/// Encode reversible 5/3 typed component planes into a classic J2K or HTJ2K
665/// codestream.
666///
667/// This is the component-plane entry point for JPEG 2000 codestreams whose
668/// components have different precision or signedness. Plane buffers are
669/// supplied at each component's own SIZ sampling grid. Components are encoded
670/// without a reversible color transform.
671///
672/// # Errors
673///
674/// Returns an error for invalid component count, dimensions, sampling,
675/// precision, sample buffers, or options, or when a codec stage fails.
676pub fn encode_typed_component_planes_53(
677    planes: &[EncodeTypedComponentPlane<'_>],
678    width: u32,
679    height: u32,
680    options: &EncodeOptions,
681) -> crate::EncodeResult<Vec<u8>> {
682    let session = NativeEncodeSession::try_new(NativeEncodeRetainedInput::none())?;
683    encode_typed_component_planes_53_for_session(planes, width, height, options, &session)
684        .map_err(NativeEncodePipelineError::into_encode_error)
685}
686
687/// Encode precomputed reversible 5/3 wavelet coefficients into a classic
688/// JPEG 2000 Part 1 codestream.
689///
690fn block_coding_mode(options: &EncodeOptions) -> BlockCodingMode {
691    if options.use_ht_block_coding {
692        BlockCodingMode::HighThroughput
693    } else {
694        BlockCodingMode::Classic
695    }
696}
697
698fn ht_target_coding_passes_for_options(
699    options: &EncodeOptions,
700    block_coding_mode: BlockCodingMode,
701) -> u8 {
702    if block_coding_mode == BlockCodingMode::HighThroughput
703        && !options.reversible
704        && options.num_layers > 1
705    {
706        options.num_layers.min(3)
707    } else {
708        1
709    }
710}
711
712/// Return the largest global irreversible scale, from the options' own scale
713/// upward, at which every step of their component quantization profile is
714/// still representable in QCD/QCC.
715///
716/// Rate search uses this as its ceiling. Past it some profiled steps would be
717/// out of range, which encode rejects rather than clamping them and changing
718/// the profile's ratios. `None` means there is no profile, or an exact
719/// all-ones profile that leaves the default quantization unchanged.
720#[doc(hidden)]
721#[must_use]
722pub fn irreversible_quantization_profile_scale_ceiling(
723    bit_depth: u8,
724    options: &EncodeOptions,
725) -> Option<f32> {
726    let profile = options
727        .irreversible_quantization_profile
728        .as_deref()
729        .filter(|profile| !quantize::profile_is_unity(profile))?;
730    Some(quantize::profile_scale_ceiling(
731        bit_depth,
732        requested_guard_bits(options, options.use_mct, false),
733        options.irreversible_quantization_scale,
734        options.irreversible_quantization_subband_scales,
735        profile,
736    ))
737}
738
739/// Resolve the rate-search scale interval from every weighted QCD/QCC step.
740///
741/// Options must already have valid positive scales and profile shape. An
742/// inverted interval means no common representable scale exists. With no
743/// active profile, the interval includes scale one and ends at global
744/// saturation. Explicit fixed-scale encoding still rejects unrepresentable
745/// active profiles instead of adjusting their scale.
746#[doc(hidden)]
747#[must_use]
748pub fn irreversible_quantization_scale_bounds(
749    bit_depth: u8,
750    options: &EncodeOptions,
751) -> (f32, f32) {
752    let guard_bits = requested_guard_bits(options, options.use_mct, false);
753    // HT block coding supports at most 31 coded bitplanes. For irreversible
754    // steps Mb = guard_bits + exponent - 1, which may narrow the QCD range.
755    let maximum_exponent = if options.use_ht_block_coding {
756        32_u16.saturating_sub(u16::from(guard_bits)).min(31)
757    } else {
758        31
759    };
760    quantize::quantization_scale_bounds(
761        bit_depth,
762        guard_bits,
763        options.irreversible_quantization_subband_scales,
764        options.irreversible_quantization_profile.as_deref(),
765        options.num_decomposition_levels,
766        maximum_exponent,
767    )
768}
769
770fn requested_guard_bits(options: &EncodeOptions, use_mct: bool, openhtj2k_qfactor: bool) -> u8 {
771    if openhtj2k_qfactor {
772        1
773    } else if options.reversible && !use_mct {
774        options.guard_bits
775    } else {
776        options.guard_bits.max(2)
777    }
778}
779
780enum PreparedCodeBlockCoefficients {
781    I32(Vec<i32>),
782    I64(Vec<i64>),
783    Empty,
784}
785
786#[cfg(test)]
787impl PreparedCodeBlockCoefficients {
788    fn is_empty(&self) -> bool {
789        match self {
790            Self::I32(values) => values.is_empty(),
791            Self::I64(values) => values.is_empty(),
792            Self::Empty => true,
793        }
794    }
795}
796
797struct PreparedEncodeCodeBlock {
798    coefficients: PreparedCodeBlockCoefficients,
799    width: u32,
800    height: u32,
801}
802
803struct PreparedEncodeSubband {
804    origin: (u32, u32),
805    code_blocks: Vec<PreparedEncodeCodeBlock>,
806    preencoded_ht_code_blocks: Option<Vec<EncodedHtJ2kCodeBlock>>,
807    preencoded_ht_maximum_cleanup_magnitude: Option<u64>,
808    num_cbs_x: u32,
809    num_cbs_y: u32,
810    code_block_width: u32,
811    code_block_height: u32,
812    width: u32,
813    height: u32,
814    sub_band_type: SubBandType,
815    total_bitplanes: u8,
816    block_coding_mode: BlockCodingMode,
817    ht_target_coding_passes: u8,
818}
819
820struct PreparedResolutionPacket {
821    component: u16,
822    resolution: u32,
823    precinct: u64,
824    subbands: Vec<PreparedEncodeSubband>,
825}
826
827struct PreparedCompactCodeBlock<'a> {
828    data: &'a [u8],
829    cleanup_length: u32,
830    refinement_length: u32,
831    num_coding_passes: u8,
832    num_zero_bitplanes: u8,
833}
834
835struct PreparedCompactSubband<'a> {
836    code_blocks: Vec<PreparedCompactCodeBlock<'a>>,
837    num_cbs_x: u32,
838    num_cbs_y: u32,
839}
840
841struct PreparedCompactResolutionPacket<'a> {
842    component: u16,
843    resolution: u32,
844    precinct: u64,
845    subbands: Vec<PreparedCompactSubband<'a>>,
846}
847
848#[cfg(test)]
849#[path = "encode_tests.rs"]
850mod tests;