kamadak-exif-0.3.1/Cargo.toml.orig010064400234210023421000000007501331145510500151430ustar0000000000000000[package] name = "kamadak-exif" version = "0.3.1" authors = ["KAMADA Ken'ichi "] description = "Exif parsing library written in pure Rust" documentation = "https://docs.rs/kamadak-exif" homepage = "https://github.com/kamadak/exif-rs" repository = "https://github.com/kamadak/exif-rs" readme = "README" keywords = ["Exif", "JPEG", "parser", "reader", "TIFF"] categories = ["multimedia::encoding", "parser-implementations"] license = "BSD-2-Clause" [lib] name = "exif" kamadak-exif-0.3.1/Cargo.toml0000644000000017630000000000000114230ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "kamadak-exif" version = "0.3.1" authors = ["KAMADA Ken'ichi "] description = "Exif parsing library written in pure Rust" homepage = "https://github.com/kamadak/exif-rs" documentation = "https://docs.rs/kamadak-exif" readme = "README" keywords = ["Exif", "JPEG", "parser", "reader", "TIFF"] categories = ["multimedia::encoding", "parser-implementations"] license = "BSD-2-Clause" repository = "https://github.com/kamadak/exif-rs" [lib] name = "exif" kamadak-exif-0.3.1/README010064400234210023421000000015661331145502400131420ustar0000000000000000Exif parsing library written in pure Rust ----------------------------------------- This is a pure-Rust library to parse Exif data. This library can parse TIFF and JPEG images and extract Exif attributes. Usage ----- Add a dependency entry to your Cargo.toml. Specify "kamadak-exif" if you use crates.io. The canonical name of this crate is "exif", but it is renamed on crates.io to avoid a naming conflict. [dependencies] kamadak-exif = "0.3" Add the following to your crate root. extern crate exif; Run "cargo doc" in the source directory to generate the API reference. It is also available online at . See examples directory for sample codes. Dependencies ------------ Rust 1.20 or later is required to build. Specifications -------------- - Exif Version 2.31 - TIFF Revision 6.0 kamadak-exif-0.3.1/examples/dumpexif.rs010064400234210023421000000050521331145502400162610ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // extern crate exif; use std::env; use std::fmt::Write; use std::fs::File; use std::io::BufReader; use std::path::{Path, PathBuf}; fn main() { for path in env::args_os().skip(1).map(PathBuf::from) { if let Err(e) = dump_file(&path) { println!("{}: {}", path.display(), e); } } } fn dump_file(path: &Path) -> Result<(), exif::Error> { let file = File::open(path)?; let reader = exif::Reader::new(&mut BufReader::new(&file))?; println!("{}", path.display()); for f in reader.fields() { let thumb = if f.thumbnail { "1/" } else { "0/" }; println!(" {}{}: {}", thumb, f.tag, f.value.display_as(f.tag)); if let exif::Value::Ascii(ref s) = f.value { println!(" Ascii({:?})", s.iter().map(escape).collect::>()); } else { println!(" {:?}", f.value); } } Ok(()) } fn escape(bytes: &&[u8]) -> String { let mut buf = String::new(); for &c in *bytes { match c { b'\\' | b'"' => write!(buf, "\\{}", c as char).unwrap(), 0x20...0x7e => buf.write_char(c as char).unwrap(), _ => write!(buf, "\\x{:02x}", c).unwrap(), } } buf } kamadak-exif-0.3.1/examples/reading.rs010064400234210023421000000062471317305666600160770ustar0000000000000000// // Copyright (c) 2017 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // extern crate exif; use std::fs::File; use std::io::BufReader; use exif::{DateTime, Reader, Value, Tag}; fn main() { let file = File::open("tests/exif.jpg").unwrap(); let reader = Reader::new(&mut BufReader::new(&file)).unwrap(); // To obtain a string representation, `Value::display_as` can be used // for any tag. let tag_list = [Tag::ExifVersion, Tag::PixelXDimension, Tag::XResolution, Tag::ImageDescription, Tag::DateTime]; for &tag in tag_list.iter() { if let Some(field) = reader.get_field(tag, false) { println!("{}: {}", field.tag, field.value.display_as(field.tag)); } } // To get unsigned integer value(s) from either of BYTE, SHORT, // or LONG, `Value::get_uint` or `Value::iter_uint` can be used. if let Some(field) = reader.get_field(Tag::PixelXDimension, false) { if let Some(width) = field.value.get_uint(0) { println!("Valid width of the image is {}.", width); } } // To convert a Rational or SRational to an f64, `Rational::to_f64` // or `SRational::to_f64` can be used. if let Some(field) = reader.get_field(Tag::XResolution, false) { match field.value { Value::Rational(ref vec) if !vec.is_empty() => println!("X resolution is {}.", vec[0].to_f64()), _ => {}, } } // To parse a DateTime-like field, `DateTime::from_ascii` can be used. if let Some(field) = reader.get_field(Tag::DateTime, false) { match field.value { Value::Ascii(ref vec) if !vec.is_empty() => { if let Ok(datetime) = DateTime::from_ascii(vec[0]) { println!("Year of DateTime is {}.", datetime.year); } }, _ => {}, } } } kamadak-exif-0.3.1/src/endian.rs010064400234210023421000000156571313260417200146640ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::io; use std::mem; // This is a module to select endianess by using generics // in order to avoid run-time dispatching penalty at the cost of // increased object size. pub trait Endian { fn loadu16(buf: &[u8], from: usize) -> u16; fn loadu32(buf: &[u8], from: usize) -> u32; fn loadu64(buf: &[u8], from: usize) -> u64; fn writeu16(w: &mut W, num: u16) -> io::Result<()> where W: io::Write; fn writeu32(w: &mut W, num: u32) -> io::Result<()> where W: io::Write; fn writeu64(w: &mut W, num: u64) -> io::Result<()> where W: io::Write; } pub struct BigEndian; pub struct LittleEndian; macro_rules! generate_load { ($name:ident, $int_type:ident, $from_func:ident) => ( fn $name(buf: &[u8], offset: usize) -> $int_type { // Check if the specified range of the slice is valid // before transmute(). This will also detect the // wrap-around of (offset + size_of) in the release mode. let buf = &buf[offset .. offset + mem::size_of::<$int_type>()]; let ptr = buf.as_ptr() as *const $int_type; let num = unsafe { mem::transmute(*ptr) }; $int_type::$from_func(num) } ) } macro_rules! generate_write { ($name:ident, $int_type:ident, $type_size:expr, $to_func:ident) => ( fn $name(w: &mut W, num: $int_type) -> io::Result<()> where W: io::Write { let buf: [u8; $type_size] = unsafe { mem::transmute(num.$to_func()) }; w.write_all(&buf) } ) } impl Endian for BigEndian { generate_load!(loadu16, u16, from_be); generate_load!(loadu32, u32, from_be); generate_load!(loadu64, u64, from_be); generate_write!(writeu16, u16, 2, to_be); generate_write!(writeu32, u32, 4, to_be); generate_write!(writeu64, u64, 8, to_be); } impl Endian for LittleEndian { generate_load!(loadu16, u16, from_le); generate_load!(loadu32, u32, from_le); generate_load!(loadu64, u64, from_le); generate_write!(writeu16, u16, 2, to_le); generate_write!(writeu32, u32, 4, to_le); generate_write!(writeu64, u64, 8, to_le); } #[cfg(test)] mod tests { use super::*; #[test] fn loadu16() { assert_eq!(BigEndian::loadu16(&[0x01, 0x02], 0), 0x0102); assert_eq!(BigEndian::loadu16(&[0x01, 0x02, 0x03], 1), 0x0203); assert_eq!(LittleEndian::loadu16(&[0x01, 0x02], 0), 0x0201); assert_eq!(LittleEndian::loadu16(&[0x01, 0x02, 0x03], 1), 0x0302); } #[test] fn loadu32() { assert_eq!(BigEndian::loadu32(&[0x01, 0x02, 0x03, 0x04], 0), 0x01020304); assert_eq!(BigEndian::loadu32(&[0x01, 0x02, 0x03, 0x04, 0x05], 1), 0x02030405); assert_eq!(LittleEndian::loadu32(&[0x01, 0x02, 0x03, 0x04], 0), 0x04030201); assert_eq!(LittleEndian::loadu32(&[0x01, 0x02, 0x03, 0x04, 0x05], 1), 0x05040302); } #[test] fn loadu64() { assert_eq!(BigEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0), 0x0102030405060708); assert_eq!(BigEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], 1), 0x0203040506070809); assert_eq!(LittleEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08], 0), 0x0807060504030201); assert_eq!(LittleEndian::loadu64(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09], 1), 0x0908070605040302); } #[test] fn writeu16() { let mut buf = Vec::new(); BigEndian::writeu16(&mut buf, 0x0102).unwrap(); LittleEndian::writeu16(&mut buf, 0x0304).unwrap(); assert_eq!(buf, b"\x01\x02\x04\x03"); } #[test] fn writeu32() { let mut buf = Vec::new(); BigEndian::writeu32(&mut buf, 0x01020304).unwrap(); LittleEndian::writeu32(&mut buf, 0x05060708).unwrap(); assert_eq!(buf, b"\x01\x02\x03\x04\x08\x07\x06\x05"); } #[test] fn writeu64() { let mut buf = Vec::new(); BigEndian::writeu64(&mut buf, 0x0102030405060708).unwrap(); LittleEndian::writeu64(&mut buf, 0x090a0b0c0d0e0f10).unwrap(); assert_eq!(buf, b"\x01\x02\x03\x04\x05\x06\x07\x08\ \x10\x0f\x0e\x0d\x0c\x0b\x0a\x09"); } #[test] fn dispatch() { fn dispatch_sub(data: &[u8]) -> u16 where E: Endian { E::loadu16(data, 0) } assert_eq!(dispatch_sub::(&[0x01, 0x02]), 0x0102); assert_eq!(dispatch_sub::(&[0x01, 0x02]), 0x0201); } #[test] fn static_dispatch() { fn dispatch_sub(data: &[u8]) -> u16 where E: Endian { E::loadu16(data, 0) } assert_eq!(dispatch_sub:: as *const (), dispatch_sub:: as *const ()); assert_ne!(dispatch_sub:: as *const (), dispatch_sub:: as *const ()); } #[test] #[should_panic(expected = "index 3 out of range for slice of length 2")] fn out_of_range() { BigEndian::loadu16(&[0x01, 0x02], 1); } // "attempt to add with overflow" with the arithmetic overflow // check, and "slice index starts at 18446744073709551615 but ends // at 1" without it. #[test] #[should_panic(expected = "at")] fn wrap_around() { BigEndian::loadu16(&[0x01, 0x02], (-1isize) as usize); } } kamadak-exif-0.3.1/src/error.rs010064400234210023421000000066451331145502400145530ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::error; use std::fmt; use std::io; /// An error type returned when parsing Exif data. #[derive(Debug)] pub enum Error { /// Input data was malformed or truncated. InvalidFormat(&'static str), /// Input data could not be read due to an I/O error and /// a `std::io::Error` value is associated with this variant. Io(io::Error), /// Exif attribute information was not found in JPEG data. NotFound(&'static str), /// The value of the field is blank. Some fields have blank values /// whose meanings are defined as "unknown". Such a blank value /// should be treated the same as the absence of the field. BlankValue(&'static str), /// Field values or image data are too big to encode. TooBig(&'static str), /// The field type is not supported and cannnot be encoded. NotSupported(&'static str), } impl From for Error { fn from(err: io::Error) -> Error { Error::Io(err) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Error::InvalidFormat(msg) => f.write_str(msg), Error::Io(ref err) => err.fmt(f), Error::NotFound(msg) => f.write_str(msg), Error::BlankValue(msg) => f.write_str(msg), Error::TooBig(msg) => f.write_str(msg), Error::NotSupported(msg) => f.write_str(msg), } } } impl error::Error for Error { fn description(&self) -> &str { match *self { Error::InvalidFormat(msg) => msg, Error::Io(ref err) => err.description(), Error::NotFound(msg) => msg, Error::BlankValue(msg) => msg, Error::TooBig(msg) => msg, Error::NotSupported(msg) => msg, } } fn cause(&self) -> Option<&error::Error> { match *self { Error::InvalidFormat(_) => None, Error::Io(ref err) => Some(err), Error::NotFound(_) => None, Error::BlankValue(_) => None, Error::TooBig(_) => None, Error::NotSupported(_) => None, } } } kamadak-exif-0.3.1/src/jpeg.rs010064400234210023421000000122571331145502400143430ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::io; use std::io::Read; use error::Error; use util::read8; use util::read16; mod marker { // The first byte of a marker. pub const P: u8 = 0xff; // Marker codes. pub const Z: u8 = 0x00; // Not a marker but a byte stuffing. pub const TEM: u8 = 0x01; pub const RST0: u8 = 0xd0; pub const RST7: u8 = 0xd7; pub const SOI: u8 = 0xd8; pub const EOI: u8 = 0xd9; pub const SOS: u8 = 0xda; pub const APP1: u8 = 0xe1; } // SOI marker as the JPEG header. const JPEG_SIG: [u8; 2] = [marker::P, marker::SOI]; // Exif identifier code "Exif\0\0". [EXIF23 4.7.2] const EXIF_ID: [u8; 6] = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]; /// Get the Exif attribute information segment from a JPEG file. pub fn get_exif_attr(reader: &mut R) -> Result, Error> where R: io::BufRead { match get_exif_attr_sub(reader) { Err(Error::Io(ref e)) if e.kind() == io::ErrorKind::UnexpectedEof => Err(Error::InvalidFormat("Broken JPEG file")), r => r, } } fn get_exif_attr_sub(reader: &mut R) -> Result, Error> where R: io::BufRead { let mut soi = [0u8; 2]; reader.read_exact(&mut soi)?; if soi != [marker::P, marker::SOI] { return Err(Error::InvalidFormat("Not a JPEG file")); } loop { // Find a marker prefix. Discard non-ff bytes, which appear if // we are in the scan data after SOS or we are out of sync. reader.read_until(marker::P, &mut Vec::new())?; // Get a marker code. let mut code; loop { code = read8(reader)?; if code != marker::P { break; } } // Continue or return early on stand-alone markers. match code { marker::Z | marker::TEM | marker::RST0...marker::RST7 => continue, marker::SOI => return Err(Error::InvalidFormat("Unexpected SOI")), marker::EOI => return Err(Error::NotFound("No Exif data found")), _ => {}, } // Read marker segments. let seglen = read16(reader)?; if seglen < 2 { return Err(Error::InvalidFormat("Invalid segment length")); } let mut seg = Vec::new(); reader.by_ref().take(seglen as u64 - 2).read_to_end(&mut seg)?; if code == marker::APP1 && seg.starts_with(&EXIF_ID) { return Ok(seg.split_off(EXIF_ID.len())); } if code == marker::SOS { // Skipping the scan data is handled in the main loop, // so there is nothing to do here. } } } pub fn is_jpeg(buf: &[u8]) -> bool { buf.starts_with(&JPEG_SIG) } #[cfg(test)] mod tests { use std::io::Cursor; use super::*; #[test] fn truncated() { let sets: &[&[u8]] = &[ b"", b"\xff", b"\xff\xd8", b"\xff\xd8\x00", b"\xff\xd8\xff", b"\xff\xd8\xff\xe1\x00\x08\x03\x04", ]; for &data in sets { assert_err_pat!(get_exif_attr(&mut Cursor::new(data)), Error::InvalidFormat("Broken JPEG file")); } } #[test] fn no_exif() { let data = b"\xff\xd8\xff\xd9"; assert_err_pat!(get_exif_attr(&mut Cursor::new(data)), Error::NotFound(_)); } #[test] fn out_of_sync() { let data = b"\xff\xd8\x01\x02\x03\xff\x00\xff\xd9"; assert_err_pat!(get_exif_attr(&mut Cursor::new(data)), Error::NotFound(_)); } #[test] fn empty() { let data = b"\xff\xd8\xff\xe1\x00\x08Exif\0\0\xff\xd9"; assert_ok!(get_exif_attr(&mut Cursor::new(data)), []); } #[test] fn non_empty() { let data = b"\xff\xd8\xff\xe1\x00\x0aExif\0\0\xbe\xad\xff\xd9"; assert_ok!(get_exif_attr(&mut Cursor::new(data)), [0xbe, 0xad]); } } kamadak-exif-0.3.1/src/lib.rs010064400234210023421000000055731331145502400141670ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // //! This is a pure-Rust library to parse Exif data. //! This library can parse TIFF and JPEG images and extract Exif //! attributes. //! //! # Examples //! //! An example to parse JPEG/TIFF files: //! //! ``` //! for path in &["tests/exif.jpg", "tests/exif.tif"] { //! let file = std::fs::File::open(path).unwrap(); //! let reader = exif::Reader::new( //! &mut std::io::BufReader::new(&file)).unwrap(); //! for f in reader.fields() { //! println!("{} {} {}", //! f.tag, f.thumbnail, f.value.display_as(f.tag)); //! } //! } //! ``` //! //! # Compatibility //! //! Major changes between 0.2.3 and 0.3 are listed below. //! //! * Enum Error has two new variants: TooBig and NotSupported. //! * Value::Undefined has the 2nd member to keep the offset of the value. //! * Struct DateTime has two new fields: nanosecond and offset. //! * The tag constants have been changed to associated constants of //! struct `Tag`. Use `Tag::TagName` instead of `tag::TagName`. pub use error::Error; pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg; pub use reader::Reader; pub use tag_priv::{Context, Tag}; pub use tag_priv::constants as tag; pub use tiff::{DateTime, Field}; pub use tiff::parse_exif; pub use value::Value; pub use value::{Rational, SRational}; /// The interfaces in this module are experimental and unstable. pub mod experimental { pub use writer::Writer; } #[cfg(test)] #[macro_use] mod tmacro; mod endian; mod error; mod jpeg; mod reader; #[path = "tag.rs"] mod tag_priv; mod tiff; mod util; mod value; mod writer; kamadak-exif-0.3.1/src/reader.rs010064400234210023421000000146331331145502400146600ustar0000000000000000// // Copyright (c) 2017 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::collections::HashMap; use std::io; use std::io::Read; use std::mem; use error::Error; use jpeg; use tag_priv::Tag; use tiff; use tiff::Field; /// The `Reader` struct reads a JPEG or TIFF image, /// parses the Exif attributes in it, and holds the results. // // The struct Reader is self-contained, which means that it does not // have any external reference. The `fields` field actually refers to // the `buf` field. The type system of the current Rust (as of 1.15) // cannot represent this, so the static lifetime is used to trick it. // // This struct can be moved because the contents of a Vec are allocated // in the heap and do not change their addresses by the move. // // The static lifetime is a lie and it must be kept secret in this struct. // - This struct must not be destructured by the users. // - The `fields` must be adjusted to refer the struct itself when // returned to the outside world. pub struct Reader { // TIFF data. buf: Vec, // Exif fields. fields: Vec>, // True if the TIFF data is little endian. little_endian: bool, // HashMap to find a field quickly. field_map: HashMap<(Tag, bool), &'static Field<'static>>, } impl Reader { /// Reads a JPEG or TIFF image and parses the Exif attributes in it. /// If an error occurred, `exif::Error` is returned. pub fn new(reader: &mut R) -> Result where R: io::BufRead { // Parse the data. let mut buf = Vec::new(); reader.by_ref().take(4).read_to_end(&mut buf)?; if jpeg::is_jpeg(&buf) { let exif_buf = jpeg::get_exif_attr( &mut buf.as_mut_slice().chain(reader))?; buf = exif_buf; } else if tiff::is_tiff(&buf) { reader.read_to_end(&mut buf)?; } else { return Err(Error::InvalidFormat("Unknown image format")); } // Cheat on the type system and erase the lifetime by transmute(). // The scope releases the inner `v` to unborrow `buf`. let (fields, le) = { let (v, le) = tiff::parse_exif(&buf)?; (unsafe { mem::transmute::, Vec>(v) }, le) }; // Initialize the HashMap of all fields. let mut field_map = HashMap::new(); for f in &fields { field_map.insert((f.tag, f.thumbnail), unsafe { mem::transmute::<&Field, &Field>(f) }); } Ok(Reader { buf: buf, fields: fields, little_endian: le, field_map: field_map }) } /// Returns the slice that contains the TIFF data. #[inline] pub fn buf(&self) -> &[u8] { &self.buf[..] } /// Returns a slice of Exif fields. #[inline] pub fn fields<'a>(&'a self) -> &[Field<'a>] { &self.fields } /// Returns true if the TIFF data is in the little-endian byte order. #[inline] pub fn little_endian(&self) -> bool { self.little_endian } /// Returns a reference to the Exif field specified by the tag /// and the thumbnail flag. #[inline] pub fn get_field(&self, tag: Tag, thumbnail: bool) -> Option<&Field> { self.field_map.get(&(tag, thumbnail)).map(|&f| f) } } #[cfg(test)] mod tests { use std::fs::File; use std::io::BufReader; use value::Value; use super::*; static TIFF_ASCII: &'static [u8] = b"MM\0\x2a\0\0\0\x08\0\x01\x01\x0e\0\x02\0\0\0\x04ABC\0\0\0\0\0"; // Test if moving a `Reader` does not invalidate the references in it. // The referer is in the heap and does not move, so this test is not // so meaningful. #[test] fn move_reader() { let r1 = Reader::new(&mut BufReader::new(TIFF_ASCII)).unwrap(); let ptr = &r1 as *const _; move_in_and_drop(r1, ptr); let (r2, ptr) = move_out_and_drop(); assert!(ptr != &r2 as *const _, "not moved"); check_abc(r2.fields()); box_and_drop(); } #[inline(never)] fn move_in_and_drop(r1: Reader, ptr: *const Reader) { assert!(ptr != &r1 as *const _, "not moved"); check_abc(r1.fields()); } #[inline(never)] fn move_out_and_drop() -> (Reader, *const Reader) { let r2 = Reader::new(&mut BufReader::new(TIFF_ASCII)).unwrap(); let ptr = &r2 as *const _; (r2, ptr) } fn box_and_drop() { let r = Reader::new(&mut BufReader::new(TIFF_ASCII)).unwrap(); let ptr = &r as *const _; let b = Box::new(r); assert!(ptr != &*b as *const _, "not moved"); check_abc(b.fields()); } fn check_abc(fields: &[Field]) { if let Value::Ascii(ref v) = fields[0].value { assert_eq!(*v, vec![b"ABC"]); } else { panic!("TIFF ASCII field is expected"); } } #[test] fn get_field() { let file = File::open("tests/exif.jpg").unwrap(); let reader = Reader::new(&mut BufReader::new(&file)).unwrap(); assert_pat!(reader.get_field(Tag::ExifVersion, false).unwrap().value, Value::Undefined(b"0230", _)); } } kamadak-exif-0.3.1/src/tag.rs010064400234210023421000001402761331145502400141740ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::fmt; use value; use value::Value; use util::{atou16, isupper}; /// A tag of a TIFF field. /// /// Some well-known tags are provided as associated constants of /// this type. The constant names follow the Exif specification /// but not the Rust naming conventions. // // This is not an enum to keep safety and API stability, while // supporting unknown tag numbers. This comment is based on the // behavior of Rust 1.12. // Storing unknown values in a repr(u16) enum is unsafe. The compiler // assumes that there is no undefined discriminant even with a C-like // enum, so the exhaustiveness check of a match expression will break. // Storing unknown values in a special variant such as Unknown(u16) // tends to break backward compatibility. When Tag::VariantFoo is // defined in a new version of the library, the old codes using // Tag::Unknown(Foo's tag number) will break. // // Use of constants is restricted in patterns. As of Rust 1.12, // PartialEq and Eq need to be _automatically derived_ for Tag to // emulate structural equivalency. // #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Tag(pub Context, pub u16); impl Tag { /// Returns the context of the tag. /// /// # Examples /// ``` /// use exif::{Context, Tag}; /// assert_eq!(Tag::DateTime.context(), Context::Tiff); /// assert_eq!(Tag::ExposureTime.context(), Context::Exif); /// ``` #[inline] pub fn context(self) -> Context { self.0 } /// Returns the tag number. /// /// # Examples /// ``` /// use exif::Tag; /// assert_eq!(Tag::DateTime.number(), 0x132); /// ``` #[inline] pub fn number(self) -> u16 { self.1 } /// Returns the description of the tag. #[inline] pub fn description(&self) -> Option<&str> { get_tag_info(*self).map(|ti| ti.desc) } /// Returns the default value of the tag. `None` is returned if /// it is not defined in the standard or it depends on the context. #[inline] pub fn default_value(&self) -> Option { get_tag_info(*self).and_then(|ti| (&ti.default).into()) } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match get_tag_info(*self) { Some(ti) => f.pad(ti.name), None => f.pad(&format!("{:?}", self)), } } } /// An enum that indicates how a tag number is interpreted. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Context { /// TIFF attributes defined in the TIFF Rev. 6.0 specification. Tiff, // 0th/1st IFD /// Exif attributes. Exif, // 0th/1st IFD -- Exif IFD /// GPS attributes. Gps, // 0th/1st IFD -- GPS IFD /// Interoperability attributes. Interop, // 0th/1st IFD -- Exif IFD -- Interoperability IFD } macro_rules! generate_well_known_tag_constants { ( $( |$ctx:path| $( // Copy the doc attribute to the actual definition. $( #[$attr:meta] )* ($name:ident, $num:expr, $defval:expr, $dispval:ident, $desc:expr) ),+, )+ ) => ( /// A module that contains Exif tag constants. /// /// Compatibility warning: Exif tag constants in this module will be /// converted to associated constants of `Tag` when the feature is /// stabilized. /// /// It is not recommended to import the constants directly into /// your namespace; import the module and use with the module name /// like `tag::DateTime`. The constant names follow the Exif /// specification but not the Rust naming conventions, and a user /// of the constants will get the non_upper_case_globals warning /// if a bare constant is used in a match arm. // This is discussed in // . pub mod constants { use super::{Context, Tag}; $($( $( #[$attr] )* #[allow(non_upper_case_globals)] #[deprecated(since = "0.3.0", note = "use `Tag::TagName` instead of `tag::TagName`")] pub const $name: Tag = Tag($ctx, $num); )+)+ } impl Tag { $($( $( #[$attr] )* #[allow(non_upper_case_globals)] pub const $name: Tag = Tag($ctx, $num); )+)+ } // Use a separate module to avoid name conflicts between // const Tag and static TagInfo. mod tag_info { use std::fmt; use value::Value; use value::DefaultValue; pub struct TagInfo { pub name: &'static str, pub desc: &'static str, pub default: DefaultValue, pub dispval: fn(&mut fmt::Write, &Value) -> fmt::Result, } $($( #[allow(non_upper_case_globals)] pub static $name: TagInfo = TagInfo { name: stringify!($name), desc: $desc, default: $defval, dispval: super::$dispval, }; )+)+ } fn get_tag_info(tag: Tag) -> Option<&'static tag_info::TagInfo> { match tag { $($( Tag::$name => Some(&tag_info::$name), )+)+ _ => None, } } ) } // Tag constant names do not follow the Rust naming conventions but // the Exif field names: camel cases and all-capital acronyms. generate_well_known_tag_constants!( // Exif-specific IFDs [EXIF23 4.6.3]. |Context::Tiff| /// A pointer to the Exif IFD. This is used for the internal structure /// of Exif data and will not be returned to the user. (ExifIFDPointer, 0x8769, DefaultValue::None, d_default, "Exif IFD pointer"), /// A pointer to the GPS IFD. This is used for the internal structure /// of Exif data and will not be returned to the user. (GPSInfoIFDPointer, 0x8825, DefaultValue::None, d_default, "GPS Info IFD pointer"), |Context::Exif| /// A pointer to the interoperability IFD. This is used for the internal /// structure of Exif data and will not be returned to the user. (InteropIFDPointer, 0xa005, DefaultValue::None, d_default, "Interoperability IFD pointer"), // TIFF primary and thumbnail attributes [EXIF23 4.6.4 Table 4, // 4.6.8 Table 17, and 4.6.8 Table 21]. |Context::Tiff| (ImageWidth, 0x100, DefaultValue::None, d_default, "Image width"), (ImageLength, 0x101, DefaultValue::None, d_default, "Image height"), (BitsPerSample, 0x102, DefaultValue::Short(&[8, 8, 8]), d_default, "Number of bits per component"), (Compression, 0x103, DefaultValue::None, d_compression, "Compression scheme"), (PhotometricInterpretation, 0x106, DefaultValue::None, d_photointp, "Pixel composition"), (ImageDescription, 0x10e, DefaultValue::None, d_default, "Image title"), (Make, 0x10f, DefaultValue::None, d_default, "Manufacturer of image input equipment"), (Model, 0x110, DefaultValue::None, d_default, "Model of image input equipment"), (StripOffsets, 0x111, DefaultValue::None, d_default, "Image data location"), (Orientation, 0x112, DefaultValue::Short(&[1]), d_orientation, "Orientation of image"), (SamplesPerPixel, 0x115, DefaultValue::Short(&[3]), d_default, "Number of components"), (RowsPerStrip, 0x116, DefaultValue::None, d_default, "Number of rows per strip"), (StripByteCounts, 0x117, DefaultValue::None, d_default, "Bytes per compressed strip"), (XResolution, 0x11a, DefaultValue::Rational(&[(72, 1)]), d_decimal, "Image resolution in width direction"), (YResolution, 0x11b, DefaultValue::Rational(&[(72, 1)]), d_decimal, "Image resolution in height direction"), (PlanarConfiguration, 0x11c, DefaultValue::Short(&[1]), d_planarcfg, "Image data arrangement"), (ResolutionUnit, 0x128, DefaultValue::Short(&[2]), d_resunit, "Unit of X and Y resolution"), (TransferFunction, 0x12d, DefaultValue::None, d_default, "Transfer function"), (Software, 0x131, DefaultValue::None, d_default, "Software used"), (DateTime, 0x132, DefaultValue::None, d_datetime, "File change date and time"), (Artist, 0x13b, DefaultValue::None, d_default, "Person who created the image"), (WhitePoint, 0x13e, DefaultValue::None, d_decimal, "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, "Bytes of JPEG data"), (YCbCrCoefficients, 0x211, DefaultValue::Unspecified, d_decimal, "Color space transformation matrix coefficients"), (YCbCrSubSampling, 0x212, DefaultValue::None, d_ycbcrsubsamp, "Subsampling ratio of Y to C"), (YCbCrPositioning, 0x213, DefaultValue::Short(&[1]), d_ycbcrpos, "Y and C positioning"), (ReferenceBlackWhite, 0x214, DefaultValue::ContextDependent, d_decimal, "Pair of black and white reference values"), (Copyright, 0x8298, DefaultValue::None, d_default, "Copyright holder"), // Exif IFD attributes [EXIF23 4.6.5 Table 7 and 4.6.8 Table 18]. |Context::Exif| (ExposureTime, 0x829a, DefaultValue::None, d_exptime, "Exposure time"), (FNumber, 0x829d, DefaultValue::None, d_fnumber, "F number"), (ExposureProgram, 0x8822, DefaultValue::None, d_expprog, "Exposure program"), (SpectralSensitivity, 0x8824, DefaultValue::None, d_default, "Spectral sensitivity"), (PhotographicSensitivity, 0x8827, DefaultValue::None, d_default, "Photographic sensitivity"), (OECF, 0x8828, DefaultValue::None, d_default, "Optoelectric conversion factor"), (SensitivityType, 0x8830, DefaultValue::None, d_sensitivitytype, "Sensitivity type"), (StandardOutputSensitivity, 0x8831, DefaultValue::None, d_default, "Standard output sensitivity"), (RecommendedExposureIndex, 0x8832, DefaultValue::None, d_default, "Recommended exposure index"), (ISOSpeed, 0x8833, DefaultValue::None, d_default, "ISO speed"), (ISOSpeedLatitudeyyy, 0x8834, DefaultValue::None, d_default, "ISO speed latitude yyy"), (ISOSpeedLatitudezzz, 0x8835, DefaultValue::None, d_default, "ISO speed latitude zzz"), // The absence of this field means non-conformance to Exif, so the default // value specified in the standard (e.g., "0231") should not apply. (ExifVersion, 0x9000, DefaultValue::None, d_exifver, "Exif version"), (DateTimeOriginal, 0x9003, DefaultValue::None, d_datetime, "Date and time of original data generation"), (DateTimeDigitized, 0x9004, DefaultValue::None, d_datetime, "Date and time of digital data generation"), (OffsetTime, 0x9010, DefaultValue::None, d_default, "Offset data of DateTime"), (OffsetTimeOriginal, 0x9011, DefaultValue::None, d_default, "Offset data of DateTimeOriginal"), (OffsetTimeDigitized, 0x9012, DefaultValue::None, d_default, "Offset data of DateTimeDigitized"), (ComponentsConfiguration, 0x9101, DefaultValue::ContextDependent, d_cpntcfg, "Meaning of each component"), (CompressedBitsPerPixel, 0x9102, DefaultValue::None, d_decimal, "Image compression mode"), (ShutterSpeedValue, 0x9201, DefaultValue::None, d_decimal, "Shutter speed"), (ApertureValue, 0x9202, DefaultValue::None, d_decimal, "Aperture"), (BrightnessValue, 0x9203, DefaultValue::None, d_decimal, "Brightness"), (ExposureBiasValue, 0x9204, DefaultValue::None, d_decimal, "Exposure bias"), (MaxApertureValue, 0x9205, DefaultValue::None, d_decimal, "Maximum lens aperture"), (SubjectDistance, 0x9206, DefaultValue::None, d_subjdist, "Subject distance"), (MeteringMode, 0x9207, DefaultValue::Short(&[0]), d_metering, "Metering mode"), (LightSource, 0x9208, DefaultValue::Short(&[0]), d_lightsrc, "Light source"), (Flash, 0x9209, DefaultValue::Unspecified, d_flash, "Flash"), (FocalLength, 0x920a, DefaultValue::None, d_decimal, "Lens focal length"), (SubjectArea, 0x9214, DefaultValue::None, d_subjarea, "Subject area"), (MakerNote, 0x927c, DefaultValue::None, d_default, "Manufacturer notes"), (UserComment, 0x9286, DefaultValue::None, d_default, "User comments"), (SubSecTime, 0x9290, DefaultValue::None, d_default, "DateTime subseconds"), (SubSecTimeOriginal, 0x9291, DefaultValue::None, d_default, "DateTimeOriginal subseconds"), (SubSecTimeDigitized, 0x9292, DefaultValue::None, d_default, "DateTimeDigitized subseconds"), (Temperature, 0x9400, DefaultValue::None, d_optdecimal, "Temperature"), (Humidity, 0x9401, DefaultValue::None, d_optdecimal, "Humidity"), (Pressure, 0x9402, DefaultValue::None, d_optdecimal, "Pressure"), (WaterDepth, 0x9403, DefaultValue::None, d_optdecimal, "Water depth"), (Acceleration, 0x9404, DefaultValue::None, d_optdecimal, "Acceleration"), (CameraElevationAngle, 0x9405, DefaultValue::None, d_optdecimal, "Camera elevation angle"), (FlashpixVersion, 0xa000, DefaultValue::Undefined(b"0100"), d_exifver, "Supported Flashpix version"), (ColorSpace, 0xa001, DefaultValue::Unspecified, d_cspace, "Color space information"), (PixelXDimension, 0xa002, DefaultValue::None, d_default, "Valid image width"), (PixelYDimension, 0xa003, DefaultValue::Unspecified, d_default, "Valid image height"), (RelatedSoundFile, 0xa004, DefaultValue::None, d_default, "Related audio file"), (FlashEnergy, 0xa20b, DefaultValue::None, d_decimal, "Flash energy"), (SpatialFrequencyResponse, 0xa20c, DefaultValue::None, d_default, "Spatial frequency response"), (FocalPlaneXResolution, 0xa20e, DefaultValue::None, d_decimal, "Focal plane X resolution"), (FocalPlaneYResolution, 0xa20f, DefaultValue::None, d_decimal, "Focal plane Y resolution"), (FocalPlaneResolutionUnit, 0xa210, DefaultValue::Short(&[2]), d_resunit, "Focal plane resolution unit"), (SubjectLocation, 0xa214, DefaultValue::None, d_subjarea, "Subject location"), (ExposureIndex, 0xa215, DefaultValue::None, d_decimal, "Exposure index"), (SensingMethod, 0xa217, DefaultValue::None, d_sensingmethod, "Sensing method"), (FileSource, 0xa300, DefaultValue::Undefined(&[3]), d_filesrc, "File source"), (SceneType, 0xa301, DefaultValue::Undefined(&[1]), d_scenetype, "Scene type"), (CFAPattern, 0xa302, DefaultValue::None, d_default, "CFA pattern"), (CustomRendered, 0xa401, DefaultValue::Short(&[0]), d_customrendered, "Custom image processing"), (ExposureMode, 0xa402, DefaultValue::None, d_expmode, "Exposure mode"), (WhiteBalance, 0xa403, DefaultValue::None, d_whitebalance, "White balance"), (DigitalZoomRatio, 0xa404, DefaultValue::None, d_dzoomratio, "Digital zoom ratio"), (FocalLengthIn35mmFilm, 0xa405, DefaultValue::None, d_focallen35, "Focal length in 35 mm film"), (SceneCaptureType, 0xa406, DefaultValue::Short(&[0]), d_scenecaptype, "Scene capture type"), (GainControl, 0xa407, DefaultValue::None, d_gainctrl, "Gain control"), (Contrast, 0xa408, DefaultValue::Short(&[0]), d_contrast, "Contrast"), (Saturation, 0xa409, DefaultValue::Short(&[0]), d_saturation, "Saturation"), (Sharpness, 0xa40a, DefaultValue::Short(&[0]), d_sharpness, "Sharpness"), (DeviceSettingDescription, 0xa40b, DefaultValue::None, d_default, "Device settings description"), (SubjectDistanceRange, 0xa40c, DefaultValue::None, d_subjdistrange, "Subject distance range"), (ImageUniqueID, 0xa420, DefaultValue::None, d_default, "Unique image ID"), (CameraOwnerName, 0xa430, DefaultValue::None, d_default, "Camera owner name"), (BodySerialNumber, 0xa431, DefaultValue::None, d_default, "Body serial number"), (LensSpecification, 0xa432, DefaultValue::None, d_lensspec, "Lens specification"), (LensMake, 0xa433, DefaultValue::None, d_default, "Lens make"), (LensModel, 0xa434, DefaultValue::None, d_default, "Lens model"), (LensSerialNumber, 0xa435, DefaultValue::None, d_default, "Lens serial number"), (Gamma, 0xa500, DefaultValue::None, d_decimal, "Gamma"), // GPS attributes [EXIF23 4.6.6 Table 15 and 4.6.8 Table 19]. |Context::Gps| // Depends on the Exif version. (GPSVersionID, 0x0, DefaultValue::ContextDependent, d_gpsver, "GPS tag version"), (GPSLatitudeRef, 0x1, DefaultValue::None, d_gpslatlongref, "North or south latitude"), (GPSLatitude, 0x2, DefaultValue::None, d_gpsdms, "Latitude"), (GPSLongitudeRef, 0x3, DefaultValue::None, d_gpslatlongref, "East or West Longitude"), (GPSLongitude, 0x4, DefaultValue::None, d_gpsdms, "Longitude"), (GPSAltitudeRef, 0x5, DefaultValue::Byte(&[0]), d_gpsaltref, "Altitude reference"), (GPSAltitude, 0x6, DefaultValue::None, d_decimal, "Altitude"), (GPSTimeStamp, 0x7, DefaultValue::None, d_gpstimestamp, "GPS time (atomic clock)"), (GPSSatellites, 0x8, DefaultValue::None, d_default, "GPS satellites used for measurement"), (GPSStatus, 0x9, DefaultValue::None, d_gpsstatus, "GPS receiver status"), (GPSMeasureMode, 0xa, DefaultValue::None, d_gpsmeasuremode, "GPS measurement mode"), (GPSDOP, 0xb, DefaultValue::None, d_decimal, "Measurement precision"), (GPSSpeedRef, 0xc, DefaultValue::Ascii(&[b"K"]), d_gpsspeedref, "Speed unit"), (GPSSpeed, 0xd, DefaultValue::None, d_decimal, "Speed of GPS receiver"), (GPSTrackRef, 0xe, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, "Reference for direction of movement"), (GPSTrack, 0xf, DefaultValue::None, d_decimal, "Direction of movement"), (GPSImgDirectionRef, 0x10, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, "Reference for direction of image"), (GPSImgDirection, 0x11, DefaultValue::None, d_decimal, "Direction of image"), (GPSMapDatum, 0x12, DefaultValue::None, d_default, "Geodetic survey data used"), (GPSDestLatitudeRef, 0x13, DefaultValue::None, d_gpslatlongref, "Reference for latitude of destination"), (GPSDestLatitude, 0x14, DefaultValue::None, d_gpsdms, "Latitude of destination"), (GPSDestLongitudeRef, 0x15, DefaultValue::None, d_gpslatlongref, "Reference for longitude of destination"), (GPSDestLongitude, 0x16, DefaultValue::None, d_gpsdms, "Longitude of destination"), (GPSDestBearingRef, 0x17, DefaultValue::Ascii(&[b"T"]), d_gpsdirref, "Reference for bearing of destination"), (GPSDestBearing, 0x18, DefaultValue::None, d_decimal, "Bearing of destination"), (GPSDestDistanceRef, 0x19, DefaultValue::Ascii(&[b"K"]), d_gpsdistref, "Reference for distance to destination"), (GPSDestDistance, 0x1a, DefaultValue::None, d_decimal, "Distance to destination"), (GPSProcessingMethod, 0x1b, DefaultValue::None, d_ascii_in_undef, "Name of GPS processing method"), (GPSAreaInformation, 0x1c, DefaultValue::None, d_default, "Name of GPS area"), (GPSDateStamp, 0x1d, DefaultValue::None, d_gpsdatestamp, "GPS date"), (GPSDifferential, 0x1e, DefaultValue::None, d_gpsdifferential, "GPS differential correction"), (GPSHPositioningError, 0x1f, DefaultValue::None, d_decimal, "Horizontal positioning error"), // Interoperability attributes [EXIF23 4.6.7 Table 16 and 4.6.8 Table 20]. |Context::Interop| (InteroperabilityIndex, 0x1, DefaultValue::None, d_default, "Interoperability identification"), ); // For Value::display_as(). pub fn display_value_as<'a>(value: &'a Value, tag: Tag) -> value::Display<'a> { match get_tag_info(tag) { Some(ti) => value::Display { fmt: ti.dispval, value: value }, None => value::Display { fmt: d_default, value: value }, } } // Compression (TIFF 0x103) fn d_compression(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "uncompressed", Some(2) => "Modified Huffman", Some(6) => "JPEG", Some(32773) => "PackBits", _ => return d_unknown(w, value, "unknown compression "), }; w.write_str(s) } // PhotometricInterpretation (TIFF 0x106) fn d_photointp(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "white is zero", Some(1) => "black is zero", Some(2) => "RGB", Some(3) => "palette color", Some(4) => "transparency mask", Some(6) => "YCbCr", _ => return d_unknown(w, value, "unknown photometric interpretation "), }; w.write_str(s) } // Orientation (TIFF 0x112) fn d_orientation(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "row 0 at top and column 0 at left", Some(2) => "row 0 at top and column 0 at right", Some(3) => "row 0 at bottom and column 0 at right", Some(4) => "row 0 at bottom and column 0 at left", Some(5) => "row 0 at left and column 0 at top", Some(6) => "row 0 at right and column 0 at top", Some(7) => "row 0 at right and column 0 at bottom", Some(8) => "row 0 at left and column 0 at bottom", _ => return d_unknown(w, value, "unknown orientation "), }; w.write_str(s) } // PlanarConfiguration (TIFF 0x11c) fn d_planarcfg(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "chunky", Some(2) => "planar", _ => return d_unknown(w, value, "unknown planar configuration "), }; w.write_str(s) } // ResolutionUnit (TIFF 0x128) fn d_resunit(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "no absolute unit", Some(2) => "pixels per inch", Some(3) => "pixels per centimeter", _ => return d_unknown(w, value, "unknown unit "), }; w.write_str(s) } // DateTime (TIFF 0x132), DateTimeOriginal (Exif 0x9003), and // DateTimeDigitized (Exif 0x9004) fn d_datetime(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Ascii(ref v) = *value { if let Some(dt) = v.first() { if let Ok(dt) = ::tiff::DateTime::from_ascii(dt) { return write!(w, "{}", dt) } } } d_default(w, value) } // YCbCrSubSampling (TIFF 0x212) fn d_ycbcrsubsamp(w: &mut fmt::Write, value: &Value) -> fmt::Result { let horiz = value.get_uint(0).unwrap_or(0); let vert = value.get_uint(1).unwrap_or(0); let s = match (horiz, vert) { (1, 1) => "full horizontally, full vertically (4:4:4)", (1, 2) => "full horizontally, half vertically", (1, 4) => "full horizontally, quarter vertically", (2, 1) => "half horizontally, full vertically (4:2:2)", (2, 2) => "half horizontally, half vertically (4:2:0)", (2, 4) => "half horizontally, quarter vertically", (4, 1) => "quarter horizontally, full vertically (4:1:1)", (4, 2) => "quarter horizontally, half vertically", (4, 4) => "quarter horizontally, quarter vertically", _ => return d_default(w, value), }; w.write_str(s) } // YCbCrPositioning (TIFF 0x213) fn d_ycbcrpos(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "centered", Some(2) => "co-sited", _ => return d_unknown(w, value, "unknown YCbCr positioning "), }; w.write_str(s) } // ExposureTime (Exif 0x829a) fn d_exptime(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Rational(ref v) = *value { if let Some(et) = v.first() { if et.num >= et.denom { return write!(w, "{}", et.to_f64()); } else if et.num != 0 { return write!(w, "1/{}", et.denom as f64 / et.num as f64); } } } d_default(w, value) } // FNumber (Exif 0x829d) fn d_fnumber(w: &mut fmt::Write, value: &Value) -> fmt::Result { w.write_str("f/")?; d_decimal(w, value) } // ExposureProgram (Exif 0x8822) fn d_expprog(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "manual", Some(2) => "normal program", Some(3) => "aperture priority", Some(4) => "shutter priority", Some(5) => "creative program", Some(6) => "action program", Some(7) => "portrait mode", Some(8) => "landscape mode", _ => return d_unknown(w, value, "unknown exposure program "), }; w.write_str(s) } // SensitivityType (Exif 0x8830) fn d_sensitivitytype(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "SOS", Some(2) => "REI", Some(3) => "ISO speed", Some(4) => "SOS/REI", Some(5) => "SOS/ISO speed", Some(6) => "REI/ISO speed", Some(7) => "SOS/REI/ISO speed", _ => return d_unknown(w, value, "unknown sensitivity type "), }; w.write_str(s) } // ExifVersion (Exif 0x9000), FlashpixVersion (Exif 0xa000) fn d_exifver(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Undefined(u, _) = *value { if u.len() == 4 { if let Ok(major) = atou16(&u[0..2]) { if let Ok(minor) = atou16(&u[2..4]) { if minor % 10 == 0 { return write!(w, "{}.{}", major, minor / 10); } else { return write!(w, "{}.{:02}", major, minor); } } } } } d_default(w, value) } // ComponentsConfiguration (Exif 0x9101) fn d_cpntcfg(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Undefined(u, _) = *value { for &x in u { match x { 0 => w.write_char('_'), 1 => w.write_char('Y'), 2 => w.write_str("Cb"), 3 => w.write_str("Cr"), 4 => w.write_char('R'), 5 => w.write_char('G'), 6 => w.write_char('B'), _ => w.write_char('?'), }?; } return Ok(()); } d_default(w, value) } // SubjectDistance (Exif 0x9206) fn d_subjdist(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Rational(ref v) = *value { if let Some(dist) = v.first() { if dist.num == 0 { return w.write_str("unknown"); } else if dist.num == 0xffffffff { return w.write_str("infinity"); } } } d_decimal(w, value) } // MeteringMode (Exif 0x9207) fn d_metering(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "average", Some(2) => "center-weighted average", Some(3) => "spot", Some(4) => "multi-spot", Some(5) => "pattern", Some(6) => "partial", Some(255) => "other", _ => return d_unknown(w, value, "unknown metering mode "), }; w.write_str(s) } // LightSource (Exif 0x9208) fn d_lightsrc(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "daylight", Some(2) => "fluorescent", Some(3) => "tungsten", Some(4) => "flash", Some(9) => "fine weather", Some(10) => "cloudy weather", Some(11) => "shade", Some(12) => "daylight fluorescent (D 5700-7100K)", Some(13) => "day white fluorescent (N 4600-5500K)", Some(14) => "cool white fluorescent (W 3800-4500K)", Some(15) => "white fluorescent (WW 3250-3800K)", Some(16) => "warm white fluorescent (L 2600-3250K)", Some(17) => "standard light A", Some(18) => "standard light B", Some(19) => "standard light C", Some(20) => "D55", Some(21) => "D65", Some(22) => "D75", Some(23) => "D50", Some(24) => "ISO studio tungsten", Some(255) => "other", _ => return d_unknown(w, value, "unknown light source "), }; w.write_str(s) } // Flash (Exif 0x9209) fn d_flash(w: &mut fmt::Write, value: &Value) -> fmt::Result { const FIRED: &[&str] = &["not fired", "fired"]; const RETURN: &[&str] = &[ ", no return light detection function", ", return light status 1 (reserved)", ", return light not detected", ", return light detected", ]; const AUTO: &[&str] = &[ ", auto mode 0 (unknown)", ", forced", ", suppressed", ", auto"]; const FUNCTION: &[&str] = &["", ", no function present"]; const RED_EYE: &[&str] = &["", ", red-eye reduction"]; if let Some(v) = value.get_uint(0) { write!(w, "{}{}{}{}{}{}", FIRED[v as usize & 1], RETURN[v as usize >> 1 & 3], AUTO[v as usize >> 3 & 3], FUNCTION[v as usize >> 5 & 1], RED_EYE[v as usize >> 6 & 1], if v >> 7 != 0 { ", unknown MSB bits" } else { "" }) } else { d_default(w, value) } } // SubjectArea (Exif 0x9214), SubjectLocation (Exif 0xa214) // Only (x, y) case is valid for SubjectLocation. fn d_subjarea(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Some(x) = value.get_uint(0) { if let Some(y) = value.get_uint(1) { if let Some(d) = value.get_uint(2) { if let Some(h) = value.get_uint(3) { return write!(w, "rectangle (x={}, y={}, w={}, h={})", x, y, d, h); } return write!(w, "circle (x={}, y={}, d={})", x, y, d); } return write!(w, "point (x={}, y={})", x, y); } } d_default(w, value) } // Rational/SRational with 0xffffffff being unknown. // Temperature (Exif 0x9400), Humidity (Exif 0x9401), // Pressure (Exif 0x9402), WaterDepth (Exif 0x9403), // Acceleration (Exif 0x9404), CameraElevationAngle (Exif 0x9405) fn d_optdecimal(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) if v.len() > 0 => if v[0].denom != 0xffffffff { write!(w, "{}", v[0].to_f64()) } else { w.write_str("unknown") }, Value::SRational(ref v) if v.len() > 0 => if v[0].denom != -1 { write!(w, "{}", v[0].to_f64()) } else { w.write_str("unknown") }, _ => d_decimal(w, value), } } // ColorSpace (Exif 0xa001) fn d_cspace(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "sRGB", Some(0xffff) => "uncalibrated", _ => return d_unknown(w, value, "unknown color space "), }; w.write_str(s) } // SensingMethod (Exif 0xa217) fn d_sensingmethod(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "not defined", Some(2) => "one-chip color area sensor", Some(3) => "two-chip color area sensor", Some(4) => "three-chip color area sensor", Some(5) => "color sequential area sensor", Some(7) => "trilinear sensor", Some(8) => "color sequential linear sensor", _ => return d_unknown(w, value, "unknown sensing method "), }; w.write_str(s) } // FileSource (Exif 0xa300) fn d_filesrc(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Undefined(s, _) => s.first().map(|&x| x), _ => None, } { Some(0) => "others", Some(1) => "transparency scanner", Some(2) => "reflective scanner", Some(3) => "DSC", _ => return d_unknown(w, value, "unknown file source "), }; w.write_str(s) } // SceneType (Exif 0xa301) fn d_scenetype(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Undefined(s, _) => s.first().map(|&x| x), _ => None, } { Some(1) => "directly photographed image", _ => return d_unknown(w, value, "unknown scene type "), }; w.write_str(s) } // CustomRendered (Exif 0xa401) fn d_customrendered(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal process", Some(1) => "custom process", _ => return d_unknown(w, value, "unknown custom rendered "), }; w.write_str(s) } // ExposureMode (Exif 0xa402) fn d_expmode(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "auto exposure", Some(1) => "manual exposure", Some(2) => "auto bracket", _ => return d_unknown(w, value, "unknown exposure mode "), }; w.write_str(s) } // WhiteBalance (Exif 0xa403) fn d_whitebalance(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "auto white balance", Some(1) => "manual white balance", _ => return d_unknown(w, value, "unknown white balance mode "), }; w.write_str(s) } // DigitalZoomRatio (Exif 0xa404) fn d_dzoomratio(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Rational(ref v) = *value { if v.len() > 0 && v[0].num == 0 { return w.write_str("unused"); } } d_decimal(w, value) } // FocalLengthIn35mmFilm (Exif 0xa405) fn d_focallen35(w: &mut fmt::Write, value: &Value) -> fmt::Result { match value.get_uint(0) { Some(0) => w.write_str("unknown"), _ => d_default(w, value), } } // SceneCaptureType (Exif 0xa406) fn d_scenecaptype(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "standard", Some(1) => "landscape", Some(2) => "portrait", Some(3) => "night scene", _ => return d_unknown(w, value, "unknown scene capture type "), }; w.write_str(s) } // GainControl (Exif 0xa407) fn d_gainctrl(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "none", Some(1) => "low gain up", Some(2) => "high gain up", Some(3) => "low gain down", Some(4) => "high gain down", _ => return d_unknown(w, value, "unknown gain control "), }; w.write_str(s) } // Contrast (Exif 0xa408) fn d_contrast(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal", Some(1) => "soft", Some(2) => "hard", _ => return d_unknown(w, value, "unknown contrast processing "), }; w.write_str(s) } // Saturation (Exif 0xa409) fn d_saturation(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal", Some(1) => "low saturation", Some(2) => "high saturation", _ => return d_unknown(w, value, "unknown saturation processing "), }; w.write_str(s) } // Sharpness (Exif 0xa40a) fn d_sharpness(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "normal", Some(1) => "soft", Some(2) => "hard", _ => return d_unknown(w, value, "unknown sharpness processing "), }; w.write_str(s) } // SubjectDistanceRange (Exif 0xa40c) fn d_subjdistrange(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(1) => "macro", Some(2) => "close view", Some(3) => "distant view", _ => return d_unknown(w, value, "unknown subject distance range "), }; w.write_str(s) } // LensSpecification (Exif 0xa432) fn d_lensspec(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) if v.len() >= 4 => // There are several notations: "F1.4" in Japan, "f/1.4" // in the U.S., and so on. write!(w, "{}-{} mm, f/{}-{}", v[0].to_f64(), v[1].to_f64(), v[2].to_f64(), v[3].to_f64()), _ => d_default(w, value), } } // GPSVersionID (Exif/GPS 0x0) fn d_gpsver(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Byte(ref v) if v.len() >= 4 => write!(w, "{}.{}.{}.{}", v[0], v[1], v[2], v[3]), _ => d_default(w, value), } } // GPSLatitudeRef (Exif/GPS 0x1), GPSLongitudeRef (Exif/GPS 0x3) // GPSDestLatitudeRef (Exif/GPS 0x13), GPSDestLongitudeRef (Exif/GPS 0x15) fn d_gpslatlongref(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Ascii(ref v) if (v.len() == 1 && v[0].len() == 1 && isupper(v[0][0])) => w.write_char(v[0][0] as char), _ => d_default(w, value), } } // GPSLatitude (Exif/GPS 0x2), GPSLongitude (Exif/GPS 0x4), // GPSDestLatitude (Exif/GPS 0x14), GPSDestLongitude (Exif/GPS 0x16) fn d_gpsdms(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) if v.len() >= 3 => write!(w, "{} deg {} min {} sec", v[0].to_f64(), v[1].to_f64(), v[2].to_f64()), _ => d_default(w, value), } } // GPSAltitudeRef (Exif/GPS 0x5) fn d_gpsaltref(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "above sea level", Some(1) => "below sea level", _ => return d_unknown(w, value, "unknown GPS altitude ref "), }; w.write_str(s) } // GPSTimeStamp (Exif/GPS 0x7) fn d_gpstimestamp(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) if v.len() >= 3 => { let (h, m, s) = (v[0].to_f64(), v[1].to_f64(), v[2].to_f64()); write!(w, "{}{}:{}{}:{}{}", if h < 10.0 { "0" } else { "" }, h, if m < 10.0 { "0" } else { "" }, m, if s < 10.0 { "0" } else { "" }, s) }, _ => d_default(w, value), } } // GPSStatus (Exif/GPS 0x9) fn d_gpsstatus(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Ascii(ref v) => v.first().map(|&x| x), _ => None, } { Some(b"A") => "measurement in progress", Some(b"V") => "measurement interrupted", _ => return d_unknown(w, value, "unknown GPS status "), }; w.write_str(s) } // GPSMeasure (Exif/GPS 0xa) fn d_gpsmeasuremode(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Ascii(ref v) => v.first().map(|&x| x), _ => None, } { Some(b"2") => "2-dimensional measurement", Some(b"3") => "3-dimensional measurement", _ => return d_unknown(w, value, "unknown GPS measurement mode "), }; w.write_str(s) } // GPSSpeedRef (Exif/GPS 0xc) fn d_gpsspeedref(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Ascii(ref v) => v.first().map(|&x| x), _ => None, } { Some(b"K") => "km/h", Some(b"M") => "mph", Some(b"N") => "knots", _ => return d_unknown(w, value, "unknown GPS speed ref "), }; w.write_str(s) } // GPSTrackRef (Exif/GPS 0xe), GPSImgDirectionRef (Exif/GPS 0x10), // GPSDestBearingRef (Exif/GPS 0x17) fn d_gpsdirref(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Ascii(ref v) => v.first().map(|&x| x), _ => None, } { Some(b"T") => "true direction", Some(b"M") => "magnetic direction", _ => return d_unknown(w, value, "unknown GPS direction ref "), }; w.write_str(s) } // GPSDestDistanceRef (Exif/GPS 0x19) fn d_gpsdistref(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match match *value { Value::Ascii(ref v) => v.first().map(|&x| x), _ => None, } { Some(b"K") => "km", Some(b"M") => "miles", Some(b"N") => "nautical miles", _ => return d_unknown(w, value, "unknown GPS distance ref "), }; w.write_str(s) } // GPSDateStamp (Exif/GPS 0x1d) fn d_gpsdatestamp(w: &mut fmt::Write, value: &Value) -> fmt::Result { if let Value::Ascii(ref v) = *value { if let Some(data) = v.first() { if data.len() >= 10 && data[4] == b':' && data[7] == b':' { if let Ok(year) = atou16(&data[0..4]) { if let Ok(month) = atou16(&data[5..7]) { if let Ok(day) = atou16(&data[8..10]) { return write!(w, "{:04}-{:02}-{:02}", year, month, day) } } } } } } d_default(w, value) } // GPSDifferential (Exif/GPS 0x1e) fn d_gpsdifferential(w: &mut fmt::Write, value: &Value) -> fmt::Result { let s = match value.get_uint(0) { Some(0) => "no differential correction", Some(1) => "differential correction applied", _ => return d_unknown(w, value, "unknown GPS differential correction "), }; w.write_str(s) } fn d_ascii_in_undef(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Undefined(s, _) => d_sub_ascii(w, s), _ => d_default(w, value), } } fn d_decimal(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Rational(ref v) => d_sub_comma_f64(w, v), Value::SRational(ref v) => d_sub_comma_f64(w, v), _ => d_default(w, value), } } #[inline(never)] fn d_unknown(w: &mut fmt::Write, value: &Value, prefix: &str) -> fmt::Result { w.write_str(prefix)?; d_default(w, value) } fn d_default(w: &mut fmt::Write, value: &Value) -> fmt::Result { match *value { Value::Byte(ref v) => d_sub_comma(w, v), Value::Ascii(ref v) => { let mut first = true; for x in v { if !first { w.write_str(", ")?; } first = false; d_sub_ascii(w, x)?; } Ok(()) }, Value::Short(ref v) => d_sub_comma(w, v), Value::Long(ref v) => d_sub_comma(w, v), Value::Rational(ref v) => d_sub_comma(w, v), Value::SByte(ref v) => d_sub_comma(w, v), Value::Undefined(s, _) => d_sub_hex(w, s), Value::SShort(ref v) => d_sub_comma(w, v), Value::SLong(ref v) => d_sub_comma(w, v), Value::SRational(ref v) => d_sub_comma(w, v), Value::Float(ref v) => d_sub_comma(w, v), Value::Double(ref v) => d_sub_comma(w, v), Value::Unknown(t, c, o) => write!(w, "unknown value (type={}, count={}, offset={:#x})", t, c, o), } } fn d_sub_comma(w: &mut fmt::Write, slice: &[T]) -> fmt::Result where T: fmt::Display { let mut first = true; for x in slice { match first { true => write!(w, "{}", x), false => write!(w, ", {}", x), }?; first = false; } Ok(()) } fn d_sub_comma_f64(w: &mut fmt::Write, slice: &[T]) -> fmt::Result where T: Copy + Into { let mut first = true; for &x in slice { let x: f64 = x.into(); match first { true => write!(w, "{}", x), false => write!(w, ", {}", x), }?; first = false; } Ok(()) } fn d_sub_hex(w: &mut fmt::Write, bytes: &[u8]) -> fmt::Result { w.write_str("0x")?; for x in bytes { write!(w, "{:02x}", x)?; } Ok(()) } fn d_sub_ascii(w: &mut fmt::Write, bytes: &[u8]) -> fmt::Result { w.write_char('"')?; for &c in bytes { match c { b'\\' | b'"' => { w.write_char('\\')?; w.write_char(c as char)?; }, 0x20...0x7e => w.write_char(c as char)?, _ => write!(w, "\\x{:02x}", c)?, } } w.write_char('"') } #[cfg(test)] mod tests { use value::Rational; use super::*; // This test checks if Tag constants can be used in patterns. #[test] fn tag_constant_in_pattern() { // Destructuring, which will always work. match Tag(Context::Tiff, 0x132) { Tag(Context::Tiff, 0x132) => {}, _ => panic!("failed to match Tag"), } // Matching against a constant. Test if this compiles. match Tag(Context::Tiff, 0x132) { Tag::DateTime => {}, _ => panic!("failed to match Tag"), } } #[test] fn default_value() { assert_pat!(Tag::DateTime.default_value(), None); match Tag::BitsPerSample.default_value() { Some(Value::Short(v)) => assert_eq!(v, &[8, 8, 8]), _ => panic!(), } match Tag::XResolution.default_value() { Some(Value::Rational(v)) => { assert_eq!(v.len(), 1); assert_eq!(v[0].num, 72); assert_eq!(v[0].denom, 1); }, _ => panic!(), } match Tag::FileSource.default_value() { Some(Value::Undefined(v, _)) => assert_eq!(v, &[3]), _ => panic!(), } match Tag::GPSAltitudeRef.default_value() { Some(Value::Byte(v)) => assert_eq!(v, &[0]), _ => panic!(), } match Tag::GPSSpeedRef.default_value() { Some(Value::Ascii(v)) => assert_eq!(v, &[b"K"]), _ => panic!(), } } #[test] fn tag_fmt_display() { let tag1 = Tag(Context::Tiff, 0x132); assert_eq!(format!("{:15}", tag1), "DateTime "); assert_eq!(format!("{:>15}", tag1), " DateTime"); assert_eq!(format!("{:5.6}", tag1), "DateTi"); let tag2 = Tag(Context::Exif, 0); assert_eq!(format!("{:15}", tag2), "Tag(Exif, 0) "); assert_eq!(format!("{:>15}", tag2), " Tag(Exif, 0)"); assert_eq!(format!("{:5.6}", tag2), "Tag(Ex"); } #[test] fn disp_val_sub() { let mut buf = String::new(); d_sub_comma(&mut buf, &[0u16, 1, 2]).unwrap(); assert_eq!(buf, "0, 1, 2"); let mut buf = String::new(); d_sub_comma(&mut buf, &[Rational { num: 3, denom: 5 }]).unwrap(); assert_eq!(buf, "3/5"); let mut buf = String::new(); d_sub_comma_f64(&mut buf, &[Rational { num: 1, denom: 2 }]).unwrap(); assert_eq!(buf, "0.5"); let mut buf = String::new(); d_sub_hex(&mut buf, b"abc\x00\xff").unwrap(); assert_eq!(buf, "0x61626300ff"); let mut buf = String::new(); d_sub_ascii(&mut buf, b"a \"\\b\"\n").unwrap(); assert_eq!(buf, r#""a \"\\b\"\x0a""#); } } kamadak-exif-0.3.1/src/tiff.rs010064400234210023421000000302111331145502400143340ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::fmt; use endian::{Endian, BigEndian, LittleEndian}; use error::Error; use tag_priv::{Context, Tag}; use value::Value; use value::get_type_info; use util::{atou16, ctou32}; // TIFF header magic numbers [EXIF23 4.5.2]. const TIFF_BE: u16 = 0x4d4d; const TIFF_LE: u16 = 0x4949; const TIFF_FORTY_TWO: u16 = 0x002a; pub const TIFF_BE_SIG: [u8; 4] = [0x4d, 0x4d, 0x00, 0x2a]; pub const TIFF_LE_SIG: [u8; 4] = [0x49, 0x49, 0x2a, 0x00]; /// A TIFF field. #[derive(Debug)] pub struct Field<'a> { /// The tag of this field. pub tag: Tag, /// False for the primary image and true for the thumbnail. pub thumbnail: bool, /// The value of this field. pub value: Value<'a>, } /// Parse the Exif attributes in the TIFF format. /// /// Returns a Vec of Exif fields and a bool. /// The boolean value is true if the data is little endian. /// If an error occurred, `exif::Error` is returned. pub fn parse_exif(data: &[u8]) -> Result<(Vec, bool), Error> { // Check the byte order and call the real parser. if data.len() < 8 { return Err(Error::InvalidFormat("Truncated TIFF header")); } match BigEndian::loadu16(data, 0) { TIFF_BE => parse_exif_sub::(data).map(|v| (v, false)), TIFF_LE => parse_exif_sub::(data).map(|v| (v, true)), _ => Err(Error::InvalidFormat("Invalid TIFF byte order")), } } fn parse_exif_sub(data: &[u8]) -> Result, Error> where E: Endian { // Parse the rest of the header (42 and the IFD offset). if E::loadu16(data, 2) != TIFF_FORTY_TWO { return Err(Error::InvalidFormat("Invalid forty two")); } let ifd_offset = E::loadu32(data, 4) as usize; let mut fields = Vec::new(); parse_ifd::(&mut fields, data, ifd_offset, Context::Tiff, false)?; Ok(fields) } // Parse IFD [EXIF23 4.6.2]. fn parse_ifd<'a, E>(fields: &mut Vec>, data: &'a [u8], offset: usize, ctx: Context, thumbnail: bool) -> Result<(), Error> where E: Endian { // Count (the number of the entries). if data.len() < offset || data.len() - offset < 2 { return Err(Error::InvalidFormat("Truncated IFD count")); } let count = E::loadu16(data, offset) as usize; // Array of entries. (count * 12) never overflows. if data.len() - offset - 2 < count * 12 { return Err(Error::InvalidFormat("Truncated IFD")); } for i in 0..count as usize { let tag = E::loadu16(data, offset + 2 + i * 12); let typ = E::loadu16(data, offset + 2 + i * 12 + 2); let cnt = E::loadu32(data, offset + 2 + i * 12 + 4) as usize; let valofs_at = offset + 2 + i * 12 + 8; let (unitlen, parser) = get_type_info::(typ); let vallen = unitlen.checked_mul(cnt).ok_or( Error::InvalidFormat("Invalid entry count"))?; let val; if unitlen == 0 { val = Value::Unknown(typ, cnt as u32, valofs_at as u32); } else if vallen <= 4 { val = parser(data, valofs_at, cnt); } else { let ofs = E::loadu32(data, valofs_at) as usize; if data.len() < ofs || data.len() - ofs < vallen { return Err(Error::InvalidFormat("Truncated field value")); } val = parser(data, ofs, cnt); } // No infinite recursion will occur because the context is not // recursively defined. let tag = Tag(ctx, tag); match tag { Tag::ExifIFDPointer => parse_child_ifd::( fields, data, &val, Context::Exif, thumbnail)?, Tag::GPSInfoIFDPointer => parse_child_ifd::( fields, data, &val, Context::Gps, thumbnail)?, Tag::InteropIFDPointer => parse_child_ifd::( fields, data, &val, Context::Interop, thumbnail)?, _ => fields.push(Field { tag: tag, thumbnail: thumbnail, value: val }), } } // Offset to the next IFD. if data.len() - offset - 2 - count * 12 < 4 { return Err(Error::InvalidFormat("Truncated next IFD offset")); } let next_ifd_offset = E::loadu32(data, offset + 2 + count * 12) as usize; // Ignore IFDs after IFD1 (thumbnail) for now. if next_ifd_offset == 0 || thumbnail { return Ok(()); } if ctx != Context::Tiff { return Err(Error::InvalidFormat("Unexpected next IFD")); } parse_ifd::(fields, data, next_ifd_offset, Context::Tiff, true) } fn parse_child_ifd<'a, E>(fields: &mut Vec>, data: &'a [u8], pointer: &Value, ctx: Context, thumbnail: bool) -> Result<(), Error> where E: Endian { // A pointer field has type == LONG and count == 1, so the // value (IFD offset) must be embedded in the "value offset" // element of the field. let ofs = pointer.get_uint(0).ok_or( Error::InvalidFormat("Invalid pointer"))? as usize; parse_ifd::(fields, data, ofs, ctx, thumbnail) } pub fn is_tiff(buf: &[u8]) -> bool { buf.starts_with(&TIFF_BE_SIG) || buf.starts_with(&TIFF_LE_SIG) } /// A struct used to parse a DateTime field. /// /// # Examples /// ``` /// use exif::DateTime; /// let dt = DateTime::from_ascii(b"2016:05:04 03:02:01").unwrap(); /// assert_eq!(dt.year, 2016); /// assert_eq!(format!("{}", dt), "2016-05-04 03:02:01"); /// ``` #[derive(Debug)] pub struct DateTime { pub year: u16, pub month: u8, pub day: u8, pub hour: u8, pub minute: u8, pub second: u8, /// The subsecond data in nanoseconds. If the Exif attribute has /// more sigfinicant digits, they are rounded down. pub nanosecond: Option, /// The offset of the time zone in minutes. pub offset: Option, } impl DateTime { /// Parse an ASCII data of a DateTime field. The range of a number /// is not validated, so, for example, 13 may be returned as the month. /// /// If the value is blank, `Error::BlankValue` is returned. pub fn from_ascii(data: &[u8]) -> Result { if data == b" : : : : " || data == b" " { return Err(Error::BlankValue("DateTime is blank")); } else if data.len() < 19 { return Err(Error::InvalidFormat("DateTime too short")); } else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' && data[13] == b':' && data[16] == b':') { return Err(Error::InvalidFormat("Invalid DateTime delimiter")); } Ok(DateTime { year: atou16(&data[0..4])?, month: atou16(&data[5..7])? as u8, day: atou16(&data[8..10])? as u8, hour: atou16(&data[11..13])? as u8, minute: atou16(&data[14..16])? as u8, second: atou16(&data[17..19])? as u8, nanosecond: None, offset: None, }) } /// Parses an SubsecTime-like field. pub fn parse_subsec(&mut self, data: &[u8]) -> Result<(), Error> { let mut subsec = 0; let mut ndigits = 0; for &c in data { if c == b' ' { break; } subsec = subsec * 10 + ctou32(c)?; ndigits += 1; if ndigits >= 9 { break; } } if ndigits == 0 { self.nanosecond = None; } else { for _ in ndigits..9 { subsec *= 10; } self.nanosecond = Some(subsec); } Ok(()) } /// Parses an OffsetTime-like field. pub fn parse_offset(&mut self, data: &[u8]) -> Result<(), Error> { if data == b" : " || data == b" " { return Err(Error::BlankValue("OffsetTime is blank")); } else if data.len() < 6 { return Err(Error::InvalidFormat("OffsetTime too short")); } else if data[3] != b':' { return Err(Error::InvalidFormat("Invalid OffsetTime delimiter")); } let hour = atou16(&data[1..3])?; let min = atou16(&data[4..6])?; let offset = (hour * 60 + min) as i16; self.offset = Some(match data[0] { b'+' => offset, b'-' => -offset, _ => return Err(Error::InvalidFormat("Invalid OffsetTime sign")), }); Ok(()) } } impl fmt::Display for DateTime { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:04}-{:02}-{:02} {:02}:{:02}:{:02}", self.year, self.month, self.day, self.hour, self.minute, self.second) } } #[cfg(test)] mod tests { use super::*; // Before the error is returned, the IFD is parsed twice as the // 0th and 1st IFDs. #[test] fn inf_loop_by_next() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\0\x03\0\0\0\x01\0\x14\0\0\0\0\0\x08"; // assert_err_pat!(parse_exif(data), // Error::InvalidFormat("Unexpected next IFD")); let (v, _) = parse_exif(data).unwrap(); assert_eq!(v.len(), 2); } #[test] fn unknown_field() { let data = b"MM\0\x2a\0\0\0\x08\ \0\x01\x01\0\xff\xff\0\0\0\x01\0\x14\0\0\0\0\0\0"; let (v, _) = parse_exif(data).unwrap(); assert_eq!(v.len(), 1); assert_pat!(v[0].value, Value::Unknown(0xffff, 1, 0x12)); } #[test] fn date_time() { let mut dt = DateTime::from_ascii(b"2016:05:04 03:02:01").unwrap(); assert_eq!(dt.year, 2016); assert_eq!(format!("{}", dt), "2016-05-04 03:02:01"); dt.parse_subsec(b"987").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987000000); dt.parse_subsec(b"000987").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987000); dt.parse_subsec(b"987654321").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987654321); dt.parse_subsec(b"9876543219").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 987654321); dt.parse_subsec(b"130 ").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 130000000); dt.parse_subsec(b"0").unwrap(); assert_eq!(dt.nanosecond.unwrap(), 0); dt.parse_subsec(b"").unwrap(); assert!(dt.nanosecond.is_none()); dt.parse_subsec(b" ").unwrap(); assert!(dt.nanosecond.is_none()); dt.parse_offset(b"+00:00").unwrap(); assert_eq!(dt.offset.unwrap(), 0); dt.parse_offset(b"+01:23").unwrap(); assert_eq!(dt.offset.unwrap(), 83); dt.parse_offset(b"+99:99").unwrap(); assert_eq!(dt.offset.unwrap(), 6039); dt.parse_offset(b"-01:23").unwrap(); assert_eq!(dt.offset.unwrap(), -83); dt.parse_offset(b"-99:99").unwrap(); assert_eq!(dt.offset.unwrap(), -6039); assert_err_pat!(dt.parse_offset(b" : "), Error::BlankValue(_)); assert_err_pat!(dt.parse_offset(b" "), Error::BlankValue(_)); } } kamadak-exif-0.3.1/src/tmacro.rs010064400234210023421000000044121306573333700147110ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // // Macros for testing. macro_rules! assert_ok { ($expr:expr, $value:expr) => ( match $expr { Ok(v) => assert_eq!(v, $value), r => panic!("assertion failed: unexpected {:?}", r), } ) } macro_rules! assert_pat { ($expr:expr, $pat:pat) => ( match $expr { $pat => {}, ref r => panic!("assertion failed: unexpected {:?}", r), } ) } macro_rules! assert_err_pat { ($expr:expr, $variant:pat) => ( match $expr { Err($variant) => {}, r => panic!("assertion failed: unexpected {:?}", r), } ) } // This macro is intended to be used with std::io::Error, but other // types with kind() will also work. macro_rules! assert_err_kind { ($expr:expr, $kind:expr) => ( match $expr { Err(e) => assert_eq!(e.kind(), $kind), r => panic!("assertion failed: unexpected {:?}", r), } ) } kamadak-exif-0.3.1/src/util.rs010064400234210023421000000106731331145502400143730ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::io; use error::Error; const ASCII_0: u8 = 0x30; const ASCII_9: u8 = 0x39; const ASCII_A: u8 = 0x41; const ASCII_Z: u8 = 0x5a; pub fn read8(reader: &mut R) -> Result where R: io::Read { let mut buf: [u8; 1] = unsafe { ::std::mem::uninitialized() }; reader.read_exact(&mut buf).and(Ok(buf[0])) } pub fn read16(reader: &mut R) -> Result where R: io::Read { let mut buf: [u8; 2] = unsafe { ::std::mem::uninitialized() }; reader.read_exact(&mut buf)?; Ok(((buf[0] as u16) << 8) + buf[1] as u16) } // This function must not be called with more than 4 bytes. pub fn atou16(bytes: &[u8]) -> Result { if cfg!(debug_assertions) && bytes.len() >= 5 { panic!("atou16 accepts up to 4 bytes"); } if bytes.len() == 0 { return Err(Error::InvalidFormat("Not a number")); } let mut n = 0; for &c in bytes { if c < ASCII_0 || ASCII_9 < c { return Err(Error::InvalidFormat("Not a number")); } n = n * 10 + (c - ASCII_0) as u16; } Ok(n) } pub fn ctou32(c: u8) -> Result { if c < ASCII_0 || ASCII_9 < c { return Err(Error::InvalidFormat("Not a number")); } Ok((c - ASCII_0) as u32) } pub fn isupper(c: u8) -> bool { ASCII_A <= c && c <= ASCII_Z } #[cfg(test)] mod tests { use std::io::Cursor; use std::io::ErrorKind; use std::io::Read; use super::*; #[test] fn read8_len() { let mut reader = Cursor::new([]); assert_err_kind!(read8(&mut reader), ErrorKind::UnexpectedEof); let mut reader = Cursor::new([0x01]); assert_ok!(read8(&mut reader), 0x01); let mut reader = Cursor::new([0x01, 0x02]); let mut buf = Vec::new(); assert_ok!(read8(&mut reader), 0x01); assert_ok!(reader.read_to_end(&mut buf), 1); assert_eq!(buf, [0x02]); } #[test] fn read16_len() { let mut reader = Cursor::new([]); assert_err_kind!(read16(&mut reader), ErrorKind::UnexpectedEof); let mut reader = Cursor::new([0x01]); assert_err_kind!(read16(&mut reader), ErrorKind::UnexpectedEof); let mut reader = Cursor::new([0x01, 0x02]); assert_ok!(read16(&mut reader), 0x0102); let mut reader = Cursor::new([0x01, 0x02, 0x03]); let mut buf = Vec::new(); assert_ok!(read16(&mut reader), 0x0102); assert_ok!(reader.read_to_end(&mut buf), 1); assert_eq!(buf, [0x03]); } #[test] fn atou16_misc() { assert_ok!(atou16(b"0"), 0); assert_ok!(atou16(b"0010"), 10); assert_ok!(atou16(b"9999"), 9999); assert_err_pat!(atou16(b""), Error::InvalidFormat(_)); assert_err_pat!(atou16(b"/"), Error::InvalidFormat(_)); assert_err_pat!(atou16(b":"), Error::InvalidFormat(_)); assert_err_pat!(atou16(b"-1"), Error::InvalidFormat(_)); } #[test] fn isupper() { assert!(super::isupper(b'A')); assert!(super::isupper(b'Z')); assert!(!super::isupper(b'A' - 1)); assert!(!super::isupper(b'Z' + 1)); assert!(!super::isupper(b'a')); } } kamadak-exif-0.3.1/src/value.rs010064400234210023421000000647131331145502400145360ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::fmt; use endian::Endian; /// Types and values of TIFF fields (for Exif attributes). #[derive(Debug)] pub enum Value<'a> { /// Vector of 8-bit unsigned integers. Byte(Vec), /// Vector of slices of 8-bit bytes containing 7-bit ASCII characters. /// The trailing null characters are not included. Note that /// the 8th bits may present if a non-conforming data is given. Ascii(Vec<&'a [u8]>), /// Vector of 16-bit unsigned integers. Short(Vec), /// Vector of 32-bit unsigned integers. Long(Vec), /// Vector of unsigned rationals. /// An unsigned rational number is a pair of 32-bit unsigned integers. Rational(Vec), /// Vector of 8-bit signed integers. Unused in the Exif specification. SByte(Vec), /// Slice of 8-bit bytes. /// /// The second member keeps the offset of the value in the Exif data. /// The interpretation of the value does not generally depend on /// the location, but if it does, the offset information helps. /// When encoding Exif, it is ignored. Undefined(&'a [u8], u32), /// Vector of 16-bit signed integers. Unused in the Exif specification. SShort(Vec), /// Vector of 32-bit signed integers. SLong(Vec), /// Vector of signed rationals. /// A signed rational number is a pair of 32-bit signed integers. SRational(Vec), /// Vector of 32-bit (single precision) floating-point numbers. /// Unused in the Exif specification. Float(Vec), /// Vector of 64-bit (double precision) floating-point numbers. /// Unused in the Exif specification. Double(Vec), /// The type is unknown to this implementation. /// The associated values are the type, the count, and the /// offset of the "Value Offset" element. Unknown(u16, u32, u32), } impl<'a> Value<'a> { /// Returns an object that implements `std::fmt::Display` for /// printing a value in a tag-specific format. /// The tag of the value is specified as the argument. /// /// # Examples /// /// ``` /// use exif::{Value, Tag}; /// let val = Value::Undefined(b"0231", 0); /// assert_eq!(format!("{}", val.display_as(Tag::ExifVersion)), /// "2.31"); /// let val = Value::Short(vec![2]); /// assert_eq!(format!("{}", val.display_as(Tag::ResolutionUnit)), /// "pixels per inch"); /// ``` #[inline] pub fn display_as(&self, tag: ::tag_priv::Tag) -> Display { ::tag_priv::display_value_as(self, tag) } /// Returns the unsigned integer at the given position. /// None is returned if the value type is not unsigned integer /// (BYTE, SHORT, or LONG) or the position is out of bounds. pub fn get_uint(&self, index: usize) -> Option { match *self { Value::Byte(ref v) if v.len() > index => Some(v[index] as u32), Value::Short(ref v) if v.len() > index => Some(v[index] as u32), Value::Long(ref v) if v.len() > index => Some(v[index]), _ => None, } } /// Returns an iterator over the unsigned integers (BYTE, SHORT, or LONG). /// The iterator yields `u32` regardless of the underlying integer size. /// The returned iterator implements `Iterator` and `ExactSizeIterator` /// traits. /// `None` is returned if the value is not an unsigned integer type. #[inline] pub fn iter_uint(&self) -> Option { match *self { Value::Byte(ref v) => Some(UIntIter { iter: Box::new(v.iter().map(|&x| x as u32)) }), Value::Short(ref v) => Some(UIntIter { iter: Box::new(v.iter().map(|&x| x as u32)) }), Value::Long(ref v) => Some(UIntIter { iter: Box::new(v.iter().map(|&x| x)) }), _ => None, } } } // A struct that wraps std::slice::Iter<'a, u8/u16/u32>. pub struct UIntIter<'a> { iter: Box + 'a> } impl<'a> Iterator for UIntIter<'a> { type Item = u32; #[inline] fn next(&mut self) -> Option { self.iter.next() } #[inline] fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl<'a> ExactSizeIterator for UIntIter<'a> {} /// Helper struct for printing a value in a tag-specific format. pub struct Display<'a> { pub fmt: fn(&mut fmt::Write, &Value) -> fmt::Result, pub value: &'a Value<'a>, } impl<'a> fmt::Display for Display<'a> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { (self.fmt)(f, self.value) } } // Static default values. pub enum DefaultValue { None, Byte(&'static [u8]), Ascii(&'static [&'static [u8]]), Short(&'static [u16]), Rational(&'static [(u32, u32)]), Undefined(&'static [u8]), // Depends on other tags, JPEG markers, etc. ContextDependent, // Unspecified in the Exif standard. Unspecified, } impl<'a> From<&'a DefaultValue> for Option> { fn from(defval: &DefaultValue) -> Option { match *defval { DefaultValue::None => None, DefaultValue::Byte(s) => Some(Value::Byte(s.to_vec())), DefaultValue::Ascii(s) => Some(Value::Ascii(s.to_vec())), DefaultValue::Short(s) => Some(Value::Short(s.to_vec())), DefaultValue::Rational(s) => Some(Value::Rational( s.iter().map(|&t| tuple2rational(t)).collect())), DefaultValue::Undefined(s) => Some(Value::Undefined(s, 0)), DefaultValue::ContextDependent => None, DefaultValue::Unspecified => None, } } } /// An unsigned rational number, which is a pair of 32-bit unsigned integers. #[derive(Copy, Clone)] pub struct Rational { pub num: u32, pub denom: u32 } impl Rational { /// Converts the value to an f64. #[inline] pub fn to_f64(&self) -> f64 { self.num as f64 / self.denom as f64 } } fn tuple2rational(t: (u32, u32)) -> Rational { Rational { num: t.0, denom: t.1 } } impl fmt::Debug for Rational { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Rational({}/{})", self.num, self.denom) } } impl fmt::Display for Rational { /// Formatting parameters other than width are not supported. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let buf = fmt_rational_sub(f, self.num, self.denom); f.pad_integral(true, "", &buf) } } impl From for f64 { #[inline] fn from(r: Rational) -> f64 { r.to_f64() } } impl From for f32 { #[inline] fn from(r: Rational) -> f32 { r.to_f64() as f32 } } /// A signed rational number, which is a pair of 32-bit signed integers. #[derive(Copy, Clone)] pub struct SRational { pub num: i32, pub denom: i32 } impl SRational { /// Converts the value to an f64. #[inline] pub fn to_f64(&self) -> f64 { self.num as f64 / self.denom as f64 } } impl fmt::Debug for SRational { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SRational({}/{})", self.num, self.denom) } } impl fmt::Display for SRational { /// Formatting parameters other than width are not supported. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let buf = fmt_rational_sub( f, self.num.wrapping_abs() as u32, self.denom); f.pad_integral(self.num >= 0, "", &buf) } } impl From for f64 { #[inline] fn from(r: SRational) -> f64 { r.to_f64() } } impl From for f32 { #[inline] fn from(r: SRational) -> f32 { r.to_f64() as f32 } } // Only u32 or i32 are expected for T. fn fmt_rational_sub(f: &mut fmt::Formatter, num: u32, denom: T) -> String where T: fmt::Display { // The API to get the alignment is not yet stable as of Rust 1.16, // so it is not fully supported. match (f.sign_plus(), f.precision(), f.sign_aware_zero_pad()) { (true, Some(prec), true) => format!("{}/{:+0w$}", num, denom, w = prec), (true, Some(prec), false) => format!("{}/{:+w$}", num, denom, w = prec), (true, None, _) => format!("{}/{:+}", num, denom), (false, Some(prec), true) => format!("{}/{:0w$}", num, denom, w = prec), (false, Some(prec), false) => format!("{}/{:w$}", num, denom, w = prec), (false, None, _) => format!("{}/{}", num, denom), } } type Parser<'a> = fn(&'a [u8], usize, usize) -> Value<'a>; // Return the length of a single value and the parser of the type. pub fn get_type_info<'a, E>(typecode: u16) -> (usize, Parser<'a>) where E: Endian { match typecode { 1 => (1, parse_byte), 2 => (1, parse_ascii), 3 => (2, parse_short::), 4 => (4, parse_long::), 5 => (8, parse_rational::), 6 => (1, parse_sbyte), 7 => (1, parse_undefined), 8 => (2, parse_sshort::), 9 => (4, parse_slong::), 10 => (8, parse_srational::), 11 => (4, parse_float::), 12 => (8, parse_double::), _ => (0, parse_unknown), } } fn parse_byte<'a>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> { Value::Byte(data[offset .. offset + count].to_vec()) } fn parse_ascii<'a>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> { // Any ASCII field can contain multiple strings [TIFF6 Image File // Directory]. let iter = (&data[offset .. offset + count]).split(|&b| b == b'\0'); let mut v: Vec<&[u8]> = iter.collect(); if v.last().map_or(false, |&s| s.len() == 0) { v.pop(); } Value::Ascii(v) } fn parse_short<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu16(data, offset + i * 2)); } Value::Short(val) } fn parse_long<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu32(data, offset + i * 4)); } Value::Long(val) } fn parse_rational<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(Rational { num: E::loadu32(data, offset + i * 8), denom: E::loadu32(data, offset + i * 8 + 4), }); } Value::Rational(val) } fn parse_sbyte<'a>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> { let uslice = &data[offset .. offset + count]; let islice = unsafe { ::std::slice::from_raw_parts( uslice.as_ptr() as *const i8, count) }; Value::SByte(islice.to_vec()) } fn parse_undefined<'a>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> { Value::Undefined(&data[offset .. offset + count], offset as u32) } fn parse_sshort<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu16(data, offset + i * 2) as i16); } Value::SShort(val) } fn parse_slong<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(E::loadu32(data, offset + i * 4) as i32); } Value::SLong(val) } fn parse_srational<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(SRational { num: E::loadu32(data, offset + i * 8) as i32, denom: E::loadu32(data, offset + i * 8 + 4) as i32, }); } Value::SRational(val) } // TIFF and Rust use IEEE 754 format, so no conversion is required. fn parse_float<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(f32::from_bits(E::loadu32(data, offset + i * 4))); } Value::Float(val) } // TIFF and Rust use IEEE 754 format, so no conversion is required. fn parse_double<'a, E>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> where E: Endian { let mut val = Vec::with_capacity(count); for i in 0..count { val.push(f64::from_bits(E::loadu64(data, offset + i * 8))); } Value::Double(val) } // This is a dummy function and will never be called. #[allow(unused_variables)] fn parse_unknown<'a>(data: &'a [u8], offset: usize, count: usize) -> Value<'a> { unreachable!() } #[cfg(test)] mod tests { use endian::BigEndian; use super::*; #[test] fn byte() { let sets: &[(&[u8], &[u8])] = &[ (b"x", b""), (b"x\xbe\xad", b"\xbe\xad"), ]; let (unitlen, parser) = get_type_info::(1); for &(data, ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Byte(v) => assert_eq!(v, ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn ascii() { let sets: &[(&[u8], Vec<&[u8]>)] = &[ (b"x", vec![]), // malformed (b"x\0", vec![b""]), (b"x\0\0", vec![b"", b""]), (b"xA", vec![b"A"]), // malformed (b"xA\0", vec![b"A"]), (b"xA\0B", vec![b"A", b"B"]), // malformed (b"xA\0B\0", vec![b"A", b"B"]), (b"xA\0\xbe\0", vec![b"A", b"\xbe"]), // not ASCII ]; let (unitlen, parser) = get_type_info::(2); for &(data, ref ans) in sets { match parser(data, 1, (data.len() - 1) / unitlen) { Value::Ascii(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn short() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\x03\x04", vec![0x0102, 0x0304]), ]; let (unitlen, parser) = get_type_info::(3); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Short(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn long() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\x03\x04\x05\x06\x07\x08", vec![0x01020304, 0x05060708]), ]; let (unitlen, parser) = get_type_info::(4); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Long(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn rational() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\xa1\x02\x03\x04\x05\x06\x07\x08\ \x09\x0a\x0b\x0c\xbd\x0e\x0f\x10", vec![Rational { num: 0xa1020304, denom: 0x05060708 }, Rational { num: 0x090a0b0c, denom: 0xbd0e0f10 }]), ]; let (unitlen, parser) = get_type_info::(5); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Rational(v) => { assert_eq!(v.len(), ans.len()); for (x, y) in v.iter().zip(ans.iter()) { assert!(x.num == y.num && x.denom == y.denom); } }, v => panic!("wrong variant {:?}", v), } } } #[test] fn sbyte() { let sets: &[(&[u8], &[i8])] = &[ (b"x", &[]), (b"x\xbe\x7d", &[-0x42, 0x7d]), ]; let (unitlen, parser) = get_type_info::(6); for &(data, ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SByte(v) => assert_eq!(v, ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn undefined() { let sets: &[(&[u8], &[u8])] = &[ (b"x", b""), (b"x\xbe\xad", b"\xbe\xad"), ]; let (unitlen, parser) = get_type_info::(7); for &(data, ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Undefined(v, o) => { assert_eq!(v, ans); assert_eq!(o, 1); }, v => panic!("wrong variant {:?}", v), } } } #[test] fn sshort() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\xf3\x04", vec![0x0102, -0x0cfc]), ]; let (unitlen, parser) = get_type_info::(8); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SShort(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn slong() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x01\x02\x03\x04\x85\x06\x07\x08", vec![0x01020304, -0x7af9f8f8]), ]; let (unitlen, parser) = get_type_info::(9); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SLong(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn srational() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\xa1\x02\x03\x04\x05\x06\x07\x08\ \x09\x0a\x0b\x0c\xbd\x0e\x0f\x10", vec![SRational { num: -0x5efdfcfc, denom: 0x05060708 }, SRational { num: 0x090a0b0c, denom: -0x42f1f0f0 }]), ]; let (unitlen, parser) = get_type_info::(10); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::SRational(v) => { assert_eq!(v.len(), ans.len()); for (x, y) in v.iter().zip(ans.iter()) { assert!(x.num == y.num && x.denom == y.denom); } }, v => panic!("wrong variant {:?}", v), } } } #[test] fn float() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x7f\x7f\xff\xff\x80\x80\x00\x00\x40\x00\x00\x00", vec![::std::f32::MAX, -::std::f32::MIN_POSITIVE, 2.0]), ]; let (unitlen, parser) = get_type_info::(11); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Float(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } #[test] fn double() { let sets: &[(&[u8], Vec)] = &[ (b"x", vec![]), (b"x\x7f\xef\xff\xff\xff\xff\xff\xff\ \x80\x10\x00\x00\x00\x00\x00\x00\ \x40\x00\x00\x00\x00\x00\x00\x00", vec![::std::f64::MAX, -::std::f64::MIN_POSITIVE, 2.0]), ]; let (unitlen, parser) = get_type_info::(12); for &(data, ref ans) in sets { assert!((data.len() - 1) % unitlen == 0); match parser(data, 1, (data.len() - 1) / unitlen) { Value::Double(v) => assert_eq!(v, *ans), v => panic!("wrong variant {:?}", v), } } } // These functions are never called in a way that an out-of-range access // could happen, so this test is hypothetical but just for safety. #[test] #[should_panic(expected = "index 5 out of range for slice of length 4")] fn short_oor() { parse_short::(b"\x01\x02\x03\x04", 1, 2); } #[test] fn unknown() { let (unitlen, _parser) = get_type_info::(0xffff); assert_eq!(unitlen, 0); } #[test] fn get_uint() { let v = Value::Byte(vec![1, 2]); assert_eq!(v.get_uint(0), Some(1)); assert_eq!(v.get_uint(1), Some(2)); assert_eq!(v.get_uint(2), None); let v = Value::Short(vec![1, 2]); assert_eq!(v.get_uint(0), Some(1)); assert_eq!(v.get_uint(1), Some(2)); assert_eq!(v.get_uint(2), None); let v = Value::Long(vec![1, 2]); assert_eq!(v.get_uint(0), Some(1)); assert_eq!(v.get_uint(1), Some(2)); assert_eq!(v.get_uint(2), None); let v = Value::SLong(vec![1, 2]); assert_eq!(v.get_uint(0), None); assert_eq!(v.get_uint(1), None); assert_eq!(v.get_uint(2), None); } #[test] fn iter_uint() { let vlist = &[ Value::Byte(vec![1, 2]), Value::Short(vec![1, 2]), Value::Long(vec![1, 2]), ]; for v in vlist { let mut it = v.iter_uint().unwrap(); assert_eq!(it.next(), Some(1)); assert_eq!(it.next(), Some(2)); assert_eq!(it.next(), None); } let v = Value::SLong(vec![1, 2]); assert!(v.iter_uint().is_none()); } #[test] fn iter_uint_is_exact_size_iter() { let v = Value::Byte(vec![1, 2, 3]); let mut it = v.iter_uint().unwrap(); assert_eq!(it.len(), 3); assert_eq!(it.next(), Some(1)); assert_eq!(it.len(), 2); } #[test] fn rational_fmt_display() { let r = Rational { num: u32::max_value(), denom: u32::max_value() }; assert_eq!(format!("{}", r), "4294967295/4294967295"); let r = Rational { num: 10, denom: 20 }; assert_eq!(format!("{}", r), "10/20"); assert_eq!(format!("{:11}", r), " 10/20"); assert_eq!(format!("{:3}", r), "10/20"); } #[test] fn srational_fmt_display() { let r = SRational { num: i32::min_value(), denom: i32::min_value() }; assert_eq!(format!("{}", r), "-2147483648/-2147483648"); let r = SRational { num: i32::max_value(), denom: i32::max_value() }; assert_eq!(format!("{}", r), "2147483647/2147483647"); let r = SRational { num: -10, denom: 20 }; assert_eq!(format!("{}", r), "-10/20"); assert_eq!(format!("{:11}", r), " -10/20"); assert_eq!(format!("{:3}", r), "-10/20"); let r = SRational { num: 10, denom: -20 }; assert_eq!(format!("{}", r), "10/-20"); assert_eq!(format!("{:11}", r), " 10/-20"); assert_eq!(format!("{:3}", r), "10/-20"); let r = SRational { num: -10, denom: -20 }; assert_eq!(format!("{}", r), "-10/-20"); assert_eq!(format!("{:11}", r), " -10/-20"); assert_eq!(format!("{:3}", r), "-10/-20"); } #[test] fn ratioanl_f64() { use std::{f64, u32}; assert_eq!(f64::from(Rational { num: 1, denom: 2 }), 0.5); assert_eq!(f64::from(Rational { num: 1, denom: u32::MAX }), 2.3283064370807974e-10); assert_eq!(f64::from(Rational { num: u32::MAX, denom: 1 }), u32::MAX as f64); assert_eq!(f64::from(Rational { num: u32::MAX - 1, denom: u32::MAX }), 0.9999999997671694); assert_eq!(f64::from(Rational { num: u32::MAX, denom: u32::MAX - 1 }), 1.0000000002328306); assert_eq!(f64::from(Rational { num: 1, denom: 0 }), f64::INFINITY); assert!(f64::from(Rational { num: 0, denom: 0 }).is_nan()); assert_eq!(f64::from(SRational { num: 1, denom: 2 }), 0.5); assert_eq!(f64::from(SRational { num: -1, denom: 2 }), -0.5); assert_eq!(f64::from(SRational { num: 1, denom: -2 }), -0.5); assert_eq!(f64::from(SRational { num: -1, denom: -2 }), 0.5); assert_eq!(f64::from(SRational { num: 1, denom: 0 }), f64::INFINITY); assert_eq!(f64::from(SRational { num: -1, denom: 0 }), f64::NEG_INFINITY); } #[test] fn rational_f32() { // If num and demon are converted to f32 before the division, // the precision is lost in this example. assert_eq!(f32::from(Rational { num: 1, denom: 16777217 }), 5.960464e-8); } } kamadak-exif-0.3.1/src/writer.rs010064400234210023421000000731051331145502400147310ustar0000000000000000// // Copyright (c) 2017 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::io; use std::io::{Seek, SeekFrom, Write}; use std::slice; use endian::{Endian, BigEndian, LittleEndian}; use error::Error; use tag_priv::{Context, Tag}; use tiff::{Field, TIFF_BE_SIG, TIFF_LE_SIG}; use value::Value; /// The `Writer` struct is used to encode and write Exif data. /// /// # Examples /// /// ``` /// use exif::{Field, Value, Tag}; /// use exif::experimental::Writer; /// let image_desc = Field { /// tag: Tag::ImageDescription, /// thumbnail: false, /// value: Value::Ascii(vec![b"Sample"]), /// }; /// let mut writer = Writer::new(); /// let mut buf = std::io::Cursor::new(Vec::new()); /// writer.push_field(&image_desc); /// writer.write(&mut buf, false).unwrap(); /// static expected: &[u8] = /// b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ /// \x00\x01\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x1a\ /// \x00\x00\x00\x00\ /// Sample\0"; /// assert_eq!(buf.into_inner(), expected); /// ``` #[derive(Debug)] pub struct Writer<'a> { tiff_fields: Vec<&'a Field<'a>>, exif_fields: Vec<&'a Field<'a>>, gps_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]]>, tn_strips: Option<&'a [&'a [u8]]>, tiles: Option<&'a [&'a [u8]]>, tn_jpeg: Option<&'a [u8]>, } struct WriterState<'a> { tiff_fields: Vec<&'a Field<'a>>, exif_fields: Vec<&'a Field<'a>>, gps_fields: Vec<&'a Field<'a>>, interop_fields: Vec<&'a Field<'a>>, tiff_ifd_offset: u32, exif_ifd_offset: u32, gps_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> { /// Constructs an empty `Writer`. pub fn new() -> Writer<'a> { Writer { tiff_fields: 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, } } /// Appends a field to be written. /// /// 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, 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/tile data. Field { tag: Tag::StripOffsets, .. } | 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, .. } => {}, // Other normal tags. Field { tag: Tag(Context::Tiff, _), thumbnail: false, .. } => self.tiff_fields.push(field), Field { tag: Tag(Context::Exif, _), thumbnail: false, .. } => self.exif_fields.push(field), Field { tag: Tag(Context::Gps, _), thumbnail: false, .. } => self.gps_fields.push(field), Field { tag: Tag(Context::Interop, _), thumbnail: false, .. } => self.interop_fields.push(field), Field { tag: Tag(Context::Tiff, _), thumbnail: true, .. } => self.tn_tiff_fields.push(field), Field { tag: Tag(Context::Exif, _), thumbnail: true, .. } => self.tn_exif_fields.push(field), Field { tag: Tag(Context::Gps, _), thumbnail: true, .. } => self.tn_gps_fields.push(field), Field { tag: Tag(Context::Interop, _), thumbnail: true, .. } => self.tn_interop_fields.push(field), } } /// Sets TIFF strips for the primary image. /// If this method is called multiple times, the last one is used. pub fn set_strips(&mut self, strips: &'a [&'a [u8]]) { self.strips = Some(strips); } /// Sets TIFF strips for the thumbnail image. /// If this method is called multiple times, the last one is used. pub fn set_thumbnail_strips(&mut self, strips: &'a [&'a [u8]]) { 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]) { self.tn_jpeg = Some(jpeg); } /// Encodes Exif data and writes it into `w`. /// /// The write position of `w` must be set to zero before calling /// this method. pub fn write(&mut self, w: &mut W, little_endian: bool) -> Result<(), Error> where W: Write + Seek { // TIFF signature and the offset of the 0th IFD. if little_endian { w.write_all(&TIFF_LE_SIG)?; LittleEndian::writeu32(w, 8)?; } else { w.write_all(&TIFF_BE_SIG)?; BigEndian::writeu32(w, 8)?; } // Write the primary image. let ws = WriterState { tiff_fields: self.tiff_fields.clone(), exif_fields: self.exif_fields.clone(), 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, false, 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 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))?; // 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, true, little_endian)?; w.flush()?; Ok(()) } } // Synthesizes special fields, writes an image, and returns the offset // of the next IFD offset. fn synthesize_fields(w: &mut W, ws: WriterState, thumbnail: bool, little_endian: bool) -> Result where W: Write + Seek { let exif_in_tiff; let gps_in_tiff; 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. let mut ws = ws; if let Some(strips) = ws.strips { strip_offsets = Field { tag: Tag::StripOffsets, thumbnail: thumbnail, value: Value::Long(vec![0; strips.len()]), }; ws.tiff_fields.push(&strip_offsets); strip_byte_counts = Field { tag: Tag::StripByteCounts, thumbnail: thumbnail, value: Value::Long( strips.iter().map(|s| s.len() as u32).collect()), }; 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, thumbnail: thumbnail, value: Value::Long(vec![0]), }; ws.tiff_fields.push(&jpeg_offset); jpeg_length = Field { tag: Tag::JPEGInterchangeFormatLength, thumbnail: thumbnail, value: Value::Long(vec![jpeg.len() as u32]), }; ws.tiff_fields.push(&jpeg_length); } let interop_fields_len = ws.interop_fields.len(); let gps_fields_len = ws.gps_fields.len(); let exif_fields_len = ws.exif_fields.len() + match interop_fields_len { 0 => 0, _ => 1 }; let tiff_fields_len = ws.tiff_fields.len() + match gps_fields_len { 0 => 0, _ => 1 } + match exif_fields_len { 0 => 0, _ => 1 }; ws.tiff_ifd_offset = reserve_ifd(w, tiff_fields_len)?; if exif_fields_len > 0 { ws.exif_ifd_offset = reserve_ifd(w, exif_fields_len)?; exif_in_tiff = Field { tag: Tag::ExifIFDPointer, thumbnail: thumbnail, value: Value::Long(vec![ws.exif_ifd_offset]), }; ws.tiff_fields.push(&exif_in_tiff); } if gps_fields_len > 0 { ws.gps_ifd_offset = reserve_ifd(w, gps_fields_len)?; gps_in_tiff = Field { tag: Tag::GPSInfoIFDPointer, thumbnail: thumbnail, value: Value::Long(vec![ws.gps_ifd_offset]), }; ws.tiff_fields.push(&gps_in_tiff); } if interop_fields_len > 0 { ws.interop_ifd_offset = reserve_ifd(w, interop_fields_len)?; interop_in_exif = Field { tag: Tag::InteropIFDPointer, thumbnail: thumbnail, value: Value::Long(vec![ws.interop_ifd_offset]), }; ws.exif_fields.push(&interop_in_exif); } ws.tiff_fields.sort_by_key(|f| f.tag.number()); ws.exif_fields.sort_by_key(|f| f.tag.number()); ws.gps_fields.sort_by_key(|f| f.tag.number()); ws.interop_fields.sort_by_key(|f| f.tag.number()); match little_endian { false => write_image::<_, BigEndian>(w, ws), true => write_image::<_, LittleEndian>(w, ws), } } // 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, tile_offsets_offset, jpeg_offset) = write_ifd_and_fields::<_, E>( w, &ws.tiff_fields, ws.tiff_ifd_offset)?; if ws.exif_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.exif_fields, ws.exif_ifd_offset)?; } if ws.gps_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.gps_fields, ws.gps_ifd_offset)?; } if ws.interop_fields.len() > 0 { write_ifd_and_fields::<_, E>( w, &ws.interop_fields, ws.interop_ifd_offset)?; } if let Some(strips) = ws.strips { let mut strip_offsets = Vec::new(); for strip in strips { strip_offsets.push(get_offset(w)?); w.write_all(strip)?; } let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(strip_offsets_offset as u64))?; for ofs in strip_offsets { E::writeu32(w, ofs)?; } w.seek(SeekFrom::Start(origpos))?; } if let Some(tiles) = ws.tiles { let mut tile_offsets = Vec::new(); for tile in tiles { tile_offsets.push(get_offset(w)?); w.write_all(tile)?; } let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(tile_offsets_offset as u64))?; for ofs in tile_offsets { E::writeu32(w, ofs)?; } w.seek(SeekFrom::Start(origpos))?; } if let Some(jpeg) = ws.jpeg { let offset = get_offset(w)?; w.write_all(jpeg)?; let origpos = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(jpeg_offset as u64))?; E::writeu32(w, offset)?; w.seek(SeekFrom::Start(origpos))?; } Ok(next_ifd_offset_offset) } // Advances the write position to make a space for a new IFD and // returns the offset of the IFD. fn reserve_ifd(w: &mut W, count: usize) -> Result where W: Write + Seek { let ifdpos = get_offset(w)?; assert!(ifdpos % 2 == 0); // The number of entries (2) + array of entries (12 * n) + // the next IFD pointer (4). w.seek(SeekFrom::Current(2 + count as i64 * 12 + 4))?; Ok(ifdpos) } // Writes an IFD and its fields, and // 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, 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(); // Write the number of entries. E::writeu16(&mut ifd, fields.len() as u16)?; // Write the fields. for f in fields { let (typ, cnt, mut valbuf) = compose_value::(&f.value)?; if cnt as u32 as usize != cnt { return Err(Error::TooBig("Too long array")); } E::writeu16(&mut ifd, f.tag.number())?; E::writeu16(&mut ifd, typ)?; E::writeu32(&mut ifd, cnt as u32)?; // Embed the value itself into the offset, or // encode as an offset and the value. if valbuf.len() <= 4 { valbuf.resize(4, 0); ifd.write_all(&valbuf)?; } else { // The value must begin on a word boundary. [TIFF6, Section 2: // TIFF Structure, Image File Directory, IFD Entry, p. 15] let valofs = pad_and_get_offset(w)?; E::writeu32(&mut ifd, valofs)?; w.write_all(&valbuf)?; } if f.tag == Tag::StripOffsets { strip_offsets_offset = match valbuf.len() { 0...4 => ifd_offset + ifd.len() as u32 - 4, _ => 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, _ => get_offset(w)? - valbuf.len() as u32, }; } if f.tag == Tag::JPEGInterchangeFormat { jpeg_offset = ifd_offset + ifd.len() as u32 - 4; } } // Write the next IFD pointer. let next_ifd_offset_offset = ifd_offset + ifd.len() as u32; E::writeu32(&mut ifd, 0)?; // Write the IFD. write_at(w, &ifd, ifd_offset)?; Ok((next_ifd_offset_offset, strip_offsets_offset, tile_offsets_offset, jpeg_offset)) } // Returns the type, count, and encoded value. fn compose_value(value: &Value) -> Result<(u16, usize, Vec), Error> where E: Endian { match *value { Value::Byte(ref vec) => Ok((1, vec.len(), vec.clone())), Value::Ascii(ref vec) => { let mut buf = Vec::new(); for &s in vec { buf.extend_from_slice(s); buf.push(0); } Ok((2, buf.len(), buf)) }, Value::Short(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu16(&mut buf, v)?; } Ok((3, vec.len(), buf)) }, Value::Long(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v)?; } Ok((4, vec.len(), buf)) }, Value::Rational(ref vec) => { let mut buf = Vec::new(); for v in vec { E::writeu32(&mut buf, v.num)?; E::writeu32(&mut buf, v.denom)?; } Ok((5, vec.len(), buf)) }, Value::SByte(ref vec) => { let uslice = unsafe { slice::from_raw_parts( vec.as_ptr() as *const u8, vec.len()) }; Ok((6, vec.len(), uslice.to_vec())) }, Value::Undefined(ref s, _) => Ok((7, s.len(), s.to_vec())), Value::SShort(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu16(&mut buf, v as u16)?; } Ok((8, vec.len(), buf)) }, Value::SLong(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v as u32)?; } Ok((9, vec.len(), buf)) }, Value::SRational(ref vec) => { let mut buf = Vec::new(); for v in vec { E::writeu32(&mut buf, v.num as u32)?; E::writeu32(&mut buf, v.denom as u32)?; } Ok((10, vec.len(), buf)) }, Value::Float(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu32(&mut buf, v.to_bits())?; } Ok((11, vec.len(), buf)) }, Value::Double(ref vec) => { let mut buf = Vec::new(); for &v in vec { E::writeu64(&mut buf, v.to_bits())?; } Ok((12, vec.len(), buf)) }, Value::Unknown(_, _, _) => Err(Error::NotSupported("Cannot write unknown field types")), } } fn write_at(w: &mut W, buf: &[u8], offset: u32) -> io::Result<()> where W: Write + Seek { let orig = w.seek(SeekFrom::Current(0))?; w.seek(SeekFrom::Start(offset as u64))?; w.write_all(buf)?; w.seek(SeekFrom::Start(orig))?; Ok(()) } // Aligns `w` to the two-byte (word) boundary and returns the new offset. fn pad_and_get_offset(w: &mut W) -> Result where W: Write + Seek { let mut pos = w.seek(SeekFrom::Current(0))?; if pos >= (1 << 32) - 1 { return Err(Error::TooBig("Offset too large")); } if pos % 2 != 0 { w.write_all(&[0])?; pos += 1; } Ok(pos as u32) } fn get_offset(w: &mut W) -> Result where W: Write + Seek { let pos = w.seek(SeekFrom::Current(0))?; if pos as u32 as u64 != pos { return Err(Error::TooBig("Offset too large")); } Ok(pos as u32) } #[cfg(test)] mod tests { use std::io::Cursor; use value::{Rational, SRational}; use super::*; #[test] fn primary() { let image_desc = Field { tag: Tag::ImageDescription, thumbnail: false, value: Value::Ascii(vec![b"Sample"]), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&image_desc); 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\x07\x00\x00\x00\x1a\ \x00\x00\x00\x00\ Sample\0"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_exif_only() { let exif_ver = Field { tag: Tag::ExifVersion, thumbnail: false, value: Value::Undefined(b"0231", 0), }; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&exif_ver); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x01\x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x1a\ \x00\x00\x00\x00\ \x00\x01\x90\x00\x00\x07\x00\x00\x00\x040231\ \x00\x00\x00\x00"; 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). let jpeg = b"JPEG"; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.set_thumbnail_jpeg(jpeg); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x00\x00\x00\x00\x0e\ \x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x2c\ \x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ JPEG"; assert_eq!(buf.into_inner(), expected); } #[test] fn thumbnail_tiff() { // This is not a valid TIFF strip (only for testing). let strips: &[&[u8]] = &[b"STRIP"]; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.set_thumbnail_strips(strips); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x00\x00\x00\x00\x0e\ \x00\x02\x01\x11\x00\x04\x00\x00\x00\x01\x00\x00\x00\x2c\ \x01\x17\x00\x04\x00\x00\x00\x01\x00\x00\x00\x05\ \x00\x00\x00\x00\ STRIP"; assert_eq!(buf.into_inner(), expected); } #[test] fn primary_and_thumbnail() { let image_desc = Field { tag: Tag::ImageDescription, thumbnail: false, value: Value::Ascii(vec![b"Sample"]), }; let exif_ver = Field { tag: Tag::ExifVersion, thumbnail: false, value: Value::Undefined(b"0231", 0), }; let gps_ver = Field { tag: Tag::GPSVersionID, thumbnail: false, value: Value::Byte(vec![2, 3, 0, 0]), }; let interop_index = Field { tag: Tag::InteroperabilityIndex, thumbnail: false, value: Value::Ascii(vec![b"ABC"]), }; let jpeg = b"JPEG"; let mut writer = Writer::new(); let mut buf = Cursor::new(Vec::new()); writer.push_field(&image_desc); writer.push_field(&exif_ver); writer.push_field(&gps_ver); writer.push_field(&interop_index); writer.set_thumbnail_jpeg(jpeg); writer.write(&mut buf, false).unwrap(); let expected: &[u8] = b"\x4d\x4d\x00\x2a\x00\x00\x00\x08\ \x00\x03\x01\x0e\x00\x02\x00\x00\x00\x07\x00\x00\x00\x74\ \x87\x69\x00\x04\x00\x00\x00\x01\x00\x00\x00\x32\ \x88\x25\x00\x04\x00\x00\x00\x01\x00\x00\x00\x50\ \x00\x00\x00\x7c\ \x00\x02\x90\x00\x00\x07\x00\x00\x00\x040231\ \xa0\x05\x00\x04\x00\x00\x00\x01\x00\x00\x00\x62\ \x00\x00\x00\x00\ \x00\x01\x00\x00\x00\x01\x00\x00\x00\x04\x02\x03\x00\x00\ \x00\x00\x00\x00\ \x00\x01\x00\x01\x00\x02\x00\x00\x00\x04ABC\0\ \x00\x00\x00\x00\ Sample\0\0\ \x00\x02\x02\x01\x00\x04\x00\x00\x00\x01\x00\x00\x00\x9a\ \x02\x02\x00\x04\x00\x00\x00\x01\x00\x00\x00\x04\ \x00\x00\x00\x00\ JPEG"; assert_eq!(buf.into_inner(), expected); } #[test] fn write_twice() { let image_desc = Field { tag: Tag::ImageDescription, thumbnail: false, value: Value::Ascii(vec![b"Sample"]), }; let mut writer = Writer::new(); writer.push_field(&image_desc); let mut buf1 = Cursor::new(Vec::new()); writer.write(&mut buf1, false).unwrap(); let mut buf2 = Cursor::new(Vec::new()); writer.write(&mut buf2, false).unwrap(); assert_eq!(buf1.into_inner(), buf2.into_inner()); } #[test] fn compose_field_value() { let patterns = vec![ (Value::Byte(vec![1, 2]), (1, 2, vec![1, 2]), (1, 2, vec![1, 2])), (Value::Ascii(vec![b"a", b"b"]), (2, 4, b"a\0b\0".to_vec()), (2, 4, b"a\0b\0".to_vec())), (Value::Short(vec![0x0102, 0x0304]), (3, 2, b"\x01\x02\x03\x04".to_vec()), (3, 2, b"\x02\x01\x04\x03".to_vec())), (Value::Long(vec![0x01020304, 0x05060708]), (4, 2, b"\x01\x02\x03\x04\x05\x06\x07\x08".to_vec()), (4, 2, b"\x04\x03\x02\x01\x08\x07\x06\x05".to_vec())), (Value::Rational(vec![Rational { num: 1, denom: 2}, Rational { num: 3, denom: 4}]), (5, 2, b"\0\0\0\x01\0\0\0\x02\0\0\0\x03\0\0\0\x04".to_vec()), (5, 2, b"\x01\0\0\0\x02\0\0\0\x03\0\0\0\x04\0\0\0".to_vec())), (Value::SByte(vec![-2, -128]), (6, 2, b"\xfe\x80".to_vec()), (6, 2, b"\xfe\x80".to_vec())), (Value::Undefined(b"abc", 0), (7, 3, b"abc".to_vec()), (7, 3, b"abc".to_vec())), (Value::SShort(vec![-2, -0x8000]), (8, 2, b"\xff\xfe\x80\x00".to_vec()), (8, 2, b"\xfe\xff\x00\x80".to_vec())), (Value::SLong(vec![-2, -0x80000000]), (9, 2, b"\xff\xff\xff\xfe\x80\x00\x00\x00".to_vec()), (9, 2, b"\xfe\xff\xff\xff\x00\x00\x00\x80".to_vec())), (Value::SRational(vec![SRational { num: -1, denom: -2}, SRational { num: -3, denom: -4}]), (10, 2, b"\xff\xff\xff\xff\xff\xff\xff\xfe\ \xff\xff\xff\xfd\xff\xff\xff\xfc".to_vec()), (10, 2, b"\xff\xff\xff\xff\xfe\xff\xff\xff\ \xfd\xff\xff\xff\xfc\xff\xff\xff".to_vec())), (Value::Float(vec![2.5, -0.5]), (11, 2, b"\x40\x20\x00\x00\xbf\x00\x00\x00".to_vec()), (11, 2, b"\x00\x00\x20\x40\x00\x00\x00\xbf".to_vec())), (Value::Double(vec![2.5, -0.5]), (12, 2, b"\x40\x04\x00\x00\x00\x00\x00\x00\ \xbf\xe0\x00\x00\x00\x00\x00\x00".to_vec()), (12, 2, b"\x00\x00\x00\x00\x00\x00\x04\x40\ \x00\x00\x00\x00\x00\x00\xe0\xbf".to_vec())), ]; for (val, be, le) in patterns.into_iter() { assert_eq!(compose_value::(&val).unwrap(), be); assert_eq!(compose_value::(&val).unwrap(), le); } } } kamadak-exif-0.3.1/tests/exif.jpg010064400234210023421000000023541317305666600150740ustar0000000000000000JFIFHH^ExifMM* Vbj(2riTest imageFHH2016:05:04 03:02:010230JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?] W Slw(C     C    ?pzٳaMz.ʵ^r9GCP@ 16\T9AV?m9 ʯՔw|8.hۏڸGkamadak-exif-0.3.1/tests/exif.tif010064400234210023421000000011251306122264400150540ustar0000000000000000MM* @; (i0230Test imageHHkamadak-exif-0.3.1/tests/platform.rs010064400234210023421000000027661305430532700156250ustar0000000000000000// // Copyright (c) 2016 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // use std::mem; // This library assumes that usize is not smaller than u32. #[test] fn size_of_usize() { assert!(mem::size_of::() >= mem::size_of::()); } kamadak-exif-0.3.1/tests/rwrcmp.rs010064400234210023421000000177701317305666600153270ustar0000000000000000// // Copyright (c) 2017 KAMADA Ken'ichi. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF // SUCH DAMAGE. // //! Read, write, re-read, and compare the results. //! //! This test can be also compiled as a command-line program. extern crate exif; use std::env; use std::fs::File; use std::io::{BufReader, Cursor}; use std::path::Path; #[cfg(not(test))] use exif::Error; use exif::{Reader, Value, Tag}; use exif::experimental::Writer; #[test] fn exif_jpg() { rwr_compare("tests/exif.jpg"); } #[test] fn exif_tif() { rwr_compare("tests/exif.tif"); } fn main() { for path in env::args_os().skip(1) { rwr_compare(&path); } } fn rwr_compare

