diff --git a/Cargo.lock b/Cargo.lock index a639dfb..ae2da1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.1" @@ -13,6 +18,14 @@ dependencies = [ "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "miniz_oxide" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num" version = "0.2.0" @@ -85,16 +98,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rust_geotiff" -version = "0.0.1" +version = "0.0.2" dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] +"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum enum_primitive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" "checksum num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4825417e1e1406b3782a8ce92f4d53f26ec055e3622e1881ca8e9f5f9e08db" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" "checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" diff --git a/Cargo.toml b/Cargo.toml index 50181da..bf4f82e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "rust_geotiff" -version = "0.0.1" -authors = ["Dominik Bucher "] +version = "0.0.2" +authors = ["Dominik Bucher ", "Peter Braden "] [dependencies] byteorder = "*" enum_primitive = "*" num = "*" +miniz_oxide = "0.3" diff --git a/README.md b/README.md index 3b0fa41..7861e80 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,9 @@ x.get_value_at(longitude, latitude); Where `longitude` corresponds to the `image_length` and `latitude` to the `image_width`. This might be a bit counter intuitive, but seems consistent with GDAL (have to look into this). +NB: GeoTiff files can encode data as integers, or as floating point values - for +simplicity we represent both as f64 values. + Caution: the `longitude` and `latitude` are only in pixels, no coordinate transformations are applied! ## Development and Testing diff --git a/resources/mapzen-geotiff-14-10348-7801.tif b/resources/mapzen-geotiff-14-10348-7801.tif new file mode 100644 index 0000000..d7ea061 Binary files /dev/null and b/resources/mapzen-geotiff-14-10348-7801.tif differ diff --git a/src/lib.rs b/src/lib.rs index d87770f..7419f6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,12 +2,8 @@ extern crate byteorder; #[macro_use] extern crate enum_primitive; extern crate num; +extern crate miniz_oxide; -use num::FromPrimitive; - -use byteorder::{ReadBytesExt, WriteBytesExt, BigEndian, LittleEndian}; -use std::io::{Read, Seek}; -use std::collections::{HashMap, HashSet}; use std::io::Result; use std::fmt; @@ -15,7 +11,6 @@ mod lowlevel; mod reader; pub mod tiff; -use tiff::*; use reader::*; pub use tiff::TIFF; @@ -30,8 +25,14 @@ impl TIFF { tiff_reader.load(filename) } + /// Read from an open file + pub fn read(reader: &mut dyn SeekableReader) -> Result> { + let tiff_reader = TIFFReader; + tiff_reader.read(reader) + } + /// Gets the value at a given coordinate (in pixels). - pub fn get_value_at(&self, lon: usize, lat: usize) -> usize { + pub fn get_value_at(&self, lon: usize, lat: usize) -> f64 { self.image_data[lon][lat][0] } } diff --git a/src/lowlevel.rs b/src/lowlevel.rs index d494f0c..3982772 100644 --- a/src/lowlevel.rs +++ b/src/lowlevel.rs @@ -57,7 +57,7 @@ pub fn tag_size(t: &TagType) -> u32 { TagType::SignedRationalTag => 8, TagType::FloatTag => 4, TagType::DoubleTag => 8, - _ => 0, + //_ => 0, } } @@ -85,16 +85,19 @@ pub enum PhotometricInterpretation { 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 compression chosen for this TIFF. +enum_from_primitive! { + #[repr(u16)] + #[derive(Debug)] + pub enum Compression { + None = 1, + Huffman = 2, + LZW = 5, + OJPEG = 6, + JPEG = 7, + AdobeDeflate = 8, + PackBits = 32773, + } } /// The resolution unit of this TIFF. @@ -106,7 +109,8 @@ pub enum ResolutionUnit { Centimetre = 3, } -/// The sample format of this TIFF. +// The sample format of this TIFF. +enum_from_primitive! { #[repr(u16)] #[derive(Debug)] pub enum SampleFormat { @@ -115,6 +119,7 @@ pub enum SampleFormat { IEEEFloatingPoint = 3, Undefined = 4, } +} /// The image type of this TIFF. #[derive(Debug)] @@ -224,6 +229,10 @@ enum_from_primitive! { // Extension TIFF Tags // See http://www.awaresystems.be/imaging/tiff/tifftags/extension.html XMPTag = 0x02bc, + TileWidth = 0x0142, + TileLength = 0x0143, + TileOffsets = 0x0144, + TileByteCounts = 0x0145, // Private Tags PhotoshopTag = 0x8649, diff --git a/src/reader.rs b/src/reader.rs index 3ffef1d..5678159 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3,10 +3,12 @@ use std::path::Path; use std::fs::File; use num::FromPrimitive; +use miniz_oxide::inflate::decompress_to_vec_zlib; + use byteorder::{ReadBytesExt, ByteOrder, BigEndian, LittleEndian}; -use lowlevel::{TIFFByteOrder, TIFFTag, BYTE, SBYTE, SHORT, SSHORT, LONG, SLONG, FLOAT, - TagType, TagValue, tag_size}; +use lowlevel::{TIFFByteOrder, TIFFTag, + TagType, TagValue, tag_size, Compression, SampleFormat}; use tiff::{TIFF, IFD, IFDEntry, decode_tag, decode_tag_type}; /// A helper trait to indicate that something needs to be seekable and readable. @@ -29,7 +31,7 @@ impl TIFFReader { } /// Reads the `.tiff` file, starting with the byte order. - pub fn read(&self, reader: &mut SeekableReader) -> Result> { + 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), @@ -37,7 +39,7 @@ impl TIFFReader { } /// Helper function to read the byte order, one of `LittleEndian` or `BigEndian`. - pub fn read_byte_order(&self, reader: &mut SeekableReader) -> Result { + 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::()?) { @@ -51,7 +53,7 @@ impl TIFFReader { /// /// This starts by reading the magic number, the IFD offset, the IFDs themselves, and finally, /// the image data. - fn read_tiff(&self, reader: &mut SeekableReader) -> Result> { + 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)?; @@ -63,7 +65,7 @@ impl TIFFReader { } /// Reads the magic number, i.e., 42. - fn read_magic(&self, reader: &mut SeekableReader) -> Result<()> { + fn read_magic(&self, reader: &mut dyn SeekableReader) -> Result<()> { // Bytes 2-3: 0042 // Read and validate HeaderMagic match reader.read_u16::()? { @@ -73,11 +75,11 @@ impl TIFFReader { } /// Reads the IFD offset. The first IFD is then read from this position. - pub fn read_ifd_offset(&self, reader: &mut SeekableReader) -> Result { + 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); + //println!("IFD offset: {:?}", ifd_offset_field); Ok(ifd_offset_field) } @@ -85,12 +87,12 @@ impl TIFFReader { /// /// 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 SeekableReader, ifd_offset: u32) -> Result { + 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); + //println!("IFD entry count: {}", entry_count); let mut ifd = IFD { count: entry_count, entries: Vec::with_capacity(entry_count as usize) }; @@ -106,7 +108,7 @@ impl TIFFReader { } /// Reads `n` bytes from a reader into a Vec. - fn read_n(&self, reader: &mut SeekableReader, bytes_to_read: u64) -> 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); @@ -137,13 +139,13 @@ impl TIFFReader { &TagType::FloatTag => TagValue::FloatValue(Endian::read_f32(&vec[..])), &TagType::DoubleTag => TagValue::DoubleValue(Endian::read_f64(&vec[..])), &TagType::UndefinedTag => TagValue::ByteValue(0), - _ => panic!("Tag not found!"), + //_ => panic!("Tag not found!"), } } /// 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 { + fn vec_to_int_value(&self, vec: Vec) -> usize { let len = vec.len(); match len { 0 => 0 as usize, @@ -155,13 +157,24 @@ impl TIFFReader { } } + fn vec_to_f64_value(&self, vec: Vec) -> f64 { + let len = vec.len(); + match len { + 0 => 0 as f64, + 1 => vec[0] as f64, + 4 => Endian::read_f32(&vec[..]) as f64, + 8 => Endian::read_f64(&vec[..]) as f64, + _ => 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 SeekableReader) -> Result { - println!("Reading tag at {}/{}", ifd_offset, entry_number); + 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))?; @@ -188,8 +201,8 @@ impl TIFFReader { // 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); + //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 { @@ -218,19 +231,82 @@ impl TIFFReader { 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); + //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) } + + fn read_block_data(&self, + reader: &mut dyn SeekableReader, + offset: &u32, + byte_count: &u32, + block_size: usize, + image_depth: usize, + compression: Compression, + sample_format: &SampleFormat + ) -> Result> { + + reader.seek(SeekFrom::Start(*offset as u64))?; + let mut decompressed = vec![0u8; block_size * image_depth]; + + match compression { + Compression::None => { + // Normally the byte_count should equal the block_size * image_depth. + // + // However at the end of a stripped image, we can have a truncated buffer: + // + // See per spec: "For example, if ImageLength is 24, and RowsPerStrip is 10, + // then there are 3strips, with 10 rows in the first strip, 10 rows in the + // second strip, and 4 rows in thethird strip. (The data in the last strip is + // not padded with 6 extra rows of dummydata.)" + // + // In this case we need to resize the buffer, and as in the decompressed case + // the byte_count is equal to the length: + if block_size * image_depth != *byte_count as usize { + decompressed.resize(*byte_count as usize, 0); + } + + reader.read_exact(&mut decompressed)?; + }, + Compression::AdobeDeflate => { + let mut compressed = vec![0u8; *byte_count as usize]; + reader.read_exact(&mut compressed)?; + decompressed = decompress_to_vec_zlib(&compressed).expect("DEFLATE failed to decompress data."); + }, + _ => { + println!("Compression: {:?}", compression); + return Err(Error::new(ErrorKind::InvalidData, "Compression not supported")); + } + + } + + let mut elevations = vec![0.0; decompressed.len() / image_depth]; + for (i, v) in decompressed.chunks(image_depth).enumerate() { + match sample_format { + SampleFormat::UnsignedInteger => { elevations[i] = self.vec_to_int_value::(v.to_vec()) as f64; }, + SampleFormat::TwosComplementSignedInteger => { elevations[i] = self.vec_to_int_value::(v.to_vec()) as f64; }, + SampleFormat::IEEEFloatingPoint => { elevations[i] = self.vec_to_f64_value::(v.to_vec()); }, + SampleFormat::Undefined => { return Err(Error::new(ErrorKind::InvalidData, "SampleFormat is undefined"))}, + } + } + + Ok(elevations) + } + /// 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 SeekableReader, - ifd: &IFD) -> Result>>> { + fn read_image_data(&self, reader: &mut dyn SeekableReader, + ifd: &IFD) -> Result>>> { + + let compression = ifd.entries.iter().find(|&e| e.tag == TIFFTag::CompressionTag) + .ok_or(Error::new(ErrorKind::InvalidData, "Compression Tag not found."))?; + let compression = match compression.value[0] { + TagValue::ShortValue(v) => v, + _ => 0, + }; + // Image size and depth. let image_length = ifd.entries.iter().find(|&e| e.tag == TIFFTag::ImageLengthTag) .ok_or(Error::new(ErrorKind::InvalidData, "Image length not found."))?; @@ -239,20 +315,12 @@ impl TIFFReader { let image_depth = ifd.entries.iter().find(|&e| e.tag == TIFFTag::BitsPerSampleTag) .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 == TIFFTag::RowsPerStripTag) - .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 == TIFFTag::StripOffsetsTag) - .ok_or(Error::new(ErrorKind::InvalidData, "Strip offsets not found."))?; - let strip_byte_counts = ifd.entries.iter().find(|&e| e.tag == TIFFTag::StripByteCountsTag) - .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, @@ -261,54 +329,161 @@ impl TIFFReader { 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); + + let sample_format = ifd.entries.iter().find(|&e| e.tag == TIFFTag::SampleFormatTag) + .ok_or(Error::new(ErrorKind::InvalidData, "SampleFormat not found."))?; + + let sample_format = match sample_format.value[0] { + TagValue::ShortValue(v) => FromPrimitive::from_u16(v).unwrap(), + _ => SampleFormat::Undefined, + }; + + 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 j in 0..image_width { - &img[i as usize].push(vec![0; 1]); // TODO To be changed to take into account SamplesPerPixel! + for _j in 0..image_width { + &img[i as usize].push(vec![0.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), - _ => (), + + // There are two storage strategies in a TIFF, strips or tiles. + // See TIFF 6.0 Specification Section 15. + // + // To work out which we are using, we look for TileWidth, and if it's found, we switch to + // tiling strategy. + + let tile_strategy = ifd.entries.iter().find(|&e| e.tag == TIFFTag::TileWidth); + if tile_strategy.is_some() { + // Tile strategy + let tile_width = ifd.entries.iter().find(|&e| e.tag == TIFFTag::TileWidth) + .ok_or(Error::new(ErrorKind::InvalidData, "Tile Width not found."))?; + let tile_length = ifd.entries.iter().find(|&e| e.tag == TIFFTag::TileWidth) + .ok_or(Error::new(ErrorKind::InvalidData, "Tile Length not found."))?; + let tile_offsets = ifd.entries.iter().find(|&e| e.tag == TIFFTag::TileOffsets) + .ok_or(Error::new(ErrorKind::InvalidData, "Tile offsets not found."))?; + let tile_byte_counts = ifd.entries.iter().find(|&e| e.tag == TIFFTag::TileByteCounts) + .ok_or(Error::new(ErrorKind::InvalidData, "Tile Byte Countes not found."))?; + + let tile_width = match tile_width.value[0] { + TagValue::ShortValue(v) => v, + _ => 0 as u16 }; - } - 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), - _ => (), + + let tile_length = match tile_length.value[0] { + TagValue::ShortValue(v) => v, + _ => 0 as u16 }; - } - // 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 i 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; + + let mut offsets: Vec = Vec::with_capacity(tile_offsets.value.len()); + for v in &tile_offsets.value { + match v { + TagValue::LongValue(v) => offsets.push(*v), + _ => (), + }; + } + let mut byte_counts: Vec = Vec::with_capacity(tile_byte_counts.value.len()); + for v in &tile_byte_counts.value { + match v { + TagValue::ShortValue(v) => byte_counts.push(*v as u32), + TagValue::LongValue(v) => byte_counts.push(*v), + _ => (), + }; + } + + + + let mut tile = 0; + let tiles_across = (image_width + tile_width - 1) / tile_width; + let _tiles_down = (image_length + tile_length - 1) / tile_length; + //println!("{} x {} tiles of {} x {} ({} x {})", tiles_across, tiles_down, + // tile_width, tile_length, image_width, image_length); + + for (offset, byte_count) in offsets.iter().zip(byte_counts.iter()) { + let block = self.read_block_data::( + reader, offset, byte_count, + tile_width as usize * tile_length as usize, + image_depth as usize, + Compression::from_u16(compression).unwrap(), + &sample_format)?; + // Here we have to be careful as tiles can contain padding, which is junk data + // that should be discarded if it exceeds the bounds of ImageWidth or + // ImageLength + let mut curr_x = ((tile % tiles_across) * tile_width) as usize; + let tile_min_y = ((tile / tiles_across) * tile_length) as usize; + let mut curr_y = tile_min_y; + let tile_max_x = (curr_x + tile_width as usize).min(image_width as usize); + let tile_max_y = (curr_y + tile_length as usize).min(image_length as usize); + + //println!("tile {},{} to {},{},", curr_x, curr_y, tile_max_x, tile_max_y); + //println!("bytes: {}, depth: {}, {}", *byte_count, image_depth, block.len()); + + for v in block { + img[curr_x][curr_y][0] = v; + //println!("{} {} -> {}", curr_x, curr_y, v); curr_y += 1; + if curr_y >= tile_max_y { + curr_y = tile_min_y; + curr_x += 1; + } + if curr_x >= tile_max_x { + // This is padding. + //println!("!!PADDING {} {} {}", curr_x, curr_y, tile_max_x); + //break; + } } - if curr_y >= img[curr_x].len() as usize { - curr_y = 0; - curr_x += 1; + tile +=1; + } + + } else { + // Strip strategy + + // 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 == TIFFTag::RowsPerStripTag) + .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 == TIFFTag::StripOffsetsTag) + .ok_or(Error::new(ErrorKind::InvalidData, "Strip offsets not found."))?; + let strip_byte_counts = ifd.entries.iter().find(|&e| e.tag == TIFFTag::StripByteCountsTag) + .ok_or(Error::new(ErrorKind::InvalidData, "Strip byte counts not found."))?; + + // 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), + _ => (), + }; + } + + let mut curr_x = 0; + let mut curr_y = 0; + for (offset, byte_count) in offsets.iter().zip(byte_counts.iter()) { + let strip = self.read_block_data::( + reader, offset, byte_count, + rows_per_strip as usize * image_width as usize, + image_depth as usize, + Compression::from_u16(compression).unwrap(), + &sample_format)?; + + for v in strip { + img[curr_x][curr_y][0] = v; + curr_y += 1; + if curr_y >= img[curr_x].len() as usize { + curr_y = 0; + curr_x += 1; + } } } } diff --git a/src/tiff.rs b/src/tiff.rs index 7504747..4d618ef 100644 --- a/src/tiff.rs +++ b/src/tiff.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::{HashSet}; use enum_primitive::FromPrimitive; use lowlevel::*; @@ -10,7 +10,7 @@ use lowlevel::*; pub struct TIFF { pub ifds: Vec, // This is width * length * bytes_per_sample. - pub image_data: Vec>>, + pub image_data: Vec>>, } /// The header of a TIFF file. This comes first in any TIFF file and contains the byte order diff --git a/tests/integration.rs b/tests/integration.rs index 02cfd41..c94d626 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2,31 +2,29 @@ extern crate rust_geotiff as tiff; use tiff::TIFF; -//#[test] +/*#[test] fn test_load() { match TIFF::open("resources/marbles.tif") { Ok(x) => println!("Read tiff {}", x), Err(e) => println!("File I/O Error: {:?}", e), } } +*/ #[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_2() -> Result<(), std::io::Error> { + let x = TIFF::open("resources/zh_dem_25.tif")?; + assert_eq!(x.image_data.len(), 366); + assert_eq!(x.image_data[0].len(), 399); - 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), - } + assert_eq!(x.get_value_at(0, 0).round() as usize, 551); + assert_eq!(x.get_value_at(45, 67).round() as usize, 530); + assert_eq!(x.get_value_at(142, 325).round() as usize, 587); + Ok(()) } -// TODO Not supported yet, as this uses TileByteCounts instead of StripByteCounts. -//#[test] +/* 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) => { @@ -40,3 +38,16 @@ fn test_load_3() { Err(e) => println!("File I/O Error: {:?}", e), } } +*/ + +#[test] +fn test_load_4() -> Result<(), std::io::Error> { + let t = TIFF::open("resources/mapzen-geotiff-14-10348-7801.tif")?; + assert_eq!(t.image_data.len(), 512); + assert_eq!(t.image_data[0].len(), 512); + //println!("{:?}", t); + + assert_eq!(t.get_value_at(0,0).round() as usize, 467); + + Ok(()) +}