Skip to main content

j2k_transcode/
resident.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Backend-neutral resident buffer descriptors for transcode handoff.
4
5use core::fmt;
6use core::marker::PhantomData;
7
8use j2k_core::{BackendKind, DeviceMemoryRange};
9
10/// Error returned by resident transcode handoff descriptor constructors.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ResidentHandoffError {
13    /// Buffer range length is zero.
14    EmptyRange,
15    /// Buffer range offset plus length overflowed.
16    OffsetOverflow,
17    /// Buffer range exceeds the caller-supplied allocation length.
18    RangeExceedsAllocation,
19    /// The buffer range belongs to a different backend than the descriptor requires.
20    BackendMismatch {
21        /// Backend required by the descriptor.
22        expected: BackendKind,
23        /// Backend carried by the memory range.
24        actual: BackendKind,
25    },
26    /// Image or component dimensions must be nonzero.
27    ZeroDimension,
28    /// Component sampling factors must be nonzero.
29    ZeroSampling,
30    /// Bit depth must be in the supported 1..=32 range.
31    InvalidBitDepth,
32    /// Byte stride or element size must be nonzero.
33    ZeroByteStride,
34    /// Row layout metadata exceeds the resident buffer range.
35    LayoutExceedsBuffer,
36    /// Codestream byte length exceeds the resident buffer capacity.
37    CodestreamExceedsCapacity,
38}
39
40impl fmt::Display for ResidentHandoffError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::EmptyRange => f.write_str("resident buffer range is empty"),
44            Self::OffsetOverflow => f.write_str("resident buffer range offset overflows"),
45            Self::RangeExceedsAllocation => {
46                f.write_str("resident buffer range exceeds allocation length")
47            }
48            Self::BackendMismatch { expected, actual } => write!(
49                f,
50                "resident buffer backend mismatch: expected {expected:?}, got {actual:?}"
51            ),
52            Self::ZeroDimension => f.write_str("resident component dimensions must be nonzero"),
53            Self::ZeroSampling => f.write_str("resident component sampling must be nonzero"),
54            Self::InvalidBitDepth => f.write_str("resident sample bit depth must be 1..=32"),
55            Self::ZeroByteStride => f.write_str("resident byte stride must be nonzero"),
56            Self::LayoutExceedsBuffer => f.write_str("resident row layout exceeds buffer range"),
57            Self::CodestreamExceedsCapacity => {
58                f.write_str("resident codestream byte length exceeds buffer capacity")
59            }
60        }
61    }
62}
63
64impl std::error::Error for ResidentHandoffError {}
65
66/// Borrowed, backend-visible memory range with a caller-owned lifetime.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct ResidentBufferRef<'a> {
69    memory: DeviceMemoryRange,
70    _lifetime: PhantomData<&'a ()>,
71}
72
73impl ResidentBufferRef<'_> {
74    /// Build a borrowed resident buffer reference after validating basic range shape.
75    pub fn new(memory: DeviceMemoryRange) -> Result<Self, ResidentHandoffError> {
76        if memory.len == 0 {
77            return Err(ResidentHandoffError::EmptyRange);
78        }
79        memory
80            .offset
81            .checked_add(memory.len)
82            .ok_or(ResidentHandoffError::OffsetOverflow)?;
83        Ok(Self {
84            memory,
85            _lifetime: PhantomData,
86        })
87    }
88
89    /// Build a borrowed resident buffer reference and validate it against an allocation length.
90    pub fn with_allocation_len(
91        memory: DeviceMemoryRange,
92        allocation_len: usize,
93    ) -> Result<Self, ResidentHandoffError> {
94        let buffer = Self::new(memory)?;
95        let end = memory
96            .offset
97            .checked_add(memory.len)
98            .ok_or(ResidentHandoffError::OffsetOverflow)?;
99        if end > allocation_len {
100            return Err(ResidentHandoffError::RangeExceedsAllocation);
101        }
102        Ok(buffer)
103    }
104
105    /// Return the opaque memory range.
106    pub const fn memory_range(&self) -> DeviceMemoryRange {
107        self.memory
108    }
109
110    /// Backend that owns this resident range.
111    pub const fn backend(&self) -> BackendKind {
112        self.memory.backend
113    }
114
115    /// Byte length of the resident range.
116    pub const fn byte_len(&self) -> usize {
117        self.memory.len
118    }
119
120    fn require_backend(self, backend: BackendKind) -> Result<Self, ResidentHandoffError> {
121        if self.memory.backend == backend {
122            Ok(self)
123        } else {
124            Err(ResidentHandoffError::BackendMismatch {
125                expected: backend,
126                actual: self.memory.backend,
127            })
128        }
129    }
130}
131
132/// Component sampling factors preserved through JPEG-to-HTJ2K transcode.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub struct ResidentSampling {
135    /// JPEG 2000 `XRsiz` horizontal sampling factor.
136    pub x_rsiz: u8,
137    /// JPEG 2000 `YRsiz` vertical sampling factor.
138    pub y_rsiz: u8,
139}
140
141impl ResidentSampling {
142    /// Build nonzero sampling metadata.
143    pub const fn new(x_rsiz: u8, y_rsiz: u8) -> Result<Self, ResidentHandoffError> {
144        if x_rsiz == 0 || y_rsiz == 0 {
145            return Err(ResidentHandoffError::ZeroSampling);
146        }
147        Ok(Self { x_rsiz, y_rsiz })
148    }
149}
150
151/// Sample precision metadata for resident handoff buffers.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub struct ResidentSampleInfo {
154    /// Component precision in bits.
155    pub bit_depth: u8,
156    /// Whether sample or coefficient values are signed.
157    pub signed: bool,
158}
159
160impl ResidentSampleInfo {
161    /// Build sample metadata with a supported bit depth.
162    pub const fn new(bit_depth: u8, signed: bool) -> Result<Self, ResidentHandoffError> {
163        if bit_depth == 0 || bit_depth > 32 {
164            return Err(ResidentHandoffError::InvalidBitDepth);
165        }
166        Ok(Self { bit_depth, signed })
167    }
168}
169
170/// Color interpretation carried by a resident transcode handoff buffer.
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
172pub enum ResidentColorModel {
173    /// Color interpretation is unknown or intentionally deferred.
174    Unknown,
175    /// Single-component grayscale.
176    Grayscale,
177    /// RGB-like component ordering.
178    Rgb,
179    /// JPEG YCbCr/YBR component ordering.
180    YCbCr,
181    /// RGBA-like component ordering.
182    Rgba,
183}
184
185/// Per-component geometry shared by resident handoff descriptors.
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub struct ResidentComponentGeometry {
188    /// Component index in source/component order.
189    pub component_index: usize,
190    /// Native component width in samples.
191    pub width: u32,
192    /// Native component height in samples.
193    pub height: u32,
194    /// Component sampling factors.
195    pub sampling: ResidentSampling,
196}
197
198impl ResidentComponentGeometry {
199    /// Build component geometry with nonzero dimensions and sampling.
200    pub const fn new(
201        component_index: usize,
202        width: u32,
203        height: u32,
204        sampling: ResidentSampling,
205    ) -> Result<Self, ResidentHandoffError> {
206        if width == 0 || height == 0 {
207            return Err(ResidentHandoffError::ZeroDimension);
208        }
209        Ok(Self {
210            component_index,
211            width,
212            height,
213            sampling,
214        })
215    }
216}
217
218/// Coefficient ordering used by a resident JPEG DCT-grid buffer.
219#[derive(Debug, Clone, Copy, PartialEq, Eq)]
220pub enum ResidentDctCoefficientOrder {
221    /// Natural raster order within each 8x8 block.
222    Natural,
223    /// JPEG zig-zag order within each 8x8 block.
224    ZigZag,
225}
226
227/// Resident JPEG DCT coefficient grid descriptor.
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub struct ResidentJpegDctGrid<'a> {
230    /// Backend-visible coefficient buffer.
231    pub buffer: ResidentBufferRef<'a>,
232    /// Component geometry and sampling.
233    pub component: ResidentComponentGeometry,
234    /// Coefficient precision and signedness.
235    pub sample: ResidentSampleInfo,
236    /// Color interpretation of the source image/component set.
237    pub color: ResidentColorModel,
238    /// Padded DCT block columns.
239    pub block_cols: u32,
240    /// Padded DCT block rows.
241    pub block_rows: u32,
242    /// Byte stride between consecutive block rows in the resident buffer.
243    pub row_pitch_bytes: usize,
244    /// Bytes per coefficient in the resident buffer.
245    pub bytes_per_coefficient: usize,
246    /// Coefficient order within each DCT block.
247    pub coefficient_order: ResidentDctCoefficientOrder,
248}
249
250impl<'a> ResidentJpegDctGrid<'a> {
251    /// Build a resident JPEG DCT-grid descriptor.
252    pub fn new(
253        buffer: ResidentBufferRef<'a>,
254        component: ResidentComponentGeometry,
255        sample: ResidentSampleInfo,
256        color: ResidentColorModel,
257        layout: ResidentDctGridLayout,
258    ) -> Result<Self, ResidentHandoffError> {
259        if layout.block_cols == 0 || layout.block_rows == 0 {
260            return Err(ResidentHandoffError::ZeroDimension);
261        }
262        if layout.row_pitch_bytes == 0 || layout.bytes_per_coefficient == 0 {
263            return Err(ResidentHandoffError::ZeroByteStride);
264        }
265        let row_coefficients = usize::try_from(layout.block_cols)
266            .ok()
267            .and_then(|cols| cols.checked_mul(64))
268            .ok_or(ResidentHandoffError::OffsetOverflow)?;
269        validate_row_layout_fits_buffer(
270            buffer,
271            row_coefficients,
272            layout.block_rows,
273            layout.row_pitch_bytes,
274            layout.bytes_per_coefficient,
275        )?;
276        Ok(Self {
277            buffer,
278            component,
279            sample,
280            color,
281            block_cols: layout.block_cols,
282            block_rows: layout.block_rows,
283            row_pitch_bytes: layout.row_pitch_bytes,
284            bytes_per_coefficient: layout.bytes_per_coefficient,
285            coefficient_order: layout.coefficient_order,
286        })
287    }
288
289    /// Validate this descriptor is backed by the expected backend.
290    pub fn require_backend(self, backend: BackendKind) -> Result<Self, ResidentHandoffError> {
291        self.buffer.require_backend(backend)?;
292        Ok(self)
293    }
294}
295
296/// Layout metadata for a resident JPEG DCT-grid descriptor.
297#[derive(Debug, Clone, Copy, PartialEq, Eq)]
298pub struct ResidentDctGridLayout {
299    /// Padded DCT block columns.
300    pub block_cols: u32,
301    /// Padded DCT block rows.
302    pub block_rows: u32,
303    /// Byte stride between consecutive block rows in the resident buffer.
304    pub row_pitch_bytes: usize,
305    /// Bytes per coefficient in the resident buffer.
306    pub bytes_per_coefficient: usize,
307    /// Coefficient order within each DCT block.
308    pub coefficient_order: ResidentDctCoefficientOrder,
309}
310
311/// Wavelet subband represented by a resident DWT buffer.
312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum ResidentDwtSubbandKind {
314    /// Low-low subband.
315    LowLow,
316    /// High-low subband.
317    HighLow,
318    /// Low-high subband.
319    LowHigh,
320    /// High-high subband.
321    HighHigh,
322}
323
324/// Resident projected DWT subband descriptor.
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub struct ResidentDwtSubband<'a> {
327    /// Backend-visible subband buffer.
328    pub buffer: ResidentBufferRef<'a>,
329    /// Component geometry and sampling.
330    pub component: ResidentComponentGeometry,
331    /// Coefficient precision and signedness.
332    pub sample: ResidentSampleInfo,
333    /// Color interpretation of the source image/component set.
334    pub color: ResidentColorModel,
335    /// DWT decomposition level.
336    pub level: u8,
337    /// Subband kind within the level.
338    pub subband: ResidentDwtSubbandKind,
339    /// Native subband width in coefficients.
340    pub width: u32,
341    /// Native subband height in coefficients.
342    pub height: u32,
343    /// Byte stride between subband rows.
344    pub row_pitch_bytes: usize,
345    /// Bytes per coefficient in the resident buffer.
346    pub bytes_per_coefficient: usize,
347}
348
349impl<'a> ResidentDwtSubband<'a> {
350    /// Build a resident DWT subband descriptor.
351    pub fn new(
352        buffer: ResidentBufferRef<'a>,
353        component: ResidentComponentGeometry,
354        sample: ResidentSampleInfo,
355        color: ResidentColorModel,
356        layout: ResidentDwtSubbandLayout,
357    ) -> Result<Self, ResidentHandoffError> {
358        if layout.width == 0 || layout.height == 0 {
359            return Err(ResidentHandoffError::ZeroDimension);
360        }
361        if layout.row_pitch_bytes == 0 || layout.bytes_per_coefficient == 0 {
362            return Err(ResidentHandoffError::ZeroByteStride);
363        }
364        validate_row_layout_fits_buffer(
365            buffer,
366            usize::try_from(layout.width).map_err(|_| ResidentHandoffError::OffsetOverflow)?,
367            layout.height,
368            layout.row_pitch_bytes,
369            layout.bytes_per_coefficient,
370        )?;
371        Ok(Self {
372            buffer,
373            component,
374            sample,
375            color,
376            level: layout.level,
377            subband: layout.subband,
378            width: layout.width,
379            height: layout.height,
380            row_pitch_bytes: layout.row_pitch_bytes,
381            bytes_per_coefficient: layout.bytes_per_coefficient,
382        })
383    }
384
385    /// Validate this descriptor is backed by the expected backend.
386    pub fn require_backend(self, backend: BackendKind) -> Result<Self, ResidentHandoffError> {
387        self.buffer.require_backend(backend)?;
388        Ok(self)
389    }
390}
391
392fn validate_row_layout_fits_buffer(
393    buffer: ResidentBufferRef<'_>,
394    row_values: usize,
395    rows: u32,
396    row_pitch_bytes: usize,
397    bytes_per_value: usize,
398) -> Result<(), ResidentHandoffError> {
399    let row_bytes = row_values
400        .checked_mul(bytes_per_value)
401        .ok_or(ResidentHandoffError::OffsetOverflow)?;
402    if row_pitch_bytes < row_bytes {
403        return Err(ResidentHandoffError::LayoutExceedsBuffer);
404    }
405    let rows = usize::try_from(rows).map_err(|_| ResidentHandoffError::OffsetOverflow)?;
406    let last_row_offset = rows
407        .saturating_sub(1)
408        .checked_mul(row_pitch_bytes)
409        .ok_or(ResidentHandoffError::OffsetOverflow)?;
410    let required_len = last_row_offset
411        .checked_add(row_bytes)
412        .ok_or(ResidentHandoffError::OffsetOverflow)?;
413    if required_len > buffer.byte_len() {
414        return Err(ResidentHandoffError::LayoutExceedsBuffer);
415    }
416    Ok(())
417}
418
419/// Layout metadata for a resident DWT subband descriptor.
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
421pub struct ResidentDwtSubbandLayout {
422    /// DWT decomposition level.
423    pub level: u8,
424    /// Subband kind within the level.
425    pub subband: ResidentDwtSubbandKind,
426    /// Native subband width in coefficients.
427    pub width: u32,
428    /// Native subband height in coefficients.
429    pub height: u32,
430    /// Byte stride between subband rows.
431    pub row_pitch_bytes: usize,
432    /// Bytes per coefficient in the resident buffer.
433    pub bytes_per_coefficient: usize,
434}
435
436/// Resident codestream buffer descriptor.
437#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub struct ResidentCodestreamBuffer<'a> {
439    /// Backend-visible codestream buffer.
440    pub buffer: ResidentBufferRef<'a>,
441    /// Number of valid codestream bytes.
442    pub byte_len: usize,
443    /// Allocated codestream capacity.
444    pub capacity: usize,
445}
446
447impl<'a> ResidentCodestreamBuffer<'a> {
448    /// Build a resident codestream descriptor and validate byte length.
449    pub fn new(
450        buffer: ResidentBufferRef<'a>,
451        byte_len: usize,
452        capacity: usize,
453    ) -> Result<Self, ResidentHandoffError> {
454        if byte_len > capacity || capacity > buffer.byte_len() {
455            return Err(ResidentHandoffError::CodestreamExceedsCapacity);
456        }
457        Ok(Self {
458            buffer,
459            byte_len,
460            capacity,
461        })
462    }
463
464    /// Validate this descriptor is backed by the expected backend.
465    pub fn require_backend(self, backend: BackendKind) -> Result<Self, ResidentHandoffError> {
466        self.buffer.require_backend(backend)?;
467        Ok(self)
468    }
469}