Skip to main content

j2k_core/
traits.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use alloc::vec::Vec;
4
5use crate::{
6    accelerator::{DeviceMemoryRange, ExecutionStats, SurfaceResidency},
7    backend::{BackendKind, BackendRequest},
8    batch::{TileRegionScaledDecodeJob, TileRegionScaledDeviceDecodeRequest},
9    context::{CodecContext, DecoderContext},
10    error::CodecError,
11    pixel::PixelFormat,
12    row_sink::RowSink,
13    sample::Sample,
14    scale::Downscale,
15    scratch::ScratchPool,
16    types::{DecodeOutcome, Info, Rect},
17};
18
19/// Error wrapper used by row-streaming decode when either the codec or the
20/// caller-provided row sink can fail.
21#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
22pub enum DecodeRowsError<D, E>
23where
24    D: core::error::Error + 'static,
25    E: core::error::Error + 'static,
26{
27    #[error(transparent)]
28    /// Codec decode failure.
29    Decode(D),
30    #[error(transparent)]
31    /// Caller-provided row sink failure.
32    Sink(E),
33}
34
35/// Common associated types shared by image codecs.
36pub trait ImageCodec {
37    /// Codec-specific error type.
38    type Error: CodecError;
39    /// Non-fatal warning type returned in successful decode outcomes.
40    type Warning: core::fmt::Debug + core::fmt::Display + Send + Sync + 'static;
41    /// Caller-owned scratch pool type used to reuse allocations.
42    type Pool: ScratchPool;
43}
44
45/// Decoded image data resident on a specific backend.
46pub trait DeviceSurface {
47    /// Backend that owns or produced the surface.
48    fn backend_kind(&self) -> BackendKind;
49    /// Memory residency of the surface.
50    fn residency(&self) -> SurfaceResidency {
51        SurfaceResidency::for_backend(self.backend_kind())
52    }
53    /// Surface dimensions in pixels.
54    fn dimensions(&self) -> (u32, u32);
55    /// Pixel format stored by the surface.
56    fn pixel_format(&self) -> PixelFormat;
57    /// Number of bytes represented by the surface.
58    fn byte_len(&self) -> usize;
59    /// Execution statistics attached to the surface.
60    fn execution_stats(&self) -> ExecutionStats {
61        ExecutionStats::default()
62    }
63    /// Backend-visible memory range, when the backend can expose one safely.
64    fn memory_range(&self) -> Option<DeviceMemoryRange> {
65        None
66    }
67}
68
69/// Submitted device decode operation that can be waited on for completion.
70pub trait DeviceSubmission {
71    /// Completed output type.
72    type Output;
73    /// Submission or decode error type.
74    type Error;
75
76    /// Wait for the submission and return its output.
77    ///
78    /// # Errors
79    ///
80    /// Returns [`Self::Error`] if submission or device execution fails.
81    fn wait(self) -> Result<Self::Output, Self::Error>;
82}
83
84/// Already-completed submission used by synchronous fallback paths.
85#[derive(Debug)]
86#[doc(hidden)]
87pub struct ReadySubmission<T, E>(Result<T, E>);
88
89impl<T, E> ReadySubmission<T, E> {
90    /// Wrap an immediate result as a submission.
91    pub fn from_result(result: Result<T, E>) -> Self {
92        Self(result)
93    }
94}
95
96impl<T, E> DeviceSubmission for ReadySubmission<T, E> {
97    type Output = T;
98    type Error = E;
99
100    fn wait(self) -> Result<Self::Output, Self::Error> {
101        self.0
102    }
103}
104
105/// Mutable device session that tracks submitted backend work.
106#[doc(hidden)]
107pub trait DeviceSubmitSession {
108    /// Record a submitted device operation.
109    fn record_submit(&mut self);
110}
111
112/// Record a device submission and wrap an immediate result as ready.
113#[doc(hidden)]
114pub fn submit_ready_device<S, T, E>(
115    session: &mut S,
116    submit: impl FnOnce(&mut S) -> Result<T, E>,
117) -> ReadySubmission<T, E>
118where
119    S: DeviceSubmitSession + ?Sized,
120{
121    session.record_submit();
122    ReadySubmission::from_result(submit(session))
123}
124
125/// Borrowed-image decode API for codecs that parse compressed bytes directly.
126pub trait ImageDecode<'a>: ImageCodec + Sized + 'a {
127    /// Borrowed parse product that can later construct a decoder.
128    type View: 'a;
129
130    /// Inspect metadata without decoding pixels.
131    ///
132    /// # Errors
133    ///
134    /// Returns the codec-specific [`ImageCodec::Error`] when the compressed input is invalid or
135    /// unsupported.
136    fn inspect(input: &'a [u8]) -> Result<Info, Self::Error>;
137    /// Parse compressed bytes into a borrowed view.
138    ///
139    /// # Errors
140    ///
141    /// Returns the codec-specific [`ImageCodec::Error`] when the compressed input cannot be
142    /// parsed.
143    fn parse(input: &'a [u8]) -> Result<Self::View, Self::Error>;
144    /// Build a decoder from a parsed view.
145    ///
146    /// # Errors
147    ///
148    /// Returns the codec-specific [`ImageCodec::Error`] when the parsed view is unsupported or
149    /// inconsistent.
150    fn from_view(view: Self::View) -> Result<Self, Self::Error>;
151
152    /// Decode the full image into caller-owned output.
153    ///
154    /// # Errors
155    ///
156    /// Returns the codec-specific [`ImageCodec::Error`] for invalid input, unsupported output, or
157    /// an undersized or invalid output layout.
158    fn decode_into(
159        &mut self,
160        out: &mut [u8],
161        stride: usize,
162        fmt: PixelFormat,
163    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
164
165    /// Decode the full image into caller-owned output with reusable scratch.
166    ///
167    /// # Errors
168    ///
169    /// Returns the codec-specific [`ImageCodec::Error`] for invalid input, unsupported output,
170    /// scratch failure, or an invalid output layout.
171    fn decode_into_with_scratch(
172        &mut self,
173        pool: &mut Self::Pool,
174        out: &mut [u8],
175        stride: usize,
176        fmt: PixelFormat,
177    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
178
179    /// Decode a source-coordinate region into caller-owned output.
180    ///
181    /// # Errors
182    ///
183    /// Returns the codec-specific [`ImageCodec::Error`] when the input, region, output layout, or
184    /// scratch state cannot be decoded.
185    fn decode_region_into(
186        &mut self,
187        pool: &mut Self::Pool,
188        out: &mut [u8],
189        stride: usize,
190        fmt: PixelFormat,
191        roi: Rect,
192    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
193
194    /// Decode the full image at reduced resolution into caller-owned output.
195    ///
196    /// # Errors
197    ///
198    /// Returns the codec-specific [`ImageCodec::Error`] when the input, scale, output layout, or
199    /// scratch state cannot be decoded.
200    fn decode_scaled_into(
201        &mut self,
202        pool: &mut Self::Pool,
203        out: &mut [u8],
204        stride: usize,
205        fmt: PixelFormat,
206        scale: Downscale,
207    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
208
209    /// Decode a source-coordinate region at reduced resolution into caller-owned output.
210    ///
211    /// # Errors
212    ///
213    /// Returns the codec-specific [`ImageCodec::Error`] when the input, region, scale, output
214    /// layout, or scratch state cannot be decoded.
215    fn decode_region_scaled_into(
216        &mut self,
217        pool: &mut Self::Pool,
218        out: &mut [u8],
219        stride: usize,
220        fmt: PixelFormat,
221        roi: Rect,
222        scale: Downscale,
223    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
224}
225
226/// Adapter hook for decoders whose host-output path delegates to a CPU decoder.
227///
228/// GPU adapter crates implement this trait when their public decoder wraps a
229/// CPU decoder for host output but has backend-specific device submission
230/// methods. The blanket [`ImageDecode`] impl below keeps the CPU-host
231/// delegation in one place.
232#[doc(hidden)]
233pub trait CpuBackedImageDecode<'a>: ImageCodec + Sized + 'a {
234    /// CPU decoder that owns the host-output implementation.
235    type Cpu: ImageDecode<'a, Pool = Self::Pool>;
236    /// Borrowed parse product used by this adapter.
237    type View: 'a;
238
239    /// Inspect metadata through the CPU codec and map it to core info.
240    fn inspect_cpu(input: &'a [u8]) -> Result<Info, Self::Error>;
241    /// Parse compressed bytes through the CPU codec or adapter view.
242    fn parse_cpu(input: &'a [u8]) -> Result<Self::View, Self::Error>;
243    /// Build this adapter from a parsed CPU view.
244    fn from_cpu_view(view: Self::View) -> Result<Self, Self::Error>;
245    /// Borrow the wrapped CPU decoder mutably.
246    fn cpu_decoder_mut(&mut self) -> &mut Self::Cpu;
247    /// Convert a CPU decode outcome into this adapter's warning type.
248    fn map_cpu_outcome(
249        outcome: DecodeOutcome<<Self::Cpu as ImageCodec>::Warning>,
250    ) -> DecodeOutcome<Self::Warning>;
251}
252
253#[doc(hidden)]
254impl<'a, T> ImageDecode<'a> for T
255where
256    T: CpuBackedImageDecode<'a>,
257    <T::Cpu as ImageCodec>::Error: Into<T::Error>,
258{
259    type View = T::View;
260
261    fn inspect(input: &'a [u8]) -> Result<Info, Self::Error> {
262        T::inspect_cpu(input)
263    }
264
265    fn parse(input: &'a [u8]) -> Result<Self::View, Self::Error> {
266        T::parse_cpu(input)
267    }
268
269    fn from_view(view: Self::View) -> Result<Self, Self::Error> {
270        T::from_cpu_view(view)
271    }
272
273    fn decode_into(
274        &mut self,
275        out: &mut [u8],
276        stride: usize,
277        fmt: PixelFormat,
278    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
279        let outcome = self
280            .cpu_decoder_mut()
281            .decode_into(out, stride, fmt)
282            .map_err(Into::into)?;
283        Ok(T::map_cpu_outcome(outcome))
284    }
285
286    fn decode_into_with_scratch(
287        &mut self,
288        pool: &mut Self::Pool,
289        out: &mut [u8],
290        stride: usize,
291        fmt: PixelFormat,
292    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
293        let outcome = self
294            .cpu_decoder_mut()
295            .decode_into_with_scratch(pool, out, stride, fmt)
296            .map_err(Into::into)?;
297        Ok(T::map_cpu_outcome(outcome))
298    }
299
300    fn decode_region_into(
301        &mut self,
302        pool: &mut Self::Pool,
303        out: &mut [u8],
304        stride: usize,
305        fmt: PixelFormat,
306        roi: Rect,
307    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
308        let outcome = self
309            .cpu_decoder_mut()
310            .decode_region_into(pool, out, stride, fmt, roi)
311            .map_err(Into::into)?;
312        Ok(T::map_cpu_outcome(outcome))
313    }
314
315    fn decode_scaled_into(
316        &mut self,
317        pool: &mut Self::Pool,
318        out: &mut [u8],
319        stride: usize,
320        fmt: PixelFormat,
321        scale: Downscale,
322    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
323        let outcome = self
324            .cpu_decoder_mut()
325            .decode_scaled_into(pool, out, stride, fmt, scale)
326            .map_err(Into::into)?;
327        Ok(T::map_cpu_outcome(outcome))
328    }
329
330    fn decode_region_scaled_into(
331        &mut self,
332        pool: &mut Self::Pool,
333        out: &mut [u8],
334        stride: usize,
335        fmt: PixelFormat,
336        roi: Rect,
337        scale: Downscale,
338    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error> {
339        let outcome = self
340            .cpu_decoder_mut()
341            .decode_region_scaled_into(pool, out, stride, fmt, roi, scale)
342            .map_err(Into::into)?;
343        Ok(T::map_cpu_outcome(outcome))
344    }
345}
346
347/// Decode API for implementations that can submit work to a device backend.
348pub trait ImageDecodeSubmit<'a>: ImageDecode<'a> {
349    /// Mutable session state shared across submissions.
350    type Session: Default + Send;
351    /// Device surface returned by completed submissions.
352    type DeviceSurface: DeviceSurface;
353    /// Submission handle type.
354    type SubmittedSurface: DeviceSubmission<Output = Self::DeviceSurface, Error = Self::Error>;
355
356    /// Submit full-image decode to the requested backend.
357    ///
358    /// # Errors
359    ///
360    /// Returns the codec-specific [`ImageCodec::Error`] when the request is invalid, unsupported,
361    /// or cannot be submitted.
362    fn submit_to_device(
363        &mut self,
364        session: &mut Self::Session,
365        fmt: PixelFormat,
366        backend: BackendRequest,
367    ) -> Result<Self::SubmittedSurface, Self::Error>;
368
369    /// Submit region decode to the requested backend.
370    ///
371    /// # Errors
372    ///
373    /// Returns the codec-specific [`ImageCodec::Error`] when the region or backend request is
374    /// invalid, unsupported, or cannot be submitted.
375    fn submit_region_to_device(
376        &mut self,
377        session: &mut Self::Session,
378        fmt: PixelFormat,
379        roi: Rect,
380        backend: BackendRequest,
381    ) -> Result<Self::SubmittedSurface, Self::Error>;
382
383    /// Submit reduced-resolution decode to the requested backend.
384    ///
385    /// # Errors
386    ///
387    /// Returns the codec-specific [`ImageCodec::Error`] when the scale or backend request is
388    /// invalid, unsupported, or cannot be submitted.
389    fn submit_scaled_to_device(
390        &mut self,
391        session: &mut Self::Session,
392        fmt: PixelFormat,
393        scale: Downscale,
394        backend: BackendRequest,
395    ) -> Result<Self::SubmittedSurface, Self::Error>;
396
397    /// Submit region decode at reduced resolution to the requested backend.
398    ///
399    /// # Errors
400    ///
401    /// Returns the codec-specific [`ImageCodec::Error`] when the region, scale, or backend request
402    /// is invalid, unsupported, or cannot be submitted.
403    fn submit_region_scaled_to_device(
404        &mut self,
405        session: &mut Self::Session,
406        fmt: PixelFormat,
407        roi: Rect,
408        scale: Downscale,
409        backend: BackendRequest,
410    ) -> Result<Self::SubmittedSurface, Self::Error>;
411}
412
413/// Synchronous device-output decode API.
414pub trait ImageDecodeDevice<'a>: ImageDecode<'a> {
415    /// Device surface returned by decode calls.
416    type DeviceSurface: DeviceSurface;
417
418    /// Decode the full image to the requested backend.
419    ///
420    /// # Errors
421    ///
422    /// Returns the codec-specific [`ImageCodec::Error`] if submission or device execution fails.
423    fn decode_to_device(
424        &mut self,
425        fmt: PixelFormat,
426        backend: BackendRequest,
427    ) -> Result<<Self as ImageDecodeDevice<'a>>::DeviceSurface, Self::Error>
428    where
429        Self: ImageDecodeSubmit<'a, DeviceSurface = <Self as ImageDecodeDevice<'a>>::DeviceSurface>,
430    {
431        let mut session = <Self as ImageDecodeSubmit<'a>>::Session::default();
432        <Self as ImageDecodeSubmit<'a>>::submit_to_device(self, &mut session, fmt, backend)?.wait()
433    }
434
435    /// Decode a source-coordinate region to the requested backend.
436    ///
437    /// # Errors
438    ///
439    /// Returns the codec-specific [`ImageCodec::Error`] if the region is invalid or submission or
440    /// device execution fails.
441    fn decode_region_to_device(
442        &mut self,
443        fmt: PixelFormat,
444        roi: Rect,
445        backend: BackendRequest,
446    ) -> Result<<Self as ImageDecodeDevice<'a>>::DeviceSurface, Self::Error>
447    where
448        Self: ImageDecodeSubmit<'a, DeviceSurface = <Self as ImageDecodeDevice<'a>>::DeviceSurface>,
449    {
450        let mut session = <Self as ImageDecodeSubmit<'a>>::Session::default();
451        <Self as ImageDecodeSubmit<'a>>::submit_region_to_device(
452            self,
453            &mut session,
454            fmt,
455            roi,
456            backend,
457        )?
458        .wait()
459    }
460
461    /// Decode the full image at reduced resolution to the requested backend.
462    ///
463    /// # Errors
464    ///
465    /// Returns the codec-specific [`ImageCodec::Error`] if the scale is unsupported or submission
466    /// or device execution fails.
467    fn decode_scaled_to_device(
468        &mut self,
469        fmt: PixelFormat,
470        scale: Downscale,
471        backend: BackendRequest,
472    ) -> Result<<Self as ImageDecodeDevice<'a>>::DeviceSurface, Self::Error>
473    where
474        Self: ImageDecodeSubmit<'a, DeviceSurface = <Self as ImageDecodeDevice<'a>>::DeviceSurface>,
475    {
476        let mut session = <Self as ImageDecodeSubmit<'a>>::Session::default();
477        <Self as ImageDecodeSubmit<'a>>::submit_scaled_to_device(
478            self,
479            &mut session,
480            fmt,
481            scale,
482            backend,
483        )?
484        .wait()
485    }
486
487    /// Decode a source-coordinate region at reduced resolution to the requested backend.
488    ///
489    /// # Errors
490    ///
491    /// Returns the codec-specific [`ImageCodec::Error`] if the region or scale is invalid or
492    /// submission or device execution fails.
493    fn decode_region_scaled_to_device(
494        &mut self,
495        fmt: PixelFormat,
496        roi: Rect,
497        scale: Downscale,
498        backend: BackendRequest,
499    ) -> Result<<Self as ImageDecodeDevice<'a>>::DeviceSurface, Self::Error>
500    where
501        Self: ImageDecodeSubmit<'a, DeviceSurface = <Self as ImageDecodeDevice<'a>>::DeviceSurface>,
502    {
503        let mut session = <Self as ImageDecodeSubmit<'a>>::Session::default();
504        <Self as ImageDecodeSubmit<'a>>::submit_region_scaled_to_device(
505            self,
506            &mut session,
507            fmt,
508            roi,
509            scale,
510            backend,
511        )?
512        .wait()
513    }
514}
515
516/// Row-streaming decode API for large images or stripe-oriented callers.
517pub trait ImageDecodeRows<'a, S: Sample>: ImageDecode<'a> {
518    /// Decode rows into `sink` without requiring one contiguous output buffer.
519    ///
520    /// # Errors
521    ///
522    /// Returns [`DecodeRowsError::Decode`] for codec failures or
523    /// [`DecodeRowsError::Sink`] when the destination rejects a row.
524    #[expect(
525        clippy::type_complexity,
526        reason = "the public contract must preserve distinct codec and sink error types"
527    )]
528    fn decode_rows<R: RowSink<S>>(
529        &mut self,
530        sink: &mut R,
531    ) -> Result<DecodeOutcome<Self::Warning>, DecodeRowsError<Self::Error, R::Error>>;
532}
533
534/// Stateless tile-batch decode helpers that reuse caller-owned context.
535pub trait TileBatchDecode: ImageCodec {
536    /// Codec-specific context cached across tiles.
537    type Context: CodecContext;
538
539    /// Decode one tile into caller-owned output.
540    ///
541    /// # Errors
542    ///
543    /// Returns the codec-specific [`ImageCodec::Error`] when the tile input or output layout cannot
544    /// be decoded.
545    fn decode_tile(
546        ctx: &mut DecoderContext<Self::Context>,
547        pool: &mut Self::Pool,
548        input: &[u8],
549        out: &mut [u8],
550        stride: usize,
551        fmt: PixelFormat,
552    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
553
554    /// Decode one tile region into caller-owned output.
555    ///
556    /// # Errors
557    ///
558    /// Returns the codec-specific [`ImageCodec::Error`] when the tile, region, or output layout
559    /// cannot be decoded.
560    fn decode_tile_region(
561        ctx: &mut DecoderContext<Self::Context>,
562        pool: &mut Self::Pool,
563        input: &[u8],
564        out: &mut [u8],
565        stride: usize,
566        fmt: PixelFormat,
567        roi: Rect,
568    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
569
570    /// Decode one tile at reduced resolution into caller-owned output.
571    ///
572    /// # Errors
573    ///
574    /// Returns the codec-specific [`ImageCodec::Error`] when the tile, scale, or output layout
575    /// cannot be decoded.
576    fn decode_tile_scaled(
577        ctx: &mut DecoderContext<Self::Context>,
578        pool: &mut Self::Pool,
579        input: &[u8],
580        out: &mut [u8],
581        stride: usize,
582        fmt: PixelFormat,
583        scale: Downscale,
584    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
585
586    /// Decode one tile region at reduced resolution into caller-owned output.
587    ///
588    /// # Errors
589    ///
590    /// Returns the codec-specific [`ImageCodec::Error`] when the tile, region, scale, or output
591    /// layout cannot be decoded.
592    fn decode_tile_region_scaled(
593        ctx: &mut DecoderContext<Self::Context>,
594        pool: &mut Self::Pool,
595        fmt: PixelFormat,
596        job: TileRegionScaledDecodeJob<'_, '_>,
597    ) -> Result<DecodeOutcome<Self::Warning>, Self::Error>;
598}
599
600/// Tile-batch helpers that return synchronous device surfaces.
601pub trait TileBatchDecodeDevice: ImageCodec {
602    /// Codec-specific context cached across tiles.
603    type Context: CodecContext;
604    /// Device surface returned by decode calls.
605    type DeviceSurface: DeviceSurface;
606
607    /// Decode one tile to the requested backend.
608    ///
609    /// # Errors
610    ///
611    /// Returns the codec-specific [`ImageCodec::Error`] if the tile cannot be submitted or device
612    /// execution fails.
613    fn decode_tile_to_device(
614        ctx: &mut DecoderContext<<Self as TileBatchDecodeDevice>::Context>,
615        pool: &mut Self::Pool,
616        input: &[u8],
617        fmt: PixelFormat,
618        backend: BackendRequest,
619    ) -> Result<<Self as TileBatchDecodeDevice>::DeviceSurface, Self::Error>
620    where
621        Self: TileBatchDecodeSubmit<
622            Context = <Self as TileBatchDecodeDevice>::Context,
623            DeviceSurface = <Self as TileBatchDecodeDevice>::DeviceSurface,
624        >,
625    {
626        let mut session = <Self as TileBatchDecodeSubmit>::Session::default();
627        <Self as TileBatchDecodeSubmit>::submit_tile_to_device(
628            ctx,
629            &mut session,
630            pool,
631            input,
632            fmt,
633            backend,
634        )?
635        .wait()
636    }
637
638    /// Decode one tile region to the requested backend.
639    ///
640    /// # Errors
641    ///
642    /// Returns the codec-specific [`ImageCodec::Error`] if the region is invalid or submission or
643    /// device execution fails.
644    fn decode_tile_region_to_device(
645        ctx: &mut DecoderContext<<Self as TileBatchDecodeDevice>::Context>,
646        pool: &mut Self::Pool,
647        input: &[u8],
648        fmt: PixelFormat,
649        roi: Rect,
650        backend: BackendRequest,
651    ) -> Result<<Self as TileBatchDecodeDevice>::DeviceSurface, Self::Error>
652    where
653        Self: TileBatchDecodeSubmit<
654            Context = <Self as TileBatchDecodeDevice>::Context,
655            DeviceSurface = <Self as TileBatchDecodeDevice>::DeviceSurface,
656        >,
657    {
658        let mut session = <Self as TileBatchDecodeSubmit>::Session::default();
659        <Self as TileBatchDecodeSubmit>::submit_tile_region_to_device(
660            ctx,
661            &mut session,
662            pool,
663            input,
664            fmt,
665            roi,
666            backend,
667        )?
668        .wait()
669    }
670
671    /// Decode one tile at reduced resolution to the requested backend.
672    ///
673    /// # Errors
674    ///
675    /// Returns the codec-specific [`ImageCodec::Error`] if the scale is unsupported or submission
676    /// or device execution fails.
677    fn decode_tile_scaled_to_device(
678        ctx: &mut DecoderContext<<Self as TileBatchDecodeDevice>::Context>,
679        pool: &mut Self::Pool,
680        input: &[u8],
681        fmt: PixelFormat,
682        scale: Downscale,
683        backend: BackendRequest,
684    ) -> Result<<Self as TileBatchDecodeDevice>::DeviceSurface, Self::Error>
685    where
686        Self: TileBatchDecodeSubmit<
687            Context = <Self as TileBatchDecodeDevice>::Context,
688            DeviceSurface = <Self as TileBatchDecodeDevice>::DeviceSurface,
689        >,
690    {
691        let mut session = <Self as TileBatchDecodeSubmit>::Session::default();
692        <Self as TileBatchDecodeSubmit>::submit_tile_scaled_to_device(
693            ctx,
694            &mut session,
695            pool,
696            input,
697            fmt,
698            scale,
699            backend,
700        )?
701        .wait()
702    }
703
704    /// Decode one tile region at reduced resolution to the requested backend.
705    ///
706    /// # Errors
707    ///
708    /// Returns the codec-specific [`ImageCodec::Error`] if the region or scale is invalid or
709    /// submission or device execution fails.
710    fn decode_tile_region_scaled_to_device(
711        ctx: &mut DecoderContext<<Self as TileBatchDecodeDevice>::Context>,
712        pool: &mut Self::Pool,
713        input: &[u8],
714        fmt: PixelFormat,
715        roi: Rect,
716        scale: Downscale,
717        backend: BackendRequest,
718    ) -> Result<<Self as TileBatchDecodeDevice>::DeviceSurface, Self::Error>
719    where
720        Self: TileBatchDecodeSubmit<
721            Context = <Self as TileBatchDecodeDevice>::Context,
722            DeviceSurface = <Self as TileBatchDecodeDevice>::DeviceSurface,
723        >,
724    {
725        let mut session = <Self as TileBatchDecodeSubmit>::Session::default();
726        <Self as TileBatchDecodeSubmit>::submit_tile_region_scaled_to_device(
727            ctx,
728            &mut session,
729            pool,
730            TileRegionScaledDeviceDecodeRequest {
731                input,
732                fmt,
733                roi,
734                scale,
735                backend,
736            },
737        )?
738        .wait()
739    }
740}
741
742/// Full-tile batch helpers that decode many independent tiles to device surfaces.
743pub trait TileBatchDecodeManyDevice: ImageCodec {
744    /// Codec-specific context cached across tiles.
745    type Context: CodecContext;
746    /// Device surface returned by decode calls.
747    type DeviceSurface: DeviceSurface;
748
749    /// Decode many full tiles to the requested backend, preserving input order.
750    ///
751    /// # Errors
752    ///
753    /// Returns the codec-specific [`ImageCodec::Error`] if any tile cannot be decoded by the
754    /// requested backend.
755    fn decode_tiles_to_device(
756        ctx: &mut DecoderContext<Self::Context>,
757        pool: &mut Self::Pool,
758        inputs: &[&[u8]],
759        fmt: PixelFormat,
760        backend: BackendRequest,
761    ) -> Result<Vec<Self::DeviceSurface>, Self::Error>;
762}
763
764/// Tile-batch helpers that queue device submissions.
765pub trait TileBatchDecodeSubmit: ImageCodec {
766    /// Codec-specific context cached across tiles.
767    type Context: CodecContext;
768    /// Mutable session state shared across submissions.
769    type Session: Default + Send;
770    /// Device surface returned by completed submissions.
771    type DeviceSurface: DeviceSurface;
772    /// Submission handle type.
773    type SubmittedSurface: DeviceSubmission<Output = Self::DeviceSurface, Error = Self::Error>;
774
775    /// Submit one full tile to the requested backend.
776    ///
777    /// # Errors
778    ///
779    /// Returns the codec-specific [`ImageCodec::Error`] when the request is invalid, unsupported,
780    /// or cannot be submitted.
781    fn submit_tile_to_device(
782        ctx: &mut DecoderContext<Self::Context>,
783        session: &mut Self::Session,
784        pool: &mut Self::Pool,
785        input: &[u8],
786        fmt: PixelFormat,
787        backend: BackendRequest,
788    ) -> Result<Self::SubmittedSurface, Self::Error>;
789
790    /// Submit one tile region to the requested backend.
791    ///
792    /// # Errors
793    ///
794    /// Returns the codec-specific [`ImageCodec::Error`] when the region or backend request is
795    /// invalid, unsupported, or cannot be submitted.
796    fn submit_tile_region_to_device(
797        ctx: &mut DecoderContext<Self::Context>,
798        session: &mut Self::Session,
799        pool: &mut Self::Pool,
800        input: &[u8],
801        fmt: PixelFormat,
802        roi: Rect,
803        backend: BackendRequest,
804    ) -> Result<Self::SubmittedSurface, Self::Error>;
805
806    /// Submit one tile at reduced resolution to the requested backend.
807    ///
808    /// # Errors
809    ///
810    /// Returns the codec-specific [`ImageCodec::Error`] when the scale or backend request is
811    /// invalid, unsupported, or cannot be submitted.
812    fn submit_tile_scaled_to_device(
813        ctx: &mut DecoderContext<Self::Context>,
814        session: &mut Self::Session,
815        pool: &mut Self::Pool,
816        input: &[u8],
817        fmt: PixelFormat,
818        scale: Downscale,
819        backend: BackendRequest,
820    ) -> Result<Self::SubmittedSurface, Self::Error>;
821
822    /// Submit one tile region at reduced resolution to the requested backend.
823    ///
824    /// # Errors
825    ///
826    /// Returns the codec-specific [`ImageCodec::Error`] when the request is invalid, unsupported,
827    /// or cannot be submitted.
828    fn submit_tile_region_scaled_to_device(
829        ctx: &mut DecoderContext<Self::Context>,
830        session: &mut Self::Session,
831        pool: &mut Self::Pool,
832        request: TileRegionScaledDeviceDecodeRequest<'_>,
833    ) -> Result<Self::SubmittedSurface, Self::Error>;
834}
835
836/// Tile payload decompression API for container codecs such as Deflate, Zstd,
837/// LZW, and uncompressed data.
838pub trait TileDecompress {
839    /// Codec-specific error type.
840    type Error: CodecError;
841    /// Caller-owned scratch pool type.
842    type Pool: ScratchPool;
843
844    /// Return the expected decoded size when the compressed payload encodes it.
845    ///
846    /// # Errors
847    ///
848    /// Returns [`Self::Error`] when the payload header is invalid or unsupported.
849    fn expected_size(input: &[u8]) -> Result<Option<usize>, Self::Error>;
850
851    /// Decompress `input` into `out`, returning the number of bytes written.
852    ///
853    /// # Errors
854    ///
855    /// Returns [`Self::Error`] when the payload is invalid, scratch allocation
856    /// fails, or `out` cannot hold the decoded bytes.
857    fn decompress_into(
858        pool: &mut Self::Pool,
859        input: &[u8],
860        out: &mut [u8],
861    ) -> Result<usize, Self::Error>;
862}
863
864#[cfg(test)]
865mod tests;