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/// Type-erased pixel data container, one variant per supported data type.
150#[derive(Debug, Clone)]
151pub enum SampleData {
152    /// Signed 8-bit integer pixel data.
153    I8(Vec<i8>),
154    /// Unsigned 8-bit integer pixel data.
155    U8(Vec<u8>),
156    /// Signed 16-bit integer pixel data.
157    I16(Vec<i16>),
158    /// Unsigned 16-bit integer pixel data.
159    U16(Vec<u16>),
160    /// Signed 32-bit integer pixel data.
161    I32(Vec<i32>),
162    /// Unsigned 32-bit integer pixel data.
163    U32(Vec<u32>),
164    /// 32-bit floating-point pixel data.
165    F32(Vec<f32>),
166    /// 64-bit floating-point pixel data.
167    F64(Vec<f64>),
168}
169
170/// Read header metadata from a LERC blob without decoding pixel data.
171pub fn decode_info(data: &[u8]) -> Result<LercInfo> {
172    decode::decode_info(data)
173}
174
175/// Decode a LERC blob, returning the image with pixel data and validity masks.
176pub fn decode(data: &[u8]) -> Result<Image> {
177    decode::decode(data)
178}
179
180/// Encode an image into a LERC blob with the given precision.
181pub fn encode(image: &Image, precision: Precision<f64>) -> Result<Vec<u8>> {
182    let max_z_error = match precision {
183        Precision::Lossless => {
184            if image.data_type.is_integer() {
185                0.5
186            } else {
187                0.0
188            }
189        }
190        Precision::Tolerance(val) => val,
191    };
192    encode::encode(image, max_z_error)
193}
194
195// ---------------------------------------------------------------------------
196// Typed convenience encode/decode helpers
197// ---------------------------------------------------------------------------
198
199/// Encode a single-band image with all pixels valid.
200///
201/// The pixel type `T` determines the LERC data type automatically via `Sample`.
202/// Returns an error if `data.len() != width * height`.
203pub fn encode_slice<T: Sample>(
204    width: u32,
205    height: u32,
206    data: &[T],
207    precision: Precision<T>,
208) -> Result<Vec<u8>> {
209    let expected = (width as usize) * (height as usize);
210    if data.len() != expected {
211        return Err(LercError::InvalidData(alloc::format!(
212            "data length {} does not match width*height {expected}",
213            data.len()
214        )));
215    }
216    let max_z_error: f64 = match precision {
217        Precision::Lossless => {
218            if T::is_integer() {
219                0.5
220            } else {
221                0.0
222            }
223        }
224        Precision::Tolerance(val) => val.to_f64(),
225    };
226    let image = Image {
227        width,
228        height,
229        depth: 1,
230        bands: 1,
231        data_type: T::DATA_TYPE,
232        valid_masks: vec![BitMask::all_valid(expected)],
233        data: T::into_lerc_data(data.to_vec()),
234        no_data_value: None,
235    };
236    encode::encode(&image, max_z_error)
237}
238
239/// Encode a single-band image with a validity mask.
240///
241/// The pixel type `T` determines the LERC data type automatically via `Sample`.
242/// Returns an error if `data.len() != width * height` or if the mask size does not match.
243pub fn encode_slice_masked<T: Sample>(
244    width: u32,
245    height: u32,
246    data: &[T],
247    mask: &BitMask,
248    precision: Precision<T>,
249) -> Result<Vec<u8>> {
250    let expected = (width as usize) * (height as usize);
251    if data.len() != expected {
252        return Err(LercError::InvalidData(alloc::format!(
253            "data length {} does not match width*height {expected}",
254            data.len()
255        )));
256    }
257    if mask.num_pixels() != expected {
258        return Err(LercError::InvalidData(alloc::format!(
259            "mask pixel count {} does not match width*height {expected}",
260            mask.num_pixels()
261        )));
262    }
263    let max_z_error: f64 = match precision {
264        Precision::Lossless => {
265            if T::is_integer() {
266                0.5
267            } else {
268                0.0
269            }
270        }
271        Precision::Tolerance(val) => val.to_f64(),
272    };
273    let image = Image {
274        width,
275        height,
276        depth: 1,
277        bands: 1,
278        data_type: T::DATA_TYPE,
279        valid_masks: vec![mask.clone()],
280        data: T::into_lerc_data(data.to_vec()),
281        no_data_value: None,
282    };
283    encode::encode(&image, max_z_error)
284}
285
286/// Decode a single-band, single-depth LERC blob, returning typed pixel data,
287/// the validity mask, width, and height.
288///
289/// The pixel type `T` must match the blob's data type. Returns an error on type
290/// mismatch or if the blob contains multiple bands or depths (use [`decode`] for
291/// multi-band/multi-depth blobs to get full shape and per-band masks).
292pub fn decode_slice<T: Sample>(blob: &[u8]) -> Result<(Vec<T>, BitMask, u32, u32)> {
293    let image = decode::decode(blob)?;
294    if image.bands > 1 {
295        return Err(LercError::InvalidData(alloc::format!(
296            "decode_slice requires single-band data, got {} bands (use decode() instead)",
297            image.bands
298        )));
299    }
300    if image.depth > 1 {
301        return Err(LercError::InvalidData(alloc::format!(
302            "decode_slice requires single-depth data, got depth={} (use decode() instead)",
303            image.depth
304        )));
305    }
306    let w = image.width;
307    let h = image.height;
308    let pixels = T::try_from_lerc_data(image.data).map_err(|_| {
309        LercError::InvalidData(alloc::format!(
310            "expected {:?} data but blob contains {:?}",
311            T::DATA_TYPE,
312            image.data_type
313        ))
314    })?;
315    let mask = image
316        .valid_masks
317        .into_iter()
318        .next()
319        .unwrap_or_else(|| BitMask::all_valid((w as usize) * (h as usize)));
320    Ok((pixels, mask, w, h))
321}
322
323// ---------------------------------------------------------------------------
324// Typed accessor methods on Image
325// ---------------------------------------------------------------------------
326
327impl Image {
328    /// Try to borrow the pixel data as `&[T]`.
329    ///
330    /// Returns `None` if the image's data type does not match `T`.
331    pub fn as_typed<T: Sample>(&self) -> Option<&[T]> {
332        T::try_ref_lerc_data(&self.data)
333    }
334
335    /// Return the validity mask for the first band, or `None` if no masks are present.
336    pub fn mask(&self) -> Option<&BitMask> {
337        self.valid_masks.first()
338    }
339
340    /// Get the pixel value at `(row, col)` for single-band, single-depth images.
341    ///
342    /// Returns `None` if the data type does not match `T`, if `bands > 1` or
343    /// `depth > 1`, or if the coordinates are out of bounds.
344    pub fn pixel<T: Sample>(&self, row: u32, col: u32) -> Option<T> {
345        if self.bands != 1 || self.depth != 1 {
346            return None;
347        }
348        if row >= self.height || col >= self.width {
349            return None;
350        }
351        let data = self.as_typed::<T>()?;
352        let idx = row as usize * self.width as usize + col as usize;
353        Some(data[idx])
354    }
355
356    /// Iterate over valid pixels as `(row, col, value)` tuples.
357    ///
358    /// Only works for single-band, single-depth images. Returns `None` if the data
359    /// type does not match `T` or if `bands > 1` or `depth > 1`.
360    /// The iterator respects the validity mask, skipping invalid pixels.
361    pub fn valid_pixels<'a, T: Sample + 'a>(
362        &'a self,
363    ) -> Option<impl Iterator<Item = (u32, u32, T)> + 'a> {
364        if self.bands != 1 || self.depth != 1 {
365            return None;
366        }
367        let data = self.as_typed::<T>()?;
368        let width = self.width;
369        let mask = self.valid_masks.first();
370        Some(data.iter().enumerate().filter_map(move |(idx, &val)| {
371            let is_valid = match mask {
372                Some(m) => m.is_valid(idx),
373                None => true,
374            };
375            if is_valid {
376                let row = (idx / width as usize) as u32;
377                let col = (idx % width as usize) as u32;
378                Some((row, col, val))
379            } else {
380                None
381            }
382        }))
383    }
384
385    /// Get dimensions as `(width, height)`.
386    pub fn dimensions(&self) -> (u32, u32) {
387        (self.width, self.height)
388    }
389
390    /// Total number of pixels (`width * height`).
391    pub fn num_pixels(&self) -> usize {
392        self.width as usize * self.height as usize
393    }
394
395    /// Check if all pixels in the first band are valid.
396    ///
397    /// Returns `true` if there is no mask (all pixels are implicitly valid),
398    /// if the first band's mask is [`BitMask::AllValid`] (O(1)), or if an
399    /// explicit mask happens to have every bit set (O(n) popcount fallback).
400    pub fn all_valid(&self) -> bool {
401        match self.valid_masks.first() {
402            Some(m) => m.is_all_valid(),
403            None => true,
404        }
405    }
406
407    /// Create a single-band, all-valid `Image` from a typed pixel vector
408    /// and dimensions.
409    ///
410    /// Returns an error if `data.len() != width * height`.
411    pub fn from_pixels<T: Sample>(width: u32, height: u32, data: Vec<T>) -> Result<Self> {
412        let expected = width as usize * height as usize;
413        if data.len() != expected {
414            return Err(LercError::InvalidData(alloc::format!(
415                "data length {} does not match width*height {expected}",
416                data.len()
417            )));
418        }
419        Ok(Self {
420            width,
421            height,
422            depth: 1,
423            bands: 1,
424            data_type: T::DATA_TYPE,
425            valid_masks: vec![BitMask::all_valid(expected)],
426            data: T::into_lerc_data(data),
427            no_data_value: None,
428        })
429    }
430}
431
432// ---------------------------------------------------------------------------
433// Zero-copy decode-into API
434// ---------------------------------------------------------------------------
435
436/// Decode a LERC blob into a pre-allocated buffer, returning metadata.
437///
438/// The type `T` must match the blob's data type (e.g., `f32` for `DataType::Float`).
439/// The buffer must have at least `width * height * n_depth * n_bands` elements.
440///
441/// Returns `LercError::TypeMismatch` if `T` does not match the blob's data type.
442/// Returns `LercError::OutputBufferTooSmall` if the buffer is too small.
443pub fn decode_into<T: Sample>(data: &[u8], output: &mut [T]) -> Result<DecodeResult> {
444    decode::decode_into(data, output)
445}