Support tiled TIFF images.

This is just for convenience in case the primary image is tiled.
Tiled images are not part of the Exif 2.31 standard or the TIFF 6.0
baseline specification.
This commit is contained in:
KAMADA Ken'ichi 2017-07-07 22:21:44 +09:00
parent d348ceb54b
commit b8aefa2722
2 changed files with 85 additions and 10 deletions

View File

@ -245,6 +245,12 @@ generate_well_known_tag_constants!(
"White point chromaticity"), "White point chromaticity"),
(PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal, (PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal,
"Chromaticities of primaries"), "Chromaticities of primaries"),
// Not referenced in Exif.
(TileOffsets, 0x144, DefaultValue::None, d_default,
"Tiled image data location"),
// Not referenced in Exif.
(TileByteCounts, 0x145, DefaultValue::None, d_default,
"Bytes per compressed tile"),
(JPEGInterchangeFormat, 0x201, DefaultValue::None, d_default, (JPEGInterchangeFormat, 0x201, DefaultValue::None, d_default,
"Offset to JPEG SOI"), "Offset to JPEG SOI"),
(JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default, (JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default,

View File

@ -47,6 +47,7 @@ pub struct Writer<'a> {
tn_interop_fields: Vec<&'a Field<'a>>, tn_interop_fields: Vec<&'a Field<'a>>,
strips: Option<&'a [&'a [u8]]>, strips: Option<&'a [&'a [u8]]>,
tn_strips: Option<&'a [&'a [u8]]>, tn_strips: Option<&'a [&'a [u8]]>,
tiles: Option<&'a [&'a [u8]]>,
tn_jpeg: Option<&'a [u8]>, tn_jpeg: Option<&'a [u8]>,
} }
@ -60,6 +61,7 @@ struct WriterState<'a> {
gps_ifd_offset: u32, gps_ifd_offset: u32,
interop_ifd_offset: u32, interop_ifd_offset: u32,
strips: Option<&'a [&'a [u8]]>, strips: Option<&'a [&'a [u8]]>,
tiles: Option<&'a [&'a [u8]]>,
jpeg: Option<&'a [u8]>, jpeg: Option<&'a [u8]>,
} }
@ -77,6 +79,7 @@ impl<'a> Writer<'a> {
tn_interop_fields: Vec::new(), tn_interop_fields: Vec::new(),
strips: None, strips: None,
tn_strips: None, tn_strips: None,
tiles: None,
tn_jpeg: None, tn_jpeg: None,
} }
} }
@ -86,19 +89,21 @@ impl<'a> Writer<'a> {
/// The fields can be appended in any order. /// The fields can be appended in any order.
/// Duplicate fields must not be appended. /// Duplicate fields must not be appended.
/// ///
/// The following fields are ignored and synthesized when needed:
/// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer, /// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer,
/// StripOffsets, StripByteCounts, JPEGInterchangeFormat, and /// StripOffsets, StripByteCounts, TileOffsets, TileByteCounts,
/// JPEGInterchangeFormatLength are ignored. /// JPEGInterchangeFormat, and JPEGInterchangeFormatLength.
/// They are synthesized when needed.
pub fn push_field(&mut self, field: &'a Field) { pub fn push_field(&mut self, field: &'a Field) {
match *field { match *field {
// Ignore the tags for the internal data structure. // Ignore the tags for the internal data structure.
Field { tag: tag::ExifIFDPointer, .. } | Field { tag: tag::ExifIFDPointer, .. } |
Field { tag: tag::GPSInfoIFDPointer, .. } | Field { tag: tag::GPSInfoIFDPointer, .. } |
Field { tag: tag::InteropIFDPointer, .. } => {}, Field { tag: tag::InteropIFDPointer, .. } => {},
// These tags are synthesized from the actual strip data. // These tags are synthesized from the actual strip/tile data.
Field { tag: tag::StripOffsets, .. } | Field { tag: tag::StripOffsets, .. } |
Field { tag: tag::StripByteCounts, .. } => {}, Field { tag: tag::StripByteCounts, .. } |
Field { tag: tag::TileOffsets, .. } |
Field { tag: tag::TileByteCounts, .. } => {},
// These tags are synthesized from the actual JPEG thumbnail. // These tags are synthesized from the actual JPEG thumbnail.
Field { tag: tag::JPEGInterchangeFormat, .. } | Field { tag: tag::JPEGInterchangeFormat, .. } |
Field { tag: tag::JPEGInterchangeFormatLength, .. } => {}, Field { tag: tag::JPEGInterchangeFormatLength, .. } => {},
@ -134,6 +139,12 @@ impl<'a> Writer<'a> {
self.tn_strips = Some(strips); self.tn_strips = Some(strips);
} }
/// Sets TIFF tiles for the primary image.
/// If this method is called multiple times, the last one is used.
pub fn set_tiles(&mut self, tiles: &'a [&'a [u8]]) {
self.tiles = Some(tiles);
}
/// Sets JPEG data for the thumbnail image. /// Sets JPEG data for the thumbnail image.
/// If this method is called multiple times, the last one is used. /// If this method is called multiple times, the last one is used.
pub fn set_thumbnail_jpeg(&mut self, jpeg: &'a [u8]) { pub fn set_thumbnail_jpeg(&mut self, jpeg: &'a [u8]) {
@ -170,6 +181,7 @@ impl<'a> Writer<'a> {
gps_ifd_offset: 0, gps_ifd_offset: 0,
interop_ifd_offset: 0, interop_ifd_offset: 0,
strips: self.strips, strips: self.strips,
tiles: self.tiles,
jpeg: None, jpeg: None,
}; };
let next_ifd_offset_offset = let next_ifd_offset_offset =
@ -208,6 +220,7 @@ impl<'a> Writer<'a> {
gps_ifd_offset: 0, gps_ifd_offset: 0,
interop_ifd_offset: 0, interop_ifd_offset: 0,
strips: self.tn_strips, strips: self.tn_strips,
tiles: None,
jpeg: self.tn_jpeg, jpeg: self.tn_jpeg,
}; };
try!(synthesize_fields(w, ws, true, little_endian)); try!(synthesize_fields(w, ws, true, little_endian));
@ -227,6 +240,8 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
let interop_in_exif; let interop_in_exif;
let strip_offsets; let strip_offsets;
let strip_byte_counts; let strip_byte_counts;
let tile_offsets;
let tile_byte_counts;
let jpeg_offset; let jpeg_offset;
let jpeg_length; let jpeg_length;
// Shrink the scope so that referenced fields live longer than ws. // Shrink the scope so that referenced fields live longer than ws.
@ -247,6 +262,21 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
}; };
ws.tiff_fields.push(&strip_byte_counts); ws.tiff_fields.push(&strip_byte_counts);
} }
if let Some(tiles) = ws.tiles {
tile_offsets = Field {
tag: tag::TileOffsets,
thumbnail: thumbnail,
value: Value::Long(vec![0; tiles.len()]),
};
ws.tiff_fields.push(&tile_offsets);
tile_byte_counts = Field {
tag: tag::TileByteCounts,
thumbnail: thumbnail,
value: Value::Long(
tiles.iter().map(|s| s.len() as u32).collect()),
};
ws.tiff_fields.push(&tile_byte_counts);
}
if let Some(jpeg) = ws.jpeg { if let Some(jpeg) = ws.jpeg {
jpeg_offset = Field { jpeg_offset = Field {
tag: tag::JPEGInterchangeFormat, tag: tag::JPEGInterchangeFormat,
@ -313,7 +343,8 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, thumbnail: bool,
// Writes an image and returns the offset of the next IFD offset. // Writes an image and returns the offset of the next IFD offset.
fn write_image<W, E>(w: &mut W, ws: WriterState) fn write_image<W, E>(w: &mut W, ws: WriterState)
-> Result<u32, Error> where W: Write + Seek, E: Endian { -> Result<u32, Error> where W: Write + Seek, E: Endian {
let (next_ifd_offset_offset, strip_offsets_offset, jpeg_offset) = let (next_ifd_offset_offset,
strip_offsets_offset, tile_offsets_offset, jpeg_offset) =
try!(write_ifd_and_fields::<_, E>( try!(write_ifd_and_fields::<_, E>(
w, &ws.tiff_fields, ws.tiff_ifd_offset)); w, &ws.tiff_fields, ws.tiff_ifd_offset));
if ws.exif_fields.len() > 0 { if ws.exif_fields.len() > 0 {
@ -342,6 +373,19 @@ fn write_image<W, E>(w: &mut W, ws: WriterState)
} }
try!(w.seek(SeekFrom::Start(origpos))); try!(w.seek(SeekFrom::Start(origpos)));
} }
if let Some(tiles) = ws.tiles {
let mut tile_offsets = Vec::new();
for tile in tiles {
tile_offsets.push(try!(get_offset(w)));
try!(w.write_all(tile));
}
let origpos = try!(w.seek(SeekFrom::Current(0)));
try!(w.seek(SeekFrom::Start(tile_offsets_offset as u64)));
for ofs in tile_offsets {
try!(E::writeu32(w, ofs));
}
try!(w.seek(SeekFrom::Start(origpos)));
}
if let Some(jpeg) = ws.jpeg { if let Some(jpeg) = ws.jpeg {
let offset = try!(get_offset(w)); let offset = try!(get_offset(w));
try!(w.write_all(jpeg)); try!(w.write_all(jpeg));
@ -367,13 +411,14 @@ fn reserve_ifd<W>(w: &mut W, count: usize)
} }
// Writes an IFD and its fields, and // Writes an IFD and its fields, and
// returns the offsets of the next IFD offset, StripOffsets value, and // returns the offsets of the next IFD offset, StripOffsets value,
// JPEGInterchangeFormat value. // TileOffsets value, and JPEGInterchangeFormat value.
fn write_ifd_and_fields<W, E>( fn write_ifd_and_fields<W, E>(
w: &mut W, fields: &Vec<&Field>, ifd_offset: u32) w: &mut W, fields: &Vec<&Field>, ifd_offset: u32)
-> Result<(u32, u32, u32), Error> where W: Write + Seek, E: Endian -> Result<(u32, u32, u32, u32), Error> where W: Write + Seek, E: Endian
{ {
let mut strip_offsets_offset = 0; let mut strip_offsets_offset = 0;
let mut tile_offsets_offset = 0;
let mut jpeg_offset = 0; let mut jpeg_offset = 0;
let mut ifd = Vec::new(); let mut ifd = Vec::new();
@ -406,6 +451,12 @@ fn write_ifd_and_fields<W, E>(
_ => try!(get_offset(w)) - valbuf.len() as u32, _ => try!(get_offset(w)) - valbuf.len() as u32,
}; };
} }
if f.tag == tag::TileOffsets {
tile_offsets_offset = match valbuf.len() {
0...4 => ifd_offset + ifd.len() as u32 - 4,
_ => try!(get_offset(w)) - valbuf.len() as u32,
};
}
if f.tag == tag::JPEGInterchangeFormat { if f.tag == tag::JPEGInterchangeFormat {
jpeg_offset = ifd_offset + ifd.len() as u32 - 4; jpeg_offset = ifd_offset + ifd.len() as u32 - 4;
} }
@ -417,7 +468,8 @@ fn write_ifd_and_fields<W, E>(
// Write the IFD. // Write the IFD.
try!(write_at(w, &ifd, ifd_offset)); try!(write_at(w, &ifd, ifd_offset));
Ok((next_ifd_offset_offset, strip_offsets_offset, jpeg_offset)) Ok((next_ifd_offset_offset,
strip_offsets_offset, tile_offsets_offset, jpeg_offset))
} }
// Returns the type, count, and encoded value. // Returns the type, count, and encoded value.
@ -581,6 +633,23 @@ mod tests {
assert_eq!(buf.into_inner(), expected); assert_eq!(buf.into_inner(), expected);
} }
#[test]
fn primary_tiff_tiled() {
// This is not a valid TIFF tile (only for testing).
let tiles: &[&[u8]] = &[b"TILE"];
let mut writer = Writer::new();
let mut buf = Cursor::new(Vec::new());
writer.set_tiles(tiles);
writer.write(&mut buf, false).unwrap();
let expected: &[u8] =
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
\x00\x02\x01\x44\x00\x04\x00\x00\x00\x01\x00\x00\x00\x26\
\x01\x45\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\
\x00\x00\x00\x00\
TILE";
assert_eq!(buf.into_inner(), expected);
}
#[test] #[test]
fn thumbnail_jpeg() { fn thumbnail_jpeg() {
// This is not a valid JPEG data (only for testing). // This is not a valid JPEG data (only for testing).