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}