diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f38e8cc..a0bd812 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -29,7 +29,7 @@ jobs: - name: "cargo check" run: cargo check --all --all-features - - name: "cargo test" - run: | - cargo test --all - cargo test --all --all-features + - run: cargo install cargo-all-features + + - name: "cargo test all features" + run: cargo test-all-features diff --git a/Cargo.toml b/Cargo.toml index 1c4ed93..0ef18a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,14 +15,27 @@ flate2 = "1.0.20" futures = "0.3.31" jpeg = { package = "jpeg-decoder", version = "0.3.0", default-features = false } num_enum = "0.7.3" -object_store = "0.12" +object_store = { version = "0.12", optional = true } # In the future we could make this feature-flagged, but for now we depend on # object_store which uses reqwest. -reqwest = { version = "0.12", default-features = false } +reqwest = { version = "0.12", default-features = false, optional = true } thiserror = "1" -tokio = { version = "1.43.0", optional = true } +tokio = { version = "1.43.0", optional = true, features = ["fs", "io-util"] } weezl = "0.1.0" [dev-dependencies] tiff = "0.9.1" -tokio = { version = "1.9", features = ["macros", "fs", "rt-multi-thread"] } +tokio = { version = "1.9", features = ["macros", "fs", "rt-multi-thread", "io-util"] } + +[features] +default = ["object_store", "reqwest"] +tokio = ["dep:tokio"] +reqwest = ["dep:reqwest"] +object_store = ["dep:object_store"] + +[package.metadata.cargo-all-features] +# If your crate has a large number of optional dependencies, skip them for speed +# skip_optional_dependencies = true + +# Exclude certain features from the build matrix +denylist = ["default"] diff --git a/src/cog.rs b/src/cog.rs index e9e3c5b..69a5d98 100644 --- a/src/cog.rs +++ b/src/cog.rs @@ -56,6 +56,7 @@ impl TIFF { } } +#[cfg(feature = "object_store")] #[cfg(test)] mod test { use std::io::BufReader; diff --git a/src/error.rs b/src/error.rs index 9f5b7f0..ae0b7eb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,7 @@ pub enum AsyncTiffError { JPEGDecodingError(#[from] jpeg::Error), /// Error while fetching data using object store. + #[cfg(feature = "object_store")] #[error(transparent)] ObjectStore(#[from] object_store::Error), @@ -32,6 +33,7 @@ pub enum AsyncTiffError { InternalTIFFError(#[from] crate::tiff::TiffError), /// Reqwest error + #[cfg(feature = "reqwest")] #[error(transparent)] ReqwestError(#[from] reqwest::Error), diff --git a/src/ifd.rs b/src/ifd.rs index d522826..3d7e927 100644 --- a/src/ifd.rs +++ b/src/ifd.rs @@ -1,13 +1,14 @@ use std::collections::HashMap; use std::io::Read; use std::ops::Range; +use std::sync::Arc; -use bytes::Bytes; +use bytes::{buf::Buf, Bytes}; use num_enum::TryFromPrimitive; use crate::error::{AsyncTiffError, AsyncTiffResult}; use crate::geo::{GeoKeyDirectory, GeoKeyTag}; -use crate::reader::{AsyncCursor, AsyncFileReader}; +use crate::reader::{AsyncCursor, AsyncFileReader, EndianAwareReader}; use crate::tiff::tags::{ CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, ResolutionUnit, SampleFormat, Tag, Type, @@ -195,26 +196,24 @@ impl ImageFileDirectory { } else { cursor.read_u16().await?.into() }; - let mut tags = HashMap::with_capacity(tag_count as usize); - for _ in 0..tag_count { - let (tag_name, tag_value) = read_tag(cursor, bigtiff).await?; - tags.insert(tag_name, tag_value); - } - // Tag 2 bytes // Type 2 bytes // Count: // - bigtiff: 8 bytes // - else: 4 bytes // Value: - // - bigtiff: 8 bytes either a pointer the value itself - // - else: 4 bytes either a pointer the value itself + // - bigtiff: 8 bytes either a pointer or the value itself + // - else: 4 bytes either a pointer or the value itself let ifd_entry_byte_size = if bigtiff { 20 } else { 12 }; - // The size of `tag_count` that we read above - let tag_count_byte_size = if bigtiff { 8 } else { 2 }; - // Reset the cursor position before reading the next ifd offset - cursor.seek(ifd_start + (ifd_entry_byte_size * tag_count) + tag_count_byte_size); + // read all tag data into an EndianAwareReader + let mut reader = cursor.read(ifd_entry_byte_size * tag_count).await?; + + let mut tags = HashMap::with_capacity(tag_count as usize); + for _ in 0..tag_count { + let (tag_name, tag_value) = read_tag(&mut reader, cursor.reader(), bigtiff).await?; + tags.insert(tag_name, tag_value); + } let next_ifd_offset = if bigtiff { cursor.read_u64().await? @@ -838,26 +837,61 @@ impl ImageFileDirectory { } /// Read a single tag from the cursor -async fn read_tag(cursor: &mut AsyncCursor, bigtiff: bool) -> AsyncTiffResult<(Tag, Value)> { - let start_cursor_position = cursor.position(); +async fn read_tag( + cursor: &mut EndianAwareReader, + file_reader: &Arc, + bigtiff: bool, +) -> AsyncTiffResult<(Tag, Value)> { + // let start_cursor_position = cursor.position(); - let tag_name = Tag::from_u16_exhaustive(cursor.read_u16().await?); + let tag_name = Tag::from_u16_exhaustive(cursor.read_u16()?); - let tag_type_code = cursor.read_u16().await?; + let tag_type_code = cursor.read_u16()?; let tag_type = Type::from_u16(tag_type_code).expect( "Unknown tag type {tag_type_code}. TODO: we should skip entries with unknown tag types.", ); let count = if bigtiff { - cursor.read_u64().await? + cursor.read_u64()? + } else { + cursor.read_u32()?.into() + }; + + let tag_size = tag_type.size(); + + let value_byte_length = count.checked_mul(tag_size).unwrap(); + + // prefetch all tag data + let mut data = if (bigtiff && value_byte_length <= 8) || value_byte_length <= 4 { + // value fits in offset field + let mut res = vec![0u8; value_byte_length as usize]; + cursor.read_exact(&mut res)?; + if bigtiff { + cursor.advance(8 - value_byte_length)?; + } else { + cursor.advance(4 - value_byte_length)?; + } + EndianAwareReader::new(Bytes::from_owner(res).reader(), cursor.endianness()) } else { - cursor.read_u32().await?.into() + // fetch using file_reader + let offset = if bigtiff { + cursor.read_u64()? + } else { + cursor.read_u32()?.into() + }; + let reader = file_reader + .get_bytes(offset..offset + value_byte_length) + .await? + .reader(); + EndianAwareReader::new(reader, cursor.endianness()) + // cursor.seek(offset); + // cursor.read(value_byte_length).await? }; - let tag_value = read_tag_value(cursor, tag_type, count, bigtiff).await?; + let tag_value = read_tag_value(&mut data, tag_type, count)?; - // TODO: better handle management of cursor state - let ifd_entry_size = if bigtiff { 20 } else { 12 }; - cursor.seek(start_cursor_position + ifd_entry_size); + // TODO: better handle management of cursor state <- should be done now + // let ifd_entry_size = if bigtiff { 20 } else { 12 }; + // cursor.seek(start_cursor_position + ifd_entry_size); Ok((tag_name, tag_value)) } @@ -867,67 +901,29 @@ async fn read_tag(cursor: &mut AsyncCursor, bigtiff: bool) -> AsyncTiffResult<(T /// NOTE: this does not maintain cursor state // This is derived from the upstream tiff crate: // https://github.com/image-rs/image-tiff/blob/6dc7a266d30291db1e706c8133357931f9e2a053/src/decoder/ifd.rs#L369-L639 -async fn read_tag_value( - cursor: &mut AsyncCursor, +fn read_tag_value( + data: &mut EndianAwareReader, tag_type: Type, count: u64, - bigtiff: bool, ) -> AsyncTiffResult { // Case 1: there are no values so we can return immediately. if count == 0 { return Ok(Value::List(vec![])); } - let tag_size = match tag_type { - Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, - Type::SHORT | Type::SSHORT => 2, - Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, - Type::LONG8 - | Type::SLONG8 - | Type::DOUBLE - | Type::RATIONAL - | Type::SRATIONAL - | Type::IFD8 => 8, - }; - - let value_byte_length = count.checked_mul(tag_size).unwrap(); - // Case 2: there is one value. if count == 1 { - // 2a: the value is 5-8 bytes and we're in BigTiff mode. - if bigtiff && value_byte_length > 4 && value_byte_length <= 8 { - let mut data = cursor.read(value_byte_length).await?; - - return Ok(match tag_type { - Type::LONG8 => Value::UnsignedBig(data.read_u64()?), - Type::SLONG8 => Value::SignedBig(data.read_i64()?), - Type::DOUBLE => Value::Double(data.read_f64()?), - Type::RATIONAL => Value::Rational(data.read_u32()?, data.read_u32()?), - Type::SRATIONAL => Value::SRational(data.read_i32()?, data.read_i32()?), - Type::IFD8 => Value::IfdBig(data.read_u64()?), - Type::BYTE - | Type::SBYTE - | Type::ASCII - | Type::UNDEFINED - | Type::SHORT - | Type::SSHORT - | Type::LONG - | Type::SLONG - | Type::FLOAT - | Type::IFD => unreachable!(), - }); - } - - // 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 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::LONG8 => Value::UnsignedBig(data.read_u64()?), + Type::SLONG8 => Value::SignedBig(data.read_i64()?), + Type::DOUBLE => Value::Double(data.read_f64()?), + Type::RATIONAL => Value::Rational(data.read_u32()?, data.read_u32()?), + Type::SRATIONAL => Value::SRational(data.read_i32()?, data.read_i32()?), + Type::IFD8 => Value::IfdBig(data.read_u64()?), 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::IFD => Value::Ifd(data.read_u32()?), Type::SSHORT => Value::Signed(data.read_i16()? as i32), Type::LONG => Value::Unsigned(data.read_u32()?), Type::SLONG => Value::Signed(data.read_i32()?), @@ -940,266 +936,120 @@ async fn read_tag_value( // return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); } } - Type::LONG8 => { - let offset = data.read_u32()?; - cursor.seek(offset as _); - Value::UnsignedBig(cursor.read_u64().await?) - } - Type::SLONG8 => { - let offset = data.read_u32()?; - cursor.seek(offset as _); - Value::SignedBig(cursor.read_i64().await?) - } - Type::DOUBLE => { - let offset = data.read_u32()?; - cursor.seek(offset as _); - Value::Double(cursor.read_f64().await?) - } - Type::RATIONAL => { - let offset = data.read_u32()?; - cursor.seek(offset as _); - let numerator = cursor.read_u32().await?; - let denominator = cursor.read_u32().await?; - Value::Rational(numerator, denominator) - } - Type::SRATIONAL => { - let offset = data.read_u32()?; - cursor.seek(offset as _); - let numerator = cursor.read_i32().await?; - let denominator = cursor.read_i32().await?; - Value::SRational(numerator, denominator) - } - Type::IFD => Value::Ifd(data.read_u32()?), - Type::IFD8 => { - let offset = data.read_u32()?; - cursor.seek(offset as _); - Value::IfdBig(cursor.read_u64().await?) - } }); } - // Case 3: There is more than one value, but it fits in the offset field. - if value_byte_length <= 4 || bigtiff && value_byte_length <= 8 { - let mut data = cursor.read(value_byte_length).await?; - if bigtiff { - cursor.advance(8 - value_byte_length); - } else { - cursor.advance(4 - value_byte_length); - } - - match tag_type { - Type::BYTE | Type::UNDEFINED => { - return { - Ok(Value::List( - (0..count) - .map(|_| Value::Byte(data.read_u8().unwrap())) - .collect(), - )) - }; - } - Type::SBYTE => { - return { - Ok(Value::List( - (0..count) - .map(|_| Value::Signed(data.read_i8().unwrap() as i32)) - .collect(), - )) - } - } - Type::ASCII => { - let mut buf = vec![0; count as usize]; - data.read_exact(&mut buf)?; - if buf.is_ascii() && buf.ends_with(&[0]) { - let v = std::str::from_utf8(&buf) - .map_err(|err| AsyncTiffError::General(err.to_string()))?; - let v = v.trim_matches(char::from(0)); - return Ok(Value::Ascii(v.into())); - } else { - panic!("Invalid tag"); - // return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); - } - } - Type::SHORT => { - let mut v = Vec::new(); - for _ in 0..count { - v.push(Value::Short(data.read_u16()?)); - } - return Ok(Value::List(v)); - } - Type::SSHORT => { - let mut v = Vec::new(); - for _ in 0..count { - v.push(Value::Signed(i32::from(data.read_i16()?))); - } - return Ok(Value::List(v)); - } - Type::LONG => { - let mut v = Vec::new(); - for _ in 0..count { - v.push(Value::Unsigned(data.read_u32()?)); - } - return Ok(Value::List(v)); - } - Type::SLONG => { - let mut v = Vec::new(); - for _ in 0..count { - v.push(Value::Signed(data.read_i32()?)); - } - return Ok(Value::List(v)); - } - Type::FLOAT => { - let mut v = Vec::new(); - for _ in 0..count { - v.push(Value::Float(data.read_f32()?)); - } - return Ok(Value::List(v)); - } - Type::IFD => { - let mut v = Vec::new(); - for _ in 0..count { - v.push(Value::Ifd(data.read_u32()?)); - } - return Ok(Value::List(v)); - } - Type::LONG8 - | Type::SLONG8 - | Type::RATIONAL - | Type::SRATIONAL - | Type::DOUBLE - | Type::IFD8 => { - unreachable!() - } - } - } - - // Seek cursor - let offset = if bigtiff { - cursor.read_u64().await? - } else { - cursor.read_u32().await?.into() - }; - cursor.seek(offset); - - // Case 4: there is more than one value, and it doesn't fit in the offset field. match tag_type { - // TODO check if this could give wrong results - // at a different endianess of file/computer. Type::BYTE | Type::UNDEFINED => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Byte(cursor.read_u8().await?)) + v.push(Value::Byte(data.read_u8()?)); } Ok(Value::List(v)) } Type::SBYTE => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Signed(cursor.read_i8().await? as i32)) + v.push(Value::SignedByte(data.read_i8()?)); } Ok(Value::List(v)) } + Type::ASCII => { + let mut buf = vec![0; count as usize]; + data.read_exact(&mut buf)?; + if buf.is_ascii() && buf.ends_with(&[0]) { + let v = std::str::from_utf8(&buf) + .map_err(|err| AsyncTiffError::General(err.to_string()))?; + let v = v.trim_matches(char::from(0)); + Ok(Value::Ascii(v.into())) + } else { + panic!("Invalid tag"); + // return Err(TiffError::FormatError(TiffFormatError::InvalidTag)); + } + } Type::SHORT => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Short(cursor.read_u16().await?)) + v.push(Value::Short(data.read_u16()?)); } Ok(Value::List(v)) } Type::SSHORT => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Signed(cursor.read_i16().await? as i32)) + v.push(Value::Signed(i32::from(data.read_i16()?))); } Ok(Value::List(v)) } Type::LONG => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Unsigned(cursor.read_u32().await?)) + v.push(Value::Unsigned(data.read_u32()?)); } Ok(Value::List(v)) } Type::SLONG => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Signed(cursor.read_i32().await?)) + v.push(Value::Signed(data.read_i32()?)); } Ok(Value::List(v)) } Type::FLOAT => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Float(cursor.read_f32().await?)) + v.push(Value::Float(data.read_f32()?)); } Ok(Value::List(v)) } Type::DOUBLE => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Double(cursor.read_f64().await?)) + v.push(Value::Double(data.read_f64()?)) } Ok(Value::List(v)) } Type::RATIONAL => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Rational( - cursor.read_u32().await?, - cursor.read_u32().await?, - )) + v.push(Value::Rational(data.read_u32()?, data.read_u32()?)) } Ok(Value::List(v)) } Type::SRATIONAL => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::SRational( - cursor.read_i32().await?, - cursor.read_i32().await?, - )) + v.push(Value::SRational(data.read_i32()?, data.read_i32()?)) } Ok(Value::List(v)) } Type::LONG8 => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::UnsignedBig(cursor.read_u64().await?)) + v.push(Value::UnsignedBig(data.read_u64()?)) } Ok(Value::List(v)) } Type::SLONG8 => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::SignedBig(cursor.read_i64().await?)) + v.push(Value::SignedBig(data.read_i64()?)) } Ok(Value::List(v)) } Type::IFD => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::Ifd(cursor.read_u32().await?)) + v.push(Value::Ifd(data.read_u32()?)) } Ok(Value::List(v)) } Type::IFD8 => { let mut v = Vec::with_capacity(count as _); for _ in 0..count { - v.push(Value::IfdBig(cursor.read_u64().await?)) + v.push(Value::IfdBig(data.read_u64()?)) } Ok(Value::List(v)) } - Type::ASCII => { - let mut out = vec![0; count as _]; - let mut buf = cursor.read(count).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) { - out.truncate(first); - } - Ok(Value::Ascii( - String::from_utf8(out).map_err(|err| AsyncTiffError::General(err.to_string()))?, - )) - } } } diff --git a/src/reader.rs b/src/reader.rs index 8563077..4cdc01d 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -8,7 +8,10 @@ use std::sync::Arc; use byteorder::{BigEndian, LittleEndian, ReadBytesExt}; use bytes::buf::Reader; use bytes::{Buf, Bytes}; -use futures::future::{BoxFuture, FutureExt, TryFutureExt}; +use futures::future::{BoxFuture, FutureExt}; +#[cfg(feature = "object_store")] +use futures::TryFutureExt; +#[cfg(feature = "object_store")] use object_store::ObjectStore; use crate::error::{AsyncTiffError, AsyncTiffResult}; @@ -67,36 +70,36 @@ impl AsyncFileReader for Box { } } -// #[cfg(feature = "tokio")] -// impl AsyncFileReader -// for T -// { -// fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { -// use tokio::io::{AsyncReadExt, AsyncSeekExt}; - -// async move { -// self.seek(std::io::SeekFrom::Start(range.start)).await?; - -// let to_read = (range.end - range.start).try_into().unwrap(); -// let mut buffer = Vec::with_capacity(to_read); -// let read = self.take(to_read as u64).read_to_end(&mut buffer).await?; -// if read != to_read { -// return Err(AsyncTiffError::EndOfFile(to_read, read)); -// } - -// Ok(buffer.into()) -// } -// .boxed() -// } -// } +#[cfg(feature = "tokio")] +impl AsyncFileReader for tokio::fs::File { + fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { + use tokio::io::{AsyncReadExt, AsyncSeekExt}; + + async move { + let mut file = (*self).try_clone().await?; + file.seek(std::io::SeekFrom::Start(range.start)).await?; + + let to_read = (range.end - range.start).try_into().unwrap(); + let mut buffer = Vec::with_capacity(to_read); + let read = file.take(to_read as u64).read_to_end(&mut buffer).await?; + if read != to_read { + return Err(AsyncTiffError::EndOfFile(to_read, read)); + } + + Ok(buffer.into()) + } + .boxed() + } +} /// An AsyncFileReader that reads from an [`ObjectStore`] instance. +#[cfg(feature = "object_store")] #[derive(Clone, Debug)] pub struct ObjectReader { store: Arc, path: object_store::path::Path, } - +#[cfg(feature = "object_store")] impl ObjectReader { /// Creates a new [`ObjectReader`] for the provided [`ObjectStore`] and path /// @@ -105,7 +108,7 @@ impl ObjectReader { Self { store, path } } } - +#[cfg(feature = "object_store")] impl AsyncFileReader for ObjectReader { fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { let range = range.start as _..range.end as _; @@ -134,19 +137,20 @@ impl AsyncFileReader for ObjectReader { } /// An AsyncFileReader that reads from a URL using reqwest. +#[cfg(feature = "reqwest")] #[derive(Debug, Clone)] pub struct ReqwestReader { client: reqwest::Client, url: reqwest::Url, } - +#[cfg(feature = "reqwest")] impl ReqwestReader { /// Construct a new ReqwestReader from a reqwest client and URL. pub fn new(client: reqwest::Client, url: reqwest::Url) -> Self { Self { client, url } } } - +#[cfg(feature = "reqwest")] impl AsyncFileReader for ReqwestReader { fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { let url = self.url.clone(); @@ -268,31 +272,37 @@ impl AsyncCursor { } /// Read a u8 from the cursor, advancing the internal state by 1 byte. + #[allow(dead_code)] pub(crate) async fn read_u8(&mut self) -> AsyncTiffResult { self.read(1).await?.read_u8() } /// Read a i8 from the cursor, advancing the internal state by 1 byte. + #[allow(dead_code)] pub(crate) async fn read_i8(&mut self) -> AsyncTiffResult { self.read(1).await?.read_i8() } /// Read a u16 from the cursor, advancing the internal state by 2 bytes. + #[allow(dead_code)] pub(crate) async fn read_u16(&mut self) -> AsyncTiffResult { self.read(2).await?.read_u16() } /// Read a i16 from the cursor, advancing the internal state by 2 bytes. + #[allow(dead_code)] pub(crate) async fn read_i16(&mut self) -> AsyncTiffResult { self.read(2).await?.read_i16() } /// Read a u32 from the cursor, advancing the internal state by 4 bytes. + #[allow(dead_code)] pub(crate) async fn read_u32(&mut self) -> AsyncTiffResult { self.read(4).await?.read_u32() } /// Read a i32 from the cursor, advancing the internal state by 4 bytes. + #[allow(dead_code)] pub(crate) async fn read_i32(&mut self) -> AsyncTiffResult { self.read(4).await?.read_i32() } @@ -303,19 +313,21 @@ impl AsyncCursor { } /// Read a i64 from the cursor, advancing the internal state by 8 bytes. + #[allow(dead_code)] pub(crate) async fn read_i64(&mut self) -> AsyncTiffResult { self.read(8).await?.read_i64() } + #[allow(dead_code)] pub(crate) async fn read_f32(&mut self) -> AsyncTiffResult { self.read(4).await?.read_f32() } + #[allow(dead_code)] pub(crate) async fn read_f64(&mut self) -> AsyncTiffResult { self.read(8).await?.read_f64() } - #[allow(dead_code)] pub(crate) fn reader(&self) -> &Arc { &self.reader } @@ -326,6 +338,7 @@ impl AsyncCursor { } /// Advance cursor position by a set amount + #[allow(dead_code)] pub(crate) fn advance(&mut self, amount: u64) { self.offset += amount; } @@ -334,6 +347,7 @@ impl AsyncCursor { self.offset = offset; } + #[allow(dead_code)] pub(crate) fn position(&self) -> u64 { self.offset } @@ -345,6 +359,23 @@ pub(crate) struct EndianAwareReader { } impl EndianAwareReader { + pub(crate) fn new(reader: Reader, endianness: Endianness) -> Self { + Self { reader, endianness } + } + + pub(crate) fn endianness(&self) -> Endianness { + self.endianness + } + + pub(crate) fn advance(&mut self, amt: u64) -> AsyncTiffResult { + // TODO: can we use consume? + // from https://stackoverflow.com/a/42247224 + Ok(std::io::copy( + &mut self.reader.by_ref().take(amt), + &mut std::io::sink(), + )?) + } + /// Read a u8 from the cursor, advancing the internal state by 1 byte. pub(crate) fn read_u8(&mut self) -> AsyncTiffResult { Ok(self.reader.read_u8()?) diff --git a/src/tiff/tags.rs b/src/tiff/tags.rs index 524a726..567f314 100644 --- a/src/tiff/tags.rs +++ b/src/tiff/tags.rs @@ -175,6 +175,22 @@ pub enum Type(u16) { } } +impl Type { + pub const fn size(&self) -> u64 { + match self { + Type::BYTE | Type::SBYTE | Type::ASCII | Type::UNDEFINED => 1, + Type::SHORT | Type::SSHORT => 2, + Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD => 4, + Type::LONG8 + | Type::SLONG8 + | Type::DOUBLE + | Type::RATIONAL + | Type::SRATIONAL + | Type::IFD8 => 8, + } + } +} + tags! { /// See [TIFF compression tags](https://www.awaresystems.be/imaging/tiff/tifftags/compression.html) /// for reference. diff --git a/tests/image_tiff/util.rs b/tests/image_tiff/util.rs index 834ccc7..b4dd1c7 100644 --- a/tests/image_tiff/util.rs +++ b/tests/image_tiff/util.rs @@ -1,15 +1,78 @@ -use std::env::current_dir; +use async_tiff::TIFF; use std::sync::Arc; +#[cfg(feature = "object_store")] use async_tiff::reader::ObjectReader; -use async_tiff::TIFF; +#[cfg(feature = "object_store")] use object_store::local::LocalFileSystem; +#[cfg(feature = "object_store")] +use std::env::current_dir; + +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +use async_tiff::{ + error::{AsyncTiffError, AsyncTiffResult}, + reader::AsyncFileReader, +}; +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +use bytes::Bytes; +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +use futures::{future::BoxFuture, FutureExt}; +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +use std::ops::Range; const TEST_IMAGE_DIR: &str = "tests/image_tiff/images/"; +#[cfg(feature = "object_store")] pub(crate) async fn open_tiff(filename: &str) -> TIFF { let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap()); let path = format!("{TEST_IMAGE_DIR}/{filename}"); let reader = Arc::new(ObjectReader::new(store.clone(), path.as_str().into())); TIFF::try_open(reader).await.unwrap() } + +#[cfg(all(feature = "tokio", not(feature = "object_store")))] +pub(crate) async fn open_tiff(filename: &str) -> TIFF { + // let store = Arc::new(LocalFileSystem::new_with_prefix(current_dir().unwrap()).unwrap()); + let path = format!("{TEST_IMAGE_DIR}/{filename}"); + let reader = Arc::new( + tokio::fs::File::open(path) + .await + .expect("could not open file"), + ); + TIFF::try_open(reader).await.unwrap() +} + +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +#[derive(Debug)] +struct TokioFile(tokio::fs::File); +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +impl AsyncFileReader for TokioFile { + fn get_bytes(&self, range: Range) -> BoxFuture<'_, AsyncTiffResult> { + use tokio::io::{AsyncReadExt, AsyncSeekExt}; + + async move { + let mut file = self.0.try_clone().await?; + file.seek(std::io::SeekFrom::Start(range.start)).await?; + + let to_read = (range.end - range.start).try_into().unwrap(); + let mut buffer = Vec::with_capacity(to_read); + let read = file.take(to_read as u64).read_to_end(&mut buffer).await?; + if read != to_read { + return Err(AsyncTiffError::EndOfFile(to_read, read)); + } + + Ok(buffer.into()) + } + .boxed() + } +} +#[cfg(not(any(feature = "tokio", feature = "object_store")))] +pub(crate) async fn open_tiff(filename: &str) -> TIFF { + let path = format!("{TEST_IMAGE_DIR}/{filename}"); + let reader = Arc::new(TokioFile( + tokio::fs::File::open(path) + .await + .expect("could not open file"), + )); + TIFF::try_open(reader).await.unwrap() +}