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        // On 32-bit targets, a 4 GB memory budget exceeds the usable address
66        // space (~3 GB). Cap at 2 GB so the guard triggers before the global
67        // allocator aborts on OOM.
68        let max_memory = if cfg!(target_pointer_width = "32") {
69            2u64 << 30 // 2 GB
70        } else {
71            4u64 << 30 // 4 GB
72        };
73        Self {
74            max_pixels: Some(1 << 28),        // ~256 megapixels
75            max_extra_channels: Some(256),    // 256 extra channels
76            max_icc_size: Some(1 << 28),      // 256 MB
77            max_tree_size: Some(1 << 22),     // 4M nodes
78            max_patches: None,                // Use image-size-based default
79            max_spline_points: Some(1 << 20), // 1M points
80            max_reference_frames: Some(4),    // 4 reference frames
81            max_memory_bytes: Some(max_memory),
82        }
83    }
84}
85
86impl JxlDecoderLimits {
87    /// Returns limits with no restrictions (all None).
88    /// Use with caution - only for trusted input.
89    pub fn unlimited() -> Self {
90        Self {
91            max_pixels: None,
92            max_extra_channels: None,
93            max_icc_size: None,
94            max_tree_size: None,
95            max_patches: None,
96            max_spline_points: None,
97            max_reference_frames: None,
98            max_memory_bytes: None,
99        }
100    }
101
102    /// Returns restrictive limits suitable for untrusted web content.
103    pub fn restrictive() -> Self {
104        Self {
105            max_pixels: Some(100_000_000),    // 100 megapixels
106            max_extra_channels: Some(16),     // 16 extra channels
107            max_icc_size: Some(1 << 20),      // 1 MB
108            max_tree_size: Some(1 << 20),     // 1M nodes
109            max_patches: Some(1 << 16),       // 64K patches
110            max_spline_points: Some(1 << 16), // 64K points
111            max_reference_frames: Some(2),    // 2 reference frames
112            max_memory_bytes: Some(1 << 30),  // 1 GB total memory
113        }
114    }
115}
116
117pub enum JxlProgressiveMode {
118    /// Renders all pixels in every call to Process.
119    Eager,
120    /// Renders pixels once passes are completed.
121    Pass,
122    /// Renders pixels only once the final frame is ready.
123    FullFrame,
124}
125
126#[non_exhaustive]
127pub struct JxlDecoderOptions {
128    pub adjust_orientation: bool,
129    pub render_spot_colors: bool,
130    pub coalescing: bool,
131    pub desired_intensity_target: Option<f32>,
132    pub skip_preview: bool,
133    pub progressive_mode: JxlProgressiveMode,
134    pub cms: Option<Box<dyn JxlCms>>,
135    /// Use high precision mode for decoding.
136    /// When false (default), uses lower precision settings that match libjxl's default.
137    /// When true, uses higher precision at the cost of performance.
138    ///
139    /// This affects multiple decoder decisions including spline rendering precision
140    /// and potentially intermediate buffer storage (e.g., using f32 vs f16).
141    pub high_precision: bool,
142    /// If true, multiply RGB by alpha before writing to output buffer.
143    /// This produces premultiplied alpha output, which is useful for compositing.
144    /// Default: false (output straight alpha)
145    pub premultiply_output: bool,
146    /// Security limits to prevent resource exhaustion attacks.
147    /// Use `JxlDecoderLimits::restrictive()` for untrusted input.
148    pub limits: JxlDecoderLimits,
149    /// Cooperative cancellation / timeout handle.
150    /// Default: `Arc::new(enough::Unstoppable)` (no cancellation).
151    pub stop: Arc<dyn enough::Stop>,
152    /// Enable parallel decoding and rendering using rayon.
153    ///
154    /// When `true` (the default when the `threads` feature is enabled),
155    /// group decoding and rendering are parallelized across rayon's global
156    /// thread pool. Control thread count via `RAYON_NUM_THREADS` or
157    /// `rayon::ThreadPoolBuilder::build_global()`.
158    ///
159    /// When `false`, all decoding is single-threaded.
160    pub parallel: bool,
161}
162
163impl Default for JxlDecoderOptions {
164    fn default() -> Self {
165        Self {
166            adjust_orientation: true,
167            render_spot_colors: true,
168            coalescing: true,
169            skip_preview: true,
170            desired_intensity_target: None,
171            progressive_mode: JxlProgressiveMode::Pass,
172            cms: None,
173            high_precision: false,
174            premultiply_output: false,
175            limits: JxlDecoderLimits::default(),
176            stop: Arc::new(enough::Unstoppable),
177            parallel: cfg!(feature = "threads"),
178        }
179    }
180}