Skip to main content

lerc_reader/
lib.rs

1#![deny(unsafe_op_in_unsafe_fn)]
2#![warn(missing_docs)]
3
4//! Pure-Rust LERC decoder.
5//!
6//! The public API distinguishes strict single-blob entry points from
7//! concatenated-band helpers:
8//!
9//! - inspect a single blob with [`get_blob_info`]
10//! - inspect only the first blob with [`inspect_first`]
11//! - pass an explicit shared or external mask with the `*_with_mask` single-blob variants
12//! - count concatenated blobs with [`get_band_count`]
13//! - decode a single blob with [`decode`]
14//! - decode only the first blob with [`decode_first`]
15//! - decode concatenated band sets with [`decode_band_set`]
16//! - seed a first-band external mask with the band-set `*_with_mask` variants
17//! - decode promoted `f64` buffers with [`decode_to_f64`]
18//! - decode only the first promoted `f64` blob with [`decode_first_to_f64`]
19//! - decode directly into `ndarray::ArrayD` with the default `ndarray` feature
20
21mod allocation;
22mod bitstuff;
23mod huffman;
24mod io;
25mod lerc1;
26mod lerc2;
27mod materialize;
28mod pixel;
29mod stream;
30mod types;
31
32#[cfg(test)]
33mod test_support;
34#[cfg(test)]
35mod tests;
36
37use lerc_core::{BandLayout, BandSetInfo, BlobInfo, DataType, Error, Result};
38use materialize::BandSink;
39#[cfg(feature = "rayon")]
40use materialize::{copy_band_values_into_slice, PixelDataWriter};
41#[cfg(feature = "ndarray")]
42use ndarray::ArrayD;
43#[cfg(feature = "rayon")]
44use rayon::prelude::*;
45
46use crate::allocation::default_vec;
47use crate::pixel::Sample;
48#[cfg(feature = "ndarray")]
49pub use crate::types::NdArrayElement;
50pub use crate::types::{BandElement, BandElementKind, Decoded, DecodedBandSet, DecodedF64};
51
52/// Controls optional work performed while inspecting a blob.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[non_exhaustive]
55pub struct InspectOptions {
56    /// Whether Lerc1 blocks are decoded far enough to compute their exact value range.
57    pub compute_value_range: bool,
58}
59
60impl InspectOptions {
61    /// Creates options that preserve the exact-range behavior of [`inspect_first`].
62    pub const fn new() -> Self {
63        Self {
64            compute_value_range: true,
65        }
66    }
67
68    /// Enables or disables exact Lerc1 value-range computation.
69    pub const fn with_compute_value_range(mut self, compute_value_range: bool) -> Self {
70        self.compute_value_range = compute_value_range;
71        self
72    }
73}
74
75impl Default for InspectOptions {
76    fn default() -> Self {
77        Self::new()
78    }
79}
80
81macro_rules! dispatch_band_element {
82    ($target:ty, |$concrete:ident| $body:block) => {
83        match <$target as BandElement>::KIND {
84            BandElementKind::I8 => {
85                type $concrete = i8;
86                $body
87            }
88            BandElementKind::U8 => {
89                type $concrete = u8;
90                $body
91            }
92            BandElementKind::I16 => {
93                type $concrete = i16;
94                $body
95            }
96            BandElementKind::U16 => {
97                type $concrete = u16;
98                $body
99            }
100            BandElementKind::I32 => {
101                type $concrete = i32;
102                $body
103            }
104            BandElementKind::U32 => {
105                type $concrete = u32;
106                $body
107            }
108            BandElementKind::F32 => {
109                type $concrete = f32;
110                $body
111            }
112            BandElementKind::F64 => {
113                type $concrete = f64;
114                $body
115            }
116        }
117    };
118}
119
120/// Inspects the first blob and computes exact Lerc1 value ranges.
121///
122/// # Errors
123/// Returns an error when the first blob is malformed or unsupported.
124pub fn inspect_first(blob: &[u8]) -> Result<BlobInfo> {
125    inspect_first_with_options(blob, InspectOptions::default())
126}
127
128/// Inspects the first blob with configurable Lerc1 range computation.
129///
130/// # Errors
131/// Returns an error when the first blob is malformed or unsupported.
132pub fn inspect_first_with_options(blob: &[u8], options: InspectOptions) -> Result<BlobInfo> {
133    if lerc1::is_lerc1(blob) {
134        return lerc1::inspect_with_mask_options(blob, None, options.compute_value_range)
135            .map(|(info, _)| info);
136    }
137    if lerc2::is_lerc2(blob) {
138        return lerc2::inspect(blob, None);
139    }
140    Err(Error::InvalidMagic)
141}
142
143/// Inspects the first blob using `mask` when it omits its own validity mask.
144///
145/// # Errors
146/// Returns an error for malformed input or a mismatched external mask.
147pub fn inspect_first_with_mask(blob: &[u8], mask: &[u8]) -> Result<BlobInfo> {
148    let (info, _) = inspect_first_mask_with_info(blob, Some(mask), Some(mask))?;
149    Ok(info)
150}
151
152/// Strictly inspects one blob and rejects trailing bytes.
153///
154/// # Errors
155/// Returns an error for malformed input or any trailing data.
156pub fn get_blob_info(blob: &[u8]) -> Result<BlobInfo> {
157    let info = inspect_first(blob)?;
158    ensure_single_blob_consumed(blob.len(), info.blob_size, "get_blob_info", "inspect_first")?;
159    Ok(info)
160}
161
162/// Strictly inspects one externally masked blob and rejects trailing bytes.
163///
164/// # Errors
165/// Returns an error for malformed input, a mismatched mask, or trailing data.
166pub fn get_blob_info_with_mask(blob: &[u8], mask: &[u8]) -> Result<BlobInfo> {
167    let info = inspect_first_with_mask(blob, mask)?;
168    ensure_single_blob_consumed(
169        blob.len(),
170        info.blob_size,
171        "get_blob_info_with_mask",
172        "inspect_first_with_mask",
173    )?;
174    Ok(info)
175}
176
177/// Counts validated blobs in a concatenated band payload.
178///
179/// # Errors
180/// Returns an error if any blob boundary, header, or inherited mask is invalid.
181pub fn get_band_count(blob: &[u8]) -> Result<usize> {
182    let mut offset = 0usize;
183    let mut count = 0usize;
184    let mut lerc1_mask: Option<Vec<u8>> = None;
185    let mut lerc2_mask: Option<Vec<u8>> = None;
186    let mut declared_band_count: Option<usize> = None;
187
188    while offset < blob.len() {
189        if declared_band_count == Some(count) {
190            return Err(Error::invalid_blob(
191                "Lerc2 v6 remaining-band count is smaller than the payload",
192            ));
193        }
194        let slice = &blob[offset..];
195        let info = if lerc1::is_lerc1(slice) {
196            let parsed = lerc1::parse(slice, lerc1_mask.as_deref())?;
197            lerc1_mask = parsed.mask;
198            lerc2_mask = None;
199            parsed.info
200        } else if lerc2::is_lerc2(slice) {
201            let (info, mask) = lerc2::inspect_with_mask(slice, lerc2_mask.as_deref())?;
202            lerc2_mask = mask;
203            lerc1_mask = None;
204            info
205        } else {
206            return Err(Error::InvalidMagic);
207        };
208
209        if matches!(info.version, lerc_core::Version::Lerc2(version) if version >= 6) {
210            let declared = count
211                .checked_add(1)
212                .and_then(|count| count.checked_add(info.remaining_band_count as usize))
213                .ok_or(Error::SizeOverflow("declared Lerc2 band count"))?;
214            if declared_band_count.is_some_and(|previous| previous != declared) {
215                return Err(Error::invalid_blob(
216                    "Lerc2 v6 headers disagree about the remaining-band count",
217                ));
218            }
219            declared_band_count = Some(declared);
220        }
221
222        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
223        count += 1;
224    }
225
226    if declared_band_count.is_some_and(|declared| declared != count) {
227        return Err(Error::invalid_blob(
228            "Lerc2 v6 remaining-band count exceeds the payload",
229        ));
230    }
231    Ok(count)
232}
233
234/// Decodes the first blob and permits trailing bytes.
235///
236/// # Errors
237/// Returns an error when the first blob is malformed or unsupported.
238pub fn decode_first(blob: &[u8]) -> Result<Decoded> {
239    decode_first_with_masks(blob, None, None)
240}
241
242/// Decodes the first blob with an initial external mask and permits trailing bytes.
243///
244/// # Errors
245/// Returns an error for malformed input or a mismatched mask.
246pub fn decode_first_with_mask(blob: &[u8], mask: &[u8]) -> Result<Decoded> {
247    decode_first_with_masks(blob, Some(mask), Some(mask))
248}
249
250/// Strictly decodes exactly one blob into its native sample type.
251///
252/// # Errors
253/// Returns an error for malformed input, unsupported features, or trailing bytes.
254pub fn decode(blob: &[u8]) -> Result<Decoded> {
255    let decoded = decode_first(blob)?;
256    ensure_single_blob_consumed(blob.len(), decoded.info.blob_size, "decode", "decode_first")?;
257    Ok(decoded)
258}
259
260/// Strictly decodes one blob with an initial external mask.
261///
262/// # Errors
263/// Returns an error for malformed input, a mismatched mask, or trailing bytes.
264pub fn decode_with_mask(blob: &[u8], mask: &[u8]) -> Result<Decoded> {
265    let decoded = decode_first_with_mask(blob, mask)?;
266    ensure_single_blob_consumed(
267        blob.len(),
268        decoded.info.blob_size,
269        "decode_with_mask",
270        "decode_first_with_mask",
271    )?;
272    Ok(decoded)
273}
274
275/// Reads and decodes exactly one blob, leaving subsequent stream bytes unread.
276///
277/// # Errors
278/// Returns an error for I/O failure, truncation, or invalid encoded data.
279pub fn decode_from_reader<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<Decoded> {
280    let blob = read_required_stream_blob(reader, None)?;
281    decode(&blob)
282}
283
284/// Reads and decodes one blob with an initial external mask.
285///
286/// # Errors
287/// Returns an error for I/O failure, truncation, invalid data, or a mismatched mask.
288pub fn decode_from_reader_with_mask<R: std::io::Read + ?Sized>(
289    reader: &mut R,
290    mask: &[u8],
291) -> Result<Decoded> {
292    let blob = read_required_stream_blob(reader, Some(mask))?;
293    decode_with_mask(&blob, mask)
294}
295
296/// Decodes every concatenated blob as a native-typed band set.
297///
298/// # Errors
299/// Returns an error if any band is invalid or band shapes disagree.
300pub fn decode_band_set(blob: &[u8]) -> Result<DecodedBandSet> {
301    decode_band_set_with_lerc2_mask(blob, None)
302}
303
304/// Decodes a band set, seeding the first blob with an external mask.
305///
306/// # Errors
307/// Returns an error for invalid blobs, masks, or mismatched band shapes.
308pub fn decode_band_set_with_mask(blob: &[u8], mask: &[u8]) -> Result<DecodedBandSet> {
309    decode_band_set_with_lerc2_mask(blob, Some(mask))
310}
311
312/// Reads concatenated blobs through clean EOF and decodes them as a band set.
313///
314/// # Errors
315/// Returns an error for I/O failure, truncation, invalid blobs, or mismatched shapes.
316pub fn decode_band_set_from_reader<R: std::io::Read + ?Sized>(
317    reader: &mut R,
318) -> Result<DecodedBandSet> {
319    decode_band_set_from_reader_impl(reader, None)
320}
321
322/// Streams a band set through EOF with an initial external mask.
323///
324/// # Errors
325/// Returns an error for I/O failure, invalid data, masks, or band shapes.
326pub fn decode_band_set_from_reader_with_mask<R: std::io::Read + ?Sized>(
327    reader: &mut R,
328    mask: &[u8],
329) -> Result<DecodedBandSet> {
330    decode_band_set_from_reader_impl(reader, Some(mask))
331}
332
333/// Decodes a band set into a homogeneous vector in the requested layout.
334///
335/// # Errors
336/// Returns an error for invalid input or an incompatible output element type.
337pub fn decode_band_set_vec<T: BandElement>(
338    blob: &[u8],
339    layout: BandLayout,
340) -> Result<(BandSetInfo, Vec<T>)> {
341    decode_band_set_owned(blob, layout, None)
342}
343
344/// Decodes an externally masked band set into a homogeneous vector.
345///
346/// # Errors
347/// Returns an error for invalid input, mask mismatch, or incompatible element type.
348pub fn decode_band_set_vec_with_mask<T: BandElement>(
349    blob: &[u8],
350    mask: &[u8],
351    layout: BandLayout,
352) -> Result<(BandSetInfo, Vec<T>)> {
353    decode_band_set_owned(blob, layout, Some(mask))
354}
355
356/// Decodes a band set directly into an exactly sized output slice.
357///
358/// # Errors
359/// Returns an error for invalid input, incompatible types, or the wrong output length.
360pub fn decode_band_set_into<T: BandElement>(
361    blob: &[u8],
362    layout: BandLayout,
363    out: &mut [T],
364) -> Result<BandSetInfo> {
365    decode_band_set_into_direct(blob, layout, None, out)
366}
367
368/// Decodes an externally masked band set directly into an output slice.
369///
370/// # Errors
371/// Returns an error for invalid input, mask mismatch, type mismatch, or wrong length.
372pub fn decode_band_set_into_with_mask<T: BandElement>(
373    blob: &[u8],
374    mask: &[u8],
375    layout: BandLayout,
376    out: &mut [T],
377) -> Result<BandSetInfo> {
378    decode_band_set_into_direct(blob, layout, Some(mask), out)
379}
380
381#[cfg(feature = "ndarray")]
382/// Decodes a band set into an interleaved ndarray.
383///
384/// # Errors
385/// Returns an error for invalid data, incompatible types, or shape construction failure.
386pub fn decode_band_set_ndarray<T: BandElement>(blob: &[u8]) -> Result<ArrayD<T>> {
387    decode_band_set_ndarray_with_layout(blob, BandLayout::Interleaved)
388}
389
390#[cfg(feature = "ndarray")]
391/// Decodes an externally masked band set into an interleaved ndarray.
392///
393/// # Errors
394/// Returns an error for invalid data, mask mismatch, type mismatch, or shape failure.
395pub fn decode_band_set_ndarray_with_mask<T: BandElement>(
396    blob: &[u8],
397    mask: &[u8],
398) -> Result<ArrayD<T>> {
399    decode_band_set_ndarray_with_layout_and_mask(blob, BandLayout::Interleaved, mask)
400}
401
402#[cfg(feature = "ndarray")]
403/// Decodes a band set into an ndarray using `layout`.
404///
405/// # Errors
406/// Returns an error for invalid data, incompatible types, or shape failure.
407pub fn decode_band_set_ndarray_with_layout<T: BandElement>(
408    blob: &[u8],
409    layout: BandLayout,
410) -> Result<ArrayD<T>> {
411    decode_band_set_ndarray_with_layout_impl(blob, layout, None)
412}
413
414#[cfg(feature = "ndarray")]
415/// Decodes an externally masked band set into an ndarray using `layout`.
416///
417/// # Errors
418/// Returns an error for invalid data, masks, types, or shape construction.
419pub fn decode_band_set_ndarray_with_layout_and_mask<T: BandElement>(
420    blob: &[u8],
421    layout: BandLayout,
422    mask: &[u8],
423) -> Result<ArrayD<T>> {
424    decode_band_set_ndarray_with_layout_impl(blob, layout, Some(mask))
425}
426
427#[cfg(feature = "ndarray")]
428/// Decodes and promotes a band set into an interleaved `f64` ndarray.
429///
430/// # Errors
431/// Returns an error for invalid data or shape construction failure.
432pub fn decode_band_set_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
433    decode_band_set_ndarray_f64_with_layout(blob, BandLayout::Interleaved)
434}
435
436#[cfg(feature = "ndarray")]
437/// Decodes and promotes an externally masked band set into an `f64` ndarray.
438///
439/// # Errors
440/// Returns an error for invalid data, mask mismatch, or shape failure.
441pub fn decode_band_set_ndarray_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<ArrayD<f64>> {
442    decode_band_set_ndarray_f64_with_layout_and_mask(blob, BandLayout::Interleaved, mask)
443}
444
445#[cfg(feature = "ndarray")]
446/// Decodes and promotes a band set into an `f64` ndarray using `layout`.
447///
448/// # Errors
449/// Returns an error for invalid data or shape construction failure.
450pub fn decode_band_set_ndarray_f64_with_layout(
451    blob: &[u8],
452    layout: BandLayout,
453) -> Result<ArrayD<f64>> {
454    decode_band_set_ndarray_f64_with_optional_mask(blob, layout, None)
455}
456
457#[cfg(feature = "ndarray")]
458/// Decodes and promotes an externally masked band set using `layout`.
459///
460/// # Errors
461/// Returns an error for invalid data, mask mismatch, or shape failure.
462pub fn decode_band_set_ndarray_f64_with_layout_and_mask(
463    blob: &[u8],
464    layout: BandLayout,
465    mask: &[u8],
466) -> Result<ArrayD<f64>> {
467    decode_band_set_ndarray_f64_with_optional_mask(blob, layout, Some(mask))
468}
469
470#[cfg(feature = "ndarray")]
471/// Returns decoded band masks as an ndarray without decoding pixel values.
472///
473/// # Errors
474/// Returns an error for invalid blobs, inconsistent masks, or shape failure.
475pub fn decode_band_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
476    let (info, band_masks) = inspect_band_masks(blob, None)?;
477    crate::types::band_masks_into_ndarray(info, band_masks)
478}
479
480#[cfg(feature = "ndarray")]
481/// Returns band masks seeded by an external mask as an ndarray.
482///
483/// # Errors
484/// Returns an error for invalid blobs, masks, or shape construction.
485pub fn decode_band_mask_ndarray_with_mask(blob: &[u8], mask: &[u8]) -> Result<Option<ArrayD<u8>>> {
486    let (info, band_masks) = inspect_band_masks(blob, Some(mask))?;
487    crate::types::band_masks_into_ndarray(info, band_masks)
488}
489
490fn decode_band_set_with_lerc2_mask(
491    blob: &[u8],
492    initial_mask: Option<&[u8]>,
493) -> Result<DecodedBandSet> {
494    let mut offset = 0usize;
495    let mut bands = Vec::new();
496    let mut infos = Vec::new();
497    let mut band_masks = Vec::new();
498    let mut lerc1_mask = initial_mask.map(<[u8]>::to_vec);
499    let mut lerc2_mask = initial_mask.map(<[u8]>::to_vec);
500
501    while offset < blob.len() {
502        let decoded = decode_first_with_masks(
503            &blob[offset..],
504            lerc1_mask.as_deref(),
505            lerc2_mask.as_deref(),
506        )?;
507
508        if lerc1::is_lerc1(&blob[offset..]) {
509            lerc1_mask = decoded.mask.clone();
510            lerc2_mask = None;
511        } else {
512            lerc2_mask = decoded.mask.clone();
513            lerc1_mask = None;
514        }
515
516        offset = checked_next_offset(offset, decoded.info.blob_size, blob.len())?;
517        infos.push(decoded.info);
518        bands.push(decoded.pixels);
519        band_masks.push(decoded.mask);
520    }
521
522    DecodedBandSet::new(BandSetInfo::new(infos)?, bands, band_masks)
523}
524
525#[cfg(feature = "ndarray")]
526fn decode_band_set_ndarray_with_layout_impl<T: BandElement>(
527    blob: &[u8],
528    layout: BandLayout,
529    initial_mask: Option<&[u8]>,
530) -> Result<ArrayD<T>> {
531    let (info, values) = decode_band_set_owned(blob, layout, initial_mask)?;
532    let shape = info.ndarray_shape_for_layout(layout);
533    ArrayD::from_shape_vec(ndarray::IxDyn(&shape), values).map_err(|e| {
534        Error::invalid_blob(format!(
535            "failed to build ndarray from decoded band set: {e}"
536        ))
537    })
538}
539
540#[cfg(feature = "ndarray")]
541fn decode_band_set_ndarray_f64_with_optional_mask(
542    blob: &[u8],
543    layout: BandLayout,
544    initial_mask: Option<&[u8]>,
545) -> Result<ArrayD<f64>> {
546    let band_info = decode_band_set_to_f64_info(blob, layout, initial_mask)?;
547    let shape = band_info.0.ndarray_shape_for_layout(layout);
548    ArrayD::from_shape_vec(ndarray::IxDyn(&shape), band_info.1).map_err(|e| {
549        Error::invalid_blob(format!(
550            "failed to build ndarray from decoded band set: {e}"
551        ))
552    })
553}
554
555/// Strictly decodes one blob and promotes every sample to `f64`.
556///
557/// # Errors
558/// Returns an error for malformed data, unsupported features, or trailing bytes.
559pub fn decode_to_f64(blob: &[u8]) -> Result<DecodedF64> {
560    let decoded = decode_first_to_f64(blob)?;
561    ensure_single_blob_consumed(
562        blob.len(),
563        decoded.info.blob_size,
564        "decode_to_f64",
565        "decode_first_to_f64",
566    )?;
567    Ok(decoded)
568}
569
570/// Strictly decodes one externally masked blob as promoted `f64` samples.
571///
572/// # Errors
573/// Returns an error for invalid input, mask mismatch, or trailing bytes.
574pub fn decode_to_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<DecodedF64> {
575    let decoded = decode_first_to_f64_with_mask(blob, mask)?;
576    ensure_single_blob_consumed(
577        blob.len(),
578        decoded.info.blob_size,
579        "decode_to_f64_with_mask",
580        "decode_first_to_f64_with_mask",
581    )?;
582    Ok(decoded)
583}
584
585/// Reads and decodes one blob as `f64`, leaving later stream bytes unread.
586///
587/// # Errors
588/// Returns an error for I/O failure, truncation, or invalid data.
589pub fn decode_to_f64_from_reader<R: std::io::Read + ?Sized>(reader: &mut R) -> Result<DecodedF64> {
590    let blob = read_required_stream_blob(reader, None)?;
591    decode_to_f64(&blob)
592}
593
594/// Reads and decodes one externally masked blob as `f64`.
595///
596/// # Errors
597/// Returns an error for I/O failure, invalid data, or mask mismatch.
598pub fn decode_to_f64_from_reader_with_mask<R: std::io::Read + ?Sized>(
599    reader: &mut R,
600    mask: &[u8],
601) -> Result<DecodedF64> {
602    let blob = read_required_stream_blob(reader, Some(mask))?;
603    decode_to_f64_with_mask(&blob, mask)
604}
605
606/// Decodes the first blob as promoted `f64` samples and permits trailing bytes.
607///
608/// # Errors
609/// Returns an error when the first blob is malformed or unsupported.
610pub fn decode_first_to_f64(blob: &[u8]) -> Result<DecodedF64> {
611    decode_first_f64(blob)
612}
613
614/// Decodes the first externally masked blob as `f64` and permits trailing bytes.
615///
616/// # Errors
617/// Returns an error for invalid data or a mismatched mask.
618pub fn decode_first_to_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<DecodedF64> {
619    decode_first_f64_with_masks(blob, Some(mask), Some(mask))
620}
621
622#[cfg(feature = "ndarray")]
623/// Strictly decodes one native-typed blob into an ndarray.
624///
625/// # Errors
626/// Returns an error for invalid data, incompatible output type, or shape failure.
627pub fn decode_ndarray<T: NdArrayElement>(blob: &[u8]) -> Result<ArrayD<T>> {
628    decode(blob)?.into_ndarray()
629}
630
631#[cfg(feature = "ndarray")]
632/// Strictly decodes one externally masked blob into an ndarray.
633///
634/// # Errors
635/// Returns an error for invalid data, masks, types, or shape construction.
636pub fn decode_ndarray_with_mask<T: NdArrayElement>(blob: &[u8], mask: &[u8]) -> Result<ArrayD<T>> {
637    decode_with_mask(blob, mask)?.into_ndarray()
638}
639
640#[cfg(feature = "ndarray")]
641/// Strictly decodes one blob into a promoted `f64` ndarray.
642///
643/// # Errors
644/// Returns an error for invalid data or shape construction failure.
645pub fn decode_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
646    decode_to_f64(blob)?.into_ndarray()
647}
648
649#[cfg(feature = "ndarray")]
650/// Strictly decodes one externally masked blob into an `f64` ndarray.
651///
652/// # Errors
653/// Returns an error for invalid data, mask mismatch, or shape failure.
654pub fn decode_ndarray_f64_with_mask(blob: &[u8], mask: &[u8]) -> Result<ArrayD<f64>> {
655    decode_to_f64_with_mask(blob, mask)?.into_ndarray()
656}
657
658#[cfg(feature = "ndarray")]
659/// Strictly decodes one blob's validity mask as an ndarray.
660///
661/// # Errors
662/// Returns an error for invalid data, trailing bytes, or shape construction.
663pub fn decode_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
664    let (info, mask) = inspect_first_mask_with_info(blob, None, None)?;
665    ensure_single_blob_consumed(
666        blob.len(),
667        info.blob_size,
668        "decode_mask_ndarray",
669        "inspect_first",
670    )?;
671    mask_to_ndarray(&info, mask)
672}
673
674#[cfg(feature = "ndarray")]
675/// Strictly decodes one blob's externally seeded mask as an ndarray.
676///
677/// # Errors
678/// Returns an error for invalid data, mask mismatch, trailing bytes, or shape failure.
679pub fn decode_mask_ndarray_with_mask(blob: &[u8], mask: &[u8]) -> Result<Option<ArrayD<u8>>> {
680    let (info, decoded_mask) = inspect_first_mask_with_info(blob, Some(mask), Some(mask))?;
681    ensure_single_blob_consumed(
682        blob.len(),
683        info.blob_size,
684        "decode_mask_ndarray_with_mask",
685        "inspect_first_with_mask",
686    )?;
687    mask_to_ndarray(&info, decoded_mask)
688}
689
690fn decode_first_with_masks(
691    blob: &[u8],
692    lerc1_shared_mask: Option<&[u8]>,
693    lerc2_shared_mask: Option<&[u8]>,
694) -> Result<Decoded> {
695    if lerc1::is_lerc1(blob) {
696        return lerc1::decode(blob, lerc1_shared_mask);
697    }
698    if lerc2::is_lerc2(blob) {
699        return lerc2::decode(blob, lerc2_shared_mask);
700    }
701    Err(Error::InvalidMagic)
702}
703
704fn inspect_first_mask_with_info(
705    blob: &[u8],
706    lerc1_shared_mask: Option<&[u8]>,
707    lerc2_shared_mask: Option<&[u8]>,
708) -> Result<(BlobInfo, Option<Vec<u8>>)> {
709    if lerc1::is_lerc1(blob) {
710        return lerc1::inspect_mask(blob, lerc1_shared_mask);
711    }
712    if lerc2::is_lerc2(blob) {
713        return lerc2::inspect_with_mask(blob, lerc2_shared_mask);
714    }
715    Err(Error::InvalidMagic)
716}
717
718fn decode_first_f64(blob: &[u8]) -> Result<DecodedF64> {
719    decode_first_f64_with_masks(blob, None, None)
720}
721
722fn decode_first_f64_with_masks(
723    blob: &[u8],
724    lerc1_shared_mask: Option<&[u8]>,
725    lerc2_shared_mask: Option<&[u8]>,
726) -> Result<DecodedF64> {
727    if lerc1::is_lerc1(blob) {
728        return lerc1::decode_f64(blob, lerc1_shared_mask);
729    }
730    if lerc2::is_lerc2(blob) {
731        return lerc2::decode_f64(blob, lerc2_shared_mask);
732    }
733    Err(Error::InvalidMagic)
734}
735
736fn decode_band_set_owned<T: BandElement>(
737    blob: &[u8],
738    layout: BandLayout,
739    initial_mask: Option<&[u8]>,
740) -> Result<(BandSetInfo, Vec<T>)> {
741    let band_info = scan_band_infos(blob, initial_mask)?;
742    decode_band_set_owned_direct(blob, layout, band_info, initial_mask)
743}
744
745fn decode_band_set_into_direct<T: BandElement>(
746    blob: &[u8],
747    layout: BandLayout,
748    initial_mask: Option<&[u8]>,
749    out: &mut [T],
750) -> Result<BandSetInfo> {
751    dispatch_band_element!(T, |Concrete| {
752        decode_band_set_into_impl::<Concrete>(
753            blob,
754            layout,
755            initial_mask,
756            cast_slice_mut::<T, Concrete>(out),
757        )
758    })
759}
760
761fn decode_band_set_owned_direct<T: BandElement>(
762    blob: &[u8],
763    layout: BandLayout,
764    band_info: BandSetInfo,
765    initial_mask: Option<&[u8]>,
766) -> Result<(BandSetInfo, Vec<T>)> {
767    dispatch_band_element!(T, |Concrete| {
768        decode_band_set_owned_direct_impl::<Concrete>(blob, layout, band_info, initial_mask)
769            .map(|(info, values)| (info, cast_vec::<T, Concrete>(values)))
770    })
771}
772
773fn decode_band_set_into_impl<T: Sample + BandElement>(
774    blob: &[u8],
775    layout: BandLayout,
776    initial_mask: Option<&[u8]>,
777    out: &mut [T],
778) -> Result<BandSetInfo> {
779    let band_info = scan_band_infos(blob, initial_mask)?;
780    decode_band_set_into_impl_with_info(blob, layout, initial_mask, &band_info, out)
781}
782
783fn decode_band_set_into_impl_with_info<T: Sample + BandElement>(
784    blob: &[u8],
785    layout: BandLayout,
786    initial_mask: Option<&[u8]>,
787    band_info: &BandSetInfo,
788    out: &mut [T],
789) -> Result<BandSetInfo> {
790    validate_band_output_type::<T>(band_info)?;
791    #[cfg(feature = "rayon")]
792    {
793        if band_info.band_count() > 1 {
794            return decode_band_set_into_parallel(blob, layout, initial_mask, band_info, out);
795        }
796    }
797    decode_band_set_into_sequential(blob, layout, initial_mask, band_info, out)
798}
799
800fn validate_band_output_type<T: BandElement>(band_info: &BandSetInfo) -> Result<()> {
801    if T::KIND == BandElementKind::F64 {
802        return Ok(());
803    }
804    let expected = match T::KIND {
805        BandElementKind::I8 => DataType::I8,
806        BandElementKind::U8 => DataType::U8,
807        BandElementKind::I16 => DataType::I16,
808        BandElementKind::U16 => DataType::U16,
809        BandElementKind::I32 => DataType::I32,
810        BandElementKind::U32 => DataType::U32,
811        BandElementKind::F32 => DataType::F32,
812        BandElementKind::F64 => unreachable!("f64 promotion returned above"),
813    };
814    if band_info
815        .bands()
816        .iter()
817        .any(|band| band.data_type != expected)
818    {
819        return Err(Error::InvalidArgument(
820            "output element type must match every native band type unless decoding to f64",
821        ));
822    }
823    Ok(())
824}
825
826fn decode_band_set_into_sequential<T: Sample + BandElement>(
827    blob: &[u8],
828    layout: BandLayout,
829    initial_mask: Option<&[u8]>,
830    band_info: &BandSetInfo,
831    out: &mut [T],
832) -> Result<BandSetInfo> {
833    let band_count = band_info.band_count();
834    let expected_len = band_info.value_count()?;
835    if out.len() != expected_len {
836        return Err(Error::InvalidArgument(
837            "output slice length does not match decoded band set length",
838        ));
839    }
840
841    let pixel_count = band_info.first().pixel_count()?;
842    let depth = band_info.depth() as usize;
843    let mut offset = 0usize;
844    let mut band_index = 0usize;
845    let mut lerc1_mask = initial_mask.map(<[u8]>::to_vec);
846    let mut lerc2_mask = initial_mask.map(<[u8]>::to_vec);
847    let mut decoded_infos = Vec::with_capacity(band_count);
848
849    while offset < blob.len() {
850        let slice = &blob[offset..];
851        let is_lerc1 = lerc1::is_lerc1(slice);
852        let mut sink = BandSink::new(out, pixel_count, depth, band_index, band_count, layout);
853        let (info, mask) = if is_lerc1 {
854            lerc1::decode_into(slice, lerc1_mask.as_deref(), &mut sink)?
855        } else if lerc2::is_lerc2(slice) {
856            lerc2::decode_into(slice, lerc2_mask.as_deref(), &mut sink)?
857        } else {
858            return Err(Error::InvalidMagic);
859        };
860
861        if is_lerc1 {
862            lerc1_mask = mask;
863            lerc2_mask = None;
864        } else {
865            lerc2_mask = mask;
866            lerc1_mask = None;
867        }
868
869        let blob_size = info.blob_size;
870        decoded_infos.push(info);
871        offset = checked_next_offset(offset, blob_size, blob.len())?;
872        band_index += 1;
873    }
874
875    BandSetInfo::new(decoded_infos)
876}
877
878#[cfg(feature = "rayon")]
879#[derive(Debug, Clone, Copy)]
880enum BandFormat {
881    Lerc1,
882    Lerc2,
883}
884
885#[cfg(feature = "rayon")]
886#[derive(Debug)]
887struct ParallelBand<'a> {
888    blob: &'a [u8],
889    format: BandFormat,
890    inherited_mask: Option<std::sync::Arc<[u8]>>,
891}
892
893#[cfg(feature = "rayon")]
894fn scan_parallel_bands<'a>(
895    blob: &'a [u8],
896    initial_mask: Option<&[u8]>,
897) -> Result<Vec<ParallelBand<'a>>> {
898    use std::sync::Arc;
899
900    let mut offset = 0usize;
901    let mut bands = Vec::new();
902    let mut lerc1_mask = initial_mask.map(Arc::<[u8]>::from);
903    let mut lerc2_mask = initial_mask.map(Arc::<[u8]>::from);
904
905    while offset < blob.len() {
906        let slice = &blob[offset..];
907        let format = if lerc1::is_lerc1(slice) {
908            BandFormat::Lerc1
909        } else if lerc2::is_lerc2(slice) {
910            BandFormat::Lerc2
911        } else {
912            return Err(Error::InvalidMagic);
913        };
914        let inherited_mask = match format {
915            BandFormat::Lerc1 => lerc1_mask.clone(),
916            BandFormat::Lerc2 => lerc2_mask.clone(),
917        };
918        let (info, decoded_mask) =
919            inspect_first_mask_with_info(slice, lerc1_mask.as_deref(), lerc2_mask.as_deref())?;
920        let next = checked_next_offset(offset, info.blob_size, blob.len())?;
921
922        bands.push(ParallelBand {
923            blob: &blob[offset..next],
924            format,
925            inherited_mask,
926        });
927
928        match format {
929            BandFormat::Lerc1 => {
930                lerc1_mask = decoded_mask.map(Arc::from);
931                lerc2_mask = None;
932            }
933            BandFormat::Lerc2 => {
934                lerc2_mask = decoded_mask.map(Arc::from);
935                lerc1_mask = None;
936            }
937        }
938        offset = next;
939    }
940
941    Ok(bands)
942}
943
944#[cfg(feature = "rayon")]
945fn decode_parallel_band<T: Sample>(
946    band: &ParallelBand<'_>,
947    out: &mut [T],
948    depth: usize,
949) -> Result<BlobInfo> {
950    let mut writer = PixelDataWriter::new(out, depth);
951    let (info, _) = match band.format {
952        BandFormat::Lerc1 => {
953            lerc1::decode_into(band.blob, band.inherited_mask.as_deref(), &mut writer)?
954        }
955        BandFormat::Lerc2 => {
956            lerc2::decode_into(band.blob, band.inherited_mask.as_deref(), &mut writer)?
957        }
958    };
959    Ok(info)
960}
961
962#[cfg(feature = "rayon")]
963fn decode_band_set_into_parallel<T: Sample + BandElement>(
964    blob: &[u8],
965    layout: BandLayout,
966    initial_mask: Option<&[u8]>,
967    band_info: &BandSetInfo,
968    out: &mut [T],
969) -> Result<BandSetInfo> {
970    let expected_len = band_info.value_count()?;
971    if out.len() != expected_len {
972        return Err(Error::InvalidArgument(
973            "output slice length does not match decoded band set length",
974        ));
975    }
976
977    let bands = scan_parallel_bands(blob, initial_mask)?;
978    let band_count = band_info.band_count();
979    if bands.len() != band_count {
980        return Err(Error::Internal(
981            "parallel band scan disagrees with decoded band metadata",
982        ));
983    }
984    let pixel_count = band_info.first().pixel_count()?;
985    let depth = band_info.depth() as usize;
986    let band_len = band_info.first().sample_count()?;
987
988    let infos = match layout {
989        BandLayout::Bsq => out
990            .par_chunks_mut(band_len)
991            .zip(bands.par_iter())
992            .map(|(band_out, band)| decode_parallel_band(band, band_out, depth))
993            .collect::<Result<Vec<_>>>()?,
994        BandLayout::Interleaved => {
995            let decoded = bands
996                .par_iter()
997                .map(|band| {
998                    let mut values = default_vec(band_len, "parallel decoded band")?;
999                    let info = decode_parallel_band(band, &mut values, depth)?;
1000                    Ok((info, values))
1001                })
1002                .collect::<Result<Vec<_>>>()?;
1003            let mut infos = Vec::with_capacity(band_count);
1004            for (band_index, (info, values)) in decoded.into_iter().enumerate() {
1005                copy_band_values_into_slice(
1006                    out,
1007                    &values,
1008                    pixel_count,
1009                    depth,
1010                    band_index,
1011                    band_count,
1012                    layout,
1013                )?;
1014                infos.push(info);
1015            }
1016            infos
1017        }
1018    };
1019
1020    BandSetInfo::new(infos)
1021}
1022
1023#[cfg(feature = "ndarray")]
1024fn decode_band_set_to_f64_info(
1025    blob: &[u8],
1026    layout: BandLayout,
1027    initial_mask: Option<&[u8]>,
1028) -> Result<(BandSetInfo, Vec<f64>)> {
1029    let band_info = scan_band_infos(blob, initial_mask)?;
1030    decode_band_set_owned_direct_impl::<f64>(blob, layout, band_info, initial_mask)
1031}
1032
1033#[cfg(feature = "ndarray")]
1034fn inspect_band_masks(
1035    blob: &[u8],
1036    initial_mask: Option<&[u8]>,
1037) -> Result<(BandSetInfo, Vec<Option<Vec<u8>>>)> {
1038    let mut offset = 0usize;
1039    let mut infos = Vec::new();
1040    let mut band_masks = Vec::new();
1041    let mut lerc1_mask = initial_mask.map(<[u8]>::to_vec);
1042    let mut lerc2_mask = initial_mask.map(<[u8]>::to_vec);
1043
1044    while offset < blob.len() {
1045        let slice = &blob[offset..];
1046        let is_lerc1 = lerc1::is_lerc1(slice);
1047        let (info, mask) =
1048            inspect_first_mask_with_info(slice, lerc1_mask.as_deref(), lerc2_mask.as_deref())?;
1049
1050        if is_lerc1 {
1051            lerc1_mask = mask.clone();
1052            lerc2_mask = None;
1053        } else {
1054            lerc2_mask = mask.clone();
1055            lerc1_mask = None;
1056        }
1057
1058        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
1059        infos.push(info);
1060        band_masks.push(mask);
1061    }
1062
1063    Ok((BandSetInfo::new(infos)?, band_masks))
1064}
1065
1066fn scan_band_infos(blob: &[u8], initial_mask: Option<&[u8]>) -> Result<BandSetInfo> {
1067    let mut offset = 0usize;
1068    let mut infos = Vec::new();
1069    let mut lerc1_mask = initial_mask.map(<[u8]>::to_vec);
1070    let mut lerc2_mask = initial_mask.map(<[u8]>::to_vec);
1071
1072    while offset < blob.len() {
1073        let slice = &blob[offset..];
1074        let info = if lerc1::is_lerc1(slice) {
1075            let parsed = lerc1::parse(slice, lerc1_mask.as_deref())?;
1076            let info = parsed.info;
1077            lerc1_mask = parsed.mask;
1078            lerc2_mask = None;
1079            info
1080        } else if lerc2::is_lerc2(slice) {
1081            let (info, mask) = lerc2::inspect_with_mask(slice, lerc2_mask.as_deref())?;
1082            lerc2_mask = mask;
1083            lerc1_mask = None;
1084            info
1085        } else {
1086            return Err(Error::InvalidMagic);
1087        };
1088
1089        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
1090        infos.push(info);
1091    }
1092
1093    BandSetInfo::new(infos)
1094}
1095
1096fn read_required_stream_blob<R: std::io::Read + ?Sized>(
1097    reader: &mut R,
1098    lerc1_shared_mask: Option<&[u8]>,
1099) -> Result<Vec<u8>> {
1100    stream::read_next_blob(reader, lerc1_shared_mask)?.ok_or(Error::Truncated {
1101        offset: 0,
1102        needed: 10,
1103        available: 0,
1104    })
1105}
1106
1107fn decode_band_set_from_reader_impl<R: std::io::Read + ?Sized>(
1108    reader: &mut R,
1109    initial_mask: Option<&[u8]>,
1110) -> Result<DecodedBandSet> {
1111    let mut bands = Vec::new();
1112    let mut infos = Vec::new();
1113    let mut band_masks = Vec::new();
1114    let mut lerc1_mask = initial_mask.map(<[u8]>::to_vec);
1115    let mut lerc2_mask = initial_mask.map(<[u8]>::to_vec);
1116
1117    while let Some(blob) = stream::read_next_blob(reader, lerc1_mask.as_deref())? {
1118        let is_lerc1 = lerc1::is_lerc1(&blob);
1119        let decoded = decode_first_with_masks(&blob, lerc1_mask.as_deref(), lerc2_mask.as_deref())?;
1120
1121        if is_lerc1 {
1122            lerc1_mask = decoded.mask.clone();
1123            lerc2_mask = None;
1124        } else {
1125            lerc2_mask = decoded.mask.clone();
1126            lerc1_mask = None;
1127        }
1128        infos.push(decoded.info);
1129        bands.push(decoded.pixels);
1130        band_masks.push(decoded.mask);
1131    }
1132
1133    DecodedBandSet::new(BandSetInfo::new(infos)?, bands, band_masks)
1134}
1135
1136fn ensure_single_blob_consumed(
1137    blob_len: usize,
1138    decoded_len: usize,
1139    strict_api: &str,
1140    permissive_api: &str,
1141) -> Result<()> {
1142    if blob_len == decoded_len {
1143        return Ok(());
1144    }
1145    Err(Error::invalid_blob(format!(
1146        "{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",
1147        blob_len - decoded_len
1148    )))
1149}
1150
1151fn checked_next_offset(offset: usize, next_len: usize, total_len: usize) -> Result<usize> {
1152    let next = offset
1153        .checked_add(next_len)
1154        .ok_or(Error::SizeOverflow("concatenated band offset"))?;
1155    if next <= offset || next > total_len {
1156        return Err(Error::invalid_blob("invalid concatenated band blob size"));
1157    }
1158    Ok(next)
1159}
1160
1161#[cfg(feature = "ndarray")]
1162fn mask_to_ndarray(info: &BlobInfo, mask: Option<Vec<u8>>) -> Result<Option<ArrayD<u8>>> {
1163    let shape = info.mask_ndarray_shape();
1164    mask.map(|mask| {
1165        ArrayD::from_shape_vec(ndarray::IxDyn(&shape), mask).map_err(|e| {
1166            Error::invalid_blob(format!("failed to build ndarray from decoded mask: {e}"))
1167        })
1168    })
1169    .transpose()
1170}
1171
1172fn decode_band_set_owned_direct_impl<T: Sample + BandElement + Copy + Default>(
1173    blob: &[u8],
1174    layout: BandLayout,
1175    band_info: BandSetInfo,
1176    initial_mask: Option<&[u8]>,
1177) -> Result<(BandSetInfo, Vec<T>)> {
1178    let mut values = default_vec(band_info.value_count()?, "decoded band set")?;
1179    let decoded_info =
1180        decode_band_set_into_impl_with_info(blob, layout, initial_mask, &band_info, &mut values)?;
1181    Ok((decoded_info, values))
1182}
1183
1184fn cast_slice_mut<T, U>(slice: &mut [T]) -> &mut [U] {
1185    debug_assert_eq!(std::mem::size_of::<T>(), std::mem::size_of::<U>());
1186    debug_assert_eq!(std::mem::align_of::<T>(), std::mem::align_of::<U>());
1187    // SAFETY: callers only reach this helper through dispatch_band_element!, where
1188    // T and U are the same concrete primitive type selected by BandElementKind.
1189    unsafe { &mut *(slice as *mut [T] as *mut [U]) }
1190}
1191
1192fn cast_vec<T, U>(values: Vec<U>) -> Vec<T> {
1193    debug_assert_eq!(std::mem::size_of::<T>(), std::mem::size_of::<U>());
1194    debug_assert_eq!(std::mem::align_of::<T>(), std::mem::align_of::<U>());
1195    let len = values.len();
1196    let cap = values.capacity();
1197    let ptr = values.as_ptr() as *mut T;
1198    std::mem::forget(values);
1199    // SAFETY: callers only reach this helper through dispatch_band_element!, where
1200    // T and U are the same concrete primitive type. The allocation, length, and
1201    // capacity therefore remain valid for Vec<T>.
1202    unsafe { Vec::from_raw_parts(ptr, len, cap) }
1203}