(path: P) where P: AsRef { let path = path.as_ref(); // Read. let file = File::open(path).unwrap(); #[cfg(test)] let reader1 = Reader::new(&mut BufReader::new(&file)).unwrap(); #[cfg(not(test))] let reader1 = match Reader::new(&mut BufReader::new(&file)) { Ok(reader) => reader, Err(e) => { println!("{}: {}: Skipped", path.display(), e); return; }, }; let strips = get_strips(&reader1, false); let tn_strips = get_strips(&reader1, true); let tiles = get_tiles(&reader1, false); let tn_jpeg = get_jpeg(&reader1, true); // Write. let mut writer = Writer::new(); for f in reader1.fields() { writer.push_field(f); } if let Some(ref strips) = strips { writer.set_strips(strips); } if let Some(ref tn_strips) = tn_strips { writer.set_thumbnail_strips(tn_strips); } if let Some(ref tiles) = tiles { writer.set_tiles(tiles); } if let Some(ref tn_jpeg) = tn_jpeg { writer.set_thumbnail_jpeg(tn_jpeg); } let mut out = Cursor::new(Vec::new()); #[cfg(test)] writer.write(&mut out, reader1.little_endian()).unwrap(); #[cfg(not(test))] match writer.write(&mut out, reader1.little_endian()) { Ok(_) => {}, Err(Error::NotSupported(_)) => { println!("{}: Contains unknown type", path.display()); return; }, e => e.unwrap(), } let out = out.into_inner(); // Re-read. let reader2 = Reader::new(&mut &out[..]).unwrap(); // Sort the fields (some files have wrong tag order). let mut fields1 = reader1.fields().iter().map(|f| f).collect::>(); fields1.sort_by_key(|f| f.tag.number()); let mut fields2 = reader2.fields().iter().map(|f| f).collect::>(); fields2.sort_by_key(|f| f.tag.number()); // Compare. assert_eq!(reader1.fields().len(), reader2.fields().len()); for (f1, f2) in fields1.iter().zip(fields2.iter()) { assert_eq!(f1.tag, f2.tag); assert_eq!(f1.thumbnail, f2.thumbnail); match f1.tag { Tag::StripOffsets | Tag::TileOffsets | Tag::JPEGInterchangeFormat => continue, _ => {}, } compare_field_value(&f1.value, &f2.value); } assert_eq!(get_strips(&reader2, false), strips); assert_eq!(get_strips(&reader2, true), tn_strips); assert_eq!(get_tiles(&reader2, false), tiles); assert_eq!(get_jpeg(&reader2, true), tn_jpeg); } // Compare field values. fn compare_field_value(value1: &Value, value2: &Value) { // The TIFF standard requires that BYTE, SHORT, or LONG values should // be accepted for any unsigned integer field. if let (Some(it1), Some(it2)) = (value1.iter_uint(), value2.iter_uint()) { assert!(it1.eq(it2)); return; } // Compare other fields strictly. match (value1, value2) { (&Value::Ascii(ref v1), &Value::Ascii(ref v2)) => assert_eq!(v1, v2), (&Value::Rational(ref v1), &Value::Rational(ref v2)) => { assert_eq!(v1.len(), v2.len()); for (r1, r2) in v1.iter().zip(v2.iter()) { assert_eq!(r1.num, r2.num); assert_eq!(r1.denom, r2.denom); } }, (&Value::SByte(ref v1), &Value::SByte(ref v2)) => assert_eq!(v1, v2), (&Value::Undefined(ref v1, _), &Value::Undefined(ref v2, _)) => assert_eq!(v1, v2), (&Value::SShort(ref v1), &Value::SShort(ref v2)) => assert_eq!(v1, v2), (&Value::SLong(ref v1), &Value::SLong(ref v2)) => assert_eq!(v1, v2), (&Value::SRational(ref v1), &Value::SRational(ref v2)) => { assert_eq!(v1.len(), v2.len()); for (r1, r2) in v1.iter().zip(v2.iter()) { assert_eq!(r1.num, r2.num); assert_eq!(r1.denom, r2.denom); } }, (&Value::Float(ref v1), &Value::Float(ref v2)) => assert_eq!(v1, v2), (&Value::Double(ref v1), &Value::Double(ref v2)) => assert_eq!(v1, v2), _ => panic!(format!("{:?} != {:?}", value1, value2)), } } fn get_strips(reader: &Reader, thumbnail: bool) -> Option> { let offsets = reader.get_field(Tag::StripOffsets, thumbnail) .and_then(|f| f.value.iter_uint()); let counts = reader.get_field(Tag::StripByteCounts, thumbnail) .and_then(|f| f.value.iter_uint()); let (offsets, counts) = match (offsets, counts) { (Some(offsets), Some(counts)) => (offsets, counts), (None, None) => return None, _ => panic!("inconsistent strip offsets and byte counts"), }; let buf = reader.buf(); assert_eq!(offsets.len(), counts.len()); let strips = offsets.zip(counts).map( |(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect(); Some(strips) } fn get_tiles(reader: &Reader, thumbnail: bool) -> Option> { let offsets = reader.get_field(Tag::TileOffsets, thumbnail) .and_then(|f| f.value.iter_uint()); let counts = reader.get_field(Tag::TileByteCounts, thumbnail) .and_then(|f| f.value.iter_uint()); let (offsets, counts) = match (offsets, counts) { (Some(offsets), Some(counts)) => (offsets, counts), (None, None) => return None, _ => panic!("inconsistent tile offsets and byte counts"), }; assert_eq!(offsets.len(), counts.len()); let buf = reader.buf(); let strips = offsets.zip(counts).map( |(ofs, cnt)| &buf[ofs as usize .. (ofs + cnt) as usize]).collect(); Some(strips) } fn get_jpeg(reader: &Reader, thumbnail: bool) -> Option<&[u8]> { let offset = reader.get_field(Tag::JPEGInterchangeFormat, thumbnail) .and_then(|f| f.value.get_uint(0)); let len = reader.get_field(Tag::JPEGInterchangeFormatLength, thumbnail) .and_then(|f| f.value.get_uint(0)); let (offset, len) = match (offset, len) { (Some(offset), Some(len)) => (offset as usize, len as usize), (None, None) => return None, _ => panic!("inconsistent JPEG offset and length"), }; let buf = reader.buf(); Some(&buf[offset..offset+len]) }