diff --git a/Cargo.lock b/Cargo.lock index 14cff1c..ad4e4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,16 +3,16 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] -name = "byteorder" -version = "1.3.1" +name = "autocfg" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "cfg-if" @@ -22,27 +22,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] -[[package]] -name = "enum_primitive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" -dependencies = [ - "num-traits 0.1.43", -] - [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "miniz_oxide", @@ -52,9 +43,7 @@ dependencies = [ name = "geotiff" version = "0.0.1" dependencies = [ - "byteorder", - "enum_primitive", - "num", + "num-traits", "tiff", ] @@ -66,91 +55,22 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - -[[package]] -name = "num" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits 0.2.6", -] - -[[package]] -name = "num-bigint" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" -dependencies = [ - "num-integer", - "num-traits 0.2.6", -] - -[[package]] -name = "num-complex" -version = "0.2.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "num-traits 0.2.6", -] - -[[package]] -name = "num-integer" -version = "0.1.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -dependencies = [ - "num-traits 0.2.6", -] - -[[package]] -name = "num-iter" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" -dependencies = [ - "num-integer", - "num-traits 0.2.6", -] - -[[package]] -name = "num-rational" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits 0.2.6", + "adler2", ] [[package]] name = "num-traits" -version = "0.1.43" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "num-traits 0.2.6", + "autocfg", ] -[[package]] -name = "num-traits" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" - [[package]] name = "tiff" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index ed2b642..4ec1237 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,5 @@ authors = ["Dominik Bucher "] repository = "https://github.com/georust/geotiff" [dependencies] -byteorder = "*" -enum_primitive = "*" -num = "*" +num-traits = "0.2" tiff = "0.9" diff --git a/src/geotiff.rs b/src/geotiff.rs deleted file mode 100644 index f75424f..0000000 --- a/src/geotiff.rs +++ /dev/null @@ -1,56 +0,0 @@ -use tiff::tags::{Tag, Type}; - -use crate::lowlevel::*; - -/// The basic TIFF struct. This includes the header (specifying byte order and IFD offsets) as -/// well as all the image file directories (IFDs) plus image data. -/// -/// The image data has a size of width * length * bytes_per_sample. -#[derive(Debug)] -pub struct TIFF { - pub ifds: Vec, - // This is width * length * bytes_per_sample. - pub image_data: Vec>>, -} - -/// The header of a TIFF file. This comes first in any TIFF file and contains the byte order -/// as well as the offset to the IFD table. -#[derive(Debug)] -pub struct TIFFHeader { - pub byte_order: TIFFByteOrder, - pub ifd_offset: LONG, -} - -/// An image file directory (IFD) within this TIFF. It contains the number of individual IFD entries -/// as well as a Vec with all the entries. -#[derive(Debug)] -pub struct IFD { - pub count: u16, - pub entries: Vec, -} - -/// A single entry within an image file directory (IDF). It consists of a tag, a type, and several -/// tag values. -#[derive(Debug)] -pub struct IFDEntry { - pub tag: Tag, - pub tpe: Type, - pub count: LONG, - pub value_offset: LONG, - pub value: Vec, -} - -/// Implementations for the IFD struct. -impl IFD { - pub fn get_image_length() -> usize { - 3 - } - - pub fn get_image_width() -> usize { - 3 - } - - pub fn get_bytes_per_sample() -> usize { - 3 - } -} diff --git a/src/lib.rs b/src/lib.rs index f9a9061..ff8b374 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,40 +1,99 @@ -extern crate byteorder; -#[macro_use] -extern crate enum_primitive; -extern crate num; +use std::any::type_name; +use std::io::{Read, Seek}; -use std::fmt; -use std::io::Result; +use num_traits::FromPrimitive; +use tiff::decoder::{Decoder, DecodingResult}; +use tiff::tags::Tag; +use tiff::TiffResult; -pub mod geotiff; -mod lowlevel; -mod reader; +use crate::raster_data::*; -pub use geotiff::TIFF; -use reader::*; +mod raster_data; -/// The GeoTIFF library reads `.tiff` files. +macro_rules! unwrap_primitive_type { + ($result: expr, $actual: ty, $expected: ty) => { + $result + .ok_or_else(|| { + format!( + "Cannot represent {} as {}", + type_name::<$actual>(), + type_name::<$expected>() + ) + }) + .unwrap() + }; +} + +/// The basic GeoTIFF struct. This includes any metadata as well as the actual raster data. /// -/// It is primarily used within a routing application that needs to parse digital elevation models. -/// As such, other use cases are NOT tested (for now). -impl TIFF { - /// Opens a `.tiff` file at the location indicated by `filename`. - pub fn open(filename: &str) -> Result> { - let tiff_reader = TIFFReader; - tiff_reader.load(filename) - } +/// The raster data has a size of raster_width * raster_height * num_samples +#[derive(Debug)] +pub struct GeoTiff { + pub raster_width: usize, + pub raster_height: usize, + pub num_samples: usize, + raster_data: RasterData, +} + +impl GeoTiff { + pub fn read(reader: R) -> TiffResult { + let mut decoder = Decoder::new(reader)?; - /// Gets the value at a given coordinate (in pixels). - pub fn get_value_at(&self, lon: usize, lat: usize) -> usize { - self.image_data[lon][lat][0] + let (raster_width, raster_height) = decoder + .dimensions() + .map(|(width, height)| (width as usize, height as usize))?; + let num_samples = match decoder.find_tag(Tag::SamplesPerPixel)? { + None => 1, + Some(value) => value.into_u16()? as usize, + }; + let raster_data = match decoder.read_image()? { + DecodingResult::U8(data) => RasterData::U8(data), + DecodingResult::U16(data) => RasterData::U16(data), + DecodingResult::U32(data) => RasterData::U32(data), + DecodingResult::U64(data) => RasterData::U64(data), + DecodingResult::F32(data) => RasterData::F32(data), + DecodingResult::F64(data) => RasterData::F64(data), + DecodingResult::I8(data) => RasterData::I8(data), + DecodingResult::I16(data) => RasterData::I16(data), + DecodingResult::I32(data) => RasterData::I32(data), + DecodingResult::I64(data) => RasterData::I64(data), + }; + + Ok(Self { + raster_width, + raster_height, + num_samples, + raster_data, + }) } -} -/// Overwrite default display function. -impl fmt::Display for TIFF { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "TIFF(Image size: [{}, {}, {}], Tag data: {:?})", - self.image_data.len(), self.image_data[0].len(), - self.image_data[0][0].len(), self.ifds) + pub fn get_value_at(&self, x: usize, y: usize, sample: usize) -> T { + let GeoTiff { + raster_width, + num_samples, + raster_data, + .. + } = self; + + if &sample >= num_samples { + panic!( + "sample out of bounds: the number of samples is {} but the sample is {}", + num_samples, sample + ) + } + + let index = (y * raster_width + x) * num_samples + sample; + match raster_data { + RasterData::U8(data) => unwrap_primitive_type!(T::from_u8(data[index]), u8, T), + RasterData::U16(data) => unwrap_primitive_type!(T::from_u16(data[index]), u16, T), + RasterData::U32(data) => unwrap_primitive_type!(T::from_u32(data[index]), u32, T), + RasterData::U64(data) => unwrap_primitive_type!(T::from_u64(data[index]), u64, T), + RasterData::F32(data) => unwrap_primitive_type!(T::from_f32(data[index]), f32, T), + RasterData::F64(data) => unwrap_primitive_type!(T::from_f64(data[index]), f64, T), + RasterData::I8(data) => unwrap_primitive_type!(T::from_i8(data[index]), i8, T), + RasterData::I16(data) => unwrap_primitive_type!(T::from_i16(data[index]), i16, T), + RasterData::I32(data) => unwrap_primitive_type!(T::from_i32(data[index]), i32, T), + RasterData::I64(data) => unwrap_primitive_type!(T::from_i64(data[index]), i64, T), + } } } diff --git a/src/lowlevel.rs b/src/lowlevel.rs deleted file mode 100644 index 2b9b774..0000000 --- a/src/lowlevel.rs +++ /dev/null @@ -1,226 +0,0 @@ -#![allow(deprecated)] -use tiff::tags::Type; - -// Base types of the TIFF format. -pub type BYTE = u8; -pub type SHORT = u16; -pub type LONG = u32; -pub type ASCII = String; -pub type RATIONAL = (u32, u32); -pub type SBYTE = i8; -pub type SSHORT = i16; -pub type SLONG = i32; -pub type SRATIONAL = (i32, i32); -pub type FLOAT = f32; -pub type DOUBLE = f64; - -// Different values individual components can take. -enum_from_primitive! { - #[repr(u16)] - #[derive(Debug)] - pub enum TIFFByteOrder { - LittleEndian = 0x4949, - BigEndian = 0x4d4d, - } -} - -/// Helper function that returns the size of a certain tag. -pub fn tag_size(t: &Type) -> u32 { - match *t { - Type::BYTE => 1, - Type::ASCII => 1, - Type::SHORT => 2, - Type::LONG => 4, - Type::RATIONAL => 8, - Type::SBYTE => 1, - Type::UNDEFINED => 1, - Type::SSHORT => 2, - Type::SLONG => 2, - Type::SRATIONAL => 8, - Type::FLOAT => 4, - Type::DOUBLE => 8, - Type::IFD => 4, - Type::LONG8 => 8, - Type::SLONG8 => 8, - Type::IFD8 => 8, - _ => unimplemented!(), - } -} - -/// All the possible values of tags. -#[derive(Debug)] -pub enum TagValue { - ByteValue(BYTE), - AsciiValue(ASCII), - ShortValue(SHORT), - LongValue(LONG), - RationalValue(RATIONAL), - SignedByteValue(SBYTE), - SignedShortValue(SSHORT), - SignedLongValue(SLONG), - SignedRationalValue(SRATIONAL), - FloatValue(FLOAT), - DoubleValue(DOUBLE), -} - -/// The photometric interpretation of the GeoTIFF. -#[repr(u16)] -#[derive(Debug)] -pub enum PhotometricInterpretation { - WhiteIsZero = 0, - BlackIsZero = 1, -} - -/// The compression chosen for this TIFF. -#[repr(u16)] -#[derive(Debug)] -pub enum Compression { - None = 1, - Huffman = 2, - LZW = 5, - OJPEG = 6, - JPEG = 7, - PackBits = 32773, -} - -/// The resolution unit of this TIFF. -#[repr(u16)] -#[derive(Debug)] -pub enum ResolutionUnit { - None = 1, - Inch = 2, - Centimetre = 3, -} - -/// The sample format of this TIFF. -#[repr(u16)] -#[derive(Debug)] -pub enum SampleFormat { - UnsignedInteger = 1, - TwosComplementSignedInteger = 2, - IEEEFloatingPoint = 3, - Undefined = 4, -} - -/// The image type of this TIFF. -#[derive(Debug)] -pub enum ImageType { - Bilevel, - Grayscale, - PaletteColour, - RGB, - YCbCr, -} - -/// The image orientation of this TIFF. -#[repr(u16)] -#[derive(Debug)] -pub enum ImageOrientation { - TopLeft = 1, // row 0 top, col 0 lhs - TopRight = 2, // row 0 top, col 0 rhs - BottomRight = 3, // row 0 bottom, col 0 rhs - BottomLeft = 4, // row 0 bottom, col 0 lhs - LeftTop = 5, // row 0 lhs, col 0 top - RightTop = 6, // row 0 rhs, col 0 top - RightBottom = 7, // row 0 rhs, col 0 bottom - LeftBottom = 8, // row 0 lhs, col 0 bottom -} - - -// Baseline Tags -enum_from_primitive! { - #[repr(u16)] - #[deprecated(since="0.1.0", note="use tiff::tags::Tag instead")] - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub enum TIFFTag { - - // Baseline Tags - ArtistTag = 0x013b, - BitsPerSampleTag = 0x0102, - CellLengthTag = 0x0109, - CellWidthTag = 0x0108, - ColorMapTag = 0x0140, - CompressionTag = 0x0103, - CopyrightTag = 0x8298, - DateTimeTag = 0x0132, - ExtraSamplesTag = 0x0152, - FillOrderTag = 0x010a, - FreeByteCountsTag = 0x0121, - FreeOffsetsTag = 0x0120, - GrayResponseCurveTag = 0x0123, - GrayResponseUnitTag = 0x0122, - HostComputerTag = 0x013c, - ImageDescriptionTag = 0x010e, - ImageLengthTag = 0x0101, - ImageWidthTag = 0x0100, - MakeTag = 0x010f, - MaxSampleValueTag = 0x0119, - MinSampleValueTag = 0x0118, - ModelTag = 0x0110, - NewSubfileTypeTag = 0x00fe, - OrientationTag = 0x0112, - PhotometricInterpretationTag = 0x0106, - PlanarConfigurationTag = 0x011c, - PredictorTag = 0x013d, - ResolutionUnitTag = 0x0128, - RowsPerStripTag = 0x0116, - SampleFormatTag = 0x0153, - SamplesPerPixelTag = 0x0115, - SoftwareTag = 0x0131, - StripByteCountsTag = 0x0117, - StripOffsetsTag = 0x0111, - SubfileTypeTag = 0x00ff, - ThresholdingTag = 0x0107, - XResolutionTag = 0x011a, - YResolutionTag = 0x011b, - - // Section 20: Colorimetry - WhitePointTag = 0x013e, - PrimaryChromaticities = 0x013f, - TransferFunction = 0x012d, - TransferRange = 0x0156, - ReferenceBlackWhite = 0x0214, - - // Section 21: YCbCr Images - YCbCrCoefficients = 0x0211, - YCbCrSubsampling = 0x0212, - YCbCrPositioning = 0x0213, - - // TIFF/EP Tags - SubIFDsTag = 0x014a, - JPEGTablesTag = 0x015b, - CFARepeatPatternDimTag = 0x828d, - BatteryLevelTag = 0x828f, - ModelPixelScaleTag = 0x830e, - IPTCTag = 0x83BB, - ModelTiepointTag = 0x8482, - ModelTransformationTag = 0x85D8, - InterColorProfileTag = 0x8773, - GeoKeyDirectoryTag = 0x87AF, - GeoDoubleParamsTag = 0x87B0, - GeoAsciiParamsTag = 0x87B1, - InterlaceTag = 0x8829, - TimeZoneOffsetTag = 0x882a, - SelfTimerModeTag = 0x882b, - NoiseTag = 0x920d, - ImageNumberTag = 0x9211, - SecurityClassificationTag = 0x9212, - ImageHistoryTag = 0x9213, - EPStandardIdTag = 0x9216, - - // Extension TIFF Tags - // See http://www.awaresystems.be/imaging/tiff/tifftags/extension.html - XMPTag = 0x02bc, - - // Private Tags - PhotoshopTag = 0x8649, - EXIFTag = 0x8769, - - GDALMETADATA = 0xA480, - GDALNODATA = 0xA481, - } -} - -// Default Values -static PHOTOMETRIC_INTERPRETATION_SHORT_DEFAULT: SHORT = 1; -static PHOTOMETRIC_INTERPRETATION_LONG_DEFAULT: LONG = 1; diff --git a/src/raster_data.rs b/src/raster_data.rs new file mode 100644 index 0000000..8a35b82 --- /dev/null +++ b/src/raster_data.rs @@ -0,0 +1,53 @@ +use std::fmt; +use std::fmt::{Debug, Formatter}; + +pub(super) enum RasterData { + U8(Vec), + U16(Vec), + U32(Vec), + U64(Vec), + F32(Vec), + F64(Vec), + I8(Vec), + I16(Vec), + I32(Vec), + I64(Vec), +} + +impl Debug for RasterData { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_fmt(format_args!( + "RasterData {{ type: {}, len: {} }}", + match self { + RasterData::U8(_) => "u8", + RasterData::U16(_) => "u16", + RasterData::U32(_) => "u32", + RasterData::U64(_) => "u64", + RasterData::F32(_) => "f32", + RasterData::F64(_) => "f64", + RasterData::I8(_) => "i8", + RasterData::I16(_) => "i16", + RasterData::I32(_) => "i32", + RasterData::I64(_) => "i64", + }, + self.len() + )) + } +} + +impl RasterData { + fn len(&self) -> usize { + match self { + RasterData::U8(data) => data.len(), + RasterData::U16(data) => data.len(), + RasterData::U32(data) => data.len(), + RasterData::U64(data) => data.len(), + RasterData::F32(data) => data.len(), + RasterData::F64(data) => data.len(), + RasterData::I8(data) => data.len(), + RasterData::I16(data) => data.len(), + RasterData::I32(data) => data.len(), + RasterData::I64(data) => data.len(), + } + } +} diff --git a/src/reader.rs b/src/reader.rs deleted file mode 100644 index 35e08c5..0000000 --- a/src/reader.rs +++ /dev/null @@ -1,364 +0,0 @@ -use std::fs::File; -use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom}; -use std::path::Path; - -use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt}; -use num::FromPrimitive; -use tiff::tags::{Tag, Type}; - -use crate::geotiff::{IFDEntry, IFD, TIFF}; -use crate::lowlevel::{tag_size, TIFFByteOrder, TagValue}; - -/// A helper trait to indicate that something needs to be seekable and readable. -pub trait SeekableReader: Seek + Read {} - -impl SeekableReader for T {} - -/// The TIFF reader class that encapsulates all functionality related to reading `.tiff` files. -/// In particular, this includes reading the TIFF header, the image file directories (IFD), and -/// the plain data. -pub struct TIFFReader; - -impl TIFFReader { - /// Loads a `.tiff` file, as specified by `filename`. - pub fn load(&self, filename: &str) -> Result> { - let filepath = Path::new(filename); - let mut reader = File::open(&filepath)?; - - self.read(&mut reader) - } - - /// Reads the `.tiff` file, starting with the byte order. - pub fn read(&self, reader: &mut dyn SeekableReader) -> Result> { - match self.read_byte_order(reader)? { - TIFFByteOrder::LittleEndian => self.read_tiff::(reader), - TIFFByteOrder::BigEndian => self.read_tiff::(reader), - } - } - - /// Helper function to read the byte order, one of `LittleEndian` or `BigEndian`. - pub fn read_byte_order(&self, reader: &mut dyn SeekableReader) -> Result { - // Bytes 0-1: "II" or "MM" - // Read and validate ByteOrder - match TIFFByteOrder::from_u16(reader.read_u16::()?) { - Some(TIFFByteOrder::LittleEndian) => Ok(TIFFByteOrder::LittleEndian), - Some(TIFFByteOrder::BigEndian) => Ok(TIFFByteOrder::BigEndian), - None => Err(Error::new(ErrorKind::Other, format!("Invalid byte order in header."))), - } - } - - /// Reads the `.tiff` file, given a `ByteOrder`. - /// - /// This starts by reading the magic number, the IFD offset, the IFDs themselves, and finally, - /// the image data. - fn read_tiff(&self, reader: &mut dyn SeekableReader) -> Result> { - self.read_magic::(reader)?; - let ifd_offset = self.read_ifd_offset::(reader)?; - let ifd = self.read_IFD::(reader, ifd_offset)?; - let image_data = self.read_image_data::(reader, &ifd)?; - Ok(Box::new(TIFF { - ifds: vec![ifd], - image_data, - })) - } - - /// Reads the magic number, i.e., 42. - fn read_magic(&self, reader: &mut dyn SeekableReader) -> Result<()> { - // Bytes 2-3: 0042 - // Read and validate HeaderMagic - match reader.read_u16::()? { - 42 => Ok(()), - _ => Err(Error::new(ErrorKind::Other, "Invalid magic number in header")), - } - } - - /// Reads the IFD offset. The first IFD is then read from this position. - pub fn read_ifd_offset(&self, reader: &mut dyn SeekableReader) -> Result { - // Bytes 4-7: offset - // Offset from start of file to first IFD - let ifd_offset_field = reader.read_u32::()?; - println!("IFD offset: {:?}", ifd_offset_field); - Ok(ifd_offset_field) - } - - /// Reads an IFD. - /// - /// This starts by reading the number of entries, and then the tags within each entry. - #[allow(non_snake_case)] - fn read_IFD(&self, reader: &mut dyn SeekableReader, ifd_offset: u32) -> Result { - reader.seek(SeekFrom::Start(ifd_offset as u64))?; - // 2 byte count of IFD entries - let entry_count = reader.read_u16::()?; - - println!("IFD entry count: {}", entry_count); - - let mut ifd = IFD { count: entry_count, entries: Vec::with_capacity(entry_count as usize) }; - - for entry_number in 0..entry_count as usize { - let entry = self.read_tag::(ifd_offset as u64 + 2, entry_number, reader); - match entry { - Ok(e) => ifd.entries.push(e), - Err(err) => println!("Invalid tag at index {}: {}", entry_number, err), - } - } - - Ok(ifd) - } - - /// Reads `n` bytes from a reader into a Vec. - fn read_n(&self, reader: &mut dyn SeekableReader, bytes_to_read: u64) -> Vec { - let mut buf = Vec::with_capacity(bytes_to_read as usize); - let mut chunk = reader.take(bytes_to_read); - let status = chunk.read_to_end(&mut buf); - match status { - Ok(n) => assert_eq!(bytes_to_read as usize, n), - _ => panic!("Didn't read enough"), - } - buf - } - - /// Converts a Vec into a TagValue, depending on the type of the tag. In the TIFF file - /// format, each tag type indicates which value it stores (e.g., a byte, ascii, or long value). - /// This means that the tag values have to be read taking the tag type into consideration. - fn vec_to_tag_value(&self, vec: Vec, tpe: &Type) -> TagValue { - let len = vec.len(); - match tpe { - Type::BYTE => TagValue::ByteValue(vec[0]), - Type::ASCII => TagValue::AsciiValue(String::from_utf8_lossy(&vec).to_string()), - Type::SHORT => TagValue::ShortValue(Endian::read_u16(&vec[..])), - Type::LONG => TagValue::LongValue(Endian::read_u32(&vec[..])), - Type::RATIONAL => TagValue::RationalValue(( - Endian::read_u32(&vec[..(len / 2)]), - Endian::read_u32(&vec[(len / 2)..]), - )), - Type::SBYTE => TagValue::SignedByteValue(vec[0] as i8), - Type::UNDEFINED => TagValue::ByteValue(0), - Type::SSHORT => TagValue::SignedShortValue(Endian::read_i16(&vec[..])), - Type::SLONG => TagValue::SignedLongValue(Endian::read_i32(&vec[..])), - Type::SRATIONAL => TagValue::SignedRationalValue(( - Endian::read_i32(&vec[..(len / 2)]), - Endian::read_i32(&vec[(len / 2)..]), - )), - Type::FLOAT => TagValue::FloatValue(Endian::read_f32(&vec[..])), - Type::DOUBLE => TagValue::DoubleValue(Endian::read_f64(&vec[..])), - Type::IFD => unimplemented!(), - Type::LONG8 => unimplemented!(), - Type::SLONG8 => unimplemented!(), - Type::IFD8 => unimplemented!(), - _ => unimplemented!(), - } - } - - /// Converts a number of u8 values to a usize value. This doesn't check if usize is at least - /// u64, so be careful with large values. - fn vec_to_value(&self, vec: Vec) -> usize { - let len = vec.len(); - match len { - 0 => 0 as usize, - 1 => vec[0] as usize, - 2 => Endian::read_u16(&vec[..]) as usize, - 4 => Endian::read_u32(&vec[..]) as usize, - 8 => Endian::read_u64(&vec[..]) as usize, - _ => panic!("Vector has wrong number of elements!"), - } - } - - /// Reads a single tag (given an IFD offset) into an IFDEntry. - /// - /// This consists of reading the tag ID, field type, number of values, offset to values. After - /// decoding the tag and type, the values are retrieved. - fn read_tag( - &self, - ifd_offset: u64, - entry_number: usize, - reader: &mut dyn SeekableReader, - ) -> Result { - println!("Reading tag at {}/{}", ifd_offset, entry_number); - // Seek beginning (as each tag is 12 bytes long). - reader.seek(SeekFrom::Start(ifd_offset + 12 * entry_number as u64))?; - - // Bytes 0..1: u16 tag ID - let tag_value = reader.read_u16::()?; - - // Bytes 2..3: u16 field Type - let tpe_value = reader.read_u16::()?; - - // Bytes 4..7: u32 number of Values of type - let count_value = reader.read_u32::()?; - - // Bytes 8..11: u32 offset in file to Value - let value_offset_value = reader.read_u32::()?; - - // Decode the tag. - let tag_msg = format!("Invalid tag {:04X}", tag_value); - let tag = Tag::from_u16(tag_value).ok_or(Error::new(ErrorKind::InvalidData, tag_msg))?; - - // Decode the type. - let tpe_msg = format!("Invalid tag type {:04X}", tpe_value); - let tpe = Type::from_u16(tpe_value).expect(&tpe_msg); - let value_size = tag_size(&tpe); - - // Let's get the value(s) of this tag. - let tot_size = count_value * value_size; - println!("{:04X} {:04X} {:08X} {:08X} {:?} {:?} {:?} {:?}", tag_value, tpe_value, - count_value, value_offset_value, tag, tpe, value_size, tot_size); - - let mut values = Vec::with_capacity(count_value as usize); - if tot_size <= 4 { - // Can directly read the value at the value field. For simplicity, we simply reset - // the reader to the correct position. - reader.seek(SeekFrom::Start(ifd_offset + 12 * entry_number as u64 + 8))?; - for _ in 0..count_value as usize { - let value = self.read_n(reader, value_size as u64); - values.push(self.vec_to_tag_value::(value, &tpe)); - } - } else { - // Have to read from the address pointed at by the value field. - reader.seek(SeekFrom::Start(value_offset_value as u64))?; - for _ in 0..count_value as usize { - let value = self.read_n(reader, value_size as u64); - values.push(self.vec_to_tag_value::(value, &tpe)); - } - } - - // Create IFD entry. - let ifd_entry = IFDEntry { - tag, - tpe, - count: count_value, - value_offset: value_offset_value, - value: values, - }; - - println!("IFD[{:?}] tag: {:?} type: {:?} count: {} offset: {:08x} value: {:?}", - entry_number, ifd_entry.tag, ifd_entry.tpe, ifd_entry.count, - ifd_entry.value_offset, ifd_entry.value); - - Ok(ifd_entry) - } - - /// Reads the image data into a 3D-Vec. - /// - /// As for now, the following assumptions are made: - /// * No compression is used, i.e., CompressionTag == 1. - fn read_image_data( - &self, - reader: &mut dyn SeekableReader, - ifd: &IFD, - ) -> Result>>> { - // Image size and depth. - let image_length = ifd - .entries - .iter() - .find(|&e| e.tag == Tag::ImageLength) - .ok_or(Error::new( - ErrorKind::InvalidData, - "Image length not found.", - ))?; - let image_width = ifd - .entries - .iter() - .find(|&e| e.tag == Tag::ImageWidth) - .ok_or(Error::new(ErrorKind::InvalidData, "Image width not found."))?; - let image_depth = ifd - .entries - .iter() - .find(|&e| e.tag == Tag::BitsPerSample) - .ok_or(Error::new(ErrorKind::InvalidData, "Image depth not found."))?; - - // Storage location within the TIFF. First, lets get the number of rows per strip. - let rows_per_strip = ifd - .entries - .iter() - .find(|&e| e.tag == Tag::RowsPerStrip) - .ok_or(Error::new( - ErrorKind::InvalidData, - "Rows per strip not found.", - ))?; - // For each strip, its offset within the TIFF file. - let strip_offsets = ifd - .entries - .iter() - .find(|&e| e.tag == Tag::StripOffsets) - .ok_or(Error::new( - ErrorKind::InvalidData, - "Strip offsets not found.", - ))?; - let strip_byte_counts = ifd - .entries - .iter() - .find(|&e| e.tag == Tag::StripByteCounts) - .ok_or(Error::new( - ErrorKind::InvalidData, - "Strip byte counts not found.", - ))?; - - // Create the output Vec. - let image_length = match image_length.value[0] { - TagValue::ShortValue(v) => v, - _ => 0 as u16, - }; - let image_width = match image_width.value[0] { - TagValue::ShortValue(v) => v, - _ => 0 as u16, - }; - let image_depth = match image_depth.value[0] { - TagValue::ShortValue(v) => v / 8, - _ => 0 as u16, - }; - // TODO The img Vec should optimally not be of usize, but of size "image_depth". - let mut img: Vec>> = Vec::with_capacity(image_length as usize); - for i in 0..image_length { - img.push(Vec::with_capacity(image_width as usize)); - for _ in 0..image_width { - img[i as usize].push(vec![0; 1]); // TODO To be changed to take into account SamplesPerPixel! - } - } - - // Read strip after strip, and copy it into the output Vec. - let _rows_per_strip = match rows_per_strip.value[0] { - TagValue::ShortValue(v) => v, - _ => 0 as u16, - }; - let mut offsets: Vec = Vec::with_capacity(strip_offsets.value.len()); - for v in &strip_offsets.value { - match v { - TagValue::LongValue(v) => offsets.push(*v), - _ => (), - }; - } - let mut byte_counts: Vec = Vec::with_capacity(strip_byte_counts.value.len()); - for v in &strip_byte_counts.value { - match v { - TagValue::LongValue(v) => byte_counts.push(*v), - _ => (), - }; - } - // A bit much boilerplate, but should be okay and fast. - let mut curr_x = 0; - let mut curr_y = 0; - let mut curr_z = 0; - for (offset, byte_count) in offsets.iter().zip(byte_counts.iter()) { - reader.seek(SeekFrom::Start(*offset as u64))?; - for _ in 0..(*byte_count / image_depth as u32) { - let v = self.read_n(reader, image_depth as u64); - // println!("x {:?} len {:?}", curr_x, img.len()); - // println!("y {:?} wid {:?}", curr_y, img[0].len()); - // println!("z {:?} dep {:?}", curr_z, img[0][0].len()); - img[curr_x][curr_y][curr_z] = self.vec_to_value::(v); - curr_z += 1; - if curr_z >= img[curr_x][curr_y].len() { - curr_z = 0; - curr_y += 1; - } - if curr_y >= img[curr_x].len() as usize { - curr_y = 0; - curr_x += 1; - } - } - } - - // Return the output Vec. - Ok(img) - } -} diff --git a/tests/integration.rs b/tests/integration.rs index 948b8a1..c02e36e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -1,42 +1,34 @@ -extern crate geotiff as tiff; +use std::fs::File; +use std::path::Path; -use tiff::TIFF; +use geotiff::GeoTiff; -//#[test] -fn test_load() { - match TIFF::open("resources/marbles.tif") { - Ok(x) => println!("Read tiff {}", x), - Err(e) => println!("File I/O Error: {:?}", e), - } +fn read_geotiff>(path: P) -> GeoTiff { + GeoTiff::read(File::open(path).expect("File I/O error")).expect("File I/O error") } #[test] -fn test_load_2() { - match TIFF::open("resources/zh_dem_25.tif") { - Ok(x) => { - assert_eq!(x.image_data.len(), 366); - assert_eq!(x.image_data[0].len(), 399); +fn test_load_marbles() { + let geotiff = read_geotiff("resources/marbles.tif"); - assert_eq!(x.get_value_at(0, 0), 551); - assert_eq!(x.get_value_at(45, 67), 530); - assert_eq!(x.get_value_at(142, 325), 587); - }, - Err(e) => println!("File I/O Error: {:?}", e), - } + println!("{geotiff:?}"); + assert_eq!(geotiff.raster_width, 1419); + assert_eq!(geotiff.raster_height, 1001); + assert_eq!(geotiff.num_samples, 3); + assert_eq!(geotiff.get_value_at::(761, 599, 0), 147); + assert_eq!(geotiff.get_value_at::(761, 599, 1), 128); + assert_eq!(geotiff.get_value_at::(761, 599, 2), 165); } -// TODO Not supported yet, as this uses TileByteCounts instead of StripByteCounts. -//#[test] -fn test_load_3() { - match TIFF::open("resources/large_tif/DEM_ZH.tif") { - Ok(x) => { - assert_eq!(x.image_data.len(), 366); - assert_eq!(x.image_data[0].len(), 399); +#[test] +fn test_load_zh_dem_25() { + let geotiff = read_geotiff("resources/zh_dem_25.tif"); - assert_eq!(x.get_value_at(0, 0), 551); - assert_eq!(x.get_value_at(45, 67), 530); - assert_eq!(x.get_value_at(142, 325), 587); - }, - Err(e) => println!("File I/O Error: {:?}", e), - } + println!("{geotiff:?}"); + assert_eq!(geotiff.raster_width, 399); + assert_eq!(geotiff.raster_height, 366); + assert_eq!(geotiff.num_samples, 1); + assert_eq!(geotiff.get_value_at::(0, 0, 0), 551); + assert_eq!(geotiff.get_value_at::(67, 45, 0), 530); + assert_eq!(geotiff.get_value_at::(325, 142, 0), 587); }