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}