1use 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#[derive(Debug)]
20pub struct JpegOutputBuffer {
21 bytes: Vec<u8>,
22 dimensions: (u32, u32),
23 stride: usize,
24 fmt: PixelFormat,
25}
26
27impl JpegOutputBuffer {
28 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 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 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 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 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 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 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 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 #[must_use]
187 pub fn as_slice(&self) -> &[u8] {
188 &self.bytes
189 }
190
191 #[must_use]
193 pub fn as_mut_slice(&mut self) -> &mut [u8] {
194 &mut self.bytes
195 }
196
197 #[must_use]
199 pub fn dimensions(&self) -> (u32, u32) {
200 self.dimensions
201 }
202
203 #[must_use]
205 pub fn stride(&self) -> usize {
206 self.stride
207 }
208
209 #[must_use]
211 pub fn pixel_format(&self) -> PixelFormat {
212 self.fmt
213 }
214
215 #[must_use]
217 pub fn len(&self) -> usize {
218 self.bytes.len()
219 }
220
221 #[must_use]
223 pub fn is_empty(&self) -> bool {
224 self.bytes.is_empty()
225 }
226
227 #[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}