diff --git a/src/async_reader.rs b/src/async_reader.rs index 3bbf577..cfd2108 100644 --- a/src/async_reader.rs +++ b/src/async_reader.rs @@ -1,9 +1,11 @@ -use std::io::Cursor; +use std::fmt::Debug; +use std::io::Read; use std::ops::Range; use std::sync::Arc; use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; -use bytes::Bytes; +use bytes::buf::Reader; +use bytes::{Buf, Bytes}; use futures::future::{BoxFuture, FutureExt, TryFutureExt}; use object_store::ObjectStore; @@ -25,7 +27,7 @@ use crate::error::{AiocogeoError, Result}; /// [`ObjectStore`]: object_store::ObjectStore /// /// [`tokio::fs::File`]: https://docs.rs/tokio/latest/tokio/fs/struct.File.html -pub trait AsyncFileReader: Send + Sync { +pub trait AsyncFileReader: Debug + Send + Sync { /// Retrieve the bytes in `range` fn get_bytes(&mut self, range: Range) -> BoxFuture<'_, Result>; @@ -57,7 +59,9 @@ impl AsyncFileReader for Box { } #[cfg(feature = "tokio")] -impl AsyncFileReader for T { +impl AsyncFileReader + for T +{ fn get_bytes(&mut self, range: Range) -> BoxFuture<'_, Result> { use tokio::io::{AsyncReadExt, AsyncSeekExt}; @@ -114,6 +118,7 @@ impl AsyncFileReader for ObjectReader { } } +#[derive(Debug)] pub struct PrefetchReader { reader: Box, buffer: Bytes, @@ -161,25 +166,13 @@ pub enum Endianness { /// A wrapper around an [ObjectStore] that provides a seek-oriented interface // TODO: in the future add buffering to this +#[derive(Debug)] pub(crate) struct AsyncCursor { reader: Box, offset: usize, endianness: Endianness, } -/// Macro to generate functions to read scalar values from the cursor -macro_rules! impl_read_byteorder { - ($method_name:ident, $typ:ty) => { - pub(crate) async fn $method_name(&mut self) -> Result<$typ> { - let mut buf = Cursor::new(self.read(<$typ>::BITS as usize / 8).await?); - match self.endianness { - Endianness::LittleEndian => Ok(buf.$method_name::()?), - Endianness::BigEndian => Ok(buf.$method_name::()?), - } - } - }; -} - impl AsyncCursor { /// Create a new AsyncCursor from a reader and endianness. pub(crate) fn new(reader: Box, endianness: Endianness) -> Self { @@ -196,6 +189,7 @@ impl AsyncCursor { // Initialize with default endianness and then set later let mut cursor = Self::new(reader, Default::default()); let magic_bytes = cursor.read(2).await?; + let magic_bytes = magic_bytes.as_ref(); // Should be b"II" for little endian or b"MM" for big endian if magic_bytes == Bytes::from_static(b"II") { @@ -212,52 +206,68 @@ impl AsyncCursor { } /// Consume self and return the underlying [`AsyncFileReader`]. + #[allow(dead_code)] pub(crate) fn into_inner(self) -> Box { self.reader } /// Read the given number of bytes, advancing the internal cursor state by the same amount. - pub(crate) async fn read(&mut self, length: usize) -> Result { + pub(crate) async fn read(&mut self, length: usize) -> Result { let range = self.offset as _..(self.offset + length) as _; self.offset += length; - self.reader.get_bytes(range).await + let bytes = self.reader.get_bytes(range).await?; + Ok(EndianAwareReader { + reader: bytes.reader(), + endianness: self.endianness, + }) } /// Read a u8 from the cursor, advancing the internal state by 1 byte. pub(crate) async fn read_u8(&mut self) -> Result { - let buf = self.read(1).await?; - Ok(Cursor::new(buf).read_u8()?) + self.read(1).await?.read_u8() } /// Read a i8 from the cursor, advancing the internal state by 1 byte. pub(crate) async fn read_i8(&mut self) -> Result { - let buf = self.read(1).await?; - Ok(Cursor::new(buf).read_i8()?) + self.read(1).await?.read_i8() + } + + /// Read a u16 from the cursor, advancing the internal state by 2 bytes. + pub(crate) async fn read_u16(&mut self) -> Result { + self.read(2).await?.read_u16() + } + + /// Read a i16 from the cursor, advancing the internal state by 2 bytes. + pub(crate) async fn read_i16(&mut self) -> Result { + self.read(2).await?.read_i16() + } + + /// Read a u32 from the cursor, advancing the internal state by 4 bytes. + pub(crate) async fn read_u32(&mut self) -> Result { + self.read(4).await?.read_u32() + } + + /// Read a i32 from the cursor, advancing the internal state by 4 bytes. + pub(crate) async fn read_i32(&mut self) -> Result { + self.read(4).await?.read_i32() + } + + /// Read a u64 from the cursor, advancing the internal state by 8 bytes. + pub(crate) async fn read_u64(&mut self) -> Result { + self.read(8).await?.read_u64() } - impl_read_byteorder!(read_u16, u16); - impl_read_byteorder!(read_u32, u32); - impl_read_byteorder!(read_u64, u64); - impl_read_byteorder!(read_i16, i16); - impl_read_byteorder!(read_i32, i32); - impl_read_byteorder!(read_i64, i64); + /// Read a i64 from the cursor, advancing the internal state by 8 bytes. + pub(crate) async fn read_i64(&mut self) -> Result { + self.read(8).await?.read_i64() + } pub(crate) async fn read_f32(&mut self) -> Result { - let mut buf = Cursor::new(self.read(4).await?); - let out = match self.endianness { - Endianness::LittleEndian => buf.read_f32::()?, - Endianness::BigEndian => buf.read_f32::()?, - }; - Ok(out) + self.read(4).await?.read_f32() } pub(crate) async fn read_f64(&mut self) -> Result { - let mut buf = Cursor::new(self.read(8).await?); - let out = match self.endianness { - Endianness::LittleEndian => buf.read_f64::()?, - Endianness::BigEndian => buf.read_f64::()?, - }; - Ok(out) + self.read(8).await?.read_f64() } #[allow(dead_code)] @@ -265,6 +275,11 @@ impl AsyncCursor { &self.reader } + #[allow(dead_code)] + pub(crate) fn endianness(&self) -> Endianness { + self.endianness + } + /// Advance cursor position by a set amount pub(crate) fn advance(&mut self, amount: usize) { self.offset += amount; @@ -278,3 +293,93 @@ impl AsyncCursor { self.offset } } + +pub(crate) struct EndianAwareReader { + reader: Reader, + endianness: Endianness, +} + +impl EndianAwareReader { + /// Read a u8 from the cursor, advancing the internal state by 1 byte. + pub(crate) fn read_u8(&mut self) -> Result { + Ok(self.reader.read_u8()?) + } + + /// Read a i8 from the cursor, advancing the internal state by 1 byte. + pub(crate) fn read_i8(&mut self) -> Result { + Ok(self.reader.read_i8()?) + } + + pub(crate) fn read_u16(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_u16::()?), + Endianness::BigEndian => Ok(self.reader.read_u16::()?), + } + } + + pub(crate) fn read_i16(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_i16::()?), + Endianness::BigEndian => Ok(self.reader.read_i16::()?), + } + } + + pub(crate) fn read_u32(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_u32::()?), + Endianness::BigEndian => Ok(self.reader.read_u32::()?), + } + } + + pub(crate) fn read_i32(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_i32::()?), + Endianness::BigEndian => Ok(self.reader.read_i32::()?), + } + } + + pub(crate) fn read_u64(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_u64::()?), + Endianness::BigEndian => Ok(self.reader.read_u64::()?), + } + } + + pub(crate) fn read_i64(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_i64::()?), + Endianness::BigEndian => Ok(self.reader.read_i64::()?), + } + } + + pub(crate) fn read_f32(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_f32::()?), + Endianness::BigEndian => Ok(self.reader.read_f32::()?), + } + } + + pub(crate) fn read_f64(&mut self) -> Result { + match self.endianness { + Endianness::LittleEndian => Ok(self.reader.read_f64::()?), + Endianness::BigEndian => Ok(self.reader.read_f64::()?), + } + } + + #[allow(dead_code)] + pub(crate) fn into_inner(self) -> (Reader, Endianness) { + (self.reader, self.endianness) + } +} + +impl AsRef<[u8]> for EndianAwareReader { + fn as_ref(&self) -> &[u8] { + self.reader.get_ref().as_ref() + } +} + +impl Read for EndianAwareReader { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.reader.read(buf) + } +} diff --git a/src/cog.rs b/src/cog.rs index 8ebd6f0..84d2bd2 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -3,9 +3,10 @@ use crate::error::Result; use crate::ifd::ImageFileDirectories; use crate::AsyncFileReader; +#[derive(Debug)] pub struct COGReader { #[allow(dead_code)] - reader: Box, + cursor: AsyncCursor, ifds: ImageFileDirectories, } @@ -21,8 +22,7 @@ impl COGReader { let ifds = ImageFileDirectories::open(&mut cursor, first_ifd_location as usize).await?; - let reader = cursor.into_inner(); - Ok(Self { reader, ifds }) + Ok(Self { cursor, ifds }) } pub fn ifds(&self) -> &ImageFileDirectories { diff --git a/src/ifd.rs b/src/ifd.rs index ec2560c..5422b6d 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -1,9 +1,8 @@ use std::collections::HashMap; -use std::io::{Cursor, Read}; +use std::io::Read; use std::ops::Range; -use byteorder::{LittleEndian, ReadBytesExt}; -use bytes::{Buf, Bytes}; +use bytes::Bytes; use num_enum::TryFromPrimitive; use tiff::decoder::ifd::Value; use tiff::tags::{ @@ -23,6 +22,7 @@ const DOCUMENT_NAME: u16 = 269; /// A collection of all the IFD // TODO: maybe separate out the primary/first image IFD out of the vec, as that one should have // geospatial metadata? +#[derive(Debug)] pub struct ImageFileDirectories { /// There's always at least one IFD in a TIFF. We store this separately ifds: Vec, @@ -152,13 +152,13 @@ pub struct ImageFileDirectory { /// different from PaletteColor then next denotes the colorspace of the ColorMap entries. pub(crate) color_map: Option>, - pub(crate) tile_width: u32, - pub(crate) tile_height: u32, + pub(crate) tile_width: Option, + pub(crate) tile_height: Option, - pub(crate) tile_offsets: Vec, - pub(crate) tile_byte_counts: Vec, + pub(crate) tile_offsets: Option>, + pub(crate) tile_byte_counts: Option>, - pub(crate) extra_samples: Option>, + pub(crate) extra_samples: Option>, pub(crate) sample_format: Vec, @@ -253,6 +253,7 @@ impl ImageFileDirectory { let mut other_tags = HashMap::new(); tag_data.drain().try_for_each(|(tag, value)| { + dbg!(&tag); match tag { Tag::NewSubfileType => new_subfile_type = Some(value.into_u32()?), Tag::ImageWidth => image_width = Some(value.into_u32()?), @@ -297,7 +298,7 @@ impl ImageFileDirectory { Tag::TileLength => tile_height = Some(value.into_u32()?), Tag::TileOffsets => tile_offsets = Some(value.into_u32_vec()?), Tag::TileByteCounts => tile_byte_counts = Some(value.into_u32_vec()?), - Tag::ExtraSamples => extra_samples = Some(value.into_u8_vec()?), + Tag::ExtraSamples => extra_samples = Some(value.into_u16_vec()?), Tag::SampleFormat => { let values = value.into_u16_vec()?; sample_format = Some( @@ -343,7 +344,7 @@ impl ImageFileDirectory { let key_revision = header[1]; assert_eq!(key_revision, 1); - // let key_minor_revision = header[2]; + let _key_minor_revision = header[2]; let number_of_keys = header[3]; let mut tags = HashMap::with_capacity(number_of_keys as usize); @@ -402,6 +403,16 @@ impl ImageFileDirectory { geo_key_directory = Some(GeoKeyDirectory::from_tags(tags)?); } + let samples_per_pixel = samples_per_pixel.expect("samples_per_pixel not found"); + let planar_configuration = if let Some(planar_configuration) = planar_configuration { + planar_configuration + } else if samples_per_pixel == 1 { + // If SamplesPerPixel is 1, PlanarConfiguration is irrelevant, and need not be included. + // https://web.archive.org/web/20240329145253/https://www.awaresystems.be/imaging/tiff/tifftags/planarconfiguration.html + PlanarConfiguration::Chunky + } else { + panic!("planar_configuration not found and samples_per_pixel not 1") + }; Ok(Self { new_subfile_type, image_width: image_width.expect("image_width not found"), @@ -414,14 +425,14 @@ impl ImageFileDirectory { image_description, strip_offsets, orientation, - samples_per_pixel: samples_per_pixel.expect("samples_per_pixel not found"), + samples_per_pixel, rows_per_strip, strip_byte_counts, min_sample_value, max_sample_value, x_resolution, y_resolution, - planar_configuration: planar_configuration.expect("planar_configuration not found"), + planar_configuration, resolution_unit, software, date_time, @@ -429,12 +440,15 @@ impl ImageFileDirectory { host_computer, predictor, color_map, - tile_width: tile_width.expect("tile_width not found"), - tile_height: tile_height.expect("tile_height not found"), - tile_offsets: tile_offsets.expect("tile_offsets not found"), - tile_byte_counts: tile_byte_counts.expect("tile_byte_counts not found"), + tile_width, + tile_height, + tile_offsets, + tile_byte_counts, extra_samples, - sample_format: sample_format.expect("sample_format not found"), + // Uint8 is the default for SampleFormat + // https://web.archive.org/web/20240329145340/https://www.awaresystems.be/imaging/tiff/tifftags/sampleformat.html + sample_format: sample_format + .unwrap_or(vec![SampleFormat::Uint; samples_per_pixel as _]), copyright, jpeg_tables, geo_key_directory, @@ -570,21 +584,21 @@ impl ImageFileDirectory { self.predictor } - pub fn tile_width(&self) -> u32 { + pub fn tile_width(&self) -> Option { self.tile_width } - pub fn tile_height(&self) -> u32 { + pub fn tile_height(&self) -> Option { self.tile_height } - pub fn tile_offsets(&self) -> &[u32] { - &self.tile_offsets + pub fn tile_offsets(&self) -> Option<&[u32]> { + self.tile_offsets.as_deref() } - pub fn tile_byte_counts(&self) -> &[u32] { - &self.tile_byte_counts + pub fn tile_byte_counts(&self) -> Option<&[u32]> { + self.tile_byte_counts.as_deref() } - pub fn extra_samples(&self) -> Option<&[u8]> { + pub fn extra_samples(&self) -> Option<&[u16]> { self.extra_samples.as_deref() } @@ -668,12 +682,14 @@ impl ImageFileDirectory { } } - fn get_tile_byte_range(&self, x: usize, y: usize) -> Range { - let idx = (y * self.tile_count().0) + x; - let offset = self.tile_offsets[idx] as usize; + fn get_tile_byte_range(&self, x: usize, y: usize) -> Option> { + let tile_offsets = self.tile_offsets.as_deref()?; + let tile_byte_counts = self.tile_byte_counts.as_deref()?; + let idx = (y * self.tile_count()?.0) + x; + let offset = tile_offsets[idx] as usize; // TODO: aiocogeo has a -1 here, but I think that was in error - let byte_count = self.tile_byte_counts[idx] as usize; - offset as _..(offset + byte_count) as _ + let byte_count = tile_byte_counts[idx] as usize; + Some(offset as _..(offset + byte_count) as _) } pub async fn get_tile( @@ -683,7 +699,9 @@ impl ImageFileDirectory { mut reader: Box, decoder_registry: &DecoderRegistry, ) -> Result { - let range = self.get_tile_byte_range(x, y); + let range = self + .get_tile_byte_range(x, y) + .ok_or(AiocogeoError::General("Not a tiled TIFF".to_string()))?; let buf = reader.get_bytes(range).await?; decode_tile( buf, @@ -704,11 +722,14 @@ impl ImageFileDirectory { assert_eq!(x.len(), y.len(), "x and y should have same len"); // 1: Get all the byte ranges for all tiles - let byte_ranges: Vec<_> = x + let byte_ranges = x .iter() .zip(y) - .map(|(x, y)| self.get_tile_byte_range(*x, *y)) - .collect(); + .map(|(x, y)| { + self.get_tile_byte_range(*x, *y) + .ok_or(AiocogeoError::General("Not a tiled TIFF".to_string())) + }) + .collect::>>()?; // 2: Fetch using `get_ranges let buffers = reader.get_byte_ranges(byte_ranges).await?; @@ -729,10 +750,11 @@ impl ImageFileDirectory { } /// Return the number of x/y tiles in the IFD - pub fn tile_count(&self) -> (usize, usize) { - let x_count = (self.image_width as f64 / self.tile_width as f64).ceil(); - let y_count = (self.image_height as f64 / self.tile_height as f64).ceil(); - (x_count as usize, y_count as usize) + /// Returns `None` if this is not a tiled TIFF + pub fn tile_count(&self) -> Option<(usize, usize)> { + let x_count = (self.image_width as f64 / self.tile_width? as f64).ceil(); + let y_count = (self.image_height as f64 / self.tile_height? as f64).ceil(); + Some((x_count as usize, y_count as usize)) } /// Return the geotransform of the image @@ -828,19 +850,19 @@ async fn read_tag_value( // NOTE: we should only be reading value_byte_length when it's 4 bytes or fewer. Right now // we're reading even if it's 8 bytes, but then only using the first 4 bytes of this // buffer. - let data = cursor.read(value_byte_length).await?; + let mut data = cursor.read(value_byte_length).await?; // 2b: the value is at most 4 bytes or doesn't fit in the offset field. return Ok(match tag_type { - Type::BYTE | Type::UNDEFINED => Value::Byte(data.reader().read_u8()?), - Type::SBYTE => Value::Signed(data.reader().read_i8()? as i32), - Type::SHORT => Value::Short(data.reader().read_u16::()?), - Type::SSHORT => Value::Signed(data.reader().read_i16::()? as i32), - Type::LONG => Value::Unsigned(data.reader().read_u32::()?), - Type::SLONG => Value::Signed(data.reader().read_i32::()?), - Type::FLOAT => Value::Float(data.reader().read_f32::()?), + Type::BYTE | Type::UNDEFINED => Value::Byte(data.read_u8()?), + Type::SBYTE => Value::Signed(data.read_i8()? as i32), + Type::SHORT => Value::Short(data.read_u16()?), + Type::SSHORT => Value::Signed(data.read_i16()? as i32), + Type::LONG => Value::Unsigned(data.read_u32()?), + Type::SLONG => Value::Signed(data.read_i32()?), + Type::FLOAT => Value::Float(data.read_f32()?), Type::ASCII => { - if data[0] == 0 { + if data.as_ref()[0] == 0 { Value::Ascii("".to_string()) } else { panic!("Invalid tag"); @@ -848,37 +870,37 @@ async fn read_tag_value( } } Type::LONG8 => { - let offset = data.reader().read_u32::()?; + let offset = data.read_u32()?; cursor.seek(offset as usize); Value::UnsignedBig(cursor.read_u64().await?) } Type::SLONG8 => { - let offset = data.reader().read_u32::()?; + let offset = data.read_u32()?; cursor.seek(offset as usize); Value::SignedBig(cursor.read_i64().await?) } Type::DOUBLE => { - let offset = data.reader().read_u32::()?; + let offset = data.read_u32()?; cursor.seek(offset as usize); Value::Double(cursor.read_f64().await?) } Type::RATIONAL => { - let offset = data.reader().read_u32::()?; + let offset = data.read_u32()?; cursor.seek(offset as usize); let numerator = cursor.read_u32().await?; let denominator = cursor.read_u32().await?; Value::Rational(numerator, denominator) } Type::SRATIONAL => { - let offset = data.reader().read_u32::()?; + let offset = data.read_u32()?; cursor.seek(offset as usize); let numerator = cursor.read_i32().await?; let denominator = cursor.read_i32().await?; Value::SRational(numerator, denominator) } - Type::IFD => Value::Ifd(data.reader().read_u32::()?), + Type::IFD => Value::Ifd(data.read_u32()?), Type::IFD8 => { - let offset = data.reader().read_u32::()?; + let offset = data.read_u32()?; cursor.seek(offset as usize); Value::IfdBig(cursor.read_u64().await?) } @@ -888,33 +910,31 @@ async fn read_tag_value( // Case 3: There is more than one value, but it fits in the offset field. if value_byte_length <= 4 { - let data = cursor.read(value_byte_length).await?; + let mut data = cursor.read(value_byte_length).await?; cursor.advance(4 - value_byte_length); match tag_type { Type::BYTE | Type::UNDEFINED => { return { - let mut data_cursor = Cursor::new(data); Ok(Value::List( (0..count) - .map(|_| Value::Byte(data_cursor.read_u8().unwrap())) + .map(|_| Value::Byte(data.read_u8().unwrap())) .collect(), )) - } + }; } Type::SBYTE => { return { - let mut data_cursor = Cursor::new(data); Ok(Value::List( (0..count) - .map(|_| Value::Signed(data_cursor.read_i8().unwrap() as i32)) + .map(|_| Value::Signed(data.read_i8().unwrap() as i32)) .collect(), )) } } Type::ASCII => { let mut buf = vec![0; count]; - data.reader().read_exact(&mut buf)?; + data.read_exact(&mut buf)?; if buf.is_ascii() && buf.ends_with(&[0]) { let v = std::str::from_utf8(&buf) .map_err(|err| AiocogeoError::General(err.to_string()))?; @@ -926,50 +946,44 @@ async fn read_tag_value( } } Type::SHORT => { - let mut reader = data.reader(); let mut v = Vec::new(); for _ in 0..count { - v.push(Value::Short(reader.read_u16::()?)); + v.push(Value::Short(data.read_u16()?)); } return Ok(Value::List(v)); } Type::SSHORT => { - let mut reader = data.reader(); let mut v = Vec::new(); for _ in 0..count { - v.push(Value::Signed(i32::from(reader.read_i16::()?))); + v.push(Value::Signed(i32::from(data.read_i16()?))); } return Ok(Value::List(v)); } Type::LONG => { - let mut reader = data.reader(); let mut v = Vec::new(); for _ in 0..count { - v.push(Value::Unsigned(reader.read_u32::()?)); + v.push(Value::Unsigned(data.read_u32()?)); } return Ok(Value::List(v)); } Type::SLONG => { - let mut reader = data.reader(); let mut v = Vec::new(); for _ in 0..count { - v.push(Value::Signed(reader.read_i32::()?)); + v.push(Value::Signed(data.read_i32()?)); } return Ok(Value::List(v)); } Type::FLOAT => { - let mut reader = data.reader(); let mut v = Vec::new(); for _ in 0..count { - v.push(Value::Float(reader.read_f32::()?)); + v.push(Value::Float(data.read_f32()?)); } return Ok(Value::List(v)); } Type::IFD => { - let mut reader = data.reader(); let mut v = Vec::new(); for _ in 0..count { - v.push(Value::Ifd(reader.read_u32::()?)); + v.push(Value::Ifd(data.read_u32()?)); } return Ok(Value::List(v)); } @@ -1100,8 +1114,8 @@ async fn read_tag_value( Type::ASCII => { let n = count; let mut out = vec![0; n]; - let buf = cursor.read(n).await?; - buf.reader().read_exact(&mut out)?; + let mut buf = cursor.read(n).await?; + buf.read_exact(&mut out)?; // Strings may be null-terminated, so we trim anything downstream of the null byte if let Some(first) = out.iter().position(|&b| b == 0) { diff --git a/tests/image_tiff/README.md b/tests/image_tiff/README.md new file mode 100644 index 0000000..de7a73c --- /dev/null +++ b/tests/image_tiff/README.md @@ -0,0 +1,3 @@ +Tests that have been vendored from image-tiff + +https://github.com/image-rs/image-tiff/tree/3bfb43e83e31b0da476832067ada68a82b378b7b diff --git a/tests/image_tiff/decode_bigtiff_images.rs b/tests/image_tiff/decode_bigtiff_images.rs new file mode 100644 index 0000000..9113f42 --- /dev/null +++ b/tests/image_tiff/decode_bigtiff_images.rs @@ -0,0 +1,46 @@ +extern crate tiff; + +use tiff::decoder::Decoder; +use tiff::tags::Tag; +use tiff::ColorType; + +use std::fs::File; +use std::path::PathBuf; + +const TEST_IMAGE_DIR: &str = "./tests/images/bigtiff"; + +#[test] +fn test_big_tiff() { + let filenames = ["BigTIFF.tif", "BigTIFFMotorola.tif", "BigTIFFLong.tif"]; + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (64, 64) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::RGB(8) + ); + assert_eq!( + decoder + .get_tag_u64(Tag::StripOffsets) + .expect("Cannot get StripOffsets"), + 16 + ); + assert_eq!( + decoder + .get_tag_u64(Tag::RowsPerStrip) + .expect("Cannot get RowsPerStrip"), + 64 + ); + assert_eq!( + decoder + .get_tag_u64(Tag::StripByteCounts) + .expect("Cannot get StripByteCounts"), + 12288 + ) + } +} diff --git a/tests/image_tiff/decode_fp16_images.rs b/tests/image_tiff/decode_fp16_images.rs new file mode 100644 index 0000000..71c346f --- /dev/null +++ b/tests/image_tiff/decode_fp16_images.rs @@ -0,0 +1,195 @@ +extern crate tiff; + +use tiff::decoder::{Decoder, DecodingResult}; +use tiff::ColorType; + +use std::fs::File; +use std::path::PathBuf; + +const TEST_IMAGE_DIR: &str = "./tests/image_tiff/images/"; + +/// Test a basic all white image +#[test] +fn test_white_ieee_fp16() { + let filenames = ["white-fp16.tiff"]; + + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (256, 256) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::Gray(16) + ); + if let DecodingResult::F16(img) = decoder.read_image().unwrap() { + for p in img { + assert!(p == half::f16::from_f32_const(1.0)); + } + } else { + panic!("Wrong data type"); + } + } +} + +/// Test a single black pixel, to make sure scaling is ok +#[test] +fn test_one_black_pixel_ieee_fp16() { + let filenames = ["single-black-fp16.tiff"]; + + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (256, 256) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::Gray(16) + ); + if let DecodingResult::F16(img) = decoder.read_image().unwrap() { + for (i, p) in img.iter().enumerate() { + if i == 0 { + assert!(p < &half::f16::from_f32_const(0.001)); + } else { + assert!(p == &half::f16::from_f32_const(1.0)); + } + } + } else { + panic!("Wrong data type"); + } + } +} + +/// Test white with horizontal differencing predictor +#[test] +fn test_pattern_horizontal_differencing_ieee_fp16() { + let filenames = ["white-fp16-pred2.tiff"]; + + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (256, 256) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::Gray(16) + ); + if let DecodingResult::F16(img) = decoder.read_image().unwrap() { + // 0, 2, 5, 8, 12, 16, 255 are black + let black = [0, 2, 5, 8, 12, 16, 255]; + for (i, p) in img.iter().enumerate() { + if black.contains(&i) { + assert!(p < &half::f16::from_f32_const(0.001)); + } else { + assert!(p == &half::f16::from_f32_const(1.0)); + } + } + } else { + panic!("Wrong data type"); + } + } +} + +/// Test white with floating point predictor +#[test] +fn test_pattern_predictor_ieee_fp16() { + let filenames = ["white-fp16-pred3.tiff"]; + + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (256, 256) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::Gray(16) + ); + if let DecodingResult::F16(img) = decoder.read_image().unwrap() { + // 0, 2, 5, 8, 12, 16, 255 are black + let black = [0, 2, 5, 8, 12, 16, 255]; + for (i, p) in img.iter().enumerate() { + if black.contains(&i) { + assert!(p < &half::f16::from_f32_const(0.001)); + } else { + assert!(p == &half::f16::from_f32_const(1.0)); + } + } + } else { + panic!("Wrong data type"); + } + } +} + +/// Test several random images +/// we'rell compare against a pnm file, that scales from 0 (for 0.0) to 65767 (for 1.0) +#[test] +fn test_predictor_ieee_fp16() { + // first parse pnm, skip the first 4 \n + let pnm_path = PathBuf::from(TEST_IMAGE_DIR).join("random-fp16.pgm"); + let pnm_bytes = std::fs::read(pnm_path).expect("Failed to read expected PNM file"); + + // PGM looks like this: + // --- + // P5 + // #Created with GIMP + // 16 16 + // 65535 + // ... + // --- + // get index of 4th \n + let byte_start = pnm_bytes + .iter() + .enumerate() + .filter(|(_, &v)| v == b'\n') + .map(|(i, _)| i) + .nth(3) + .expect("Must be 4 \\n's"); + + let pnm_values: Vec = pnm_bytes[(byte_start + 1)..] + .chunks(2) + .map(|slice| { + let bts = [slice[0], slice[1]]; + (u16::from_be_bytes(bts) as f32) / (u16::MAX as f32) + }) + .collect(); + assert!(pnm_values.len() == 256); + + let filenames = [ + "random-fp16-pred2.tiff", + "random-fp16-pred3.tiff", + "random-fp16.tiff", + ]; + + for filename in filenames.iter() { + let path = PathBuf::from(TEST_IMAGE_DIR).join(filename); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!( + decoder.dimensions().expect("Cannot get dimensions"), + (16, 16) + ); + assert_eq!( + decoder.colortype().expect("Cannot get colortype"), + ColorType::Gray(16) + ); + if let DecodingResult::F16(img) = decoder.read_image().unwrap() { + for (exp, found) in std::iter::zip(pnm_values.iter(), img.iter()) { + assert!((exp - found.to_f32()).abs() < 0.0001); + } + } else { + panic!("Wrong data type"); + } + } +} diff --git a/tests/image_tiff/decode_geotiff_images.rs b/tests/image_tiff/decode_geotiff_images.rs new file mode 100644 index 0000000..83457ec --- /dev/null +++ b/tests/image_tiff/decode_geotiff_images.rs @@ -0,0 +1,45 @@ +extern crate tiff; + +use async_tiff::{COGReader, ObjectReader}; +use object_store::local::LocalFileSystem; + +use std::env::current_dir; +use std::sync::Arc; + +const TEST_IMAGE_DIR: &str = "tests/image_tiff/images"; + +#[tokio::test] +async fn test_geo_tiff() { + let filenames = ["geo-5b.tif"]; + let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap()); + + for filename in filenames.iter() { + let path = format!("{TEST_IMAGE_DIR}/{filename}"); + let reader = ObjectReader::new(store.clone(), path.as_str().into()); + let image_reader = COGReader::try_open(Box::new(reader)).await.unwrap(); + let ifd = &image_reader.ifds().as_ref()[0]; + dbg!(&ifd); + assert_eq!(ifd.image_height(), 10); + assert_eq!(ifd.image_width(), 10); + assert_eq!(ifd.bits_per_sample(), vec![16; 5]); + assert_eq!( + ifd.strip_offsets().expect("Cannot get StripOffsets"), + vec![418] + ); + assert_eq!(ifd.rows_per_strip().expect("Cannot get RowsPerStrip"), 10); + assert_eq!( + ifd.strip_byte_counts().expect("Cannot get StripByteCounts"), + vec![1000] + ); + assert_eq!( + ifd.model_pixel_scale().expect("Cannot get pixel scale"), + vec![60.0, 60.0, 0.0] + ); + + // We don't currently support reading strip images + // let DecodingResult::I16(data) = decoder.read_image().unwrap() else { + // panic!("Cannot read band data") + // }; + // assert_eq!(data.len(), 500); + } +} diff --git a/tests/image_tiff/decode_images.rs b/tests/image_tiff/decode_images.rs new file mode 100644 index 0000000..6d0bf7f --- /dev/null +++ b/tests/image_tiff/decode_images.rs @@ -0,0 +1,643 @@ +extern crate tiff; + +use async_tiff::{COGReader, ObjectReader}; +use object_store::local::LocalFileSystem; +use tiff::tags::PhotometricInterpretation; + +use std::env::current_dir; +use std::sync::Arc; + +const TEST_IMAGE_DIR: &str = "tests/image_tiff/images/"; + +async fn open_tiff(filename: &str) -> COGReader { + let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap()); + let path = format!("{TEST_IMAGE_DIR}/{filename}"); + let reader = Box::new(ObjectReader::new(store.clone(), path.as_str().into())); + COGReader::try_open(reader).await.unwrap() +} + +#[tokio::test] +async fn cmyk_u8() { + let tiff = open_tiff("cmyk-3c-8b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::CMYK + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 8)); +} + +#[tokio::test] +async fn test_cmyk_u16() { + let tiff = open_tiff("cmyk-3c-16b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::CMYK + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 16)); +} + +#[tokio::test] +async fn test_cmyk_f32() { + let tiff = open_tiff("cmyk-3c-32b-float.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::CMYK + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 32)); +} + +#[tokio::test] +async fn test_gray_u8() { + let tiff = open_tiff("minisblack-1c-8b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 8)); +} + +#[tokio::test] +async fn test_gray_u12() { + let tiff = open_tiff("12bit.cropped.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 12)); +} + +#[tokio::test] +async fn test_gray_u16() { + let tiff = open_tiff("minisblack-1c-16b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 16)); +} + +#[tokio::test] +async fn test_gray_u32() { + let tiff = open_tiff("gradient-1c-32b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 32)); +} + +#[tokio::test] +async fn test_gray_u64() { + let tiff = open_tiff("gradient-1c-64b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 64)); +} + +#[tokio::test] +async fn test_gray_f32() { + let tiff = open_tiff("gradient-1c-32b-float.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 32)); +} + +#[tokio::test] +async fn test_gray_f64() { + let tiff = open_tiff("gradient-1c-64b-float.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 64)); +} + +#[tokio::test] +async fn test_rgb_u8() { + let tiff = open_tiff("rgb-3c-8b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 8)); +} + +#[tokio::test] +async fn test_rgb_u12() { + let tiff = open_tiff("12bit.cropped.rgb.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 12)); +} + +#[tokio::test] +async fn test_rgb_u16() { + let tiff = open_tiff("rgb-3c-16b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 16)); +} + +#[tokio::test] +async fn test_rgb_u32() { + let tiff = open_tiff("gradient-3c-32b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 32)); +} + +#[tokio::test] +async fn test_rgb_u64() { + let tiff = open_tiff("gradient-3c-64b.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 64)); +} + +#[tokio::test] +async fn test_rgb_f32() { + let tiff = open_tiff("gradient-3c-32b-float.tiff").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 32)); +} + +#[tokio::test] +async fn test_int8() { + let tiff = open_tiff("int8.tif").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 8)); +} + +#[tokio::test] +async fn test_int8_rgb() { + let tiff = open_tiff("int8_rgb.tif").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 8)); +} + +#[tokio::test] +async fn test_int16() { + let tiff = open_tiff("int16.tif").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::BlackIsZero + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 16)); +} + +#[tokio::test] +async fn test_int16_rgb() { + let tiff = open_tiff("int16_rgb.tif").await; + let ifd = &tiff.ifds().as_ref()[0]; + assert!(matches!( + ifd.photometric_interpretation(), + PhotometricInterpretation::RGB + )); + assert!(ifd.bits_per_sample().iter().all(|x| *x == 16)); +} + +#[tokio::test] +async fn test_string_tags() { + // these files have null-terminated strings for their Software tag. One has extra bytes after + // the null byte, so we check both to ensure that we're truncating properly + let filenames = ["minisblack-1c-16b.tiff", "rgb-3c-16b.tiff"]; + for filename in filenames.iter() { + let tiff = open_tiff(filename).await; + let ifd = &tiff.ifds().as_ref()[0]; + let software = ifd.software().unwrap(); + assert_eq!( + software, + "GraphicsMagick 1.2 unreleased Q16 http://www.GraphicsMagick.org/" + ); + } +} + +// #[test] +// fn test_decode_data() { +// let mut image_data = Vec::new(); +// for x in 0..100 { +// for y in 0..100u8 { +// let val = x + y; +// image_data.push(val); +// image_data.push(val); +// image_data.push(val); +// } +// } +// let file = File::open("./tests/decodedata-rgb-3c-8b.tiff").unwrap(); +// let mut decoder = Decoder::new(file).unwrap(); +// assert_eq!(decoder.colortype().unwrap(), ColorType::RGB(8)); +// assert_eq!(decoder.dimensions().unwrap(), (100, 100)); +// if let DecodingResult::U8(img_res) = decoder.read_image().unwrap() { +// assert_eq!(image_data, img_res); +// } else { +// panic!("Wrong data type"); +// } +// } + +// macro_rules! test_image_sum { +// ($name:ident, $buffer:ident, $sum_ty:ty) => { +// fn $name(file: &str, expected_type: ColorType, expected_sum: $sum_ty) { +// let path = PathBuf::from(TEST_IMAGE_DIR).join(file); +// let img_file = File::open(path).expect("Cannot find test image!"); +// let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +// assert_eq!(decoder.colortype().unwrap(), expected_type); +// let img_res = decoder.read_image().unwrap(); + +// match img_res { +// DecodingResult::$buffer(res) => { +// let sum: $sum_ty = res.into_iter().map(<$sum_ty>::from).sum(); +// assert_eq!(sum, expected_sum); +// } +// _ => panic!("Wrong bit depth"), +// } +// } +// }; +// } + +// test_image_sum!(test_image_sum_u8, U8, u64); +// test_image_sum!(test_image_sum_i8, I8, i64); +// test_image_sum!(test_image_sum_u16, U16, u64); +// test_image_sum!(test_image_sum_i16, I16, i64); +// test_image_sum!(test_image_sum_u32, U32, u64); +// test_image_sum!(test_image_sum_u64, U64, u64); +// test_image_sum!(test_image_sum_f32, F32, f32); +// test_image_sum!(test_image_sum_f64, F64, f64); + +// /// Tests that a decoder can be constructed for an image and the color type +// /// read from the IFD and is of the appropriate type, but the type is +// /// unsupported. +// fn test_image_color_type_unsupported(file: &str, expected_type: ColorType) { +// let path = PathBuf::from(TEST_IMAGE_DIR).join(file); +// let img_file = File::open(path).expect("Cannot find test image!"); +// let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +// assert_eq!(decoder.colortype().unwrap(), expected_type); +// assert!(match decoder.read_image() { +// Err(tiff::TiffError::UnsupportedError( +// tiff::TiffUnsupportedError::UnsupportedColorType(_), +// )) => true, +// _ => false, +// }); +// } + +// #[test] +// fn issue_69() { +// test_image_sum_u16("issue_69_lzw.tiff", ColorType::Gray(16), 1015486); +// test_image_sum_u16("issue_69_packbits.tiff", ColorType::Gray(16), 1015486); +// } + +// // TODO: GrayA support +// //#[test] +// //fn test_gray_alpha_u8() +// //{ +// //let img_file = File::open("./tests/images/minisblack-2c-8b-alpha.tiff").expect("Cannot find test image!"); +// //let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +// //assert_eq!(decoder.colortype().unwrap(), ColorType::GrayA(8)); +// //let img_res = decoder.read_image(); +// //assert!(img_res.is_ok()); +// //} + +// #[test] +// fn test_tiled_gray_i1() { +// test_image_sum_u8("tiled-gray-i1.tif", ColorType::Gray(1), 30531); +// } + +// #[test] +// fn test_tiled_rgb_u8() { +// test_image_sum_u8("tiled-rgb-u8.tif", ColorType::RGB(8), 39528948); +// } + +// #[test] +// fn test_tiled_rect_rgb_u8() { +// test_image_sum_u8("tiled-rect-rgb-u8.tif", ColorType::RGB(8), 62081032); +// } + +// /* #[test] +// fn test_tiled_jpeg_rgb_u8() { +// test_image_sum_u8("tiled-jpeg-rgb-u8.tif", ColorType::RGB(8), 93031606); +// } */ +// #[test] +// fn test_tiled_oversize_gray_i8() { +// test_image_sum_i8("tiled-oversize-gray-i8.tif", ColorType::Gray(8), 1214996); +// } + +// #[test] +// fn test_tiled_cmyk_i8() { +// test_image_sum_i8("tiled-cmyk-i8.tif", ColorType::CMYK(8), 1759101); +// } + +// #[test] +// fn test_tiled_incremental() { +// let file = "tiled-rgb-u8.tif"; +// let expected_type = ColorType::RGB(8); +// let sums = [ +// 188760, 195639, 108148, 81986, 665088, 366140, 705317, 423366, 172033, 324455, 244102, +// 81853, 181258, 247971, 129486, 55600, 565625, 422102, 730888, 379271, 232142, 292549, +// 244045, 86866, 188141, 115036, 150785, 84389, 353170, 459325, 719619, 329594, 278663, +// 220474, 243048, 113563, 189152, 109684, 179391, 122188, 279651, 622093, 724682, 302459, +// 268428, 204499, 224255, 124674, 170668, 121868, 192768, 183367, 378029, 585651, 657712, +// 296790, 241444, 197083, 198429, 134869, 182318, 86034, 203655, 182338, 297255, 601284, +// 633813, 242531, 228578, 206441, 193552, 125412, 181527, 165439, 202531, 159538, 268388, +// 565790, 611382, 272967, 236497, 215154, 158881, 90806, 106114, 182342, 191824, 186138, +// 215174, 393193, 701228, 198866, 227944, 193830, 166330, 49008, 55719, 122820, 197316, +// 161969, 203152, 170986, 624427, 188605, 186187, 111064, 115192, 39538, 48626, 163929, +// 144682, 135796, 194141, 154198, 584125, 180255, 153524, 121433, 132641, 35743, 47798, +// 152343, 162874, 167664, 160175, 133038, 659882, 138339, 166470, 124173, 118929, 51317, +// 45267, 155776, 161331, 161006, 130052, 137618, 337291, 106481, 161999, 127343, 87724, +// 59540, 63907, 155677, 140668, 141523, 108061, 168657, 186482, 98599, 147614, 139963, 90444, +// 56602, 92547, 125644, 134212, 126569, 144153, 179800, 174516, 133969, 129399, 117681, +// 83305, 55075, 110737, 115108, 128572, 128911, 130922, 179986, 143288, 145884, 155856, +// 96683, 94057, 56238, 79649, 71651, 70182, 75010, 77009, 98855, 78979, 74341, 83482, 53403, +// 59842, 30305, +// ]; + +// let path = PathBuf::from(TEST_IMAGE_DIR).join(file); +// let img_file = File::open(path).expect("Cannot find test image!"); +// let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +// assert_eq!(decoder.colortype().unwrap(), expected_type); + +// let tiles = decoder.tile_count().unwrap(); +// assert_eq!(tiles as usize, sums.len()); + +// for tile in 0..tiles { +// match decoder.read_chunk(tile).unwrap() { +// DecodingResult::U8(res) => { +// let sum: u64 = res.into_iter().map(::from).sum(); +// assert_eq!(sum, sums[tile as usize]); +// } +// _ => panic!("Wrong bit depth"), +// } +// } +// } + +// #[test] +// fn test_planar_rgb_u8() { +// // gdal_translate tiled-rgb-u8.tif planar-rgb-u8.tif -co INTERLEAVE=BAND -co COMPRESS=LZW -co PROFILE=BASELINE +// let file = "planar-rgb-u8.tif"; +// let expected_type = ColorType::RGB(8); + +// let path = PathBuf::from(TEST_IMAGE_DIR).join(file); +// let img_file = File::open(path).expect("Cannot find test image!"); +// let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); +// assert_eq!(decoder.colortype().unwrap(), expected_type); + +// let chunks = decoder.strip_count().unwrap(); +// assert_eq!(chunks as usize, 72); + +// // convert -quiet planar-rgb-u8.tif[0] -crop 1x1+0+0 txt: +// // 0,0: (73,51,30) #49331E srgb(73,51,30) + +// // 1st band (red) +// match decoder.read_chunk(0).unwrap() { +// DecodingResult::U8(chunk) => { +// assert_eq!(chunk[0], 73); +// } +// _ => panic!("Wrong bit depth"), +// } +// // 2nd band (green) +// match decoder.read_chunk(chunks / 3).unwrap() { +// DecodingResult::U8(chunk) => { +// assert_eq!(chunk[0], 51); +// } +// _ => panic!("Wrong bit depth"), +// } +// // 3rd band (blue) +// match decoder.read_chunk(chunks / 3 * 2).unwrap() { +// DecodingResult::U8(chunk) => { +// assert_eq!(chunk[0], 30); +// } +// _ => panic!("Wrong bit depth"), +// } + +// test_image_sum_u8(file, ColorType::RGB(8), 15417630); +// } + +// #[test] +// fn test_div_zero() { +// use tiff::{TiffError, TiffFormatError}; + +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, +// 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 39, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, +// 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 158, 0, 0, 251, 67, 1, 3, 0, +// 1, 0, 0, 0, 40, 0, 0, 0, 66, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 178, 178, 178, 178, +// 178, 178, 178, +// ]; + +// let err = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + +// match err { +// TiffError::FormatError(TiffFormatError::StripTileTagConflict) => {} +// unexpected => panic!("Unexpected error {}", unexpected), +// } +// } + +// #[test] +// fn test_too_many_value_bytes() { +// let image = [ +// 73, 73, 43, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 8, 0, 0, 0, +// 23, 0, 12, 0, 0, 65, 4, 0, 1, 6, 0, 0, 1, 16, 0, 1, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, +// 0, 0, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 59, 73, 84, 186, 202, 83, 240, 66, 1, 53, 22, 56, 47, +// 0, 0, 0, 0, 0, 0, 1, 222, 4, 0, 58, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 4, 0, 0, 100, 0, +// 0, 89, 89, 89, 89, 89, 89, 89, 89, 96, 1, 20, 89, 89, 89, 89, 18, +// ]; + +// let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + +// match error { +// tiff::TiffError::LimitsExceeded => {} +// unexpected => panic!("Unexpected error {}", unexpected), +// } +// } + +// #[test] +// fn fuzzer_testcase5() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 0, 1, 1, 4, 0, 1, 0, 0, +// 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, +// 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 0, 0, 0, 0, 246, 16, 0, 0, 22, 1, 4, 0, 1, +// 0, 0, 0, 40, 0, 251, 255, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 178, 178, 178, 178, +// 178, 178, 178, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn fuzzer_testcase1() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 99, 255, 255, 254, 1, 1, 4, 0, 1, +// 0, 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, +// 0, 0, 0, 0, 0, 17, 1, 4, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, +// 22, 1, 4, 0, 1, 0, 0, 0, 44, 0, 0, 0, 23, 1, 4, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 1, 0, 178, +// 178, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn fuzzer_testcase6() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 100, 0, 0, 148, 1, 1, 4, 0, 1, 0, +// 0, 0, 158, 0, 0, 251, 3, 1, 3, 255, 254, 255, 255, 0, 1, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, +// 0, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 0, 2, 0, 0, 0, 63, 0, 0, 0, 22, +// 1, 4, 0, 1, 0, 0, 0, 44, 0, 248, 255, 23, 1, 4, 0, 1, 0, 0, 0, 178, 178, 178, 0, 1, 178, +// 178, 178, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn oom() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, +// 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, +// 17, 1, 4, 0, 1, 0, 0, 0, 3, 77, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1, +// 0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 178, 48, 178, 178, 178, 178, 162, 178, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn fuzzer_testcase4() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 40, 1, 0, 0, +// 0, 158, 0, 0, 251, 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, +// 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 3, 128, 0, 0, 22, 1, 4, 0, 1, +// 0, 0, 0, 40, 0, 0, 0, 23, 1, 4, 0, 1, 0, 0, 0, 48, 178, 178, 178, 0, 1, 0, 13, 13, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn fuzzer_testcase2() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 15, 0, 0, 254, 44, 1, 0, 0, 0, 0, 0, 32, 0, 0, 0, 1, 4, 0, 1, 0, +// 0, 0, 0, 1, 0, 0, 91, 1, 1, 0, 0, 0, 0, 0, 242, 4, 0, 0, 0, 22, 0, 56, 77, 0, 77, 1, 0, 0, +// 73, 42, 0, 1, 4, 0, 1, 0, 0, 0, 4, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 158, 0, 0, 251, 3, 1, +// 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, +// 0, 0, 0, 0, 1, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 4, 61, 1, 18, 0, 1, 0, 0, 0, 202, 0, 0, 0, 17, +// 1, 100, 0, 129, 0, 0, 0, 0, 0, 0, 0, 232, 254, 252, 255, 254, 255, 255, 255, 1, 29, 0, 0, +// 22, 1, 3, 0, 1, 0, 0, 0, 16, 0, 0, 0, 23, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 123, 73, 254, 0, +// 73, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn invalid_jpeg_tag_2() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 242, 0, 1, 4, 0, 1, 0, +// 0, 0, 0, 129, 16, 0, 1, 1, 4, 0, 1, 0, 0, 0, 214, 0, 0, 248, 253, 1, 3, 0, 1, 0, 0, 0, 64, +// 0, 0, 0, 3, 1, 3, 0, 1, 0, 0, 0, 7, 0, 0, 0, 6, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 64, 14, 1, 0, +// 2, 0, 0, 148, 0, 206, 0, 0, 0, 17, 1, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, +// 1, 0, 0, 0, 22, 1, 4, 0, 17, 0, 0, 201, 1, 0, 0, 0, 23, 1, 2, 0, 20, 0, 0, 0, 194, 0, 0, 0, +// 91, 1, 7, 0, 5, 0, 0, 0, 64, 0, 0, 0, 237, 254, 65, 255, 255, 255, 255, 255, 1, 0, 0, 0, +// 22, 1, 4, 0, 1, 0, 0, 0, 42, 0, 0, 0, 23, 1, 255, 255, 255, 255, 255, 36, 36, 0, 0, 0, 0, +// 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 36, 73, 73, 0, 42, 36, 36, 36, 36, 0, 0, 8, 0, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn fuzzer_testcase3() { +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 2, 0, 0, 0, 61, 1, 9, 0, 46, 22, +// 128, 0, 0, 0, 0, 1, 6, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 17, 1, 4, 0, 27, 0, 0, 0, 0, 0, 0, +// 0, 1, 1, 3, 0, 1, 0, 0, 0, 17, 1, 0, 231, 22, 1, 1, 0, 1, 0, 0, 0, 130, 0, 0, 0, 23, 1, 4, +// 0, 14, 0, 0, 0, 0, 0, 0, 0, 133, 133, 133, 77, 77, 77, 0, 0, 22, 128, 0, 255, 255, 255, +// 255, 255, +// ]; + +// let _ = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); +// } + +// #[test] +// fn timeout() { +// use tiff::{TiffError, TiffFormatError}; + +// let image = [ +// 73, 73, 42, 0, 8, 0, 0, 0, 16, 0, 254, 0, 4, 0, 1, 68, 0, 0, 0, 2, 0, 32, 254, 252, 0, 109, +// 0, 129, 0, 0, 0, 32, 0, 58, 0, 1, 4, 0, 1, 0, 6, 0, 0, 0, 8, 0, 0, 1, 73, 73, 42, 0, 8, 0, +// 0, 0, 8, 0, 0, 1, 4, 0, 1, 0, 0, 0, 21, 0, 0, 0, 61, 1, 255, 128, 9, 0, 0, 8, 0, 1, 113, 2, +// 3, 1, 3, 0, 1, 0, 0, 0, 5, 0, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 112, 0, 0, 36, 0, 0, +// 0, 112, 56, 200, 0, 5, 0, 0, 64, 0, 0, 1, 0, 4, 0, 0, 0, 2, 0, 6, 1, 3, 0, 1, 0, 0, 0, 0, +// 0, 0, 4, 17, 1, 1, 0, 93, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 3, 6, 0, 231, 22, 1, +// 1, 0, 1, 0, 0, 0, 2, 64, 118, 36, 23, 1, 1, 0, 43, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 8, +// 0, 0, 73, 73, 42, 0, 8, 0, 0, 0, 0, 0, 32, +// ]; + +// let error = tiff::decoder::Decoder::new(std::io::Cursor::new(&image)).unwrap_err(); + +// match error { +// TiffError::FormatError(TiffFormatError::CycleInOffsets) => {} +// e => panic!("Unexpected error {:?}", e), +// } +// } + +// #[test] +// fn test_no_rows_per_strip() { +// test_image_sum_u8("no_rows_per_strip.tiff", ColorType::RGB(8), 99448840); +// } + +// #[test] +// fn test_predictor_3_rgb_f32() { +// test_image_sum_f32("predictor-3-rgb-f32.tif", ColorType::RGB(32), 54004.33); +// } + +// #[test] +// fn test_predictor_3_gray_f32() { +// test_image_sum_f32("predictor-3-gray-f32.tif", ColorType::Gray(32), 20008.275); +// } + +// #[test] +// #[cfg(feature = "zstd")] +// fn test_zstd_compression() { +// // gdal_translate -co COMPRESS=ZSTD -co ZSTD_LEVEL=20 int16.tif int16_zstd.tif +// test_image_sum_i16("int16_zstd.tif", ColorType::Gray(16), 354396); +// } diff --git a/tests/image_tiff/decodedata-rgb-3c-8b.tiff b/tests/image_tiff/decodedata-rgb-3c-8b.tiff new file mode 100644 index 0000000..ad80292 Binary files /dev/null and b/tests/image_tiff/decodedata-rgb-3c-8b.tiff differ diff --git a/tests/image_tiff/images/12bit.cropped.rgb.tiff b/tests/image_tiff/images/12bit.cropped.rgb.tiff new file mode 100644 index 0000000..8c8bb58 Binary files /dev/null and b/tests/image_tiff/images/12bit.cropped.rgb.tiff differ diff --git a/tests/image_tiff/images/12bit.cropped.tiff b/tests/image_tiff/images/12bit.cropped.tiff new file mode 100644 index 0000000..f075b6e Binary files /dev/null and b/tests/image_tiff/images/12bit.cropped.tiff differ diff --git a/tests/image_tiff/images/COPYRIGHT b/tests/image_tiff/images/COPYRIGHT new file mode 100644 index 0000000..8282186 --- /dev/null +++ b/tests/image_tiff/images/COPYRIGHT @@ -0,0 +1,21 @@ +Copyright (c) 1988-1997 Sam Leffler +Copyright (c) 1991-1997 Silicon Graphics, Inc. + +Permission to use, copy, modify, distribute, and sell this software and +its documentation for any purpose is hereby granted without fee, provided +that (i) the above copyright notices and this permission notice appear in +all copies of the software and related documentation, and (ii) the names of +Sam Leffler and Silicon Graphics may not be used in any advertising or +publicity relating to the software without the specific, prior written +permission of Sam Leffler and Silicon Graphics. + +THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, +EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY +WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR +ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, +OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF +LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +OF THIS SOFTWARE. diff --git a/tests/image_tiff/images/README.txt b/tests/image_tiff/images/README.txt new file mode 100644 index 0000000..17f6292 --- /dev/null +++ b/tests/image_tiff/images/README.txt @@ -0,0 +1,29 @@ +The test files in this directory mostly have dimension 157x151. +The naming convention is + + photometric-channels-bits.tiff + +minisblack-1c-16b.tiff +minisblack-1c-8b.tiff +miniswhite-1c-1b.tiff +palette-1c-1b.tiff +palette-1c-4b.tiff +palette-1c-8b.tiff +rgb-3c-16b.tiff +rgb-3c-8b.tiff + +logluv-3c-16b.tiff: logluv compression/photometric interp +minisblack-2c-8b-alpha.tiff: grey+alpha + +BMP files (anchient BMPv2 since v3 does not work): + + palette-1c-8b.bmp + rgb-3c-8b.bmp + +GIF files (anchient GIF '87 since modern GIF does not work): + palette-1c-8b.gif + +PNM files: + minisblack-1c-8b.pgm + miniswhite-1c-1b.pbm + rgb-3c-8b.ppm diff --git a/tests/image_tiff/images/bigtiff/BigTIFF.tif b/tests/image_tiff/images/bigtiff/BigTIFF.tif new file mode 100644 index 0000000..8a0e5a2 Binary files /dev/null and b/tests/image_tiff/images/bigtiff/BigTIFF.tif differ diff --git a/tests/image_tiff/images/bigtiff/BigTIFFLong.tif b/tests/image_tiff/images/bigtiff/BigTIFFLong.tif new file mode 100644 index 0000000..1a321ef Binary files /dev/null and b/tests/image_tiff/images/bigtiff/BigTIFFLong.tif differ diff --git a/tests/image_tiff/images/bigtiff/BigTIFFMotorola.tif b/tests/image_tiff/images/bigtiff/BigTIFFMotorola.tif new file mode 100644 index 0000000..4699ecd Binary files /dev/null and b/tests/image_tiff/images/bigtiff/BigTIFFMotorola.tif differ diff --git a/tests/image_tiff/images/cmyk-3c-16b.tiff b/tests/image_tiff/images/cmyk-3c-16b.tiff new file mode 100644 index 0000000..3b79eae Binary files /dev/null and b/tests/image_tiff/images/cmyk-3c-16b.tiff differ diff --git a/tests/image_tiff/images/cmyk-3c-32b-float.tiff b/tests/image_tiff/images/cmyk-3c-32b-float.tiff new file mode 100644 index 0000000..9ad77d8 Binary files /dev/null and b/tests/image_tiff/images/cmyk-3c-32b-float.tiff differ diff --git a/tests/image_tiff/images/cmyk-3c-8b.tiff b/tests/image_tiff/images/cmyk-3c-8b.tiff new file mode 100644 index 0000000..d906bbc Binary files /dev/null and b/tests/image_tiff/images/cmyk-3c-8b.tiff differ diff --git a/tests/image_tiff/images/geo-5b.tif b/tests/image_tiff/images/geo-5b.tif new file mode 100644 index 0000000..f52f450 Binary files /dev/null and b/tests/image_tiff/images/geo-5b.tif differ diff --git a/tests/image_tiff/images/gradient-1c-32b-float.tiff b/tests/image_tiff/images/gradient-1c-32b-float.tiff new file mode 100644 index 0000000..7acb00f Binary files /dev/null and b/tests/image_tiff/images/gradient-1c-32b-float.tiff differ diff --git a/tests/image_tiff/images/gradient-1c-32b.tiff b/tests/image_tiff/images/gradient-1c-32b.tiff new file mode 100644 index 0000000..3b05740 Binary files /dev/null and b/tests/image_tiff/images/gradient-1c-32b.tiff differ diff --git a/tests/image_tiff/images/gradient-1c-64b-float.tiff b/tests/image_tiff/images/gradient-1c-64b-float.tiff new file mode 100644 index 0000000..a2f843c Binary files /dev/null and b/tests/image_tiff/images/gradient-1c-64b-float.tiff differ diff --git a/tests/image_tiff/images/gradient-1c-64b.tiff b/tests/image_tiff/images/gradient-1c-64b.tiff new file mode 100644 index 0000000..43dec0c Binary files /dev/null and b/tests/image_tiff/images/gradient-1c-64b.tiff differ diff --git a/tests/image_tiff/images/gradient-3c-32b-float.tiff b/tests/image_tiff/images/gradient-3c-32b-float.tiff new file mode 100644 index 0000000..de3fa10 Binary files /dev/null and b/tests/image_tiff/images/gradient-3c-32b-float.tiff differ diff --git a/tests/image_tiff/images/gradient-3c-32b.tiff b/tests/image_tiff/images/gradient-3c-32b.tiff new file mode 100644 index 0000000..d76596e Binary files /dev/null and b/tests/image_tiff/images/gradient-3c-32b.tiff differ diff --git a/tests/image_tiff/images/gradient-3c-64b.tiff b/tests/image_tiff/images/gradient-3c-64b.tiff new file mode 100644 index 0000000..0c5c4dc Binary files /dev/null and b/tests/image_tiff/images/gradient-3c-64b.tiff differ diff --git a/tests/image_tiff/images/int16.tif b/tests/image_tiff/images/int16.tif new file mode 100755 index 0000000..5853b06 Binary files /dev/null and b/tests/image_tiff/images/int16.tif differ diff --git a/tests/image_tiff/images/int16_rgb.tif b/tests/image_tiff/images/int16_rgb.tif new file mode 100755 index 0000000..950a9ac Binary files /dev/null and b/tests/image_tiff/images/int16_rgb.tif differ diff --git a/tests/image_tiff/images/int16_zstd.tif b/tests/image_tiff/images/int16_zstd.tif new file mode 100644 index 0000000..c1093e4 Binary files /dev/null and b/tests/image_tiff/images/int16_zstd.tif differ diff --git a/tests/image_tiff/images/int8.tif b/tests/image_tiff/images/int8.tif new file mode 100644 index 0000000..807e0fd Binary files /dev/null and b/tests/image_tiff/images/int8.tif differ diff --git a/tests/image_tiff/images/int8_rgb.tif b/tests/image_tiff/images/int8_rgb.tif new file mode 100644 index 0000000..a0f6db0 Binary files /dev/null and b/tests/image_tiff/images/int8_rgb.tif differ diff --git a/tests/image_tiff/images/issue_69_lzw.tiff b/tests/image_tiff/images/issue_69_lzw.tiff new file mode 100644 index 0000000..3870fc6 Binary files /dev/null and b/tests/image_tiff/images/issue_69_lzw.tiff differ diff --git a/tests/image_tiff/images/issue_69_packbits.tiff b/tests/image_tiff/images/issue_69_packbits.tiff new file mode 100644 index 0000000..224edc0 Binary files /dev/null and b/tests/image_tiff/images/issue_69_packbits.tiff differ diff --git a/tests/image_tiff/images/logluv-3c-16b.tiff b/tests/image_tiff/images/logluv-3c-16b.tiff new file mode 100644 index 0000000..d43680f Binary files /dev/null and b/tests/image_tiff/images/logluv-3c-16b.tiff differ diff --git a/tests/image_tiff/images/minisblack-1c-16b.tiff b/tests/image_tiff/images/minisblack-1c-16b.tiff new file mode 100644 index 0000000..a8dbae7 Binary files /dev/null and b/tests/image_tiff/images/minisblack-1c-16b.tiff differ diff --git a/tests/image_tiff/images/minisblack-1c-8b.pgm b/tests/image_tiff/images/minisblack-1c-8b.pgm new file mode 100644 index 0000000..0faca40 Binary files /dev/null and b/tests/image_tiff/images/minisblack-1c-8b.pgm differ diff --git a/tests/image_tiff/images/minisblack-1c-8b.tiff b/tests/image_tiff/images/minisblack-1c-8b.tiff new file mode 100644 index 0000000..ea0f9ac Binary files /dev/null and b/tests/image_tiff/images/minisblack-1c-8b.tiff differ diff --git a/tests/image_tiff/images/minisblack-1c-i16b.tiff b/tests/image_tiff/images/minisblack-1c-i16b.tiff new file mode 100644 index 0000000..62c4ce6 Binary files /dev/null and b/tests/image_tiff/images/minisblack-1c-i16b.tiff differ diff --git a/tests/image_tiff/images/minisblack-1c-i8b.tiff b/tests/image_tiff/images/minisblack-1c-i8b.tiff new file mode 100644 index 0000000..e1449f9 Binary files /dev/null and b/tests/image_tiff/images/minisblack-1c-i8b.tiff differ diff --git a/tests/image_tiff/images/minisblack-2c-8b-alpha.tiff b/tests/image_tiff/images/minisblack-2c-8b-alpha.tiff new file mode 100644 index 0000000..5fe8f58 Binary files /dev/null and b/tests/image_tiff/images/minisblack-2c-8b-alpha.tiff differ diff --git a/tests/image_tiff/images/miniswhite-1c-1b.pbm b/tests/image_tiff/images/miniswhite-1c-1b.pbm new file mode 100644 index 0000000..6cff929 Binary files /dev/null and b/tests/image_tiff/images/miniswhite-1c-1b.pbm differ diff --git a/tests/image_tiff/images/miniswhite-1c-1b.tiff b/tests/image_tiff/images/miniswhite-1c-1b.tiff new file mode 100644 index 0000000..bfc0838 Binary files /dev/null and b/tests/image_tiff/images/miniswhite-1c-1b.tiff differ diff --git a/tests/image_tiff/images/no_rows_per_strip.tiff b/tests/image_tiff/images/no_rows_per_strip.tiff new file mode 100644 index 0000000..017ce58 Binary files /dev/null and b/tests/image_tiff/images/no_rows_per_strip.tiff differ diff --git a/tests/image_tiff/images/palette-1c-1b.tiff b/tests/image_tiff/images/palette-1c-1b.tiff new file mode 100644 index 0000000..25a1a32 Binary files /dev/null and b/tests/image_tiff/images/palette-1c-1b.tiff differ diff --git a/tests/image_tiff/images/palette-1c-4b.tiff b/tests/image_tiff/images/palette-1c-4b.tiff new file mode 100644 index 0000000..38ba325 Binary files /dev/null and b/tests/image_tiff/images/palette-1c-4b.tiff differ diff --git a/tests/image_tiff/images/palette-1c-8b.tiff b/tests/image_tiff/images/palette-1c-8b.tiff new file mode 100644 index 0000000..b87685a Binary files /dev/null and b/tests/image_tiff/images/palette-1c-8b.tiff differ diff --git a/tests/image_tiff/images/planar-rgb-u8.tif b/tests/image_tiff/images/planar-rgb-u8.tif new file mode 100644 index 0000000..8de45d6 Binary files /dev/null and b/tests/image_tiff/images/planar-rgb-u8.tif differ diff --git a/tests/image_tiff/images/predictor-3-gray-f32.tif b/tests/image_tiff/images/predictor-3-gray-f32.tif new file mode 100644 index 0000000..9fbda84 Binary files /dev/null and b/tests/image_tiff/images/predictor-3-gray-f32.tif differ diff --git a/tests/image_tiff/images/predictor-3-rgb-f32.tif b/tests/image_tiff/images/predictor-3-rgb-f32.tif new file mode 100644 index 0000000..d338f59 Binary files /dev/null and b/tests/image_tiff/images/predictor-3-rgb-f32.tif differ diff --git a/tests/image_tiff/images/quad-lzw-compat.tiff b/tests/image_tiff/images/quad-lzw-compat.tiff new file mode 100644 index 0000000..ec614bf Binary files /dev/null and b/tests/image_tiff/images/quad-lzw-compat.tiff differ diff --git a/tests/image_tiff/images/quad-tile.jpg.tiff b/tests/image_tiff/images/quad-tile.jpg.tiff new file mode 100644 index 0000000..99b0bc2 Binary files /dev/null and b/tests/image_tiff/images/quad-tile.jpg.tiff differ diff --git a/tests/image_tiff/images/random-fp16-pred2.tiff b/tests/image_tiff/images/random-fp16-pred2.tiff new file mode 100644 index 0000000..63e85db Binary files /dev/null and b/tests/image_tiff/images/random-fp16-pred2.tiff differ diff --git a/tests/image_tiff/images/random-fp16-pred3.tiff b/tests/image_tiff/images/random-fp16-pred3.tiff new file mode 100644 index 0000000..289b17f Binary files /dev/null and b/tests/image_tiff/images/random-fp16-pred3.tiff differ diff --git a/tests/image_tiff/images/random-fp16.pgm b/tests/image_tiff/images/random-fp16.pgm new file mode 100644 index 0000000..49d9842 Binary files /dev/null and b/tests/image_tiff/images/random-fp16.pgm differ diff --git a/tests/image_tiff/images/random-fp16.tiff b/tests/image_tiff/images/random-fp16.tiff new file mode 100644 index 0000000..77b7a27 Binary files /dev/null and b/tests/image_tiff/images/random-fp16.tiff differ diff --git a/tests/image_tiff/images/rgb-3c-16b.tiff b/tests/image_tiff/images/rgb-3c-16b.tiff new file mode 100644 index 0000000..f1a0279 Binary files /dev/null and b/tests/image_tiff/images/rgb-3c-16b.tiff differ diff --git a/tests/image_tiff/images/rgb-3c-8b.ppm b/tests/image_tiff/images/rgb-3c-8b.ppm new file mode 100644 index 0000000..6eca035 Binary files /dev/null and b/tests/image_tiff/images/rgb-3c-8b.ppm differ diff --git a/tests/image_tiff/images/rgb-3c-8b.tiff b/tests/image_tiff/images/rgb-3c-8b.tiff new file mode 100644 index 0000000..f64d404 Binary files /dev/null and b/tests/image_tiff/images/rgb-3c-8b.tiff differ diff --git a/tests/image_tiff/images/single-black-fp16.tiff b/tests/image_tiff/images/single-black-fp16.tiff new file mode 100644 index 0000000..dbd8ade Binary files /dev/null and b/tests/image_tiff/images/single-black-fp16.tiff differ diff --git a/tests/image_tiff/images/tiled-cmyk-i8.tif b/tests/image_tiff/images/tiled-cmyk-i8.tif new file mode 100644 index 0000000..8f48189 Binary files /dev/null and b/tests/image_tiff/images/tiled-cmyk-i8.tif differ diff --git a/tests/image_tiff/images/tiled-gray-i1.tif b/tests/image_tiff/images/tiled-gray-i1.tif new file mode 100644 index 0000000..d6305d4 Binary files /dev/null and b/tests/image_tiff/images/tiled-gray-i1.tif differ diff --git a/tests/image_tiff/images/tiled-jpeg-rgb-u8.tif b/tests/image_tiff/images/tiled-jpeg-rgb-u8.tif new file mode 100644 index 0000000..f9782b2 Binary files /dev/null and b/tests/image_tiff/images/tiled-jpeg-rgb-u8.tif differ diff --git a/tests/image_tiff/images/tiled-jpeg-ycbcr.tif b/tests/image_tiff/images/tiled-jpeg-ycbcr.tif new file mode 100644 index 0000000..a8303e6 Binary files /dev/null and b/tests/image_tiff/images/tiled-jpeg-ycbcr.tif differ diff --git a/tests/image_tiff/images/tiled-oversize-gray-i8.tif b/tests/image_tiff/images/tiled-oversize-gray-i8.tif new file mode 100644 index 0000000..bc17255 Binary files /dev/null and b/tests/image_tiff/images/tiled-oversize-gray-i8.tif differ diff --git a/tests/image_tiff/images/tiled-rect-rgb-u8.tif b/tests/image_tiff/images/tiled-rect-rgb-u8.tif new file mode 100644 index 0000000..bb65829 Binary files /dev/null and b/tests/image_tiff/images/tiled-rect-rgb-u8.tif differ diff --git a/tests/image_tiff/images/tiled-rgb-u8.tif b/tests/image_tiff/images/tiled-rgb-u8.tif new file mode 100644 index 0000000..1f0846d Binary files /dev/null and b/tests/image_tiff/images/tiled-rgb-u8.tif differ diff --git a/tests/image_tiff/images/white-fp16-pred2.tiff b/tests/image_tiff/images/white-fp16-pred2.tiff new file mode 100644 index 0000000..bb05569 Binary files /dev/null and b/tests/image_tiff/images/white-fp16-pred2.tiff differ diff --git a/tests/image_tiff/images/white-fp16-pred3.tiff b/tests/image_tiff/images/white-fp16-pred3.tiff new file mode 100644 index 0000000..0126bb2 Binary files /dev/null and b/tests/image_tiff/images/white-fp16-pred3.tiff differ diff --git a/tests/image_tiff/images/white-fp16.tiff b/tests/image_tiff/images/white-fp16.tiff new file mode 100644 index 0000000..95d4ff9 Binary files /dev/null and b/tests/image_tiff/images/white-fp16.tiff differ diff --git a/tests/image_tiff/mod.rs b/tests/image_tiff/mod.rs new file mode 100644 index 0000000..5f252c8 --- /dev/null +++ b/tests/image_tiff/mod.rs @@ -0,0 +1,2 @@ +mod decode_geotiff_images; +mod decode_images; diff --git a/tests/image_tiff/predict.rs b/tests/image_tiff/predict.rs new file mode 100644 index 0000000..31a4465 --- /dev/null +++ b/tests/image_tiff/predict.rs @@ -0,0 +1,231 @@ +extern crate tiff; + +use tiff::decoder::{Decoder, DecodingResult}; +use tiff::encoder::{colortype, Compression, Predictor, TiffEncoder}; +use tiff::ColorType; + +use std::fs::File; +use std::io::{Cursor, Seek, SeekFrom}; +use std::path::PathBuf; + +const TEST_IMAGE_DIR: &str = "./tests/image_tiff/images/"; + +macro_rules! test_predict { + ($name:ident, $buffer:ident, $buffer_ty:ty) => { + fn $name>( + file: &str, + expected_type: ColorType, + ) { + let path = PathBuf::from(TEST_IMAGE_DIR).join(file); + let file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(file).expect("Cannot create decoder!"); + + assert_eq!(decoder.colortype().unwrap(), expected_type); + let image_data = match decoder.read_image().unwrap() { + DecodingResult::$buffer(res) => res, + _ => panic!("Wrong data type"), + }; + + let mut predicted = Vec::with_capacity(image_data.len()); + C::horizontal_predict(&image_data, &mut predicted); + + let sample_size = C::SAMPLE_FORMAT.len(); + + (0..sample_size).for_each(|i| { + assert_eq!(predicted[i], image_data[i]); + }); + + (sample_size..image_data.len()).for_each(|i| { + predicted[i] = predicted[i].wrapping_add(predicted[i - sample_size]); + assert_eq!(predicted[i], image_data[i]); + }); + } + }; +} + +test_predict!(test_u8_predict, U8, u8); +test_predict!(test_i8_predict, I8, i8); +test_predict!(test_u16_predict, U16, u16); +test_predict!(test_i16_predict, I16, i16); +test_predict!(test_u32_predict, U32, u32); +test_predict!(test_u64_predict, U64, u64); + +#[test] +fn test_gray_u8_predict() { + test_u8_predict::("minisblack-1c-8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_gray_i8_predict() { + test_i8_predict::("minisblack-1c-i8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_rgb_u8_predict() { + test_u8_predict::("rgb-3c-8b.tiff", ColorType::RGB(8)); +} + +#[test] +fn test_cmyk_u8_predict() { + test_u8_predict::("cmyk-3c-8b.tiff", ColorType::CMYK(8)); +} + +#[test] +fn test_gray_u16_predict() { + test_u16_predict::("minisblack-1c-16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_gray_i16_predict() { + test_i16_predict::("minisblack-1c-i16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_rgb_u16_predict() { + test_u16_predict::("rgb-3c-16b.tiff", ColorType::RGB(16)); +} + +#[test] +fn test_cmyk_u16_predict() { + test_u16_predict::("cmyk-3c-16b.tiff", ColorType::CMYK(16)); +} + +#[test] +fn test_gray_u32_predict() { + test_u32_predict::("gradient-1c-32b.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_u32_predict() { + test_u32_predict::("gradient-3c-32b.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_gray_u64_predict() { + test_u64_predict::("gradient-1c-64b.tiff", ColorType::Gray(64)); +} + +#[test] +fn test_rgb_u64_predict() { + test_u64_predict::("gradient-3c-64b.tiff", ColorType::RGB(64)); +} + +#[test] +fn test_ycbcr_u8_predict() { + test_u8_predict::("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8)); +} + +macro_rules! test_predict_roundtrip { + ($name:ident, $buffer:ident, $buffer_ty:ty) => { + fn $name>( + file: &str, + expected_type: ColorType, + ) { + let path = PathBuf::from(TEST_IMAGE_DIR).join(file); + let img_file = File::open(path).expect("Cannot find test image!"); + let mut decoder = Decoder::new(img_file).expect("Cannot create decoder"); + assert_eq!(decoder.colortype().unwrap(), expected_type); + + let image_data = match decoder.read_image().unwrap() { + DecodingResult::$buffer(res) => res, + _ => panic!("Wrong data type"), + }; + + let mut file = Cursor::new(Vec::new()); + { + let mut tiff = TiffEncoder::new(&mut file) + .unwrap() + .with_predictor(Predictor::Horizontal); + + let (width, height) = decoder.dimensions().unwrap(); + tiff.write_image::(width, height, &image_data).unwrap(); + } + file.seek(SeekFrom::Start(0)).unwrap(); + { + let mut decoder = Decoder::new(&mut file).unwrap(); + if let DecodingResult::$buffer(img_res) = + decoder.read_image().expect("Decoding image failed") + { + assert_eq!(image_data, img_res); + } else { + panic!("Wrong data type"); + } + } + } + }; +} + +test_predict_roundtrip!(test_u8_predict_roundtrip, U8, u8); +test_predict_roundtrip!(test_i8_predict_roundtrip, I8, i8); +test_predict_roundtrip!(test_u16_predict_roundtrip, U16, u16); +test_predict_roundtrip!(test_i16_predict_roundtrip, I16, i16); +test_predict_roundtrip!(test_u32_predict_roundtrip, U32, u32); +test_predict_roundtrip!(test_u64_predict_roundtrip, U64, u64); + +#[test] +fn test_gray_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("minisblack-1c-8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_gray_i8_predict_roundtrip() { + test_i8_predict_roundtrip::("minisblack-1c-i8b.tiff", ColorType::Gray(8)); +} + +#[test] +fn test_rgb_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("rgb-3c-8b.tiff", ColorType::RGB(8)); +} + +#[test] +fn test_cmyk_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("cmyk-3c-8b.tiff", ColorType::CMYK(8)); +} + +#[test] +fn test_gray_u16_predict_roundtrip() { + test_u16_predict_roundtrip::("minisblack-1c-16b.tiff", ColorType::Gray(16)); +} + +#[test] +fn test_gray_i16_predict_roundtrip() { + test_i16_predict_roundtrip::( + "minisblack-1c-i16b.tiff", + ColorType::Gray(16), + ); +} + +#[test] +fn test_rgb_u16_predict_roundtrip() { + test_u16_predict_roundtrip::("rgb-3c-16b.tiff", ColorType::RGB(16)); +} + +#[test] +fn test_cmyk_u16_predict_roundtrip() { + test_u16_predict_roundtrip::("cmyk-3c-16b.tiff", ColorType::CMYK(16)); +} + +#[test] +fn test_gray_u32_predict_roundtrip() { + test_u32_predict_roundtrip::("gradient-1c-32b.tiff", ColorType::Gray(32)); +} + +#[test] +fn test_rgb_u32_predict_roundtrip() { + test_u32_predict_roundtrip::("gradient-3c-32b.tiff", ColorType::RGB(32)); +} + +#[test] +fn test_gray_u64_predict_roundtrip() { + test_u64_predict_roundtrip::("gradient-1c-64b.tiff", ColorType::Gray(64)); +} + +#[test] +fn test_rgb_u64_predict_roundtrip() { + test_u64_predict_roundtrip::("gradient-3c-64b.tiff", ColorType::RGB(64)); +} + +#[test] +fn test_ycbcr_u8_predict_roundtrip() { + test_u8_predict_roundtrip::("tiled-jpeg-ycbcr.tif", ColorType::YCbCr(8)); +} diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..948c439 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1 @@ +mod image_tiff;