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