1#![warn(clippy::all)]
34#![deny(clippy::unwrap_used)]
37#![allow(clippy::module_name_repetitions)]
38#![allow(dead_code)]
40#![allow(clippy::expect_used)]
42#![allow(clippy::too_many_arguments)]
44#![allow(clippy::manual_clamp)]
46#![allow(clippy::vec_init_then_push)]
48#![allow(missing_docs)]
50
51pub mod adaptive_tiling;
52pub mod band_algebra;
53pub mod cog;
54pub mod color_space;
55pub mod compression;
56pub mod geokeys;
57pub mod jpeg_codec;
58pub mod lerc_codec;
59pub mod overviews;
60pub mod tiff;
61pub mod writer;
62
63pub use cog::CogReader;
65pub use geokeys::{GeoKey, GeoKeyDirectory, ModelType, RasterType};
66pub use tiff::{Compression, ImageInfo, PhotometricInterpretation, TiffFile, TiffHeader, TiffTag};
67pub use writer::{
68 CogWriter, CogWriterOptions, GeoTiffWriter, GeoTiffWriterOptions, OverviewResampling,
69 WriterConfig,
70};
71
72use oxigdal_core::buffer::RasterBuffer;
73use oxigdal_core::error::{FormatError, OxiGdalError, Result};
74use oxigdal_core::io::DataSource;
75use oxigdal_core::types::{
76 ColorInterpretation, GeoTransform, NoDataValue, RasterDataType, RasterMetadata,
77};
78
79fn parse_geokeys_to_wkt(geo_keys: Option<&GeoKeyDirectory>) -> Option<String> {
87 let geo_keys = geo_keys?;
88 let epsg_code = geo_keys.epsg_code()?;
89
90 Some(match epsg_code {
93 4326 => {
95 r#"GEOGCS["WGS 84",
96 DATUM["WGS_1984",
97 SPHEROID["WGS 84",6378137,298.257223563,
98 AUTHORITY["EPSG","7030"]],
99 AUTHORITY["EPSG","6326"]],
100 PRIMEM["Greenwich",0,
101 AUTHORITY["EPSG","8901"]],
102 UNIT["degree",0.0174532925199433,
103 AUTHORITY["EPSG","9122"]],
104 AXIS["Latitude",NORTH],
105 AXIS["Longitude",EAST],
106 AUTHORITY["EPSG","4326"]]"#
107 .to_string()
108 }
109 3857 => {
111 r#"PROJCS["WGS 84 / Pseudo-Mercator",
112 GEOGCS["WGS 84",
113 DATUM["WGS_1984",
114 SPHEROID["WGS 84",6378137,298.257223563,
115 AUTHORITY["EPSG","7030"]],
116 AUTHORITY["EPSG","6326"]],
117 PRIMEM["Greenwich",0,
118 AUTHORITY["EPSG","8901"]],
119 UNIT["degree",0.0174532925199433,
120 AUTHORITY["EPSG","9122"]],
121 AUTHORITY["EPSG","4326"]],
122 PROJECTION["Mercator_1SP"],
123 PARAMETER["central_meridian",0],
124 PARAMETER["scale_factor",1],
125 PARAMETER["false_easting",0],
126 PARAMETER["false_northing",0],
127 UNIT["metre",1,
128 AUTHORITY["EPSG","9001"]],
129 AXIS["Easting",EAST],
130 AXIS["Northing",NORTH],
131 EXTENSION["PROJ4","+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"],
132 AUTHORITY["EPSG","3857"]]"#
133 .to_string()
134 }
135 32601..=32660 => {
137 let zone = epsg_code - 32600;
138 format!(
139 r#"PROJCS["WGS 84 / UTM zone {}N",
140 GEOGCS["WGS 84",
141 DATUM["WGS_1984",
142 SPHEROID["WGS 84",6378137,298.257223563,
143 AUTHORITY["EPSG","7030"]],
144 AUTHORITY["EPSG","6326"]],
145 PRIMEM["Greenwich",0,
146 AUTHORITY["EPSG","8901"]],
147 UNIT["degree",0.0174532925199433,
148 AUTHORITY["EPSG","9122"]],
149 AUTHORITY["EPSG","4326"]],
150 PROJECTION["Transverse_Mercator"],
151 PARAMETER["latitude_of_origin",0],
152 PARAMETER["central_meridian",{}],
153 PARAMETER["scale_factor",0.9996],
154 PARAMETER["false_easting",500000],
155 PARAMETER["false_northing",0],
156 UNIT["metre",1,
157 AUTHORITY["EPSG","9001"]],
158 AXIS["Easting",EAST],
159 AXIS["Northing",NORTH],
160 AUTHORITY["EPSG","{}""]]"#,
161 zone,
162 zone as i32 * 6 - 183,
163 epsg_code
164 )
165 }
166 32701..=32760 => {
168 let zone = epsg_code - 32700;
169 format!(
170 r#"PROJCS["WGS 84 / UTM zone {}S",
171 GEOGCS["WGS 84",
172 DATUM["WGS_1984",
173 SPHEROID["WGS 84",6378137,298.257223563,
174 AUTHORITY["EPSG","7030"]],
175 AUTHORITY["EPSG","6326"]],
176 PRIMEM["Greenwich",0,
177 AUTHORITY["EPSG","8901"]],
178 UNIT["degree",0.0174532925199433,
179 AUTHORITY["EPSG","9122"]],
180 AUTHORITY["EPSG","4326"]],
181 PROJECTION["Transverse_Mercator"],
182 PARAMETER["latitude_of_origin",0],
183 PARAMETER["central_meridian",{}],
184 PARAMETER["scale_factor",0.9996],
185 PARAMETER["false_easting",500000],
186 PARAMETER["false_northing",10000000],
187 UNIT["metre",1,
188 AUTHORITY["EPSG","9001"]],
189 AXIS["Easting",EAST],
190 AXIS["Northing",NORTH],
191 AUTHORITY["EPSG","{}""]]"#,
192 zone,
193 zone as i32 * 6 - 183,
194 epsg_code
195 )
196 }
197 4269 => {
199 r#"GEOGCS["NAD83",
200 DATUM["North_American_Datum_1983",
201 SPHEROID["GRS 1980",6378137,298.257222101,
202 AUTHORITY["EPSG","7019"]],
203 AUTHORITY["EPSG","6269"]],
204 PRIMEM["Greenwich",0,
205 AUTHORITY["EPSG","8901"]],
206 UNIT["degree",0.0174532925199433,
207 AUTHORITY["EPSG","9122"]],
208 AXIS["Latitude",NORTH],
209 AXIS["Longitude",EAST],
210 AUTHORITY["EPSG","4269"]]"#
211 .to_string()
212 }
213 4267 => {
215 r#"GEOGCS["NAD27",
216 DATUM["North_American_Datum_1927",
217 SPHEROID["Clarke 1866",6378206.4,294.978698213898,
218 AUTHORITY["EPSG","7008"]],
219 AUTHORITY["EPSG","6267"]],
220 PRIMEM["Greenwich",0,
221 AUTHORITY["EPSG","8901"]],
222 UNIT["degree",0.0174532925199433,
223 AUTHORITY["EPSG","9122"]],
224 AXIS["Latitude",NORTH],
225 AXIS["Longitude",EAST],
226 AUTHORITY["EPSG","4267"]]"#
227 .to_string()
228 }
229 _ => format!("EPSG:{}", epsg_code),
231 })
232}
233
234fn parse_photometric_interpretation(
243 photometric: PhotometricInterpretation,
244 samples_per_pixel: u16,
245) -> Vec<ColorInterpretation> {
246 match photometric {
247 PhotometricInterpretation::WhiteIsZero | PhotometricInterpretation::BlackIsZero => {
248 if samples_per_pixel == 1 {
250 vec![ColorInterpretation::Gray]
251 } else if samples_per_pixel == 2 {
252 vec![ColorInterpretation::Gray, ColorInterpretation::Alpha]
253 } else {
254 vec![ColorInterpretation::Gray; samples_per_pixel as usize]
256 }
257 }
258 PhotometricInterpretation::Rgb => {
259 match samples_per_pixel {
261 1 => vec![ColorInterpretation::Red],
262 2 => vec![ColorInterpretation::Red, ColorInterpretation::Green],
263 3 => vec![
264 ColorInterpretation::Red,
265 ColorInterpretation::Green,
266 ColorInterpretation::Blue,
267 ],
268 4 => vec![
269 ColorInterpretation::Red,
270 ColorInterpretation::Green,
271 ColorInterpretation::Blue,
272 ColorInterpretation::Alpha,
273 ],
274 _ => {
275 let mut interp = vec![
277 ColorInterpretation::Red,
278 ColorInterpretation::Green,
279 ColorInterpretation::Blue,
280 ];
281 if samples_per_pixel > 3 {
282 interp.push(ColorInterpretation::Alpha);
283 }
284 for _ in 4..samples_per_pixel {
285 interp.push(ColorInterpretation::Undefined);
286 }
287 interp
288 }
289 }
290 }
291 PhotometricInterpretation::Palette => {
292 if samples_per_pixel == 1 {
294 vec![ColorInterpretation::PaletteIndex]
295 } else if samples_per_pixel == 2 {
296 vec![
297 ColorInterpretation::PaletteIndex,
298 ColorInterpretation::Alpha,
299 ]
300 } else {
301 vec![ColorInterpretation::PaletteIndex; samples_per_pixel as usize]
302 }
303 }
304 PhotometricInterpretation::Cmyk => {
305 match samples_per_pixel {
307 1 => vec![ColorInterpretation::Cyan],
308 2 => vec![ColorInterpretation::Cyan, ColorInterpretation::Magenta],
309 3 => vec![
310 ColorInterpretation::Cyan,
311 ColorInterpretation::Magenta,
312 ColorInterpretation::Yellow,
313 ],
314 4 => vec![
315 ColorInterpretation::Cyan,
316 ColorInterpretation::Magenta,
317 ColorInterpretation::Yellow,
318 ColorInterpretation::Black,
319 ],
320 _ => {
321 let mut interp = vec![
323 ColorInterpretation::Cyan,
324 ColorInterpretation::Magenta,
325 ColorInterpretation::Yellow,
326 ColorInterpretation::Black,
327 ];
328 for _ in 4..samples_per_pixel {
329 interp.push(ColorInterpretation::Undefined);
330 }
331 interp
332 }
333 }
334 }
335 PhotometricInterpretation::YCbCr => {
336 match samples_per_pixel {
338 1 => vec![ColorInterpretation::YCbCrY],
339 2 => vec![ColorInterpretation::YCbCrY, ColorInterpretation::YCbCrCb],
340 3 => vec![
341 ColorInterpretation::YCbCrY,
342 ColorInterpretation::YCbCrCb,
343 ColorInterpretation::YCbCrCr,
344 ],
345 _ => {
346 let mut interp = vec![
348 ColorInterpretation::YCbCrY,
349 ColorInterpretation::YCbCrCb,
350 ColorInterpretation::YCbCrCr,
351 ];
352 if samples_per_pixel > 3 {
353 interp.push(ColorInterpretation::Alpha);
354 }
355 for _ in 4..samples_per_pixel {
356 interp.push(ColorInterpretation::Undefined);
357 }
358 interp
359 }
360 }
361 }
362 _ => vec![ColorInterpretation::Undefined; samples_per_pixel as usize],
364 }
365}
366
367pub struct GeoTiffReader<S: DataSource> {
369 cog_reader: CogReader<S>,
370 geo_transform: Option<GeoTransform>,
371 nodata: NoDataValue,
372}
373
374impl<S: DataSource> GeoTiffReader<S> {
375 pub fn open(source: S) -> Result<Self> {
380 let cog_reader = CogReader::open(source)?;
381
382 let geo_transform = cog_reader.geo_transform()?;
384
385 let nodata = cog_reader.nodata()?;
387
388 Ok(Self {
389 cog_reader,
390 geo_transform,
391 nodata,
392 })
393 }
394
395 #[must_use]
397 pub fn width(&self) -> u64 {
398 self.cog_reader.width()
399 }
400
401 #[must_use]
403 pub fn height(&self) -> u64 {
404 self.cog_reader.height()
405 }
406
407 #[must_use]
409 pub fn band_count(&self) -> u32 {
410 u32::from(self.cog_reader.primary_info().samples_per_pixel)
411 }
412
413 #[must_use]
415 pub fn data_type(&self) -> Option<RasterDataType> {
416 self.cog_reader.primary_info().data_type()
417 }
418
419 #[must_use]
421 pub fn tile_size(&self) -> Option<(u32, u32)> {
422 self.cog_reader.tile_size()
423 }
424
425 #[must_use]
427 pub fn overview_count(&self) -> usize {
428 self.cog_reader.overview_count()
429 }
430
431 #[must_use]
433 pub fn geo_transform(&self) -> Option<&GeoTransform> {
434 self.geo_transform.as_ref()
435 }
436
437 #[must_use]
439 pub const fn nodata(&self) -> NoDataValue {
440 self.nodata
441 }
442
443 #[must_use]
445 pub fn epsg_code(&self) -> Option<u32> {
446 self.cog_reader.epsg_code()
447 }
448
449 #[must_use]
451 pub fn compression(&self) -> Compression {
452 self.cog_reader.primary_info().compression
453 }
454
455 #[must_use]
457 pub fn tile_count(&self) -> (u32, u32) {
458 self.cog_reader.tile_count()
459 }
460
461 pub fn read_tile(&self, level: usize, tile_x: u32, tile_y: u32) -> Result<Vec<u8>> {
466 self.cog_reader.read_tile(level, tile_x, tile_y)
467 }
468
469 pub fn read_tile_buffer(&self, level: usize, tile_x: u32, tile_y: u32) -> Result<RasterBuffer> {
474 let data = self.read_tile(level, tile_x, tile_y)?;
475 let info = self.cog_reader.primary_info();
476
477 let tile_width = info.tile_width.unwrap_or(info.width as u32) as u64;
478 let tile_height = info.tile_height.unwrap_or(info.height as u32) as u64;
479 let data_type =
480 info.data_type()
481 .ok_or(OxiGdalError::Format(FormatError::InvalidDataType {
482 type_id: 0,
483 }))?;
484
485 RasterBuffer::new(data, tile_width, tile_height, data_type, self.nodata)
486 }
487
488 #[must_use]
490 pub fn metadata(&self) -> RasterMetadata {
491 let info = self.cog_reader.primary_info();
492
493 let crs_wkt = parse_geokeys_to_wkt(self.cog_reader.geo_keys());
495
496 let color_interpretation =
498 parse_photometric_interpretation(info.photometric, info.samples_per_pixel);
499
500 RasterMetadata {
501 width: info.width,
502 height: info.height,
503 band_count: u32::from(info.samples_per_pixel),
504 data_type: info.data_type().unwrap_or(RasterDataType::UInt8),
505 geo_transform: self.geo_transform,
506 crs_wkt,
507 nodata: self.nodata,
508 color_interpretation,
509 layout: oxigdal_core::types::PixelLayout::Tiled {
510 tile_width: info.tile_width.unwrap_or(256),
511 tile_height: info.tile_height.unwrap_or(256),
512 },
513 driver_metadata: Vec::new(),
514 }
515 }
516
517 pub fn read_band(&self, level: usize, _band: usize) -> Result<Vec<u8>> {
522 let (tiles_x, tiles_y) = self.tile_count();
524 let info = self.cog_reader.primary_info();
525
526 let width = info.width as usize;
527 let height = info.height as usize;
528 let bytes_per_sample = (info.bits_per_sample.first().copied().unwrap_or(8) / 8) as usize;
529 let samples_per_pixel = info.samples_per_pixel as usize;
530
531 let mut result = vec![0u8; width * height * bytes_per_sample * samples_per_pixel];
532
533 let is_tiled = info.tile_width.is_some() && info.tile_height.is_some();
535
536 let (tile_width, default_tile_height) = if is_tiled {
537 (
538 info.tile_width.unwrap_or(width as u32) as usize,
539 info.tile_height.unwrap_or(height as u32) as usize,
540 )
541 } else {
542 (width, info.rows_per_strip.unwrap_or(height as u32) as usize)
544 };
545
546 for ty in 0..tiles_y {
547 for tx in 0..tiles_x {
548 let tile_data = self.read_tile(level, tx, ty)?;
549
550 let x_start = tx as usize * tile_width;
552 let y_start = ty as usize * default_tile_height;
553
554 let actual_rows = (height - y_start).min(default_tile_height);
556
557 let src_stride = if is_tiled { tile_width } else { width };
560
561 for row in 0..actual_rows {
562 let dst_y = y_start + row;
563 if dst_y >= height {
564 break;
565 }
566
567 let src_offset = row * src_stride * bytes_per_sample * samples_per_pixel;
568 let dst_offset = dst_y * width * bytes_per_sample * samples_per_pixel
569 + x_start * bytes_per_sample * samples_per_pixel;
570
571 let copy_width = tile_width.min(width - x_start);
572 let copy_bytes = copy_width * bytes_per_sample * samples_per_pixel;
573
574 if src_offset + copy_bytes <= tile_data.len()
575 && dst_offset + copy_bytes <= result.len()
576 {
577 result[dst_offset..dst_offset + copy_bytes]
578 .copy_from_slice(&tile_data[src_offset..src_offset + copy_bytes]);
579 }
580 }
581 }
582 }
583
584 Ok(result)
585 }
586
587 pub fn new(source: S) -> Result<Self> {
592 Self::open(source)
593 }
594}
595
596#[must_use]
598pub fn is_tiff(data: &[u8]) -> bool {
599 if data.len() < 4 {
600 return false;
601 }
602
603 (data[0] == 0x49 && data[1] == 0x49 && data[2] == 0x2A && data[3] == 0x00) || (data[0] == 0x4D && data[1] == 0x4D && data[2] == 0x00 && data[3] == 0x2A) || (data[0] == 0x49 && data[1] == 0x49 && data[2] == 0x2B && data[3] == 0x00) || (data[0] == 0x4D && data[1] == 0x4D && data[2] == 0x00 && data[3] == 0x2B) }
609
610pub fn is_cog<S: DataSource>(source: &S) -> Result<bool> {
612 let tiff = TiffFile::parse(source)?;
613 let validation = cog::validate_cog(&tiff, source);
614 Ok(validation.is_valid)
615}
616
617#[cfg(test)]
618mod tests {
619 use super::*;
620 use crate::geokeys::GeoKeyEntry;
621
622 #[test]
623 fn test_is_tiff() {
624 assert!(is_tiff(&[0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00]));
626
627 assert!(is_tiff(&[0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08]));
629
630 assert!(is_tiff(&[0x49, 0x49, 0x2B, 0x00, 0x08, 0x00, 0x00, 0x00]));
632
633 assert!(!is_tiff(&[0x89, 0x50, 0x4E, 0x47])); assert!(!is_tiff(&[0xFF, 0xD8, 0xFF])); assert!(!is_tiff(&[]));
637 }
638
639 #[test]
640 fn test_parse_geokeys_to_wkt_none() {
641 let wkt = parse_geokeys_to_wkt(None);
643 assert!(wkt.is_none());
644 }
645
646 #[test]
647 fn test_parse_geokeys_to_wkt_epsg_4326() {
648 let geo_dir = GeoKeyDirectory {
650 version: 1,
651 key_revision_major: 1,
652 key_revision_minor: 0,
653 entries: vec![GeoKeyEntry {
654 key_id: GeoKey::GeographicType as u16,
655 tiff_tag_location: 0,
656 count: 1,
657 value_offset: 4326,
658 }],
659 double_params: Vec::new(),
660 ascii_params: String::new(),
661 };
662
663 let wkt = parse_geokeys_to_wkt(Some(&geo_dir));
664 assert!(wkt.is_some());
665 let wkt_str = wkt.unwrap_or_default();
666 assert!(wkt_str.contains("WGS 84"));
667 assert!(wkt_str.contains("EPSG"));
668 assert!(wkt_str.contains("4326"));
669 }
670
671 #[test]
672 fn test_parse_geokeys_to_wkt_epsg_3857() {
673 let geo_dir = GeoKeyDirectory {
675 version: 1,
676 key_revision_major: 1,
677 key_revision_minor: 0,
678 entries: vec![GeoKeyEntry {
679 key_id: GeoKey::ProjectedCsType as u16,
680 tiff_tag_location: 0,
681 count: 1,
682 value_offset: 3857,
683 }],
684 double_params: Vec::new(),
685 ascii_params: String::new(),
686 };
687
688 let wkt = parse_geokeys_to_wkt(Some(&geo_dir));
689 assert!(wkt.is_some());
690 let wkt_str = wkt.unwrap_or_default();
691 assert!(wkt_str.contains("Pseudo-Mercator"));
692 assert!(wkt_str.contains("3857"));
693 }
694
695 #[test]
696 fn test_parse_geokeys_to_wkt_utm_north() {
697 let geo_dir = GeoKeyDirectory {
699 version: 1,
700 key_revision_major: 1,
701 key_revision_minor: 0,
702 entries: vec![GeoKeyEntry {
703 key_id: GeoKey::ProjectedCsType as u16,
704 tiff_tag_location: 0,
705 count: 1,
706 value_offset: 32632,
707 }],
708 double_params: Vec::new(),
709 ascii_params: String::new(),
710 };
711
712 let wkt = parse_geokeys_to_wkt(Some(&geo_dir));
713 assert!(wkt.is_some());
714 let wkt_str = wkt.unwrap_or_default();
715 assert!(wkt_str.contains("UTM zone 32N"));
716 assert!(wkt_str.contains("32632"));
717 assert!(wkt_str.contains("central_meridian"));
718 }
719
720 #[test]
721 fn test_parse_geokeys_to_wkt_utm_south() {
722 let geo_dir = GeoKeyDirectory {
724 version: 1,
725 key_revision_major: 1,
726 key_revision_minor: 0,
727 entries: vec![GeoKeyEntry {
728 key_id: GeoKey::ProjectedCsType as u16,
729 tiff_tag_location: 0,
730 count: 1,
731 value_offset: 32732,
732 }],
733 double_params: Vec::new(),
734 ascii_params: String::new(),
735 };
736
737 let wkt = parse_geokeys_to_wkt(Some(&geo_dir));
738 assert!(wkt.is_some());
739 let wkt_str = wkt.unwrap_or_default();
740 assert!(wkt_str.contains("UTM zone 32S"));
741 assert!(wkt_str.contains("32732"));
742 assert!(wkt_str.contains("false_northing"));
743 }
744
745 #[test]
746 fn test_parse_geokeys_to_wkt_nad83() {
747 let geo_dir = GeoKeyDirectory {
749 version: 1,
750 key_revision_major: 1,
751 key_revision_minor: 0,
752 entries: vec![GeoKeyEntry {
753 key_id: GeoKey::GeographicType as u16,
754 tiff_tag_location: 0,
755 count: 1,
756 value_offset: 4269,
757 }],
758 double_params: Vec::new(),
759 ascii_params: String::new(),
760 };
761
762 let wkt = parse_geokeys_to_wkt(Some(&geo_dir));
763 assert!(wkt.is_some());
764 let wkt_str = wkt.unwrap_or_default();
765 assert!(wkt_str.contains("NAD83"));
766 assert!(wkt_str.contains("4269"));
767 }
768
769 #[test]
770 fn test_parse_geokeys_to_wkt_unknown_epsg() {
771 let geo_dir = GeoKeyDirectory {
773 version: 1,
774 key_revision_major: 1,
775 key_revision_minor: 0,
776 entries: vec![GeoKeyEntry {
777 key_id: GeoKey::ProjectedCsType as u16,
778 tiff_tag_location: 0,
779 count: 1,
780 value_offset: 9999,
781 }],
782 double_params: Vec::new(),
783 ascii_params: String::new(),
784 };
785
786 let wkt = parse_geokeys_to_wkt(Some(&geo_dir));
787 assert!(wkt.is_some());
788 assert_eq!(wkt.unwrap_or_default(), "EPSG:9999");
789 }
790
791 #[test]
792 fn test_parse_photometric_gray_single() {
793 let interp = parse_photometric_interpretation(PhotometricInterpretation::BlackIsZero, 1);
794 assert_eq!(interp.len(), 1);
795 assert_eq!(interp[0], ColorInterpretation::Gray);
796 }
797
798 #[test]
799 fn test_parse_photometric_gray_with_alpha() {
800 let interp = parse_photometric_interpretation(PhotometricInterpretation::WhiteIsZero, 2);
801 assert_eq!(interp.len(), 2);
802 assert_eq!(interp[0], ColorInterpretation::Gray);
803 assert_eq!(interp[1], ColorInterpretation::Alpha);
804 }
805
806 #[test]
807 fn test_parse_photometric_rgb() {
808 let interp = parse_photometric_interpretation(PhotometricInterpretation::Rgb, 3);
809 assert_eq!(interp.len(), 3);
810 assert_eq!(interp[0], ColorInterpretation::Red);
811 assert_eq!(interp[1], ColorInterpretation::Green);
812 assert_eq!(interp[2], ColorInterpretation::Blue);
813 }
814
815 #[test]
816 fn test_parse_photometric_rgba() {
817 let interp = parse_photometric_interpretation(PhotometricInterpretation::Rgb, 4);
818 assert_eq!(interp.len(), 4);
819 assert_eq!(interp[0], ColorInterpretation::Red);
820 assert_eq!(interp[1], ColorInterpretation::Green);
821 assert_eq!(interp[2], ColorInterpretation::Blue);
822 assert_eq!(interp[3], ColorInterpretation::Alpha);
823 }
824
825 #[test]
826 fn test_parse_photometric_palette() {
827 let interp = parse_photometric_interpretation(PhotometricInterpretation::Palette, 1);
828 assert_eq!(interp.len(), 1);
829 assert_eq!(interp[0], ColorInterpretation::PaletteIndex);
830 }
831
832 #[test]
833 fn test_parse_photometric_cmyk() {
834 let interp = parse_photometric_interpretation(PhotometricInterpretation::Cmyk, 4);
835 assert_eq!(interp.len(), 4);
836 assert_eq!(interp[0], ColorInterpretation::Cyan);
837 assert_eq!(interp[1], ColorInterpretation::Magenta);
838 assert_eq!(interp[2], ColorInterpretation::Yellow);
839 assert_eq!(interp[3], ColorInterpretation::Black);
840 }
841
842 #[test]
843 fn test_parse_photometric_ycbcr() {
844 let interp = parse_photometric_interpretation(PhotometricInterpretation::YCbCr, 3);
845 assert_eq!(interp.len(), 3);
846 assert_eq!(interp[0], ColorInterpretation::YCbCrY);
847 assert_eq!(interp[1], ColorInterpretation::YCbCrCb);
848 assert_eq!(interp[2], ColorInterpretation::YCbCrCr);
849 }
850
851 #[test]
852 fn test_parse_photometric_ycbcr_with_alpha() {
853 let interp = parse_photometric_interpretation(PhotometricInterpretation::YCbCr, 4);
854 assert_eq!(interp.len(), 4);
855 assert_eq!(interp[0], ColorInterpretation::YCbCrY);
856 assert_eq!(interp[1], ColorInterpretation::YCbCrCb);
857 assert_eq!(interp[2], ColorInterpretation::YCbCrCr);
858 assert_eq!(interp[3], ColorInterpretation::Alpha);
859 }
860
861 #[test]
862 fn test_parse_photometric_rgb_extra_bands() {
863 let interp = parse_photometric_interpretation(PhotometricInterpretation::Rgb, 6);
864 assert_eq!(interp.len(), 6);
865 assert_eq!(interp[0], ColorInterpretation::Red);
866 assert_eq!(interp[1], ColorInterpretation::Green);
867 assert_eq!(interp[2], ColorInterpretation::Blue);
868 assert_eq!(interp[3], ColorInterpretation::Alpha);
869 assert_eq!(interp[4], ColorInterpretation::Undefined);
870 assert_eq!(interp[5], ColorInterpretation::Undefined);
871 }
872
873 #[test]
874 fn test_parse_photometric_undefined() {
875 let interp =
877 parse_photometric_interpretation(PhotometricInterpretation::TransparencyMask, 2);
878 assert_eq!(interp.len(), 2);
879 assert_eq!(interp[0], ColorInterpretation::Undefined);
880 assert_eq!(interp[1], ColorInterpretation::Undefined);
881 }
882}