Skip to main content

oxigdal_core/buffer/
band_iterator.rs

1//! Band iteration for multi-band raster datasets.
2//!
3//! Provides [`BandIterator`] for lazy per-band iteration over raster data,
4//! supporting all [`PixelLayout`] memory organizations (BSQ, BIL, BIP, Tiled).
5//!
6//! # Examples
7//!
8//! ```
9//! use oxigdal_core::buffer::{MultiBandBuffer, RasterBuffer};
10//! use oxigdal_core::types::{RasterDataType, ColorInterpretation, NoDataValue, PixelLayout};
11//!
12//! // Create an RGB image (3 bands, 100×100)
13//! let band_r = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
14//! let band_g = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
15//! let band_b = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
16//!
17//! let multi = MultiBandBuffer::from_bands(
18//!     vec![band_r, band_g, band_b],
19//!     vec![ColorInterpretation::Red, ColorInterpretation::Green, ColorInterpretation::Blue],
20//! ).expect("should create multi-band buffer");
21//!
22//! for band in multi.bands() {
23//!     println!("Band {}: {} ({:?})", band.index(), band.buffer().width(), band.color());
24//! }
25//! ```
26
27#[cfg(not(feature = "std"))]
28#[allow(unused_imports)]
29use crate::compat::*;
30use crate::error::{OxiGdalError, Result};
31use crate::types::{ColorInterpretation, NoDataValue, PixelLayout, RasterDataType};
32
33use super::{BufferStatistics, RasterBuffer};
34
35/// A multi-band raster buffer holding multiple single-band buffers.
36///
37/// All bands must share identical dimensions and data type.
38#[derive(Debug, Clone)]
39pub struct MultiBandBuffer {
40    /// Per-band pixel data (one `RasterBuffer` per band).
41    bands: Vec<RasterBuffer>,
42    /// Per-band color interpretation.
43    colors: Vec<ColorInterpretation>,
44    /// Per-band nodata values (if different from the buffer's own nodata).
45    nodata_overrides: Vec<Option<NoDataValue>>,
46    /// Pixel layout describing the original memory organization.
47    layout: PixelLayout,
48}
49
50impl MultiBandBuffer {
51    /// Create a `MultiBandBuffer` from individual band buffers and color interpretations.
52    ///
53    /// All bands must have the same width, height, and data type.
54    ///
55    /// # Errors
56    ///
57    /// Returns an error if bands have mismatched dimensions or data types, or if
58    /// the color interpretation count doesn't match the band count.
59    pub fn from_bands(bands: Vec<RasterBuffer>, colors: Vec<ColorInterpretation>) -> Result<Self> {
60        if bands.is_empty() {
61            return Err(OxiGdalError::InvalidParameter {
62                parameter: "bands",
63                message: "At least one band is required".to_string(),
64            });
65        }
66        if colors.len() != bands.len() {
67            return Err(OxiGdalError::InvalidParameter {
68                parameter: "colors",
69                message: format!(
70                    "Color interpretation count ({}) must match band count ({})",
71                    colors.len(),
72                    bands.len()
73                ),
74            });
75        }
76
77        let w = bands[0].width();
78        let h = bands[0].height();
79        let dt = bands[0].data_type();
80
81        for (i, band) in bands.iter().enumerate().skip(1) {
82            if band.width() != w || band.height() != h {
83                return Err(OxiGdalError::InvalidParameter {
84                    parameter: "bands",
85                    message: format!(
86                        "Band {} dimensions {}x{} differ from band 0 dimensions {}x{}",
87                        i,
88                        band.width(),
89                        band.height(),
90                        w,
91                        h
92                    ),
93                });
94            }
95            if band.data_type() != dt {
96                return Err(OxiGdalError::InvalidParameter {
97                    parameter: "bands",
98                    message: format!(
99                        "Band {} data type {:?} differs from band 0 data type {:?}",
100                        i,
101                        band.data_type(),
102                        dt
103                    ),
104                });
105            }
106        }
107
108        let nodata_overrides = vec![None; bands.len()];
109
110        Ok(Self {
111            bands,
112            colors,
113            nodata_overrides,
114            layout: PixelLayout::BandSequential,
115        })
116    }
117
118    /// Create a `MultiBandBuffer` from interleaved (BIP) data.
119    ///
120    /// Deinterleaves band-interleaved-by-pixel data into separate band buffers.
121    ///
122    /// # Errors
123    ///
124    /// Returns an error if the data size doesn't match dimensions × band_count × type_size.
125    pub fn from_interleaved(
126        data: &[u8],
127        width: u64,
128        height: u64,
129        band_count: u32,
130        data_type: RasterDataType,
131        nodata: NoDataValue,
132    ) -> Result<Self> {
133        let pixel_size = data_type.size_bytes();
134        let expected = (width * height * band_count as u64 * pixel_size as u64) as usize;
135        if data.len() != expected {
136            return Err(OxiGdalError::InvalidParameter {
137                parameter: "data",
138                message: format!(
139                    "Interleaved data size {} differs from expected {} ({}x{}x{}x{})",
140                    data.len(),
141                    expected,
142                    width,
143                    height,
144                    band_count,
145                    pixel_size
146                ),
147            });
148        }
149
150        let pixels = width * height;
151        let band_size = (pixels * pixel_size as u64) as usize;
152        let bc = band_count as usize;
153        let ps = pixel_size;
154
155        let mut band_buffers = Vec::with_capacity(bc);
156        for b in 0..bc {
157            let mut band_data = vec![0u8; band_size];
158            for pixel_idx in 0..(pixels as usize) {
159                let src_offset = pixel_idx * bc * ps + b * ps;
160                let dst_offset = pixel_idx * ps;
161                band_data[dst_offset..dst_offset + ps]
162                    .copy_from_slice(&data[src_offset..src_offset + ps]);
163            }
164            band_buffers.push(RasterBuffer::new(
165                band_data, width, height, data_type, nodata,
166            )?);
167        }
168
169        let colors = default_color_interpretation(bc);
170
171        Ok(Self {
172            bands: band_buffers,
173            colors,
174            nodata_overrides: vec![None; bc],
175            layout: PixelLayout::BandInterleavedByPixel,
176        })
177    }
178
179    /// Create a `MultiBandBuffer` from band-sequential (BSQ) data.
180    ///
181    /// # Errors
182    ///
183    /// Returns an error if the data size doesn't match dimensions × band_count × type_size.
184    pub fn from_bsq(
185        data: &[u8],
186        width: u64,
187        height: u64,
188        band_count: u32,
189        data_type: RasterDataType,
190        nodata: NoDataValue,
191    ) -> Result<Self> {
192        let pixel_size = data_type.size_bytes();
193        let expected = (width * height * band_count as u64 * pixel_size as u64) as usize;
194        if data.len() != expected {
195            return Err(OxiGdalError::InvalidParameter {
196                parameter: "data",
197                message: format!(
198                    "BSQ data size {} differs from expected {}",
199                    data.len(),
200                    expected
201                ),
202            });
203        }
204
205        let band_size = (width * height * pixel_size as u64) as usize;
206        let bc = band_count as usize;
207        let mut band_buffers = Vec::with_capacity(bc);
208
209        for b in 0..bc {
210            let start = b * band_size;
211            let band_data = data[start..start + band_size].to_vec();
212            band_buffers.push(RasterBuffer::new(
213                band_data, width, height, data_type, nodata,
214            )?);
215        }
216
217        let colors = default_color_interpretation(bc);
218
219        Ok(Self {
220            bands: band_buffers,
221            colors,
222            nodata_overrides: vec![None; bc],
223            layout: PixelLayout::BandSequential,
224        })
225    }
226
227    /// Create a `MultiBandBuffer` from Band-Interleaved-by-Line (BIL) data.
228    ///
229    /// Rearranges BIL-layout bytes into the internal BSQ (band-sequential) representation.
230    ///
231    /// BIL layout: for each row `r` and then each band `b`, the `width * pixel_size` bytes
232    /// for that (row, band) slice are contiguous:
233    ///
234    /// ```text
235    /// [row0_band0_col0..colN, row0_band1_col0..colN, …, row0_bandN_col0..colN,
236    ///  row1_band0_col0..colN, row1_band1_col0..colN, …, rowH_bandN_col0..colN]
237    /// ```
238    ///
239    /// # Errors
240    ///
241    /// Returns an error if the data size doesn't match `width × height × band_count × pixel_size`.
242    pub fn from_bil(
243        data: &[u8],
244        width: u64,
245        height: u64,
246        band_count: u32,
247        data_type: RasterDataType,
248        nodata: NoDataValue,
249    ) -> Result<Self> {
250        let pixel_size = data_type.size_bytes();
251        let bc = band_count as usize;
252        let w = width as usize;
253        let h = height as usize;
254        let expected = bc * h * w * pixel_size;
255        if data.len() != expected {
256            return Err(OxiGdalError::InvalidParameter {
257                parameter: "data",
258                message: format!(
259                    "BIL data size {} differs from expected {} ({}x{}x{}x{})",
260                    data.len(),
261                    expected,
262                    width,
263                    height,
264                    band_count,
265                    pixel_size
266                ),
267            });
268        }
269
270        // BSQ band buffer size: one contiguous block per band
271        let band_byte_size = h * w * pixel_size;
272        let mut band_buffers: Vec<RasterBuffer> = Vec::with_capacity(bc);
273
274        for b in 0..bc {
275            let mut bsq_data = vec![0u8; band_byte_size];
276            for r in 0..h {
277                // BIL source offset: row r, band b
278                let bil_offset = (r * bc + b) * w * pixel_size;
279                // BSQ destination offset: band b, row r
280                let bsq_offset = r * w * pixel_size;
281                let row_bytes = w * pixel_size;
282                bsq_data[bsq_offset..bsq_offset + row_bytes]
283                    .copy_from_slice(&data[bil_offset..bil_offset + row_bytes]);
284            }
285            band_buffers.push(RasterBuffer::new(
286                bsq_data, width, height, data_type, nodata,
287            )?);
288        }
289
290        let colors = default_color_interpretation(bc);
291
292        Ok(Self {
293            bands: band_buffers,
294            colors,
295            nodata_overrides: vec![None; bc],
296            layout: PixelLayout::BandInterleavedByLine,
297        })
298    }
299
300    /// Returns the number of bands.
301    #[must_use]
302    pub fn band_count(&self) -> u32 {
303        self.bands.len() as u32
304    }
305
306    /// Returns the width in pixels (same for all bands).
307    #[must_use]
308    pub fn width(&self) -> u64 {
309        self.bands.first().map_or(0, |b| b.width())
310    }
311
312    /// Returns the height in pixels (same for all bands).
313    #[must_use]
314    pub fn height(&self) -> u64 {
315        self.bands.first().map_or(0, |b| b.height())
316    }
317
318    /// Returns the data type (same for all bands).
319    #[must_use]
320    pub fn data_type(&self) -> RasterDataType {
321        self.bands
322            .first()
323            .map_or(RasterDataType::UInt8, |b| b.data_type())
324    }
325
326    /// Returns the pixel layout.
327    #[must_use]
328    pub fn layout(&self) -> PixelLayout {
329        self.layout
330    }
331
332    /// Set per-band nodata override value.
333    pub fn set_band_nodata(&mut self, band: u32, nodata: NoDataValue) -> Result<()> {
334        let idx = band as usize;
335        if idx >= self.bands.len() {
336            return Err(OxiGdalError::InvalidParameter {
337                parameter: "band",
338                message: format!("Band index {} out of range (0..{})", band, self.bands.len()),
339            });
340        }
341        self.nodata_overrides[idx] = Some(nodata);
342        Ok(())
343    }
344
345    /// Get a reference to a specific band buffer.
346    ///
347    /// # Errors
348    ///
349    /// Returns an error if `band` is out of range.
350    pub fn band(&self, band: u32) -> Result<BandRef<'_>> {
351        let idx = band as usize;
352        if idx >= self.bands.len() {
353            return Err(OxiGdalError::InvalidParameter {
354                parameter: "band",
355                message: format!("Band index {} out of range (0..{})", band, self.bands.len()),
356            });
357        }
358        Ok(BandRef {
359            index: band,
360            buffer: &self.bands[idx],
361            color: self.colors[idx],
362            nodata_override: self.nodata_overrides[idx],
363        })
364    }
365
366    /// Get a mutable reference to a specific band buffer.
367    ///
368    /// # Errors
369    ///
370    /// Returns an error if `band` is out of range.
371    pub fn band_mut(&mut self, band: u32) -> Result<&mut RasterBuffer> {
372        let idx = band as usize;
373        if idx >= self.bands.len() {
374            return Err(OxiGdalError::InvalidParameter {
375                parameter: "band",
376                message: format!("Band index {} out of range (0..{})", band, self.bands.len()),
377            });
378        }
379        Ok(&mut self.bands[idx])
380    }
381
382    /// Returns a lazy iterator over all bands.
383    #[must_use]
384    pub fn bands(&self) -> BandIterator<'_> {
385        BandIterator {
386            multi: self,
387            current: 0,
388        }
389    }
390
391    /// Interleave all bands into a single BIP byte buffer.
392    ///
393    /// Returns pixels in band-interleaved-by-pixel order: R₁G₁B₁ R₂G₂B₂ ...
394    #[must_use]
395    pub fn to_interleaved(&self) -> Vec<u8> {
396        let ps = self.data_type().size_bytes();
397        let bc = self.bands.len();
398        let pixels = (self.width() * self.height()) as usize;
399        let mut out = vec![0u8; pixels * bc * ps];
400
401        for (b, band) in self.bands.iter().enumerate() {
402            let src = band.as_bytes();
403            for pixel_idx in 0..pixels {
404                let dst_off = pixel_idx * bc * ps + b * ps;
405                let src_off = pixel_idx * ps;
406                out[dst_off..dst_off + ps].copy_from_slice(&src[src_off..src_off + ps]);
407            }
408        }
409
410        out
411    }
412
413    /// Flatten all bands into BSQ byte buffer.
414    ///
415    /// Returns band-sequential data: all of band 0, then all of band 1, etc.
416    #[must_use]
417    pub fn to_bsq(&self) -> Vec<u8> {
418        let mut out = Vec::with_capacity(self.bands.iter().map(|b| b.as_bytes().len()).sum());
419        for band in &self.bands {
420            out.extend_from_slice(band.as_bytes());
421        }
422        out
423    }
424
425    /// Emits the buffer data in Band-Interleaved-by-Line (BIL) layout.
426    ///
427    /// BIL layout: for each row `r` and then each band `b`, all `width * pixel_size` bytes
428    /// for that (row, band) slice are written contiguously:
429    ///
430    /// ```text
431    /// [row0_band0, row0_band1, …, row0_bandN, row1_band0, …, rowH_bandN]
432    /// ```
433    #[must_use]
434    pub fn to_bil(&self) -> Vec<u8> {
435        let bc = self.bands.len();
436        let pixel_size = self.data_type().size_bytes();
437        let h = self.height() as usize;
438        let w = self.width() as usize;
439        let total = bc * h * w * pixel_size;
440        let mut out = vec![0u8; total];
441
442        for (b, band) in self.bands.iter().enumerate() {
443            let src = band.as_bytes();
444            for r in 0..h {
445                // BIL destination offset for (row r, band b)
446                let bil_offset = (r * bc + b) * w * pixel_size;
447                // BSQ source offset for (band b, row r)
448                let bsq_offset = r * w * pixel_size;
449                let row_bytes = w * pixel_size;
450                out[bil_offset..bil_offset + row_bytes]
451                    .copy_from_slice(&src[bsq_offset..bsq_offset + row_bytes]);
452            }
453        }
454
455        out
456    }
457
458    /// Compute statistics for all bands.
459    ///
460    /// # Errors
461    ///
462    /// Returns an error if statistics computation fails for any band.
463    pub fn compute_all_statistics(&self) -> Result<Vec<BufferStatistics>> {
464        self.bands.iter().map(|b| b.compute_statistics()).collect()
465    }
466
467    /// Consume and return the inner band buffers.
468    #[must_use]
469    pub fn into_bands(self) -> Vec<RasterBuffer> {
470        self.bands
471    }
472}
473
474/// A borrowed reference to a single band within a [`MultiBandBuffer`].
475#[derive(Debug, Clone, Copy)]
476pub struct BandRef<'a> {
477    /// 0-based band index.
478    index: u32,
479    /// Reference to the band's pixel buffer.
480    buffer: &'a RasterBuffer,
481    /// Color interpretation.
482    color: ColorInterpretation,
483    /// Per-band nodata override (if set).
484    nodata_override: Option<NoDataValue>,
485}
486
487impl<'a> BandRef<'a> {
488    /// Returns the 0-based band index.
489    #[must_use]
490    pub fn index(&self) -> u32 {
491        self.index
492    }
493
494    /// Returns the 1-based band index (GDAL convention).
495    #[must_use]
496    pub fn gdal_index(&self) -> u32 {
497        self.index + 1
498    }
499
500    /// Returns the band's pixel buffer.
501    #[must_use]
502    pub fn buffer(&self) -> &'a RasterBuffer {
503        self.buffer
504    }
505
506    /// Returns the color interpretation.
507    #[must_use]
508    pub fn color(&self) -> ColorInterpretation {
509        self.color
510    }
511
512    /// Returns the effective nodata value (override or buffer default).
513    #[must_use]
514    pub fn nodata(&self) -> NoDataValue {
515        self.nodata_override.unwrap_or(self.buffer.nodata())
516    }
517
518    /// Get a typed slice of the band's pixel data.
519    ///
520    /// # Errors
521    ///
522    /// Returns an error if `T`'s size doesn't match the band's data type.
523    pub fn as_slice<T: Copy + 'static>(&self) -> Result<&[T]> {
524        self.buffer.as_slice::<T>()
525    }
526}
527
528/// Lazy iterator over bands of a [`MultiBandBuffer`].
529///
530/// Yields [`BandRef`] for each band in order (band 0, 1, 2, ...).
531/// Does not copy pixel data — references the underlying buffers.
532pub struct BandIterator<'a> {
533    multi: &'a MultiBandBuffer,
534    current: u32,
535}
536
537impl<'a> Iterator for BandIterator<'a> {
538    type Item = BandRef<'a>;
539
540    fn next(&mut self) -> Option<Self::Item> {
541        if self.current >= self.multi.band_count() {
542            return None;
543        }
544        let idx = self.current as usize;
545        let band = BandRef {
546            index: self.current,
547            buffer: &self.multi.bands[idx],
548            color: self.multi.colors[idx],
549            nodata_override: self.multi.nodata_overrides[idx],
550        };
551        self.current += 1;
552        Some(band)
553    }
554
555    fn size_hint(&self) -> (usize, Option<usize>) {
556        let remaining = (self.multi.band_count() - self.current) as usize;
557        (remaining, Some(remaining))
558    }
559}
560
561impl ExactSizeIterator for BandIterator<'_> {}
562
563/// Default color interpretation based on band count.
564fn default_color_interpretation(band_count: usize) -> Vec<ColorInterpretation> {
565    match band_count {
566        1 => vec![ColorInterpretation::Gray],
567        3 => vec![
568            ColorInterpretation::Red,
569            ColorInterpretation::Green,
570            ColorInterpretation::Blue,
571        ],
572        4 => vec![
573            ColorInterpretation::Red,
574            ColorInterpretation::Green,
575            ColorInterpretation::Blue,
576            ColorInterpretation::Alpha,
577        ],
578        _ => vec![ColorInterpretation::Undefined; band_count],
579    }
580}
581
582#[cfg(test)]
583mod tests {
584    use super::*;
585
586    #[test]
587    fn test_multi_band_from_bands() {
588        let r = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
589        let g = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
590        let b = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
591
592        let multi = MultiBandBuffer::from_bands(
593            vec![r, g, b],
594            vec![
595                ColorInterpretation::Red,
596                ColorInterpretation::Green,
597                ColorInterpretation::Blue,
598            ],
599        )
600        .expect("should create multi-band buffer");
601
602        assert_eq!(multi.band_count(), 3);
603        assert_eq!(multi.width(), 100);
604        assert_eq!(multi.height(), 100);
605        assert_eq!(multi.data_type(), RasterDataType::UInt8);
606    }
607
608    #[test]
609    fn test_band_iterator() {
610        let bands: Vec<_> = (0..4)
611            .map(|_| RasterBuffer::zeros(50, 50, RasterDataType::Float32))
612            .collect();
613        let colors = vec![
614            ColorInterpretation::Red,
615            ColorInterpretation::Green,
616            ColorInterpretation::Blue,
617            ColorInterpretation::Alpha,
618        ];
619
620        let multi = MultiBandBuffer::from_bands(bands, colors).expect("should create");
621
622        let collected: Vec<_> = multi.bands().collect();
623        assert_eq!(collected.len(), 4);
624        assert_eq!(collected[0].index(), 0);
625        assert_eq!(collected[0].gdal_index(), 1);
626        assert_eq!(collected[0].color(), ColorInterpretation::Red);
627        assert_eq!(collected[3].color(), ColorInterpretation::Alpha);
628    }
629
630    #[test]
631    fn test_band_iterator_exact_size() {
632        let multi = MultiBandBuffer::from_bands(
633            vec![
634                RasterBuffer::zeros(10, 10, RasterDataType::UInt8),
635                RasterBuffer::zeros(10, 10, RasterDataType::UInt8),
636            ],
637            vec![ColorInterpretation::Gray, ColorInterpretation::Alpha],
638        )
639        .expect("should create");
640
641        let iter = multi.bands();
642        assert_eq!(iter.len(), 2);
643    }
644
645    #[test]
646    fn test_multi_band_empty_error() {
647        let result = MultiBandBuffer::from_bands(vec![], vec![]);
648        assert!(result.is_err());
649    }
650
651    #[test]
652    fn test_multi_band_dimension_mismatch() {
653        let a = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
654        let b = RasterBuffer::zeros(50, 50, RasterDataType::UInt8);
655
656        let result = MultiBandBuffer::from_bands(
657            vec![a, b],
658            vec![ColorInterpretation::Gray, ColorInterpretation::Alpha],
659        );
660        assert!(result.is_err());
661    }
662
663    #[test]
664    fn test_multi_band_type_mismatch() {
665        let a = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
666        let b = RasterBuffer::zeros(100, 100, RasterDataType::Float32);
667
668        let result = MultiBandBuffer::from_bands(
669            vec![a, b],
670            vec![ColorInterpretation::Gray, ColorInterpretation::Alpha],
671        );
672        assert!(result.is_err());
673    }
674
675    #[test]
676    fn test_multi_band_color_count_mismatch() {
677        let a = RasterBuffer::zeros(100, 100, RasterDataType::UInt8);
678
679        let result = MultiBandBuffer::from_bands(
680            vec![a],
681            vec![ColorInterpretation::Red, ColorInterpretation::Green],
682        );
683        assert!(result.is_err());
684    }
685
686    #[test]
687    fn test_band_access() {
688        let mut buf = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
689        buf.set_pixel(5, 5, 42.0).expect("should set");
690
691        let multi = MultiBandBuffer::from_bands(vec![buf], vec![ColorInterpretation::Gray])
692            .expect("should create");
693
694        let band = multi.band(0).expect("should get band");
695        assert_eq!(band.buffer().get_pixel(5, 5).expect("should get"), 42.0);
696    }
697
698    #[test]
699    fn test_band_out_of_range() {
700        let multi = MultiBandBuffer::from_bands(
701            vec![RasterBuffer::zeros(10, 10, RasterDataType::UInt8)],
702            vec![ColorInterpretation::Gray],
703        )
704        .expect("should create");
705
706        assert!(multi.band(1).is_err());
707    }
708
709    #[test]
710    fn test_from_interleaved_roundtrip() {
711        // Create 3-band 2x2 UInt8 interleaved data: R₁G₁B₁ R₂G₁B₂ ...
712        let data = vec![
713            10, 20, 30, // pixel (0,0): R=10, G=20, B=30
714            40, 50, 60, // pixel (1,0)
715            70, 80, 90, // pixel (0,1)
716            100, 110, 120, // pixel (1,1)
717        ];
718
719        let multi = MultiBandBuffer::from_interleaved(
720            &data,
721            2,
722            2,
723            3,
724            RasterDataType::UInt8,
725            NoDataValue::None,
726        )
727        .expect("should create from interleaved");
728
729        assert_eq!(multi.band_count(), 3);
730        assert_eq!(multi.width(), 2);
731        assert_eq!(multi.height(), 2);
732        assert_eq!(multi.layout(), PixelLayout::BandInterleavedByPixel);
733
734        // Verify band 0 (Red): 10, 40, 70, 100
735        let r_band = multi.band(0).expect("should get");
736        assert_eq!(r_band.as_slice::<u8>().expect("slice"), &[10, 40, 70, 100]);
737
738        // Verify band 1 (Green): 20, 50, 80, 110
739        let g_band = multi.band(1).expect("should get");
740        assert_eq!(g_band.as_slice::<u8>().expect("slice"), &[20, 50, 80, 110]);
741
742        // Verify band 2 (Blue): 30, 60, 90, 120
743        let b_band = multi.band(2).expect("should get");
744        assert_eq!(b_band.as_slice::<u8>().expect("slice"), &[30, 60, 90, 120]);
745
746        // Roundtrip back to interleaved
747        let interleaved = multi.to_interleaved();
748        assert_eq!(interleaved, data);
749    }
750
751    #[test]
752    fn test_from_bsq_roundtrip() {
753        // BSQ: all of R, then all of G, then all of B
754        let data = vec![
755            10, 40, 70, 100, // band 0 (Red)
756            20, 50, 80, 110, // band 1 (Green)
757            30, 60, 90, 120, // band 2 (Blue)
758        ];
759
760        let multi =
761            MultiBandBuffer::from_bsq(&data, 2, 2, 3, RasterDataType::UInt8, NoDataValue::None)
762                .expect("should create from BSQ");
763
764        assert_eq!(multi.band_count(), 3);
765        assert_eq!(multi.layout(), PixelLayout::BandSequential);
766
767        let r_band = multi.band(0).expect("should get");
768        assert_eq!(r_band.as_slice::<u8>().expect("slice"), &[10, 40, 70, 100]);
769
770        let bsq = multi.to_bsq();
771        assert_eq!(bsq, data);
772    }
773
774    #[test]
775    fn test_compute_all_statistics() {
776        let mut r = RasterBuffer::zeros(2, 2, RasterDataType::Float32);
777        r.set_pixel(0, 0, 1.0).expect("set");
778        r.set_pixel(1, 0, 2.0).expect("set");
779        r.set_pixel(0, 1, 3.0).expect("set");
780        r.set_pixel(1, 1, 4.0).expect("set");
781
782        let multi = MultiBandBuffer::from_bands(vec![r], vec![ColorInterpretation::Gray])
783            .expect("should create");
784
785        let stats = multi.compute_all_statistics().expect("should compute");
786        assert_eq!(stats.len(), 1);
787        assert!((stats[0].min - 1.0).abs() < 1e-6);
788        assert!((stats[0].max - 4.0).abs() < 1e-6);
789        assert!((stats[0].mean - 2.5).abs() < 1e-6);
790    }
791
792    #[test]
793    fn test_band_nodata_override() {
794        let buf = RasterBuffer::zeros(5, 5, RasterDataType::Float32);
795        let mut multi = MultiBandBuffer::from_bands(vec![buf], vec![ColorInterpretation::Gray])
796            .expect("should create");
797
798        // Default: no override, uses buffer's nodata
799        let band = multi.band(0).expect("get");
800        assert_eq!(band.nodata(), NoDataValue::None);
801
802        // Set override
803        multi
804            .set_band_nodata(0, NoDataValue::Float(-9999.0))
805            .expect("should set");
806
807        let band = multi.band(0).expect("get");
808        assert_eq!(band.nodata(), NoDataValue::Float(-9999.0));
809    }
810
811    #[test]
812    fn test_band_mut() {
813        let buf = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
814        let mut multi = MultiBandBuffer::from_bands(vec![buf], vec![ColorInterpretation::Gray])
815            .expect("should create");
816
817        let band = multi.band_mut(0).expect("should get mut");
818        band.set_pixel(0, 0, 99.0).expect("should set");
819
820        let band = multi.band(0).expect("should get");
821        assert_eq!(band.buffer().get_pixel(0, 0).expect("should get"), 99.0);
822    }
823
824    #[test]
825    fn test_into_bands() {
826        let r = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
827        let g = RasterBuffer::zeros(10, 10, RasterDataType::UInt8);
828
829        let multi = MultiBandBuffer::from_bands(
830            vec![r, g],
831            vec![ColorInterpretation::Gray, ColorInterpretation::Alpha],
832        )
833        .expect("should create");
834
835        let bands = multi.into_bands();
836        assert_eq!(bands.len(), 2);
837    }
838
839    #[test]
840    fn test_from_bil_roundtrip() {
841        // 2 bands, 3 rows, 4 cols of u8
842        // BIL layout: [row0_b0: 4 bytes, row0_b1: 4 bytes, row1_b0: 4 bytes, ...]
843        let bands = 2usize;
844        let height = 3usize;
845        let width = 4usize;
846        // Manually build BIL bytes
847        let mut bil: Vec<u8> = Vec::new();
848        for row in 0..height {
849            for band in 0..bands {
850                for col in 0..width {
851                    bil.push(((band * 100 + row * 10 + col) & 0xFF) as u8);
852                }
853            }
854        }
855        let buf = MultiBandBuffer::from_bil(
856            &bil,
857            width as u64,
858            height as u64,
859            bands as u32,
860            RasterDataType::UInt8,
861            NoDataValue::None,
862        )
863        .expect("should create from BIL");
864        let back = buf.to_bil();
865        assert_eq!(bil, back, "BIL roundtrip failed");
866    }
867
868    #[test]
869    fn test_from_bil_to_bsq_equivalence() {
870        // Build the same logical data via BIL and BSQ, verify identical BSQ output
871        let (bands, height, width) = (2usize, 2usize, 3usize);
872        let mut bsq_data: Vec<u8> = Vec::new();
873        for band in 0..bands {
874            for row in 0..height {
875                for col in 0..width {
876                    bsq_data.push((band * 10 + row * 3 + col) as u8);
877                }
878            }
879        }
880        let mut bil_data: Vec<u8> = vec![0; bands * height * width];
881        for band in 0..bands {
882            for row in 0..height {
883                for col in 0..width {
884                    let bsq_idx = band * height * width + row * width + col;
885                    let bil_idx = row * bands * width + band * width + col;
886                    bil_data[bil_idx] = bsq_data[bsq_idx];
887                }
888            }
889        }
890        let from_bil = MultiBandBuffer::from_bil(
891            &bil_data,
892            width as u64,
893            height as u64,
894            bands as u32,
895            RasterDataType::UInt8,
896            NoDataValue::None,
897        )
898        .expect("should create from BIL");
899        let bsq_out = from_bil.to_bsq();
900        assert_eq!(bsq_data, bsq_out, "BSQ equivalence failed");
901    }
902
903    #[test]
904    fn test_from_bil_mismatched_size() {
905        // Wrong size for 2x3x4 layout
906        let data: Vec<u8> = vec![0; 5];
907        let result =
908            MultiBandBuffer::from_bil(&data, 4, 3, 2, RasterDataType::UInt8, NoDataValue::None);
909        assert!(result.is_err(), "expected error for size mismatch");
910    }
911
912    #[test]
913    fn test_from_bil_various_types() {
914        // u8 roundtrip for different dimensions
915        for &(bands, height, width) in &[(2usize, 3usize, 4usize), (3usize, 2usize, 5usize)] {
916            // u8 roundtrip
917            let mut bil_u8: Vec<u8> = Vec::new();
918            for row in 0..height {
919                for band in 0..bands {
920                    for col in 0..width {
921                        bil_u8.push(((band * 100 + row * 10 + col) & 0xFF) as u8);
922                    }
923                }
924            }
925            let buf = MultiBandBuffer::from_bil(
926                &bil_u8,
927                width as u64,
928                height as u64,
929                bands as u32,
930                RasterDataType::UInt8,
931                NoDataValue::None,
932            )
933            .expect("u8 from_bil should succeed");
934            let back = buf.to_bil();
935            assert_eq!(
936                bil_u8, back,
937                "u8 BIL roundtrip failed for {}x{}x{}",
938                bands, height, width
939            );
940
941            // u16 roundtrip — encode as raw bytes (little-endian)
942            let mut bil_u16_raw: Vec<u8> = Vec::new();
943            for row in 0..height {
944                for band in 0..bands {
945                    for col in 0..width {
946                        let v: u16 = (band * 1000 + row * 10 + col) as u16;
947                        bil_u16_raw.extend_from_slice(&v.to_ne_bytes());
948                    }
949                }
950            }
951            let buf_u16 = MultiBandBuffer::from_bil(
952                &bil_u16_raw,
953                width as u64,
954                height as u64,
955                bands as u32,
956                RasterDataType::UInt16,
957                NoDataValue::None,
958            )
959            .expect("u16 from_bil should succeed");
960            let back_u16 = buf_u16.to_bil();
961            assert_eq!(
962                bil_u16_raw, back_u16,
963                "u16 BIL roundtrip failed for {}x{}x{}",
964                bands, height, width
965            );
966
967            // f32 roundtrip — encode as raw bytes (native-endian)
968            let mut bil_f32_raw: Vec<u8> = Vec::new();
969            for row in 0..height {
970                for band in 0..bands {
971                    for col in 0..width {
972                        let v: f32 = (band * 100 + row * 10 + col) as f32 + 0.5;
973                        bil_f32_raw.extend_from_slice(&v.to_ne_bytes());
974                    }
975                }
976            }
977            let buf_f32 = MultiBandBuffer::from_bil(
978                &bil_f32_raw,
979                width as u64,
980                height as u64,
981                bands as u32,
982                RasterDataType::Float32,
983                NoDataValue::None,
984            )
985            .expect("f32 from_bil should succeed");
986            let back_f32 = buf_f32.to_bil();
987            assert_eq!(
988                bil_f32_raw, back_f32,
989                "f32 BIL roundtrip failed for {}x{}x{}",
990                bands, height, width
991            );
992        }
993    }
994
995    #[test]
996    fn test_default_color_interpretation() {
997        let colors_1 = default_color_interpretation(1);
998        assert_eq!(colors_1, vec![ColorInterpretation::Gray]);
999
1000        let colors_3 = default_color_interpretation(3);
1001        assert_eq!(
1002            colors_3,
1003            vec![
1004                ColorInterpretation::Red,
1005                ColorInterpretation::Green,
1006                ColorInterpretation::Blue,
1007            ]
1008        );
1009
1010        let colors_4 = default_color_interpretation(4);
1011        assert_eq!(
1012            colors_4,
1013            vec![
1014                ColorInterpretation::Red,
1015                ColorInterpretation::Green,
1016                ColorInterpretation::Blue,
1017                ColorInterpretation::Alpha,
1018            ]
1019        );
1020
1021        let colors_5 = default_color_interpretation(5);
1022        assert!(
1023            colors_5
1024                .iter()
1025                .all(|c| *c == ColorInterpretation::Undefined)
1026        );
1027    }
1028}