1use alloc::{format, string::ToString};
4
5use j2k_core::Unsupported;
6
7use super::contracts::{MAX_JPEG2000_PART1_COMPONENTS, MAX_PART1_SAMPLE_BIT_DEPTH};
8use crate::J2kError;
9
10pub(super) fn raw_pixel_bytes_per_sample(bit_depth: u8) -> usize {
11 usize::from(bit_depth).div_ceil(8).max(1)
12}
13
14#[derive(Debug, Clone, Copy)]
16pub struct J2kLosslessSamples<'a> {
17 pub data: &'a [u8],
19 pub width: u32,
21 pub height: u32,
23 pub components: u16,
26 pub bit_depth: u8,
28 pub signed: bool,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33struct SampleGeometry {
34 expected_bytes: usize,
35}
36
37#[derive(Debug, Clone, Copy)]
38struct SampleGeometryRequest<'a> {
39 data: &'a [u8],
40 width: u32,
41 height: u32,
42 components: u16,
43 bit_depth: u8,
44 max_bit_depth: u8,
45 component_what: &'static str,
46 bit_depth_what: &'static str,
47}
48
49fn validate_sample_geometry(
50 request: SampleGeometryRequest<'_>,
51) -> Result<SampleGeometry, J2kError> {
52 let SampleGeometryRequest {
53 data,
54 width,
55 height,
56 components,
57 bit_depth,
58 max_bit_depth,
59 component_what,
60 bit_depth_what,
61 } = request;
62 if width == 0 || height == 0 {
63 return Err(J2kError::InvalidSamples {
64 what: "dimensions must be non-zero".to_string(),
65 });
66 }
67 if components == 0 || components > MAX_JPEG2000_PART1_COMPONENTS {
68 return Err(J2kError::Unsupported(Unsupported {
69 what: component_what,
70 }));
71 }
72 if bit_depth == 0 || bit_depth > max_bit_depth {
73 return Err(J2kError::Unsupported(Unsupported {
74 what: bit_depth_what,
75 }));
76 }
77 let bytes_per_sample = raw_pixel_bytes_per_sample(bit_depth);
78 let expected_bytes = (width as usize)
79 .checked_mul(height as usize)
80 .and_then(|px| px.checked_mul(usize::from(components)))
81 .and_then(|samples| samples.checked_mul(bytes_per_sample))
82 .ok_or(J2kError::DimensionOverflow { width, height })?;
83 if data.len() != expected_bytes {
84 let what = if data.len() < expected_bytes {
85 format!(
86 "pixel data too short: expected {expected_bytes} bytes, got {}",
87 data.len()
88 )
89 } else {
90 format!(
91 "pixel data has trailing bytes: expected {expected_bytes} bytes, got {}",
92 data.len()
93 )
94 };
95 return Err(J2kError::InvalidSamples { what });
96 }
97 Ok(SampleGeometry { expected_bytes })
98}
99
100impl<'a> J2kLosslessSamples<'a> {
101 pub fn new(
103 data: &'a [u8],
104 width: u32,
105 height: u32,
106 components: u16,
107 bit_depth: u8,
108 signed: bool,
109 ) -> Result<Self, J2kError> {
110 let geometry = validate_sample_geometry(SampleGeometryRequest {
111 data,
112 width,
113 height,
114 components,
115 bit_depth,
116 max_bit_depth: MAX_PART1_SAMPLE_BIT_DEPTH,
117 component_what: "JPEG 2000 lossless encode supports 1-16384 component samples",
118 bit_depth_what: "JPEG 2000 lossless encode supports 1-38 bits per sample for classic reversible codestreams",
119 })?;
120 debug_assert_eq!(geometry.expected_bytes, data.len());
121 Ok(Self {
122 data,
123 width,
124 height,
125 components,
126 bit_depth,
127 signed,
128 })
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
139pub struct J2kRoiRegion {
140 pub component: u16,
142 pub x: u32,
144 pub y: u32,
146 pub width: u32,
148 pub height: u32,
150 pub shift: u8,
152}
153
154#[derive(Debug, Clone, Copy)]
156pub struct J2kLosslessComponentPlane<'a> {
157 pub data: &'a [u8],
159 pub x_rsiz: u8,
161 pub y_rsiz: u8,
163}
164
165#[derive(Debug, Clone, Copy)]
168pub struct J2kLosslessComponentSamples<'a> {
169 pub planes: &'a [J2kLosslessComponentPlane<'a>],
171 pub width: u32,
173 pub height: u32,
175 pub bit_depth: u8,
178 pub signed: bool,
181}
182
183impl<'a> J2kLosslessComponentSamples<'a> {
184 pub fn new(
186 planes: &'a [J2kLosslessComponentPlane<'a>],
187 width: u32,
188 height: u32,
189 bit_depth: u8,
190 signed: bool,
191 ) -> Result<Self, J2kError> {
192 if width == 0 || height == 0 {
193 return Err(J2kError::InvalidSamples {
194 what: "dimensions must be non-zero".to_string(),
195 });
196 }
197 if planes.is_empty() || planes.len() > usize::from(MAX_JPEG2000_PART1_COMPONENTS) {
198 return Err(J2kError::Unsupported(Unsupported {
199 what: "JPEG 2000 lossless component-plane encode supports 1-16384 components",
200 }));
201 }
202 if bit_depth == 0 || bit_depth > MAX_PART1_SAMPLE_BIT_DEPTH {
203 return Err(J2kError::Unsupported(Unsupported {
204 what: "JPEG 2000 lossless component-plane encode supports 1-38 bits per sample",
205 }));
206 }
207 for (index, plane) in planes.iter().enumerate() {
208 validate_component_plane_geometry(plane, width, height, bit_depth, index)?;
209 }
210 Ok(Self {
211 planes,
212 width,
213 height,
214 bit_depth,
215 signed,
216 })
217 }
218
219 #[must_use]
221 pub fn components(&self) -> u16 {
222 u16::try_from(self.planes.len()).unwrap_or(MAX_JPEG2000_PART1_COMPONENTS)
223 }
224}
225
226#[derive(Debug, Clone, Copy)]
228pub struct J2kLosslessTypedComponentPlane<'a> {
229 pub data: &'a [u8],
231 pub x_rsiz: u8,
233 pub y_rsiz: u8,
235 pub bit_depth: u8,
237 pub signed: bool,
239}
240
241#[derive(Debug, Clone, Copy)]
244pub struct J2kLosslessTypedComponentSamples<'a> {
245 pub planes: &'a [J2kLosslessTypedComponentPlane<'a>],
247 pub width: u32,
249 pub height: u32,
251}
252
253impl<'a> J2kLosslessTypedComponentSamples<'a> {
254 pub fn new(
256 planes: &'a [J2kLosslessTypedComponentPlane<'a>],
257 width: u32,
258 height: u32,
259 ) -> Result<Self, J2kError> {
260 if width == 0 || height == 0 {
261 return Err(J2kError::InvalidSamples {
262 what: "dimensions must be non-zero".to_string(),
263 });
264 }
265 if planes.is_empty() || planes.len() > usize::from(MAX_JPEG2000_PART1_COMPONENTS) {
266 return Err(J2kError::Unsupported(Unsupported {
267 what: "JPEG 2000 lossless typed component-plane encode supports 1-16384 components",
268 }));
269 }
270 for (index, plane) in planes.iter().enumerate() {
271 validate_typed_component_plane_geometry(plane, width, height, index)?;
272 }
273 Ok(Self {
274 planes,
275 width,
276 height,
277 })
278 }
279
280 #[must_use]
282 pub fn components(&self) -> u16 {
283 u16::try_from(self.planes.len()).unwrap_or(MAX_JPEG2000_PART1_COMPONENTS)
284 }
285
286 #[must_use]
288 pub fn max_bit_depth(&self) -> u8 {
289 self.planes
290 .iter()
291 .map(|plane| plane.bit_depth)
292 .max()
293 .unwrap_or(0)
294 }
295
296 #[must_use]
298 pub fn all_components_signed(&self) -> bool {
299 self.planes.iter().all(|plane| plane.signed)
300 }
301}
302
303fn validate_component_plane_geometry(
304 plane: &J2kLosslessComponentPlane<'_>,
305 width: u32,
306 height: u32,
307 bit_depth: u8,
308 index: usize,
309) -> Result<(), J2kError> {
310 if plane.x_rsiz == 0 || plane.y_rsiz == 0 {
311 return Err(J2kError::InvalidSamples {
312 what: format!("component plane {index} sampling factors must be non-zero"),
313 });
314 }
315 let bytes_per_sample = raw_pixel_bytes_per_sample(bit_depth);
316 let component_width = width.div_ceil(u32::from(plane.x_rsiz));
317 let component_height = height.div_ceil(u32::from(plane.y_rsiz));
318 let expected_bytes = (component_width as usize)
319 .checked_mul(component_height as usize)
320 .and_then(|samples| samples.checked_mul(bytes_per_sample))
321 .ok_or(J2kError::DimensionOverflow { width, height })?;
322 if plane.data.len() != expected_bytes {
323 return Err(J2kError::InvalidSamples {
324 what: format!(
325 "component plane {index} data length mismatch: expected {expected_bytes} bytes, got {}",
326 plane.data.len()
327 ),
328 });
329 }
330 Ok(())
331}
332
333fn validate_typed_component_plane_geometry(
334 plane: &J2kLosslessTypedComponentPlane<'_>,
335 width: u32,
336 height: u32,
337 index: usize,
338) -> Result<(), J2kError> {
339 if plane.x_rsiz == 0 || plane.y_rsiz == 0 {
340 return Err(J2kError::InvalidSamples {
341 what: format!("component plane {index} sampling factors must be non-zero"),
342 });
343 }
344 if plane.bit_depth == 0 || plane.bit_depth > MAX_PART1_SAMPLE_BIT_DEPTH {
345 return Err(J2kError::Unsupported(Unsupported {
346 what: "JPEG 2000 lossless typed component-plane encode supports 1-38 bits per sample",
347 }));
348 }
349 let bytes_per_sample = raw_pixel_bytes_per_sample(plane.bit_depth);
350 let component_width = width.div_ceil(u32::from(plane.x_rsiz));
351 let component_height = height.div_ceil(u32::from(plane.y_rsiz));
352 let expected_bytes = (component_width as usize)
353 .checked_mul(component_height as usize)
354 .and_then(|samples| samples.checked_mul(bytes_per_sample))
355 .ok_or(J2kError::DimensionOverflow { width, height })?;
356 if plane.data.len() != expected_bytes {
357 return Err(J2kError::InvalidSamples {
358 what: format!(
359 "component plane {index} data length mismatch: expected {expected_bytes} bytes, got {}",
360 plane.data.len()
361 ),
362 });
363 }
364 Ok(())
365}
366
367#[derive(Debug, Clone, Copy)]
369pub struct J2kLossySamples<'a> {
370 pub data: &'a [u8],
372 pub width: u32,
374 pub height: u32,
376 pub components: u16,
379 pub bit_depth: u8,
381 pub signed: bool,
383}
384
385impl<'a> J2kLossySamples<'a> {
386 pub fn new(
388 data: &'a [u8],
389 width: u32,
390 height: u32,
391 components: u16,
392 bit_depth: u8,
393 signed: bool,
394 ) -> Result<Self, J2kError> {
395 let geometry = validate_sample_geometry(SampleGeometryRequest {
396 data,
397 width,
398 height,
399 components,
400 bit_depth,
401 max_bit_depth: MAX_PART1_SAMPLE_BIT_DEPTH,
402 component_what: "JPEG 2000 lossy encode supports 1-16384 component samples",
403 bit_depth_what: "JPEG 2000 lossy encode supports 1-38 bits per sample",
404 })?;
405 debug_assert_eq!(geometry.expected_bytes, data.len());
406 Ok(Self {
407 data,
408 width,
409 height,
410 components,
411 bit_depth,
412 signed,
413 })
414 }
415}