Skip to main content

lerc/
lib.rs

1//! Pure Rust implementation of the LERC (Limited Error Raster Compression) format.
2//!
3//! Supports encoding and decoding of raster images with configurable lossy or lossless
4//! compression. Compatible with ESRI's LERC2 format specification.
5
6#![cfg_attr(not(feature = "std"), no_std)]
7#![allow(
8    clippy::cast_possible_truncation,
9    clippy::cast_possible_wrap,
10    clippy::cast_sign_loss,
11    clippy::cast_precision_loss,
12    clippy::cast_lossless
13)]
14
15extern crate alloc;
16
17/// Error types for LERC encoding and decoding.
18pub mod error;
19/// Pixel data types and the `Sample` trait for type-safe encoding/decoding.
20pub mod types;
21
22/// Validity bitmask for tracking valid/invalid pixels.
23pub mod bitmask;
24pub(crate) mod bitstuffer;
25/// Fletcher-32 checksum used by the LERC2 format.
26#[allow(dead_code)]
27pub mod checksum;
28#[allow(dead_code)]
29pub(crate) mod header;
30#[allow(dead_code)]
31pub(crate) mod huffman;
32pub(crate) mod rle;
33
34pub(crate) mod decode;
35pub(crate) mod encode;
36#[allow(dead_code)]
37pub(crate) mod fpl;
38pub(crate) mod lerc1;
39#[allow(dead_code)]
40pub(crate) mod tiles;
41
42pub use error::{LercError, Result};
43pub use types::{DataType, Sample};
44
45use alloc::vec;
46use alloc::vec::Vec;
47
48use bitmask::BitMask;
49
50/// Controls the precision/error tolerance for LERC encoding.
51///
52/// `Lossless` preserves exact values. `Tolerance(x)` allows decoded values
53/// to differ from originals by at most +/-x.
54#[derive(Debug, Clone, Copy, PartialEq, Default)]
55pub enum Precision<T> {
56    /// Lossless compression. Exact round-trip for all pixel values.
57    #[default]
58    Lossless,
59    /// Lossy compression. Decoded values are within the given tolerance of originals.
60    Tolerance(T),
61}
62
63/// Metadata returned from a decode-into operation (no owned pixel data).
64#[derive(Debug, Clone)]
65pub struct DecodeResult {
66    /// Image width in pixels.
67    pub width: u32,
68    /// Image height in pixels.
69    pub height: u32,
70    /// Number of values per pixel (depth slices).
71    pub depth: u32,
72    /// Number of bands in the image.
73    pub bands: u32,
74    /// Pixel data type of the decoded blob.
75    pub data_type: DataType,
76    /// Per-band validity masks indicating which pixels are valid.
77    pub valid_masks: Vec<BitMask>,
78    /// NoData sentinel value, if the blob uses NoData encoding.
79    pub no_data_value: Option<f64>,
80}
81
82/// Header metadata extracted from a LERC blob without decoding pixel data.
83#[derive(Debug, Clone, Default)]
84pub struct LercInfo {
85    /// LERC format version number.
86    pub version: i32,
87    /// Image width in pixels.
88    pub width: u32,
89    /// Image height in pixels.
90    pub height: u32,
91    /// Number of values per pixel (depth slices).
92    pub depth: u32,
93    /// Number of bands in the image.
94    pub bands: u32,
95    /// Pixel data type stored in the blob.
96    pub data_type: DataType,
97    /// Number of valid (non-masked) pixels.
98    pub valid_pixels: u32,
99    /// Maximum error tolerance used during encoding.
100    pub tolerance: f64,
101    /// Minimum pixel value across all valid pixels.
102    pub min_value: f64,
103    /// Maximum pixel value across all valid pixels.
104    pub max_value: f64,
105    /// Total size of the LERC blob in bytes.
106    pub blob_size: u32,
107    /// The original NoData value, if the blob uses NoData encoding (v6+, depth > 1).
108    pub no_data_value: Option<f64>,
109}
110
111/// A decoded raster image with pixel data and validity masks.
112#[derive(Debug, Clone)]
113pub struct Image {
114    /// Image width in pixels.
115    pub width: u32,
116    /// Image height in pixels.
117    pub height: u32,
118    /// Number of values per pixel (depth slices).
119    pub depth: u32,
120    /// Number of bands in the image.
121    pub bands: u32,
122    /// Pixel data type.
123    pub data_type: DataType,
124    /// Per-band validity masks indicating which pixels are valid.
125    pub valid_masks: Vec<BitMask>,
126    /// Pixel sample data stored as a typed vector.
127    pub data: SampleData,
128    /// The original NoData value, if any. When set during encoding with depth > 1,
129    /// pixels matching this value in invalid depth slices are encoded with a sentinel.
130    /// On decode, the sentinel is remapped back to this value.
131    pub no_data_value: Option<f64>,
132}
133
134impl Default for Image {
135    fn default() -> Self {
136        Self {
137            width: 0,
138            height: 0,
139            depth: 1,
140            bands: 1,
141            data_type: DataType::Byte,
142            valid_masks: Vec::new(),
143            data: SampleData::U8(Vec::new()),
144            no_data_value: None,
145        }
146    }
147}
148
149/// Decoded pixel data and metadata returned by [`decode_slice`].
150///
151/// Holds the typed pixel buffer, the validity mask, and the image dimensions
152/// for a single-band, single-depth LERC blob.
153#[derive(Debug, Clone)]
154pub struct DecodedSlice<T> {
155    /// Decoded pixel data, one element per pixel, in row-major order.
156    pub pixels: Vec<T>,
157    /// Validity mask indicating which pixels are valid.
158    pub mask: BitMask,
159    /// Image width in pixels.
160    pub width: u32,
161    /// Image height in pixels.
162    pub height: u32,
163}
164
165/// Type-erased pixel data container, one variant per supported data type.
166#[derive(Debug, Clone)]
167pub enum SampleData {
168    /// Signed 8-bit integer pixel data.
169    I8(Vec<i8>),
170    /// Unsigned 8-bit integer pixel data.
171    U8(Vec<u8>),
172    /// Signed 16-bit integer pixel data.
173    I16(Vec<i16>),
174    /// Unsigned 16-bit integer pixel data.
175    U16(Vec<u16>),
176    /// Signed 32-bit integer pixel data.
177    I32(Vec<i32>),
178    /// Unsigned 32-bit integer pixel data.
179    U32(Vec<u32>),
180    /// 32-bit floating-point pixel data.
181    F32(Vec<f32>),
182    /// 64-bit floating-point pixel data.
183    F64(Vec<f64>),
184}
185
186/// Read header metadata from a LERC blob without decoding pixel data.
187pub fn decode_info(data: &[u8]) -> Result<LercInfo> {
188    decode::decode_info(data)
189}
190
191/// Decode a LERC blob, returning the image with pixel data and validity masks.
192pub fn decode(data: &[u8]) -> Result<Image> {
193    decode::decode(data)
194}
195
196/// Encode an image into a LERC blob with the given precision.
197///
198/// This entry point clones the image's pixel buffer when called via the
199/// owning `Image` path. To avoid that clone, see [`encode_borrowed`], which
200/// takes a borrowed `&[T]` and image-shape parameters directly.
201pub fn encode(image: &Image, precision: Precision<f64>) -> Result<Vec<u8>> {
202    let max_z_error = match precision {
203        Precision::Lossless => {
204            if image.data_type.is_integer() {
205                0.5
206            } else {
207                0.0
208            }
209        }
210        Precision::Tolerance(val) => val,
211    };
212    encode::encode(image, max_z_error)
213}
214
215/// Zero-copy multi-band encode entry point.
216///
217/// Encodes a raster image directly from a borrowed pixel slice, avoiding the
218/// buffer clone forced by the [`Image`]-based [`encode`] API. The pixel type
219/// `T` determines the LERC data type automatically via [`Sample`]; tolerances
220/// are expressed in `T` and widened to `f64` internally, matching the typed
221/// slice helpers ([`encode_slice`], [`encode_slice_masked`]).
222///
223/// # Data layout
224///
225/// `data` is band-major: outermost is `band`, then row-major within each band,
226/// then `depth` slices interleaved per pixel. Concretely, the value for band
227/// `b`, row `r`, column `c`, depth `d` is at index
228/// `b * (width * height * depth) + (r * width + c) * depth + d`.
229///
230/// `data.len()` must equal `width * height * depth * bands`.
231///
232/// # Validity masks
233///
234/// `masks.len()` must equal `bands`, with one [`BitMask`] per band (each
235/// having `num_pixels() == width * height`). For fully valid bands, pass
236/// [`BitMask::all_valid(width as usize * height as usize)`](BitMask::all_valid).
237///
238/// # Errors
239///
240/// Returns [`LercError::InvalidData`] if the data length, the number of
241/// masks, or any mask's pixel count does not match the declared shape.
242///
243/// # Examples
244///
245/// ```
246/// use lerc::{Precision, encode_borrowed};
247/// use lerc::bitmask::BitMask;
248///
249/// let width = 4u32;
250/// let height = 3u32;
251/// let pixels: Vec<f32> = (0..12).map(|i| i as f32).collect();
252/// let masks = [BitMask::all_valid((width * height) as usize)];
253/// let blob = encode_borrowed::<f32>(
254///     width, height, 1, 1,
255///     &pixels,
256///     &masks,
257///     None,
258///     Precision::Lossless,
259/// ).expect("encode failed");
260/// assert!(!blob.is_empty());
261/// ```
262#[allow(clippy::too_many_arguments)]
263pub fn encode_borrowed<T: Sample>(
264    width: u32,
265    height: u32,
266    depth: u32,
267    bands: u32,
268    data: &[T],
269    masks: &[BitMask],
270    no_data_value: Option<f64>,
271    precision: Precision<T>,
272) -> Result<Vec<u8>> {
273    let pixels_per_band = (width as usize) * (height as usize);
274    let expected = pixels_per_band * (depth as usize) * (bands as usize);
275    if data.len() != expected {
276        return Err(LercError::InvalidData(alloc::format!(
277            "data length {} does not match width*height*depth*bands {expected}",
278            data.len()
279        )));
280    }
281    if masks.len() != bands as usize {
282        return Err(LercError::InvalidData(alloc::format!(
283            "masks length {} does not match bands {}",
284            masks.len(),
285            bands
286        )));
287    }
288    for (i, mask) in masks.iter().enumerate() {
289        if mask.num_pixels() != pixels_per_band {
290            return Err(LercError::InvalidData(alloc::format!(
291                "mask[{i}] pixel count {} does not match width*height {pixels_per_band}",
292                mask.num_pixels()
293            )));
294        }
295    }
296    let max_z_error: f64 = match precision {
297        Precision::Lossless => {
298            if T::is_integer() {
299                0.5
300            } else {
301                0.0
302            }
303        }
304        Precision::Tolerance(val) => val.to_f64(),
305    };
306    encode::encode_borrowed_typed(
307        width,
308        height,
309        depth,
310        bands,
311        data,
312        masks,
313        no_data_value,
314        max_z_error,
315    )
316}
317
318// ---------------------------------------------------------------------------
319// Typed convenience encode/decode helpers
320// ---------------------------------------------------------------------------
321
322/// Encode a single-band image with all pixels valid.
323///
324/// The pixel type `T` determines the LERC data type automatically via `Sample`.
325/// Returns an error if `data.len() != width * height`.
326///
327/// This helper internally clones `data` into an owned `Image`. For zero-copy
328/// encoding, including multi-band layouts, see [`encode_borrowed`].
329pub fn encode_slice<T: Sample>(
330    width: u32,
331    height: u32,
332    data: &[T],
333    precision: Precision<T>,
334) -> Result<Vec<u8>> {
335    let expected = (width as usize) * (height as usize);
336    if data.len() != expected {
337        return Err(LercError::InvalidData(alloc::format!(
338            "data length {} does not match width*height {expected}",
339            data.len()
340        )));
341    }
342    let max_z_error: f64 = match precision {
343        Precision::Lossless => {
344            if T::is_integer() {
345                0.5
346            } else {
347                0.0
348            }
349        }
350        Precision::Tolerance(val) => val.to_f64(),
351    };
352    let image = Image {
353        width,
354        height,
355        depth: 1,
356        bands: 1,
357        data_type: T::DATA_TYPE,
358        valid_masks: vec![BitMask::all_valid(expected)],
359        data: T::into_lerc_data(data.to_vec()),
360        no_data_value: None,
361    };
362    encode::encode(&image, max_z_error)
363}
364
365/// Encode a single-band image with a validity mask.
366///
367/// The pixel type `T` determines the LERC data type automatically via `Sample`.
368/// Returns an error if `data.len() != width * height` or if the mask size does not match.
369///
370/// This helper internally clones `data` into an owned `Image`. For zero-copy
371/// encoding, including multi-band layouts, see [`encode_borrowed`].
372pub fn encode_slice_masked<T: Sample>(
373    width: u32,
374    height: u32,
375    data: &[T],
376    mask: &BitMask,
377    precision: Precision<T>,
378) -> Result<Vec<u8>> {
379    let expected = (width as usize) * (height as usize);
380    if data.len() != expected {
381        return Err(LercError::InvalidData(alloc::format!(
382            "data length {} does not match width*height {expected}",
383            data.len()
384        )));
385    }
386    if mask.num_pixels() != expected {
387        return Err(LercError::InvalidData(alloc::format!(
388            "mask pixel count {} does not match width*height {expected}",
389            mask.num_pixels()
390        )));
391    }
392    let max_z_error: f64 = match precision {
393        Precision::Lossless => {
394            if T::is_integer() {
395                0.5
396            } else {
397                0.0
398            }
399        }
400        Precision::Tolerance(val) => val.to_f64(),
401    };
402    let image = Image {
403        width,
404        height,
405        depth: 1,
406        bands: 1,
407        data_type: T::DATA_TYPE,
408        valid_masks: vec![mask.clone()],
409        data: T::into_lerc_data(data.to_vec()),
410        no_data_value: None,
411    };
412    encode::encode(&image, max_z_error)
413}
414
415/// Decode a single-band, single-depth LERC blob into a [`DecodedSlice<T>`]
416/// containing the typed pixel data, validity mask, and image dimensions.
417///
418/// The pixel type `T` must match the blob's data type. Returns an error on type
419/// mismatch or if the blob contains multiple bands or depths (use [`decode`] for
420/// multi-band/multi-depth blobs to get full shape and per-band masks).
421pub fn decode_slice<T: Sample>(blob: &[u8]) -> Result<DecodedSlice<T>> {
422    let image = decode::decode(blob)?;
423    if image.bands > 1 {
424        return Err(LercError::InvalidData(alloc::format!(
425            "decode_slice requires single-band data, got {} bands (use decode() instead)",
426            image.bands
427        )));
428    }
429    if image.depth > 1 {
430        return Err(LercError::InvalidData(alloc::format!(
431            "decode_slice requires single-depth data, got depth={} (use decode() instead)",
432            image.depth
433        )));
434    }
435    let w = image.width;
436    let h = image.height;
437    let pixels = T::try_from_lerc_data(image.data).map_err(|_| {
438        LercError::InvalidData(alloc::format!(
439            "expected {:?} data but blob contains {:?}",
440            T::DATA_TYPE,
441            image.data_type
442        ))
443    })?;
444    let mask = image
445        .valid_masks
446        .into_iter()
447        .next()
448        .unwrap_or_else(|| BitMask::all_valid((w as usize) * (h as usize)));
449    Ok(DecodedSlice {
450        pixels,
451        mask,
452        width: w,
453        height: h,
454    })
455}
456
457// ---------------------------------------------------------------------------
458// Typed accessor methods on Image
459// ---------------------------------------------------------------------------
460
461impl Image {
462    /// Try to borrow the pixel data as `&[T]`.
463    ///
464    /// Returns `None` if the image's data type does not match `T`.
465    pub fn as_typed<T: Sample>(&self) -> Option<&[T]> {
466        T::try_ref_lerc_data(&self.data)
467    }
468
469    /// Return the validity mask for the first band, or `None` if no masks are present.
470    pub fn mask(&self) -> Option<&BitMask> {
471        self.valid_masks.first()
472    }
473
474    /// Get the pixel value at `(row, col)` for single-band, single-depth images.
475    ///
476    /// Returns `None` if the data type does not match `T`, if `bands > 1` or
477    /// `depth > 1`, or if the coordinates are out of bounds.
478    pub fn pixel<T: Sample>(&self, row: u32, col: u32) -> Option<T> {
479        if self.bands != 1 || self.depth != 1 {
480            return None;
481        }
482        if row >= self.height || col >= self.width {
483            return None;
484        }
485        let data = self.as_typed::<T>()?;
486        let idx = row as usize * self.width as usize + col as usize;
487        Some(data[idx])
488    }
489
490    /// Iterate over valid pixels as `(row, col, value)` tuples.
491    ///
492    /// Only works for single-band, single-depth images. Returns `None` if the data
493    /// type does not match `T` or if `bands > 1` or `depth > 1`.
494    /// The iterator respects the validity mask, skipping invalid pixels.
495    pub fn valid_pixels<'a, T: Sample + 'a>(
496        &'a self,
497    ) -> Option<impl Iterator<Item = (u32, u32, T)> + 'a> {
498        if self.bands != 1 || self.depth != 1 {
499            return None;
500        }
501        let data = self.as_typed::<T>()?;
502        let width = self.width;
503        let mask = self.valid_masks.first();
504        Some(data.iter().enumerate().filter_map(move |(idx, &val)| {
505            let is_valid = match mask {
506                Some(m) => m.is_valid(idx),
507                None => true,
508            };
509            if is_valid {
510                let row = (idx / width as usize) as u32;
511                let col = (idx % width as usize) as u32;
512                Some((row, col, val))
513            } else {
514                None
515            }
516        }))
517    }
518
519    /// Get dimensions as `(width, height)`.
520    pub fn dimensions(&self) -> (u32, u32) {
521        (self.width, self.height)
522    }
523
524    /// Total number of pixels (`width * height`).
525    pub fn num_pixels(&self) -> usize {
526        self.width as usize * self.height as usize
527    }
528
529    /// Check if all pixels in the first band are valid.
530    ///
531    /// Returns `true` if there is no mask (all pixels are implicitly valid),
532    /// if the first band's mask is [`BitMask::AllValid`] (O(1)), or if an
533    /// explicit mask happens to have every bit set (O(n) popcount fallback).
534    pub fn all_valid(&self) -> bool {
535        match self.valid_masks.first() {
536            Some(m) => m.is_all_valid(),
537            None => true,
538        }
539    }
540
541    /// Create a single-band, all-valid `Image` from a typed pixel vector
542    /// and dimensions.
543    ///
544    /// Returns an error if `data.len() != width * height`.
545    pub fn from_pixels<T: Sample>(width: u32, height: u32, data: Vec<T>) -> Result<Self> {
546        let expected = width as usize * height as usize;
547        if data.len() != expected {
548            return Err(LercError::InvalidData(alloc::format!(
549                "data length {} does not match width*height {expected}",
550                data.len()
551            )));
552        }
553        Ok(Self {
554            width,
555            height,
556            depth: 1,
557            bands: 1,
558            data_type: T::DATA_TYPE,
559            valid_masks: vec![BitMask::all_valid(expected)],
560            data: T::into_lerc_data(data),
561            no_data_value: None,
562        })
563    }
564}
565
566// ---------------------------------------------------------------------------
567// Zero-copy decode-into API
568// ---------------------------------------------------------------------------
569
570/// Decode a LERC blob into a pre-allocated buffer, returning metadata.
571///
572/// The type `T` must match the blob's data type (e.g., `f32` for `DataType::Float`).
573/// The buffer must have at least `width * height * n_depth * n_bands` elements.
574///
575/// Returns `LercError::TypeMismatch` if `T` does not match the blob's data type.
576/// Returns `LercError::OutputBufferTooSmall` if the buffer is too small.
577pub fn decode_into<T: Sample>(data: &[u8], output: &mut [T]) -> Result<DecodeResult> {
578    decode::decode_into(data, output)
579}
580
581/// Decode a LERC blob into a pre-allocated buffer, filling invalid pixels with
582/// a caller-supplied sentinel value.
583///
584/// This is a convenience layer over [`decode_into`]: it performs the same decode
585/// (so the same constraints on type and buffer size apply), then walks each
586/// band's validity mask and writes `nodata` into every position that the mask
587/// reports as invalid. For pixels with `depth > 1`, all `depth` slices of an
588/// invalid pixel receive the sentinel.
589///
590/// The returned [`DecodeResult`] still carries `valid_masks`, so callers that
591/// want both the sentinel-filled buffer and the masks (e.g. to distinguish
592/// genuine sentinel-valued pixels from mask-driven fills) have access to both.
593///
594/// Bands whose mask is [`BitMask::AllValid`] are skipped entirely — no writes
595/// occur for those bands.
596pub fn decode_into_with_nodata<T: Sample>(
597    data: &[u8],
598    output: &mut [T],
599    nodata: T,
600) -> Result<DecodeResult> {
601    let result = decode::decode_into(data, output)?;
602
603    let n_cols = result.width as usize;
604    let n_rows = result.height as usize;
605    let n_depth = result.depth as usize;
606    let band_size = n_rows * n_cols * n_depth;
607
608    for (band_idx, mask) in result.valid_masks.iter().enumerate() {
609        if mask.is_all_valid() {
610            continue;
611        }
612        let band_offset = band_idx * band_size;
613        for i in 0..n_rows {
614            let row_start = band_offset + i * n_cols * n_depth;
615            for j in 0..n_cols {
616                let k = i * n_cols + j;
617                if !mask.is_valid(k) {
618                    let base = row_start + j * n_depth;
619                    for m in 0..n_depth {
620                        output[base + m] = nodata;
621                    }
622                }
623            }
624        }
625    }
626
627    Ok(result)
628}