Skip to main content

zenjxl_decoder/api/
options.rs

1// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6use crate::api::JxlCms;
7
8use std::sync::Arc;
9
10/// Security limits for the JXL decoder to prevent resource exhaustion attacks.
11///
12/// These limits protect against "JXL bombs" - maliciously crafted files designed
13/// to exhaust memory or CPU. All limits are optional; `None` means use the default.
14///
15/// # Example
16/// ```
17/// use zenjxl_decoder::api::JxlDecoderLimits;
18///
19/// // Use restrictive preset for untrusted input
20/// let limits = JxlDecoderLimits::restrictive();
21///
22/// // Or use defaults for normal operation
23/// let defaults = JxlDecoderLimits::default();
24///
25/// // Or unlimited for trusted input (use with caution)
26/// let unlimited = JxlDecoderLimits::unlimited();
27/// ```
28#[derive(Debug, Clone)]
29#[non_exhaustive]
30pub struct JxlDecoderLimits {
31    /// Maximum total pixels (width × height). Default: 2^30 (~1 billion).
32    /// This is checked early during header parsing.
33    pub max_pixels: Option<usize>,
34
35    /// Maximum number of extra channels (alpha, depth, etc.). Default: 256.
36    /// Each extra channel requires memory proportional to image size.
37    pub max_extra_channels: Option<usize>,
38
39    /// Maximum ICC profile size in bytes. Default: 2^28 (256 MB).
40    pub max_icc_size: Option<usize>,
41
42    /// Maximum modular tree size (number of nodes). Default: 2^22.
43    /// Limits memory and CPU for tree-based entropy coding.
44    pub max_tree_size: Option<usize>,
45
46    /// Maximum number of patches. Default: derived from image size.
47    /// Set to limit patch-based attacks.
48    pub max_patches: Option<usize>,
49
50    /// Maximum number of spline control points. Default: 2^20.
51    pub max_spline_points: Option<u32>,
52
53    /// Maximum number of reference frames stored. Default: 4.
54    /// Each reference frame consumes memory equal to the image size.
55    pub max_reference_frames: Option<usize>,
56
57    /// Maximum total memory budget in bytes. Default: None (unlimited).
58    /// When set, the decoder tracks allocations and fails if budget exceeded.
59    /// This provides defense-in-depth against memory exhaustion attacks.
60    pub max_memory_bytes: Option<u64>,
61}
62
63impl Default for JxlDecoderLimits {
64    fn default() -> Self {
65        Self {
66            max_pixels: Some(1 << 28),        // ~256 megapixels
67            max_extra_channels: Some(256),    // 256 extra channels
68            max_icc_size: Some(1 << 28),      // 256 MB
69            max_tree_size: Some(1 << 22),     // 4M nodes
70            max_patches: None,                // Use image-size-based default
71            max_spline_points: Some(1 << 20), // 1M points
72            max_reference_frames: Some(4),    // 4 reference frames
73            max_memory_bytes: None,           // No overall memory limit by default
74        }
75    }
76}
77
78impl JxlDecoderLimits {
79    /// Returns limits with no restrictions (all None).
80    /// Use with caution - only for trusted input.
81    pub fn unlimited() -> Self {
82        Self {
83            max_pixels: None,
84            max_extra_channels: None,
85            max_icc_size: None,
86            max_tree_size: None,
87            max_patches: None,
88            max_spline_points: None,
89            max_reference_frames: None,
90            max_memory_bytes: None,
91        }
92    }
93
94    /// Returns restrictive limits suitable for untrusted web content.
95    pub fn restrictive() -> Self {
96        Self {
97            max_pixels: Some(100_000_000),    // 100 megapixels
98            max_extra_channels: Some(16),     // 16 extra channels
99            max_icc_size: Some(1 << 20),      // 1 MB
100            max_tree_size: Some(1 << 20),     // 1M nodes
101            max_patches: Some(1 << 16),       // 64K patches
102            max_spline_points: Some(1 << 16), // 64K points
103            max_reference_frames: Some(2),    // 2 reference frames
104            max_memory_bytes: Some(1 << 30),  // 1 GB total memory
105        }
106    }
107}
108
109pub enum JxlProgressiveMode {
110    /// Renders all pixels in every call to Process.
111    Eager,
112    /// Renders pixels once passes are completed.
113    Pass,
114    /// Renders pixels only once the final frame is ready.
115    FullFrame,
116}
117
118#[non_exhaustive]
119pub struct JxlDecoderOptions {
120    pub adjust_orientation: bool,
121    pub render_spot_colors: bool,
122    pub coalescing: bool,
123    pub desired_intensity_target: Option<f32>,
124    pub skip_preview: bool,
125    pub progressive_mode: JxlProgressiveMode,
126    pub cms: Option<Box<dyn JxlCms>>,
127    /// Use high precision mode for decoding.
128    /// When false (default), uses lower precision settings that match libjxl's default.
129    /// When true, uses higher precision at the cost of performance.
130    ///
131    /// This affects multiple decoder decisions including spline rendering precision
132    /// and potentially intermediate buffer storage (e.g., using f32 vs f16).
133    pub high_precision: bool,
134    /// If true, multiply RGB by alpha before writing to output buffer.
135    /// This produces premultiplied alpha output, which is useful for compositing.
136    /// Default: false (output straight alpha)
137    pub premultiply_output: bool,
138    /// Security limits to prevent resource exhaustion attacks.
139    /// Use `JxlDecoderLimits::restrictive()` for untrusted input.
140    pub limits: JxlDecoderLimits,
141    /// Cooperative cancellation / timeout handle.
142    /// Default: `Arc::new(enough::Unstoppable)` (no cancellation).
143    pub stop: Arc<dyn enough::Stop>,
144    /// Enable parallel decoding and rendering using rayon.
145    ///
146    /// When `true` (the default when the `threads` feature is enabled),
147    /// group decoding and rendering are parallelized across rayon's global
148    /// thread pool. Control thread count via `RAYON_NUM_THREADS` or
149    /// `rayon::ThreadPoolBuilder::build_global()`.
150    ///
151    /// When `false`, all decoding is single-threaded.
152    pub parallel: bool,
153}
154
155impl Default for JxlDecoderOptions {
156    fn default() -> Self {
157        Self {
158            adjust_orientation: true,
159            render_spot_colors: true,
160            coalescing: true,
161            skip_preview: true,
162            desired_intensity_target: None,
163            progressive_mode: JxlProgressiveMode::Pass,
164            cms: None,
165            high_precision: false,
166            premultiply_output: false,
167            limits: JxlDecoderLimits::default(),
168            stop: Arc::new(enough::Unstoppable),
169            parallel: cfg!(feature = "threads"),
170        }
171    }
172}