Skip to main content

j2k_jpeg/
output_buffer.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Reusable host output buffers for JPEG tile decode.
4
5use alloc::vec::Vec;
6use j2k_core::{
7    strided_output_len_capped, try_host_vec_filled, validate_strided_output_buffer, BufferError,
8    HostAllocationError, PixelFormat, DEFAULT_MAX_HOST_ALLOCATION_BYTES,
9};
10
11const OUTPUT_BUFFER_ALLOCATION: &str = "JPEG output buffer";
12
13/// Caller-owned reusable host pixel buffer.
14///
15/// The buffer uses a tight stride by default and can be resized across viewport
16/// reads. Resizing to a same-or-smaller byte requirement keeps existing vector
17/// capacity, so callers can reuse allocations while still passing ordinary
18/// `&mut [u8]` slices into decode APIs.
19#[derive(Debug)]
20pub struct JpegOutputBuffer {
21    bytes: Vec<u8>,
22    dimensions: (u32, u32),
23    stride: usize,
24    fmt: PixelFormat,
25}
26
27impl JpegOutputBuffer {
28    /// Create a tightly packed output buffer for `dimensions` and `fmt`.
29    ///
30    /// Uses the shared default host allocation cap.
31    ///
32    /// # Errors
33    /// Returns [`BufferError`] if the requested shape overflows byte counts,
34    /// exceeds the default host allocation cap, or cannot be reserved.
35    pub fn new(dimensions: (u32, u32), fmt: PixelFormat) -> Result<Self, BufferError> {
36        Self::new_with_max_bytes(dimensions, fmt, DEFAULT_MAX_HOST_ALLOCATION_BYTES)
37    }
38
39    /// Create a tightly packed output buffer with an explicit allocation cap.
40    ///
41    /// # Errors
42    /// Returns [`BufferError`] if the requested shape overflows byte counts,
43    /// exceeds `max_bytes`, or cannot be reserved.
44    fn new_with_max_bytes(
45        dimensions: (u32, u32),
46        fmt: PixelFormat,
47        max_bytes: usize,
48    ) -> Result<Self, BufferError> {
49        let stride = tight_stride(dimensions.0, fmt)?;
50        Self::with_stride_with_max_bytes(dimensions, stride, fmt, max_bytes)
51    }
52
53    /// Create an output buffer with an explicit row stride.
54    ///
55    /// Uses the shared default host allocation cap.
56    ///
57    /// # Errors
58    /// Returns [`BufferError`] if the stride is too small, sizes overflow, the
59    /// allocation exceeds the default host allocation cap, or reservation fails.
60    pub fn with_stride(
61        dimensions: (u32, u32),
62        stride: usize,
63        fmt: PixelFormat,
64    ) -> Result<Self, BufferError> {
65        Self::with_stride_with_max_bytes(dimensions, stride, fmt, DEFAULT_MAX_HOST_ALLOCATION_BYTES)
66    }
67
68    /// Create an output buffer with explicit row stride and allocation cap.
69    ///
70    /// # Errors
71    /// Returns [`BufferError`] if the stride is too small, sizes overflow, the
72    /// allocation exceeds `max_bytes`, or reservation fails.
73    fn with_stride_with_max_bytes(
74        dimensions: (u32, u32),
75        stride: usize,
76        fmt: PixelFormat,
77        max_bytes: usize,
78    ) -> Result<Self, BufferError> {
79        let len = strided_output_len_capped(
80            dimensions,
81            stride,
82            fmt,
83            max_bytes,
84            OUTPUT_BUFFER_ALLOCATION,
85        )?;
86        validate_strided_output_buffer(dimensions, len, stride, fmt)?;
87        Ok(Self {
88            bytes: try_output_bytes(len, max_bytes)?,
89            dimensions,
90            stride,
91            fmt,
92        })
93    }
94
95    /// Resize to a tightly packed output shape.
96    ///
97    /// Uses the shared default host allocation cap.
98    ///
99    /// Existing decoded bytes may be discarded when the allocation must grow
100    /// or stale capacity exceeds the active cap.
101    ///
102    /// # Errors
103    /// Returns [`BufferError`] if the requested shape overflows byte counts,
104    /// exceeds the default host allocation cap, or cannot be reserved.
105    pub fn resize(&mut self, dimensions: (u32, u32), fmt: PixelFormat) -> Result<(), BufferError> {
106        self.resize_with_max_bytes(dimensions, fmt, DEFAULT_MAX_HOST_ALLOCATION_BYTES)
107    }
108
109    /// Resize to a tightly packed output shape with an explicit allocation cap.
110    ///
111    /// # Errors
112    /// Returns [`BufferError`] if the requested shape overflows byte counts,
113    /// exceeds `max_bytes`, or cannot be reserved.
114    fn resize_with_max_bytes(
115        &mut self,
116        dimensions: (u32, u32),
117        fmt: PixelFormat,
118        max_bytes: usize,
119    ) -> Result<(), BufferError> {
120        let stride = tight_stride(dimensions.0, fmt)?;
121        self.resize_with_stride_with_max_bytes(dimensions, stride, fmt, max_bytes)
122    }
123
124    /// Resize with an explicit row stride.
125    ///
126    /// Uses the shared default host allocation cap.
127    /// Existing decoded bytes may be discarded when storage must be replaced.
128    ///
129    /// # Errors
130    /// Returns [`BufferError`] if the stride is too small, sizes overflow, the
131    /// allocation exceeds the default host allocation cap, or reservation fails.
132    pub fn resize_with_stride(
133        &mut self,
134        dimensions: (u32, u32),
135        stride: usize,
136        fmt: PixelFormat,
137    ) -> Result<(), BufferError> {
138        self.resize_with_stride_with_max_bytes(
139            dimensions,
140            stride,
141            fmt,
142            DEFAULT_MAX_HOST_ALLOCATION_BYTES,
143        )
144    }
145
146    /// Resize with explicit row stride and allocation cap.
147    /// Existing decoded bytes may be discarded when storage must be replaced.
148    ///
149    /// # Errors
150    /// Returns [`BufferError`] if the stride is too small, sizes overflow, the
151    /// allocation exceeds `max_bytes`, or reservation fails.
152    fn resize_with_stride_with_max_bytes(
153        &mut self,
154        dimensions: (u32, u32),
155        stride: usize,
156        fmt: PixelFormat,
157        max_bytes: usize,
158    ) -> Result<(), BufferError> {
159        let len = strided_output_len_capped(
160            dimensions,
161            stride,
162            fmt,
163            max_bytes,
164            OUTPUT_BUFFER_ALLOCATION,
165        )?;
166        validate_strided_output_buffer(dimensions, len, stride, fmt)?;
167        if self.bytes.capacity() > max_bytes || len > self.bytes.capacity() {
168            self.clear_storage();
169            self.bytes = try_output_bytes(len, max_bytes)?;
170        } else {
171            self.bytes.resize(len, 0);
172        }
173        self.dimensions = dimensions;
174        self.stride = stride;
175        self.fmt = fmt;
176        Ok(())
177    }
178
179    fn clear_storage(&mut self) {
180        self.bytes = Vec::new();
181        self.dimensions = (0, 0);
182        self.stride = 0;
183    }
184
185    /// Borrow the decoded pixel bytes.
186    #[must_use]
187    pub fn as_slice(&self) -> &[u8] {
188        &self.bytes
189    }
190
191    /// Mutably borrow the decoded pixel bytes.
192    #[must_use]
193    pub fn as_mut_slice(&mut self) -> &mut [u8] {
194        &mut self.bytes
195    }
196
197    /// Current dimensions in pixels.
198    #[must_use]
199    pub fn dimensions(&self) -> (u32, u32) {
200        self.dimensions
201    }
202
203    /// Current row stride in bytes.
204    #[must_use]
205    pub fn stride(&self) -> usize {
206        self.stride
207    }
208
209    /// Current pixel format.
210    #[must_use]
211    pub fn pixel_format(&self) -> PixelFormat {
212        self.fmt
213    }
214
215    /// Current logical byte length.
216    #[must_use]
217    pub fn len(&self) -> usize {
218        self.bytes.len()
219    }
220
221    /// Whether the logical byte length is zero.
222    #[must_use]
223    pub fn is_empty(&self) -> bool {
224        self.bytes.is_empty()
225    }
226
227    /// Retained vector capacity in bytes.
228    #[must_use]
229    pub fn capacity(&self) -> usize {
230        self.bytes.capacity()
231    }
232}
233
234fn host_allocation_error(error: HostAllocationError, what: &'static str) -> BufferError {
235    BufferError::HostAllocationFailed {
236        bytes: error.requested_bytes(),
237        what,
238    }
239}
240
241fn try_output_bytes(len: usize, max_bytes: usize) -> Result<Vec<u8>, BufferError> {
242    let bytes = try_host_vec_filled(len, 0)
243        .map_err(|error| host_allocation_error(error, OUTPUT_BUFFER_ALLOCATION))?;
244    ensure_output_capacity(bytes.capacity(), max_bytes)?;
245    Ok(bytes)
246}
247
248fn ensure_output_capacity(capacity: usize, max_bytes: usize) -> Result<(), BufferError> {
249    if capacity > max_bytes {
250        return Err(BufferError::AllocationTooLarge {
251            requested: capacity,
252            cap: max_bytes,
253            what: OUTPUT_BUFFER_ALLOCATION,
254        });
255    }
256    Ok(())
257}
258
259fn tight_stride(width: u32, fmt: PixelFormat) -> Result<usize, BufferError> {
260    (width as usize)
261        .checked_mul(fmt.bytes_per_pixel())
262        .ok_or(BufferError::SizeOverflow {
263            what: "tight JPEG output stride",
264        })
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    const HUGE_DIMENSIONS: (u32, u32) = (65_500, 65_500);
272
273    fn assert_allocation_too_large(error: &BufferError) {
274        assert!(
275            matches!(
276                error,
277                BufferError::AllocationTooLarge {
278                    requested,
279                    cap: DEFAULT_MAX_HOST_ALLOCATION_BYTES,
280                    what: "JPEG output buffer",
281                } if *requested > DEFAULT_MAX_HOST_ALLOCATION_BYTES
282            ),
283            "expected AllocationTooLarge, got {error:?}"
284        );
285    }
286
287    #[test]
288    fn new_rejects_huge_output_before_allocation() {
289        let err = JpegOutputBuffer::new(HUGE_DIMENSIONS, PixelFormat::Rgba16)
290            .expect_err("huge output must be capped");
291        assert_allocation_too_large(&err);
292    }
293
294    #[test]
295    fn with_stride_rejects_huge_output_before_allocation() {
296        let stride = HUGE_DIMENSIONS.0 as usize * PixelFormat::Rgba16.bytes_per_pixel();
297        let err = JpegOutputBuffer::with_stride(HUGE_DIMENSIONS, stride, PixelFormat::Rgba16)
298            .expect_err("huge output must be capped");
299        assert_allocation_too_large(&err);
300    }
301
302    #[test]
303    fn resize_rejects_huge_output_before_allocation() {
304        let mut buffer =
305            JpegOutputBuffer::new((1, 1), PixelFormat::Rgba8).expect("small output buffer");
306        let err = buffer
307            .resize(HUGE_DIMENSIONS, PixelFormat::Rgba16)
308            .expect_err("huge output must be capped");
309        assert_allocation_too_large(&err);
310        assert_eq!(buffer.dimensions(), (1, 1));
311    }
312
313    #[test]
314    fn resize_with_stride_rejects_huge_output_before_allocation() {
315        let mut buffer =
316            JpegOutputBuffer::new((1, 1), PixelFormat::Rgba8).expect("small output buffer");
317        let stride = HUGE_DIMENSIONS.0 as usize * PixelFormat::Rgba16.bytes_per_pixel();
318        let err = buffer
319            .resize_with_stride(HUGE_DIMENSIONS, stride, PixelFormat::Rgba16)
320            .expect_err("huge output must be capped");
321        assert_allocation_too_large(&err);
322        assert_eq!(buffer.dimensions(), (1, 1));
323    }
324
325    #[test]
326    fn explicit_max_bytes_helpers_enforce_smaller_caps() {
327        let err = JpegOutputBuffer::new_with_max_bytes((2, 2), PixelFormat::Rgba8, 15)
328            .expect_err("caller cap should be enforced");
329        assert!(matches!(
330            err,
331            BufferError::AllocationTooLarge {
332                requested: 16,
333                cap: 15,
334                what: "JPEG output buffer",
335            }
336        ));
337    }
338
339    #[test]
340    fn allocator_failure_keeps_its_public_typed_category() {
341        let error = host_allocation_error(
342            HostAllocationError::for_elements::<u16>(2048),
343            OUTPUT_BUFFER_ALLOCATION,
344        );
345        assert_eq!(
346            error,
347            BufferError::HostAllocationFailed {
348                bytes: 4096,
349                what: OUTPUT_BUFFER_ALLOCATION,
350            }
351        );
352    }
353
354    #[test]
355    fn resize_drops_stale_capacity_before_using_a_smaller_cap() {
356        let mut buffer =
357            JpegOutputBuffer::new_with_max_bytes((4, 4), PixelFormat::Rgba8, 64).unwrap();
358        assert!(buffer.capacity() >= 64);
359        buffer
360            .resize_with_max_bytes((1, 1), PixelFormat::Rgba8, 4)
361            .expect("stale capacity must be replaced");
362        assert_eq!(buffer.dimensions(), (1, 1));
363        assert_eq!(buffer.len(), 4);
364        assert!(buffer.capacity() <= 4);
365    }
366
367    #[test]
368    fn growth_replaces_disposable_decoded_contents() {
369        let mut buffer = JpegOutputBuffer::new((1, 1), PixelFormat::Gray8).unwrap();
370        buffer.as_mut_slice().fill(0xa5);
371        buffer.resize((2, 1), PixelFormat::Gray8).unwrap();
372        assert_eq!(buffer.as_slice(), [0, 0]);
373    }
374
375    #[test]
376    fn actual_allocator_capacity_is_checked_against_the_cap() {
377        assert!(matches!(
378            ensure_output_capacity(17, 16),
379            Err(BufferError::AllocationTooLarge {
380                requested: 17,
381                cap: 16,
382                what: OUTPUT_BUFFER_ALLOCATION,
383            })
384        ));
385    }
386}