Skip to main content

j2k/encode/
contracts.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use alloc::vec::Vec;
4
5use j2k_core::BackendKind;
6
7use crate::J2kEncodeDispatchReport;
8
9pub(super) use j2k_types::{
10    MAX_JPEG2000_PART1_COMPONENTS,
11    MAX_JPEG2000_PART1_SAMPLE_BIT_DEPTH as MAX_PART1_SAMPLE_BIT_DEPTH,
12};
13pub(super) const MAX_RAW_PIXEL_ENCODE_BIT_DEPTH: u8 = 24;
14pub(super) const MAX_CLASSIC_REVERSIBLE_MARKER_BITPLANES: u16 = 37;
15pub(super) const MAX_HTJ2K_ENCODE_BITPLANES: u16 = 31;
16
17macro_rules! define_encoded_j2k {
18    (
19        $(#[$attr:meta])*
20        pub struct $name:ident {
21            $($extra_fields:tt)*
22        }
23    ) => {
24        $(#[$attr])*
25        pub struct $name {
26            /// Raw JPEG 2000 codestream bytes.
27            pub codestream: Vec<u8>,
28            /// Backend that satisfied the encode contract.
29            pub backend: BackendKind,
30            /// Encode-stage dispatches observed while producing this codestream.
31            ///
32            /// This can be nonzero even when [`Self::backend`] is [`BackendKind::Cpu`]
33            /// for Auto routes that used one or more device stages but did not satisfy
34            /// every stage required for a fully device-backed encode contract.
35            pub dispatch_report: J2kEncodeDispatchReport,
36            /// Encoded image width in pixels.
37            pub width: u32,
38            /// Encoded image height in pixels.
39            pub height: u32,
40            /// Encoded component count.
41            pub components: u16,
42            /// Encoded significant bits per sample.
43            pub bit_depth: u8,
44            /// Whether encoded samples are signed.
45            pub signed: bool,
46            $($extra_fields)*
47        }
48    };
49}
50
51/// Backend preference for JPEG 2000 lossless encoding.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
53pub enum EncodeBackendPreference {
54    /// Pick the fastest safe backend exposed by the caller, falling back to CPU.
55    #[default]
56    Auto,
57    /// Require the pure Rust CPU encoder.
58    CpuOnly,
59    /// Require a device encoder and fail if unavailable or unsupported.
60    RequireDevice,
61}
62
63/// Supported JPEG 2000 progression orders for the lossless encode facade.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
65pub enum J2kProgressionOrder {
66    /// Layer-resolution-component-position progression.
67    #[default]
68    Lrcp,
69    /// Resolution-layer-component-position progression.
70    Rlcp,
71    /// Resolution-position-component-layer progression.
72    Rpcl,
73    /// Position-component-resolution-layer progression.
74    Pcrl,
75    /// Component-position-resolution-layer progression.
76    Cprl,
77}
78
79/// Supported code-block coding modes for the lossless encode facade.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
81pub enum J2kBlockCodingMode {
82    /// Classic JPEG 2000 Part 1 EBCOT block coding.
83    #[default]
84    Classic,
85    /// High-throughput JPEG 2000 Part 15 block coding.
86    HighThroughput,
87}
88
89/// Reversible transform profile for lossless JPEG 2000 output.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
91pub enum ReversibleTransform {
92    /// Reversible color transform with 5/3 wavelet transform.
93    #[default]
94    Rct53,
95    /// No color transform with 5/3 wavelet transform.
96    None53,
97}
98
99/// Validation policy for the lossless encode facade.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
101pub enum J2kEncodeValidation {
102    /// Decode the produced codestream with the native CPU decoder and compare
103    /// decoded samples before returning.
104    #[default]
105    CpuRoundTrip,
106    /// Skip facade validation because the caller performs equivalent external
107    /// validation, for example by decoding on a device backend.
108    External,
109}
110
111/// Options controlling JPEG 2000 lossless encoding.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113#[expect(
114    clippy::struct_excessive_bools,
115    reason = "the public options type exposes independent compatibility switches"
116)]
117#[non_exhaustive]
118pub struct J2kLosslessEncodeOptions {
119    /// Backend preference for encode stages.
120    pub backend: EncodeBackendPreference,
121    /// Code-block coding mode for the codestream.
122    pub block_coding_mode: J2kBlockCodingMode,
123    /// Packet progression order.
124    pub progression: J2kProgressionOrder,
125    /// Optional explicit lossless decomposition level request.
126    ///
127    /// Requests are clamped to the geometry-safe maximum for the tile.
128    pub max_decomposition_levels: Option<u8>,
129    /// Optional tile width and height.
130    pub tile_size: Option<(u32, u32)>,
131    /// Optional maximum number of complete packets to place in each tile-part.
132    pub tile_part_packet_limit: Option<u16>,
133    /// Number of lossless quality layers to write.
134    ///
135    /// HTJ2K uses this to request cleanup plus refinement coding passes from
136    /// the native block encoder. Values outside 1..=32 are rejected by the
137    /// native encoder.
138    pub quality_layers: u8,
139    /// Write a TLM marker segment.
140    pub write_tlm: bool,
141    /// Write PLT packet-length marker segments.
142    pub write_plt: bool,
143    /// Write PLM packet-length marker segments.
144    pub write_plm: bool,
145    /// Write PPM packed packet-header marker segments.
146    pub write_ppm: bool,
147    /// Write PPT packed packet-header marker segments.
148    pub write_ppt: bool,
149    /// Write SOP packet marker segments.
150    pub write_sop: bool,
151    /// Write EPH packet header termination markers.
152    pub write_eph: bool,
153    /// Reversible transform profile.
154    pub reversible_transform: ReversibleTransform,
155    /// Validation policy applied before returning encoded bytes.
156    pub validation: J2kEncodeValidation,
157}
158
159impl Default for J2kLosslessEncodeOptions {
160    fn default() -> Self {
161        Self {
162            backend: EncodeBackendPreference::Auto,
163            block_coding_mode: J2kBlockCodingMode::Classic,
164            progression: J2kProgressionOrder::Lrcp,
165            max_decomposition_levels: None,
166            tile_size: None,
167            tile_part_packet_limit: None,
168            quality_layers: 1,
169            write_tlm: false,
170            write_plt: false,
171            write_plm: false,
172            write_ppm: false,
173            write_ppt: false,
174            write_sop: false,
175            write_eph: false,
176            reversible_transform: ReversibleTransform::Rct53,
177            validation: J2kEncodeValidation::CpuRoundTrip,
178        }
179    }
180}
181
182impl J2kLosslessEncodeOptions {
183    /// Create JPEG 2000 lossless encode options.
184    pub const fn new(
185        backend: EncodeBackendPreference,
186        block_coding_mode: J2kBlockCodingMode,
187        progression: J2kProgressionOrder,
188        max_decomposition_levels: Option<u8>,
189        reversible_transform: ReversibleTransform,
190        validation: J2kEncodeValidation,
191    ) -> Self {
192        Self {
193            backend,
194            block_coding_mode,
195            progression,
196            max_decomposition_levels,
197            tile_size: None,
198            tile_part_packet_limit: None,
199            quality_layers: 1,
200            write_tlm: false,
201            write_plt: false,
202            write_plm: false,
203            write_ppm: false,
204            write_ppt: false,
205            write_sop: false,
206            write_eph: false,
207            reversible_transform,
208            validation,
209        }
210    }
211
212    /// Return options with a different backend preference.
213    #[must_use]
214    pub const fn with_backend(mut self, backend: EncodeBackendPreference) -> Self {
215        self.backend = backend;
216        self
217    }
218
219    /// Return options using adaptive accelerated routing.
220    #[must_use]
221    pub const fn with_accelerated_backend(self) -> Self {
222        self.with_backend(EncodeBackendPreference::Auto)
223    }
224
225    /// Return options using the portable CPU route.
226    #[must_use]
227    pub const fn with_cpu_only_backend(self) -> Self {
228        self.with_backend(EncodeBackendPreference::CpuOnly)
229    }
230
231    /// Return options requiring a strict device route.
232    #[must_use]
233    pub const fn with_strict_device_backend(self) -> Self {
234        self.with_backend(EncodeBackendPreference::RequireDevice)
235    }
236
237    /// Return options with a different code-block coding mode.
238    #[must_use]
239    pub const fn with_block_coding_mode(mut self, block_coding_mode: J2kBlockCodingMode) -> Self {
240        self.block_coding_mode = block_coding_mode;
241        self
242    }
243
244    /// Return options with a different packet progression order.
245    #[must_use]
246    pub const fn with_progression(mut self, progression: J2kProgressionOrder) -> Self {
247        self.progression = progression;
248        self
249    }
250
251    /// Return options with a different maximum decomposition-level request.
252    #[must_use]
253    pub const fn with_max_decomposition_levels(
254        mut self,
255        max_decomposition_levels: Option<u8>,
256    ) -> Self {
257        self.max_decomposition_levels = max_decomposition_levels;
258        self
259    }
260
261    /// Return options with a different tile size.
262    #[must_use]
263    pub const fn with_tile_size(mut self, tile_size: Option<(u32, u32)>) -> Self {
264        self.tile_size = tile_size;
265        self
266    }
267
268    /// Return options with a different tile-part packet limit.
269    #[must_use]
270    pub const fn with_tile_part_packet_limit(
271        mut self,
272        tile_part_packet_limit: Option<u16>,
273    ) -> Self {
274        self.tile_part_packet_limit = tile_part_packet_limit;
275        self
276    }
277
278    /// Return options with a different lossless quality-layer count.
279    #[must_use]
280    pub const fn with_quality_layers(mut self, quality_layers: u8) -> Self {
281        self.quality_layers = quality_layers;
282        self
283    }
284
285    /// Return options with explicit marker segment requests.
286    #[must_use]
287    pub fn with_marker_segments(mut self, marker_segments: &[J2kMarkerSegment]) -> Self {
288        self.write_tlm = false;
289        self.write_plt = false;
290        self.write_plm = false;
291        self.write_ppm = false;
292        self.write_ppt = false;
293        self.write_sop = false;
294        self.write_eph = false;
295        for marker in marker_segments {
296            match marker {
297                J2kMarkerSegment::Sop => self.write_sop = true,
298                J2kMarkerSegment::Eph => self.write_eph = true,
299                J2kMarkerSegment::Tlm => self.write_tlm = true,
300                J2kMarkerSegment::Plt => self.write_plt = true,
301                J2kMarkerSegment::Plm => self.write_plm = true,
302                J2kMarkerSegment::Ppm => self.write_ppm = true,
303                J2kMarkerSegment::Ppt => self.write_ppt = true,
304            }
305        }
306        self
307    }
308
309    /// Return options with a different reversible transform.
310    #[must_use]
311    pub const fn with_reversible_transform(
312        mut self,
313        reversible_transform: ReversibleTransform,
314    ) -> Self {
315        self.reversible_transform = reversible_transform;
316        self
317    }
318
319    /// Return options with a different validation policy.
320    #[must_use]
321    pub const fn with_validation(mut self, validation: J2kEncodeValidation) -> Self {
322        self.validation = validation;
323        self
324    }
325}
326
327/// Rate target for stable lossy JPEG 2000 encoding.
328#[derive(Debug, Clone, Copy, PartialEq)]
329pub enum J2kRateTarget {
330    /// Target total codestream bits per image pixel.
331    BitsPerPixel(f64),
332    /// Target total codestream byte size.
333    Bytes(u64),
334    /// Target decoded peak signal-to-noise ratio in dB.
335    PsnrDb(f64),
336}
337
338/// One cumulative lossy quality layer request.
339#[derive(Debug, Clone, Copy, PartialEq)]
340pub struct J2kQualityLayer {
341    /// Cumulative target for this quality layer.
342    pub target: J2kRateTarget,
343}
344
345impl J2kQualityLayer {
346    /// Create a cumulative lossy quality layer target.
347    #[must_use]
348    pub const fn new(target: J2kRateTarget) -> Self {
349        Self { target }
350    }
351}
352
353/// Optional JPEG 2000 marker segment requested for lossy encode output.
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
355pub enum J2kMarkerSegment {
356    /// SOP packet marker segments.
357    Sop,
358    /// EPH packet header termination markers.
359    Eph,
360    /// TLM tile-part length marker segment.
361    Tlm,
362    /// PLT packet length marker segments.
363    Plt,
364    /// PLM packet length marker segments.
365    Plm,
366    /// PPM packed packet-header marker segments.
367    Ppm,
368    /// PPT packed packet-header marker segments.
369    Ppt,
370}
371
372/// Options controlling stable lossy JPEG 2000 encoding.
373#[derive(Debug, Clone, PartialEq)]
374#[non_exhaustive]
375pub struct J2kLossyEncodeOptions {
376    /// Backend preference for encode stages.
377    pub backend: EncodeBackendPreference,
378    /// Code-block coding mode for the codestream.
379    pub block_coding_mode: J2kBlockCodingMode,
380    /// Packet progression order.
381    pub progression: J2kProgressionOrder,
382    /// Optional explicit lossy decomposition level request.
383    pub max_decomposition_levels: Option<u8>,
384    /// Single codestream rate target.
385    pub rate_target: Option<J2kRateTarget>,
386    /// Cumulative quality layer targets.
387    pub quality_layers: Vec<J2kQualityLayer>,
388    /// Optional tile width and height.
389    pub tile_size: Option<(u32, u32)>,
390    /// Optional maximum number of complete packets to place in each tile-part.
391    pub tile_part_packet_limit: Option<u16>,
392    /// Optional precinct exponents in COD/COC order.
393    pub precinct_exponents: Vec<(u8, u8)>,
394    /// Optional marker segments requested for the codestream.
395    pub marker_segments: Vec<J2kMarkerSegment>,
396    /// Allowed PSNR target tolerance in dB.
397    pub psnr_tolerance_db: f64,
398    /// Iteration budget for lossy target searches.
399    pub psnr_iteration_budget: u8,
400    /// Validation policy applied before returning encoded bytes.
401    pub validation: J2kEncodeValidation,
402}
403
404impl Default for J2kLossyEncodeOptions {
405    fn default() -> Self {
406        Self {
407            backend: EncodeBackendPreference::Auto,
408            block_coding_mode: J2kBlockCodingMode::Classic,
409            progression: J2kProgressionOrder::Lrcp,
410            max_decomposition_levels: None,
411            rate_target: None,
412            quality_layers: Vec::new(),
413            tile_size: None,
414            tile_part_packet_limit: None,
415            precinct_exponents: Vec::new(),
416            marker_segments: Vec::new(),
417            psnr_tolerance_db: 0.25,
418            psnr_iteration_budget: 8,
419            validation: J2kEncodeValidation::CpuRoundTrip,
420        }
421    }
422}
423
424impl J2kLossyEncodeOptions {
425    /// Return options with a different backend preference.
426    #[must_use]
427    pub fn with_backend(mut self, backend: EncodeBackendPreference) -> Self {
428        self.backend = backend;
429        self
430    }
431
432    /// Return options using adaptive accelerated routing.
433    #[must_use]
434    pub fn with_accelerated_backend(self) -> Self {
435        self.with_backend(EncodeBackendPreference::Auto)
436    }
437
438    /// Return options using the portable CPU route.
439    #[must_use]
440    pub fn with_cpu_only_backend(self) -> Self {
441        self.with_backend(EncodeBackendPreference::CpuOnly)
442    }
443
444    /// Return options requiring a strict device route.
445    #[must_use]
446    pub fn with_strict_device_backend(self) -> Self {
447        self.with_backend(EncodeBackendPreference::RequireDevice)
448    }
449
450    /// Return options with a different code-block coding mode.
451    #[must_use]
452    pub fn with_block_coding_mode(mut self, block_coding_mode: J2kBlockCodingMode) -> Self {
453        self.block_coding_mode = block_coding_mode;
454        self
455    }
456
457    /// Return options with a different packet progression order.
458    #[must_use]
459    pub fn with_progression(mut self, progression: J2kProgressionOrder) -> Self {
460        self.progression = progression;
461        self
462    }
463
464    /// Return options with a different maximum decomposition-level request.
465    #[must_use]
466    pub fn with_max_decomposition_levels(mut self, max_decomposition_levels: Option<u8>) -> Self {
467        self.max_decomposition_levels = max_decomposition_levels;
468        self
469    }
470
471    /// Return options with a different single codestream rate target.
472    #[must_use]
473    pub fn with_rate_target(mut self, rate_target: Option<J2kRateTarget>) -> Self {
474        self.rate_target = rate_target;
475        self
476    }
477
478    /// Return options with different cumulative quality layer targets.
479    #[must_use]
480    pub fn with_quality_layers(mut self, quality_layers: Vec<J2kQualityLayer>) -> Self {
481        self.quality_layers = quality_layers;
482        self
483    }
484
485    /// Return options with a different tile size.
486    #[must_use]
487    pub fn with_tile_size(mut self, tile_size: Option<(u32, u32)>) -> Self {
488        self.tile_size = tile_size;
489        self
490    }
491
492    /// Return options with a different tile-part packet limit.
493    #[must_use]
494    pub fn with_tile_part_packet_limit(mut self, tile_part_packet_limit: Option<u16>) -> Self {
495        self.tile_part_packet_limit = tile_part_packet_limit;
496        self
497    }
498
499    /// Return options with different optional marker segment requests.
500    #[must_use]
501    pub fn with_marker_segments(mut self, marker_segments: Vec<J2kMarkerSegment>) -> Self {
502        self.marker_segments = marker_segments;
503        self
504    }
505
506    /// Return options with a different validation policy.
507    #[must_use]
508    pub fn with_validation(mut self, validation: J2kEncodeValidation) -> Self {
509        self.validation = validation;
510        self
511    }
512}
513
514define_encoded_j2k! {
515    /// Encoded JPEG 2000 lossless codestream and encode metadata.
516    #[derive(Debug, Clone, PartialEq, Eq)]
517    pub struct EncodedJ2k {
518    }
519}
520
521/// Metrics reported by stable lossy JPEG 2000 encoding.
522#[derive(Debug, Clone, PartialEq)]
523pub struct J2kLossyEncodeReport {
524    /// Requested effective rate target.
525    pub target: Option<J2kRateTarget>,
526    /// Number of cumulative quality layers emitted.
527    pub quality_layers: u16,
528    /// Final native irreversible quantization scale.
529    pub quantization_scale: f32,
530    /// Total encoded codestream bytes.
531    pub actual_bytes: u64,
532    /// Total codestream bits per image pixel.
533    pub actual_bits_per_pixel: f64,
534    /// Decoded PSNR in dB when CPU validation was requested.
535    pub psnr_db: Option<f64>,
536    /// HTJ2K rate granularity in bytes when HT block coding is used.
537    pub ht_rate_granularity_bytes: Option<u64>,
538}
539
540define_encoded_j2k! {
541    /// Encoded JPEG 2000 lossy codestream and encode metadata.
542    #[derive(Debug, Clone, PartialEq)]
543    pub struct EncodedLossyJ2k {
544        /// Lossy encode metrics.
545        pub report: J2kLossyEncodeReport,
546    }
547}