Skip to main content

lerc_reader/
lib.rs

1#![deny(unsafe_op_in_unsafe_fn)]
2
3//! Pure-Rust LERC decoder.
4//!
5//! The public API distinguishes strict single-blob entry points from
6//! concatenated-band helpers:
7//!
8//! - inspect a single blob with [`get_blob_info`]
9//! - inspect only the first blob with [`inspect_first`]
10//! - pass an explicit shared or external mask with the `*_with_mask` single-blob variants
11//! - count concatenated blobs with [`get_band_count`]
12//! - decode a single blob with [`decode`]
13//! - decode only the first blob with [`decode_first`]
14//! - decode concatenated band sets with [`decode_band_set`]
15//! - seed a first-band external mask with the band-set `*_with_mask` variants
16//! - decode promoted `f64` buffers with [`decode_to_f64`]
17//! - decode only the first promoted `f64` blob with [`decode_first_to_f64`]
18//! - decode directly into `ndarray::ArrayD` with [`decode_ndarray`]
19
20mod allocation;
21mod bitstuff;
22mod huffman;
23mod io;
24mod lerc1;
25mod lerc2;
26mod pixel;
27mod types;
28
29#[cfg(test)]
30mod tests;
31
32use lerc_band_materialize::{BandLayout as MaterializeLayout, BandMaterializer, BandSink};
33use lerc_core::{BandLayout, BandSetInfo, BlobInfo, Error, Result};
34use ndarray::ArrayD;
35
36use crate::pixel::Sample;
37pub use crate::types::{
38    into_band_mask_ndarray, BandElement, BandElementKind, Decoded, DecodedBandSet, DecodedF64,
39    NdArrayElement,
40};
41
42macro_rules! dispatch_band_element {
43    ($target:ty, |$concrete:ident| $body:block) => {
44        match <$target as BandElement>::KIND {
45            BandElementKind::I8 => {
46                type $concrete = i8;
47                $body
48            }
49            BandElementKind::U8 => {
50                type $concrete = u8;
51                $body
52            }
53            BandElementKind::I16 => {
54                type $concrete = i16;
55                $body
56            }
57            BandElementKind::U16 => {
58                type $concrete = u16;
59                $body
60            }
61            BandElementKind::I32 => {
62                type $concrete = i32;
63                $body
64            }
65            BandElementKind::U32 => {
66                type $concrete = u32;
67                $body
68            }
69            BandElementKind::F32 => {
70                type $concrete = f32;
71                $body
72            }
73            BandElementKind::F64 => {
74                type $concrete = f64;
75                $body
76            }
77        }
78    };
79}
80
81pub fn inspect_first(blob: &[u8]) -> Result<BlobInfo> {
82    if lerc1::is_lerc1(blob) {
83        return lerc1::inspect(blob, None);
84    }
85    if lerc2::is_lerc2(blob) {
86        return lerc2::inspect(blob, None);
87    }
88    Err(Error::InvalidMagic)
89}
90
91pub fn inspect_first_with_mask(blob: &[u8], mask: &[u8]) -> Result<BlobInfo> {
92    let (info, _) = inspect_first_mask_with_info(blob, Some(mask), Some(mask))?;
93    Ok(info)
94}
95
96pub fn get_blob_info(blob: &[u8]) -> Result<BlobInfo> {
97    let info = inspect_first(blob)?;
98    ensure_single_blob_consumed(blob.len(), info.blob_size, "get_blob_info", "inspect_first")?;
99    Ok(info)
100}
101
102pub fn get_blob_info_with_mask(blob: &[u8], mask: &[u8]) -> Result<BlobInfo> {
103    let info = inspect_first_with_mask(blob, mask)?;
104    ensure_single_blob_consumed(
105        blob.len(),
106        info.blob_size,
107        "get_blob_info_with_mask",
108        "inspect_first_with_mask",
109    )?;
110    Ok(info)
111}
112
113pub fn get_band_count(blob: &[u8]) -> Result<usize> {
114    let mut offset = 0usize;
115    let mut count = 0usize;
116    let mut lerc1_mask: Option<Vec<u8>> = None;
117
118    while offset < blob.len() {
119        let slice = &blob[offset..];
120        let next_len = if lerc1::is_lerc1(slice) {
121            let parsed = lerc1::parse(slice, lerc1_mask.as_deref())?;
122            let next_len = parsed.info.blob_size;
123            lerc1_mask = parsed.mask;
124            next_len
125        } else if lerc2::is_lerc2(slice) {
126            let (parsed, _) = lerc2::parse(slice)?;
127            parsed.info.blob_size
128        } else {
129            return Err(Error::InvalidMagic);
130        };
131
132        offset = checked_next_offset(offset, next_len, blob.len())?;
133        count += 1;
134    }
135
136    Ok(count)
137}
138
139pub fn decode_first(blob: &[u8]) -> Result<Decoded> {
140    decode_first_with_masks(blob, None, None)
141}
142
143pub fn decode_first_with_mask(blob: &[u8], mask: &[u8]) -> Result<Decoded> {
144    decode_first_with_masks(blob, Some(mask), Some(mask))
145}
146
147pub fn decode(blob: &[u8]) -> Result<Decoded> {
148    let decoded = decode_first(blob)?;
149    ensure_single_blob_consumed(blob.len(), decoded.info.blob_size, "decode", "decode_first")?;
150    Ok(decoded)
151}
152
153pub fn decode_with_mask(blob: &[u8], mask: &[u8]) -> Result<Decoded> {
154    let decoded = decode_first_with_mask(blob, mask)?;
155    ensure_single_blob_consumed(
156        blob.len(),
157        decoded.info.blob_size,
158        "decode_with_mask",
159        "decode_first_with_mask",
160    )?;
161    Ok(decoded)
162}
163
164pub fn decode_band_set(blob: &[u8]) -> Result<DecodedBandSet> {
165    decode_band_set_with_lerc2_mask(blob, None)
166}
167
168pub fn decode_band_set_with_mask(blob: &[u8], mask: &[u8]) -> Result<DecodedBandSet> {
169    decode_band_set_with_lerc2_mask(blob, Some(mask))
170}
171
172pub fn decode_band_set_vec<T: BandElement>(
173    blob: &[u8],
174    layout: BandLayout,
175) -> Result<(BandSetInfo, Vec<T>)> {
176    decode_band_set_owned(blob, layout, None)
177}
178
179pub fn decode_band_set_vec_with_mask<T: BandElement>(
180    blob: &[u8],
181    mask: &[u8],
182    layout: BandLayout,
183) -> Result<(BandSetInfo, Vec<T>)> {
184    decode_band_set_owned(blob, layout, Some(mask))
185}
186
187pub fn decode_band_set_into<T: BandElement>(
188    blob: &[u8],
189    layout: BandLayout,
190    out: &mut [T],
191) -> Result<BandSetInfo> {
192    decode_band_set_into_direct(blob, layout, None, out)
193}
194
195pub fn decode_band_set_into_with_mask<T: BandElement>(
196    blob: &[u8],
197    mask: &[u8],
198    layout: BandLayout,
199    out: &mut [T],
200) -> Result<BandSetInfo> {
201    decode_band_set_into_direct(blob, layout, Some(mask), out)
202}
203
204pub fn decode_band_set_ndarray<T: BandElement>(blob: &[u8]) -> Result<ArrayD<T>> {
205    decode_band_set_ndarray_with_layout(blob, BandLayout::Interleaved)
206}
207
208pub fn decode_band_set_ndarray_with_mask<T: BandElement>(
209    blob: &[u8],
210    mask: &[u8],
211) -> Result<ArrayD<T>> {
212    decode_band_set_ndarray_with_layout_and_mask(blob, BandLayout::Interleaved, mask)
213}
214
215pub fn decode_band_set_ndarray_with_layout<T: BandElement>(
216    blob: &[u8],
217    layout: BandLayout,
218) -> Result<ArrayD<T>> {
219    decode_band_set_ndarray_with_layout_impl(blob, layout, None)
220}
221
222pub fn decode_band_set_ndarray_with_layout_and_mask<T: BandElement>(
223    blob: &[u8],
224    layout: BandLayout,
225    mask: &[u8],
226) -> Result<ArrayD<T>> {
227    decode_band_set_ndarray_with_layout_impl(blob, layout, Some(mask))
228}
229
230pub fn decode_band_set_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
231    decode_band_set_ndarray_f64_with_optional_mask(blob, None)
232}
233
234pub fn decode_band_set_ndarray_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<ArrayD<f64>> {
235    decode_band_set_ndarray_f64_with_optional_mask(blob, Some(mask))
236}
237
238pub fn decode_band_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
239    let (info, band_masks) = inspect_band_masks(blob, None)?;
240    into_band_mask_ndarray(info, band_masks)
241}
242
243pub fn decode_band_mask_ndarray_with_mask(blob: &[u8], mask: &[u8]) -> Result<Option<ArrayD<u8>>> {
244    let (info, band_masks) = inspect_band_masks(blob, Some(mask))?;
245    into_band_mask_ndarray(info, band_masks)
246}
247
248fn decode_band_set_with_lerc2_mask(
249    blob: &[u8],
250    initial_lerc2_mask: Option<&[u8]>,
251) -> Result<DecodedBandSet> {
252    let mut offset = 0usize;
253    let mut bands = Vec::new();
254    let mut infos = Vec::new();
255    let mut band_masks = Vec::new();
256    let mut lerc1_mask: Option<Vec<u8>> = None;
257    let mut lerc2_mask = initial_lerc2_mask.map(|mask| mask.to_vec());
258
259    while offset < blob.len() {
260        let decoded = decode_first_with_masks(
261            &blob[offset..],
262            lerc1_mask.as_deref(),
263            lerc2_mask.as_deref(),
264        )?;
265
266        if lerc1::is_lerc1(&blob[offset..]) {
267            lerc1_mask = decoded.mask.clone();
268            lerc2_mask = None;
269        } else {
270            lerc2_mask = decoded.mask.clone();
271            lerc1_mask = None;
272        }
273
274        offset = checked_next_offset(offset, decoded.info.blob_size, blob.len())?;
275        infos.push(decoded.info);
276        bands.push(decoded.pixels);
277        band_masks.push(decoded.mask);
278    }
279
280    Ok(DecodedBandSet {
281        info: BandSetInfo::new(infos)?,
282        bands,
283        band_masks,
284    })
285}
286
287fn decode_band_set_ndarray_with_layout_impl<T: BandElement>(
288    blob: &[u8],
289    layout: BandLayout,
290    initial_lerc2_mask: Option<&[u8]>,
291) -> Result<ArrayD<T>> {
292    let (info, values) = decode_band_set_owned(blob, layout, initial_lerc2_mask)?;
293    let shape = info.ndarray_shape_for_layout(layout);
294    ArrayD::from_shape_vec(ndarray::IxDyn(&shape), values).map_err(|e| {
295        Error::InvalidBlob(format!(
296            "failed to build ndarray from decoded band set: {e}"
297        ))
298    })
299}
300
301fn decode_band_set_ndarray_f64_with_optional_mask(
302    blob: &[u8],
303    initial_lerc2_mask: Option<&[u8]>,
304) -> Result<ArrayD<f64>> {
305    let band_info = decode_band_set_to_f64_info(blob, BandLayout::Interleaved, initial_lerc2_mask)?;
306    let shape = band_info
307        .0
308        .ndarray_shape_for_layout(BandLayout::Interleaved);
309    ArrayD::from_shape_vec(ndarray::IxDyn(&shape), band_info.1).map_err(|e| {
310        Error::InvalidBlob(format!(
311            "failed to build ndarray from decoded band set: {e}"
312        ))
313    })
314}
315
316pub fn decode_to_f64(blob: &[u8]) -> Result<DecodedF64> {
317    let decoded = decode_first_to_f64(blob)?;
318    ensure_single_blob_consumed(
319        blob.len(),
320        decoded.info.blob_size,
321        "decode_to_f64",
322        "decode_first_to_f64",
323    )?;
324    Ok(decoded)
325}
326
327pub fn decode_to_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<DecodedF64> {
328    let decoded = decode_first_to_f64_with_mask(blob, mask)?;
329    ensure_single_blob_consumed(
330        blob.len(),
331        decoded.info.blob_size,
332        "decode_to_f64_with_mask",
333        "decode_first_to_f64_with_mask",
334    )?;
335    Ok(decoded)
336}
337
338pub fn decode_first_to_f64(blob: &[u8]) -> Result<DecodedF64> {
339    decode_first_f64(blob)
340}
341
342pub fn decode_first_to_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<DecodedF64> {
343    decode_first_f64_with_masks(blob, Some(mask), Some(mask))
344}
345
346pub fn decode_ndarray<T: NdArrayElement>(blob: &[u8]) -> Result<ArrayD<T>> {
347    decode(blob)?.into_ndarray()
348}
349
350pub fn decode_ndarray_with_mask<T: NdArrayElement>(blob: &[u8], mask: &[u8]) -> Result<ArrayD<T>> {
351    decode_with_mask(blob, mask)?.into_ndarray()
352}
353
354pub fn decode_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
355    decode_to_f64(blob)?.into_ndarray()
356}
357
358pub fn decode_ndarray_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<ArrayD<f64>> {
359    decode_to_f64_with_mask(blob, mask)?.into_ndarray()
360}
361
362pub fn decode_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
363    let (info, mask) = inspect_first_mask_with_info(blob, None, None)?;
364    ensure_single_blob_consumed(
365        blob.len(),
366        info.blob_size,
367        "decode_mask_ndarray",
368        "inspect_first",
369    )?;
370    mask_to_ndarray(&info, mask)
371}
372
373pub fn decode_mask_ndarray_with_mask(blob: &[u8], mask: &[u8]) -> Result<Option<ArrayD<u8>>> {
374    let (info, decoded_mask) = inspect_first_mask_with_info(blob, Some(mask), Some(mask))?;
375    ensure_single_blob_consumed(
376        blob.len(),
377        info.blob_size,
378        "decode_mask_ndarray_with_mask",
379        "inspect_first_with_mask",
380    )?;
381    mask_to_ndarray(&info, decoded_mask)
382}
383
384fn decode_first_with_masks(
385    blob: &[u8],
386    lerc1_shared_mask: Option<&[u8]>,
387    lerc2_shared_mask: Option<&[u8]>,
388) -> Result<Decoded> {
389    if lerc1::is_lerc1(blob) {
390        return lerc1::decode(blob, lerc1_shared_mask);
391    }
392    if lerc2::is_lerc2(blob) {
393        return lerc2::decode(blob, lerc2_shared_mask);
394    }
395    Err(Error::InvalidMagic)
396}
397
398fn inspect_first_mask_with_info(
399    blob: &[u8],
400    lerc1_shared_mask: Option<&[u8]>,
401    lerc2_shared_mask: Option<&[u8]>,
402) -> Result<(BlobInfo, Option<Vec<u8>>)> {
403    if lerc1::is_lerc1(blob) {
404        return lerc1::inspect_mask(blob, lerc1_shared_mask);
405    }
406    if lerc2::is_lerc2(blob) {
407        return lerc2::inspect_with_mask(blob, lerc2_shared_mask);
408    }
409    Err(Error::InvalidMagic)
410}
411
412fn decode_first_f64(blob: &[u8]) -> Result<DecodedF64> {
413    decode_first_f64_with_masks(blob, None, None)
414}
415
416fn decode_first_f64_with_masks(
417    blob: &[u8],
418    lerc1_shared_mask: Option<&[u8]>,
419    lerc2_shared_mask: Option<&[u8]>,
420) -> Result<DecodedF64> {
421    if lerc1::is_lerc1(blob) {
422        return lerc1::decode_f64(blob, lerc1_shared_mask);
423    }
424    if lerc2::is_lerc2(blob) {
425        return lerc2::decode_f64(blob, lerc2_shared_mask);
426    }
427    Err(Error::InvalidMagic)
428}
429
430fn decode_band_set_owned<T: BandElement>(
431    blob: &[u8],
432    layout: BandLayout,
433    initial_lerc2_mask: Option<&[u8]>,
434) -> Result<(BandSetInfo, Vec<T>)> {
435    let band_info = scan_band_infos(blob)?;
436    decode_band_set_owned_direct(blob, layout, band_info, initial_lerc2_mask)
437}
438
439fn decode_band_set_into_direct<T: BandElement>(
440    blob: &[u8],
441    layout: BandLayout,
442    initial_lerc2_mask: Option<&[u8]>,
443    out: &mut [T],
444) -> Result<BandSetInfo> {
445    dispatch_band_element!(T, |Concrete| {
446        decode_band_set_into_impl::<Concrete>(
447            blob,
448            layout,
449            initial_lerc2_mask,
450            cast_slice_mut::<T, Concrete>(out),
451        )
452    })
453}
454
455fn decode_band_set_owned_direct<T: BandElement>(
456    blob: &[u8],
457    layout: BandLayout,
458    band_info: BandSetInfo,
459    initial_lerc2_mask: Option<&[u8]>,
460) -> Result<(BandSetInfo, Vec<T>)> {
461    dispatch_band_element!(T, |Concrete| {
462        decode_band_set_owned_direct_impl::<Concrete>(blob, layout, band_info, initial_lerc2_mask)
463            .map(|(info, values)| (info, cast_vec::<T, Concrete>(values)))
464    })
465}
466
467fn decode_band_set_into_impl<T: Sample + NdArrayElement>(
468    blob: &[u8],
469    layout: BandLayout,
470    initial_lerc2_mask: Option<&[u8]>,
471    out: &mut [T],
472) -> Result<BandSetInfo> {
473    let band_info = scan_band_infos(blob)?;
474    decode_band_set_into_impl_with_info(blob, layout, initial_lerc2_mask, &band_info, out)
475}
476
477fn decode_band_set_into_impl_with_info<T: Sample + NdArrayElement>(
478    blob: &[u8],
479    layout: BandLayout,
480    initial_lerc2_mask: Option<&[u8]>,
481    band_info: &BandSetInfo,
482    out: &mut [T],
483) -> Result<BandSetInfo> {
484    let band_count = band_info.band_count();
485    let expected_len = band_info.value_count()?;
486    if out.len() != expected_len {
487        return Err(Error::InvalidBlob(format!(
488            "output slice length {} does not match decoded band set length {}",
489            out.len(),
490            expected_len
491        )));
492    }
493
494    let pixel_count = band_info.bands[0].pixel_count()?;
495    let depth = band_info.depth() as usize;
496    let mut offset = 0usize;
497    let mut band_index = 0usize;
498    let mut lerc1_mask: Option<Vec<u8>> = None;
499    let mut lerc2_mask = initial_lerc2_mask.map(|mask| mask.to_vec());
500    let mut decoded_infos = Vec::with_capacity(band_count);
501
502    while offset < blob.len() {
503        let slice = &blob[offset..];
504        let is_lerc1 = lerc1::is_lerc1(slice);
505        let mut sink = BandSink::new(
506            out,
507            pixel_count,
508            depth,
509            band_index,
510            band_count,
511            materialize_layout(layout),
512        );
513        let (info, mask) = if is_lerc1 {
514            lerc1::decode_into(slice, lerc1_mask.as_deref(), &mut sink)?
515        } else if lerc2::is_lerc2(slice) {
516            lerc2::decode_into(slice, lerc2_mask.as_deref(), &mut sink)?
517        } else {
518            return Err(Error::InvalidMagic);
519        };
520
521        if is_lerc1 {
522            lerc1_mask = mask;
523            lerc2_mask = None;
524        } else {
525            lerc2_mask = mask;
526            lerc1_mask = None;
527        }
528
529        let blob_size = info.blob_size;
530        decoded_infos.push(info);
531        offset = checked_next_offset(offset, blob_size, blob.len())?;
532        band_index += 1;
533    }
534
535    BandSetInfo::new(decoded_infos)
536}
537
538fn decode_band_set_to_f64_info(
539    blob: &[u8],
540    layout: BandLayout,
541    initial_lerc2_mask: Option<&[u8]>,
542) -> Result<(BandSetInfo, Vec<f64>)> {
543    let band_info = scan_band_infos(blob)?;
544    decode_band_set_owned_direct_impl::<f64>(blob, layout, band_info, initial_lerc2_mask)
545}
546
547fn inspect_band_masks(
548    blob: &[u8],
549    initial_lerc2_mask: Option<&[u8]>,
550) -> Result<(BandSetInfo, Vec<Option<Vec<u8>>>)> {
551    let mut offset = 0usize;
552    let mut infos = Vec::new();
553    let mut band_masks = Vec::new();
554    let mut lerc1_mask: Option<Vec<u8>> = None;
555    let mut lerc2_mask = initial_lerc2_mask.map(|mask| mask.to_vec());
556
557    while offset < blob.len() {
558        let slice = &blob[offset..];
559        let is_lerc1 = lerc1::is_lerc1(slice);
560        let (info, mask) =
561            inspect_first_mask_with_info(slice, lerc1_mask.as_deref(), lerc2_mask.as_deref())?;
562
563        if is_lerc1 {
564            lerc1_mask = mask.clone();
565            lerc2_mask = None;
566        } else {
567            lerc2_mask = mask.clone();
568            lerc1_mask = None;
569        }
570
571        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
572        infos.push(info);
573        band_masks.push(mask);
574    }
575
576    Ok((BandSetInfo::new(infos)?, band_masks))
577}
578
579fn scan_band_infos(blob: &[u8]) -> Result<BandSetInfo> {
580    let mut offset = 0usize;
581    let mut infos = Vec::new();
582    let mut lerc1_mask: Option<Vec<u8>> = None;
583
584    while offset < blob.len() {
585        let slice = &blob[offset..];
586        let (info, next_lerc1_mask) = if lerc1::is_lerc1(slice) {
587            let parsed = lerc1::parse(slice, lerc1_mask.as_deref())?;
588            let info = parsed.info;
589            let next_mask = parsed.mask;
590            (info, next_mask)
591        } else if lerc2::is_lerc2(slice) {
592            let (parsed, _) = lerc2::parse(slice)?;
593            (parsed.info, None)
594        } else {
595            return Err(Error::InvalidMagic);
596        };
597
598        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
599        lerc1_mask = next_lerc1_mask;
600        infos.push(info);
601    }
602
603    BandSetInfo::new(infos)
604}
605
606fn ensure_single_blob_consumed(
607    blob_len: usize,
608    decoded_len: usize,
609    strict_api: &str,
610    permissive_api: &str,
611) -> Result<()> {
612    if blob_len == decoded_len {
613        return Ok(());
614    }
615    Err(Error::InvalidBlob(format!(
616        "{strict_api} only accepts a single LERC blob; found {} trailing bytes, use {permissive_api} for first-blob decoding or decode_band_set for concatenated rasters",
617        blob_len - decoded_len
618    )))
619}
620
621fn checked_next_offset(offset: usize, next_len: usize, total_len: usize) -> Result<usize> {
622    let next = offset
623        .checked_add(next_len)
624        .ok_or_else(|| Error::InvalidBlob("band offset overflow".into()))?;
625    if next <= offset || next > total_len {
626        return Err(Error::InvalidBlob(
627            "invalid concatenated band blob size".into(),
628        ));
629    }
630    Ok(next)
631}
632
633fn materialize_layout(layout: BandLayout) -> MaterializeLayout {
634    match layout {
635        BandLayout::Interleaved => MaterializeLayout::Interleaved,
636        BandLayout::Bsq => MaterializeLayout::Bsq,
637    }
638}
639
640fn materialize_error(err: lerc_band_materialize::MaterializeError) -> Error {
641    Error::InvalidBlob(err.to_string())
642}
643
644fn mask_to_ndarray(info: &BlobInfo, mask: Option<Vec<u8>>) -> Result<Option<ArrayD<u8>>> {
645    let shape = info.mask_ndarray_shape();
646    mask.map(|mask| {
647        ArrayD::from_shape_vec(ndarray::IxDyn(&shape), mask).map_err(|e| {
648            Error::InvalidBlob(format!("failed to build ndarray from decoded mask: {e}"))
649        })
650    })
651    .transpose()
652}
653
654fn decode_band_set_owned_direct_impl<T: Sample + NdArrayElement + Copy + Default>(
655    blob: &[u8],
656    layout: BandLayout,
657    band_info: BandSetInfo,
658    initial_lerc2_mask: Option<&[u8]>,
659) -> Result<(BandSetInfo, Vec<T>)> {
660    let pixel_count = band_info.bands[0].pixel_count()?;
661    let depth = band_info.depth() as usize;
662    let band_count = band_info.band_count();
663    let mut materializer =
664        BandMaterializer::new(pixel_count, depth, band_count, materialize_layout(layout))
665            .map_err(materialize_error)?;
666    let mut offset = 0usize;
667    let mut band_index = 0usize;
668    let mut lerc1_mask: Option<Vec<u8>> = None;
669    let mut lerc2_mask = initial_lerc2_mask.map(|mask| mask.to_vec());
670    let mut decoded_infos = Vec::with_capacity(band_count);
671
672    while offset < blob.len() {
673        let slice = &blob[offset..];
674        let is_lerc1 = lerc1::is_lerc1(slice);
675        let (info, mask) = {
676            let mut writer = materializer
677                .band_writer(band_index)
678                .map_err(materialize_error)?;
679            let decoded = if is_lerc1 {
680                lerc1::decode_into(slice, lerc1_mask.as_deref(), &mut writer)?
681            } else if lerc2::is_lerc2(slice) {
682                lerc2::decode_into(slice, lerc2_mask.as_deref(), &mut writer)?
683            } else {
684                return Err(Error::InvalidMagic);
685            };
686            writer.finish().map_err(materialize_error)?;
687            decoded
688        };
689
690        if is_lerc1 {
691            lerc1_mask = mask;
692            lerc2_mask = None;
693        } else {
694            lerc2_mask = mask;
695            lerc1_mask = None;
696        }
697
698        let blob_size = info.blob_size;
699        decoded_infos.push(info);
700        offset = checked_next_offset(offset, blob_size, blob.len())?;
701        band_index += 1;
702    }
703
704    Ok((
705        BandSetInfo::new(decoded_infos)?,
706        materializer.finish().map_err(materialize_error)?,
707    ))
708}
709
710fn cast_slice_mut<T, U>(slice: &mut [T]) -> &mut [U] {
711    // SAFETY: callers only reach this helper through dispatch_band_element!, where
712    // T and U are the same concrete primitive type selected by BandElementKind.
713    unsafe { &mut *(slice as *mut [T] as *mut [U]) }
714}
715
716fn cast_vec<T, U>(values: Vec<U>) -> Vec<T> {
717    let len = values.len();
718    let cap = values.capacity();
719    let ptr = values.as_ptr() as *mut T;
720    std::mem::forget(values);
721    // SAFETY: callers only reach this helper through dispatch_band_element!, where
722    // T and U are the same concrete primitive type. The allocation, length, and
723    // capacity therefore remain valid for Vec<T>.
724    unsafe { Vec::from_raw_parts(ptr, len, cap) }
725}