Skip to main content

j2k_jpeg/internal/
scratch.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Reusable scratch buffers for the decode path.
4//!
5//! A [`ScratchPool`] owns every `Vec` that the sequential scan decoder
6//! would otherwise allocate on each call: the three rolling MCU stripe
7//! buffers, the per-component DC predictor, the chroma upsample rows, and
8//! the RGB row buffers used by [`j2k_core::RowSink`] drivers.
9//!
10//! Use [`Decoder::decode_into_with_scratch`](crate::Decoder::decode_into_with_scratch)
11//! / [`decode_rows_with_scratch`](crate::Decoder::decode_rows_with_scratch)
12//! with a single long-lived pool to pay the allocation cost once across a
13//! tile batch. Same-shape and cap-valid smaller requests reuse capacity.
14//! Growth discards disposable retained storage first so a reallocation never
15//! owns the stale and replacement buffers at the same time.
16
17use crate::allocation::{
18    checked_add_allocation_bytes, checked_allocation_bytes, try_reserve_for_len_with_live_budget,
19};
20use crate::entropy::sequential::{PreparedDecodePlan, StripeBuffer, StripeLayout};
21use crate::error::JpegError;
22use alloc::vec::Vec;
23use j2k_core::ScratchPool as CoreScratchPool;
24
25#[derive(Debug, Default)]
26pub(crate) struct YCbCr420Rows {
27    pub(crate) cb_top: Vec<u8>,
28    pub(crate) cb_bot: Vec<u8>,
29    pub(crate) cr_top: Vec<u8>,
30    pub(crate) cr_bot: Vec<u8>,
31}
32
33#[derive(Debug, Default)]
34pub(crate) struct YCbCrGenericRows {
35    pub(crate) cb_up: Vec<u8>,
36    pub(crate) cr_up: Vec<u8>,
37}
38
39#[derive(Debug, Default)]
40pub(crate) struct RgbGenericRows {
41    pub(crate) r: Vec<u8>,
42    pub(crate) g: Vec<u8>,
43    pub(crate) b: Vec<u8>,
44    pub(crate) k: Vec<u8>,
45}
46
47#[derive(Debug, Default)]
48pub(crate) struct SinkRows {
49    pub(crate) top_row: Vec<u8>,
50    pub(crate) bottom_row: Vec<u8>,
51}
52
53/// Pool of decoder-internal scratch buffers, reusable across many
54/// [`Decoder::decode_into_with_scratch`](crate::Decoder::decode_into_with_scratch)
55/// / [`decode_rows_with_scratch`](crate::Decoder::decode_rows_with_scratch)
56/// calls.
57#[derive(Debug, Default)]
58pub struct ScratchPool {
59    pub(crate) prev_dc: Vec<i32>,
60    pub(crate) stripe_a: StripeBuffer,
61    pub(crate) stripe_b: StripeBuffer,
62    pub(crate) stripe_c: StripeBuffer,
63    pub(crate) ycbcr_420_rows: YCbCr420Rows,
64    pub(crate) ycbcr_generic_rows: YCbCrGenericRows,
65    pub(crate) rgb_generic_rows: RgbGenericRows,
66    pub(crate) lossless_prev_row: Vec<u8>,
67    pub(crate) lossless_curr_row: Vec<u8>,
68    sink_rows: SinkRows,
69    detached_sink_bytes: usize,
70}
71
72#[derive(Clone, Copy)]
73struct SequentialScratchLayout {
74    stripe: StripeLayout,
75    component_count: usize,
76    row_width: usize,
77}
78
79impl SequentialScratchLayout {
80    fn for_plan(
81        plan: &PreparedDecodePlan,
82        mcus_per_row: u32,
83        block_size: u32,
84    ) -> Result<Self, JpegError> {
85        Ok(Self {
86            stripe: StripeLayout::for_plan(plan, mcus_per_row, block_size)?,
87            component_count: plan.sampling.len(),
88            row_width: plan.dimensions.0.div_ceil(8 / block_size.max(1)) as usize,
89        })
90    }
91
92    fn minimum_bytes(self, detached_sink_bytes: usize) -> Result<usize, JpegError> {
93        let mut total = checked_allocation_bytes::<i32>(self.component_count)?;
94        let stripe_bytes = self.stripe.allocation_bytes()?;
95        for _ in 0..3 {
96            total = checked_add_allocation_bytes(total, stripe_bytes)?;
97        }
98        let row_bytes = self
99            .row_width
100            .checked_mul(10)
101            .ok_or(JpegError::MemoryCapExceeded {
102                requested: usize::MAX,
103                cap: j2k_core::DEFAULT_MAX_HOST_ALLOCATION_BYTES,
104            })?;
105        total = checked_add_allocation_bytes(total, row_bytes)?;
106        checked_add_allocation_bytes(total, detached_sink_bytes)
107    }
108}
109
110impl ScratchPool {
111    /// Create an empty pool. The first decode that uses it pays the full
112    /// allocation cost; subsequent decodes at the same-or-smaller shape
113    /// reuse the underlying `Vec`s with zero allocations.
114    #[must_use]
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Grow every internal scratch buffer to the shape required by `plan`
120    /// and zero the predictor so each decode starts clean.
121    pub(crate) fn prepare_for(
122        &mut self,
123        plan: &PreparedDecodePlan,
124        mcus_per_row: u32,
125        block_size: u32,
126        max_bytes: usize,
127    ) -> Result<(), JpegError> {
128        let layout = SequentialScratchLayout::for_plan(plan, mcus_per_row, block_size)?;
129        let target_bytes = layout.minimum_bytes(self.detached_sink_bytes)?;
130        ensure_request_bytes(target_bytes, max_bytes)?;
131        if self.retained_bytes() > target_bytes
132            || self.projected_sequential_bytes(layout) > target_bytes
133            || self.sequential_requires_growth(layout)
134        {
135            self.release_retained_allocations();
136        }
137        ensure_request_bytes(self.projected_sequential_bytes(layout), target_bytes)?;
138        if let Err(error) = self.reserve_sequential_storage(layout, target_bytes) {
139            self.release_retained_allocations();
140            return Err(error);
141        }
142
143        self.prev_dc.resize(layout.component_count, 0);
144        resize_rows(&mut self.ycbcr_420_rows, layout.row_width);
145        resize_generic_rows(&mut self.ycbcr_generic_rows, layout.row_width);
146        resize_rgb_rows(&mut self.rgb_generic_rows, layout.row_width);
147        for dc in &mut self.prev_dc {
148            *dc = 0;
149        }
150        self.ensure_retained_capacity(target_bytes)?;
151        Ok(())
152    }
153
154    pub(crate) fn take_sink_rows(
155        &mut self,
156        row_len: usize,
157        max_bytes: usize,
158    ) -> Result<SinkRows, JpegError> {
159        if self.detached_sink_bytes != 0 {
160            return Err(JpegError::InternalInvariant {
161                reason: "scratch sink rows are already detached",
162            });
163        }
164        let sink_projected = projected_vec_bytes(&self.sink_rows.top_row, row_len)
165            .saturating_add(projected_vec_bytes(&self.sink_rows.bottom_row, row_len));
166        let without_sink = self
167            .retained_bytes()
168            .saturating_sub(vec_bytes(&self.sink_rows.top_row))
169            .saturating_sub(vec_bytes(&self.sink_rows.bottom_row));
170        if without_sink.saturating_add(sink_projected) > max_bytes
171            || self.sink_requires_growth(row_len)
172        {
173            self.release_retained_allocations();
174        }
175        let minimum = row_len.checked_mul(2).ok_or(JpegError::MemoryCapExceeded {
176            requested: usize::MAX,
177            cap: max_bytes,
178        })?;
179        ensure_request_bytes(minimum, max_bytes)?;
180        let mut live_bytes = self.retained_bytes();
181        let reserve_result = (|| {
182            try_reserve_for_len_with_live_budget(
183                &mut self.sink_rows.top_row,
184                row_len,
185                &mut live_bytes,
186                max_bytes,
187            )?;
188            try_reserve_for_len_with_live_budget(
189                &mut self.sink_rows.bottom_row,
190                row_len,
191                &mut live_bytes,
192                max_bytes,
193            )
194        })();
195        if let Err(error) = reserve_result {
196            self.release_retained_allocations();
197            return Err(error);
198        }
199        self.sink_rows.top_row.resize(row_len, 0);
200        self.sink_rows.bottom_row.resize(row_len, 0);
201        self.ensure_retained_capacity(max_bytes)?;
202        let rows = core::mem::take(&mut self.sink_rows);
203        self.detached_sink_bytes =
204            vec_bytes(&rows.top_row).saturating_add(vec_bytes(&rows.bottom_row));
205        Ok(rows)
206    }
207
208    pub(crate) fn prepare_lossless_rows(
209        &mut self,
210        predictor_row_len: usize,
211        sink_row_len: usize,
212        max_bytes: usize,
213    ) -> Result<SinkRows, JpegError> {
214        if self.detached_sink_bytes != 0 {
215            return Err(JpegError::InternalInvariant {
216                reason: "scratch sink rows are already detached",
217            });
218        }
219        if sink_row_len == 0 {
220            self.sink_rows = SinkRows::default();
221        }
222        let minimum = predictor_row_len
223            .checked_mul(2)
224            .and_then(|bytes| {
225                sink_row_len
226                    .checked_mul(2)
227                    .and_then(|sink| bytes.checked_add(sink))
228            })
229            .ok_or(JpegError::MemoryCapExceeded {
230                requested: usize::MAX,
231                cap: max_bytes,
232            })?;
233        ensure_request_bytes(minimum, max_bytes)?;
234        if self.projected_lossless_bytes(predictor_row_len, sink_row_len) > max_bytes
235            || self.lossless_requires_growth(predictor_row_len, sink_row_len)
236        {
237            self.release_retained_allocations();
238        }
239        ensure_request_bytes(
240            self.projected_lossless_bytes(predictor_row_len, sink_row_len),
241            max_bytes,
242        )?;
243        let mut live_bytes = self.retained_bytes();
244        let reserve_result = (|| {
245            try_reserve_for_len_with_live_budget(
246                &mut self.lossless_prev_row,
247                predictor_row_len,
248                &mut live_bytes,
249                max_bytes,
250            )?;
251            try_reserve_for_len_with_live_budget(
252                &mut self.lossless_curr_row,
253                predictor_row_len,
254                &mut live_bytes,
255                max_bytes,
256            )?;
257            try_reserve_for_len_with_live_budget(
258                &mut self.sink_rows.top_row,
259                sink_row_len,
260                &mut live_bytes,
261                max_bytes,
262            )?;
263            try_reserve_for_len_with_live_budget(
264                &mut self.sink_rows.bottom_row,
265                sink_row_len,
266                &mut live_bytes,
267                max_bytes,
268            )
269        })();
270        if let Err(error) = reserve_result {
271            self.release_retained_allocations();
272            return Err(error);
273        }
274        self.lossless_prev_row.resize(predictor_row_len, 0);
275        self.lossless_curr_row.resize(predictor_row_len, 0);
276        self.sink_rows.top_row.resize(sink_row_len, 0);
277        self.sink_rows.bottom_row.resize(sink_row_len, 0);
278        self.ensure_retained_capacity(max_bytes)?;
279        let rows = core::mem::take(&mut self.sink_rows);
280        self.detached_sink_bytes =
281            vec_bytes(&rows.top_row).saturating_add(vec_bytes(&rows.bottom_row));
282        Ok(rows)
283    }
284
285    pub(crate) fn restore_sink_rows(&mut self, rows: SinkRows) {
286        self.sink_rows = rows;
287        self.detached_sink_bytes = 0;
288    }
289
290    pub(crate) const fn detached_sink_bytes(&self) -> usize {
291        self.detached_sink_bytes
292    }
293
294    pub(crate) fn reconcile_external_workspace(
295        &mut self,
296        external_bytes: usize,
297        max_bytes: usize,
298    ) -> Result<(), JpegError> {
299        ensure_request_bytes(external_bytes, max_bytes)?;
300        if self.retained_bytes().saturating_add(external_bytes) > max_bytes {
301            self.release_retained_allocations();
302        }
303        ensure_request_bytes(
304            self.retained_bytes().saturating_add(external_bytes),
305            max_bytes,
306        )
307    }
308
309    pub(crate) fn release_for_external_workspace(
310        &mut self,
311        external_bytes: usize,
312        max_bytes: usize,
313    ) -> Result<(), JpegError> {
314        ensure_request_bytes(external_bytes, max_bytes)?;
315        self.release_retained_allocations();
316        ensure_request_bytes(
317            self.detached_sink_bytes.saturating_add(external_bytes),
318            max_bytes,
319        )
320    }
321
322    pub(crate) fn retained_bytes(&self) -> usize {
323        let mut total = vec_bytes(&self.prev_dc);
324        total = total
325            .saturating_add(self.stripe_a.retained_bytes())
326            .saturating_add(self.stripe_b.retained_bytes())
327            .saturating_add(self.stripe_c.retained_bytes())
328            .saturating_add(rows_bytes(&self.ycbcr_420_rows))
329            .saturating_add(generic_rows_bytes(&self.ycbcr_generic_rows))
330            .saturating_add(rgb_rows_bytes(&self.rgb_generic_rows))
331            .saturating_add(vec_bytes(&self.lossless_prev_row))
332            .saturating_add(vec_bytes(&self.lossless_curr_row))
333            .saturating_add(vec_bytes(&self.sink_rows.top_row))
334            .saturating_add(vec_bytes(&self.sink_rows.bottom_row))
335            .saturating_add(self.detached_sink_bytes);
336        total
337    }
338
339    fn projected_sequential_bytes(&self, layout: SequentialScratchLayout) -> usize {
340        let mut total = projected_vec_bytes(&self.prev_dc, layout.component_count);
341        total = total
342            .saturating_add(self.stripe_a.projected_bytes(layout.stripe))
343            .saturating_add(self.stripe_b.projected_bytes(layout.stripe))
344            .saturating_add(self.stripe_c.projected_bytes(layout.stripe))
345            .saturating_add(projected_rows_bytes(&self.ycbcr_420_rows, layout.row_width))
346            .saturating_add(projected_generic_rows_bytes(
347                &self.ycbcr_generic_rows,
348                layout.row_width,
349            ))
350            .saturating_add(projected_rgb_rows_bytes(
351                &self.rgb_generic_rows,
352                layout.row_width,
353            ))
354            .saturating_add(vec_bytes(&self.lossless_prev_row))
355            .saturating_add(vec_bytes(&self.lossless_curr_row))
356            .saturating_add(vec_bytes(&self.sink_rows.top_row))
357            .saturating_add(vec_bytes(&self.sink_rows.bottom_row))
358            .saturating_add(self.detached_sink_bytes);
359        total
360    }
361
362    fn projected_lossless_bytes(&self, predictor_row_len: usize, sink_row_len: usize) -> usize {
363        self.retained_bytes()
364            .saturating_sub(vec_bytes(&self.lossless_prev_row))
365            .saturating_sub(vec_bytes(&self.lossless_curr_row))
366            .saturating_sub(vec_bytes(&self.sink_rows.top_row))
367            .saturating_sub(vec_bytes(&self.sink_rows.bottom_row))
368            .saturating_add(projected_vec_bytes(
369                &self.lossless_prev_row,
370                predictor_row_len,
371            ))
372            .saturating_add(projected_vec_bytes(
373                &self.lossless_curr_row,
374                predictor_row_len,
375            ))
376            .saturating_add(projected_vec_bytes(&self.sink_rows.top_row, sink_row_len))
377            .saturating_add(projected_vec_bytes(
378                &self.sink_rows.bottom_row,
379                sink_row_len,
380            ))
381    }
382
383    fn reserve_sequential_storage(
384        &mut self,
385        layout: SequentialScratchLayout,
386        cap: usize,
387    ) -> Result<(), JpegError> {
388        let mut live_bytes = self.retained_bytes();
389        try_reserve_for_len_with_live_budget(
390            &mut self.prev_dc,
391            layout.component_count,
392            &mut live_bytes,
393            cap,
394        )?;
395        reserve_rows(
396            &mut self.ycbcr_420_rows,
397            layout.row_width,
398            &mut live_bytes,
399            cap,
400        )?;
401        reserve_generic_rows(
402            &mut self.ycbcr_generic_rows,
403            layout.row_width,
404            &mut live_bytes,
405            cap,
406        )?;
407        reserve_rgb_rows(
408            &mut self.rgb_generic_rows,
409            layout.row_width,
410            &mut live_bytes,
411            cap,
412        )?;
413        self.stripe_a
414            .resize_for(layout.stripe, &mut live_bytes, cap)?;
415        self.stripe_b
416            .resize_for(layout.stripe, &mut live_bytes, cap)?;
417        self.stripe_c
418            .resize_for(layout.stripe, &mut live_bytes, cap)?;
419        ensure_request_bytes(live_bytes, cap)
420    }
421
422    fn sequential_requires_growth(&self, layout: SequentialScratchLayout) -> bool {
423        self.prev_dc.capacity() < layout.component_count
424            || rows_require_growth(&self.ycbcr_420_rows, layout.row_width)
425            || generic_rows_require_growth(&self.ycbcr_generic_rows, layout.row_width)
426            || rgb_rows_require_growth(&self.rgb_generic_rows, layout.row_width)
427            || self.stripe_a.requires_growth(layout.stripe)
428            || self.stripe_b.requires_growth(layout.stripe)
429            || self.stripe_c.requires_growth(layout.stripe)
430    }
431
432    fn sink_requires_growth(&self, row_len: usize) -> bool {
433        self.sink_rows.top_row.capacity() < row_len
434            || self.sink_rows.bottom_row.capacity() < row_len
435    }
436
437    fn lossless_requires_growth(&self, predictor_row_len: usize, sink_row_len: usize) -> bool {
438        self.lossless_prev_row.capacity() < predictor_row_len
439            || self.lossless_curr_row.capacity() < predictor_row_len
440            || self.sink_rows.top_row.capacity() < sink_row_len
441            || self.sink_rows.bottom_row.capacity() < sink_row_len
442    }
443
444    fn release_retained_allocations(&mut self) {
445        self.prev_dc = Vec::new();
446        self.stripe_a = StripeBuffer::default();
447        self.stripe_b = StripeBuffer::default();
448        self.stripe_c = StripeBuffer::default();
449        self.ycbcr_420_rows = YCbCr420Rows::default();
450        self.ycbcr_generic_rows = YCbCrGenericRows::default();
451        self.rgb_generic_rows = RgbGenericRows::default();
452        self.lossless_prev_row = Vec::new();
453        self.lossless_curr_row = Vec::new();
454        self.sink_rows = SinkRows::default();
455    }
456
457    fn ensure_retained_capacity(&mut self, cap: usize) -> Result<(), JpegError> {
458        let requested = self.retained_bytes();
459        if requested > cap {
460            self.release_retained_allocations();
461            return Err(JpegError::MemoryCapExceeded { requested, cap });
462        }
463        Ok(())
464    }
465}
466
467fn ensure_request_bytes(requested: usize, cap: usize) -> Result<(), JpegError> {
468    if requested > cap {
469        return Err(JpegError::MemoryCapExceeded { requested, cap });
470    }
471    Ok(())
472}
473
474fn vec_bytes<T>(vec: &Vec<T>) -> usize {
475    vec.capacity().saturating_mul(core::mem::size_of::<T>())
476}
477
478fn projected_vec_bytes<T>(vec: &Vec<T>, target_len: usize) -> usize {
479    vec.capacity()
480        .max(target_len)
481        .saturating_mul(core::mem::size_of::<T>())
482}
483
484fn reserve_rows(
485    rows: &mut YCbCr420Rows,
486    width: usize,
487    live_bytes: &mut usize,
488    cap: usize,
489) -> Result<(), JpegError> {
490    try_reserve_for_len_with_live_budget(&mut rows.cb_top, width, live_bytes, cap)?;
491    try_reserve_for_len_with_live_budget(&mut rows.cb_bot, width, live_bytes, cap)?;
492    try_reserve_for_len_with_live_budget(&mut rows.cr_top, width, live_bytes, cap)?;
493    try_reserve_for_len_with_live_budget(&mut rows.cr_bot, width, live_bytes, cap)
494}
495
496fn resize_rows(rows: &mut YCbCr420Rows, width: usize) {
497    rows.cb_top.resize(width, 0);
498    rows.cb_bot.resize(width, 0);
499    rows.cr_top.resize(width, 0);
500    rows.cr_bot.resize(width, 0);
501}
502
503fn rows_bytes(rows: &YCbCr420Rows) -> usize {
504    vec_bytes(&rows.cb_top)
505        .saturating_add(vec_bytes(&rows.cb_bot))
506        .saturating_add(vec_bytes(&rows.cr_top))
507        .saturating_add(vec_bytes(&rows.cr_bot))
508}
509
510fn projected_rows_bytes(rows: &YCbCr420Rows, width: usize) -> usize {
511    projected_vec_bytes(&rows.cb_top, width)
512        .saturating_add(projected_vec_bytes(&rows.cb_bot, width))
513        .saturating_add(projected_vec_bytes(&rows.cr_top, width))
514        .saturating_add(projected_vec_bytes(&rows.cr_bot, width))
515}
516
517fn rows_require_growth(rows: &YCbCr420Rows, width: usize) -> bool {
518    rows.cb_top.capacity() < width
519        || rows.cb_bot.capacity() < width
520        || rows.cr_top.capacity() < width
521        || rows.cr_bot.capacity() < width
522}
523
524fn reserve_generic_rows(
525    rows: &mut YCbCrGenericRows,
526    width: usize,
527    live_bytes: &mut usize,
528    cap: usize,
529) -> Result<(), JpegError> {
530    try_reserve_for_len_with_live_budget(&mut rows.cb_up, width, live_bytes, cap)?;
531    try_reserve_for_len_with_live_budget(&mut rows.cr_up, width, live_bytes, cap)
532}
533
534fn resize_generic_rows(rows: &mut YCbCrGenericRows, width: usize) {
535    rows.cb_up.resize(width, 0);
536    rows.cr_up.resize(width, 0);
537}
538
539fn generic_rows_bytes(rows: &YCbCrGenericRows) -> usize {
540    vec_bytes(&rows.cb_up).saturating_add(vec_bytes(&rows.cr_up))
541}
542
543fn projected_generic_rows_bytes(rows: &YCbCrGenericRows, width: usize) -> usize {
544    projected_vec_bytes(&rows.cb_up, width).saturating_add(projected_vec_bytes(&rows.cr_up, width))
545}
546
547fn generic_rows_require_growth(rows: &YCbCrGenericRows, width: usize) -> bool {
548    rows.cb_up.capacity() < width || rows.cr_up.capacity() < width
549}
550
551fn reserve_rgb_rows(
552    rows: &mut RgbGenericRows,
553    width: usize,
554    live_bytes: &mut usize,
555    cap: usize,
556) -> Result<(), JpegError> {
557    try_reserve_for_len_with_live_budget(&mut rows.r, width, live_bytes, cap)?;
558    try_reserve_for_len_with_live_budget(&mut rows.g, width, live_bytes, cap)?;
559    try_reserve_for_len_with_live_budget(&mut rows.b, width, live_bytes, cap)?;
560    try_reserve_for_len_with_live_budget(&mut rows.k, width, live_bytes, cap)
561}
562
563fn resize_rgb_rows(rows: &mut RgbGenericRows, width: usize) {
564    rows.r.resize(width, 0);
565    rows.g.resize(width, 0);
566    rows.b.resize(width, 0);
567    rows.k.resize(width, 0);
568}
569
570fn rgb_rows_bytes(rows: &RgbGenericRows) -> usize {
571    vec_bytes(&rows.r)
572        .saturating_add(vec_bytes(&rows.g))
573        .saturating_add(vec_bytes(&rows.b))
574        .saturating_add(vec_bytes(&rows.k))
575}
576
577fn projected_rgb_rows_bytes(rows: &RgbGenericRows, width: usize) -> usize {
578    projected_vec_bytes(&rows.r, width)
579        .saturating_add(projected_vec_bytes(&rows.g, width))
580        .saturating_add(projected_vec_bytes(&rows.b, width))
581        .saturating_add(projected_vec_bytes(&rows.k, width))
582}
583
584fn rgb_rows_require_growth(rows: &RgbGenericRows, width: usize) -> bool {
585    rows.r.capacity() < width
586        || rows.g.capacity() < width
587        || rows.b.capacity() < width
588        || rows.k.capacity() < width
589}
590
591#[doc(hidden)]
592impl CoreScratchPool for ScratchPool {
593    fn bytes_allocated(&self) -> usize {
594        self.retained_bytes()
595    }
596
597    fn reset(&mut self) {
598        fn clear_stripe(stripe: &mut StripeBuffer) {
599            for plane in &mut stripe.planes {
600                plane.clear();
601            }
602            stripe.plane_strides.clear();
603            stripe.plane_rows.clear();
604        }
605
606        self.prev_dc.clear();
607        clear_stripe(&mut self.stripe_a);
608        clear_stripe(&mut self.stripe_b);
609        clear_stripe(&mut self.stripe_c);
610        self.ycbcr_420_rows.cb_top.clear();
611        self.ycbcr_420_rows.cb_bot.clear();
612        self.ycbcr_420_rows.cr_top.clear();
613        self.ycbcr_420_rows.cr_bot.clear();
614        self.ycbcr_generic_rows.cb_up.clear();
615        self.ycbcr_generic_rows.cr_up.clear();
616        self.rgb_generic_rows.r.clear();
617        self.rgb_generic_rows.g.clear();
618        self.rgb_generic_rows.b.clear();
619        self.rgb_generic_rows.k.clear();
620        self.lossless_prev_row.clear();
621        self.lossless_curr_row.clear();
622        self.sink_rows.top_row.clear();
623        self.sink_rows.bottom_row.clear();
624    }
625}
626
627#[cfg(test)]
628mod tests {
629    use super::{JpegError, ScratchPool};
630    use alloc::vec::Vec;
631
632    #[test]
633    fn sink_rows_have_an_exact_aggregate_cap_boundary() {
634        let mut pool = ScratchPool::new();
635        let rows = pool.take_sink_rows(8, 16).expect("exact boundary");
636        assert_eq!(rows.top_row.len(), 8);
637        assert_eq!(rows.bottom_row.len(), 8);
638        pool.restore_sink_rows(rows);
639
640        let mut pool = ScratchPool::new();
641        assert!(matches!(
642            pool.take_sink_rows(9, 17),
643            Err(JpegError::MemoryCapExceeded {
644                requested: 18,
645                cap: 17
646            })
647        ));
648    }
649
650    #[test]
651    fn same_or_smaller_sink_rows_reuse_capacity_within_cap() {
652        let mut pool = ScratchPool::new();
653        let rows = pool.take_sink_rows(8, 16).expect("seed sink rows");
654        let top_ptr = rows.top_row.as_ptr();
655        let bottom_ptr = rows.bottom_row.as_ptr();
656        pool.restore_sink_rows(rows);
657
658        let rows = pool.take_sink_rows(4, 16).expect("reuse smaller rows");
659        assert_eq!(rows.top_row.as_ptr(), top_ptr);
660        assert_eq!(rows.bottom_row.as_ptr(), bottom_ptr);
661    }
662
663    #[test]
664    fn stale_capacity_is_released_instead_of_rejecting_a_valid_request() {
665        let mut pool = ScratchPool::new();
666        let rows = pool.take_sink_rows(32, 64).expect("seed retained rows");
667        pool.restore_sink_rows(rows);
668        assert!(pool.retained_bytes() >= 64);
669
670        pool.reconcile_external_workspace(64, 64)
671            .expect("stale capacity must not change request acceptance");
672        assert_eq!(pool.retained_bytes(), 0);
673    }
674
675    #[test]
676    fn detached_rows_remain_part_of_the_live_pool_budget() {
677        let mut pool = ScratchPool::new();
678        let rows = pool.take_sink_rows(8, 16).expect("detach rows");
679        assert!(pool.retained_bytes() >= 16);
680        pool.restore_sink_rows(rows);
681    }
682
683    #[test]
684    fn actual_retained_capacity_is_checked_and_released() {
685        let mut pool = ScratchPool::new();
686        pool.sink_rows.top_row = Vec::with_capacity(17);
687        assert!(matches!(
688            pool.ensure_retained_capacity(16),
689            Err(JpegError::MemoryCapExceeded {
690                requested: 17,
691                cap: 16
692            })
693        ));
694        assert_eq!(pool.retained_bytes(), 0);
695    }
696
697    #[test]
698    fn non_pool_decode_releases_stale_capacity_even_when_it_would_fit() {
699        let mut pool = ScratchPool::new();
700        pool.sink_rows.top_row = Vec::with_capacity(64);
701
702        pool.release_for_external_workspace(1, 128)
703            .expect("external phase fits after stale storage is released");
704
705        assert_eq!(pool.retained_bytes(), 0);
706    }
707}