Skip to main content

j2k/encode/
samples.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use 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/// Borrowed interleaved samples and image geometry for lossless encoding.
15#[derive(Debug, Clone, Copy)]
16pub struct J2kLosslessSamples<'a> {
17    /// Interleaved sample bytes.
18    pub data: &'a [u8],
19    /// Image width in pixels.
20    pub width: u32,
21    /// Image height in pixels.
22    pub height: u32,
23    /// Component count. Component counts beyond four are encoded as independent
24    /// component planes without a multi-component transform.
25    pub components: u16,
26    /// Significant bits per component sample.
27    pub bit_depth: u8,
28    /// Whether component samples are signed.
29    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    /// Validate and construct a sample descriptor.
102    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/// Rectangular region-of-interest request for lossless JPEG 2000 maxshift
133/// encoding.
134///
135/// The rectangle is expressed in full-resolution image pixels. All regions for
136/// one component must use the same non-zero `shift`, because JPEG 2000 stores
137/// one RGN maxshift value per component.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
139pub struct J2kRoiRegion {
140    /// Component index to which the ROI applies.
141    pub component: u16,
142    /// Left edge in image pixels.
143    pub x: u32,
144    /// Top edge in image pixels.
145    pub y: u32,
146    /// Width in image pixels.
147    pub width: u32,
148    /// Height in image pixels.
149    pub height: u32,
150    /// Maxshift value to write for this component.
151    pub shift: u8,
152}
153
154/// Borrowed samples for one lossless component plane.
155#[derive(Debug, Clone, Copy)]
156pub struct J2kLosslessComponentPlane<'a> {
157    /// Row-major little-endian samples for this component's own SIZ grid.
158    pub data: &'a [u8],
159    /// Horizontal SIZ sampling factor (`XRsiz`).
160    pub x_rsiz: u8,
161    /// Vertical SIZ sampling factor (`YRsiz`).
162    pub y_rsiz: u8,
163}
164
165/// Borrowed component-plane samples and reference-grid image geometry for
166/// lossless encoding.
167#[derive(Debug, Clone, Copy)]
168pub struct J2kLosslessComponentSamples<'a> {
169    /// Component planes in codestream order.
170    pub planes: &'a [J2kLosslessComponentPlane<'a>],
171    /// Reference-grid image width in pixels.
172    pub width: u32,
173    /// Reference-grid image height in pixels.
174    pub height: u32,
175    /// Significant bits per component sample. Mixed component bit depths are
176    /// not yet supported by the encode facade.
177    pub bit_depth: u8,
178    /// Whether every component sample is signed. Mixed signedness is not yet
179    /// supported by the encode facade.
180    pub signed: bool,
181}
182
183impl<'a> J2kLosslessComponentSamples<'a> {
184    /// Validate and construct a component-plane sample descriptor.
185    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    /// Return the component count.
220    #[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/// Borrowed samples for one typed lossless component plane.
227#[derive(Debug, Clone, Copy)]
228pub struct J2kLosslessTypedComponentPlane<'a> {
229    /// Row-major little-endian samples for this component's own SIZ grid.
230    pub data: &'a [u8],
231    /// Horizontal SIZ sampling factor (`XRsiz`).
232    pub x_rsiz: u8,
233    /// Vertical SIZ sampling factor (`YRsiz`).
234    pub y_rsiz: u8,
235    /// Significant bits per sample for this component.
236    pub bit_depth: u8,
237    /// Whether samples in this component are signed.
238    pub signed: bool,
239}
240
241/// Borrowed typed component-plane samples and reference-grid image geometry for
242/// lossless encoding.
243#[derive(Debug, Clone, Copy)]
244pub struct J2kLosslessTypedComponentSamples<'a> {
245    /// Component planes in codestream order.
246    pub planes: &'a [J2kLosslessTypedComponentPlane<'a>],
247    /// Reference-grid image width in pixels.
248    pub width: u32,
249    /// Reference-grid image height in pixels.
250    pub height: u32,
251}
252
253impl<'a> J2kLosslessTypedComponentSamples<'a> {
254    /// Validate and construct a typed component-plane sample descriptor.
255    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    /// Return the component count.
281    #[must_use]
282    pub fn components(&self) -> u16 {
283        u16::try_from(self.planes.len()).unwrap_or(MAX_JPEG2000_PART1_COMPONENTS)
284    }
285
286    /// Return the maximum significant bit depth across all components.
287    #[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    /// Return whether every component is signed.
297    #[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/// Borrowed interleaved samples and image geometry for lossy encoding.
368#[derive(Debug, Clone, Copy)]
369pub struct J2kLossySamples<'a> {
370    /// Interleaved sample bytes.
371    pub data: &'a [u8],
372    /// Image width in pixels.
373    pub width: u32,
374    /// Image height in pixels.
375    pub height: u32,
376    /// Component count. Component counts beyond four are encoded as independent
377    /// component planes without a multi-component transform.
378    pub components: u16,
379    /// Significant bits per component sample.
380    pub bit_depth: u8,
381    /// Whether component samples are signed.
382    pub signed: bool,
383}
384
385impl<'a> J2kLossySamples<'a> {
386    /// Validate and construct a lossy sample descriptor.
387    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}