From b8aefa27229f59d8bee4c84b7ce34f613d2a7cb5 Mon Sep 17 00:00:00 2001 From: KAMADA Ken'ichi Date: Fri, 7 Jul 2017 22:21:44 +0900 Subject: [PATCH] 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. --- src/tag.rs | 6 ++++ src/writer.rs | 89 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/tag.rs b/src/tag.rs index 5a0ff22..3b0f246 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -245,6 +245,12 @@ generate_well_known_tag_constants!( "White point chromaticity"), (PrimaryChromaticities, 0x13f, DefaultValue::None, d_decimal, "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, "Offset to JPEG SOI"), (JPEGInterchangeFormatLength, 0x202, DefaultValue::None, d_default, diff --git a/src/writer.rs b/src/writer.rs index cfd4b6c..437eb91 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -47,6 +47,7 @@ pub struct Writer<'a> { tn_interop_fields: Vec<&'a Field<'a>>, strips: Option<&'a [&'a [u8]]>, tn_strips: Option<&'a [&'a [u8]]>, + tiles: Option<&'a [&'a [u8]]>, tn_jpeg: Option<&'a [u8]>, } @@ -60,6 +61,7 @@ struct WriterState<'a> { gps_ifd_offset: u32, interop_ifd_offset: u32, strips: Option<&'a [&'a [u8]]>, + tiles: Option<&'a [&'a [u8]]>, jpeg: Option<&'a [u8]>, } @@ -77,6 +79,7 @@ impl<'a> Writer<'a> { tn_interop_fields: Vec::new(), strips: None, tn_strips: None, + tiles: None, tn_jpeg: None, } } @@ -86,19 +89,21 @@ impl<'a> Writer<'a> { /// The fields can be appended in any order. /// Duplicate fields must not be appended. /// + /// The following fields are ignored and synthesized when needed: /// ExifIFDPointer, GPSInfoIFDPointer, InteropIFDPointer, - /// StripOffsets, StripByteCounts, JPEGInterchangeFormat, and - /// JPEGInterchangeFormatLength are ignored. - /// They are synthesized when needed. + /// StripOffsets, StripByteCounts, TileOffsets, TileByteCounts, + /// JPEGInterchangeFormat, and JPEGInterchangeFormatLength. pub fn push_field(&mut self, field: &'a Field) { match *field { // Ignore the tags for the internal data structure. Field { tag: tag::ExifIFDPointer, .. } | Field { tag: tag::GPSInfoIFDPointer, .. } | 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::StripByteCounts, .. } => {}, + Field { tag: tag::StripByteCounts, .. } | + Field { tag: tag::TileOffsets, .. } | + Field { tag: tag::TileByteCounts, .. } => {}, // These tags are synthesized from the actual JPEG thumbnail. Field { tag: tag::JPEGInterchangeFormat, .. } | Field { tag: tag::JPEGInterchangeFormatLength, .. } => {}, @@ -134,6 +139,12 @@ impl<'a> Writer<'a> { 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. /// If this method is called multiple times, the last one is used. pub fn set_thumbnail_jpeg(&mut self, jpeg: &'a [u8]) { @@ -170,6 +181,7 @@ impl<'a> Writer<'a> { gps_ifd_offset: 0, interop_ifd_offset: 0, strips: self.strips, + tiles: self.tiles, jpeg: None, }; let next_ifd_offset_offset = @@ -208,6 +220,7 @@ impl<'a> Writer<'a> { gps_ifd_offset: 0, interop_ifd_offset: 0, strips: self.tn_strips, + tiles: None, jpeg: self.tn_jpeg, }; try!(synthesize_fields(w, ws, true, little_endian)); @@ -227,6 +240,8 @@ fn synthesize_fields(w: &mut W, ws: WriterState, thumbnail: bool, let interop_in_exif; let strip_offsets; let strip_byte_counts; + let tile_offsets; + let tile_byte_counts; let jpeg_offset; let jpeg_length; // Shrink the scope so that referenced fields live longer than ws. @@ -247,6 +262,21 @@ fn synthesize_fields(w: &mut W, ws: WriterState, thumbnail: bool, }; 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 { jpeg_offset = Field { tag: tag::JPEGInterchangeFormat, @@ -313,7 +343,8 @@ fn synthesize_fields(w: &mut W, ws: WriterState, thumbnail: bool, // Writes an image and returns the offset of the next IFD offset. fn write_image(w: &mut W, ws: WriterState) -> Result 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>( w, &ws.tiff_fields, ws.tiff_ifd_offset)); if ws.exif_fields.len() > 0 { @@ -342,6 +373,19 @@ fn write_image(w: &mut W, ws: WriterState) } 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 { let offset = try!(get_offset(w)); try!(w.write_all(jpeg)); @@ -367,13 +411,14 @@ fn reserve_ifd(w: &mut W, count: usize) } // Writes an IFD and its fields, and -// returns the offsets of the next IFD offset, StripOffsets value, and -// JPEGInterchangeFormat value. +// returns the offsets of the next IFD offset, StripOffsets value, +// TileOffsets value, and JPEGInterchangeFormat value. fn write_ifd_and_fields( 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 tile_offsets_offset = 0; let mut jpeg_offset = 0; let mut ifd = Vec::new(); @@ -406,6 +451,12 @@ fn write_ifd_and_fields( _ => 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 { jpeg_offset = ifd_offset + ifd.len() as u32 - 4; } @@ -417,7 +468,8 @@ fn write_ifd_and_fields( // Write the IFD. 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. @@ -581,6 +633,23 @@ mod tests { 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] fn thumbnail_jpeg() { // This is not a valid JPEG data (only for testing).