Skip to main content

lerc_reader/
lib.rs

1//! Pure-Rust LERC decoder.
2//!
3//! The public API is decoder-first and mirrors the high-value inspection and
4//! decode entry points from Esri's LERC project:
5//!
6//! - inspect a blob with [`get_blob_info`]
7//! - count concatenated blobs with [`get_band_count`]
8//! - decode native pixel buffers with [`decode`]
9//! - decode promoted `f64` buffers with [`decode_to_f64`]
10//! - decode directly into `ndarray::ArrayD` with [`decode_ndarray`]
11
12mod io;
13
14use std::cmp;
15
16use io::Cursor;
17use lerc_core::{
18    BandSetInfo, BlobInfo, DataType, Decoded, DecodedBandSet, DecodedF64, Error, NdArrayElement,
19    PixelData, Result, Version,
20};
21use ndarray::ArrayD;
22
23const MAGIC_LERC1_PREFIX: &[u8; 9] = b"CntZImage";
24const MAGIC_LERC2: &[u8; 6] = b"Lerc2 ";
25const HUFFMAN_LUT_BITS_MAX: u8 = 12;
26
27#[derive(Debug, Clone)]
28struct DepthRanges {
29    min_values: Vec<f64>,
30    max_values: Vec<f64>,
31}
32
33#[derive(Debug, Clone)]
34struct HuffmanEntry {
35    bit_len: u8,
36    value: u32,
37}
38
39#[derive(Debug, Default)]
40struct HuffmanNode {
41    value: Option<u32>,
42    left: Option<Box<HuffmanNode>>,
43    right: Option<Box<HuffmanNode>>,
44}
45
46#[derive(Debug)]
47struct HuffmanInfo {
48    quick_lut: Vec<Option<HuffmanEntry>>,
49    quick_bits: u8,
50    max_bits: u8,
51    tree: HuffmanNode,
52    stuffed_data: Vec<u32>,
53    src_ptr: usize,
54    bit_pos: u8,
55}
56
57#[derive(Debug)]
58struct HuffmanStream<'a> {
59    words: &'a [u32],
60    src_ptr: usize,
61    bit_pos: u8,
62}
63
64#[derive(Debug, Clone)]
65struct Lerc1PixelsHeader {
66    num_blocks_y: usize,
67    num_blocks_x: usize,
68    max_value: f32,
69}
70
71#[derive(Debug, Clone)]
72enum Lerc1Block {
73    Zero,
74    Constant(f32),
75    Raw(Vec<f32>),
76    Stuffed {
77        offset: f32,
78        bits_per_pixel: u8,
79        num_valid_pixels: usize,
80        stuffed_data: Vec<u32>,
81    },
82}
83
84#[derive(Debug, Clone)]
85struct Lerc1Blob {
86    info: BlobInfo,
87    mask: Option<Vec<u8>>,
88    pixels: Lerc1PixelsHeader,
89    blocks: Vec<Lerc1Block>,
90    actual_num_blocks_y: usize,
91    actual_num_blocks_x: usize,
92    base_block_height: usize,
93    base_block_width: usize,
94    eof_offset: usize,
95}
96
97fn is_lerc1(blob: &[u8]) -> bool {
98    blob.starts_with(MAGIC_LERC1_PREFIX)
99}
100
101fn is_lerc2(blob: &[u8]) -> bool {
102    blob.starts_with(MAGIC_LERC2)
103}
104
105pub fn get_blob_info(blob: &[u8]) -> Result<BlobInfo> {
106    if is_lerc1(blob) {
107        let mut parsed = parse_lerc1(blob, None)?;
108        if parsed.info.valid_pixel_count != 0 {
109            let pixels = decode_lerc1_pixels(&parsed)?;
110            let (z_min, z_max) = scan_range(&pixels, parsed.mask.as_deref())?;
111            parsed.info.z_min = z_min;
112            parsed.info.z_max = z_max;
113        }
114        return Ok(parsed.info);
115    }
116
117    let (mut info, mut cursor) = parse_lerc2(blob)?;
118    let _mask = read_mask(&mut cursor, &info, None)?;
119    if should_read_depth_ranges(&info) {
120        let ranges = read_depth_ranges(&mut cursor, &info)?;
121        info.min_values = Some(ranges.min_values);
122        info.max_values = Some(ranges.max_values);
123    }
124    Ok(info)
125}
126
127pub fn get_band_count(blob: &[u8]) -> Result<usize> {
128    let mut offset = 0usize;
129    let mut count = 0usize;
130    let mut lerc1_mask: Option<Vec<u8>> = None;
131    let mut lerc2_mask: Option<Vec<u8>> = None;
132
133    while offset < blob.len() {
134        let slice = &blob[offset..];
135        let next_len = if is_lerc1(slice) {
136            let parsed = parse_lerc1(slice, lerc1_mask.as_deref())?;
137            lerc1_mask = parsed.mask.clone();
138            lerc2_mask = None;
139            parsed.eof_offset
140        } else if is_lerc2(slice) {
141            let (info, mut cursor) = parse_lerc2(slice)?;
142            let mask = read_mask(&mut cursor, &info, lerc2_mask.as_deref())?;
143            lerc2_mask = mask;
144            lerc1_mask = None;
145            info.blob_size
146        } else {
147            return Err(Error::InvalidMagic);
148        };
149
150        let next = offset
151            .checked_add(next_len)
152            .ok_or_else(|| Error::InvalidBlob("band offset overflow".into()))?;
153        if next <= offset || next > blob.len() {
154            return Err(Error::InvalidBlob(
155                "invalid concatenated band blob size".into(),
156            ));
157        }
158
159        offset = next;
160        count += 1;
161    }
162
163    Ok(count)
164}
165
166pub fn decode(blob: &[u8]) -> Result<Decoded> {
167    decode_single(blob, None, None)
168}
169
170pub fn decode_band_set(blob: &[u8]) -> Result<DecodedBandSet> {
171    let mut offset = 0usize;
172    let mut pixels = Vec::new();
173    let mut infos = Vec::new();
174    let mut band_masks = Vec::new();
175    let mut lerc1_mask: Option<Vec<u8>> = None;
176    let mut lerc2_mask: Option<Vec<u8>> = None;
177
178    while offset < blob.len() {
179        let slice = &blob[offset..];
180        let decoded = if is_lerc1(slice) {
181            let decoded = decode_single(slice, lerc1_mask.as_deref(), None)?;
182            lerc1_mask = decoded.mask.clone();
183            lerc2_mask = None;
184            decoded
185        } else if is_lerc2(slice) {
186            let decoded = decode_single(slice, None, lerc2_mask.as_deref())?;
187            lerc2_mask = decoded.mask.clone();
188            lerc1_mask = None;
189            decoded
190        } else {
191            return Err(Error::InvalidMagic);
192        };
193
194        let next = offset
195            .checked_add(decoded.info.blob_size)
196            .ok_or_else(|| Error::InvalidBlob("band offset overflow".into()))?;
197        if next <= offset || next > blob.len() {
198            return Err(Error::InvalidBlob(
199                "invalid concatenated band blob size".into(),
200            ));
201        }
202
203        infos.push(decoded.info);
204        pixels.push(decoded.pixels);
205        band_masks.push(decoded.mask);
206        offset = next;
207    }
208
209    Ok(DecodedBandSet {
210        info: BandSetInfo::new(infos)?,
211        bands: pixels,
212        band_masks,
213    })
214}
215
216pub fn decode_band_set_ndarray<T: NdArrayElement>(blob: &[u8]) -> Result<ArrayD<T>> {
217    decode_band_set(blob)?.into_ndarray()
218}
219
220pub fn decode_band_set_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
221    decode_band_set(blob)?.into_ndarray()
222}
223
224pub fn decode_band_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
225    decode_band_set(blob)?.into_band_mask_ndarray()
226}
227
228fn decode_single(
229    blob: &[u8],
230    lerc1_shared_mask: Option<&[u8]>,
231    lerc2_shared_mask: Option<&[u8]>,
232) -> Result<Decoded> {
233    if is_lerc1(blob) {
234        let mut parsed = parse_lerc1(blob, lerc1_shared_mask)?;
235        let pixels = decode_lerc1_pixels(&parsed)?;
236        if parsed.info.valid_pixel_count != 0 {
237            let (z_min, z_max) = scan_range(&pixels, parsed.mask.as_deref())?;
238            parsed.info.z_min = z_min;
239            parsed.info.z_max = z_max;
240        }
241        return Ok(Decoded {
242            info: parsed.info,
243            pixels,
244            mask: parsed.mask,
245        });
246    }
247
248    let (info, _) = parse_lerc2(blob)?;
249    let (mut info, mut cursor) = parse_lerc2(&blob[..info.blob_size])?;
250    let mask = read_mask(&mut cursor, &info, lerc2_shared_mask)?;
251
252    let depth_ranges = if should_read_depth_ranges(&info) {
253        let ranges = read_depth_ranges(&mut cursor, &info)?;
254        info.min_values = Some(ranges.min_values.clone());
255        info.max_values = Some(ranges.max_values.clone());
256        Some(ranges)
257    } else {
258        None
259    };
260
261    let pixels = decode_pixels(&mut cursor, &info, depth_ranges.as_ref(), mask.as_deref())?;
262    Ok(Decoded { info, pixels, mask })
263}
264
265pub fn decode_to_f64(blob: &[u8]) -> Result<DecodedF64> {
266    let decoded = decode(blob)?;
267    Ok(DecodedF64 {
268        info: decoded.info,
269        pixels: decoded.pixels.to_f64(),
270        mask: decoded.mask,
271    })
272}
273
274pub fn decode_ndarray<T: NdArrayElement>(blob: &[u8]) -> Result<ArrayD<T>> {
275    decode(blob)?.into_ndarray()
276}
277
278pub fn decode_ndarray_f64(blob: &[u8]) -> Result<ArrayD<f64>> {
279    decode_to_f64(blob)?.into_ndarray()
280}
281
282pub fn decode_mask_ndarray(blob: &[u8]) -> Result<Option<ArrayD<u8>>> {
283    decode(blob)?.into_mask_ndarray()
284}
285
286fn parse_lerc1(blob: &[u8], shared_mask: Option<&[u8]>) -> Result<Lerc1Blob> {
287    let mut cursor = Cursor::new(blob);
288    let magic = cursor.read_bytes(10)?;
289    if !magic.starts_with(MAGIC_LERC1_PREFIX) {
290        return Err(Error::InvalidMagic);
291    }
292
293    let version = cursor.read_i32()?;
294    if version < 0 {
295        return Err(Error::UnsupportedVersion(version as u32));
296    }
297    let image_type = cursor.read_i32()?;
298    let height = cursor.read_u32()?;
299    let width = cursor.read_u32()?;
300    let max_z_error = cursor.read_f64()?;
301
302    let mask = if let Some(shared_mask) = shared_mask {
303        Some(shared_mask.to_vec())
304    } else {
305        read_lerc1_mask(&mut cursor, width, height)?
306    };
307
308    let pixels_num_blocks_y = cursor.read_u32()? as usize;
309    let pixels_num_blocks_x = cursor.read_u32()? as usize;
310    let _pixels_num_bytes = cursor.read_u32()? as usize;
311    let pixels_max_value = cursor.read_f32()?;
312
313    if pixels_num_blocks_x == 0 || pixels_num_blocks_y == 0 {
314        return Err(Error::InvalidHeader("Lerc1 block grid must be non-zero"));
315    }
316
317    let width_usize = width as usize;
318    let height_usize = height as usize;
319    let num_pixels = width_usize
320        .checked_mul(height_usize)
321        .ok_or_else(|| Error::InvalidBlob("pixel count overflows usize".into()))?;
322    let base_block_width = width_usize / pixels_num_blocks_x;
323    let base_block_height = height_usize / pixels_num_blocks_y;
324    let actual_num_blocks_x =
325        pixels_num_blocks_x + usize::from(width_usize % pixels_num_blocks_x != 0);
326    let actual_num_blocks_y =
327        pixels_num_blocks_y + usize::from(height_usize % pixels_num_blocks_y != 0);
328
329    let valid_pixel_count = match mask.as_deref() {
330        Some(mask) => mask.iter().map(|&value| u32::from(value != 0)).sum(),
331        None => num_pixels as u32,
332    };
333
334    let mut blocks = Vec::with_capacity(actual_num_blocks_x * actual_num_blocks_y);
335    for block_y in 0..actual_num_blocks_y {
336        let this_block_height =
337            if block_y + 1 == actual_num_blocks_y && height_usize % pixels_num_blocks_y != 0 {
338                height_usize % pixels_num_blocks_y
339            } else {
340                base_block_height
341            };
342        if this_block_height == 0 {
343            continue;
344        }
345        for block_x in 0..actual_num_blocks_x {
346            let this_block_width =
347                if block_x + 1 == actual_num_blocks_x && width_usize % pixels_num_blocks_x != 0 {
348                    width_usize % pixels_num_blocks_x
349                } else {
350                    base_block_width
351                };
352            if this_block_width == 0 {
353                continue;
354            }
355
356            let block_valid_pixels = if let Some(mask) = mask.as_deref() {
357                count_valid_in_block(
358                    mask,
359                    width_usize,
360                    block_x * base_block_width,
361                    block_y * base_block_height,
362                    this_block_width,
363                    this_block_height,
364                )
365            } else {
366                this_block_width * this_block_height
367            };
368
369            let header_byte = cursor.read_u8()?;
370            let encoding = header_byte & 63;
371            if encoding > 3 {
372                return Err(Error::InvalidBlob(format!(
373                    "invalid Lerc1 block encoding {encoding}"
374                )));
375            }
376            if encoding == 2 {
377                blocks.push(Lerc1Block::Zero);
378                continue;
379            }
380
381            let offset = if header_byte != 0 && header_byte != 2 {
382                Some(read_lerc1_offset(&mut cursor, header_byte >> 6)?)
383            } else {
384                None
385            };
386
387            if encoding == 3 {
388                blocks.push(Lerc1Block::Constant(offset.ok_or(Error::InvalidBlob(
389                    "Lerc1 constant block is missing its offset".into(),
390                ))?));
391                continue;
392            }
393
394            if encoding == 0 {
395                let byte_len = block_valid_pixels.checked_mul(4).ok_or_else(|| {
396                    Error::InvalidBlob("Lerc1 raw block byte count overflows usize".into())
397                })?;
398                let values = cursor
399                    .read_bytes(byte_len)?
400                    .chunks_exact(4)
401                    .map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
402                    .collect();
403                blocks.push(Lerc1Block::Raw(values));
404                continue;
405            }
406
407            let packed_header = cursor.read_u8()?;
408            let bits_per_pixel = packed_header & 63;
409            let num_valid_pixels = match packed_header >> 6 {
410                0 => cursor.read_u32()? as usize,
411                1 => read_u16(cursor.read_bytes(2)?)? as usize,
412                2 => cursor.read_u8()? as usize,
413                other => {
414                    return Err(Error::InvalidBlob(format!(
415                        "invalid Lerc1 valid pixel count type {other}"
416                    )))
417                }
418            };
419            let data_bytes = (num_valid_pixels * usize::from(bits_per_pixel)).div_ceil(8);
420            let stuffed_data = words_from_padded(cursor.read_bytes(data_bytes)?);
421            blocks.push(Lerc1Block::Stuffed {
422                offset: offset.ok_or(Error::InvalidBlob(
423                    "Lerc1 bit-stuffed block is missing its offset".into(),
424                ))?,
425                bits_per_pixel,
426                num_valid_pixels,
427                stuffed_data,
428            });
429        }
430    }
431
432    let eof_offset = cursor.offset();
433    let info = BlobInfo {
434        version: Version::Lerc1(version as u32),
435        data_type: map_lerc1_data_type(image_type),
436        width,
437        height,
438        depth: 1,
439        min_values: None,
440        max_values: None,
441        valid_pixel_count,
442        micro_block_size: 0,
443        blob_size: eof_offset,
444        max_z_error,
445        z_min: 0.0,
446        z_max: pixels_max_value as f64,
447    };
448
449    Ok(Lerc1Blob {
450        info,
451        mask,
452        pixels: Lerc1PixelsHeader {
453            num_blocks_y: pixels_num_blocks_y,
454            num_blocks_x: pixels_num_blocks_x,
455            max_value: pixels_max_value,
456        },
457        blocks,
458        actual_num_blocks_y,
459        actual_num_blocks_x,
460        base_block_height,
461        base_block_width,
462        eof_offset,
463    })
464}
465
466fn map_lerc1_data_type(_image_type: i32) -> DataType {
467    DataType::F32
468}
469
470fn read_lerc1_offset(cursor: &mut Cursor<'_>, offset_type: u8) -> Result<f32> {
471    match offset_type {
472        0 => cursor.read_f32(),
473        1 => Ok(cursor.read_i16()? as f32),
474        2 => Ok(i8::from_le_bytes([cursor.read_u8()?]) as f32),
475        _ => Err(Error::InvalidBlob(format!(
476            "invalid Lerc1 block offset type {offset_type}"
477        ))),
478    }
479}
480
481fn read_lerc1_mask(cursor: &mut Cursor<'_>, width: u32, height: u32) -> Result<Option<Vec<u8>>> {
482    let num_blocks_y = cursor.read_u32()?;
483    let num_blocks_x = cursor.read_u32()?;
484    let num_bytes = cursor.read_u32()? as usize;
485    let max_value = cursor.read_f32()?;
486
487    let num_pixels = (width as usize)
488        .checked_mul(height as usize)
489        .ok_or_else(|| Error::InvalidBlob("pixel count overflows usize".into()))?;
490    let bitset_len = num_pixels.div_ceil(8);
491
492    if num_bytes > 0 {
493        let bitset = decode_mask_rle(cursor.read_bytes(num_bytes)?, bitset_len)?;
494        return Ok(Some(unpack_mask_bitset(&bitset, num_pixels)));
495    }
496
497    if num_blocks_y == 0 && num_blocks_x == 0 && max_value == 0.0 {
498        return Ok(Some(vec![0; num_pixels]));
499    }
500
501    Ok(None)
502}
503
504fn decode_lerc1_pixels(parsed: &Lerc1Blob) -> Result<PixelData> {
505    let width = parsed.info.width as usize;
506    let height = parsed.info.height as usize;
507    let mask = parsed.mask.as_deref();
508    let mut result = vec![0.0f32; width * height];
509    let mut block_buffer = vec![0.0f64; parsed.base_block_width * parsed.base_block_height];
510    let mut block_index = 0usize;
511
512    for block_y in 0..parsed.actual_num_blocks_y {
513        let this_block_height = if block_y + 1 == parsed.actual_num_blocks_y
514            && height % parsed.pixels.num_blocks_y != 0
515        {
516            height % parsed.pixels.num_blocks_y
517        } else {
518            parsed.base_block_height
519        };
520        if this_block_height == 0 {
521            continue;
522        }
523
524        for block_x in 0..parsed.actual_num_blocks_x {
525            let this_block_width = if block_x + 1 == parsed.actual_num_blocks_x
526                && width % parsed.pixels.num_blocks_x != 0
527            {
528                width % parsed.pixels.num_blocks_x
529            } else {
530                parsed.base_block_width
531            };
532            if this_block_width == 0 {
533                continue;
534            }
535
536            let block = &parsed.blocks[block_index];
537            block_index += 1;
538
539            let mut stuffed_values: Option<&[f64]> = None;
540            let (raw_values, constant_value) = match block {
541                Lerc1Block::Zero => (None, Some(0.0f32)),
542                Lerc1Block::Constant(value) => (None, Some(*value)),
543                Lerc1Block::Raw(values) => (Some(values.as_slice()), None),
544                Lerc1Block::Stuffed {
545                    offset,
546                    bits_per_pixel,
547                    num_valid_pixels,
548                    stuffed_data,
549                } => {
550                    if *num_valid_pixels > block_buffer.len() {
551                        return Err(Error::InvalidBlob(
552                            "Lerc1 stuffed block expands beyond its output buffer".into(),
553                        ));
554                    }
555                    block_buffer[..*num_valid_pixels].fill(0.0);
556                    unstuff_v2(
557                        stuffed_data,
558                        &mut block_buffer[..*num_valid_pixels],
559                        *bits_per_pixel,
560                        UnstuffOptions {
561                            num_pixels: *num_valid_pixels,
562                            lut_values: None,
563                            offset: Some(*offset as f64),
564                            scale: 2.0 * parsed.info.max_z_error,
565                            max_value: parsed.pixels.max_value as f64,
566                        },
567                    );
568                    stuffed_values = Some(&block_buffer[..*num_valid_pixels]);
569                    (None, None)
570                }
571            };
572
573            let mut value_index = 0usize;
574            for row in 0..this_block_height {
575                let pixel_row = block_y * parsed.base_block_height + row;
576                for col in 0..this_block_width {
577                    let pixel = pixel_row * width + block_x * parsed.base_block_width + col;
578                    if mask.map(|mask| mask[pixel] != 0).unwrap_or(true) {
579                        let value = if let Some(value) = constant_value {
580                            value
581                        } else if let Some(values) = raw_values {
582                            let value = values.get(value_index).copied().ok_or_else(|| {
583                                Error::InvalidBlob("Lerc1 raw block payload ended early".into())
584                            })?;
585                            value
586                        } else if let Some(values) = stuffed_values {
587                            values.get(value_index).copied().ok_or_else(|| {
588                                Error::InvalidBlob("Lerc1 stuffed block payload ended early".into())
589                            })? as f32
590                        } else {
591                            unreachable!()
592                        };
593                        result[pixel] = value;
594                        value_index += 1;
595                    }
596                }
597            }
598
599            let expected_values = match block {
600                Lerc1Block::Zero | Lerc1Block::Constant(_) => 0,
601                Lerc1Block::Raw(values) => values.len(),
602                Lerc1Block::Stuffed {
603                    num_valid_pixels, ..
604                } => *num_valid_pixels,
605            };
606            if expected_values != 0 && value_index != expected_values {
607                return Err(Error::InvalidBlob(
608                    "Lerc1 block payload does not match the block mask".into(),
609                ));
610            }
611        }
612    }
613
614    Ok(PixelData::F32(result))
615}
616
617fn parse_lerc2(blob: &[u8]) -> Result<(BlobInfo, Cursor<'_>)> {
618    let mut cursor = Cursor::new(blob);
619    let magic = cursor.read_bytes(6)?;
620    if magic != MAGIC_LERC2 {
621        return Err(Error::InvalidMagic);
622    }
623
624    let version = cursor.read_i32()?;
625    if version < 1 {
626        return Err(Error::UnsupportedVersion(version as u32));
627    }
628    let version = version as u32;
629    if version > 6 {
630        return Err(Error::UnsupportedVersion(version));
631    }
632
633    let checksum = if version >= 3 {
634        Some(cursor.read_u32()?)
635    } else {
636        None
637    };
638
639    let height = cursor.read_u32()?;
640    let width = cursor.read_u32()?;
641    let depth = if version >= 4 { cursor.read_u32()? } else { 1 };
642
643    let valid_pixel_count = cursor.read_u32()?;
644    let micro_block_size = cursor.read_i32()?;
645    let blob_size = cursor.read_i32()?;
646    let image_type = cursor.read_i32()?;
647    let max_z_error = cursor.read_f64()?;
648    let z_min = cursor.read_f64()?;
649    let z_max = cursor.read_f64()?;
650
651    if micro_block_size < 0 {
652        return Err(Error::InvalidHeader("negative micro block size"));
653    }
654    if blob_size <= 0 {
655        return Err(Error::InvalidHeader("non-positive blob size"));
656    }
657    let blob_size = blob_size as usize;
658    if blob_size > blob.len() {
659        return Err(Error::Truncated {
660            offset: 0,
661            needed: blob_size,
662            available: blob.len(),
663        });
664    }
665
666    if let Some(expected) = checksum {
667        let actual = fletcher32(&blob[14..blob_size]);
668        if actual != expected {
669            return Err(Error::ChecksumMismatch { expected, actual });
670        }
671    }
672
673    let info = BlobInfo {
674        version: Version::Lerc2(version),
675        data_type: DataType::from_code(image_type)?,
676        width,
677        height,
678        depth,
679        min_values: None,
680        max_values: None,
681        valid_pixel_count,
682        micro_block_size: micro_block_size as u32,
683        blob_size,
684        max_z_error,
685        z_min,
686        z_max,
687    };
688
689    Ok((info, cursor))
690}
691
692fn should_read_depth_ranges(info: &BlobInfo) -> bool {
693    matches!(info.version, Version::Lerc2(version) if version >= 4)
694        && info.valid_pixel_count != 0
695        && info.z_min != info.z_max
696}
697
698fn read_mask(
699    cursor: &mut Cursor<'_>,
700    info: &BlobInfo,
701    inherited_mask: Option<&[u8]>,
702) -> Result<Option<Vec<u8>>> {
703    let num_pixels = info.pixel_count()?;
704    let num_valid = info.valid_pixel_count as usize;
705    if num_valid > num_pixels {
706        return Err(Error::InvalidBlob(format!(
707            "valid pixel count {} exceeds pixel count {}",
708            num_valid, num_pixels
709        )));
710    }
711
712    let num_bytes = cursor.read_u32()? as usize;
713    if num_valid == num_pixels {
714        if num_bytes != 0 {
715            return Err(Error::InvalidBlob(
716                "full-valid LERC blob unexpectedly contains mask bytes".into(),
717            ));
718        }
719        return Ok(None);
720    }
721
722    if num_valid == 0 {
723        cursor.skip(num_bytes)?;
724        return Ok(Some(vec![0; num_pixels]));
725    }
726
727    if num_bytes == 0 {
728        let mask = inherited_mask.ok_or(Error::UnsupportedFeature(
729            "external masks are not yet supported",
730        ))?;
731        if mask.len() != num_pixels {
732            return Err(Error::InvalidBlob(
733                "inherited mask length does not match the current LERC blob".into(),
734            ));
735        }
736        let inherited_valid = mask.iter().filter(|&&value| value != 0).count();
737        if inherited_valid != num_valid {
738            return Err(Error::InvalidBlob(
739                "inherited mask valid count does not match the current LERC blob".into(),
740            ));
741        }
742        return Ok(Some(mask.to_vec()));
743    }
744
745    let bitset = decode_mask_rle(cursor.read_bytes(num_bytes)?, num_pixels.div_ceil(8))?;
746    Ok(Some(unpack_mask_bitset(&bitset, num_pixels)))
747}
748
749fn decode_mask_rle(encoded: &[u8], bitset_len: usize) -> Result<Vec<u8>> {
750    let mut bitset = vec![0u8; bitset_len];
751    let mut inner = Cursor::new(encoded);
752    let mut out = 0usize;
753
754    loop {
755        let count = inner.read_i16()?;
756        if count == i16::MIN {
757            break;
758        }
759        if count > 0 {
760            let count = count as usize;
761            let run = inner.read_bytes(count)?;
762            if out
763                .checked_add(count)
764                .ok_or_else(|| Error::InvalidBlob("mask RLE overflow".into()))?
765                > bitset.len()
766            {
767                return Err(Error::InvalidBlob(
768                    "mask RLE expands past bitset length".into(),
769                ));
770            }
771            bitset[out..out + count].copy_from_slice(run);
772            out += count;
773        } else {
774            let count = (-count) as usize;
775            let value = inner.read_u8()?;
776            if out
777                .checked_add(count)
778                .ok_or_else(|| Error::InvalidBlob("mask RLE overflow".into()))?
779                > bitset.len()
780            {
781                return Err(Error::InvalidBlob(
782                    "mask RLE expands past bitset length".into(),
783                ));
784            }
785            bitset[out..out + count].fill(value);
786            out += count;
787        }
788    }
789
790    if out != bitset.len() {
791        return Err(Error::InvalidBlob(
792            "mask RLE ended before filling bitset".into(),
793        ));
794    }
795    if inner.offset() != encoded.len() {
796        return Err(Error::InvalidBlob(
797            "mask RLE contains trailing bytes after sentinel".into(),
798        ));
799    }
800
801    Ok(bitset)
802}
803
804fn unpack_mask_bitset(bitset: &[u8], num_pixels: usize) -> Vec<u8> {
805    let mut mask = vec![0u8; num_pixels];
806    for (i, item) in mask.iter_mut().enumerate() {
807        let byte = bitset[i >> 3];
808        let bit = 7 - (i & 7);
809        *item = (byte >> bit) & 1;
810    }
811    mask
812}
813
814#[derive(Clone, Copy)]
815struct UnstuffOptions<'a> {
816    num_pixels: usize,
817    lut_values: Option<&'a [f64]>,
818    offset: Option<f64>,
819    scale: f64,
820    max_value: f64,
821}
822
823fn read_depth_ranges(cursor: &mut Cursor<'_>, info: &BlobInfo) -> Result<DepthRanges> {
824    let depth = info.depth as usize;
825    let range_bytes = depth
826        .checked_mul(info.data_type.byte_len())
827        .ok_or_else(|| Error::InvalidBlob("range byte length overflows usize".into()))?;
828    let min_values = read_typed_values(cursor.read_bytes(range_bytes)?, info.data_type)?;
829    let max_values = read_typed_values(cursor.read_bytes(range_bytes)?, info.data_type)?;
830    Ok(DepthRanges {
831        min_values,
832        max_values,
833    })
834}
835
836fn decode_pixels(
837    cursor: &mut Cursor<'_>,
838    info: &BlobInfo,
839    depth_ranges: Option<&DepthRanges>,
840    mask: Option<&[u8]>,
841) -> Result<PixelData> {
842    let num_pixels = info.pixel_count()?;
843    let sample_count = info.sample_count()?;
844
845    if info.valid_pixel_count == 0 {
846        return Ok(zeroed_pixels(info.data_type, sample_count));
847    }
848
849    if info.z_min == info.z_max {
850        return Ok(constant_pixels(
851            info.data_type,
852            num_pixels,
853            info.depth as usize,
854            mask,
855            ConstantValues::Single(info.z_max),
856        ));
857    }
858
859    if let Some(ranges) = depth_ranges {
860        if ranges.min_values == ranges.max_values {
861            return Ok(constant_pixels(
862                info.data_type,
863                num_pixels,
864                info.depth as usize,
865                mask,
866                ConstantValues::PerDepth(ranges.max_values.clone()),
867            ));
868        }
869    }
870
871    let one_sweep = cursor.read_u8()? != 0;
872    if one_sweep {
873        return decode_one_sweep(cursor, info, mask);
874    }
875
876    let version = match info.version {
877        Version::Lerc2(version) => version,
878        _ => unreachable!("Lerc2 decode called with a non-Lerc2 blob"),
879    };
880
881    if version > 1 && info.data_type.code() <= 1 && (info.max_z_error - 0.5).abs() < 1e-5 {
882        let encode_mode = cursor.read_u8()?;
883        if encode_mode > 2 || (version < 4 && encode_mode > 1) {
884            return Err(Error::InvalidBlob(format!(
885                "invalid Huffman flag {encode_mode}"
886            )));
887        }
888        if encode_mode != 0 {
889            return decode_huffman(cursor, info, depth_ranges, mask, encode_mode == 1);
890        }
891    }
892
893    decode_tiles(cursor, info, depth_ranges, mask)
894}
895
896fn decode_one_sweep(
897    cursor: &mut Cursor<'_>,
898    info: &BlobInfo,
899    mask: Option<&[u8]>,
900) -> Result<PixelData> {
901    let num_pixels = info.pixel_count()?;
902    let num_valid = info.valid_pixel_count as usize;
903    let depth = info.depth as usize;
904    let sample_len = info
905        .data_type
906        .byte_len()
907        .checked_mul(num_valid)
908        .and_then(|v| v.checked_mul(depth))
909        .ok_or_else(|| Error::InvalidBlob("one-sweep byte count overflows usize".into()))?;
910    let raw = read_typed_values(cursor.read_bytes(sample_len)?, info.data_type)?;
911
912    if num_valid == num_pixels {
913        return Ok(cast_pixels(info.data_type, raw));
914    }
915
916    let mask = mask.ok_or(Error::InvalidBlob(
917        "partial-valid one-sweep block is missing its decoded mask".into(),
918    ))?;
919    scatter_valid_samples(info.data_type, info.sample_count()?, depth, mask, &raw)
920}
921
922fn decode_tiles(
923    cursor: &mut Cursor<'_>,
924    info: &BlobInfo,
925    depth_ranges: Option<&DepthRanges>,
926    mask: Option<&[u8]>,
927) -> Result<PixelData> {
928    let width = info.width as usize;
929    let height = info.height as usize;
930    let depth = info.depth as usize;
931    let num_pixels = width
932        .checked_mul(height)
933        .ok_or_else(|| Error::InvalidBlob("pixel count overflows usize".into()))?;
934    let micro_block_size = info.micro_block_size as usize;
935    if micro_block_size == 0 {
936        return Err(Error::InvalidBlob(
937            "micro block size must be greater than zero".into(),
938        ));
939    }
940
941    let num_blocks_x = width.div_ceil(micro_block_size);
942    let num_blocks_y = height.div_ceil(micro_block_size);
943    let last_block_width = if width % micro_block_size == 0 {
944        micro_block_size
945    } else {
946        width % micro_block_size
947    };
948    let last_block_height = if height % micro_block_size == 0 {
949        micro_block_size
950    } else {
951        height % micro_block_size
952    };
953
954    let mut result_bsq = vec![0.0; info.sample_count()?];
955    let mut block_buffer = vec![0.0; micro_block_size * micro_block_size];
956    let version = match info.version {
957        Version::Lerc2(version) => version,
958        _ => unreachable!("Lerc2 tile decode called with a non-Lerc2 blob"),
959    };
960    let file_version_check_num = if version >= 5 { 14u8 } else { 15u8 };
961
962    for block_y in 0..num_blocks_y {
963        let this_block_height = if block_y + 1 == num_blocks_y {
964            last_block_height
965        } else {
966            micro_block_size
967        };
968        for block_x in 0..num_blocks_x {
969            let this_block_width = if block_x + 1 == num_blocks_x {
970                last_block_width
971            } else {
972                micro_block_size
973            };
974
975            for dim in 0..depth {
976                let header_byte = cursor.read_u8()?;
977                let is_diff_encoding = version >= 5 && (header_byte & 4) != 0;
978                let bits67 = header_byte >> 6;
979                let test_code = (header_byte >> 2) & file_version_check_num;
980                let expected_code =
981                    (((block_x * micro_block_size) >> 3) as u8) & file_version_check_num;
982                if test_code != expected_code {
983                    return Err(Error::InvalidBlob(
984                        "tile block integrity check failed".into(),
985                    ));
986                }
987                if is_diff_encoding && dim == 0 {
988                    return Err(Error::InvalidBlob(
989                        "diff-encoded tile block encountered in first dimension".into(),
990                    ));
991                }
992
993                let block_encoding = header_byte & 3;
994                let dim_base = dim * num_pixels;
995                let prev_dim_base = dim.checked_sub(1).map(|prev| prev * num_pixels);
996                let z_max = depth_ranges
997                    .and_then(|ranges| ranges.max_values.get(dim))
998                    .copied()
999                    .unwrap_or(info.z_max);
1000
1001                match block_encoding {
1002                    2 => {
1003                        if is_diff_encoding {
1004                            let prev_dim_base = prev_dim_base.ok_or(Error::InvalidBlob(
1005                                "diff encoding requires a previous dimension".into(),
1006                            ))?;
1007                            for row in 0..this_block_height {
1008                                let pixel_row = block_y * micro_block_size + row;
1009                                for col in 0..this_block_width {
1010                                    let pixel =
1011                                        pixel_row * width + block_x * micro_block_size + col;
1012                                    if mask.map(|m| m[pixel] != 0).unwrap_or(true) {
1013                                        result_bsq[dim_base + pixel] =
1014                                            result_bsq[prev_dim_base + pixel];
1015                                    }
1016                                }
1017                            }
1018                        }
1019                    }
1020                    0 => {
1021                        if is_diff_encoding {
1022                            return Err(Error::InvalidBlob(
1023                                "uncompressed diff-encoded tile block is invalid".into(),
1024                            ));
1025                        }
1026                        let values_needed = if let Some(mask) = mask {
1027                            count_valid_in_block(
1028                                mask,
1029                                width,
1030                                block_x * micro_block_size,
1031                                block_y * micro_block_size,
1032                                this_block_width,
1033                                this_block_height,
1034                            )
1035                        } else {
1036                            this_block_width * this_block_height
1037                        };
1038                        let byte_len = values_needed
1039                            .checked_mul(info.data_type.byte_len())
1040                            .ok_or_else(|| {
1041                                Error::InvalidBlob(
1042                                    "uncompressed block byte length overflows usize".into(),
1043                                )
1044                            })?;
1045                        let raw_values =
1046                            read_typed_values(cursor.read_bytes(byte_len)?, info.data_type)?;
1047                        let mut src = 0usize;
1048                        for row in 0..this_block_height {
1049                            let pixel_row = block_y * micro_block_size + row;
1050                            for col in 0..this_block_width {
1051                                let pixel = pixel_row * width + block_x * micro_block_size + col;
1052                                if mask.map(|m| m[pixel] != 0).unwrap_or(true) {
1053                                    result_bsq[dim_base + pixel] = raw_values[src];
1054                                    src += 1;
1055                                }
1056                            }
1057                        }
1058                        if src != raw_values.len() {
1059                            return Err(Error::InvalidBlob(
1060                                "uncompressed tile block payload contains trailing values".into(),
1061                            ));
1062                        }
1063                    }
1064                    1 | 3 => {
1065                        let offset_type = data_type_used(
1066                            if is_diff_encoding && info.data_type.code() < 6 {
1067                                DataType::I32
1068                            } else {
1069                                info.data_type
1070                            },
1071                            bits67,
1072                        )?;
1073                        let offset =
1074                            read_scalar(cursor.read_bytes(offset_type.byte_len())?, offset_type)?;
1075
1076                        if block_encoding == 3 {
1077                            for row in 0..this_block_height {
1078                                let pixel_row = block_y * micro_block_size + row;
1079                                for col in 0..this_block_width {
1080                                    let pixel =
1081                                        pixel_row * width + block_x * micro_block_size + col;
1082                                    if mask.map(|m| m[pixel] != 0).unwrap_or(true) {
1083                                        result_bsq[dim_base + pixel] = if is_diff_encoding {
1084                                            let prev_dim_base =
1085                                                prev_dim_base.ok_or(Error::InvalidBlob(
1086                                                    "diff encoding requires a previous dimension"
1087                                                        .into(),
1088                                                ))?;
1089                                            (result_bsq[prev_dim_base + pixel] + offset).min(z_max)
1090                                        } else {
1091                                            offset
1092                                        };
1093                                    }
1094                                }
1095                            }
1096                        } else {
1097                            block_buffer.fill(0.0);
1098                            let block_values = decode_bits(
1099                                cursor,
1100                                version,
1101                                &mut block_buffer,
1102                                Some(offset),
1103                                info.max_z_error,
1104                                z_max,
1105                            )?;
1106                            let mut src = 0usize;
1107                            for row in 0..this_block_height {
1108                                let pixel_row = block_y * micro_block_size + row;
1109                                for col in 0..this_block_width {
1110                                    let pixel =
1111                                        pixel_row * width + block_x * micro_block_size + col;
1112                                    if mask.map(|m| m[pixel] != 0).unwrap_or(true) {
1113                                        let value = block_buffer[src];
1114                                        result_bsq[dim_base + pixel] = if is_diff_encoding {
1115                                            let prev_dim_base =
1116                                                prev_dim_base.ok_or(Error::InvalidBlob(
1117                                                    "diff encoding requires a previous dimension"
1118                                                        .into(),
1119                                                ))?;
1120                                            value + result_bsq[prev_dim_base + pixel]
1121                                        } else {
1122                                            value
1123                                        };
1124                                        src += 1;
1125                                    }
1126                                }
1127                            }
1128                            if src != block_values {
1129                                return Err(Error::InvalidBlob(
1130                                    "bit-stuffed tile block value count does not match the block mask".into(),
1131                                ));
1132                            }
1133                        }
1134                    }
1135                    _ => {
1136                        return Err(Error::InvalidBlob(format!(
1137                            "invalid tile block encoding {block_encoding}"
1138                        )));
1139                    }
1140                }
1141            }
1142        }
1143    }
1144
1145    let values = if depth > 1 {
1146        swap_bsq_to_bip(&result_bsq, num_pixels, depth)
1147    } else {
1148        result_bsq
1149    };
1150    Ok(cast_pixels(info.data_type, values))
1151}
1152
1153fn decode_huffman(
1154    cursor: &mut Cursor<'_>,
1155    info: &BlobInfo,
1156    depth_ranges: Option<&DepthRanges>,
1157    mask: Option<&[u8]>,
1158    delta_encode: bool,
1159) -> Result<PixelData> {
1160    let width = info.width as usize;
1161    let height = info.height as usize;
1162    let depth = info.depth as usize;
1163    let num_pixels = width
1164        .checked_mul(height)
1165        .ok_or_else(|| Error::InvalidBlob("pixel count overflows usize".into()))?;
1166    let mut result_bsq = vec![0.0; info.sample_count()?];
1167
1168    let huffman = read_huffman_tree(cursor, info)?;
1169    let mut stream = HuffmanStream {
1170        words: &huffman.stuffed_data,
1171        src_ptr: huffman.src_ptr,
1172        bit_pos: huffman.bit_pos,
1173    };
1174    if stream.bit_pos > 0 {
1175        stream.src_ptr += 1;
1176        stream.bit_pos = 0;
1177    }
1178
1179    let offset = if info.data_type == DataType::I8 {
1180        128.0
1181    } else {
1182        0.0
1183    };
1184
1185    if depth < 2 || delta_encode {
1186        for dim in 0..depth {
1187            let dim_base = dim * num_pixels;
1188            let mut prev_value = 0.0;
1189            for row in 0..height {
1190                for col in 0..width {
1191                    let pixel = row * width + col;
1192                    if mask.map(|m| m[pixel] != 0).unwrap_or(true) {
1193                        let value = read_huffman_symbol(&mut stream, &huffman)? as f64 - offset;
1194                        if delta_encode {
1195                            let mut delta = value;
1196                            if col > 0 && mask.map(|m| m[pixel - 1] != 0).unwrap_or(true) {
1197                                delta += prev_value;
1198                            } else if row > 0 && mask.map(|m| m[pixel - width] != 0).unwrap_or(true)
1199                            {
1200                                delta += result_bsq[dim_base + pixel - width];
1201                            } else {
1202                                delta += prev_value;
1203                            }
1204                            let wrapped = ((delta as i64) & 0xFF) as f64;
1205                            result_bsq[dim_base + pixel] = wrapped;
1206                            prev_value = wrapped;
1207                        } else {
1208                            result_bsq[dim_base + pixel] = value;
1209                        }
1210                    }
1211                }
1212            }
1213        }
1214    } else {
1215        for row in 0..height {
1216            for col in 0..width {
1217                let pixel = row * width + col;
1218                if mask.map(|m| m[pixel] != 0).unwrap_or(true) {
1219                    for dim in 0..depth {
1220                        let dim_base = dim * num_pixels;
1221                        result_bsq[dim_base + pixel] =
1222                            read_huffman_symbol(&mut stream, &huffman)? as f64 - offset;
1223                    }
1224                }
1225            }
1226        }
1227    }
1228
1229    let values = if depth > 1 {
1230        swap_bsq_to_bip(&result_bsq, num_pixels, depth)
1231    } else {
1232        result_bsq
1233    };
1234
1235    let _ = depth_ranges;
1236    Ok(cast_pixels(info.data_type, values))
1237}
1238
1239fn read_huffman_tree(cursor: &mut Cursor<'_>, info: &BlobInfo) -> Result<HuffmanInfo> {
1240    let version = read_i32(cursor.read_bytes(4)?)?;
1241    if version < 2 {
1242        return Err(Error::UnsupportedFeature("Huffman version < 2"));
1243    }
1244    let size = read_i32(cursor.read_bytes(4)?)?;
1245    let i0 = read_i32(cursor.read_bytes(4)?)?;
1246    let i1 = read_i32(cursor.read_bytes(4)?)?;
1247    if size <= 0 || i0 < 0 || i1 <= i0 {
1248        return Err(Error::InvalidBlob("invalid Huffman table header".into()));
1249    }
1250
1251    let mut code_lengths = vec![
1252        0.0;
1253        usize::try_from(i1 - i0).map_err(|_| {
1254            Error::InvalidBlob("Huffman code length count does not fit in memory".into())
1255        })?
1256    ];
1257    let _ = decode_bits(
1258        cursor,
1259        match info.version {
1260            Version::Lerc2(version) => version,
1261            _ => unreachable!("Lerc2 Huffman decode called with a non-Lerc2 blob"),
1262        },
1263        &mut code_lengths,
1264        None,
1265        info.max_z_error,
1266        info.z_max,
1267    )?;
1268
1269    let size = size as usize;
1270    let i0 = i0 as usize;
1271    let i1 = i1 as usize;
1272    let mut code_table: Vec<Option<(u8, u32)>> = vec![None; size];
1273    for i in i0..i1 {
1274        let j = if i < size { i } else { i - size };
1275        code_table[j] = Some((code_lengths[i - i0] as u8, 0));
1276    }
1277
1278    let stuffed_data = words_from_padded(cursor.read_bytes(cursor.remaining())?);
1279    let mut stream = HuffmanStream {
1280        words: &stuffed_data,
1281        src_ptr: 0,
1282        bit_pos: 0,
1283    };
1284
1285    for entry in code_table.iter_mut().flatten() {
1286        if entry.0 > 0 {
1287            entry.1 = stream.peek_bits(entry.0 as usize)?;
1288            stream.advance(entry.0 as usize)?;
1289        }
1290    }
1291
1292    let mut max_bits = 0u8;
1293    for (bit_len, _) in code_table.iter().flatten() {
1294        max_bits = max_bits.max(*bit_len);
1295    }
1296    let quick_bits = cmp::min(max_bits, HUFFMAN_LUT_BITS_MAX);
1297    let mut quick_lut = vec![None; 1usize << quick_bits];
1298    let mut tree = HuffmanNode::default();
1299
1300    for (symbol, entry) in code_table.into_iter().enumerate() {
1301        let Some((bit_len, code)) = entry else {
1302            continue;
1303        };
1304        if bit_len == 0 {
1305            continue;
1306        }
1307        if bit_len <= quick_bits {
1308            let base = code << (quick_bits - bit_len);
1309            let num_entries = 1usize << (quick_bits - bit_len);
1310            for extra in 0..num_entries {
1311                quick_lut[(base as usize) | extra] = Some(HuffmanEntry {
1312                    bit_len,
1313                    value: symbol as u32,
1314                });
1315            }
1316        } else {
1317            insert_huffman_code(&mut tree, code, bit_len, symbol as u32)?;
1318        }
1319    }
1320
1321    let src_ptr = stream.src_ptr;
1322    let bit_pos = stream.bit_pos;
1323
1324    Ok(HuffmanInfo {
1325        quick_lut,
1326        quick_bits,
1327        max_bits,
1328        tree,
1329        stuffed_data,
1330        src_ptr,
1331        bit_pos,
1332    })
1333}
1334
1335fn insert_huffman_code(root: &mut HuffmanNode, code: u32, bit_len: u8, value: u32) -> Result<()> {
1336    let mut node = root;
1337    for shift in (0..bit_len).rev() {
1338        let bit = (code >> shift) & 1;
1339        if shift == 0 {
1340            let child = if bit == 0 {
1341                node.left
1342                    .get_or_insert_with(|| Box::new(HuffmanNode::default()))
1343            } else {
1344                node.right
1345                    .get_or_insert_with(|| Box::new(HuffmanNode::default()))
1346            };
1347            child.value = Some(value);
1348        } else {
1349            node = if bit == 0 {
1350                node.left
1351                    .get_or_insert_with(|| Box::new(HuffmanNode::default()))
1352            } else {
1353                node.right
1354                    .get_or_insert_with(|| Box::new(HuffmanNode::default()))
1355            };
1356        }
1357    }
1358    Ok(())
1359}
1360
1361fn read_huffman_symbol(stream: &mut HuffmanStream<'_>, info: &HuffmanInfo) -> Result<u32> {
1362    if let Some(entry) =
1363        info.quick_lut[stream.peek_bits(info.quick_bits as usize)? as usize].as_ref()
1364    {
1365        stream.advance(entry.bit_len as usize)?;
1366        return Ok(entry.value);
1367    }
1368
1369    let value = stream.peek_bits(info.max_bits as usize)?;
1370    let mut node = &info.tree;
1371    for used_bits in 0..info.max_bits {
1372        let bit = (value >> (info.max_bits - used_bits - 1)) & 1;
1373        node = if bit == 0 {
1374            node.left.as_deref().ok_or_else(|| {
1375                Error::InvalidBlob("Huffman decode walked a missing left node".into())
1376            })?
1377        } else {
1378            node.right.as_deref().ok_or_else(|| {
1379                Error::InvalidBlob("Huffman decode walked a missing right node".into())
1380            })?
1381        };
1382
1383        if node.left.is_none() && node.right.is_none() {
1384            let symbol = node
1385                .value
1386                .ok_or_else(|| Error::InvalidBlob("Huffman leaf is missing a symbol".into()))?;
1387            stream.advance((used_bits + 1) as usize)?;
1388            return Ok(symbol);
1389        }
1390    }
1391
1392    Err(Error::InvalidBlob(
1393        "Huffman symbol exceeded the configured code width".into(),
1394    ))
1395}
1396
1397fn decode_bits(
1398    cursor: &mut Cursor<'_>,
1399    version: u32,
1400    out: &mut [f64],
1401    offset: Option<f64>,
1402    max_z_error: f64,
1403    max_value: f64,
1404) -> Result<usize> {
1405    let header_byte = cursor.read_u8()?;
1406    let bits67 = header_byte >> 6;
1407    let count_bytes = if bits67 == 0 { 4 } else { 3 - bits67 as usize };
1408    let do_lut = (header_byte & 32) != 0;
1409    let num_bits = header_byte & 31;
1410
1411    let num_elements = match count_bytes {
1412        1 => cursor.read_u8()? as usize,
1413        2 => read_u16(cursor.read_bytes(2)?)? as usize,
1414        4 => cursor.read_u32()? as usize,
1415        _ => {
1416            return Err(Error::InvalidBlob(
1417                "invalid valid pixel count field width".into(),
1418            ))
1419        }
1420    };
1421    if num_elements > out.len() {
1422        return Err(Error::InvalidBlob(
1423            "bit-stuffed block expands beyond its output buffer".into(),
1424        ));
1425    }
1426
1427    if do_lut {
1428        let offset = offset.ok_or(Error::InvalidBlob(
1429            "LUT-compressed block is missing its base offset".into(),
1430        ))?;
1431        let lut_bytes = cursor.read_u8()? as usize;
1432        if lut_bytes == 0 {
1433            return Err(Error::InvalidBlob(
1434                "LUT-compressed block has zero LUT bytes".into(),
1435            ));
1436        }
1437        let lut_data_bytes = ((lut_bytes - 1) * usize::from(num_bits)).div_ceil(8);
1438        let lut_words = words_from_padded(cursor.read_bytes(lut_data_bytes)?);
1439        let lut_bits_per_element = bits_required(lut_bytes - 1);
1440        let stuffed_data_bytes = (num_elements * usize::from(lut_bits_per_element)).div_ceil(8);
1441        let stuffed_words = words_from_padded(cursor.read_bytes(stuffed_data_bytes)?);
1442
1443        let lut_values = if version >= 3 {
1444            unstuff_lut_v3(
1445                &lut_words,
1446                num_bits,
1447                lut_bytes - 1,
1448                offset,
1449                2.0 * max_z_error,
1450                max_value,
1451            )
1452        } else {
1453            unstuff_lut_v2(
1454                &lut_words,
1455                num_bits,
1456                lut_bytes - 1,
1457                offset,
1458                2.0 * max_z_error,
1459                max_value,
1460            )
1461        };
1462
1463        if version >= 3 {
1464            unstuff_v3(
1465                &stuffed_words,
1466                &mut out[..num_elements],
1467                lut_bits_per_element,
1468                UnstuffOptions {
1469                    num_pixels: num_elements,
1470                    lut_values: Some(&lut_values),
1471                    offset: None,
1472                    scale: 0.0,
1473                    max_value,
1474                },
1475            );
1476        } else {
1477            unstuff_v2(
1478                &stuffed_words,
1479                &mut out[..num_elements],
1480                lut_bits_per_element,
1481                UnstuffOptions {
1482                    num_pixels: num_elements,
1483                    lut_values: Some(&lut_values),
1484                    offset: None,
1485                    scale: 0.0,
1486                    max_value,
1487                },
1488            );
1489        }
1490        return Ok(num_elements);
1491    }
1492
1493    if num_bits == 0 {
1494        out[..num_elements].fill(offset.unwrap_or(0.0));
1495        return Ok(num_elements);
1496    }
1497
1498    let stuffed_data_bytes = (num_elements * usize::from(num_bits)).div_ceil(8);
1499    let stuffed_words = words_from_padded(cursor.read_bytes(stuffed_data_bytes)?);
1500    match (version >= 3, offset) {
1501        (true, Some(offset)) => unstuff_v3(
1502            &stuffed_words,
1503            &mut out[..num_elements],
1504            num_bits,
1505            UnstuffOptions {
1506                num_pixels: num_elements,
1507                lut_values: None,
1508                offset: Some(offset),
1509                scale: 2.0 * max_z_error,
1510                max_value,
1511            },
1512        ),
1513        (true, None) => original_unstuff_v3(&stuffed_words, &mut out[..num_elements], num_bits),
1514        (false, Some(offset)) => unstuff_v2(
1515            &stuffed_words,
1516            &mut out[..num_elements],
1517            num_bits,
1518            UnstuffOptions {
1519                num_pixels: num_elements,
1520                lut_values: None,
1521                offset: Some(offset),
1522                scale: 2.0 * max_z_error,
1523                max_value,
1524            },
1525        ),
1526        (false, None) => original_unstuff_v2(&stuffed_words, &mut out[..num_elements], num_bits),
1527    }
1528    Ok(num_elements)
1529}
1530
1531fn unstuff_v2(src: &[u32], dest: &mut [f64], bits_per_pixel: u8, options: UnstuffOptions<'_>) {
1532    let bit_mask = if bits_per_pixel == 32 {
1533        u32::MAX
1534    } else {
1535        (1u32 << bits_per_pixel) - 1
1536    };
1537    let mut words = src.to_vec();
1538    let num_invalid_tail_bytes =
1539        words.len() * 4 - (usize::from(bits_per_pixel) * options.num_pixels).div_ceil(8);
1540    if let Some(last) = words.last_mut() {
1541        *last <<= 8 * num_invalid_tail_bytes;
1542    }
1543
1544    let mut index = 0usize;
1545    let mut bits_left = 0usize;
1546    let mut buffer = 0u32;
1547    let nmax = options
1548        .offset
1549        .map(|offset| quantized_nmax(offset, options.scale, options.max_value));
1550
1551    for item in dest.iter_mut().take(options.num_pixels) {
1552        if bits_left == 0 {
1553            buffer = words[index];
1554            index += 1;
1555            bits_left = 32;
1556        }
1557        let n = if bits_left >= bits_per_pixel as usize {
1558            let n = (buffer >> (bits_left - bits_per_pixel as usize)) & bit_mask;
1559            bits_left -= bits_per_pixel as usize;
1560            n
1561        } else {
1562            let missing_bits = bits_per_pixel as usize - bits_left;
1563            let mut n = ((buffer & bit_mask) << missing_bits) & bit_mask;
1564            buffer = words[index];
1565            index += 1;
1566            bits_left = 32 - missing_bits;
1567            n += buffer >> bits_left;
1568            n
1569        };
1570        *item = match (options.lut_values, options.offset) {
1571            (Some(lut_values), _) => lut_values[n as usize],
1572            (None, Some(offset)) => {
1573                quantized_value(offset, options.scale, options.max_value, n, nmax.unwrap())
1574            }
1575            (None, None) => n as f64,
1576        };
1577    }
1578}
1579
1580fn unstuff_v3(src: &[u32], dest: &mut [f64], bits_per_pixel: u8, options: UnstuffOptions<'_>) {
1581    let bit_mask = if bits_per_pixel == 32 {
1582        u32::MAX
1583    } else {
1584        (1u32 << bits_per_pixel) - 1
1585    };
1586    let mut index = 0usize;
1587    let mut bits_left = 0usize;
1588    let mut bit_pos = 0usize;
1589    let mut buffer = 0u32;
1590    let nmax = options
1591        .offset
1592        .map(|offset| quantized_nmax(offset, options.scale, options.max_value));
1593
1594    for item in dest.iter_mut().take(options.num_pixels) {
1595        if bits_left == 0 {
1596            buffer = src[index];
1597            index += 1;
1598            bits_left = 32;
1599            bit_pos = 0;
1600        }
1601        let n = if bits_left >= bits_per_pixel as usize {
1602            let n = (buffer >> bit_pos) & bit_mask;
1603            bits_left -= bits_per_pixel as usize;
1604            bit_pos += bits_per_pixel as usize;
1605            n
1606        } else {
1607            let missing_bits = bits_per_pixel as usize - bits_left;
1608            let mut n = (buffer >> bit_pos) & bit_mask;
1609            buffer = src[index];
1610            index += 1;
1611            bits_left = 32 - missing_bits;
1612            n |=
1613                (buffer & ((1u32 << missing_bits) - 1)) << (bits_per_pixel as usize - missing_bits);
1614            bit_pos = missing_bits;
1615            n
1616        };
1617        *item = match (options.lut_values, options.offset) {
1618            (Some(lut_values), _) => lut_values[n as usize],
1619            (None, Some(offset)) => {
1620                quantized_value(offset, options.scale, options.max_value, n, nmax.unwrap())
1621            }
1622            (None, None) => n as f64,
1623        };
1624    }
1625}
1626
1627fn original_unstuff_v2(src: &[u32], dest: &mut [f64], bits_per_pixel: u8) {
1628    unstuff_v2(
1629        src,
1630        dest,
1631        bits_per_pixel,
1632        UnstuffOptions {
1633            num_pixels: dest.len(),
1634            lut_values: None,
1635            offset: None,
1636            scale: 0.0,
1637            max_value: 0.0,
1638        },
1639    );
1640}
1641
1642fn original_unstuff_v3(src: &[u32], dest: &mut [f64], bits_per_pixel: u8) {
1643    unstuff_v3(
1644        src,
1645        dest,
1646        bits_per_pixel,
1647        UnstuffOptions {
1648            num_pixels: dest.len(),
1649            lut_values: None,
1650            offset: None,
1651            scale: 0.0,
1652            max_value: 0.0,
1653        },
1654    );
1655}
1656
1657fn unstuff_lut_v2(
1658    src: &[u32],
1659    bits_per_pixel: u8,
1660    num_pixels: usize,
1661    offset: f64,
1662    scale: f64,
1663    max_value: f64,
1664) -> Vec<f64> {
1665    let mut values = vec![0.0; num_pixels];
1666    unstuff_v2(
1667        src,
1668        &mut values,
1669        bits_per_pixel,
1670        UnstuffOptions {
1671            num_pixels,
1672            lut_values: None,
1673            offset: Some(offset),
1674            scale,
1675            max_value,
1676        },
1677    );
1678    let mut out = Vec::with_capacity(values.len() + 1);
1679    out.push(offset);
1680    out.extend(values);
1681    out
1682}
1683
1684fn unstuff_lut_v3(
1685    src: &[u32],
1686    bits_per_pixel: u8,
1687    num_pixels: usize,
1688    offset: f64,
1689    scale: f64,
1690    max_value: f64,
1691) -> Vec<f64> {
1692    let mut values = vec![0.0; num_pixels];
1693    unstuff_v3(
1694        src,
1695        &mut values,
1696        bits_per_pixel,
1697        UnstuffOptions {
1698            num_pixels,
1699            lut_values: None,
1700            offset: Some(offset),
1701            scale,
1702            max_value,
1703        },
1704    );
1705    let mut out = Vec::with_capacity(values.len() + 1);
1706    out.push(offset);
1707    out.extend(values);
1708    out
1709}
1710
1711fn quantized_nmax(offset: f64, scale: f64, max_value: f64) -> f64 {
1712    if scale == 0.0 {
1713        f64::INFINITY
1714    } else {
1715        ((max_value - offset) / scale).ceil()
1716    }
1717}
1718
1719fn quantized_value(offset: f64, scale: f64, max_value: f64, n: u32, nmax: f64) -> f64 {
1720    if (n as f64) < nmax {
1721        offset + (n as f64) * scale
1722    } else {
1723        max_value
1724    }
1725}
1726
1727fn data_type_used(data_type: DataType, tc: u8) -> Result<DataType> {
1728    let code = match data_type.code() {
1729        2 | 4 => i32::from(data_type.code()) - i32::from(tc),
1730        3 | 5 => i32::from(data_type.code()) - 2 * i32::from(tc),
1731        6 => {
1732            if tc == 0 {
1733                6
1734            } else if tc == 1 {
1735                2
1736            } else {
1737                1
1738            }
1739        }
1740        7 => {
1741            if tc == 0 {
1742                7
1743            } else {
1744                7 - 2 * i32::from(tc) + 1
1745            }
1746        }
1747        _ => i32::from(data_type.code()),
1748    };
1749    DataType::from_code(code)
1750}
1751
1752fn bits_required(max_index: usize) -> u8 {
1753    let mut bits = 0u8;
1754    let mut value = max_index;
1755    while value > 0 {
1756        bits += 1;
1757        value >>= 1;
1758    }
1759    bits
1760}
1761
1762fn words_from_padded(bytes: &[u8]) -> Vec<u32> {
1763    if bytes.is_empty() {
1764        return Vec::new();
1765    }
1766    let mut padded = vec![0u8; bytes.len().div_ceil(4) * 4];
1767    padded[..bytes.len()].copy_from_slice(bytes);
1768    padded
1769        .chunks_exact(4)
1770        .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
1771        .collect()
1772}
1773
1774impl<'a> HuffmanStream<'a> {
1775    fn peek_bits(&self, num_bits: usize) -> Result<u32> {
1776        if num_bits == 0 {
1777            return Ok(0);
1778        }
1779        if self.src_ptr >= self.words.len() {
1780            return Err(Error::Truncated {
1781                offset: self.src_ptr * 4,
1782                needed: 4,
1783                available: 0,
1784            });
1785        }
1786        let word = self.words[self.src_ptr];
1787        let mut value = word.wrapping_shl(self.bit_pos as u32) >> (32 - num_bits as u32);
1788        if 32 - (self.bit_pos as usize) < num_bits {
1789            let next = self.words.get(self.src_ptr + 1).copied().unwrap_or(0);
1790            value |= next >> (64 - self.bit_pos as usize - num_bits);
1791        }
1792        Ok(value)
1793    }
1794
1795    fn advance(&mut self, bits: usize) -> Result<()> {
1796        let total = self.bit_pos as usize + bits;
1797        self.src_ptr = self
1798            .src_ptr
1799            .checked_add(total / 32)
1800            .ok_or_else(|| Error::InvalidBlob("Huffman bitstream pointer overflow".into()))?;
1801        self.bit_pos = (total % 32) as u8;
1802        Ok(())
1803    }
1804}
1805
1806fn count_valid_in_block(
1807    mask: &[u8],
1808    width: usize,
1809    x0: usize,
1810    y0: usize,
1811    block_width: usize,
1812    block_height: usize,
1813) -> usize {
1814    let mut count = 0usize;
1815    for row in 0..block_height {
1816        let row_offset = (y0 + row) * width + x0;
1817        for col in 0..block_width {
1818            count += usize::from(mask[row_offset + col] != 0);
1819        }
1820    }
1821    count
1822}
1823
1824fn read_typed_values(bytes: &[u8], data_type: DataType) -> Result<Vec<f64>> {
1825    let mut out = Vec::with_capacity(bytes.len() / data_type.byte_len());
1826    let mut cursor = Cursor::new(bytes);
1827    while cursor.offset() < bytes.len() {
1828        out.push(read_scalar(
1829            cursor.read_bytes(data_type.byte_len())?,
1830            data_type,
1831        )?);
1832    }
1833    Ok(out)
1834}
1835
1836fn read_scalar(bytes: &[u8], data_type: DataType) -> Result<f64> {
1837    Ok(match data_type {
1838        DataType::I8 => i8::from_le_bytes([bytes[0]]) as f64,
1839        DataType::U8 => bytes[0] as f64,
1840        DataType::I16 => i16::from_le_bytes(bytes.try_into().unwrap()) as f64,
1841        DataType::U16 => u16::from_le_bytes(bytes.try_into().unwrap()) as f64,
1842        DataType::I32 => i32::from_le_bytes(bytes.try_into().unwrap()) as f64,
1843        DataType::U32 => u32::from_le_bytes(bytes.try_into().unwrap()) as f64,
1844        DataType::F32 => f32::from_le_bytes(bytes.try_into().unwrap()) as f64,
1845        DataType::F64 => f64::from_le_bytes(bytes.try_into().unwrap()),
1846    })
1847}
1848
1849fn read_u16(bytes: &[u8]) -> Result<u16> {
1850    Ok(u16::from_le_bytes(bytes.try_into().unwrap()))
1851}
1852
1853fn read_i32(bytes: &[u8]) -> Result<i32> {
1854    Ok(i32::from_le_bytes(bytes.try_into().unwrap()))
1855}
1856
1857fn swap_bsq_to_bip(values: &[f64], num_pixels: usize, depth: usize) -> Vec<f64> {
1858    let mut out = vec![0.0; values.len()];
1859    let mut dest = 0usize;
1860    for pixel in 0..num_pixels {
1861        let mut src = pixel;
1862        for _ in 0..depth {
1863            out[dest] = values[src];
1864            dest += 1;
1865            src += num_pixels;
1866        }
1867    }
1868    out
1869}
1870
1871enum ConstantValues {
1872    Single(f64),
1873    PerDepth(Vec<f64>),
1874}
1875
1876fn constant_pixels(
1877    data_type: DataType,
1878    num_pixels: usize,
1879    depth: usize,
1880    mask: Option<&[u8]>,
1881    values: ConstantValues,
1882) -> PixelData {
1883    let total = num_pixels * depth;
1884    let mut out = vec![0.0; total];
1885    match (depth, values) {
1886        (_, ConstantValues::Single(value)) => {
1887            if let Some(mask) = mask {
1888                for (pixel, &mask_value) in mask.iter().enumerate().take(num_pixels) {
1889                    if mask_value != 0 {
1890                        let base = pixel * depth;
1891                        out[base..base + depth].fill(value);
1892                    }
1893                }
1894            } else {
1895                out.fill(value);
1896            }
1897        }
1898        (depth, ConstantValues::PerDepth(values)) => {
1899            if let Some(mask) = mask {
1900                for (pixel, &mask_value) in mask.iter().enumerate().take(num_pixels) {
1901                    if mask_value != 0 {
1902                        let base = pixel * depth;
1903                        out[base..base + depth].copy_from_slice(&values);
1904                    }
1905                }
1906            } else {
1907                for pixel in 0..num_pixels {
1908                    let base = pixel * depth;
1909                    out[base..base + depth].copy_from_slice(&values);
1910                }
1911            }
1912        }
1913    }
1914    cast_pixels(data_type, out)
1915}
1916
1917fn scatter_valid_samples(
1918    data_type: DataType,
1919    total_samples: usize,
1920    depth: usize,
1921    mask: &[u8],
1922    valid_samples: &[f64],
1923) -> Result<PixelData> {
1924    let mut out = vec![0.0; total_samples];
1925    let mut src = 0usize;
1926    for (pixel, &is_valid) in mask.iter().enumerate() {
1927        if is_valid == 0 {
1928            continue;
1929        }
1930        let base = pixel * depth;
1931        let end = base + depth;
1932        let src_end = src + depth;
1933        if src_end > valid_samples.len() {
1934            return Err(Error::InvalidBlob(
1935                "one-sweep valid sample payload is too short".into(),
1936            ));
1937        }
1938        out[base..end].copy_from_slice(&valid_samples[src..src_end]);
1939        src = src_end;
1940    }
1941    if src != valid_samples.len() {
1942        return Err(Error::InvalidBlob(
1943            "one-sweep valid sample payload contains trailing values".into(),
1944        ));
1945    }
1946    Ok(cast_pixels(data_type, out))
1947}
1948
1949fn zeroed_pixels(data_type: DataType, total_samples: usize) -> PixelData {
1950    match data_type {
1951        DataType::I8 => PixelData::I8(vec![0; total_samples]),
1952        DataType::U8 => PixelData::U8(vec![0; total_samples]),
1953        DataType::I16 => PixelData::I16(vec![0; total_samples]),
1954        DataType::U16 => PixelData::U16(vec![0; total_samples]),
1955        DataType::I32 => PixelData::I32(vec![0; total_samples]),
1956        DataType::U32 => PixelData::U32(vec![0; total_samples]),
1957        DataType::F32 => PixelData::F32(vec![0.0; total_samples]),
1958        DataType::F64 => PixelData::F64(vec![0.0; total_samples]),
1959    }
1960}
1961
1962fn cast_pixels(data_type: DataType, values: Vec<f64>) -> PixelData {
1963    match data_type {
1964        DataType::I8 => PixelData::I8(values.into_iter().map(|x| x as i8).collect()),
1965        DataType::U8 => PixelData::U8(values.into_iter().map(|x| x as u8).collect()),
1966        DataType::I16 => PixelData::I16(values.into_iter().map(|x| x as i16).collect()),
1967        DataType::U16 => PixelData::U16(values.into_iter().map(|x| x as u16).collect()),
1968        DataType::I32 => PixelData::I32(values.into_iter().map(|x| x as i32).collect()),
1969        DataType::U32 => PixelData::U32(values.into_iter().map(|x| x as u32).collect()),
1970        DataType::F32 => PixelData::F32(values.into_iter().map(|x| x as f32).collect()),
1971        DataType::F64 => PixelData::F64(values),
1972    }
1973}
1974
1975fn scan_range(pixels: &PixelData, mask: Option<&[u8]>) -> Result<(f64, f64)> {
1976    let values = pixels.to_f64();
1977    let mut min_value = f64::INFINITY;
1978    let mut max_value = f64::NEG_INFINITY;
1979
1980    for (index, value) in values.into_iter().enumerate() {
1981        if mask.map(|mask| mask[index] == 0).unwrap_or(false) {
1982            continue;
1983        }
1984        min_value = min_value.min(value);
1985        max_value = max_value.max(value);
1986    }
1987
1988    if !min_value.is_finite() || !max_value.is_finite() {
1989        return Err(Error::InvalidBlob(
1990            "cannot compute a value range for an empty LERC pixel buffer".into(),
1991        ));
1992    }
1993
1994    Ok((min_value, max_value))
1995}
1996
1997fn fletcher32(bytes: &[u8]) -> u32 {
1998    let mut sum1 = 0xffffu32;
1999    let mut sum2 = 0xffffu32;
2000    let mut words = bytes.len() / 2;
2001    let mut index = 0usize;
2002
2003    while words > 0 {
2004        let chunk = words.min(359);
2005        words -= chunk;
2006        for _ in 0..chunk {
2007            sum1 += (bytes[index] as u32) << 8;
2008            index += 1;
2009            sum2 += sum1 + bytes[index] as u32;
2010            sum1 += bytes[index] as u32;
2011            index += 1;
2012        }
2013        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
2014        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
2015    }
2016
2017    if bytes.len() & 1 != 0 {
2018        sum1 += (bytes[index] as u32) << 8;
2019        sum2 += sum1;
2020    }
2021
2022    sum1 = (sum1 & 0xffff) + (sum1 >> 16);
2023    sum2 = (sum2 & 0xffff) + (sum2 >> 16);
2024    (sum2 << 16) | (sum1 & 0xffff)
2025}
2026
2027#[cfg(test)]
2028mod tests {
2029    use super::*;
2030    use ndarray::IxDyn;
2031
2032    #[allow(clippy::too_many_arguments)]
2033    fn build_header_v2(
2034        width: u32,
2035        height: u32,
2036        valid_pixel_count: u32,
2037        image_type: i32,
2038        max_z_error: f64,
2039        z_min: f64,
2040        z_max: f64,
2041        payload_len: usize,
2042    ) -> Vec<u8> {
2043        let blob_size = 58 + 4 + payload_len;
2044        let mut bytes = Vec::with_capacity(blob_size);
2045        bytes.extend_from_slice(MAGIC_LERC2);
2046        bytes.extend_from_slice(&2i32.to_le_bytes());
2047        bytes.extend_from_slice(&height.to_le_bytes());
2048        bytes.extend_from_slice(&width.to_le_bytes());
2049        bytes.extend_from_slice(&valid_pixel_count.to_le_bytes());
2050        bytes.extend_from_slice(&8i32.to_le_bytes());
2051        bytes.extend_from_slice(&(blob_size as i32).to_le_bytes());
2052        bytes.extend_from_slice(&image_type.to_le_bytes());
2053        bytes.extend_from_slice(&max_z_error.to_le_bytes());
2054        bytes.extend_from_slice(&z_min.to_le_bytes());
2055        bytes.extend_from_slice(&z_max.to_le_bytes());
2056        bytes
2057    }
2058
2059    fn finalize_v4_with_checksum(mut bytes: Vec<u8>) -> Vec<u8> {
2060        let blob_size = bytes.len() as i32;
2061        bytes[34..38].copy_from_slice(&blob_size.to_le_bytes());
2062        let checksum = fletcher32(&bytes[14..blob_size as usize]);
2063        bytes[10..14].copy_from_slice(&checksum.to_le_bytes());
2064        bytes
2065    }
2066
2067    fn encode_mask_rle(mask: &[u8]) -> Vec<u8> {
2068        let bitset_len = mask.len().div_ceil(8);
2069        let mut bitset = vec![0u8; bitset_len];
2070        for (index, &value) in mask.iter().enumerate() {
2071            if value != 0 {
2072                bitset[index >> 3] |= 1 << (7 - (index & 7));
2073            }
2074        }
2075
2076        let mut encoded = Vec::with_capacity(bitset_len + 4);
2077        encoded.extend_from_slice(&(bitset_len as i16).to_le_bytes());
2078        encoded.extend_from_slice(&bitset);
2079        encoded.extend_from_slice(&i16::MIN.to_le_bytes());
2080        encoded
2081    }
2082
2083    fn pack_msb_bits(values: &[u32], bits_per_pixel: u8) -> Vec<u8> {
2084        let total_bits = values.len() * usize::from(bits_per_pixel);
2085        let mut bytes = vec![0u8; total_bits.div_ceil(8)];
2086        let mut bit_offset = 0usize;
2087        for &value in values {
2088            for bit in (0..bits_per_pixel).rev() {
2089                if ((value >> bit) & 1) != 0 {
2090                    let byte_index = bit_offset / 8;
2091                    let bit_index = 7 - (bit_offset % 8);
2092                    bytes[byte_index] |= 1 << bit_index;
2093                }
2094                bit_offset += 1;
2095            }
2096        }
2097        bytes
2098    }
2099
2100    fn build_lerc1_blob(
2101        include_mask_header: bool,
2102        mask: Option<&[u8]>,
2103        values: &[f32],
2104        max_z_error: f64,
2105        max_value: f32,
2106    ) -> Vec<u8> {
2107        let width = 2u32;
2108        let height = 2u32;
2109        let mut bytes = Vec::new();
2110        bytes.extend_from_slice(b"CntZImage ");
2111        bytes.extend_from_slice(&11i32.to_le_bytes());
2112        bytes.extend_from_slice(&0i32.to_le_bytes());
2113        bytes.extend_from_slice(&height.to_le_bytes());
2114        bytes.extend_from_slice(&width.to_le_bytes());
2115        bytes.extend_from_slice(&max_z_error.to_le_bytes());
2116
2117        if include_mask_header {
2118            if let Some(mask) = mask {
2119                let encoded_mask = encode_mask_rle(mask);
2120                bytes.extend_from_slice(&1u32.to_le_bytes());
2121                bytes.extend_from_slice(&1u32.to_le_bytes());
2122                bytes.extend_from_slice(&(encoded_mask.len() as u32).to_le_bytes());
2123                bytes.extend_from_slice(&1.0f32.to_le_bytes());
2124                bytes.extend_from_slice(&encoded_mask);
2125            } else {
2126                bytes.extend_from_slice(&1u32.to_le_bytes());
2127                bytes.extend_from_slice(&1u32.to_le_bytes());
2128                bytes.extend_from_slice(&0u32.to_le_bytes());
2129                bytes.extend_from_slice(&1.0f32.to_le_bytes());
2130            }
2131        }
2132
2133        let valid_count = mask
2134            .map(|mask| mask.iter().filter(|&&value| value != 0).count())
2135            .unwrap_or(values.len());
2136        let offset = values.iter().copied().reduce(f32::min).unwrap_or(0.0);
2137        let quantized: Vec<u32> = values
2138            .iter()
2139            .map(|&value| ((value - offset) / (2.0 * max_z_error as f32)).round() as u32)
2140            .collect();
2141        let max_quantized = quantized.iter().copied().max().unwrap_or(0);
2142        let bits_per_pixel = bits_required(max_quantized as usize).max(1);
2143        let payload = pack_msb_bits(&quantized, bits_per_pixel);
2144        let pixel_section_len = 1 + 4 + 1 + 1 + payload.len();
2145        bytes.extend_from_slice(&1u32.to_le_bytes());
2146        bytes.extend_from_slice(&1u32.to_le_bytes());
2147        bytes.extend_from_slice(&(pixel_section_len as u32).to_le_bytes());
2148        bytes.extend_from_slice(&max_value.to_le_bytes());
2149        bytes.push(1);
2150        bytes.extend_from_slice(&offset.to_le_bytes());
2151        bytes.push((bits_per_pixel & 63) | (2 << 6));
2152        bytes.push(valid_count as u8);
2153        bytes.extend_from_slice(&payload);
2154        bytes
2155    }
2156
2157    #[test]
2158    fn reads_blob_info_for_constant_lerc2() {
2159        let mut blob = build_header_v2(3, 2, 6, 3, 0.0, 7.0, 7.0, 0);
2160        blob.extend_from_slice(&0u32.to_le_bytes());
2161
2162        let info = get_blob_info(&blob).unwrap();
2163        assert_eq!(info.width, 3);
2164        assert_eq!(info.height, 2);
2165        assert_eq!(info.depth, 1);
2166        assert_eq!(info.data_type, DataType::U16);
2167        assert_eq!(info.blob_size, blob.len());
2168    }
2169
2170    #[test]
2171    fn decodes_constant_surface() {
2172        let mut blob = build_header_v2(2, 2, 4, 1, 0.0, 9.0, 9.0, 0);
2173        blob.extend_from_slice(&0u32.to_le_bytes());
2174
2175        let decoded = decode(&blob).unwrap();
2176        assert_eq!(decoded.mask, None);
2177        assert_eq!(decoded.pixels, PixelData::U8(vec![9, 9, 9, 9]));
2178    }
2179
2180    #[test]
2181    fn decodes_one_sweep_all_valid() {
2182        let mut blob = build_header_v2(2, 2, 4, 1, 0.0, 1.0, 4.0, 1 + 4);
2183        blob.extend_from_slice(&0u32.to_le_bytes());
2184        blob.push(1);
2185        blob.extend_from_slice(&[1, 2, 3, 4]);
2186
2187        let decoded = decode(&blob).unwrap();
2188        assert_eq!(decoded.pixels, PixelData::U8(vec![1, 2, 3, 4]));
2189    }
2190
2191    #[test]
2192    fn decodes_constant_surface_with_per_depth_ranges() {
2193        let mut bytes = Vec::new();
2194        bytes.extend_from_slice(MAGIC_LERC2);
2195        bytes.extend_from_slice(&4i32.to_le_bytes());
2196        bytes.extend_from_slice(&0u32.to_le_bytes());
2197        bytes.extend_from_slice(&1u32.to_le_bytes());
2198        bytes.extend_from_slice(&2u32.to_le_bytes());
2199        bytes.extend_from_slice(&2u32.to_le_bytes());
2200        bytes.extend_from_slice(&2u32.to_le_bytes());
2201        bytes.extend_from_slice(&8i32.to_le_bytes());
2202        bytes.extend_from_slice(&0i32.to_le_bytes());
2203        bytes.extend_from_slice(&6i32.to_le_bytes());
2204        bytes.extend_from_slice(&0.0f64.to_le_bytes());
2205        bytes.extend_from_slice(&10.0f64.to_le_bytes());
2206        bytes.extend_from_slice(&20.0f64.to_le_bytes());
2207        bytes.extend_from_slice(&0u32.to_le_bytes());
2208        bytes.extend_from_slice(&10.0f32.to_le_bytes());
2209        bytes.extend_from_slice(&20.0f32.to_le_bytes());
2210        bytes.extend_from_slice(&10.0f32.to_le_bytes());
2211        bytes.extend_from_slice(&20.0f32.to_le_bytes());
2212
2213        let blob = finalize_v4_with_checksum(bytes);
2214        let decoded = decode(&blob).unwrap();
2215        assert_eq!(decoded.pixels, PixelData::F32(vec![10.0, 20.0, 10.0, 20.0]));
2216    }
2217
2218    #[test]
2219    fn counts_concatenated_bands() {
2220        let mut blob1 = build_header_v2(1, 1, 1, 1, 0.0, 3.0, 3.0, 0);
2221        blob1.extend_from_slice(&0u32.to_le_bytes());
2222        let mut blob2 = build_header_v2(1, 1, 1, 1, 0.0, 4.0, 4.0, 0);
2223        blob2.extend_from_slice(&0u32.to_le_bytes());
2224
2225        let mut merged = blob1;
2226        merged.extend_from_slice(&blob2);
2227        assert_eq!(get_band_count(&merged).unwrap(), 2);
2228    }
2229
2230    #[test]
2231    fn reads_blob_info_for_lerc1() {
2232        let blob = build_lerc1_blob(true, None, &[1.0, 2.0, 3.0, 4.0], 0.5, 4.0);
2233
2234        let info = get_blob_info(&blob).unwrap();
2235        assert_eq!(info.version, Version::Lerc1(11));
2236        assert_eq!(info.width, 2);
2237        assert_eq!(info.height, 2);
2238        assert_eq!(info.depth, 1);
2239        assert_eq!(info.data_type, DataType::F32);
2240    }
2241
2242    #[test]
2243    fn decodes_lerc1_masked_bitstuffed_block() {
2244        let blob = build_lerc1_blob(true, Some(&[1, 0, 1, 1]), &[1.0, 3.0, 4.0], 0.5, 4.0);
2245
2246        let decoded = decode(&blob).unwrap();
2247        assert_eq!(decoded.mask, Some(vec![1, 0, 1, 1]));
2248        assert_eq!(decoded.pixels, PixelData::F32(vec![1.0, 0.0, 3.0, 4.0]));
2249        assert_eq!(decoded.info.z_min, 1.0);
2250        assert_eq!(decoded.info.z_max, 4.0);
2251    }
2252
2253    #[test]
2254    fn counts_concatenated_lerc1_bands_with_shared_mask() {
2255        let blob1 = build_lerc1_blob(true, Some(&[1, 0, 1, 1]), &[1.0, 3.0, 4.0], 0.5, 4.0);
2256        let blob2 = build_lerc1_blob(false, None, &[1.0, 3.0, 4.0], 0.5, 4.0);
2257        let mut merged = blob1;
2258        merged.extend_from_slice(&blob2);
2259
2260        assert_eq!(get_band_count(&merged).unwrap(), 2);
2261    }
2262
2263    #[test]
2264    fn decodes_lerc2_to_ndarray() {
2265        let mut blob = build_header_v2(2, 2, 4, 1, 0.0, 1.0, 4.0, 1 + 4);
2266        blob.extend_from_slice(&0u32.to_le_bytes());
2267        blob.push(1);
2268        blob.extend_from_slice(&[1, 2, 3, 4]);
2269
2270        let array = decode_ndarray::<u8>(&blob).unwrap();
2271        assert_eq!(array.shape(), &[2, 2]);
2272        assert_eq!(array[IxDyn(&[0, 0])], 1);
2273        assert_eq!(array[IxDyn(&[0, 1])], 2);
2274        assert_eq!(array[IxDyn(&[1, 0])], 3);
2275        assert_eq!(array[IxDyn(&[1, 1])], 4);
2276    }
2277
2278    #[test]
2279    fn decodes_multidimensional_lerc2_to_f64_ndarray() {
2280        let mut bytes = Vec::new();
2281        bytes.extend_from_slice(MAGIC_LERC2);
2282        bytes.extend_from_slice(&4i32.to_le_bytes());
2283        bytes.extend_from_slice(&0u32.to_le_bytes());
2284        bytes.extend_from_slice(&1u32.to_le_bytes());
2285        bytes.extend_from_slice(&2u32.to_le_bytes());
2286        bytes.extend_from_slice(&2u32.to_le_bytes());
2287        bytes.extend_from_slice(&2u32.to_le_bytes());
2288        bytes.extend_from_slice(&8i32.to_le_bytes());
2289        bytes.extend_from_slice(&0i32.to_le_bytes());
2290        bytes.extend_from_slice(&6i32.to_le_bytes());
2291        bytes.extend_from_slice(&0.0f64.to_le_bytes());
2292        bytes.extend_from_slice(&10.0f64.to_le_bytes());
2293        bytes.extend_from_slice(&20.0f64.to_le_bytes());
2294        bytes.extend_from_slice(&0u32.to_le_bytes());
2295        bytes.extend_from_slice(&10.0f32.to_le_bytes());
2296        bytes.extend_from_slice(&20.0f32.to_le_bytes());
2297        bytes.extend_from_slice(&10.0f32.to_le_bytes());
2298        bytes.extend_from_slice(&20.0f32.to_le_bytes());
2299
2300        let blob = finalize_v4_with_checksum(bytes);
2301        let array = decode_ndarray_f64(&blob).unwrap();
2302        assert_eq!(array.shape(), &[1, 2, 2]);
2303        assert_eq!(array[IxDyn(&[0, 0, 0])], 10.0);
2304        assert_eq!(array[IxDyn(&[0, 0, 1])], 20.0);
2305        assert_eq!(array[IxDyn(&[0, 1, 0])], 10.0);
2306        assert_eq!(array[IxDyn(&[0, 1, 1])], 20.0);
2307    }
2308
2309    #[test]
2310    fn decodes_lerc1_mask_to_ndarray() {
2311        let blob = build_lerc1_blob(true, Some(&[1, 0, 1, 1]), &[1.0, 3.0, 4.0], 0.5, 4.0);
2312
2313        let mask = decode_mask_ndarray(&blob).unwrap().unwrap();
2314        assert_eq!(mask.shape(), &[2, 2]);
2315        assert_eq!(mask[IxDyn(&[0, 0])], 1);
2316        assert_eq!(mask[IxDyn(&[0, 1])], 0);
2317        assert_eq!(mask[IxDyn(&[1, 0])], 1);
2318        assert_eq!(mask[IxDyn(&[1, 1])], 1);
2319    }
2320}