document_svg/document/
jpeg2000.rs1use std::path::Path;
8
9use base64::Engine;
10use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
11use jpeg2k::{ColorSpace, Image};
12
13use crate::convert::{ConvertOptions, PageConsumer, read_limited_file};
14use crate::error::{Error, Result};
15use crate::ir::{IDENTITY, Node, Page, SourceMeta};
16
17const MAX_JPEG2000_BYTES: u64 = 128 * 1024 * 1024;
18const MAX_JPEG2000_PIXELS: u64 = 20_000_000;
19const MAX_JPEG2000_DIMENSION: u32 = 100_000;
20const MAX_JPEG2000_DECODED_BYTES: usize = 128 * 1024 * 1024;
21const MAX_JPEG2000_DATA_URI_BYTES: usize = 192 * 1024 * 1024;
22
23pub(crate) fn convert(
24 path: &Path,
25 options: &ConvertOptions,
26 sink: &mut dyn PageConsumer,
27) -> Result<Vec<String>> {
28 let bytes = read_limited_file(
29 path,
30 options.max_input_bytes.min(MAX_JPEG2000_BYTES),
31 "JPEG 2000 input",
32 )?;
33 let (png, width, height, mut warnings) = decode_png(&bytes)?;
34 let data_uri = format!("data:image/png;base64,{}", BASE64_STANDARD.encode(png));
35 if data_uri.len() > MAX_JPEG2000_DATA_URI_BYTES {
36 return Err(Error::LimitExceeded(format!(
37 "JPEG 2000 PNG data URI exceeds {MAX_JPEG2000_DATA_URI_BYTES} bytes"
38 )));
39 }
40 let mut page = Page::new(1, f64::from(width), f64::from(height), "jpeg2000");
41 page.title = "JPEG 2000 image".into();
42 page.description = format!("JPEG 2000 image {width}×{height}");
43 for warning in &warnings {
44 page.warn(warning.clone());
45 }
46 page.nodes.push(Node::Image {
47 id: "jpeg2000-image-1".into(),
48 href: data_uri,
49 x: 0.0,
50 y: 0.0,
51 width: f64::from(width),
52 height: f64::from(height),
53 transform: IDENTITY,
54 opacity: 1.0,
55 clip_id: None,
56 meta: SourceMeta {
57 semantic_role: "jpeg2000:image".into(),
58 ..Default::default()
59 },
60 });
61 sink.consume(page)?;
62 warnings.sort();
63 warnings.dedup();
64 Ok(warnings)
65}
66
67fn decode_png(bytes: &[u8]) -> Result<(Vec<u8>, u32, u32, Vec<String>)> {
68 let header = crate::jpeg2000::parse_jpx_header(bytes)?;
69 if header.width == 0
70 || header.height == 0
71 || header.width > MAX_JPEG2000_DIMENSION
72 || header.height > MAX_JPEG2000_DIMENSION
73 {
74 return Err(Error::LimitExceeded(format!(
75 "JPEG 2000 dimensions {}×{} exceed the supported range",
76 header.width, header.height
77 )));
78 }
79 let pixels = u64::from(header.width)
80 .checked_mul(u64::from(header.height))
81 .ok_or_else(|| Error::LimitExceeded("JPEG 2000 pixel count overflowed".into()))?;
82 if pixels > MAX_JPEG2000_PIXELS {
83 return Err(Error::LimitExceeded(format!(
84 "JPEG 2000 image contains {pixels} pixels; maximum is {MAX_JPEG2000_PIXELS}"
85 )));
86 }
87 let decoded_sample_bytes = usize::try_from(pixels)
88 .ok()
89 .and_then(|count| count.checked_mul(header.components.len()))
90 .and_then(|count| count.checked_mul(std::mem::size_of::<i32>()))
91 .ok_or_else(|| Error::LimitExceeded("JPEG 2000 decoded sample size overflowed".into()))?;
92 if decoded_sample_bytes > MAX_JPEG2000_DECODED_BYTES {
93 return Err(Error::LimitExceeded(format!(
94 "JPEG 2000 decoded samples need {decoded_sample_bytes} bytes; maximum is {MAX_JPEG2000_DECODED_BYTES}"
95 )));
96 }
97 let image = Image::from_bytes(bytes)
98 .map_err(|error| Error::InvalidInput(format!("JPEG 2000 decode failed: {error}")))?;
99 if image.width() != header.width || image.height() != header.height {
100 return Err(Error::InvalidInput(
101 "JPEG 2000 decoder dimensions do not match the validated header".into(),
102 ));
103 }
104 let components = image.components();
105 if components.len() != header.components.len() {
106 return Err(Error::InvalidInput(
107 "JPEG 2000 decoder component count does not match the validated header".into(),
108 ));
109 }
110 let precision = header.components[0].precision;
111 if !(1..=16).contains(&precision)
112 || header.components.iter().any(|component| {
113 component.precision != precision
114 || component.signed
115 || component.width != header.width
116 || component.height != header.height
117 })
118 {
119 return Err(Error::Unsupported(
120 "JPEG 2000 requires unsigned components with a shared 1–16-bit precision".into(),
121 ));
122 }
123 let mut warnings = Vec::new();
124 let color_space = image.color_space();
125 let alpha_index = components
126 .iter()
127 .enumerate()
128 .filter_map(|(index, component)| component.is_alpha().then_some(index))
129 .collect::<Vec<_>>();
130 let alpha = match alpha_index.as_slice() {
131 [] => match components.len() {
132 2 if matches!(
133 color_space,
134 ColorSpace::Gray | ColorSpace::Unknown | ColorSpace::Unspecified
135 ) =>
136 {
137 warnings.push(
138 "JPEG 2000 two-component image was interpreted as grayscale plus alpha".into(),
139 );
140 Some(1)
141 }
142 4 if matches!(
143 color_space,
144 ColorSpace::SRGB | ColorSpace::Unknown | ColorSpace::Unspecified
145 ) =>
146 {
147 warnings.push(
148 "JPEG 2000 four-component image was interpreted as RGB plus alpha".into(),
149 );
150 Some(3)
151 }
152 _ => None,
153 },
154 [index] => Some(*index),
155 _ => {
156 return Err(Error::Unsupported(
157 "JPEG 2000 images with multiple alpha components are unsupported".into(),
158 ));
159 }
160 };
161 let color_indices = (0..components.len())
162 .filter(|index| Some(*index) != alpha)
163 .collect::<Vec<_>>();
164 let png_color = match (color_indices.len(), alpha.is_some()) {
165 (1, false) => png::ColorType::Grayscale,
166 (1, true) => png::ColorType::GrayscaleAlpha,
167 (3, false) => png::ColorType::Rgb,
168 (3, true) => png::ColorType::Rgba,
169 _ => {
170 return Err(Error::Unsupported(format!(
171 "JPEG 2000 component count {} is unsupported; expected grayscale or RGB with optional alpha",
172 components.len()
173 )));
174 }
175 };
176 let output_channels = png_color.samples();
177 let output_len = usize::try_from(pixels)
178 .ok()
179 .and_then(|count| count.checked_mul(output_channels))
180 .ok_or_else(|| Error::LimitExceeded("JPEG 2000 PNG size overflowed".into()))?;
181 if output_len > MAX_JPEG2000_DECODED_BYTES {
182 return Err(Error::LimitExceeded(format!(
183 "JPEG 2000 PNG samples need {output_len} bytes; maximum is {MAX_JPEG2000_DECODED_BYTES}"
184 )));
185 }
186 let samples = components
187 .iter()
188 .map(|component| component.data())
189 .collect::<Vec<_>>();
190 let maximum = (1u32 << precision) - 1;
191 let mut output = Vec::with_capacity(output_len);
192 for pixel in 0..usize::try_from(pixels).unwrap_or(0) {
193 for index in &color_indices {
194 output.push(scale_sample(samples[*index].get(pixel).copied(), maximum)?);
195 }
196 if let Some(alpha_index) = alpha {
197 output.push(scale_sample(
198 samples[alpha_index].get(pixel).copied(),
199 maximum,
200 )?);
201 }
202 }
203 let mut png_bytes = Vec::new();
204 {
205 let mut encoder = png::Encoder::new(&mut png_bytes, header.width, header.height);
206 encoder.set_color(png_color);
207 encoder.set_depth(png::BitDepth::Eight);
208 let mut writer = encoder.write_header().map_err(|error| {
209 Error::InvalidInput(format!("could not encode JPEG 2000 PNG header: {error}"))
210 })?;
211 writer.write_image_data(&output).map_err(|error| {
212 Error::InvalidInput(format!("could not encode JPEG 2000 PNG data: {error}"))
213 })?;
214 writer.finish().map_err(|error| {
215 Error::InvalidInput(format!("could not finish JPEG 2000 PNG image: {error}"))
216 })?;
217 }
218 if precision != 8 {
219 warnings.push(format!(
220 "JPEG 2000 {precision}-bit samples were reduced to 8-bit PNG"
221 ));
222 }
223 if !matches!(
224 color_space,
225 ColorSpace::Gray | ColorSpace::SRGB | ColorSpace::Unknown | ColorSpace::Unspecified
226 ) {
227 warnings.push("JPEG 2000 color space metadata was not applied".into());
228 }
229 Ok((png_bytes, header.width, header.height, warnings))
230}
231
232fn scale_sample(sample: Option<i32>, maximum: u32) -> Result<u8> {
233 let sample = sample.ok_or_else(|| {
234 Error::InvalidInput("JPEG 2000 decoder returned an incomplete component".into())
235 })?;
236 if sample < 0 || u32::try_from(sample).unwrap_or(u32::MAX) > maximum {
237 return Err(Error::InvalidInput(
238 "JPEG 2000 sample is outside its declared precision".into(),
239 ));
240 }
241 Ok(
242 ((u64::try_from(sample).unwrap_or(0) * 255 + u64::from(maximum) / 2) / u64::from(maximum))
243 as u8,
244 )
245}