#![cfg_attr(not(feature = "std"), no_std)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::cast_lossless
)]
extern crate alloc;
pub mod error;
pub mod types;
pub mod bitmask;
pub(crate) mod bitstuffer;
#[allow(dead_code)]
pub mod checksum;
#[allow(dead_code)]
pub(crate) mod header;
#[allow(dead_code)]
pub(crate) mod huffman;
pub(crate) mod rle;
pub(crate) mod decode;
pub(crate) mod encode;
#[allow(dead_code)]
pub(crate) mod fpl;
pub(crate) mod lerc1;
#[allow(dead_code)]
pub(crate) mod tiles;
pub use error::{LercError, Result};
pub use types::{DataType, Sample};
use alloc::vec;
use alloc::vec::Vec;
use bitmask::BitMask;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Precision<T> {
#[default]
Lossless,
Tolerance(T),
}
#[derive(Debug, Clone)]
pub struct DecodeResult {
pub width: u32,
pub height: u32,
pub depth: u32,
pub bands: u32,
pub data_type: DataType,
pub valid_masks: Vec<BitMask>,
pub no_data_value: Option<f64>,
}
#[derive(Debug, Clone, Default)]
pub struct LercInfo {
pub version: i32,
pub width: u32,
pub height: u32,
pub depth: u32,
pub bands: u32,
pub data_type: DataType,
pub valid_pixels: u32,
pub tolerance: f64,
pub min_value: f64,
pub max_value: f64,
pub blob_size: u32,
pub no_data_value: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct Image {
pub width: u32,
pub height: u32,
pub depth: u32,
pub bands: u32,
pub data_type: DataType,
pub valid_masks: Vec<BitMask>,
pub data: SampleData,
pub no_data_value: Option<f64>,
}
impl Default for Image {
fn default() -> Self {
Self {
width: 0,
height: 0,
depth: 1,
bands: 1,
data_type: DataType::Byte,
valid_masks: Vec::new(),
data: SampleData::U8(Vec::new()),
no_data_value: None,
}
}
}
#[derive(Debug, Clone)]
pub struct DecodedSlice<T> {
pub pixels: Vec<T>,
pub mask: BitMask,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone)]
pub enum SampleData {
I8(Vec<i8>),
U8(Vec<u8>),
I16(Vec<i16>),
U16(Vec<u16>),
I32(Vec<i32>),
U32(Vec<u32>),
F32(Vec<f32>),
F64(Vec<f64>),
}
pub fn decode_info(data: &[u8]) -> Result<LercInfo> {
decode::decode_info(data)
}
pub fn decode(data: &[u8]) -> Result<Image> {
decode::decode(data)
}
pub fn encode(image: &Image, precision: Precision<f64>) -> Result<Vec<u8>> {
let max_z_error = match precision {
Precision::Lossless => {
if image.data_type.is_integer() {
0.5
} else {
0.0
}
}
Precision::Tolerance(val) => val,
};
encode::encode(image, max_z_error)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_borrowed<T: Sample>(
width: u32,
height: u32,
depth: u32,
bands: u32,
data: &[T],
masks: &[BitMask],
no_data_value: Option<f64>,
precision: Precision<T>,
) -> Result<Vec<u8>> {
let pixels_per_band = (width as usize) * (height as usize);
let expected = pixels_per_band * (depth as usize) * (bands as usize);
if data.len() != expected {
return Err(LercError::InvalidData(alloc::format!(
"data length {} does not match width*height*depth*bands {expected}",
data.len()
)));
}
if masks.len() != bands as usize {
return Err(LercError::InvalidData(alloc::format!(
"masks length {} does not match bands {}",
masks.len(),
bands
)));
}
for (i, mask) in masks.iter().enumerate() {
if mask.num_pixels() != pixels_per_band {
return Err(LercError::InvalidData(alloc::format!(
"mask[{i}] pixel count {} does not match width*height {pixels_per_band}",
mask.num_pixels()
)));
}
}
let max_z_error: f64 = match precision {
Precision::Lossless => {
if T::is_integer() {
0.5
} else {
0.0
}
}
Precision::Tolerance(val) => val.to_f64(),
};
encode::encode_borrowed_typed(
width,
height,
depth,
bands,
data,
masks,
no_data_value,
max_z_error,
)
}
pub fn encode_slice<T: Sample>(
width: u32,
height: u32,
data: &[T],
precision: Precision<T>,
) -> Result<Vec<u8>> {
let expected = (width as usize) * (height as usize);
if data.len() != expected {
return Err(LercError::InvalidData(alloc::format!(
"data length {} does not match width*height {expected}",
data.len()
)));
}
let max_z_error: f64 = match precision {
Precision::Lossless => {
if T::is_integer() {
0.5
} else {
0.0
}
}
Precision::Tolerance(val) => val.to_f64(),
};
let image = Image {
width,
height,
depth: 1,
bands: 1,
data_type: T::DATA_TYPE,
valid_masks: vec![BitMask::all_valid(expected)],
data: T::into_lerc_data(data.to_vec()),
no_data_value: None,
};
encode::encode(&image, max_z_error)
}
pub fn encode_slice_masked<T: Sample>(
width: u32,
height: u32,
data: &[T],
mask: &BitMask,
precision: Precision<T>,
) -> Result<Vec<u8>> {
let expected = (width as usize) * (height as usize);
if data.len() != expected {
return Err(LercError::InvalidData(alloc::format!(
"data length {} does not match width*height {expected}",
data.len()
)));
}
if mask.num_pixels() != expected {
return Err(LercError::InvalidData(alloc::format!(
"mask pixel count {} does not match width*height {expected}",
mask.num_pixels()
)));
}
let max_z_error: f64 = match precision {
Precision::Lossless => {
if T::is_integer() {
0.5
} else {
0.0
}
}
Precision::Tolerance(val) => val.to_f64(),
};
let image = Image {
width,
height,
depth: 1,
bands: 1,
data_type: T::DATA_TYPE,
valid_masks: vec![mask.clone()],
data: T::into_lerc_data(data.to_vec()),
no_data_value: None,
};
encode::encode(&image, max_z_error)
}
pub fn decode_slice<T: Sample>(blob: &[u8]) -> Result<DecodedSlice<T>> {
let image = decode::decode(blob)?;
if image.bands > 1 {
return Err(LercError::InvalidData(alloc::format!(
"decode_slice requires single-band data, got {} bands (use decode() instead)",
image.bands
)));
}
if image.depth > 1 {
return Err(LercError::InvalidData(alloc::format!(
"decode_slice requires single-depth data, got depth={} (use decode() instead)",
image.depth
)));
}
let w = image.width;
let h = image.height;
let pixels = T::try_from_lerc_data(image.data).map_err(|_| {
LercError::InvalidData(alloc::format!(
"expected {:?} data but blob contains {:?}",
T::DATA_TYPE,
image.data_type
))
})?;
let mask = image
.valid_masks
.into_iter()
.next()
.unwrap_or_else(|| BitMask::all_valid((w as usize) * (h as usize)));
Ok(DecodedSlice {
pixels,
mask,
width: w,
height: h,
})
}
impl Image {
pub fn as_typed<T: Sample>(&self) -> Option<&[T]> {
T::try_ref_lerc_data(&self.data)
}
pub fn mask(&self) -> Option<&BitMask> {
self.valid_masks.first()
}
pub fn pixel<T: Sample>(&self, row: u32, col: u32) -> Option<T> {
if self.bands != 1 || self.depth != 1 {
return None;
}
if row >= self.height || col >= self.width {
return None;
}
let data = self.as_typed::<T>()?;
let idx = row as usize * self.width as usize + col as usize;
Some(data[idx])
}
pub fn valid_pixels<'a, T: Sample + 'a>(
&'a self,
) -> Option<impl Iterator<Item = (u32, u32, T)> + 'a> {
if self.bands != 1 || self.depth != 1 {
return None;
}
let data = self.as_typed::<T>()?;
let width = self.width;
let mask = self.valid_masks.first();
Some(data.iter().enumerate().filter_map(move |(idx, &val)| {
let is_valid = match mask {
Some(m) => m.is_valid(idx),
None => true,
};
if is_valid {
let row = (idx / width as usize) as u32;
let col = (idx % width as usize) as u32;
Some((row, col, val))
} else {
None
}
}))
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn num_pixels(&self) -> usize {
self.width as usize * self.height as usize
}
pub fn all_valid(&self) -> bool {
match self.valid_masks.first() {
Some(m) => m.is_all_valid(),
None => true,
}
}
pub fn from_pixels<T: Sample>(width: u32, height: u32, data: Vec<T>) -> Result<Self> {
let expected = width as usize * height as usize;
if data.len() != expected {
return Err(LercError::InvalidData(alloc::format!(
"data length {} does not match width*height {expected}",
data.len()
)));
}
Ok(Self {
width,
height,
depth: 1,
bands: 1,
data_type: T::DATA_TYPE,
valid_masks: vec![BitMask::all_valid(expected)],
data: T::into_lerc_data(data),
no_data_value: None,
})
}
}
pub fn decode_into<T: Sample>(data: &[u8], output: &mut [T]) -> Result<DecodeResult> {
decode::decode_into(data, output)
}
pub fn decode_into_with_nodata<T: Sample>(
data: &[u8],
output: &mut [T],
nodata: T,
) -> Result<DecodeResult> {
let result = decode::decode_into(data, output)?;
let n_cols = result.width as usize;
let n_rows = result.height as usize;
let n_depth = result.depth as usize;
let band_size = n_rows * n_cols * n_depth;
for (band_idx, mask) in result.valid_masks.iter().enumerate() {
if mask.is_all_valid() {
continue;
}
let band_offset = band_idx * band_size;
for i in 0..n_rows {
let row_start = band_offset + i * n_cols * n_depth;
for j in 0..n_cols {
let k = i * n_cols + j;
if !mask.is_valid(k) {
let base = row_start + j * n_depth;
for m in 0..n_depth {
output[base + m] = nodata;
}
}
}
}
}
Ok(result)
}