Support writing the 2nd and further IFDs.
This commit is contained in:
parent
8e9ff50ca8
commit
8a7049c9b3
293
src/writer.rs
293
src/writer.rs
|
@ -58,18 +58,30 @@ use crate::value::Value;
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Writer<'a> {
|
pub struct Writer<'a> {
|
||||||
|
ifd_list: Vec<Ifd<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct Ifd<'a> {
|
||||||
tiff_fields: Vec<&'a Field<'a>>,
|
tiff_fields: Vec<&'a Field<'a>>,
|
||||||
exif_fields: Vec<&'a Field<'a>>,
|
exif_fields: Vec<&'a Field<'a>>,
|
||||||
gps_fields: Vec<&'a Field<'a>>,
|
gps_fields: Vec<&'a Field<'a>>,
|
||||||
interop_fields: Vec<&'a Field<'a>>,
|
interop_fields: Vec<&'a Field<'a>>,
|
||||||
tn_tiff_fields: Vec<&'a Field<'a>>,
|
|
||||||
tn_exif_fields: Vec<&'a Field<'a>>,
|
|
||||||
tn_gps_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]]>,
|
|
||||||
tiles: Option<&'a [&'a [u8]]>,
|
tiles: Option<&'a [&'a [u8]]>,
|
||||||
tn_jpeg: Option<&'a [u8]>,
|
jpeg: Option<&'a [u8]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Ifd<'a> {
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.tiff_fields.is_empty() &&
|
||||||
|
self.exif_fields.is_empty() &&
|
||||||
|
self.gps_fields.is_empty() &&
|
||||||
|
self.interop_fields.is_empty() &&
|
||||||
|
self.strips.is_none() &&
|
||||||
|
self.tiles.is_none() &&
|
||||||
|
self.jpeg.is_none()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WriterState<'a> {
|
struct WriterState<'a> {
|
||||||
|
@ -81,27 +93,13 @@ struct WriterState<'a> {
|
||||||
exif_ifd_offset: u32,
|
exif_ifd_offset: u32,
|
||||||
gps_ifd_offset: u32,
|
gps_ifd_offset: u32,
|
||||||
interop_ifd_offset: u32,
|
interop_ifd_offset: u32,
|
||||||
strips: Option<&'a [&'a [u8]]>,
|
|
||||||
tiles: Option<&'a [&'a [u8]]>,
|
|
||||||
jpeg: Option<&'a [u8]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Writer<'a> {
|
impl<'a> Writer<'a> {
|
||||||
/// Constructs an empty `Writer`.
|
/// Constructs an empty `Writer`.
|
||||||
pub fn new() -> Writer<'a> {
|
pub fn new() -> Writer<'a> {
|
||||||
Writer {
|
Writer {
|
||||||
tiff_fields: Vec::new(),
|
ifd_list: Vec::new(),
|
||||||
exif_fields: Vec::new(),
|
|
||||||
gps_fields: Vec::new(),
|
|
||||||
interop_fields: Vec::new(),
|
|
||||||
tn_tiff_fields: Vec::new(),
|
|
||||||
tn_exif_fields: Vec::new(),
|
|
||||||
tn_gps_fields: Vec::new(),
|
|
||||||
tn_interop_fields: Vec::new(),
|
|
||||||
strips: None,
|
|
||||||
tn_strips: None,
|
|
||||||
tiles: None,
|
|
||||||
tn_jpeg: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,48 +127,34 @@ impl<'a> Writer<'a> {
|
||||||
Field { tag: Tag::JPEGInterchangeFormat, .. } |
|
Field { tag: Tag::JPEGInterchangeFormat, .. } |
|
||||||
Field { tag: Tag::JPEGInterchangeFormatLength, .. } => {},
|
Field { tag: Tag::JPEGInterchangeFormatLength, .. } => {},
|
||||||
// Other normal tags.
|
// Other normal tags.
|
||||||
Field { tag: Tag(Context::Tiff, _), ifd_num: In::PRIMARY, .. } =>
|
Field { tag: Tag(ctx, _), ifd_num, .. } => {
|
||||||
self.tiff_fields.push(field),
|
let ifd = self.pick_ifd(ifd_num);
|
||||||
Field { tag: Tag(Context::Exif, _), ifd_num: In::PRIMARY, .. } =>
|
match ctx {
|
||||||
self.exif_fields.push(field),
|
Context::Tiff => ifd.tiff_fields.push(field),
|
||||||
Field { tag: Tag(Context::Gps, _), ifd_num: In::PRIMARY, .. } =>
|
Context::Exif => ifd.exif_fields.push(field),
|
||||||
self.gps_fields.push(field),
|
Context::Gps => ifd.gps_fields.push(field),
|
||||||
Field { tag: Tag(Context::Interop, _), ifd_num: In::PRIMARY, .. } =>
|
Context::Interop => ifd.interop_fields.push(field),
|
||||||
self.interop_fields.push(field),
|
}
|
||||||
Field { tag: Tag(Context::Tiff, _), ifd_num: In::THUMBNAIL, .. } =>
|
},
|
||||||
self.tn_tiff_fields.push(field),
|
|
||||||
Field { tag: Tag(Context::Exif, _), ifd_num: In::THUMBNAIL, .. } =>
|
|
||||||
self.tn_exif_fields.push(field),
|
|
||||||
Field { tag: Tag(Context::Gps, _), ifd_num: In::THUMBNAIL, .. } =>
|
|
||||||
self.tn_gps_fields.push(field),
|
|
||||||
Field { tag: Tag(Context::Interop, _), ifd_num: In::THUMBNAIL, .. } =>
|
|
||||||
self.tn_interop_fields.push(field),
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets TIFF strips for the primary image.
|
/// Sets TIFF strips for the specified IFD.
|
||||||
/// 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_strips(&mut self, strips: &'a [&'a [u8]]) {
|
pub fn set_strips(&mut self, strips: &'a [&'a [u8]], ifd_num: In) {
|
||||||
self.strips = Some(strips);
|
self.pick_ifd(ifd_num).strips = Some(strips);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets TIFF strips for the thumbnail image.
|
/// Sets TIFF tiles for the specified IFD.
|
||||||
/// 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_strips(&mut self, strips: &'a [&'a [u8]]) {
|
pub fn set_tiles(&mut self, tiles: &'a [&'a [u8]], ifd_num: In) {
|
||||||
self.tn_strips = Some(strips);
|
self.pick_ifd(ifd_num).tiles = Some(tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets TIFF tiles for the primary image.
|
/// Sets JPEG data for the specified IFD.
|
||||||
/// 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_tiles(&mut self, tiles: &'a [&'a [u8]]) {
|
pub fn set_jpeg(&mut self, jpeg: &'a [u8], ifd_num: In) {
|
||||||
self.tiles = Some(tiles);
|
self.pick_ifd(ifd_num).jpeg = Some(jpeg);
|
||||||
}
|
|
||||||
|
|
||||||
/// 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]) {
|
|
||||||
self.tn_jpeg = Some(jpeg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes Exif data and writes it into `w`.
|
/// Encodes Exif data and writes it into `w`.
|
||||||
|
@ -188,69 +172,51 @@ impl<'a> Writer<'a> {
|
||||||
BigEndian::writeu32(w, 8)?;
|
BigEndian::writeu32(w, 8)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the primary image.
|
// There must be at least 1 IFD in a TIFF file [TIFF6, Section 2,
|
||||||
let ws = WriterState {
|
// Image File Directory].
|
||||||
tiff_fields: self.tiff_fields.clone(),
|
if self.ifd_list.is_empty() {
|
||||||
exif_fields: self.exif_fields.clone(),
|
return Err(Error::InvalidFormat("At least one IFD must exist"));
|
||||||
gps_fields: self.gps_fields.clone(),
|
|
||||||
interop_fields: self.interop_fields.clone(),
|
|
||||||
tiff_ifd_offset: 0,
|
|
||||||
exif_ifd_offset: 0,
|
|
||||||
gps_ifd_offset: 0,
|
|
||||||
interop_ifd_offset: 0,
|
|
||||||
strips: self.strips,
|
|
||||||
tiles: self.tiles,
|
|
||||||
jpeg: None,
|
|
||||||
};
|
|
||||||
let next_ifd_offset_offset =
|
|
||||||
synthesize_fields(w, ws, In::PRIMARY, little_endian)?;
|
|
||||||
|
|
||||||
// Do not output the thumbnail IFD if there are no data in it.
|
|
||||||
let thumbnail_absent =
|
|
||||||
self.tn_tiff_fields.len() == 0 &&
|
|
||||||
self.tn_exif_fields.len() == 0 &&
|
|
||||||
self.tn_gps_fields.len() == 0 &&
|
|
||||||
self.tn_interop_fields.len() == 0 &&
|
|
||||||
self.tn_strips == None &&
|
|
||||||
self.tn_jpeg == None;
|
|
||||||
if thumbnail_absent {
|
|
||||||
w.flush()?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
let mut ifd_num_ck = Some(0);
|
||||||
let next_ifd_offset = pad_and_get_offset(w)?;
|
let mut next_ifd_offset_offset = 4;
|
||||||
let origpos = w.seek(SeekFrom::Current(0))?;
|
for ifd in &self.ifd_list {
|
||||||
w.seek(SeekFrom::Start(next_ifd_offset_offset as u64))?;
|
// Each IFD must have at least one entry [TIFF6, Section 2,
|
||||||
match little_endian {
|
// Image File Directory].
|
||||||
false => BigEndian::writeu32(w, next_ifd_offset)?,
|
if ifd.is_empty() {
|
||||||
true => LittleEndian::writeu32(w, next_ifd_offset)?,
|
return Err(Error::InvalidFormat("IFD must not be empty"));
|
||||||
|
}
|
||||||
|
let ifd_num =
|
||||||
|
ifd_num_ck.ok_or(Error::InvalidFormat("Too many IFDs"))?;
|
||||||
|
if ifd_num > 0 {
|
||||||
|
let next_ifd_offset = pad_and_get_offset(w)?;
|
||||||
|
let origpos = w.seek(SeekFrom::Current(0))?;
|
||||||
|
w.seek(SeekFrom::Start(next_ifd_offset_offset as u64))?;
|
||||||
|
match little_endian {
|
||||||
|
false => BigEndian::writeu32(w, next_ifd_offset)?,
|
||||||
|
true => LittleEndian::writeu32(w, next_ifd_offset)?,
|
||||||
|
}
|
||||||
|
w.seek(SeekFrom::Start(origpos))?;
|
||||||
|
}
|
||||||
|
next_ifd_offset_offset =
|
||||||
|
synthesize_fields(w, ifd, In(ifd_num), little_endian)?;
|
||||||
|
ifd_num_ck = ifd_num.checked_add(1);
|
||||||
}
|
}
|
||||||
w.seek(SeekFrom::Start(origpos))?;
|
|
||||||
|
|
||||||
// Write the thumbnail image.
|
|
||||||
let ws = WriterState {
|
|
||||||
tiff_fields: self.tn_tiff_fields.clone(),
|
|
||||||
exif_fields: self.tn_exif_fields.clone(),
|
|
||||||
gps_fields: self.tn_gps_fields.clone(),
|
|
||||||
interop_fields: self.tn_interop_fields.clone(),
|
|
||||||
tiff_ifd_offset: 0,
|
|
||||||
exif_ifd_offset: 0,
|
|
||||||
gps_ifd_offset: 0,
|
|
||||||
interop_ifd_offset: 0,
|
|
||||||
strips: self.tn_strips,
|
|
||||||
tiles: None,
|
|
||||||
jpeg: self.tn_jpeg,
|
|
||||||
};
|
|
||||||
synthesize_fields(w, ws, In::THUMBNAIL, little_endian)?;
|
|
||||||
|
|
||||||
w.flush()?;
|
w.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pick_ifd(&mut self, ifd_num: In) -> &mut Ifd<'a> {
|
||||||
|
let ifd_num = ifd_num.index() as usize;
|
||||||
|
if self.ifd_list.len() <= ifd_num {
|
||||||
|
self.ifd_list.resize_with(ifd_num + 1, Default::default);
|
||||||
|
}
|
||||||
|
&mut self.ifd_list[ifd_num]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synthesizes special fields, writes an image, and returns the offset
|
// Synthesizes special fields, writes an image, and returns the offset
|
||||||
// of the next IFD offset.
|
// of the next IFD offset.
|
||||||
fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
|
fn synthesize_fields<W>(w: &mut W, ifd: &Ifd, ifd_num: In,
|
||||||
little_endian: bool)
|
little_endian: bool)
|
||||||
-> Result<u32, Error> where W: Write + Seek {
|
-> Result<u32, Error> where W: Write + Seek {
|
||||||
let exif_in_tiff;
|
let exif_in_tiff;
|
||||||
|
@ -262,10 +228,18 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
|
||||||
let tile_byte_counts;
|
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.
|
let mut ws = WriterState {
|
||||||
let mut ws = ws;
|
tiff_fields: ifd.tiff_fields.clone(),
|
||||||
|
exif_fields: ifd.exif_fields.clone(),
|
||||||
|
gps_fields: ifd.gps_fields.clone(),
|
||||||
|
interop_fields: ifd.interop_fields.clone(),
|
||||||
|
tiff_ifd_offset: 0,
|
||||||
|
exif_ifd_offset: 0,
|
||||||
|
gps_ifd_offset: 0,
|
||||||
|
interop_ifd_offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(strips) = ws.strips {
|
if let Some(strips) = ifd.strips {
|
||||||
strip_offsets = Field {
|
strip_offsets = Field {
|
||||||
tag: Tag::StripOffsets,
|
tag: Tag::StripOffsets,
|
||||||
ifd_num: ifd_num,
|
ifd_num: ifd_num,
|
||||||
|
@ -280,7 +254,7 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
|
||||||
};
|
};
|
||||||
ws.tiff_fields.push(&strip_byte_counts);
|
ws.tiff_fields.push(&strip_byte_counts);
|
||||||
}
|
}
|
||||||
if let Some(tiles) = ws.tiles {
|
if let Some(tiles) = ifd.tiles {
|
||||||
tile_offsets = Field {
|
tile_offsets = Field {
|
||||||
tag: Tag::TileOffsets,
|
tag: Tag::TileOffsets,
|
||||||
ifd_num: ifd_num,
|
ifd_num: ifd_num,
|
||||||
|
@ -295,7 +269,7 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
|
||||||
};
|
};
|
||||||
ws.tiff_fields.push(&tile_byte_counts);
|
ws.tiff_fields.push(&tile_byte_counts);
|
||||||
}
|
}
|
||||||
if let Some(jpeg) = ws.jpeg {
|
if let Some(jpeg) = ifd.jpeg {
|
||||||
jpeg_offset = Field {
|
jpeg_offset = Field {
|
||||||
tag: Tag::JPEGInterchangeFormat,
|
tag: Tag::JPEGInterchangeFormat,
|
||||||
ifd_num: ifd_num,
|
ifd_num: ifd_num,
|
||||||
|
@ -317,6 +291,7 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
|
||||||
let tiff_fields_len = ws.tiff_fields.len() +
|
let tiff_fields_len = ws.tiff_fields.len() +
|
||||||
match gps_fields_len { 0 => 0, _ => 1 } +
|
match gps_fields_len { 0 => 0, _ => 1 } +
|
||||||
match exif_fields_len { 0 => 0, _ => 1 };
|
match exif_fields_len { 0 => 0, _ => 1 };
|
||||||
|
assert_ne!(tiff_fields_len, 0);
|
||||||
|
|
||||||
ws.tiff_ifd_offset = reserve_ifd(w, tiff_fields_len)?;
|
ws.tiff_ifd_offset = reserve_ifd(w, tiff_fields_len)?;
|
||||||
if exif_fields_len > 0 {
|
if exif_fields_len > 0 {
|
||||||
|
@ -353,13 +328,13 @@ fn synthesize_fields<W>(w: &mut W, ws: WriterState, ifd_num: In,
|
||||||
ws.interop_fields.sort_by_key(|f| f.tag.number());
|
ws.interop_fields.sort_by_key(|f| f.tag.number());
|
||||||
|
|
||||||
match little_endian {
|
match little_endian {
|
||||||
false => write_image::<_, BigEndian>(w, ws),
|
false => write_image::<_, BigEndian>(w, &ws, ifd),
|
||||||
true => write_image::<_, LittleEndian>(w, ws),
|
true => write_image::<_, LittleEndian>(w, &ws, ifd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, ifd: &Ifd)
|
||||||
-> Result<u32, Error> where W: Write + Seek, E: Endian {
|
-> Result<u32, Error> where W: Write + Seek, E: Endian {
|
||||||
let (next_ifd_offset_offset,
|
let (next_ifd_offset_offset,
|
||||||
strip_offsets_offset, tile_offsets_offset, jpeg_offset) =
|
strip_offsets_offset, tile_offsets_offset, jpeg_offset) =
|
||||||
|
@ -378,7 +353,7 @@ fn write_image<W, E>(w: &mut W, ws: WriterState)
|
||||||
w, &ws.interop_fields, ws.interop_ifd_offset)?;
|
w, &ws.interop_fields, ws.interop_ifd_offset)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(strips) = ws.strips {
|
if let Some(strips) = ifd.strips {
|
||||||
let mut strip_offsets = Vec::new();
|
let mut strip_offsets = Vec::new();
|
||||||
for strip in strips {
|
for strip in strips {
|
||||||
strip_offsets.push(get_offset(w)?);
|
strip_offsets.push(get_offset(w)?);
|
||||||
|
@ -391,7 +366,7 @@ fn write_image<W, E>(w: &mut W, ws: WriterState)
|
||||||
}
|
}
|
||||||
w.seek(SeekFrom::Start(origpos))?;
|
w.seek(SeekFrom::Start(origpos))?;
|
||||||
}
|
}
|
||||||
if let Some(tiles) = ws.tiles {
|
if let Some(tiles) = ifd.tiles {
|
||||||
let mut tile_offsets = Vec::new();
|
let mut tile_offsets = Vec::new();
|
||||||
for tile in tiles {
|
for tile in tiles {
|
||||||
tile_offsets.push(get_offset(w)?);
|
tile_offsets.push(get_offset(w)?);
|
||||||
|
@ -404,7 +379,7 @@ fn write_image<W, E>(w: &mut W, ws: WriterState)
|
||||||
}
|
}
|
||||||
w.seek(SeekFrom::Start(origpos))?;
|
w.seek(SeekFrom::Start(origpos))?;
|
||||||
}
|
}
|
||||||
if let Some(jpeg) = ws.jpeg {
|
if let Some(jpeg) = ifd.jpeg {
|
||||||
let offset = get_offset(w)?;
|
let offset = get_offset(w)?;
|
||||||
w.write_all(jpeg)?;
|
w.write_all(jpeg)?;
|
||||||
let origpos = w.seek(SeekFrom::Current(0))?;
|
let origpos = w.seek(SeekFrom::Current(0))?;
|
||||||
|
@ -657,7 +632,7 @@ mod tests {
|
||||||
let tiles: &[&[u8]] = &[b"TILE"];
|
let tiles: &[&[u8]] = &[b"TILE"];
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let mut buf = Cursor::new(Vec::new());
|
let mut buf = Cursor::new(Vec::new());
|
||||||
writer.set_tiles(tiles);
|
writer.set_tiles(tiles, In::PRIMARY);
|
||||||
writer.write(&mut buf, false).unwrap();
|
writer.write(&mut buf, false).unwrap();
|
||||||
let expected: &[u8] =
|
let expected: &[u8] =
|
||||||
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
||||||
|
@ -672,14 +647,21 @@ mod tests {
|
||||||
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).
|
||||||
let jpeg = b"JPEG";
|
let jpeg = b"JPEG";
|
||||||
|
let desc = Field {
|
||||||
|
tag: Tag::ImageDescription,
|
||||||
|
ifd_num: In::PRIMARY,
|
||||||
|
value: Value::Ascii(vec![b"jpg"]),
|
||||||
|
};
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let mut buf = Cursor::new(Vec::new());
|
let mut buf = Cursor::new(Vec::new());
|
||||||
writer.set_thumbnail_jpeg(jpeg);
|
writer.push_field(&desc);
|
||||||
|
writer.set_jpeg(jpeg, In::THUMBNAIL);
|
||||||
writer.write(&mut buf, false).unwrap();
|
writer.write(&mut buf, false).unwrap();
|
||||||
let expected: &[u8] =
|
let expected: &[u8] =
|
||||||
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
||||||
\x00\x00\x00\x00\x00\x0e\
|
\x00\x01\x01\x0e\x00\x02\x00\x00\x00\x04jpg\x00\
|
||||||
\x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x2c\
|
\x00\x00\x00\x1a\
|
||||||
|
\x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x38\
|
||||||
\x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\
|
\x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\
|
||||||
\x00\x00\x00\x00\
|
\x00\x00\x00\x00\
|
||||||
JPEG";
|
JPEG";
|
||||||
|
@ -689,15 +671,22 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn thumbnail_tiff() {
|
fn thumbnail_tiff() {
|
||||||
// This is not a valid TIFF strip (only for testing).
|
// This is not a valid TIFF strip (only for testing).
|
||||||
|
let desc = Field {
|
||||||
|
tag: Tag::ImageDescription,
|
||||||
|
ifd_num: In::PRIMARY,
|
||||||
|
value: Value::Ascii(vec![b"tif"]),
|
||||||
|
};
|
||||||
let strips: &[&[u8]] = &[b"STRIP"];
|
let strips: &[&[u8]] = &[b"STRIP"];
|
||||||
let mut writer = Writer::new();
|
let mut writer = Writer::new();
|
||||||
let mut buf = Cursor::new(Vec::new());
|
let mut buf = Cursor::new(Vec::new());
|
||||||
writer.set_thumbnail_strips(strips);
|
writer.push_field(&desc);
|
||||||
|
writer.set_strips(strips, In::THUMBNAIL);
|
||||||
writer.write(&mut buf, false).unwrap();
|
writer.write(&mut buf, false).unwrap();
|
||||||
let expected: &[u8] =
|
let expected: &[u8] =
|
||||||
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
||||||
\x00\x00\x00\x00\x00\x0e\
|
\x00\x01\x01\x0e\x00\x02\x00\x00\x00\x04tif\x00\
|
||||||
\x00\x02\x01\x11\x00\x04\x00\x00\x00\x01\x00\x00\x00\x2c\
|
\x00\x00\x00\x1a\
|
||||||
|
\x00\x02\x01\x11\x00\x04\x00\x00\x00\x01\x00\x00\x00\x38\
|
||||||
\x01\x17\x00\x04\x00\x00\x00\x01\x00\x00\x00\x05\
|
\x01\x17\x00\x04\x00\x00\x00\x01\x00\x00\x00\x05\
|
||||||
\x00\x00\x00\x00\
|
\x00\x00\x00\x00\
|
||||||
STRIP";
|
STRIP";
|
||||||
|
@ -733,7 +722,7 @@ mod tests {
|
||||||
writer.push_field(&exif_ver);
|
writer.push_field(&exif_ver);
|
||||||
writer.push_field(&gps_ver);
|
writer.push_field(&gps_ver);
|
||||||
writer.push_field(&interop_index);
|
writer.push_field(&interop_index);
|
||||||
writer.set_thumbnail_jpeg(jpeg);
|
writer.set_jpeg(jpeg, In::THUMBNAIL);
|
||||||
writer.write(&mut buf, false).unwrap();
|
writer.write(&mut buf, false).unwrap();
|
||||||
let expected: &[u8] =
|
let expected: &[u8] =
|
||||||
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
||||||
|
@ -756,6 +745,58 @@ mod tests {
|
||||||
assert_eq!(buf.into_inner(), expected);
|
assert_eq!(buf.into_inner(), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn primary_thumbnail_and_2nd() {
|
||||||
|
let desc0 = Field {
|
||||||
|
tag: Tag::ImageDescription,
|
||||||
|
ifd_num: In::PRIMARY,
|
||||||
|
value: Value::Ascii(vec![b"p"]),
|
||||||
|
};
|
||||||
|
let desc1 = Field {
|
||||||
|
tag: Tag::ImageDescription,
|
||||||
|
ifd_num: In::THUMBNAIL,
|
||||||
|
value: Value::Ascii(vec![b"t"]),
|
||||||
|
};
|
||||||
|
let desc2 = Field {
|
||||||
|
tag: Tag::ImageDescription,
|
||||||
|
ifd_num: In(2),
|
||||||
|
value: Value::Ascii(vec![b"2"]),
|
||||||
|
};
|
||||||
|
let mut writer = Writer::new();
|
||||||
|
let mut buf = Cursor::new(Vec::new());
|
||||||
|
writer.push_field(&desc0);
|
||||||
|
writer.push_field(&desc1);
|
||||||
|
writer.push_field(&desc2);
|
||||||
|
writer.write(&mut buf, false).unwrap();
|
||||||
|
let expected: &[u8] =
|
||||||
|
b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\
|
||||||
|
\x00\x01\x01\x0e\x00\x02\x00\x00\x00\x02p\x00\x00\x00\
|
||||||
|
\x00\x00\x00\x1a\
|
||||||
|
\x00\x01\x01\x0e\x00\x02\x00\x00\x00\x02t\x00\x00\x00\
|
||||||
|
\x00\x00\x00\x2c\
|
||||||
|
\x00\x01\x01\x0e\x00\x02\x00\x00\x00\x022\x00\x00\x00\
|
||||||
|
\x00\x00\x00\x00";
|
||||||
|
assert_eq!(buf.into_inner(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_file() {
|
||||||
|
let mut writer = Writer::new();
|
||||||
|
let mut buf = Cursor::new(Vec::new());
|
||||||
|
assert_pat!(writer.write(&mut buf, false),
|
||||||
|
Err(Error::InvalidFormat("At least one IFD must exist")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_primary() {
|
||||||
|
let jpeg = b"JPEG";
|
||||||
|
let mut writer = Writer::new();
|
||||||
|
let mut buf = Cursor::new(Vec::new());
|
||||||
|
writer.set_jpeg(jpeg, In::THUMBNAIL);
|
||||||
|
assert_pat!(writer.write(&mut buf, false),
|
||||||
|
Err(Error::InvalidFormat("IFD must not be empty")));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_twice() {
|
fn write_twice() {
|
||||||
let image_desc = Field {
|
let image_desc = Field {
|
||||||
|
|
|
@ -82,16 +82,16 @@ fn rwr_compare<P>(path: P) where P: AsRef<Path> {
|
||||||
writer.push_field(f);
|
writer.push_field(f);
|
||||||
}
|
}
|
||||||
if let Some(ref strips) = strips {
|
if let Some(ref strips) = strips {
|
||||||
writer.set_strips(strips);
|
writer.set_strips(strips, In::PRIMARY);
|
||||||
}
|
}
|
||||||
if let Some(ref tn_strips) = tn_strips {
|
if let Some(ref tn_strips) = tn_strips {
|
||||||
writer.set_thumbnail_strips(tn_strips);
|
writer.set_strips(tn_strips, In::THUMBNAIL);
|
||||||
}
|
}
|
||||||
if let Some(ref tiles) = tiles {
|
if let Some(ref tiles) = tiles {
|
||||||
writer.set_tiles(tiles);
|
writer.set_tiles(tiles, In::PRIMARY);
|
||||||
}
|
}
|
||||||
if let Some(ref tn_jpeg) = tn_jpeg {
|
if let Some(ref tn_jpeg) = tn_jpeg {
|
||||||
writer.set_thumbnail_jpeg(tn_jpeg);
|
writer.set_jpeg(tn_jpeg, In::THUMBNAIL);
|
||||||
}
|
}
|
||||||
let mut out = Cursor::new(Vec::new());
|
let mut out = Cursor::new(Vec::new());
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
Loading…
Reference in New Issue