Skip to main content

lerc_reader/
lib.rs

1//! Pure-Rust LERC decoder.
2//!
3//! The public API distinguishes strict single-blob entry points from
4//! concatenated-band helpers:
5//!
6//! - inspect a single blob with [`get_blob_info`]
7//! - inspect only the first blob with [`inspect_first`]
8//! - count concatenated blobs with [`get_band_count`]
9//! - decode a single blob with [`decode`]
10//! - decode only the first blob with [`decode_first`]
11//! - decode concatenated band sets with [`decode_band_set`]
12//! - decode promoted `f64` buffers with [`decode_to_f64`]
13//! - decode only the first promoted `f64` blob with [`decode_first_to_f64`]
14//! - decode directly into `ndarray::ArrayD` with [`decode_ndarray`]
15
16mod bitstuff;
17mod huffman;
18mod io;
19mod lerc1;
20mod lerc2;
21mod pixel;
22mod types;
23
24#[cfg(test)]
25mod tests;
26
27use lerc_band_materialize::{BandLayout as MaterializeLayout, BandMaterializer, BandSink};
28use lerc_core::{BandLayout, BandSetInfo, BlobInfo, Error, Result};
29use ndarray::ArrayD;
30
31use crate::pixel::Sample;
32pub use crate::types::{
33    into_band_mask_ndarray, BandElement, BandElementKind, Decoded, DecodedBandSet, DecodedF64,
34    NdArrayElement,
35};
36
37macro_rules! dispatch_band_element {
38    ($target:ty, |$concrete:ident| $body:block) => {
39        match <$target as BandElement>::KIND {
40            BandElementKind::I8 => {
41                type $concrete = i8;
42                $body
43            }
44            BandElementKind::U8 => {
45                type $concrete = u8;
46                $body
47            }
48            BandElementKind::I16 => {
49                type $concrete = i16;
50                $body
51            }
52            BandElementKind::U16 => {
53                type $concrete = u16;
54                $body
55            }
56            BandElementKind::I32 => {
57                type $concrete = i32;
58                $body
59            }
60            BandElementKind::U32 => {
61                type $concrete = u32;
62                $body
63            }
64            BandElementKind::F32 => {
65                type $concrete = f32;
66                $body
67            }
68            BandElementKind::F64 => {
69                type $concrete = f64;
70                $body
71            }
72        }
73    };
74}
75
76pub fn inspect_first(blob: &[u8]) -> Result<BlobInfo> {
77    if lerc1::is_lerc1(blob) {
78        return lerc1::inspect(blob, None);
79    }
80    if lerc2::is_lerc2(blob) {
81        return lerc2::inspect(blob, None);
82    }
83    Err(Error::InvalidMagic)
84}
85
86pub fn get_blob_info(blob: &[u8]) -> Result<BlobInfo> {
87    let info = inspect_first(blob)?;
88    ensure_single_blob_consumed(blob.len(), info.blob_size, "get_blob_info", "inspect_first")?;
89    Ok(info)
90}
91
92pub fn get_band_count(blob: &[u8]) -> Result<usize> {
93    let mut offset = 0usize;
94    let mut count = 0usize;
95    let mut lerc1_mask: Option<Vec<u8>> = None;
96
97    while offset < blob.len() {
98        let slice = &blob[offset..];
99        let next_len = if lerc1::is_lerc1(slice) {
100            let parsed = lerc1::parse(slice, lerc1_mask.as_deref())?;
101            let next_len = parsed.info.blob_size;
102            lerc1_mask = parsed.mask;
103            next_len
104        } else if lerc2::is_lerc2(slice) {
105            let (info, _) = lerc2::parse(slice)?;
106            info.blob_size
107        } else {
108            return Err(Error::InvalidMagic);
109        };
110
111        offset = checked_next_offset(offset, next_len, blob.len())?;
112        count += 1;
113    }
114
115    Ok(count)
116}
117
118pub fn decode_first(blob: &[u8]) -> Result<Decoded> {
119    decode_first_with_masks(blob, None, None)
120}
121
122pub fn decode(blob: &[u8]) -> Result<Decoded> {
123    let decoded = decode_first(blob)?;
124    ensure_single_blob_consumed(blob.len(), decoded.info.blob_size, "decode", "decode_first")?;
125    Ok(decoded)
126}
127
128pub fn decode_band_set(blob: &[u8]) -> Result<DecodedBandSet> {
129    let mut offset = 0usize;
130    let mut bands = Vec::new();
131    let mut infos = Vec::new();
132    let mut band_masks = Vec::new();
133    let mut lerc1_mask: Option<Vec<u8>> = None;
134    let mut lerc2_mask: Option<Vec<u8>> = None;
135
136    while offset < blob.len() {
137        let decoded = decode_first_with_masks(
138            &blob[offset..],
139            lerc1_mask.as_deref(),
140            lerc2_mask.as_deref(),
141        )?;
142
143        if lerc1::is_lerc1(&blob[offset..]) {
144            lerc1_mask = decoded.mask.clone();
145            lerc2_mask = None;
146        } else {
147            lerc2_mask = decoded.mask.clone();
148            lerc1_mask = None;
149        }
150
151        offset = checked_next_offset(offset, decoded.info.blob_size, blob.len())?;
152        infos.push(decoded.info);
153        bands.push(decoded.pixels);
154        band_masks.push(decoded.mask);
155    }
156
157    Ok(DecodedBandSet {
158        info: BandSetInfo::new(infos)?,
159        bands,
160        band_masks,
161    })
162}
163
164pub fn decode_band_set_vec<T: BandElement>(
165    blob: &[u8],
166    layout: BandLayout,
167) -> Result<(BandSetInfo, Vec<T>)> {
168    decode_band_set_owned(blob, layout)
169}
170
171pub fn decode_band_set_into<T: BandElement>(
172    blob: &[u8],
173    layout: BandLayout,
174    out: &mut [T],
175) -> Result<BandSetInfo> {
176    decode_band_set_into_direct(blob, layout, out)
177}
178
179pub fn decode_band_set_ndarray<T: BandElement>(blob: &[u8]) -> Result<ArrayD<T>> {
180    decode_band_set_ndarray_with_layout(blob, BandLayout::Interleaved)
181}
182
183pub fn decode_band_set_ndarray_with_layout<T: BandElement>(
184    blob: &[u8],
185    layout: BandLayout,
186) -> Result<ArrayD<T>> {
187    let (info, values) = decode_band_set_owned(blob, layout)?;
188    let shape = info.ndarray_shape_for_layout(layout);
189    ArrayD::from_shape_vec(ndarray::IxDyn(&shape), values).map_err(|e| {
190        Error::InvalidBlob(format!(
191            "failed to build ndarray from decoded band set: {e}"
192        ))
193    })
194}
195
196pub fn decode_band_set_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
197    let band_info = decode_band_set_to_f64_info(blob, BandLayout::Interleaved)?;
198    let shape = band_info
199        .0
200        .ndarray_shape_for_layout(BandLayout::Interleaved);
201    ArrayD::from_shape_vec(ndarray::IxDyn(&shape), band_info.1).map_err(|e| {
202        Error::InvalidBlob(format!(
203            "failed to build ndarray from decoded band set: {e}"
204        ))
205    })
206}
207
208pub fn decode_band_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
209    let (info, band_masks) = inspect_band_masks(blob)?;
210    into_band_mask_ndarray(info, band_masks)
211}
212
213pub fn decode_to_f64(blob: &[u8]) -> Result<DecodedF64> {
214    let decoded = decode_first_to_f64(blob)?;
215    ensure_single_blob_consumed(
216        blob.len(),
217        decoded.info.blob_size,
218        "decode_to_f64",
219        "decode_first_to_f64",
220    )?;
221    Ok(decoded)
222}
223
224pub fn decode_first_to_f64(blob: &[u8]) -> Result<DecodedF64> {
225    decode_first_f64(blob)
226}
227
228pub fn decode_ndarray<T: NdArrayElement>(blob: &[u8]) -> Result<ArrayD<T>> {
229    decode(blob)?.into_ndarray()
230}
231
232pub fn decode_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
233    decode_to_f64(blob)?.into_ndarray()
234}
235
236pub fn decode_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
237    let (info, mask) = inspect_first_mask_with_info(blob, None, None)?;
238    ensure_single_blob_consumed(
239        blob.len(),
240        info.blob_size,
241        "decode_mask_ndarray",
242        "inspect_first",
243    )?;
244    mask_to_ndarray(&info, mask)
245}
246
247fn decode_first_with_masks(
248    blob: &[u8],
249    lerc1_shared_mask: Option<&[u8]>,
250    lerc2_shared_mask: Option<&[u8]>,
251) -> Result<Decoded> {
252    if lerc1::is_lerc1(blob) {
253        return lerc1::decode(blob, lerc1_shared_mask);
254    }
255    if lerc2::is_lerc2(blob) {
256        return lerc2::decode(blob, lerc2_shared_mask);
257    }
258    Err(Error::InvalidMagic)
259}
260
261fn inspect_first_mask_with_info(
262    blob: &[u8],
263    lerc1_shared_mask: Option<&[u8]>,
264    lerc2_shared_mask: Option<&[u8]>,
265) -> Result<(BlobInfo, Option<Vec<u8>>)> {
266    if lerc1::is_lerc1(blob) {
267        return lerc1::inspect_mask(blob, lerc1_shared_mask);
268    }
269    if lerc2::is_lerc2(blob) {
270        return lerc2::inspect_with_mask(blob, lerc2_shared_mask);
271    }
272    Err(Error::InvalidMagic)
273}
274
275fn decode_first_f64(blob: &[u8]) -> Result<DecodedF64> {
276    if lerc1::is_lerc1(blob) {
277        return lerc1::decode_f64(blob, None);
278    }
279    if lerc2::is_lerc2(blob) {
280        return lerc2::decode_f64(blob, None);
281    }
282    Err(Error::InvalidMagic)
283}
284
285fn decode_band_set_owned<T: BandElement>(
286    blob: &[u8],
287    layout: BandLayout,
288) -> Result<(BandSetInfo, Vec<T>)> {
289    let band_info = scan_band_infos(blob)?;
290    decode_band_set_owned_direct(blob, layout, band_info)
291}
292
293fn decode_band_set_into_direct<T: BandElement>(
294    blob: &[u8],
295    layout: BandLayout,
296    out: &mut [T],
297) -> Result<BandSetInfo> {
298    dispatch_band_element!(T, |Concrete| {
299        decode_band_set_into_impl::<Concrete>(blob, layout, cast_slice_mut::<T, Concrete>(out))
300    })
301}
302
303fn decode_band_set_owned_direct<T: BandElement>(
304    blob: &[u8],
305    layout: BandLayout,
306    band_info: BandSetInfo,
307) -> Result<(BandSetInfo, Vec<T>)> {
308    dispatch_band_element!(T, |Concrete| {
309        decode_band_set_owned_direct_impl::<Concrete>(blob, layout, band_info)
310            .map(|(info, values)| (info, cast_vec::<T, Concrete>(values)))
311    })
312}
313
314fn decode_band_set_into_impl<T: Sample + NdArrayElement>(
315    blob: &[u8],
316    layout: BandLayout,
317    out: &mut [T],
318) -> Result<BandSetInfo> {
319    let band_info = scan_band_infos(blob)?;
320    decode_band_set_into_impl_with_info(blob, layout, &band_info, out)?;
321    Ok(band_info)
322}
323
324fn decode_band_set_into_impl_with_info<T: Sample + NdArrayElement>(
325    blob: &[u8],
326    layout: BandLayout,
327    band_info: &BandSetInfo,
328    out: &mut [T],
329) -> Result<()> {
330    let band_count = band_info.band_count();
331    let expected_len = band_info.value_count()?;
332    if out.len() != expected_len {
333        return Err(Error::InvalidBlob(format!(
334            "output slice length {} does not match decoded band set length {}",
335            out.len(),
336            expected_len
337        )));
338    }
339
340    let pixel_count = band_info.bands[0].pixel_count()?;
341    let depth = band_info.depth() as usize;
342    let mut offset = 0usize;
343    let mut band_index = 0usize;
344    let mut lerc1_mask: Option<Vec<u8>> = None;
345    let mut lerc2_mask: Option<Vec<u8>> = None;
346
347    while offset < blob.len() {
348        let slice = &blob[offset..];
349        let is_lerc1 = lerc1::is_lerc1(slice);
350        let mut sink = BandSink::new(
351            out,
352            pixel_count,
353            depth,
354            band_index,
355            band_count,
356            materialize_layout(layout),
357        );
358        let (info, mask) = if is_lerc1 {
359            lerc1::decode_into(slice, lerc1_mask.as_deref(), &mut sink)?
360        } else if lerc2::is_lerc2(slice) {
361            lerc2::decode_into(slice, lerc2_mask.as_deref(), &mut sink)?
362        } else {
363            return Err(Error::InvalidMagic);
364        };
365
366        if is_lerc1 {
367            lerc1_mask = mask;
368            lerc2_mask = None;
369        } else {
370            lerc2_mask = mask;
371            lerc1_mask = None;
372        }
373
374        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
375        band_index += 1;
376    }
377
378    Ok(())
379}
380
381fn decode_band_set_to_f64_info(blob: &[u8], layout: BandLayout) -> Result<(BandSetInfo, Vec<f64>)> {
382    let band_info = scan_band_infos(blob)?;
383    decode_band_set_owned_direct_impl::<f64>(blob, layout, band_info)
384}
385
386fn inspect_band_masks(blob: &[u8]) -> Result<(BandSetInfo, Vec<Option<Vec<u8>>>)> {
387    let mut offset = 0usize;
388    let mut infos = Vec::new();
389    let mut band_masks = Vec::new();
390    let mut lerc1_mask: Option<Vec<u8>> = None;
391    let mut lerc2_mask: Option<Vec<u8>> = None;
392
393    while offset < blob.len() {
394        let slice = &blob[offset..];
395        let is_lerc1 = lerc1::is_lerc1(slice);
396        let (info, mask) =
397            inspect_first_mask_with_info(slice, lerc1_mask.as_deref(), lerc2_mask.as_deref())?;
398
399        if is_lerc1 {
400            lerc1_mask = mask.clone();
401            lerc2_mask = None;
402        } else {
403            lerc2_mask = mask.clone();
404            lerc1_mask = None;
405        }
406
407        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
408        infos.push(info);
409        band_masks.push(mask);
410    }
411
412    Ok((BandSetInfo::new(infos)?, band_masks))
413}
414
415fn scan_band_infos(blob: &[u8]) -> Result<BandSetInfo> {
416    let mut offset = 0usize;
417    let mut infos = Vec::new();
418    let mut lerc1_mask: Option<Vec<u8>> = None;
419
420    while offset < blob.len() {
421        let slice = &blob[offset..];
422        let (info, next_lerc1_mask) = if lerc1::is_lerc1(slice) {
423            let parsed = lerc1::parse(slice, lerc1_mask.as_deref())?;
424            let info = parsed.info;
425            let next_mask = parsed.mask;
426            (info, next_mask)
427        } else if lerc2::is_lerc2(slice) {
428            let (info, _) = lerc2::parse(slice)?;
429            (info, None)
430        } else {
431            return Err(Error::InvalidMagic);
432        };
433
434        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
435        lerc1_mask = next_lerc1_mask;
436        infos.push(info);
437    }
438
439    BandSetInfo::new(infos)
440}
441
442fn ensure_single_blob_consumed(
443    blob_len: usize,
444    decoded_len: usize,
445    strict_api: &str,
446    permissive_api: &str,
447) -> Result<()> {
448    if blob_len == decoded_len {
449        return Ok(());
450    }
451    Err(Error::InvalidBlob(format!(
452        "{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",
453        blob_len - decoded_len
454    )))
455}
456
457fn checked_next_offset(offset: usize, next_len: usize, total_len: usize) -> Result<usize> {
458    let next = offset
459        .checked_add(next_len)
460        .ok_or_else(|| Error::InvalidBlob("band offset overflow".into()))?;
461    if next <= offset || next > total_len {
462        return Err(Error::InvalidBlob(
463            "invalid concatenated band blob size".into(),
464        ));
465    }
466    Ok(next)
467}
468
469fn materialize_layout(layout: BandLayout) -> MaterializeLayout {
470    match layout {
471        BandLayout::Interleaved => MaterializeLayout::Interleaved,
472        BandLayout::Bsq => MaterializeLayout::Bsq,
473    }
474}
475
476fn materialize_error(err: lerc_band_materialize::MaterializeError) -> Error {
477    Error::InvalidBlob(err.to_string())
478}
479
480fn mask_to_ndarray(info: &BlobInfo, mask: Option<Vec<u8>>) -> Result<Option<ArrayD<u8>>> {
481    let shape = info.mask_ndarray_shape();
482    mask.map(|mask| {
483        ArrayD::from_shape_vec(ndarray::IxDyn(&shape), mask).map_err(|e| {
484            Error::InvalidBlob(format!("failed to build ndarray from decoded mask: {e}"))
485        })
486    })
487    .transpose()
488}
489
490fn decode_band_set_owned_direct_impl<T: Sample + NdArrayElement + Copy + Default>(
491    blob: &[u8],
492    layout: BandLayout,
493    band_info: BandSetInfo,
494) -> Result<(BandSetInfo, Vec<T>)> {
495    let expected_len = band_info.value_count()?;
496    if expected_len == 0 {
497        return Ok((band_info, Vec::new()));
498    }
499
500    let pixel_count = band_info.bands[0].pixel_count()?;
501    let depth = band_info.depth() as usize;
502    let band_count = band_info.band_count();
503    let mut materializer =
504        BandMaterializer::new(pixel_count, depth, band_count, materialize_layout(layout))
505            .map_err(materialize_error)?;
506    let mut offset = 0usize;
507    let mut band_index = 0usize;
508    let mut lerc1_mask: Option<Vec<u8>> = None;
509    let mut lerc2_mask: Option<Vec<u8>> = None;
510
511    while offset < blob.len() {
512        let slice = &blob[offset..];
513        let is_lerc1 = lerc1::is_lerc1(slice);
514        let (info, mask) = {
515            let mut writer = materializer
516                .band_writer(band_index)
517                .map_err(materialize_error)?;
518            let decoded = if is_lerc1 {
519                lerc1::decode_into(slice, lerc1_mask.as_deref(), &mut writer)?
520            } else if lerc2::is_lerc2(slice) {
521                lerc2::decode_into(slice, lerc2_mask.as_deref(), &mut writer)?
522            } else {
523                return Err(Error::InvalidMagic);
524            };
525            writer.finish().map_err(materialize_error)?;
526            decoded
527        };
528
529        if is_lerc1 {
530            lerc1_mask = mask;
531            lerc2_mask = None;
532        } else {
533            lerc2_mask = mask;
534            lerc1_mask = None;
535        }
536
537        offset = checked_next_offset(offset, info.blob_size, blob.len())?;
538        band_index += 1;
539    }
540
541    Ok((band_info, materializer.finish().map_err(materialize_error)?))
542}
543
544fn cast_slice_mut<T, U>(slice: &mut [T]) -> &mut [U] {
545    unsafe { &mut *(slice as *mut [T] as *mut [U]) }
546}
547
548fn cast_vec<T, U>(values: Vec<U>) -> Vec<T> {
549    let len = values.len();
550    let cap = values.capacity();
551    let ptr = values.as_ptr() as *mut T;
552    std::mem::forget(values);
553    unsafe { Vec::from_raw_parts(ptr, len, cap) }
554}