Skip to main content

lerc_core/
raster.rs

1use crate::error::{Error, Result};
2use crate::types::{BandLayout, DataType, PixelData};
3
4/// Borrowed, dimension-checked view of one pixel-interleaved raster band.
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub struct RasterView<'a, T: Sample> {
7    width: u32,
8    height: u32,
9    depth: u32,
10    data: &'a [T],
11}
12
13impl<'a, T: Sample> RasterView<'a, T> {
14    /// Creates a raster view.
15    ///
16    /// # Errors
17    /// Returns an error when depth is zero, dimensions overflow, or `data` has
18    /// a different length than `width * height * depth`.
19    pub fn new(width: u32, height: u32, depth: u32, data: &'a [T]) -> Result<Self> {
20        let expected_len = sample_count_from_dims(width, height, depth)?;
21        if data.len() != expected_len {
22            return Err(Error::InvalidArgument(
23                "raster slice length does not match its dimensions",
24            ));
25        }
26        Ok(Self {
27            width,
28            height,
29            depth,
30            data,
31        })
32    }
33
34    /// Returns the raster width in pixels.
35    pub fn width(self) -> u32 {
36        self.width
37    }
38
39    /// Returns the raster height in pixels.
40    pub fn height(self) -> u32 {
41        self.height
42    }
43
44    /// Returns the number of samples per pixel.
45    pub fn depth(self) -> u32 {
46        self.depth
47    }
48
49    /// Returns the underlying pixel-interleaved sample slice.
50    pub fn data(self) -> &'a [T] {
51        self.data
52    }
53
54    /// Returns the encoded sample type.
55    pub fn data_type(self) -> DataType {
56        T::DATA_TYPE
57    }
58
59    /// Returns `width * height` using checked arithmetic.
60    pub fn pixel_count(self) -> Result<usize> {
61        pixel_count_from_dims(self.width, self.height)
62    }
63
64    /// Returns the total sample count using checked arithmetic.
65    pub fn sample_count(self) -> Result<usize> {
66        sample_count_from_dims(self.width, self.height, self.depth)
67    }
68
69    /// Returns one sample by flat pixel index and depth index.
70    ///
71    /// # Panics
72    /// Panics if `pixel` or `dim` is outside this view.
73    pub fn sample(self, pixel: usize, dim: usize) -> T {
74        self.data[sample_index(pixel, self.depth as usize, dim)]
75    }
76}
77
78/// Borrowed, dimension-checked view of a multi-band raster.
79#[derive(Debug, Clone, Copy, PartialEq)]
80pub struct BandSetView<'a, T: Sample> {
81    width: u32,
82    height: u32,
83    depth: u32,
84    band_count: usize,
85    layout: BandLayout,
86    data: &'a [T],
87    pixel_count: usize,
88    pixel_stride: usize,
89    band_stride: usize,
90}
91
92impl<'a, T: Sample> BandSetView<'a, T> {
93    /// Creates a band-set view over interleaved or band-sequential samples.
94    ///
95    /// # Errors
96    /// Returns an error for zero bands or depth, overflowing dimensions, or a
97    /// sample slice whose length does not match the declared shape.
98    pub fn new(
99        width: u32,
100        height: u32,
101        depth: u32,
102        band_count: usize,
103        layout: BandLayout,
104        data: &'a [T],
105    ) -> Result<Self> {
106        if band_count == 0 {
107            return Err(Error::InvalidArgument(
108                "band_count must be greater than zero",
109            ));
110        }
111
112        let band_sample_count = sample_count_from_dims(width, height, depth)?;
113        let pixel_count = pixel_count_from_dims(width, height)?;
114        let pixel_stride = (depth as usize)
115            .checked_mul(band_count)
116            .ok_or(Error::SizeOverflow("interleaved pixel stride"))?;
117        let expected_len = band_sample_count
118            .checked_mul(band_count)
119            .ok_or(Error::SizeOverflow("band set value count"))?;
120        if data.len() != expected_len {
121            return Err(Error::InvalidArgument(
122                "band set slice length does not match its dimensions",
123            ));
124        }
125
126        Ok(Self {
127            width,
128            height,
129            depth,
130            band_count,
131            layout,
132            data,
133            pixel_count,
134            pixel_stride,
135            band_stride: band_sample_count,
136        })
137    }
138
139    /// Returns the raster width in pixels.
140    pub fn width(self) -> u32 {
141        self.width
142    }
143
144    /// Returns the raster height in pixels.
145    pub fn height(self) -> u32 {
146        self.height
147    }
148
149    /// Returns the number of samples per pixel in each band.
150    pub fn depth(self) -> u32 {
151        self.depth
152    }
153
154    /// Returns the number of bands.
155    pub fn band_count(self) -> usize {
156        self.band_count
157    }
158
159    /// Returns the memory layout of the supplied samples.
160    pub fn layout(self) -> BandLayout {
161        self.layout
162    }
163
164    /// Returns the underlying sample slice.
165    pub fn data(self) -> &'a [T] {
166        self.data
167    }
168
169    /// Returns the encoded sample type.
170    pub fn data_type(self) -> DataType {
171        T::DATA_TYPE
172    }
173
174    /// Returns the checked pixel count cached at construction.
175    pub fn pixel_count(self) -> Result<usize> {
176        Ok(self.pixel_count)
177    }
178
179    /// Returns the sample count of one band.
180    pub fn band_sample_count(self) -> Result<usize> {
181        Ok(self.band_stride)
182    }
183
184    /// Returns the total number of samples across every band.
185    pub fn value_count(self) -> Result<usize> {
186        self.band_sample_count()?
187            .checked_mul(self.band_count)
188            .ok_or(Error::SizeOverflow("band set value count"))
189    }
190
191    /// Returns one sample by band, flat pixel, and depth index.
192    ///
193    /// # Panics
194    /// Panics if any supplied index is outside this view.
195    pub fn sample(self, band: usize, pixel: usize, dim: usize) -> T {
196        let depth = self.depth as usize;
197        let index = match self.layout {
198            BandLayout::Interleaved => pixel * self.pixel_stride + band * depth + dim,
199            BandLayout::Bsq => band * self.band_stride + pixel * depth + dim,
200        };
201        self.data[index]
202    }
203}
204
205/// Borrowed, dimension-checked validity mask; zero is invalid and nonzero is valid.
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub struct MaskView<'a> {
208    width: u32,
209    height: u32,
210    data: &'a [u8],
211}
212
213impl<'a> MaskView<'a> {
214    /// Creates a validity-mask view.
215    ///
216    /// # Errors
217    /// Returns an error when dimensions overflow or the mask length differs
218    /// from `width * height`.
219    pub fn new(width: u32, height: u32, data: &'a [u8]) -> Result<Self> {
220        let expected_len = pixel_count_from_dims(width, height)?;
221        if data.len() != expected_len {
222            return Err(Error::InvalidArgument(
223                "mask slice length does not match its dimensions",
224            ));
225        }
226        Ok(Self {
227            width,
228            height,
229            data,
230        })
231    }
232
233    /// Returns the mask width in pixels.
234    pub fn width(self) -> u32 {
235        self.width
236    }
237
238    /// Returns the mask height in pixels.
239    pub fn height(self) -> u32 {
240        self.height
241    }
242
243    /// Returns the underlying byte mask.
244    pub fn data(self) -> &'a [u8] {
245        self.data
246    }
247
248    /// Returns the checked number of mask pixels.
249    pub fn pixel_count(self) -> Result<usize> {
250        pixel_count_from_dims(self.width, self.height)
251    }
252
253    /// Counts nonzero, valid mask entries.
254    pub fn valid_count(self) -> usize {
255        self.data.iter().filter(|&&value| value != 0).count()
256    }
257}
258
259/// Primitive sample types supported by LERC.
260///
261/// This trait is sealed and implemented for `i8`, `u8`, `i16`, `u16`, `i32`,
262/// `u32`, `f32`, and `f64`.
263pub trait Sample: Copy + Default + private::Sealed + 'static {
264    /// Corresponding runtime [`DataType`].
265    const DATA_TYPE: DataType;
266    /// Whether this sample is an integer type.
267    const IS_INTEGER: bool;
268
269    /// Converts an `f64` with Rust's primitive numeric-cast semantics.
270    fn from_f64(value: f64) -> Self;
271    /// Promotes the sample to `f64`.
272    fn to_f64(self) -> f64;
273    /// Reads one little-endian sample.
274    fn read_le(bytes: &[u8]) -> Result<Self>;
275    /// Appends one little-endian sample.
276    fn write_le(self, out: &mut Vec<u8>);
277    /// Wraps a homogeneous buffer in [`PixelData`].
278    fn into_pixel_data(values: Vec<Self>) -> PixelData;
279
280    /// Reads an aligned little-endian sample buffer.
281    fn read_vec(bytes: &[u8]) -> Result<Vec<Self>> {
282        let size = Self::DATA_TYPE.byte_len();
283        let chunks = bytes.chunks_exact(size);
284        if !chunks.remainder().is_empty() {
285            return Err(Error::invalid_blob(
286                "typed value payload length is not aligned to its data type",
287            ));
288        }
289        let mut values = Vec::new();
290        values
291            .try_reserve_exact(bytes.len() / size)
292            .map_err(|_| Error::AllocationFailed("typed sample buffer"))?;
293        for chunk in chunks {
294            values.push(Self::read_le(chunk)?);
295        }
296        Ok(values)
297    }
298}
299
300macro_rules! impl_sample {
301    ($ty:ty, $variant:ident, $is_integer:expr) => {
302        impl Sample for $ty {
303            const DATA_TYPE: DataType = DataType::$variant;
304            const IS_INTEGER: bool = $is_integer;
305
306            fn from_f64(value: f64) -> Self {
307                value as $ty
308            }
309
310            fn to_f64(self) -> f64 {
311                self as f64
312            }
313
314            fn read_le(bytes: &[u8]) -> Result<Self> {
315                let bytes = bytes.try_into().map_err(|_| {
316                    Error::invalid_blob("typed scalar byte length does not match its data type")
317                })?;
318                Ok(<$ty>::from_le_bytes(bytes))
319            }
320
321            fn write_le(self, out: &mut Vec<u8>) {
322                out.extend_from_slice(&self.to_le_bytes());
323            }
324
325            fn into_pixel_data(values: Vec<Self>) -> PixelData {
326                PixelData::$variant(values)
327            }
328        }
329    };
330}
331
332impl_sample!(i8, I8, true);
333impl_sample!(u8, U8, true);
334impl_sample!(i16, I16, true);
335impl_sample!(u16, U16, true);
336impl_sample!(i32, I32, true);
337impl_sample!(u32, U32, true);
338impl_sample!(f32, F32, false);
339impl_sample!(f64, F64, false);
340
341#[macro_export]
342/// Dispatches a runtime [`DataType`](crate::DataType) to a concrete primitive type alias.
343macro_rules! dispatch_data_type {
344    ($data_type:expr, $sample:ident => $body:block) => {{
345        match $data_type {
346            $crate::DataType::I8 => {
347                type $sample = i8;
348                $body
349            }
350            $crate::DataType::U8 => {
351                type $sample = u8;
352                $body
353            }
354            $crate::DataType::I16 => {
355                type $sample = i16;
356                $body
357            }
358            $crate::DataType::U16 => {
359                type $sample = u16;
360                $body
361            }
362            $crate::DataType::I32 => {
363                type $sample = i32;
364                $body
365            }
366            $crate::DataType::U32 => {
367                type $sample = u32;
368                $body
369            }
370            $crate::DataType::F32 => {
371                type $sample = f32;
372                $body
373            }
374            $crate::DataType::F64 => {
375                type $sample = f64;
376                $body
377            }
378        }
379    }};
380}
381
382/// Computes `width * height` using checked, platform-portable arithmetic.
383pub fn pixel_count_from_dims(width: u32, height: u32) -> Result<usize> {
384    let width = usize::try_from(width).map_err(|_| Error::SizeOverflow("width as usize"))?;
385    let height = usize::try_from(height).map_err(|_| Error::SizeOverflow("height as usize"))?;
386    width
387        .checked_mul(height)
388        .ok_or(Error::SizeOverflow("pixel count"))
389}
390
391/// Computes `width * height * depth` using checked arithmetic.
392pub fn sample_count_from_dims(width: u32, height: u32, depth: u32) -> Result<usize> {
393    if depth == 0 {
394        return Err(Error::InvalidArgument("depth must be greater than zero"));
395    }
396    pixel_count_from_dims(width, height)?
397        .checked_mul(depth as usize)
398        .ok_or(Error::SizeOverflow("sample count"))
399}
400
401/// Decodes little-endian samples and converts them directly to `T`.
402pub fn read_values_as<T: Sample>(bytes: &[u8], source_type: DataType) -> Result<Vec<T>> {
403    if source_type == T::DATA_TYPE {
404        return T::read_vec(bytes);
405    }
406
407    let sample_size = source_type.byte_len();
408    if bytes.len() % sample_size != 0 {
409        return Err(Error::invalid_blob(
410            "typed value payload length is not aligned to its data type",
411        ));
412    }
413    let mut out = Vec::new();
414    out.try_reserve_exact(bytes.len() / sample_size)
415        .map_err(|_| Error::AllocationFailed("converted sample buffer"))?;
416    crate::dispatch_data_type!(source_type, Source => {
417        for chunk in bytes.chunks_exact(sample_size) {
418            out.push(T::from_f64(Source::read_le(chunk)?.to_f64()));
419        }
420    });
421    Ok(out)
422}
423
424/// Rounds or truncates an `f64` through the specified primitive representation.
425pub fn coerce_f64_to_data_type(value: f64, data_type: DataType) -> f64 {
426    crate::dispatch_data_type!(data_type, Target => { Target::from_f64(value).to_f64() })
427}
428
429/// Converts a decoded value to an output sample while preserving source-type rounding.
430pub fn output_value<T: Sample>(value: f64, source_type: DataType) -> T {
431    if T::DATA_TYPE == DataType::F64 && source_type != DataType::F64 {
432        T::from_f64(coerce_f64_to_data_type(value, source_type))
433    } else {
434        T::from_f64(value)
435    }
436}
437
438/// Reads one little-endian scalar and promotes it to `f64`.
439pub fn read_scalar(bytes: &[u8], data_type: DataType) -> Result<f64> {
440    crate::dispatch_data_type!(data_type, Source => { Ok(Source::read_le(bytes)?.to_f64()) })
441}
442
443/// Reads an aligned little-endian sample buffer as promoted `f64` values.
444pub fn read_typed_values(bytes: &[u8], data_type: DataType) -> Result<Vec<f64>> {
445    let sample_size = data_type.byte_len();
446    if bytes.len() % sample_size != 0 {
447        return Err(Error::invalid_blob(
448            "typed value payload length is not aligned to its data type",
449        ));
450    }
451    let mut out = Vec::new();
452    out.try_reserve_exact(bytes.len() / sample_size)
453        .map_err(|_| Error::AllocationFailed("promoted sample buffer"))?;
454    for chunk in bytes.chunks_exact(sample_size) {
455        out.push(read_scalar(chunk, data_type)?);
456    }
457    Ok(out)
458}
459
460/// Converts and appends one scalar in the requested little-endian representation.
461pub fn append_value_as(out: &mut Vec<u8>, value: f64, data_type: DataType) {
462    crate::dispatch_data_type!(data_type, Target => {
463        Target::from_f64(value).write_le(out);
464    });
465}
466
467/// Counts valid mask entries in a rectangular row-major block.
468///
469/// # Panics
470/// Panics if the rectangle lies outside `mask` for the supplied row width.
471pub fn count_valid_in_block(
472    mask: &[u8],
473    width: usize,
474    x0: usize,
475    y0: usize,
476    block_width: usize,
477    block_height: usize,
478) -> usize {
479    let mut count = 0usize;
480    for row in 0..block_height {
481        let row_offset = (y0 + row) * width + x0;
482        for col in 0..block_width {
483            count += usize::from(mask[row_offset + col] != 0);
484        }
485    }
486    count
487}
488
489/// Returns the minimum bit width needed to represent `max_index`.
490pub fn bits_required(max_index: usize) -> u8 {
491    let mut bits = 0u8;
492    let mut value = max_index;
493    while value > 0 {
494        bits += 1;
495        value >>= 1;
496    }
497    bits
498}
499
500/// Converts a byte slice to zero-padded little-endian words.
501///
502/// # Errors
503/// Returns an error when memory for the word buffer cannot be reserved.
504pub fn words_from_padded(bytes: &[u8]) -> Result<Vec<u32>> {
505    if bytes.is_empty() {
506        return Ok(Vec::new());
507    }
508    let mut words = Vec::new();
509    words
510        .try_reserve_exact(bytes.len().div_ceil(4))
511        .map_err(|_| Error::AllocationFailed("bit-stuffed word buffer"))?;
512
513    let mut chunks = bytes.chunks_exact(4);
514    for chunk in &mut chunks {
515        words.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
516    }
517    let remainder = chunks.remainder();
518    if !remainder.is_empty() {
519        let mut word = [0u8; 4];
520        word[..remainder.len()].copy_from_slice(remainder);
521        words.push(u32::from_le_bytes(word));
522    }
523    Ok(words)
524}
525
526/// Computes the Fletcher-32 checksum variant used by Lerc2.
527pub fn fletcher32(bytes: &[u8]) -> u32 {
528    let mut sum1 = 0xffffu32;
529    let mut sum2 = 0xffffu32;
530    let mut words = bytes.len() / 2;
531    let mut index = 0usize;
532
533    while words > 0 {
534        let chunk = words.min(359);
535        words -= chunk;
536        for _ in 0..chunk {
537            sum1 += (bytes[index] as u32) << 8;
538            index += 1;
539            sum2 += sum1 + bytes[index] as u32;
540            sum1 += bytes[index] as u32;
541            index += 1;
542        }
543        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
544        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
545    }
546
547    if bytes.len() & 1 != 0 {
548        sum1 += (bytes[index] as u32) << 8;
549        sum2 += sum1;
550    }
551
552    sum1 = (sum1 & 0xffff) + (sum1 >> 16);
553    sum2 = (sum2 & 0xffff) + (sum2 >> 16);
554    (sum2 << 16) | (sum1 & 0xffff)
555}
556
557#[cfg(test)]
558mod tests {
559    use super::words_from_padded;
560
561    #[test]
562    fn converts_aligned_and_partial_little_endian_words_without_a_padding_buffer() {
563        assert!(words_from_padded(&[]).unwrap().is_empty());
564        assert_eq!(words_from_padded(&[1]).unwrap(), vec![1]);
565        assert_eq!(words_from_padded(&[1, 2, 3, 4]).unwrap(), vec![0x0403_0201]);
566        assert_eq!(
567            words_from_padded(&[1, 2, 3, 4, 5]).unwrap(),
568            vec![0x0403_0201, 5]
569        );
570    }
571}
572
573/// Computes the pixel-interleaved sample index `pixel * depth + dim`.
574pub fn sample_index(pixel: usize, depth: usize, dim: usize) -> usize {
575    pixel * depth + dim
576}
577
578mod private {
579    pub trait Sealed {}
580
581    impl Sealed for i8 {}
582    impl Sealed for u8 {}
583    impl Sealed for i16 {}
584    impl Sealed for u16 {}
585    impl Sealed for i32 {}
586    impl Sealed for u32 {}
587    impl Sealed for f32 {}
588    impl Sealed for f64 {}
589}