Skip to main content

lerc/
types.rs

1/// Pixel data type stored in the LERC blob header.
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
3#[repr(i32)]
4pub enum DataType {
5    /// Signed 8-bit integer (`i8`).
6    #[default]
7    Char = 0,
8    /// Unsigned 8-bit integer (`u8`).
9    Byte = 1,
10    /// Signed 16-bit integer (`i16`).
11    Short = 2,
12    /// Unsigned 16-bit integer (`u16`).
13    UShort = 3,
14    /// Signed 32-bit integer (`i32`).
15    Int = 4,
16    /// Unsigned 32-bit integer (`u32`).
17    UInt = 5,
18    /// 32-bit IEEE 754 floating point (`f32`).
19    Float = 6,
20    /// 64-bit IEEE 754 floating point (`f64`).
21    Double = 7,
22}
23
24impl DataType {
25    /// Convert an `i32` discriminant to a `DataType`, returning `None` if invalid.
26    pub fn from_i32(v: i32) -> Option<Self> {
27        match v {
28            0 => Some(Self::Char),
29            1 => Some(Self::Byte),
30            2 => Some(Self::Short),
31            3 => Some(Self::UShort),
32            4 => Some(Self::Int),
33            5 => Some(Self::UInt),
34            6 => Some(Self::Float),
35            7 => Some(Self::Double),
36            _ => None,
37        }
38    }
39
40    /// Size of one sample in bytes.
41    pub fn size(self) -> usize {
42        match self {
43            Self::Char | Self::Byte => 1,
44            Self::Short | Self::UShort => 2,
45            Self::Int | Self::UInt | Self::Float => 4,
46            Self::Double => 8,
47        }
48    }
49
50    /// Returns `true` if this is an integer type (not `Float` or `Double`).
51    pub fn is_integer(self) -> bool {
52        !matches!(self, Self::Float | Self::Double)
53    }
54
55    /// Returns `true` if this is a signed integer type (`Char`, `Short`, or `Int`).
56    pub fn is_signed(self) -> bool {
57        matches!(self, Self::Char | Self::Short | Self::Int)
58    }
59}
60
61pub(crate) mod sealed {
62    pub trait Sealed {}
63}
64
65/// Trait for pixel types that can be encoded/decoded with LERC.
66///
67/// Implemented for `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `f32`, and `f64`.
68pub trait Sample: sealed::Sealed + Copy + PartialOrd + Default + core::fmt::Debug {
69    /// The corresponding LERC `DataType` discriminant.
70    const DATA_TYPE: DataType;
71    /// Size of this type in bytes.
72    const BYTES: usize;
73
74    /// Convert this value to `f64`.
75    fn to_f64(self) -> f64;
76    /// Create a value from an `f64` (truncating/rounding as needed).
77    fn from_f64(v: f64) -> Self;
78    /// Reinterpret this value's bits as a `u64`.
79    fn to_bits_u64(self) -> u64;
80    /// Reinterpret a `u64`'s bits as this type.
81    fn from_bits_u64(v: u64) -> Self;
82    /// Returns `true` if this is an integer pixel type.
83    fn is_integer() -> bool;
84
85    /// Wrap a `Vec<Self>` into the corresponding `SampleData` variant.
86    fn into_lerc_data(v: alloc::vec::Vec<Self>) -> super::SampleData;
87
88    /// Try to borrow the pixel slice from a `SampleData` if the variant matches.
89    fn try_ref_lerc_data(data: &super::SampleData) -> Option<&[Self]>;
90
91    /// Try to unwrap the pixel vector from a `SampleData` if the variant matches.
92    fn try_from_lerc_data(
93        data: super::SampleData,
94    ) -> core::result::Result<alloc::vec::Vec<Self>, super::SampleData>;
95
96    /// Read a value from a little-endian byte slice. The slice must be at least `BYTES` long.
97    fn from_le_slice(s: &[u8]) -> Self;
98
99    /// Write this value as little-endian bytes to a Vec.
100    fn extend_le_bytes(self, buf: &mut alloc::vec::Vec<u8>);
101
102    /// The minimum representable value for this type as f64.
103    fn min_representable() -> f64;
104}
105
106macro_rules! impl_lerc_data_type {
107    ($ty:ty, $dt:expr, $is_int:expr, $variant:ident, $n:literal) => {
108        impl sealed::Sealed for $ty {}
109        impl Sample for $ty {
110            const DATA_TYPE: DataType = $dt;
111            const BYTES: usize = $n;
112
113            #[inline]
114            fn to_f64(self) -> f64 {
115                self as f64
116            }
117
118            #[inline]
119            fn from_f64(v: f64) -> Self {
120                v as Self
121            }
122
123            #[inline]
124            fn to_bits_u64(self) -> u64 {
125                self as u64
126            }
127
128            #[inline]
129            fn from_bits_u64(v: u64) -> Self {
130                v as Self
131            }
132
133            #[inline]
134            fn is_integer() -> bool {
135                $is_int
136            }
137
138            fn into_lerc_data(v: alloc::vec::Vec<Self>) -> super::SampleData {
139                super::SampleData::$variant(v)
140            }
141
142            fn try_ref_lerc_data(data: &super::SampleData) -> Option<&[Self]> {
143                match data {
144                    super::SampleData::$variant(v) => Some(v),
145                    _ => None,
146                }
147            }
148
149            fn try_from_lerc_data(
150                data: super::SampleData,
151            ) -> core::result::Result<alloc::vec::Vec<Self>, super::SampleData> {
152                match data {
153                    super::SampleData::$variant(v) => Ok(v),
154                    other => Err(other),
155                }
156            }
157
158            #[inline]
159            fn from_le_slice(s: &[u8]) -> Self {
160                let mut buf = [0u8; $n];
161                buf.copy_from_slice(&s[..$n]);
162                <$ty>::from_le_bytes(buf)
163            }
164
165            #[inline]
166            fn extend_le_bytes(self, buf: &mut alloc::vec::Vec<u8>) {
167                buf.extend_from_slice(&self.to_le_bytes());
168            }
169
170            #[inline]
171            fn min_representable() -> f64 {
172                <$ty>::MIN as f64
173            }
174        }
175    };
176}
177
178impl_lerc_data_type!(i8, DataType::Char, true, I8, 1);
179impl_lerc_data_type!(u8, DataType::Byte, true, U8, 1);
180impl_lerc_data_type!(i16, DataType::Short, true, I16, 2);
181impl_lerc_data_type!(u16, DataType::UShort, true, U16, 2);
182impl_lerc_data_type!(i32, DataType::Int, true, I32, 4);
183impl_lerc_data_type!(u32, DataType::UInt, true, U32, 4);
184
185impl sealed::Sealed for f32 {}
186impl Sample for f32 {
187    const DATA_TYPE: DataType = DataType::Float;
188    const BYTES: usize = 4;
189
190    #[inline]
191    fn to_f64(self) -> f64 {
192        self as f64
193    }
194
195    #[inline]
196    fn from_f64(v: f64) -> Self {
197        v as Self
198    }
199
200    #[inline]
201    fn to_bits_u64(self) -> u64 {
202        self.to_bits() as u64
203    }
204
205    #[inline]
206    fn from_bits_u64(v: u64) -> Self {
207        Self::from_bits(v as u32)
208    }
209
210    #[inline]
211    fn is_integer() -> bool {
212        false
213    }
214
215    fn into_lerc_data(v: alloc::vec::Vec<Self>) -> super::SampleData {
216        super::SampleData::F32(v)
217    }
218
219    fn try_ref_lerc_data(data: &super::SampleData) -> Option<&[Self]> {
220        match data {
221            super::SampleData::F32(v) => Some(v),
222            _ => None,
223        }
224    }
225
226    fn try_from_lerc_data(
227        data: super::SampleData,
228    ) -> core::result::Result<alloc::vec::Vec<Self>, super::SampleData> {
229        match data {
230            super::SampleData::F32(v) => Ok(v),
231            other => Err(other),
232        }
233    }
234
235    #[inline]
236    fn from_le_slice(s: &[u8]) -> Self {
237        Self::from_bits(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
238    }
239
240    #[inline]
241    fn extend_le_bytes(self, buf: &mut alloc::vec::Vec<u8>) {
242        buf.extend_from_slice(&self.to_le_bytes());
243    }
244
245    #[inline]
246    fn min_representable() -> f64 {
247        f64::MIN
248    }
249}
250
251impl sealed::Sealed for f64 {}
252impl Sample for f64 {
253    const DATA_TYPE: DataType = DataType::Double;
254    const BYTES: usize = 8;
255
256    #[inline]
257    fn to_f64(self) -> f64 {
258        self
259    }
260
261    #[inline]
262    fn from_f64(v: f64) -> Self {
263        v
264    }
265
266    #[inline]
267    fn to_bits_u64(self) -> u64 {
268        self.to_bits()
269    }
270
271    #[inline]
272    fn from_bits_u64(v: u64) -> Self {
273        Self::from_bits(v)
274    }
275
276    #[inline]
277    fn is_integer() -> bool {
278        false
279    }
280
281    fn into_lerc_data(v: alloc::vec::Vec<Self>) -> super::SampleData {
282        super::SampleData::F64(v)
283    }
284
285    fn try_ref_lerc_data(data: &super::SampleData) -> Option<&[Self]> {
286        match data {
287            super::SampleData::F64(v) => Some(v),
288            _ => None,
289        }
290    }
291
292    fn try_from_lerc_data(
293        data: super::SampleData,
294    ) -> core::result::Result<alloc::vec::Vec<Self>, super::SampleData> {
295        match data {
296            super::SampleData::F64(v) => Ok(v),
297            other => Err(other),
298        }
299    }
300
301    #[inline]
302    fn from_le_slice(s: &[u8]) -> Self {
303        Self::from_bits(u64::from_le_bytes([
304            s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7],
305        ]))
306    }
307
308    #[inline]
309    fn extend_le_bytes(self, buf: &mut alloc::vec::Vec<u8>) {
310        buf.extend_from_slice(&self.to_le_bytes());
311    }
312
313    #[inline]
314    fn min_representable() -> f64 {
315        f64::MIN
316    }
317}
318
319/// Image-level encoding mode (C++ `Lerc2::ImageEncodeMode`).
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
321#[repr(u8)]
322pub(crate) enum ImageEncodeMode {
323    Tiling = 0,
324    DeltaHuffman = 1,
325    Huffman = 2,
326    DeltaDeltaHuffman = 3,
327}
328
329impl TryFrom<u8> for ImageEncodeMode {
330    type Error = crate::error::LercError;
331    fn try_from(v: u8) -> core::result::Result<Self, Self::Error> {
332        match v {
333            0 => Ok(Self::Tiling),
334            1 => Ok(Self::DeltaHuffman),
335            2 => Ok(Self::Huffman),
336            3 => Ok(Self::DeltaDeltaHuffman),
337            _ => Err(crate::error::LercError::UnsupportedEncoding(v)),
338        }
339    }
340}
341
342/// Row/column bounds for a micro-block tile within the image grid.
343#[derive(Debug, Clone, Copy)]
344pub(crate) struct TileRect {
345    /// First row (inclusive).
346    pub i0: usize,
347    /// Past-the-end row (exclusive).
348    pub i1: usize,
349    /// First column (inclusive).
350    pub j0: usize,
351    /// Past-the-end column (exclusive).
352    pub j1: usize,
353}
354
355/// Tile compression mode: the 2-bit value stored in bits 0-1 of the tile header byte.
356///
357/// C++ uses `BlockEncodeMode` for values 0-2 and separate logic for 2-3.
358/// We unify all four wire values into one enum since they're mutually exclusive.
359/// "BitStuffLut" is not a separate wire value. LUT vs simple is determined
360/// from the bit-stuffed payload, and both use wire value 1 (BitStuffed).
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362#[repr(u8)]
363pub(crate) enum TileCompressionMode {
364    RawBinary = 0,
365    BitStuffed = 1,
366    ConstZero = 2,
367    ConstOffset = 3,
368}
369
370/// Compression flag bits within a tile header byte.
371pub(crate) mod tile_flags {
372    /// Bits 0-1: tile compression mode.
373    pub const MODE_MASK: u8 = 0x03;
374    /// Bit 2: diff encoding relative to previous depth slice.
375    pub const DIFF_ENCODING: u8 = 0x04;
376    /// Bits 6-7: type reduction code for the offset value.
377    pub const TYPE_REDUCTION_SHIFT: u8 = 6;
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383
384    /// Verify enum values match the C++ reference (Lerc2.h).
385    ///
386    /// C++: enum ImageEncodeMode { IEM_Tiling = 0, IEM_DeltaHuffman, IEM_Huffman, IEM_DeltaDeltaHuffman };
387    /// C++: enum BlockEncodeMode { BEM_RawBinary = 0, BEM_BitStuffSimple, BEM_BitStuffLUT };
388    /// C++: enum DataType { DT_Char = 0, DT_Byte, DT_Short, DT_UShort, DT_Int, DT_UInt, DT_Float, DT_Double };
389    #[test]
390    fn image_encode_mode_matches_cpp() {
391        assert_eq!(ImageEncodeMode::Tiling as u8, 0);
392        assert_eq!(ImageEncodeMode::DeltaHuffman as u8, 1);
393        assert_eq!(ImageEncodeMode::Huffman as u8, 2);
394        assert_eq!(ImageEncodeMode::DeltaDeltaHuffman as u8, 3);
395    }
396
397    #[test]
398    fn tile_compression_mode_matches_cpp() {
399        // C++ BlockEncodeMode: BEM_RawBinary=0, BEM_BitStuffSimple=1, BEM_BitStuffLUT=2
400        // C++ also uses 2=const-zero and 3=const-offset in the same 2-bit field
401        assert_eq!(TileCompressionMode::RawBinary as u8, 0);
402        assert_eq!(TileCompressionMode::BitStuffed as u8, 1);
403        assert_eq!(TileCompressionMode::ConstZero as u8, 2);
404        assert_eq!(TileCompressionMode::ConstOffset as u8, 3);
405    }
406
407    #[test]
408    fn data_type_matches_cpp() {
409        assert_eq!(DataType::Char as i32, 0);
410        assert_eq!(DataType::Byte as i32, 1);
411        assert_eq!(DataType::Short as i32, 2);
412        assert_eq!(DataType::UShort as i32, 3);
413        assert_eq!(DataType::Int as i32, 4);
414        assert_eq!(DataType::UInt as i32, 5);
415        assert_eq!(DataType::Float as i32, 6);
416        assert_eq!(DataType::Double as i32, 7);
417    }
418
419    #[test]
420    fn tile_flags_matches_cpp() {
421        assert_eq!(tile_flags::MODE_MASK, 0x03);
422        assert_eq!(tile_flags::DIFF_ENCODING, 0x04);
423        assert_eq!(tile_flags::TYPE_REDUCTION_SHIFT, 6);
424    }
425
426    #[test]
427    fn image_encode_mode_try_from_round_trip() {
428        for v in 0..=3u8 {
429            let mode = ImageEncodeMode::try_from(v).unwrap();
430            assert_eq!(mode as u8, v);
431        }
432        assert!(ImageEncodeMode::try_from(4).is_err());
433        assert!(ImageEncodeMode::try_from(255).is_err());
434    }
435}