Skip to main content

lerc_writer/
lerc2.rs

1use std::cmp::Reverse;
2use std::collections::BinaryHeap;
3
4use lerc_core::{
5    bits_required, fletcher32, BandSetView, DataType, Error, MaskView, RasterView, Result, Sample,
6};
7
8const MAGIC_LERC2: &[u8; 6] = b"Lerc2 ";
9const VERSION_4: i32 = 4;
10const VERSION_5: i32 = 5;
11const VERSION_6: i32 = 6;
12const FIXED_HEADER_LEN_V4_V5: usize = 66;
13const FIXED_HEADER_LEN_V6: usize = 90;
14const MASK_COUNT_LEN: usize = 4;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct EncodeOptions {
18    pub max_z_error: f64,
19    pub micro_block_size: u32,
20    pub no_data_value: Option<f64>,
21}
22
23impl Default for EncodeOptions {
24    fn default() -> Self {
25        Self {
26            max_z_error: 0.0,
27            micro_block_size: 8,
28            no_data_value: None,
29        }
30    }
31}
32
33#[derive(Debug, Clone)]
34struct RasterAnalysis {
35    data_type: DataType,
36    width: u32,
37    height: u32,
38    depth: u32,
39    valid_pixel_count: u32,
40    max_z_error: f64,
41    micro_block_size: u32,
42    z_min: f64,
43    z_max: f64,
44    no_data_value: Option<f64>,
45    min_values: Option<Vec<f64>>,
46    max_values: Option<Vec<f64>>,
47    plan: EncodePlan,
48}
49
50#[derive(Debug, Clone)]
51struct EncodePlan {
52    version: i32,
53    body: BodyPlan,
54}
55
56#[derive(Debug, Clone)]
57enum BodyPlan {
58    Constant,
59    PerDepthConstant,
60    OneSweep,
61    Tiled(TilingPlan),
62    Huffman(HuffmanPlan),
63}
64
65#[derive(Debug, Clone)]
66struct HuffmanPlan {
67    mode: HuffmanMode,
68    table_bytes: Vec<u8>,
69    codes: Vec<Option<HuffmanCode>>,
70    data_len: usize,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74enum HuffmanMode {
75    Delta = 1,
76    Plain = 2,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80struct HuffmanCode {
81    bit_len: u8,
82    bits: u32,
83}
84
85#[derive(Debug, Clone)]
86enum HuffmanNodeKind {
87    Leaf(u16),
88    Branch { left: usize, right: usize },
89}
90
91#[derive(Debug, Clone)]
92struct HuffmanNode {
93    kind: HuffmanNodeKind,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
97struct HuffmanHeapEntry {
98    freq: u64,
99    min_symbol: u16,
100    node_index: usize,
101}
102
103#[derive(Debug, Clone)]
104enum BlockBody {
105    ZeroOrEmpty,
106    Raw {
107        byte_len: usize,
108    },
109    Constant {
110        offset: f64,
111        offset_type: DataType,
112        type_code: u8,
113    },
114    Bitstuff {
115        offset: f64,
116        offset_type: DataType,
117        type_code: u8,
118        payload_len: usize,
119    },
120}
121
122#[derive(Debug, Clone)]
123struct BlockPlan {
124    is_diff: bool,
125    body: BlockBody,
126}
127
128impl BlockPlan {
129    fn encoded_len(&self) -> usize {
130        match self.body {
131            BlockBody::ZeroOrEmpty => 1,
132            BlockBody::Raw { byte_len } => 1 + byte_len,
133            BlockBody::Constant { offset_type, .. } => 1 + offset_type.byte_len(),
134            BlockBody::Bitstuff {
135                offset_type,
136                payload_len,
137                ..
138            } => 1 + offset_type.byte_len() + payload_len,
139        }
140    }
141
142    fn header_byte(&self, check_code: u8, version: i32) -> u8 {
143        let check_code = if version >= VERSION_5 {
144            check_code & 14
145        } else {
146            check_code & 15
147        };
148        let mut header = tile_header(
149            check_code,
150            match self.body {
151                BlockBody::ZeroOrEmpty => 2,
152                BlockBody::Raw { .. } => 0,
153                BlockBody::Constant { .. } => 3,
154                BlockBody::Bitstuff { .. } => 1,
155            },
156        );
157        if self.is_diff && version >= VERSION_5 {
158            header |= 4;
159        }
160        match self.body {
161            BlockBody::Constant { type_code, .. } | BlockBody::Bitstuff { type_code, .. } => {
162                header |= type_code << 6;
163            }
164            BlockBody::ZeroOrEmpty | BlockBody::Raw { .. } => {}
165        }
166        header
167    }
168}
169
170trait ByteSink {
171    fn push(&mut self, byte: u8) -> Result<()>;
172    fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<()>;
173    fn len(&self) -> usize;
174}
175
176struct SliceSink<'a> {
177    out: &'a mut [u8],
178    len: usize,
179}
180
181impl<'a> SliceSink<'a> {
182    fn new(out: &'a mut [u8]) -> Self {
183        Self { out, len: 0 }
184    }
185}
186
187impl ByteSink for SliceSink<'_> {
188    fn push(&mut self, byte: u8) -> Result<()> {
189        if self.len >= self.out.len() {
190            return Err(Error::OutputTooSmall {
191                needed: self.len + 1,
192                available: self.out.len(),
193            });
194        }
195        self.out[self.len] = byte;
196        self.len += 1;
197        Ok(())
198    }
199
200    fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<()> {
201        let end = self
202            .len
203            .checked_add(bytes.len())
204            .ok_or_else(|| Error::InvalidArgument("encoded blob size overflows usize".into()))?;
205        if end > self.out.len() {
206            return Err(Error::OutputTooSmall {
207                needed: end,
208                available: self.out.len(),
209            });
210        }
211        self.out[self.len..end].copy_from_slice(bytes);
212        self.len = end;
213        Ok(())
214    }
215
216    fn len(&self) -> usize {
217        self.len
218    }
219}
220
221#[derive(Debug, Default)]
222struct TileScratch {
223    raw_bytes: Vec<u8>,
224    values_f64: Vec<f64>,
225    prev_values_f64: Vec<f64>,
226    diff_values_f64: Vec<f64>,
227    quantized: Vec<u32>,
228    bitstuff_payload: Vec<u8>,
229}
230
231impl TileScratch {
232    fn clear(&mut self) {
233        self.raw_bytes.clear();
234        self.values_f64.clear();
235        self.prev_values_f64.clear();
236        self.diff_values_f64.clear();
237        self.quantized.clear();
238        self.bitstuff_payload.clear();
239    }
240}
241
242#[derive(Debug, Default)]
243struct MsbBitWriter {
244    words: Vec<u32>,
245    current: u32,
246    bits_used: u8,
247}
248
249impl MsbBitWriter {
250    fn push_bits(&mut self, bits: u32, bit_len: u8) -> Result<()> {
251        let mut remaining = bit_len as usize;
252        while remaining > 0 {
253            let space = 32usize - self.bits_used as usize;
254            let take = remaining.min(space);
255            let shift_out = remaining - take;
256            let chunk_mask = if take == 32 {
257                u32::MAX
258            } else {
259                (1u32 << take) - 1
260            };
261            let chunk = (bits >> shift_out) & chunk_mask;
262            self.current |= chunk << (space - take);
263            self.bits_used += take as u8;
264            remaining -= take;
265            if self.bits_used == 32 {
266                self.words.push(self.current);
267                self.current = 0;
268                self.bits_used = 0;
269            }
270        }
271        Ok(())
272    }
273
274    fn into_aligned_bytes(mut self) -> Vec<u8> {
275        if self.bits_used != 0 {
276            self.words.push(self.current);
277        }
278        words_to_le_bytes(&self.words)
279    }
280
281    fn into_bytes_with_trailing_word(mut self) -> Vec<u8> {
282        if self.bits_used != 0 {
283            self.words.push(self.current);
284        }
285        self.words.push(0);
286        words_to_le_bytes(&self.words)
287    }
288}
289
290trait RasterSource<T: Sample>: Copy {
291    fn width(self) -> u32;
292    fn height(self) -> u32;
293    fn depth(self) -> u32;
294    fn data_type(self) -> DataType;
295    fn pixel_count(self) -> Result<usize>;
296    fn sample(self, pixel: usize, dim: usize) -> T;
297}
298
299impl<T: Sample> RasterSource<T> for RasterView<'_, T> {
300    fn width(self) -> u32 {
301        self.width()
302    }
303
304    fn height(self) -> u32 {
305        self.height()
306    }
307
308    fn depth(self) -> u32 {
309        self.depth()
310    }
311
312    fn data_type(self) -> DataType {
313        self.data_type()
314    }
315
316    fn pixel_count(self) -> Result<usize> {
317        self.pixel_count()
318    }
319
320    fn sample(self, pixel: usize, dim: usize) -> T {
321        self.sample(pixel, dim)
322    }
323}
324
325#[derive(Debug, Clone, Copy)]
326struct BandRasterView<'a, T: Sample> {
327    band_set: BandSetView<'a, T>,
328    band_index: usize,
329}
330
331impl<T: Sample> RasterSource<T> for BandRasterView<'_, T> {
332    fn width(self) -> u32 {
333        self.band_set.width()
334    }
335
336    fn height(self) -> u32 {
337        self.band_set.height()
338    }
339
340    fn depth(self) -> u32 {
341        self.band_set.depth()
342    }
343
344    fn data_type(self) -> DataType {
345        self.band_set.data_type()
346    }
347
348    fn pixel_count(self) -> Result<usize> {
349        self.band_set.pixel_count()
350    }
351
352    fn sample(self, pixel: usize, dim: usize) -> T {
353        self.band_set.sample(self.band_index, pixel, dim)
354    }
355}
356
357#[derive(Debug, Clone, Copy)]
358enum MaskKind<'a> {
359    None,
360    Explicit(&'a [u8]),
361    External(&'a [u8]),
362}
363
364impl<'a> MaskKind<'a> {
365    fn data(self) -> Option<&'a [u8]> {
366        match self {
367            Self::None => None,
368            Self::Explicit(mask) | Self::External(mask) => Some(mask),
369        }
370    }
371
372    fn stored_payload_len(self, pixel_count: usize, valid_pixel_count: usize) -> Result<usize> {
373        match self {
374            Self::Explicit(mask) => explicit_mask_payload_len(mask, pixel_count, valid_pixel_count),
375            Self::None | Self::External(_) => Ok(0),
376        }
377    }
378}
379
380fn shared_mask_for_band(mask: Option<&[u8]>, band_index: usize) -> MaskKind<'_> {
381    match mask {
382        Some(mask) if band_index == 0 => MaskKind::Explicit(mask),
383        Some(mask) => MaskKind::External(mask),
384        None => MaskKind::None,
385    }
386}
387
388fn validate_encode_options(options: EncodeOptions) -> Result<()> {
389    if !options.max_z_error.is_finite() || options.max_z_error < 0.0 {
390        return Err(Error::InvalidArgument(
391            "max_z_error must be finite and non-negative".into(),
392        ));
393    }
394    if options.micro_block_size == 0 {
395        return Err(Error::InvalidArgument(
396            "micro_block_size must be greater than zero".into(),
397        ));
398    }
399    if options.micro_block_size > i32::MAX as u32 {
400        return Err(Error::InvalidArgument(
401            "micro_block_size exceeds the Lerc2 header limit".into(),
402        ));
403    }
404    if let Some(no_data_value) = options.no_data_value {
405        if !no_data_value.is_finite() {
406            return Err(Error::InvalidArgument(
407                "no_data_value must be finite when provided".into(),
408            ));
409        }
410    }
411    Ok(())
412}
413
414fn validate_mask_dimensions(width: u32, height: u32, mask: Option<MaskView<'_>>) -> Result<()> {
415    if let Some(mask) = mask {
416        if mask.width() != width || mask.height() != height {
417            return Err(Error::InvalidArgument(
418                "mask dimensions must match the raster dimensions".into(),
419            ));
420        }
421    }
422    Ok(())
423}
424
425fn validate_mask_slice(mask: Option<&[u8]>, pixel_count: usize) -> Result<()> {
426    if let Some(mask) = mask {
427        if mask.len() != pixel_count {
428            return Err(Error::InvalidArgument(
429                "mask length does not match the raster dimensions".into(),
430            ));
431        }
432    }
433    Ok(())
434}
435
436pub fn encoded_len_upper_bound<T: Sample>(
437    raster: RasterView<'_, T>,
438    mask: Option<MaskView<'_>>,
439    options: EncodeOptions,
440) -> Result<usize> {
441    validate_encode_options(options)?;
442    validate_mask_dimensions(raster.width(), raster.height(), mask)?;
443
444    let mask = mask.map_or(MaskKind::None, |mask| MaskKind::Explicit(mask.data()));
445    let analysis = analyze_raster(raster, mask, options)?;
446    encoded_len_upper_bound_from_analysis(raster, mask, options, &analysis)
447}
448
449pub fn encoded_band_set_len_upper_bound<T: Sample>(
450    band_set: BandSetView<'_, T>,
451    mask: Option<MaskView<'_>>,
452    options: EncodeOptions,
453) -> Result<usize> {
454    validate_encode_options(options)?;
455    validate_mask_dimensions(band_set.width(), band_set.height(), mask)?;
456
457    let shared_mask = mask.map(MaskView::data);
458    let mut total = 0usize;
459    for band_index in 0..band_set.band_count() {
460        let band = BandRasterView {
461            band_set,
462            band_index,
463        };
464        let mask = shared_mask_for_band(shared_mask, band_index);
465        let analysis = analyze_raster(band, mask, options)?;
466        total = total
467            .checked_add(encoded_len_upper_bound_from_analysis(
468                band, mask, options, &analysis,
469            )?)
470            .ok_or_else(|| {
471                Error::InvalidArgument("encoded band set size overflows usize".into())
472            })?;
473    }
474    Ok(total)
475}
476
477pub fn encode<T: Sample>(
478    raster: RasterView<'_, T>,
479    mask: Option<MaskView<'_>>,
480    options: EncodeOptions,
481) -> Result<Vec<u8>> {
482    validate_encode_options(options)?;
483    validate_mask_dimensions(raster.width(), raster.height(), mask)?;
484
485    let mask = mask.map_or(MaskKind::None, |mask| MaskKind::Explicit(mask.data()));
486    let analysis = analyze_raster(raster, mask, options)?;
487    let upper_bound = encoded_len_upper_bound_from_analysis(raster, mask, options, &analysis)?;
488    let mut out = vec![0u8; upper_bound];
489    let written = encode_into_with_analysis(raster, mask, options, &analysis, &mut out)?;
490    out.truncate(written);
491    Ok(out)
492}
493
494pub fn encode_band_set<T: Sample>(
495    band_set: BandSetView<'_, T>,
496    mask: Option<MaskView<'_>>,
497    options: EncodeOptions,
498) -> Result<Vec<u8>> {
499    validate_encode_options(options)?;
500    validate_mask_dimensions(band_set.width(), band_set.height(), mask)?;
501
502    let shared_mask = mask.map(MaskView::data);
503    let mut analyses = Vec::with_capacity(band_set.band_count());
504    let mut upper_bound = 0usize;
505
506    for band_index in 0..band_set.band_count() {
507        let band = BandRasterView {
508            band_set,
509            band_index,
510        };
511        let mask_kind = shared_mask_for_band(shared_mask, band_index);
512        let analysis = analyze_raster(band, mask_kind, options)?;
513        upper_bound = upper_bound
514            .checked_add(encoded_len_upper_bound_from_analysis(
515                band, mask_kind, options, &analysis,
516            )?)
517            .ok_or_else(|| {
518                Error::InvalidArgument("encoded band set size overflows usize".into())
519            })?;
520        analyses.push(analysis);
521    }
522
523    let mut out = vec![0u8; upper_bound];
524    let written =
525        encode_band_set_into_with_analysis(band_set, shared_mask, options, &analyses, &mut out)?;
526    out.truncate(written);
527    Ok(out)
528}
529
530pub fn encode_into<T: Sample>(
531    raster: RasterView<'_, T>,
532    mask: Option<MaskView<'_>>,
533    options: EncodeOptions,
534    out: &mut [u8],
535) -> Result<usize> {
536    validate_encode_options(options)?;
537    validate_mask_dimensions(raster.width(), raster.height(), mask)?;
538
539    let mask = mask.map_or(MaskKind::None, |mask| MaskKind::Explicit(mask.data()));
540    let analysis = analyze_raster(raster, mask, options)?;
541    encode_into_with_analysis(raster, mask, options, &analysis, out)
542}
543
544pub fn encode_band_set_into<T: Sample>(
545    band_set: BandSetView<'_, T>,
546    mask: Option<MaskView<'_>>,
547    options: EncodeOptions,
548    out: &mut [u8],
549) -> Result<usize> {
550    validate_encode_options(options)?;
551    validate_mask_dimensions(band_set.width(), band_set.height(), mask)?;
552
553    let shared_mask = mask.map(MaskView::data);
554    let mut analyses = Vec::with_capacity(band_set.band_count());
555    for band_index in 0..band_set.band_count() {
556        let band = BandRasterView {
557            band_set,
558            band_index,
559        };
560        analyses.push(analyze_raster(
561            band,
562            shared_mask_for_band(shared_mask, band_index),
563            options,
564        )?);
565    }
566
567    encode_band_set_into_with_analysis(band_set, shared_mask, options, &analyses, out)
568}
569
570fn analyze_raster<T: Sample, R: RasterSource<T>>(
571    raster: R,
572    mask: MaskKind<'_>,
573    options: EncodeOptions,
574) -> Result<RasterAnalysis> {
575    let pixel_count = raster.pixel_count()?;
576    validate_mask_slice(mask.data(), pixel_count)?;
577    if options.no_data_value.is_some() && raster.depth() <= 1 {
578        return Err(Error::InvalidArgument(
579            "no_data_value requires depth greater than one".into(),
580        ));
581    }
582    let depth = raster.depth() as usize;
583    let data_type = raster.data_type();
584
585    let mut valid_pixel_count = 0usize;
586    let mut z_min = f64::INFINITY;
587    let mut z_max = f64::NEG_INFINITY;
588    let mut min_values = vec![f64::INFINITY; depth];
589    let mut max_values = vec![f64::NEG_INFINITY; depth];
590
591    for pixel in 0..pixel_count {
592        if !pixel_is_valid(mask.data(), pixel) {
593            continue;
594        }
595        valid_pixel_count += 1;
596        for dim in 0..depth {
597            let value = raster.sample(pixel, dim).to_f64();
598            if !value.is_finite() {
599                return Err(Error::InvalidArgument(
600                    "valid raster samples must be finite".into(),
601                ));
602            }
603            z_min = z_min.min(value);
604            z_max = z_max.max(value);
605            min_values[dim] = min_values[dim].min(value);
606            max_values[dim] = max_values[dim].max(value);
607        }
608    }
609
610    let valid_pixel_count = u32::try_from(valid_pixel_count)
611        .map_err(|_| Error::InvalidArgument("valid pixel count exceeds u32".into()))?;
612    if valid_pixel_count == 0 {
613        z_min = 0.0;
614        z_max = 0.0;
615    }
616
617    let (min_values, max_values) = if valid_pixel_count != 0 && z_min != z_max {
618        (Some(min_values), Some(max_values))
619    } else {
620        (None, None)
621    };
622
623    let mut analysis = RasterAnalysis {
624        data_type,
625        width: raster.width(),
626        height: raster.height(),
627        depth: raster.depth(),
628        valid_pixel_count,
629        max_z_error: options.max_z_error,
630        micro_block_size: options.micro_block_size,
631        z_min,
632        z_max,
633        no_data_value: options.no_data_value,
634        min_values,
635        max_values,
636        plan: EncodePlan {
637            version: VERSION_4,
638            body: BodyPlan::Constant,
639        },
640    };
641    analysis.plan = plan_raster(raster, mask.data(), &analysis)?;
642    Ok(analysis)
643}
644
645fn encoded_len_upper_bound_from_analysis<T: Sample, R: RasterSource<T>>(
646    raster: R,
647    mask: MaskKind<'_>,
648    options: EncodeOptions,
649    analysis: &RasterAnalysis,
650) -> Result<usize> {
651    let pixel_count = raster.pixel_count()?;
652    validate_mask_slice(mask.data(), pixel_count)?;
653    let valid_pixel_count = analysis.valid_pixel_count as usize;
654    let depth = raster.depth() as usize;
655    let num_tiles = tile_count(raster.width() as usize, raster.height() as usize, options)?;
656    let byte_len = raster.data_type().byte_len();
657    let mask_len = mask.stored_payload_len(pixel_count, valid_pixel_count)?;
658    let range_len = depth_range_len(analysis)?;
659    let prefix_len = if analysis.valid_pixel_count == 0
660        || analysis.z_min == analysis.z_max
661        || has_per_depth_constant(analysis)
662    {
663        0
664    } else {
665        body_prefix_len(
666            raster.data_type(),
667            options.max_z_error,
668            analysis.plan.version,
669        )
670    };
671    let tile_header_len = num_tiles
672        .checked_mul(depth)
673        .ok_or_else(|| Error::InvalidArgument("tile header length overflows usize".into()))?;
674    let raw_data_len = valid_pixel_count
675        .checked_mul(depth)
676        .and_then(|len| len.checked_mul(byte_len))
677        .ok_or_else(|| Error::InvalidArgument("raw tile payload length overflows usize".into()))?;
678
679    header_len(analysis.plan.version)
680        .checked_add(MASK_COUNT_LEN)
681        .and_then(|len| len.checked_add(mask_len))
682        .and_then(|len| len.checked_add(range_len))
683        .and_then(|len| len.checked_add(prefix_len))
684        .and_then(|len| len.checked_add(tile_header_len))
685        .and_then(|len| len.checked_add(raw_data_len))
686        .ok_or_else(|| Error::InvalidArgument("encoded upper bound overflows usize".into()))
687}
688
689fn encode_into_with_analysis<T: Sample, R: RasterSource<T>>(
690    raster: R,
691    mask: MaskKind<'_>,
692    _options: EncodeOptions,
693    analysis: &RasterAnalysis,
694    out: &mut [u8],
695) -> Result<usize> {
696    let pixel_count = raster.pixel_count()?;
697    validate_mask_slice(mask.data(), pixel_count)?;
698
699    let mut sink = SliceSink::new(out);
700    let mut scratch = TileScratch::default();
701    write_header_prefix(&mut sink, analysis)?;
702    write_u32(
703        &mut sink,
704        mask.stored_payload_len(pixel_count, analysis.valid_pixel_count as usize)? as u32,
705    )?;
706    write_mask_rle(
707        &mut sink,
708        mask,
709        pixel_count,
710        analysis.valid_pixel_count as usize,
711    )?;
712    write_depth_ranges(&mut sink, analysis)?;
713    write_body(&mut sink, &mut scratch, raster, mask.data(), analysis)?;
714
715    let written = sink.len();
716    if written > i32::MAX as usize {
717        return Err(Error::InvalidArgument(
718            "encoded blob size exceeds the Lerc2 header limit".into(),
719        ));
720    }
721
722    out[34..38].copy_from_slice(&(written as i32).to_le_bytes());
723    let checksum = fletcher32(&out[14..written]);
724    out[10..14].copy_from_slice(&checksum.to_le_bytes());
725    Ok(written)
726}
727
728fn encode_band_set_into_with_analysis<T: Sample>(
729    band_set: BandSetView<'_, T>,
730    shared_mask: Option<&[u8]>,
731    options: EncodeOptions,
732    analyses: &[RasterAnalysis],
733    out: &mut [u8],
734) -> Result<usize> {
735    if analyses.len() != band_set.band_count() {
736        return Err(Error::InvalidArgument(
737            "band analysis count does not match band_count".into(),
738        ));
739    }
740
741    let mut offset = 0usize;
742    for (band_index, analysis) in analyses.iter().enumerate() {
743        let band = BandRasterView {
744            band_set,
745            band_index,
746        };
747        let written = encode_into_with_analysis(
748            band,
749            shared_mask_for_band(shared_mask, band_index),
750            options,
751            analysis,
752            &mut out[offset..],
753        )?;
754        offset = offset.checked_add(written).ok_or_else(|| {
755            Error::InvalidArgument("encoded band set size overflows usize".into())
756        })?;
757    }
758    Ok(offset)
759}
760
761fn write_header_prefix(sink: &mut impl ByteSink, analysis: &RasterAnalysis) -> Result<()> {
762    sink.extend_from_slice(MAGIC_LERC2)?;
763    write_i32(sink, analysis.plan.version)?;
764    write_u32(sink, 0)?;
765    write_u32(sink, analysis.height)?;
766    write_u32(sink, analysis.width)?;
767    write_u32(sink, analysis.depth)?;
768    write_u32(sink, analysis.valid_pixel_count)?;
769    write_i32(sink, analysis.micro_block_size as i32)?;
770    write_i32(sink, 0)?;
771    write_i32(sink, analysis.data_type.code() as i32)?;
772    if analysis.plan.version >= VERSION_6 {
773        write_i32(sink, 0)?;
774        sink.push(u8::from(analysis.no_data_value.is_some()))?;
775        sink.push(0)?;
776        sink.push(0)?;
777        sink.push(0)?;
778    }
779    write_f64(sink, analysis.max_z_error)?;
780    write_f64(sink, analysis.z_min)?;
781    write_f64(sink, analysis.z_max)?;
782    if analysis.plan.version >= VERSION_6 {
783        let no_data_value = analysis.no_data_value.unwrap_or(0.0);
784        write_f64(sink, no_data_value)?;
785        write_f64(sink, no_data_value)?;
786    }
787    Ok(())
788}
789
790fn write_depth_ranges(sink: &mut impl ByteSink, analysis: &RasterAnalysis) -> Result<()> {
791    if let (Some(min_values), Some(max_values)) = (&analysis.min_values, &analysis.max_values) {
792        for &value in min_values {
793            write_value_as(sink, value, analysis.data_type)?;
794        }
795        for &value in max_values {
796            write_value_as(sink, value, analysis.data_type)?;
797        }
798    }
799    Ok(())
800}
801
802fn write_body<T: Sample, R: RasterSource<T>>(
803    sink: &mut impl ByteSink,
804    scratch: &mut TileScratch,
805    raster: R,
806    mask: Option<&[u8]>,
807    analysis: &RasterAnalysis,
808) -> Result<()> {
809    match &analysis.plan.body {
810        BodyPlan::Constant | BodyPlan::PerDepthConstant => Ok(()),
811        BodyPlan::OneSweep => write_one_sweep_body(sink, raster, mask),
812        BodyPlan::Tiled(plan) => write_tiled_body(sink, scratch, raster, mask, analysis, plan),
813        BodyPlan::Huffman(plan) => write_huffman_body(sink, raster, mask, analysis, plan),
814    }
815}
816
817#[derive(Debug, Clone)]
818struct TilingPlan {
819    version: i32,
820    data_len: usize,
821    blocks: Vec<BlockPlan>,
822}
823
824fn plan_raster<T: Sample, R: RasterSource<T>>(
825    raster: R,
826    mask: Option<&[u8]>,
827    analysis: &RasterAnalysis,
828) -> Result<EncodePlan> {
829    if analysis.valid_pixel_count == 0 || analysis.z_min == analysis.z_max {
830        return Ok(EncodePlan {
831            version: version_with_no_data(analysis, VERSION_4),
832            body: BodyPlan::Constant,
833        });
834    }
835    if has_per_depth_constant(analysis) {
836        return Ok(EncodePlan {
837            version: version_with_no_data(analysis, VERSION_4),
838            body: BodyPlan::PerDepthConstant,
839        });
840    }
841
842    let tiling = plan_tiled_body(raster, mask, analysis)?;
843    let mut best_body = BodyPlan::Tiled(tiling.clone());
844    let mut best_version = tiling.version;
845    let mut best_non_one_len = tiling.data_len
846        + usize::from(needs_encode_mode_flag(
847            analysis.data_type,
848            analysis.max_z_error,
849            version_with_no_data(analysis, tiling.version),
850        ));
851
852    if let Some(huffman) = build_huffman_plan(raster, mask, analysis)? {
853        let huffman_total_len = huffman.data_len.checked_add(1).ok_or_else(|| {
854            Error::InvalidArgument("Huffman payload length overflows usize".into())
855        })?;
856        if huffman_total_len < best_non_one_len {
857            best_non_one_len = huffman_total_len;
858            best_version = version_with_no_data(analysis, VERSION_4);
859            best_body = BodyPlan::Huffman(huffman);
860        }
861    }
862
863    let one_sweep_len = (analysis.valid_pixel_count as usize)
864        .checked_mul(analysis.depth as usize)
865        .and_then(|len| len.checked_mul(analysis.data_type.byte_len()))
866        .ok_or_else(|| Error::InvalidArgument("one-sweep byte count overflows usize".into()))?;
867    if one_sweep_len <= best_non_one_len {
868        return Ok(EncodePlan {
869            version: version_with_no_data(analysis, VERSION_4),
870            body: BodyPlan::OneSweep,
871        });
872    }
873
874    Ok(EncodePlan {
875        version: version_with_no_data(analysis, best_version),
876        body: best_body,
877    })
878}
879
880fn plan_tiled_body<T: Sample, R: RasterSource<T>>(
881    raster: R,
882    mask: Option<&[u8]>,
883    analysis: &RasterAnalysis,
884) -> Result<TilingPlan> {
885    let width = raster.width() as usize;
886    let height = raster.height() as usize;
887    let depth = raster.depth() as usize;
888    let micro = analysis.micro_block_size as usize;
889    let num_blocks_x = width.div_ceil(micro);
890    let num_blocks_y = height.div_ceil(micro);
891    let last_block_width = if width % micro == 0 {
892        micro
893    } else {
894        width % micro
895    };
896    let last_block_height = if height % micro == 0 {
897        micro
898    } else {
899        height % micro
900    };
901    let diff_supported =
902        supports_diff_tiles(analysis.data_type, analysis.max_z_error, analysis.depth);
903    let plan_capacity = num_blocks_x
904        .checked_mul(num_blocks_y)
905        .and_then(|count| count.checked_mul(depth))
906        .ok_or_else(|| Error::InvalidArgument("tile plan block count overflows usize".into()))?;
907
908    let mut scratch = TileScratch::default();
909    let mut version4_len = 0usize;
910    let mut version5_len = 0usize;
911    let mut version4_blocks = Vec::with_capacity(plan_capacity);
912    let mut version5_blocks: Option<Vec<BlockPlan>> = None;
913    let mut used_diff = false;
914
915    for block_y in 0..num_blocks_y {
916        let block_height = if block_y + 1 == num_blocks_y {
917            last_block_height
918        } else {
919            micro
920        };
921        for block_x in 0..num_blocks_x {
922            let block_width = if block_x + 1 == num_blocks_x {
923                last_block_width
924            } else {
925                micro
926            };
927
928            for dim in 0..depth {
929                collect_block_values(
930                    &mut scratch,
931                    raster,
932                    mask,
933                    width,
934                    micro,
935                    block_x,
936                    block_y,
937                    block_width,
938                    block_height,
939                    dim,
940                    diff_supported && dim > 0,
941                );
942
943                let absolute_plan = choose_absolute_block_plan(
944                    &scratch.values_f64,
945                    scratch.raw_bytes.len(),
946                    analysis.data_type,
947                    analysis.max_z_error,
948                    &mut scratch.quantized,
949                    &mut scratch.bitstuff_payload,
950                )?;
951                version4_len = version4_len
952                    .checked_add(absolute_plan.encoded_len())
953                    .ok_or_else(|| {
954                        Error::InvalidArgument("tile payload length overflows usize".into())
955                    })?;
956                version5_len = version5_len
957                    .checked_add(absolute_plan.encoded_len())
958                    .ok_or_else(|| {
959                        Error::InvalidArgument("tile payload length overflows usize".into())
960                    })?;
961
962                let mut diff_selection = None;
963                if diff_supported
964                    && dim > 0
965                    && build_diff_values(
966                        &scratch.values_f64,
967                        &scratch.prev_values_f64,
968                        &mut scratch.diff_values_f64,
969                    )?
970                {
971                    if let Some(diff_plan) = choose_diff_block_plan(
972                        &scratch.diff_values_f64,
973                        analysis.max_z_error,
974                        &mut scratch.quantized,
975                        &mut scratch.bitstuff_payload,
976                    )? {
977                        if diff_plan.encoded_len() < absolute_plan.encoded_len() {
978                            version5_len = version5_len
979                                .checked_sub(absolute_plan.encoded_len())
980                                .and_then(|len| len.checked_add(diff_plan.encoded_len()))
981                                .ok_or_else(|| {
982                                    Error::InvalidArgument(
983                                        "tile payload length overflows usize".into(),
984                                    )
985                                })?;
986                            diff_selection = Some(diff_plan);
987                            used_diff = true;
988                        }
989                    }
990                }
991                if let Some(diff_plan) = diff_selection {
992                    if version5_blocks.is_none() {
993                        version5_blocks = Some(version4_blocks.clone());
994                    }
995                    version5_blocks.as_mut().unwrap().push(diff_plan);
996                } else if let Some(version5_blocks) = version5_blocks.as_mut() {
997                    version5_blocks.push(absolute_plan.clone());
998                }
999                version4_blocks.push(absolute_plan);
1000            }
1001        }
1002    }
1003
1004    Ok(if used_diff {
1005        TilingPlan {
1006            version: VERSION_5,
1007            data_len: version5_len,
1008            blocks: version5_blocks.expect("diff use initializes the version 5 tile plan"),
1009        }
1010    } else {
1011        TilingPlan {
1012            version: VERSION_4,
1013            data_len: version4_len,
1014            blocks: version4_blocks,
1015        }
1016    })
1017}
1018
1019fn write_one_sweep_body<T: Sample, R: RasterSource<T>>(
1020    sink: &mut impl ByteSink,
1021    raster: R,
1022    mask: Option<&[u8]>,
1023) -> Result<()> {
1024    sink.push(1)?;
1025    let pixel_count = raster.pixel_count()?;
1026    let depth = raster.depth() as usize;
1027    for pixel in 0..pixel_count {
1028        if !pixel_is_valid(mask, pixel) {
1029            continue;
1030        }
1031        for dim in 0..depth {
1032            write_value_as(sink, raster.sample(pixel, dim).to_f64(), raster.data_type())?;
1033        }
1034    }
1035    Ok(())
1036}
1037
1038fn write_tiled_body<T: Sample, R: RasterSource<T>>(
1039    sink: &mut impl ByteSink,
1040    scratch: &mut TileScratch,
1041    raster: R,
1042    mask: Option<&[u8]>,
1043    analysis: &RasterAnalysis,
1044    plan: &TilingPlan,
1045) -> Result<()> {
1046    let width = raster.width() as usize;
1047    let height = raster.height() as usize;
1048    let depth = raster.depth() as usize;
1049    let micro = analysis.micro_block_size as usize;
1050    let num_blocks_x = width.div_ceil(micro);
1051    let num_blocks_y = height.div_ceil(micro);
1052    let last_block_width = if width % micro == 0 {
1053        micro
1054    } else {
1055        width % micro
1056    };
1057    let last_block_height = if height % micro == 0 {
1058        micro
1059    } else {
1060        height % micro
1061    };
1062    sink.push(0)?;
1063    if needs_encode_mode_flag(
1064        analysis.data_type,
1065        analysis.max_z_error,
1066        analysis.plan.version,
1067    ) {
1068        sink.push(0)?;
1069    }
1070
1071    let mut block_plan_index = 0usize;
1072    for block_y in 0..num_blocks_y {
1073        let block_height = if block_y + 1 == num_blocks_y {
1074            last_block_height
1075        } else {
1076            micro
1077        };
1078        for block_x in 0..num_blocks_x {
1079            let block_width = if block_x + 1 == num_blocks_x {
1080                last_block_width
1081            } else {
1082                micro
1083            };
1084
1085            for dim in 0..depth {
1086                let block_plan = plan.blocks.get(block_plan_index).ok_or_else(|| {
1087                    Error::InvalidArgument("cached tile plan is missing a block".into())
1088                })?;
1089                block_plan_index += 1;
1090                collect_block_values(
1091                    scratch,
1092                    raster,
1093                    mask,
1094                    width,
1095                    micro,
1096                    block_x,
1097                    block_y,
1098                    block_width,
1099                    block_height,
1100                    dim,
1101                    block_plan.is_diff,
1102                );
1103                let check_code = (((block_x * micro) >> 3) as u8) & 15;
1104                prepare_cached_block_payload(
1105                    block_plan,
1106                    &scratch.values_f64,
1107                    &scratch.prev_values_f64,
1108                    &mut scratch.diff_values_f64,
1109                    analysis.max_z_error,
1110                    &mut scratch.quantized,
1111                    &mut scratch.bitstuff_payload,
1112                )?;
1113
1114                write_block_plan(
1115                    sink,
1116                    block_plan,
1117                    check_code,
1118                    analysis.plan.version,
1119                    &scratch.raw_bytes,
1120                    &scratch.bitstuff_payload,
1121                )?;
1122            }
1123        }
1124    }
1125
1126    if block_plan_index != plan.blocks.len() {
1127        return Err(Error::InvalidArgument(
1128            "cached tile plan contains trailing blocks".into(),
1129        ));
1130    }
1131
1132    Ok(())
1133}
1134
1135fn write_huffman_body<T: Sample, R: RasterSource<T>>(
1136    sink: &mut impl ByteSink,
1137    raster: R,
1138    mask: Option<&[u8]>,
1139    analysis: &RasterAnalysis,
1140    plan: &HuffmanPlan,
1141) -> Result<()> {
1142    let _ = analysis;
1143    sink.push(0)?;
1144    sink.push(plan.mode as u8)?;
1145    sink.extend_from_slice(&plan.table_bytes)?;
1146
1147    let width = raster.width() as usize;
1148    let height = raster.height() as usize;
1149    let depth = raster.depth() as usize;
1150    let data_type = raster.data_type();
1151    let mut writer = MsbBitWriter::default();
1152
1153    match plan.mode {
1154        HuffmanMode::Delta => {
1155            for dim in 0..depth {
1156                let mut prev_value = 0i32;
1157                for row in 0..height {
1158                    for col in 0..width {
1159                        let pixel = row * width + col;
1160                        if !pixel_is_valid(mask, pixel) {
1161                            continue;
1162                        }
1163                        let value =
1164                            huffman_sample_value(raster.sample(pixel, dim).to_f64(), data_type);
1165                        let predictor = if col > 0 && pixel_is_valid(mask, pixel - 1) {
1166                            prev_value
1167                        } else if row > 0 && pixel_is_valid(mask, pixel - width) {
1168                            huffman_sample_value(
1169                                raster.sample(pixel - width, dim).to_f64(),
1170                                data_type,
1171                            )
1172                        } else {
1173                            prev_value
1174                        };
1175                        let symbol = huffman_delta_symbol(value - predictor, data_type);
1176                        let code = plan.codes[symbol].ok_or_else(|| {
1177                            Error::InvalidArgument("missing Huffman delta symbol".into())
1178                        })?;
1179                        writer.push_bits(code.bits, code.bit_len)?;
1180                        prev_value = value;
1181                    }
1182                }
1183            }
1184        }
1185        HuffmanMode::Plain => {
1186            let pixel_count = raster.pixel_count()?;
1187            for pixel in 0..pixel_count {
1188                if !pixel_is_valid(mask, pixel) {
1189                    continue;
1190                }
1191                for dim in 0..depth {
1192                    let value = huffman_sample_value(raster.sample(pixel, dim).to_f64(), data_type);
1193                    let symbol = huffman_symbol(value, data_type);
1194                    let code = plan.codes[symbol]
1195                        .ok_or_else(|| Error::InvalidArgument("missing Huffman symbol".into()))?;
1196                    writer.push_bits(code.bits, code.bit_len)?;
1197                }
1198            }
1199        }
1200    }
1201
1202    sink.extend_from_slice(&writer.into_bytes_with_trailing_word())?;
1203    Ok(())
1204}
1205
1206#[allow(clippy::too_many_arguments)]
1207fn collect_block_values<T: Sample, R: RasterSource<T>>(
1208    scratch: &mut TileScratch,
1209    raster: R,
1210    mask: Option<&[u8]>,
1211    width: usize,
1212    micro: usize,
1213    block_x: usize,
1214    block_y: usize,
1215    block_width: usize,
1216    block_height: usize,
1217    dim: usize,
1218    include_prev_values: bool,
1219) {
1220    scratch.clear();
1221    for row in 0..block_height {
1222        let pixel_row = block_y * micro + row;
1223        for col in 0..block_width {
1224            let pixel = pixel_row * width + block_x * micro + col;
1225            if !pixel_is_valid(mask, pixel) {
1226                continue;
1227            }
1228            let value = raster.sample(pixel, dim);
1229            value.append_le_bytes(&mut scratch.raw_bytes);
1230            scratch.values_f64.push(value.to_f64());
1231            if include_prev_values {
1232                scratch
1233                    .prev_values_f64
1234                    .push(raster.sample(pixel, dim - 1).to_f64());
1235            }
1236        }
1237    }
1238}
1239
1240fn choose_absolute_block_plan(
1241    values: &[f64],
1242    raw_byte_len: usize,
1243    base_type: DataType,
1244    max_z_error: f64,
1245    quantized: &mut Vec<u32>,
1246    payload: &mut Vec<u8>,
1247) -> Result<BlockPlan> {
1248    let raw_len = raw_byte_len
1249        .checked_add(1)
1250        .ok_or_else(|| Error::InvalidArgument("raw block length overflows usize".into()))?;
1251    choose_block_plan(
1252        values,
1253        Some(raw_len),
1254        base_type,
1255        max_z_error,
1256        false,
1257        quantized,
1258        payload,
1259    )?
1260    .ok_or_else(|| Error::InvalidArgument("absolute tile plan unexpectedly missing".into()))
1261}
1262
1263fn choose_diff_block_plan(
1264    diff_values: &[f64],
1265    max_z_error: f64,
1266    quantized: &mut Vec<u32>,
1267    payload: &mut Vec<u8>,
1268) -> Result<Option<BlockPlan>> {
1269    choose_block_plan(
1270        diff_values,
1271        None,
1272        DataType::I32,
1273        max_z_error,
1274        true,
1275        quantized,
1276        payload,
1277    )
1278}
1279
1280fn choose_block_plan(
1281    values: &[f64],
1282    raw_len: Option<usize>,
1283    base_type: DataType,
1284    max_z_error: f64,
1285    is_diff: bool,
1286    quantized: &mut Vec<u32>,
1287    payload: &mut Vec<u8>,
1288) -> Result<Option<BlockPlan>> {
1289    if values.is_empty() {
1290        return Ok(Some(BlockPlan {
1291            is_diff,
1292            body: BlockBody::ZeroOrEmpty,
1293        }));
1294    }
1295
1296    let (min, max) = min_max(values);
1297    if min == 0.0 && max == 0.0 {
1298        return Ok(Some(BlockPlan {
1299            is_diff,
1300            body: BlockBody::ZeroOrEmpty,
1301        }));
1302    }
1303    if min == max {
1304        let (type_code, offset_type) = reduce_data_type(min, base_type)?;
1305        return Ok(Some(BlockPlan {
1306            is_diff,
1307            body: BlockBody::Constant {
1308                offset: min,
1309                offset_type,
1310                type_code,
1311            },
1312        }));
1313    }
1314
1315    if let Some(bitstuff) =
1316        try_bitstuff_tile(values, min, max, max_z_error, base_type, quantized, payload)?
1317    {
1318        let plan = BlockPlan {
1319            is_diff,
1320            body: BlockBody::Bitstuff {
1321                offset: bitstuff.offset,
1322                offset_type: bitstuff.offset_type,
1323                type_code: bitstuff.type_code,
1324                payload_len: bitstuff.payload_len,
1325            },
1326        };
1327        if raw_len.is_none() || plan.encoded_len() < raw_len.unwrap() {
1328            return Ok(Some(plan));
1329        }
1330    }
1331
1332    Ok(raw_len.map(|raw_len| BlockPlan {
1333        is_diff: false,
1334        body: BlockBody::Raw {
1335            byte_len: raw_len - 1,
1336        },
1337    }))
1338}
1339
1340fn write_block_plan(
1341    sink: &mut impl ByteSink,
1342    plan: &BlockPlan,
1343    check_code: u8,
1344    version: i32,
1345    raw_bytes: &[u8],
1346    bitstuff_payload: &[u8],
1347) -> Result<()> {
1348    sink.push(plan.header_byte(check_code, version))?;
1349    match plan.body {
1350        BlockBody::ZeroOrEmpty => Ok(()),
1351        BlockBody::Raw { .. } => sink.extend_from_slice(raw_bytes),
1352        BlockBody::Constant {
1353            offset,
1354            offset_type,
1355            ..
1356        } => write_value_as(sink, offset, offset_type),
1357        BlockBody::Bitstuff {
1358            offset,
1359            offset_type,
1360            payload_len,
1361            ..
1362        } => {
1363            write_value_as(sink, offset, offset_type)?;
1364            sink.extend_from_slice(&bitstuff_payload[..payload_len])
1365        }
1366    }
1367}
1368
1369fn prepare_cached_block_payload(
1370    plan: &BlockPlan,
1371    values: &[f64],
1372    prev_values: &[f64],
1373    diff_values: &mut Vec<f64>,
1374    max_z_error: f64,
1375    quantized: &mut Vec<u32>,
1376    payload: &mut Vec<u8>,
1377) -> Result<()> {
1378    payload.clear();
1379    let values = if plan.is_diff {
1380        if !build_diff_values(values, prev_values, diff_values)? {
1381            return Err(Error::InvalidArgument(
1382                "cached diff tile plan cannot be rebuilt".into(),
1383            ));
1384        }
1385        diff_values.as_slice()
1386    } else {
1387        values
1388    };
1389
1390    if let BlockBody::Bitstuff {
1391        offset,
1392        payload_len,
1393        ..
1394    } = &plan.body
1395    {
1396        encode_cached_bitstuff_payload(values, *offset, max_z_error, quantized, payload)?;
1397        if payload.len() != *payload_len {
1398            return Err(Error::InvalidArgument(
1399                "cached bit-stuffed tile payload length changed".into(),
1400            ));
1401        }
1402    }
1403
1404    Ok(())
1405}
1406
1407fn encode_cached_bitstuff_payload(
1408    values: &[f64],
1409    offset: f64,
1410    max_z_error: f64,
1411    quantized: &mut Vec<u32>,
1412    payload: &mut Vec<u8>,
1413) -> Result<()> {
1414    if max_z_error <= 0.0 {
1415        return Err(Error::InvalidArgument(
1416            "cached bit-stuffed tile requires positive max_z_error".into(),
1417        ));
1418    }
1419
1420    let scale = 2.0 * max_z_error;
1421    quantized.clear();
1422    quantized.reserve(values.len());
1423    let mut max_quantized = 0u32;
1424    for &value in values {
1425        let quantized_value = ((value - offset) / scale).round();
1426        if !quantized_value.is_finite() || !(0.0..=(u32::MAX as f64)).contains(&quantized_value) {
1427            return Err(Error::InvalidArgument(
1428                "cached bit-stuffed tile quantized value is out of range".into(),
1429            ));
1430        }
1431        let quantized_value = quantized_value as u32;
1432        max_quantized = max_quantized.max(quantized_value);
1433        quantized.push(quantized_value);
1434    }
1435
1436    let bits = bits_required(max_quantized as usize);
1437    if bits == 0 || bits > 31 {
1438        return Err(Error::InvalidArgument(
1439            "cached bit-stuffed tile has an invalid bit width".into(),
1440        ));
1441    }
1442
1443    let (count_code, count_bytes) = count_field(values.len())?;
1444    payload.clear();
1445    payload.reserve(1 + count_bytes + (values.len() * bits as usize).div_ceil(8));
1446    payload.push((count_code << 6) | bits);
1447    append_count(payload, values.len(), count_bytes)?;
1448    pack_lsb_bits_into(quantized, bits, payload);
1449    Ok(())
1450}
1451
1452fn build_diff_values(current: &[f64], previous: &[f64], out: &mut Vec<f64>) -> Result<bool> {
1453    if current.len() != previous.len() {
1454        return Err(Error::InvalidArgument(
1455            "diff input lengths do not match".into(),
1456        ));
1457    }
1458
1459    out.clear();
1460    out.reserve(current.len());
1461    for (&value, &prev) in current.iter().zip(previous) {
1462        let diff = value - prev;
1463        if diff < i32::MIN as f64 || diff > i32::MAX as f64 {
1464            out.clear();
1465            return Ok(false);
1466        }
1467        out.push(diff);
1468    }
1469    Ok(true)
1470}
1471
1472fn min_max(values: &[f64]) -> (f64, f64) {
1473    let mut min = f64::INFINITY;
1474    let mut max = f64::NEG_INFINITY;
1475    for &value in values {
1476        min = min.min(value);
1477        max = max.max(value);
1478    }
1479    (min, max)
1480}
1481
1482fn supports_diff_tiles(data_type: DataType, max_z_error: f64, depth: u32) -> bool {
1483    depth > 1 && is_integer_lossless(data_type, max_z_error)
1484}
1485
1486fn is_integer_lossless(data_type: DataType, max_z_error: f64) -> bool {
1487    matches!(
1488        data_type,
1489        DataType::I8 | DataType::U8 | DataType::I16 | DataType::U16 | DataType::I32 | DataType::U32
1490    ) && (max_z_error - 0.5).abs() < 1e-5
1491}
1492
1493fn build_huffman_plan<T: Sample, R: RasterSource<T>>(
1494    raster: R,
1495    mask: Option<&[u8]>,
1496    analysis: &RasterAnalysis,
1497) -> Result<Option<HuffmanPlan>> {
1498    if !supports_integer_huffman(analysis.data_type, analysis.max_z_error) {
1499        return Ok(None);
1500    }
1501
1502    let width = raster.width() as usize;
1503    let height = raster.height() as usize;
1504    let depth = raster.depth() as usize;
1505    let mut hist = [0u64; 256];
1506    let mut delta_hist = [0u64; 256];
1507
1508    for dim in 0..depth {
1509        let mut prev_value = 0i32;
1510        for row in 0..height {
1511            for col in 0..width {
1512                let pixel = row * width + col;
1513                if !pixel_is_valid(mask, pixel) {
1514                    continue;
1515                }
1516                let value =
1517                    huffman_sample_value(raster.sample(pixel, dim).to_f64(), analysis.data_type);
1518                hist[huffman_symbol(value, analysis.data_type)] += 1;
1519
1520                let predictor = if col > 0 && pixel_is_valid(mask, pixel - 1) {
1521                    prev_value
1522                } else if row > 0 && pixel_is_valid(mask, pixel - width) {
1523                    huffman_sample_value(
1524                        raster.sample(pixel - width, dim).to_f64(),
1525                        analysis.data_type,
1526                    )
1527                } else {
1528                    prev_value
1529                };
1530                delta_hist[huffman_delta_symbol(value - predictor, analysis.data_type)] += 1;
1531                prev_value = value;
1532            }
1533        }
1534    }
1535
1536    let plain = build_huffman_candidate_from_hist(&hist)?;
1537    let delta = build_huffman_candidate_from_hist(&delta_hist)?;
1538    let selected = match (plain, delta) {
1539        (Some(plain), Some(delta)) => {
1540            if plain.data_len <= delta.data_len {
1541                Some((HuffmanMode::Plain, plain))
1542            } else {
1543                Some((HuffmanMode::Delta, delta))
1544            }
1545        }
1546        (Some(plain), None) => Some((HuffmanMode::Plain, plain)),
1547        (None, Some(delta)) => Some((HuffmanMode::Delta, delta)),
1548        (None, None) => None,
1549    };
1550
1551    Ok(selected.map(|(mode, candidate)| HuffmanPlan {
1552        mode,
1553        table_bytes: candidate.table_bytes,
1554        codes: candidate.codes,
1555        data_len: candidate.data_len,
1556    }))
1557}
1558
1559struct HuffmanCandidate {
1560    table_bytes: Vec<u8>,
1561    codes: Vec<Option<HuffmanCode>>,
1562    data_len: usize,
1563}
1564
1565fn build_huffman_candidate_from_hist(hist: &[u64; 256]) -> Result<Option<HuffmanCandidate>> {
1566    let Some(codes) = build_huffman_codes(hist)? else {
1567        return Ok(None);
1568    };
1569    let table_bytes = build_huffman_table_bytes(&codes)?;
1570    let payload_bits = hist
1571        .iter()
1572        .zip(&codes)
1573        .try_fold(0usize, |acc, (&count, code)| {
1574            let Some(code) = code else {
1575                return Ok(acc);
1576            };
1577            acc.checked_add((count as usize) * code.bit_len as usize)
1578                .ok_or_else(|| {
1579                    Error::InvalidArgument("Huffman payload bit count overflows usize".into())
1580                })
1581        })?;
1582    let payload_bytes = (((payload_bits.div_ceil(32)) + 1) * 4)
1583        .checked_add(table_bytes.len())
1584        .ok_or_else(|| Error::InvalidArgument("Huffman payload length overflows usize".into()))?;
1585
1586    Ok(Some(HuffmanCandidate {
1587        table_bytes,
1588        codes,
1589        data_len: payload_bytes,
1590    }))
1591}
1592
1593fn build_huffman_codes(hist: &[u64; 256]) -> Result<Option<Vec<Option<HuffmanCode>>>> {
1594    let mut nodes = Vec::<HuffmanNode>::new();
1595    let mut heap = BinaryHeap::new();
1596
1597    for (symbol, &freq) in hist.iter().enumerate() {
1598        if freq == 0 {
1599            continue;
1600        }
1601        let node_index = nodes.len();
1602        nodes.push(HuffmanNode {
1603            kind: HuffmanNodeKind::Leaf(symbol as u16),
1604        });
1605        heap.push(Reverse(HuffmanHeapEntry {
1606            freq,
1607            min_symbol: symbol as u16,
1608            node_index,
1609        }));
1610    }
1611
1612    if heap.is_empty() {
1613        return Ok(None);
1614    }
1615
1616    while heap.len() > 1 {
1617        let Reverse(left) = heap.pop().unwrap();
1618        let Reverse(right) = heap.pop().unwrap();
1619        let node_index = nodes.len();
1620        nodes.push(HuffmanNode {
1621            kind: HuffmanNodeKind::Branch {
1622                left: left.node_index,
1623                right: right.node_index,
1624            },
1625        });
1626        heap.push(Reverse(HuffmanHeapEntry {
1627            freq: left.freq.checked_add(right.freq).ok_or_else(|| {
1628                Error::InvalidArgument("Huffman frequency count overflows u64".into())
1629            })?,
1630            min_symbol: left.min_symbol.min(right.min_symbol),
1631            node_index,
1632        }));
1633    }
1634
1635    let root = heap.pop().unwrap().0.node_index;
1636    let mut codes = vec![None; 256];
1637    if assign_huffman_codes(&nodes, root, 0, 0, &mut codes).is_err() {
1638        return Ok(None);
1639    }
1640    Ok(Some(codes))
1641}
1642
1643fn assign_huffman_codes(
1644    nodes: &[HuffmanNode],
1645    node_index: usize,
1646    bits: u32,
1647    bit_len: u8,
1648    codes: &mut [Option<HuffmanCode>],
1649) -> Result<()> {
1650    match nodes[node_index].kind {
1651        HuffmanNodeKind::Leaf(symbol) => {
1652            let bit_len = bit_len.max(1);
1653            if bit_len > 31 {
1654                return Err(Error::InvalidArgument(
1655                    "Huffman code length exceeds Lerc2 limits".into(),
1656                ));
1657            }
1658            codes[symbol as usize] = Some(HuffmanCode { bit_len, bits });
1659        }
1660        HuffmanNodeKind::Branch { left, right } => {
1661            if bit_len >= 31 {
1662                return Err(Error::InvalidArgument(
1663                    "Huffman code length exceeds Lerc2 limits".into(),
1664                ));
1665            }
1666            assign_huffman_codes(nodes, left, bits << 1, bit_len + 1, codes)?;
1667            assign_huffman_codes(nodes, right, (bits << 1) | 1, bit_len + 1, codes)?;
1668        }
1669    }
1670    Ok(())
1671}
1672
1673fn build_huffman_table_bytes(codes: &[Option<HuffmanCode>]) -> Result<Vec<u8>> {
1674    let Some(first_symbol) = codes.iter().position(Option::is_some) else {
1675        return Err(Error::InvalidArgument(
1676            "Huffman code table cannot be empty".into(),
1677        ));
1678    };
1679    let last_symbol = codes.iter().rposition(Option::is_some).unwrap() + 1;
1680    let code_lengths: Vec<u32> = codes[first_symbol..last_symbol]
1681        .iter()
1682        .map(|code| code.map_or(0, |code| code.bit_len as u32))
1683        .collect();
1684    let mut out = Vec::new();
1685    out.extend_from_slice(&2i32.to_le_bytes());
1686    out.extend_from_slice(&(codes.len() as i32).to_le_bytes());
1687    out.extend_from_slice(&(first_symbol as i32).to_le_bytes());
1688    out.extend_from_slice(&(last_symbol as i32).to_le_bytes());
1689    write_raw_bitstuff_block(&mut out, &code_lengths)?;
1690
1691    let mut writer = MsbBitWriter::default();
1692    for code in codes[first_symbol..last_symbol].iter().flatten() {
1693        writer.push_bits(code.bits, code.bit_len)?;
1694    }
1695    out.extend_from_slice(&writer.into_aligned_bytes());
1696    Ok(out)
1697}
1698
1699fn write_raw_bitstuff_block(out: &mut Vec<u8>, values: &[u32]) -> Result<()> {
1700    let max_value = values.iter().copied().max().unwrap_or(0);
1701    let bits = bits_required(max_value as usize);
1702    if bits > 31 {
1703        return Err(Error::InvalidArgument(
1704            "bit-stuffed payload exceeds the Lerc2 bit width limit".into(),
1705        ));
1706    }
1707    let (count_code, count_bytes) = count_field(values.len())?;
1708    out.push((count_code << 6) | bits);
1709    append_count(out, values.len(), count_bytes)?;
1710    if bits != 0 {
1711        pack_lsb_bits_into(values, bits, out);
1712    }
1713    Ok(())
1714}
1715
1716#[derive(Debug, Clone)]
1717struct BitstuffTile {
1718    offset: f64,
1719    offset_type: DataType,
1720    type_code: u8,
1721    payload_len: usize,
1722}
1723
1724fn try_bitstuff_tile(
1725    values: &[f64],
1726    offset: f64,
1727    max_value: f64,
1728    max_z_error: f64,
1729    base_type: DataType,
1730    quantized: &mut Vec<u32>,
1731    payload: &mut Vec<u8>,
1732) -> Result<Option<BitstuffTile>> {
1733    if max_z_error <= 0.0 {
1734        return Ok(None);
1735    }
1736    let scale = 2.0 * max_z_error;
1737    let nmax_f = ((max_value - offset) / scale).ceil();
1738    if !nmax_f.is_finite() || !(0.0..=(u32::MAX as f64)).contains(&nmax_f) {
1739        return Ok(None);
1740    }
1741    let nmax = nmax_f as u32;
1742    if nmax == 0 {
1743        return Ok(None);
1744    }
1745
1746    let epsilon = max_z_error.abs() * 1e-12 + 1e-12;
1747    quantized.clear();
1748    quantized.reserve(values.len());
1749    let mut max_quantized = 0u32;
1750    for &value in values {
1751        let quantized_value = ((value - offset) / scale).round().clamp(0.0, nmax as f64) as u32;
1752        let reconstructed = if (quantized_value as f64) < nmax as f64 {
1753            offset + quantized_value as f64 * scale
1754        } else {
1755            max_value
1756        };
1757        if (reconstructed - value).abs() > max_z_error + epsilon {
1758            return Ok(None);
1759        }
1760        max_quantized = max_quantized.max(quantized_value);
1761        quantized.push(quantized_value);
1762    }
1763
1764    let bits = bits_required(max_quantized as usize);
1765    if bits == 0 || bits > 31 {
1766        return Ok(None);
1767    }
1768
1769    let (count_code, count_bytes) = count_field(values.len())?;
1770    let (type_code, offset_type) = reduce_data_type(offset, base_type)?;
1771    payload.clear();
1772    payload.reserve(1 + count_bytes + (values.len() * bits as usize).div_ceil(8));
1773    payload.push((count_code << 6) | bits);
1774    append_count(payload, values.len(), count_bytes)?;
1775    pack_lsb_bits_into(quantized, bits, payload);
1776    Ok(Some(BitstuffTile {
1777        offset,
1778        offset_type,
1779        type_code,
1780        payload_len: payload.len(),
1781    }))
1782}
1783
1784fn count_field(count: usize) -> Result<(u8, usize)> {
1785    if count <= u8::MAX as usize {
1786        Ok((2, 1))
1787    } else if count <= u16::MAX as usize {
1788        Ok((1, 2))
1789    } else if count <= u32::MAX as usize {
1790        Ok((0, 4))
1791    } else {
1792        Err(Error::InvalidArgument(
1793            "tile valid-value count exceeds u32".into(),
1794        ))
1795    }
1796}
1797
1798fn append_count(out: &mut Vec<u8>, count: usize, count_bytes: usize) -> Result<()> {
1799    match count_bytes {
1800        1 => out.push(
1801            u8::try_from(count)
1802                .map_err(|_| Error::InvalidArgument("count does not fit in u8".into()))?,
1803        ),
1804        2 => out.extend_from_slice(
1805            &u16::try_from(count)
1806                .map_err(|_| Error::InvalidArgument("count does not fit in u16".into()))?
1807                .to_le_bytes(),
1808        ),
1809        4 => out.extend_from_slice(
1810            &u32::try_from(count)
1811                .map_err(|_| Error::InvalidArgument("count does not fit in u32".into()))?
1812                .to_le_bytes(),
1813        ),
1814        _ => {
1815            return Err(Error::InvalidArgument(
1816                "unsupported count field width".into(),
1817            ))
1818        }
1819    }
1820    Ok(())
1821}
1822
1823fn pack_lsb_bits_into(values: &[u32], bits_per_value: u8, out: &mut Vec<u8>) {
1824    let total_bits = values.len() * bits_per_value as usize;
1825    let byte_len = total_bits.div_ceil(8);
1826    let base = out.len();
1827    out.resize(base + byte_len, 0);
1828    let mut bit_offset = 0usize;
1829    for &value in values {
1830        for bit in 0..bits_per_value {
1831            if ((value >> bit) & 1) != 0 {
1832                let byte_index = bit_offset / 8;
1833                let bit_index = bit_offset % 8;
1834                out[base + byte_index] |= 1 << bit_index;
1835            }
1836            bit_offset += 1;
1837        }
1838    }
1839}
1840
1841fn reduce_data_type(value: f64, data_type: DataType) -> Result<(u8, DataType)> {
1842    let reduced = match data_type {
1843        DataType::I8 | DataType::U8 => (0, data_type),
1844        DataType::I16 => {
1845            if fits_i8(value) {
1846                (2, DataType::I8)
1847            } else if fits_u8(value) {
1848                (1, DataType::U8)
1849            } else {
1850                (0, DataType::I16)
1851            }
1852        }
1853        DataType::U16 => {
1854            if fits_u8(value) {
1855                (1, DataType::U8)
1856            } else {
1857                (0, DataType::U16)
1858            }
1859        }
1860        DataType::I32 => {
1861            if fits_i8(value) {
1862                (3, DataType::I8)
1863            } else if fits_i16(value) {
1864                (2, DataType::I16)
1865            } else if fits_u16(value) {
1866                (1, DataType::U16)
1867            } else {
1868                (0, DataType::I32)
1869            }
1870        }
1871        DataType::U32 => {
1872            if fits_u8(value) {
1873                (2, DataType::U8)
1874            } else if fits_u16(value) {
1875                (1, DataType::U16)
1876            } else {
1877                (0, DataType::U32)
1878            }
1879        }
1880        DataType::F32 => {
1881            if fits_u8(value) {
1882                (2, DataType::U8)
1883            } else if fits_i16(value) {
1884                (1, DataType::I16)
1885            } else {
1886                (0, DataType::F32)
1887            }
1888        }
1889        DataType::F64 => {
1890            if fits_i16(value) {
1891                (3, DataType::I16)
1892            } else if fits_i32(value) {
1893                (2, DataType::I32)
1894            } else if fits_f32(value) {
1895                (1, DataType::F32)
1896            } else {
1897                (0, DataType::F64)
1898            }
1899        }
1900    };
1901    Ok(reduced)
1902}
1903
1904fn fits_i8(value: f64) -> bool {
1905    (i8::MIN as f64..=i8::MAX as f64).contains(&value) && (value as i8) as f64 == value
1906}
1907
1908fn fits_u8(value: f64) -> bool {
1909    (u8::MIN as f64..=u8::MAX as f64).contains(&value) && (value as u8) as f64 == value
1910}
1911
1912fn fits_i16(value: f64) -> bool {
1913    (i16::MIN as f64..=i16::MAX as f64).contains(&value) && (value as i16) as f64 == value
1914}
1915
1916fn fits_u16(value: f64) -> bool {
1917    (u16::MIN as f64..=u16::MAX as f64).contains(&value) && (value as u16) as f64 == value
1918}
1919
1920fn fits_i32(value: f64) -> bool {
1921    (i32::MIN as f64..=i32::MAX as f64).contains(&value) && (value as i32) as f64 == value
1922}
1923
1924fn fits_f32(value: f64) -> bool {
1925    (value as f32) as f64 == value
1926}
1927
1928fn huffman_sample_value(value: f64, data_type: DataType) -> i32 {
1929    match data_type {
1930        DataType::I8 => value as i8 as i32,
1931        DataType::U8 => value as u8 as i32,
1932        _ => unreachable!("Huffman only supports 8-bit sample types"),
1933    }
1934}
1935
1936fn huffman_symbol(value: i32, data_type: DataType) -> usize {
1937    match data_type {
1938        DataType::I8 => (value as i8 as i16 + 128) as usize,
1939        DataType::U8 => value as u8 as usize,
1940        _ => unreachable!("Huffman only supports 8-bit sample types"),
1941    }
1942}
1943
1944fn huffman_delta_symbol(delta: i32, data_type: DataType) -> usize {
1945    match data_type {
1946        DataType::I8 => (((delta & 0xFF) as u8) as i8 as i16 + 128) as usize,
1947        DataType::U8 => ((delta & 0xFF) as u8) as usize,
1948        _ => unreachable!("Huffman only supports 8-bit sample types"),
1949    }
1950}
1951
1952fn words_to_le_bytes(words: &[u32]) -> Vec<u8> {
1953    let mut bytes = Vec::with_capacity(words.len() * 4);
1954    for &word in words {
1955        bytes.extend_from_slice(&word.to_le_bytes());
1956    }
1957    bytes
1958}
1959
1960fn pack_mask_bitset(mask: &[u8], pixel_count: usize) -> Result<Vec<u8>> {
1961    if mask.len() != pixel_count {
1962        return Err(Error::InvalidArgument(
1963            "mask length does not match the raster dimensions".into(),
1964        ));
1965    }
1966
1967    let bitset_len = pixel_count.div_ceil(8);
1968    let mut bitset = vec![0u8; bitset_len];
1969    for (index, &value) in mask.iter().enumerate() {
1970        if value != 0 {
1971            bitset[index >> 3] |= 1 << (7 - (index & 7));
1972        }
1973    }
1974    Ok(bitset)
1975}
1976
1977fn emit_mask_literal_chunks<F>(bytes: &[u8], emit_literal: &mut F) -> Result<()>
1978where
1979    F: FnMut(&[u8]) -> Result<()>,
1980{
1981    let mut offset = 0usize;
1982    while offset < bytes.len() {
1983        let chunk = (bytes.len() - offset).min(i16::MAX as usize);
1984        emit_literal(&bytes[offset..offset + chunk])?;
1985        offset += chunk;
1986    }
1987    Ok(())
1988}
1989
1990enum MaskRleSegment<'a> {
1991    Literal(&'a [u8]),
1992    Repeat { value: u8, count: usize },
1993}
1994
1995fn emit_mask_rle_segments<F>(bitset: &[u8], mut emit: F) -> Result<()>
1996where
1997    F: FnMut(MaskRleSegment<'_>) -> Result<()>,
1998{
1999    let mut literal_start = 0usize;
2000    let mut offset = 0usize;
2001
2002    while offset < bitset.len() {
2003        let value = bitset[offset];
2004        let mut run_end = offset + 1;
2005        while run_end < bitset.len() && bitset[run_end] == value {
2006            run_end += 1;
2007        }
2008
2009        let run_len = run_end - offset;
2010        let repeat_threshold = if literal_start == offset && run_end == bitset.len() {
2011            2
2012        } else if literal_start == offset || run_end == bitset.len() {
2013            4
2014        } else {
2015            6
2016        };
2017
2018        if run_len >= repeat_threshold {
2019            emit_mask_literal_chunks(&bitset[literal_start..offset], &mut |bytes| {
2020                emit(MaskRleSegment::Literal(bytes))
2021            })?;
2022
2023            let mut emitted = 0usize;
2024            while emitted < run_len {
2025                let chunk = (run_len - emitted).min(i16::MAX as usize);
2026                emit(MaskRleSegment::Repeat {
2027                    value,
2028                    count: chunk,
2029                })?;
2030                emitted += chunk;
2031            }
2032
2033            literal_start = run_end;
2034        }
2035
2036        offset = run_end;
2037    }
2038
2039    emit_mask_literal_chunks(&bitset[literal_start..], &mut |bytes| {
2040        emit(MaskRleSegment::Literal(bytes))
2041    })
2042}
2043
2044fn write_mask_rle(
2045    sink: &mut impl ByteSink,
2046    mask: MaskKind<'_>,
2047    pixel_count: usize,
2048    valid_pixel_count: usize,
2049) -> Result<()> {
2050    if valid_pixel_count == 0 || valid_pixel_count == pixel_count {
2051        return Ok(());
2052    }
2053
2054    let MaskKind::Explicit(mask) = mask else {
2055        return Ok(());
2056    };
2057    let bitset = pack_mask_bitset(mask, pixel_count)?;
2058    emit_mask_rle_segments(&bitset, |segment| match segment {
2059        MaskRleSegment::Literal(bytes) => {
2060            write_i16(sink, bytes.len() as i16)?;
2061            sink.extend_from_slice(bytes)
2062        }
2063        MaskRleSegment::Repeat { value, count } => {
2064            write_i16(sink, -(count as i16))?;
2065            sink.push(value)
2066        }
2067    })?;
2068    write_i16(sink, i16::MIN)
2069}
2070
2071fn explicit_mask_payload_len(
2072    mask: &[u8],
2073    pixel_count: usize,
2074    valid_pixel_count: usize,
2075) -> Result<usize> {
2076    if valid_pixel_count == 0 || valid_pixel_count == pixel_count {
2077        return Ok(0);
2078    }
2079
2080    let bitset = pack_mask_bitset(mask, pixel_count)?;
2081    let mut len = 2usize;
2082    emit_mask_rle_segments(&bitset, |segment| {
2083        len = match segment {
2084            MaskRleSegment::Literal(bytes) => len
2085                .checked_add(2)
2086                .and_then(|len| len.checked_add(bytes.len())),
2087            MaskRleSegment::Repeat { .. } => len.checked_add(3),
2088        }
2089        .ok_or_else(|| Error::InvalidArgument("mask payload length overflows usize".into()))?;
2090        Ok(())
2091    })?;
2092    Ok(len)
2093}
2094
2095fn depth_range_len(analysis: &RasterAnalysis) -> Result<usize> {
2096    if analysis.min_values.is_none() {
2097        return Ok(0);
2098    }
2099    (analysis.depth as usize)
2100        .checked_mul(2)
2101        .and_then(|len| len.checked_mul(analysis.data_type.byte_len()))
2102        .ok_or_else(|| Error::InvalidArgument("range byte count overflows usize".into()))
2103}
2104
2105fn tile_count(width: usize, height: usize, options: EncodeOptions) -> Result<usize> {
2106    let micro = options.micro_block_size as usize;
2107    let num_blocks_x = width.div_ceil(micro);
2108    let num_blocks_y = height.div_ceil(micro);
2109    num_blocks_x
2110        .checked_mul(num_blocks_y)
2111        .ok_or_else(|| Error::InvalidArgument("tile count overflows usize".into()))
2112}
2113
2114fn header_len(version: i32) -> usize {
2115    if version >= 6 {
2116        FIXED_HEADER_LEN_V6
2117    } else {
2118        FIXED_HEADER_LEN_V4_V5
2119    }
2120}
2121
2122fn body_prefix_len(data_type: DataType, max_z_error: f64, version: i32) -> usize {
2123    1 + usize::from(needs_encode_mode_flag(data_type, max_z_error, version))
2124}
2125
2126fn needs_encode_mode_flag(data_type: DataType, max_z_error: f64, version: i32) -> bool {
2127    supports_integer_huffman(data_type, max_z_error)
2128        || (version >= VERSION_6
2129            && matches!(data_type, DataType::F32 | DataType::F64)
2130            && max_z_error == 0.0)
2131}
2132
2133fn supports_integer_huffman(data_type: DataType, max_z_error: f64) -> bool {
2134    matches!(data_type, DataType::I8 | DataType::U8) && (max_z_error - 0.5).abs() < 1e-5
2135}
2136
2137fn has_per_depth_constant(analysis: &RasterAnalysis) -> bool {
2138    analysis
2139        .min_values
2140        .as_ref()
2141        .zip(analysis.max_values.as_ref())
2142        .map(|(mins, maxs)| mins == maxs)
2143        .unwrap_or(false)
2144}
2145
2146fn write_value_as(sink: &mut impl ByteSink, value: f64, data_type: DataType) -> Result<()> {
2147    match data_type {
2148        DataType::I8 => sink.push((value as i8) as u8),
2149        DataType::U8 => sink.push(value as u8),
2150        DataType::I16 => sink.extend_from_slice(&(value as i16).to_le_bytes()),
2151        DataType::U16 => sink.extend_from_slice(&(value as u16).to_le_bytes()),
2152        DataType::I32 => sink.extend_from_slice(&(value as i32).to_le_bytes()),
2153        DataType::U32 => sink.extend_from_slice(&(value as u32).to_le_bytes()),
2154        DataType::F32 => sink.extend_from_slice(&(value as f32).to_le_bytes()),
2155        DataType::F64 => sink.extend_from_slice(&value.to_le_bytes()),
2156    }
2157}
2158
2159fn write_u32(sink: &mut impl ByteSink, value: u32) -> Result<()> {
2160    sink.extend_from_slice(&value.to_le_bytes())
2161}
2162
2163fn write_i32(sink: &mut impl ByteSink, value: i32) -> Result<()> {
2164    sink.extend_from_slice(&value.to_le_bytes())
2165}
2166
2167fn write_i16(sink: &mut impl ByteSink, value: i16) -> Result<()> {
2168    sink.extend_from_slice(&value.to_le_bytes())
2169}
2170
2171fn write_f64(sink: &mut impl ByteSink, value: f64) -> Result<()> {
2172    sink.extend_from_slice(&value.to_le_bytes())
2173}
2174
2175fn version_with_no_data(analysis: &RasterAnalysis, version: i32) -> i32 {
2176    if analysis.no_data_value.is_some() {
2177        VERSION_6
2178    } else {
2179        version
2180    }
2181}
2182
2183fn tile_header(check_code: u8, encoding: u8) -> u8 {
2184    ((check_code & 15) << 2) | (encoding & 3)
2185}
2186
2187fn pixel_is_valid(mask: Option<&[u8]>, pixel: usize) -> bool {
2188    mask.map(|mask| mask[pixel] != 0).unwrap_or(true)
2189}