Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exif support #221

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/target/
**/*.rs.bk
Cargo.lock
.idea/
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ flate2 = "1.0.20"

[dev-dependencies]
criterion = "0.3.1"
kamadak-exif = "0.5.5"

[[bench]]
name = "lzw"
Expand Down
141 changes: 141 additions & 0 deletions src/decoder/ifd.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Function for reading TIFF tags

use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::io::{self, Read, Seek};
Expand All @@ -9,6 +10,7 @@ use std::str;
use super::stream::{ByteOrder, EndianReader, SmartReader};
use crate::tags::{Tag, Type};
use crate::{TiffError, TiffFormatError, TiffResult};
use crate::encoder::TiffValue;

use self::Value::{
Ascii, Byte, Double, Float, Ifd, IfdBig, List, Rational, RationalBig, SRational, SRationalBig,
Expand Down Expand Up @@ -642,6 +644,145 @@ impl Entry {
}
Ok(List(v))
}

/// retrieve entry with data read into a buffer (to cache it for writing)
pub fn as_buffered<R: Read+Seek>(&self,
bigtiff: bool,
reader: &mut SmartReader<R> ) -> TiffResult<BufferedEntry> {
// establish byte order
let bo = reader.byte_order();
let native_bo;
#[cfg(target_endian = "little")]
{native_bo = ByteOrder::LittleEndian;}
#[cfg(not(target_endian = "little"))]
{native_bo = ByteOrder::BigEndian;}

// establish size
let tag_size = match self.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_bytes = match self.count.checked_mul(tag_size) {
Some(n) => n,
None => {
return Err(TiffError::LimitsExceeded);
}
};

let mut buf = vec![0; value_bytes as usize];
// read values that fit within the IFD entry
if value_bytes <= 4 || (bigtiff && value_bytes <= 8) {
self.r(bo).read(&mut buf)?;

match self.type_ { // for multi-byte values
Type::SHORT | Type::SSHORT | Type::LONG | Type::SLONG | Type::FLOAT | Type::IFD
| Type::LONG8 | Type::SLONG8 | Type::DOUBLE | Type::IFD8 =>
if native_bo != bo { // if byte-order is non-native
// reverse byte order
let mut new_buf = vec![0; value_bytes as usize];
for i in 0..value_bytes {
new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize];
}
buf = new_buf;
}
_=>{}
}

} else { // values that use a pointer
// read pointed data
if bigtiff {
reader.goto_offset(self.r(bo).read_u64()?.into())?;
} else {
reader.goto_offset(self.r(bo).read_u32()?.into())?;
}
reader.read_exact(&mut buf)?;

match self.type_ { // for multi-byte values
Type::LONG8 | Type::SLONG8 | Type::DOUBLE =>
if native_bo != bo { // if byte-order is non-native
// reverse byte order
let mut new_buf = vec![0; value_bytes as usize];
for i in 0..value_bytes {
new_buf[i as usize] = buf[(value_bytes - 1 - i) as usize];
}
buf = new_buf;
}
Type::RATIONAL | Type::SRATIONAL =>
if native_bo != bo { // if byte-order is non-native
// reverse byte order
let mut new_buf = vec![0; 8];
new_buf[0] = buf[3];
new_buf[1] = buf[2];
new_buf[2] = buf[1];
new_buf[3] = buf[0];
new_buf[4] = buf[7];
new_buf[5] = buf[6];
new_buf[6] = buf[5];
new_buf[7] = buf[4];
buf = new_buf;
}
_=>{}
}
}

Ok(BufferedEntry{
type_: self.type_,
count: self.count.clone(),
data: buf
})
}
}

/// Entry with buffered instead of read data
#[derive(Clone)]
pub struct BufferedEntry {
type_: Type,
count: u64,
data: Vec<u8>,
}

/// Implement TiffValue to allow writing this data with encoder
impl TiffValue for BufferedEntry {
const BYTE_LEN: u8 = 1;

fn is_type(&self) -> Type {
self.type_
}

fn count(&self) -> usize {
self.count.clone() as usize
}

fn bytes(&self) -> usize {
let tag_size = match self.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_bytes = match self.count.checked_mul(tag_size) {
Some(n) => n,
None => 0,
};
value_bytes as usize
}

fn data(&self) -> Cow<[u8]> {
Cow::Borrowed(&self.data)
}
}

/// Extracts a list of BYTE tags stored in an offset
Expand Down
17 changes: 17 additions & 0 deletions src/decoder/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ impl Image {
));
}
}
(false,false,false,false) => { // allow reading Tiff without image data
chunk_type = ChunkType::None; // the ChunkType will make sure an error is thrown later if trying to read image data
strip_decoder = None;
tile_attributes = None;
chunk_offsets = Vec::new();
chunk_bytes = Vec::new();
},
(_, _, _, _) => {
return Err(TiffError::FormatError(
TiffFormatError::StripTileTagConflict,
Expand Down Expand Up @@ -504,6 +511,11 @@ impl Image {
u32::try_from(tile_attrs.tile_length)?,
))
}
ChunkType::None => {
return Err(TiffError::FormatError(
TiffFormatError::StripTileTagConflict,
))
}
}
}

Expand Down Expand Up @@ -536,6 +548,11 @@ impl Image {

Ok((u32::try_from(tile_width)?, u32::try_from(tile_length)?))
}
ChunkType::None => {
return Err(TiffError::FormatError(
TiffFormatError::StripTileTagConflict,
))
}
}
}

Expand Down
80 changes: 73 additions & 7 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::io::{self, Read, Seek};
use std::io::{self, Cursor, Read, Seek, Write};
use std::ops::Range;

use crate::{
bytecast, ColorType, TiffError, TiffFormatError, TiffResult, TiffUnsupportedError, UsageError,
};
use crate::decoder::ifd::Entry;
use crate::encoder::{DirectoryEncoder, TiffEncoder, TiffKind};

use self::ifd::Directory;
use self::image::Image;
use crate::tags::{
CompressionMethod, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat,
Tag, Type,
};
use crate::tags::{CompressionMethod, EXIF_TAGS, PhotometricInterpretation, PlanarConfiguration, Predictor, SampleFormat, Tag, Type};

use self::stream::{ByteOrder, EndianReader, SmartReader};

Expand Down Expand Up @@ -240,6 +239,7 @@ impl<'a> DecodingBuffer<'a> {
pub enum ChunkType {
Strip,
Tile,
None,
}

/// Decoding limits
Expand Down Expand Up @@ -608,6 +608,10 @@ impl<R: Read + Seek> Decoder<R> {
&self.image
}

pub fn inner(&mut self) -> &mut SmartReader<R> {
&mut self.reader
}

/// Loads the IFD at the specified index in the list, if one exists
pub fn seek_to_image(&mut self, ifd_index: usize) -> TiffResult<()> {
// Check whether we have seen this IFD before, if so then the index will be less than the length of the list of ifd offsets
Expand Down Expand Up @@ -836,7 +840,7 @@ impl<R: Read + Seek> Decoder<R> {
}

/// Reads the IFD starting at the indicated location.
fn read_ifd(
pub fn read_ifd(
reader: &mut SmartReader<R>,
bigtiff: bool,
ifd_location: u64,
Expand Down Expand Up @@ -874,10 +878,14 @@ impl<R: Read + Seek> Decoder<R> {
Ok((dir, next_ifd))
}

pub fn find_tag_entry(&self, tag: Tag) -> Option<Entry> {
self.image().ifd.as_ref().unwrap().get(&tag).cloned()
}

/// Tries to retrieve a tag.
/// Return `Ok(None)` if the tag is not present.
pub fn find_tag(&mut self, tag: Tag) -> TiffResult<Option<ifd::Value>> {
let entry = match self.image().ifd.as_ref().unwrap().get(&tag) {
let entry = match self.find_tag_entry(tag) {
None => return Ok(None),
Some(entry) => entry.clone(),
};
Expand Down Expand Up @@ -1179,4 +1187,62 @@ impl<R: Read + Seek> Decoder<R> {

Ok(result)
}

/// Extracts the EXIF metadata (if present) and returns it in a light TIFF format
pub fn read_exif(&mut self) -> TiffResult<Vec<u8>> {
// create tiff encoder for result
let mut exifdata = Cursor::new(Vec::new());
let mut encoder = TiffEncoder::new(Write::by_ref(&mut exifdata))?;

// create new IFD
let mut ifd0 = encoder.new_directory()?;

// copy Exif tags from main IFD
let exif_tags = EXIF_TAGS;
exif_tags.into_iter().for_each(| tag | {
let entry = self.find_tag_entry(tag);
if entry.is_some() {
let b_entry = entry.unwrap().as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap();
ifd0.write_tag(tag, b_entry).unwrap();
}
});

// copy sub-ifds
self.copy_ifd(Tag::ExifIfd, &mut ifd0)?;
self.copy_ifd(Tag::GpsIfd, &mut ifd0)?;
self.copy_ifd(Tag::InteropIfd, &mut ifd0)?;

ifd0.finish()?;

Ok(exifdata.into_inner())
}

fn copy_ifd<W: Seek+Write, K: TiffKind>(&mut self, tag: Tag, new_ifd: &mut DirectoryEncoder<W,K>) -> TiffResult<()> {
let exif_ifd_offset = self.find_tag(tag)?;
if exif_ifd_offset.is_some() {
let offset = if self.bigtiff {
exif_ifd_offset.unwrap().into_u64()?
} else {
exif_ifd_offset.unwrap().into_u32()?.into()
};

// create sub-ifd
new_ifd.subdirectory_start();

let (ifd, _trash1) =
Self::read_ifd(&mut self.reader, self.bigtiff.clone(), offset)?;

// loop through entries
ifd.into_iter().for_each(|(tag, value)| {
let b_entry = value.as_buffered(self.bigtiff.clone(), &mut self.reader).unwrap();
new_ifd.write_tag(tag, b_entry).unwrap();
});

// return to ifd0 and write offset
let ifd_offset = new_ifd.subirectory_close()?;
new_ifd.write_tag(tag, ifd_offset as u32)?;
}

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/decoder/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::convert::TryFrom;
use std::io::{self, BufRead, BufReader, Read, Seek, Take};

/// Byte order of the TIFF file.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ByteOrder {
/// little endian byte order
LittleEndian,
Expand Down
Loading
Loading