Skip to content

Commit

Permalink
Add BigTiff support
Browse files Browse the repository at this point in the history
  • Loading branch information
jayvdb committed Nov 29, 2024
1 parent 51383ac commit 819ea73
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 10 deletions.
88 changes: 78 additions & 10 deletions src/formats/tiff.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
use crate::util::*;
use crate::{ImageResult, ImageSize};

use std::io::{BufRead, Cursor, Seek, SeekFrom};
use std::io::{BufRead, Cursor, Read, Seek, SeekFrom};

#[derive(Debug, PartialEq)]
enum Type {
Tiff,
BigTiff,
}

pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
reader.seek(SeekFrom::Start(0))?;
Expand All @@ -20,10 +26,43 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid TIFF header").into(),
);
};
let type_marker = read_u16(reader, &endianness)?;
let tiff_type = if type_marker == 42 {
Type::Tiff
} else if type_marker == 43 {
Type::BigTiff
} else {
return Err(
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid TIFF header").into(),
);
};

if tiff_type == Type::BigTiff {
// http://bigtiff.org/ describes the BigTIFF header additions as constants 8 and 0.
let offset_bytesize = read_u16(reader, &endianness)?;
if offset_bytesize != 8 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Unrecognised BigTiff offset size",
)
.into());
}
let extra_field = read_u16(reader, &endianness)?;
if extra_field != 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid BigTiff header",
)
.into());
}
}

// Read the IFD offset from the header
reader.seek(SeekFrom::Start(4))?;
let ifd_offset = read_u32(reader, &endianness)?;
let ifd_offset = if tiff_type == Type::Tiff {
read_u32(reader, &endianness)? as u64
} else {
read_u64(reader, &endianness)?
};

// IFD offset cannot be 0
if ifd_offset == 0 {
Expand All @@ -33,17 +72,26 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
}

// Jump to the IFD offset
reader.seek(SeekFrom::Start(ifd_offset.into()))?;
reader.seek(SeekFrom::Start(ifd_offset))?;

// Read how many IFD records there are
let ifd_count = read_u16(reader, &endianness)?;
let ifd_count = if tiff_type == Type::Tiff {
read_u16(reader, &endianness)? as u64
} else {
read_u64(reader, &endianness)?
};

let mut width = None;
let mut height = None;

for _ifd in 0..ifd_count {
let tag = read_u16(reader, &endianness)?;
let kind = read_u16(reader, &endianness)?;
let _count = read_u32(reader, &endianness)?;
let _count = if tiff_type == Type::Tiff {
read_u32(reader, &endianness)? as u64
} else {
read_u64(reader, &endianness)?
};

let value_bytes = match kind {
// BYTE | ASCII | SBYTE | UNDEFINED
Expand All @@ -55,7 +103,16 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
// RATIONAL | SRATIONAL
5 | 10 => 4 * 2,
// DOUBLE | LONG8 | SLONG8 | IFD8
12 | 16 | 17 | 18 => 8,
12 | 16 | 17 | 18 => {
if tiff_type == Type::Tiff {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid IFD type for standard TIFF",
)
.into());
}
8
}
// Anything else is invalid
_ => {
return Err(std::io::Error::new(
Expand All @@ -66,8 +123,17 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
}
};

let mut value_buffer = [0; 4];
reader.read_exact(&mut value_buffer)?;
let mut value_buffer = [0; 8];
let ifd_value_length = if tiff_type == Type::Tiff { 4 } else { 8 };
let mut handle = reader.take(ifd_value_length);
let bytes_loaded = handle.read(&mut value_buffer)?;
if bytes_loaded != ifd_value_length as usize {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Invalid IFD value length",
)
.into());
}

let mut r = Cursor::new(&value_buffer[..]);
let value = match value_bytes {
Expand Down Expand Up @@ -97,5 +163,7 @@ pub fn size<R: BufRead + Seek>(reader: &mut R) -> ImageResult<ImageSize> {
}

pub fn matches(header: &[u8]) -> bool {
header.starts_with(b"II\x2A\x00") || header.starts_with(b"MM\x00\x2A")
const TYPE_MARKERS: [u8; 2] = [b'\x2A', b'\x2B'];
(header.starts_with(b"II") && TYPE_MARKERS.contains(&header[2]) && header[3] == 0)
|| (header.starts_with(b"MM\x00") && TYPE_MARKERS.contains(&header[3]))
}
10 changes: 10 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ pub enum Endian {
Big,
}

pub fn read_u64<R: BufRead + Seek>(reader: &mut R, endianness: &Endian) -> ImageResult<u64> {
let mut buf = [0; 8];
reader.read_exact(&mut buf)?;

match endianness {
Endian::Little => Ok(u64::from_le_bytes(buf)),
Endian::Big => Ok(u64::from_be_bytes(buf)),
}
}

pub fn read_i32<R: BufRead + Seek>(reader: &mut R, endianness: &Endian) -> ImageResult<i32> {
let mut attr_size_buf = [0; 4];
reader.read_exact(&mut attr_size_buf)?;
Expand Down
Binary file added tests/images/tif/test_bif.tif
Binary file not shown.
13 changes: 13 additions & 0 deletions tests/tif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,16 @@ fn tiff_test_16bit_size() {
}
);
}

#[test]
#[cfg(feature = "tiff")]
fn tiff_test_bigtiff() {
let dim = size("tests/images/tif/test_bif.tif").unwrap();
assert_eq!(
dim,
ImageSize {
width: 1,
height: 1,
}
);
}

0 comments on commit 819ea73

Please sign in to comment.