Skip to content

Commit 7be8647

Browse files
committed
Split struct Exif into a separate file.
1 parent 71c3a81 commit 7be8647

File tree

3 files changed

+173
-127
lines changed

3 files changed

+173
-127
lines changed

src/exifimpl.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//
2+
// Copyright (c) 2017 KAMADA Ken'ichi.
3+
// All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions
7+
// are met:
8+
// 1. Redistributions of source code must retain the above copyright
9+
// notice, this list of conditions and the following disclaimer.
10+
// 2. Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15+
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17+
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18+
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19+
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20+
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21+
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22+
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23+
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24+
// SUCH DAMAGE.
25+
//
26+
27+
use std::collections::HashMap;
28+
29+
use crate::tag::Tag;
30+
use crate::tiff::{Field, IfdEntry, In, ProvideUnit};
31+
32+
/// A struct that holds the parsed Exif attributes.
33+
///
34+
/// # Examples
35+
/// ```
36+
/// # fn main() { sub(); }
37+
/// # fn sub() -> Option<()> {
38+
/// # use exif::{In, Reader, Tag};
39+
/// # let file = std::fs::File::open("tests/exif.jpg").unwrap();
40+
/// # let exif = Reader::new().read_from_container(
41+
/// # &mut std::io::BufReader::new(&file)).unwrap();
42+
/// // Get a specific field.
43+
/// let xres = exif.get_field(Tag::XResolution, In::PRIMARY)?;
44+
/// assert_eq!(xres.display_value().with_unit(&exif).to_string(),
45+
/// "72 pixels per inch");
46+
/// // Iterate over all fields.
47+
/// for f in exif.fields() {
48+
/// println!("{} {} {}", f.tag, f.ifd_num, f.display_value());
49+
/// }
50+
/// # Some(()) }
51+
/// ```
52+
pub struct Exif {
53+
// TIFF data.
54+
buf: Vec<u8>,
55+
// Exif fields. Vec is used to keep the ability to enumerate all fields
56+
// even if there are duplicates.
57+
entries: Vec<IfdEntry>,
58+
// HashMap to the index of the Vec for faster random access.
59+
entry_map: HashMap<(In, Tag), usize>,
60+
// True if the TIFF data is little endian.
61+
little_endian: bool,
62+
}
63+
64+
impl Exif {
65+
/// Constructs a new `Exif`.
66+
pub(crate) fn new(buf: Vec<u8>,
67+
entries: Vec<IfdEntry>, little_endian: bool) -> Self {
68+
let entry_map = entries.iter().enumerate()
69+
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
70+
Self {
71+
buf: buf,
72+
entries: entries,
73+
entry_map: entry_map,
74+
little_endian: little_endian,
75+
}
76+
}
77+
78+
/// Returns the slice that contains the TIFF data.
79+
#[inline]
80+
pub fn buf(&self) -> &[u8] {
81+
&self.buf[..]
82+
}
83+
84+
/// Returns an iterator of Exif fields.
85+
#[inline]
86+
pub fn fields(&self) -> impl ExactSizeIterator<Item = &Field> {
87+
self.entries.iter()
88+
.map(move |e| e.ref_field(&self.buf, self.little_endian))
89+
}
90+
91+
/// Returns true if the Exif data (TIFF structure) is in the
92+
/// little-endian byte order.
93+
#[inline]
94+
pub fn little_endian(&self) -> bool {
95+
self.little_endian
96+
}
97+
98+
/// Returns a reference to the Exif field specified by the tag
99+
/// and the IFD number.
100+
#[inline]
101+
pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> {
102+
self.entry_map.get(&(ifd_num, tag))
103+
.map(|&i| self.entries[i].ref_field(&self.buf, self.little_endian))
104+
}
105+
}
106+
107+
impl<'a> ProvideUnit<'a> for &'a Exif {
108+
fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> {
109+
self.get_field(tag, ifd_num)
110+
}
111+
}
112+
113+
#[cfg(test)]
114+
mod tests {
115+
use std::fs::File;
116+
use std::io::BufReader;
117+
use crate::reader::Reader;
118+
use crate::value::Value;
119+
use super::*;
120+
121+
#[test]
122+
fn get_field() {
123+
let file = File::open("tests/yaminabe.tif").unwrap();
124+
let exif = Reader::new().read_from_container(
125+
&mut BufReader::new(&file)).unwrap();
126+
match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value {
127+
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]),
128+
ref v => panic!("wrong variant {:?}", v)
129+
}
130+
match exif.get_field(Tag::ImageDescription, In(1)).unwrap().value {
131+
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test thumbnail"]),
132+
ref v => panic!("wrong variant {:?}", v)
133+
}
134+
match exif.get_field(Tag::ImageDescription, In(2)).unwrap().value {
135+
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test 2nd IFD"]),
136+
ref v => panic!("wrong variant {:?}", v)
137+
}
138+
}
139+
140+
#[test]
141+
fn display_value_with_unit() {
142+
let file = File::open("tests/yaminabe.tif").unwrap();
143+
let exif = Reader::new().read_from_container(
144+
&mut BufReader::new(&file)).unwrap();
145+
// No unit.
146+
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
147+
assert_eq!(exifver.display_value().with_unit(&exif).to_string(),
148+
"2.31");
149+
// Fixed string.
150+
let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
151+
assert_eq!(width.display_value().with_unit(&exif).to_string(),
152+
"17 pixels");
153+
// Unit tag (with a non-default value).
154+
let gpsalt = exif.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
155+
assert_eq!(gpsalt.display_value().with_unit(&exif).to_string(),
156+
"0.5 meters below sea level");
157+
// Unit tag is missing but the default is specified.
158+
let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
159+
assert_eq!(xres.display_value().with_unit(&exif).to_string(),
160+
"72 pixels per inch");
161+
// Unit tag is missing and the default is not specified.
162+
let gpslat = exif.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
163+
assert_eq!(gpslat.display_value().with_unit(&exif).to_string(),
164+
"10 deg 0 min 0 sec [GPSLatitudeRef missing]");
165+
}
166+
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@
9090
//! See the [upgrade guide](doc::upgrade) for API incompatibilities.
9191
9292
pub use error::{Error, PartialResult};
93+
pub use exifimpl::Exif;
9394
pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg;
94-
pub use reader::{Exif, Reader};
95+
pub use reader::Reader;
9596
pub use tag::{Context, Tag};
9697
pub use tiff::{DateTime, Field, In};
9798
pub use tiff::parse_exif;
@@ -110,6 +111,7 @@ mod tmacro;
110111
pub mod doc;
111112
mod endian;
112113
mod error;
114+
mod exifimpl;
113115
mod isobmff;
114116
mod jpeg;
115117
mod png;

src/reader.rs

Lines changed: 4 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,15 @@
2424
// SUCH DAMAGE.
2525
//
2626

27-
use std::collections::HashMap;
2827
use std::io;
2928
use std::io::Read;
3029

3130
use crate::error::{Error, PartialResult};
31+
use crate::exifimpl::Exif;
3232
use crate::isobmff;
3333
use crate::jpeg;
3434
use crate::png;
35-
use crate::tag::Tag;
3635
use crate::tiff;
37-
use crate::tiff::{Field, IfdEntry, In, ProvideUnit};
3836
use crate::webp;
3937

4038
/// A struct to parse the Exif attributes and
@@ -108,14 +106,7 @@ impl Reader {
108106
let mut parser = tiff::Parser::new();
109107
parser.continue_on_error = self.continue_on_error.then(|| Vec::new());
110108
parser.parse(&data)?;
111-
let entry_map = parser.entries.iter().enumerate()
112-
.map(|(i, e)| (e.ifd_num_tag(), i)).collect();
113-
let exif = Exif {
114-
buf: data,
115-
entries: parser.entries,
116-
entry_map: entry_map,
117-
little_endian: parser.little_endian,
118-
};
109+
let exif = Exif::new(data, parser.entries, parser.little_endian);
119110
match parser.continue_on_error {
120111
Some(v) if !v.is_empty() =>
121112
Err(Error::PartialResult(PartialResult::new(exif, v))),
@@ -158,128 +149,15 @@ impl Reader {
158149
}
159150
}
160151

161-
/// A struct that holds the parsed Exif attributes.
162-
///
163-
/// # Examples
164-
/// ```
165-
/// # fn main() { sub(); }
166-
/// # fn sub() -> Option<()> {
167-
/// # use exif::{In, Reader, Tag};
168-
/// # let file = std::fs::File::open("tests/exif.jpg").unwrap();
169-
/// # let exif = Reader::new().read_from_container(
170-
/// # &mut std::io::BufReader::new(&file)).unwrap();
171-
/// // Get a specific field.
172-
/// let xres = exif.get_field(Tag::XResolution, In::PRIMARY)?;
173-
/// assert_eq!(xres.display_value().with_unit(&exif).to_string(),
174-
/// "72 pixels per inch");
175-
/// // Iterate over all fields.
176-
/// for f in exif.fields() {
177-
/// println!("{} {} {}", f.tag, f.ifd_num, f.display_value());
178-
/// }
179-
/// # Some(()) }
180-
/// ```
181-
pub struct Exif {
182-
// TIFF data.
183-
buf: Vec<u8>,
184-
// Exif fields. Vec is used to keep the ability to enumerate all fields
185-
// even if there are duplicates.
186-
entries: Vec<IfdEntry>,
187-
// HashMap to the index of the Vec for faster random access.
188-
entry_map: HashMap<(In, Tag), usize>,
189-
// True if the TIFF data is little endian.
190-
little_endian: bool,
191-
}
192-
193-
impl Exif {
194-
/// Returns the slice that contains the TIFF data.
195-
#[inline]
196-
pub fn buf(&self) -> &[u8] {
197-
&self.buf[..]
198-
}
199-
200-
/// Returns an iterator of Exif fields.
201-
#[inline]
202-
pub fn fields(&self) -> impl ExactSizeIterator<Item = &Field> {
203-
self.entries.iter()
204-
.map(move |e| e.ref_field(&self.buf, self.little_endian))
205-
}
206-
207-
/// Returns true if the Exif data (TIFF structure) is in the
208-
/// little-endian byte order.
209-
#[inline]
210-
pub fn little_endian(&self) -> bool {
211-
self.little_endian
212-
}
213-
214-
/// Returns a reference to the Exif field specified by the tag
215-
/// and the IFD number.
216-
#[inline]
217-
pub fn get_field(&self, tag: Tag, ifd_num: In) -> Option<&Field> {
218-
self.entry_map.get(&(ifd_num, tag))
219-
.map(|&i| self.entries[i].ref_field(&self.buf, self.little_endian))
220-
}
221-
}
222-
223-
impl<'a> ProvideUnit<'a> for &'a Exif {
224-
fn get_field(self, tag: Tag, ifd_num: In) -> Option<&'a Field> {
225-
self.get_field(tag, ifd_num)
226-
}
227-
}
228-
229152
#[cfg(test)]
230153
mod tests {
231154
use std::fs::File;
232155
use std::io::BufReader;
233-
use crate::tag::Context;
156+
use crate::tag::{Context, Tag};
157+
use crate::tiff::{Field, In};
234158
use crate::value::Value;
235159
use super::*;
236160

237-
#[test]
238-
fn get_field() {
239-
let file = File::open("tests/yaminabe.tif").unwrap();
240-
let exif = Reader::new().read_from_container(
241-
&mut BufReader::new(&file)).unwrap();
242-
match exif.get_field(Tag::ImageDescription, In(0)).unwrap().value {
243-
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test image"]),
244-
ref v => panic!("wrong variant {:?}", v)
245-
}
246-
match exif.get_field(Tag::ImageDescription, In(1)).unwrap().value {
247-
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test thumbnail"]),
248-
ref v => panic!("wrong variant {:?}", v)
249-
}
250-
match exif.get_field(Tag::ImageDescription, In(2)).unwrap().value {
251-
Value::Ascii(ref vec) => assert_eq!(vec, &[b"Test 2nd IFD"]),
252-
ref v => panic!("wrong variant {:?}", v)
253-
}
254-
}
255-
256-
#[test]
257-
fn display_value_with_unit() {
258-
let file = File::open("tests/yaminabe.tif").unwrap();
259-
let exif = Reader::new().read_from_container(
260-
&mut BufReader::new(&file)).unwrap();
261-
// No unit.
262-
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
263-
assert_eq!(exifver.display_value().with_unit(&exif).to_string(),
264-
"2.31");
265-
// Fixed string.
266-
let width = exif.get_field(Tag::ImageWidth, In::PRIMARY).unwrap();
267-
assert_eq!(width.display_value().with_unit(&exif).to_string(),
268-
"17 pixels");
269-
// Unit tag (with a non-default value).
270-
let gpsalt = exif.get_field(Tag::GPSAltitude, In::PRIMARY).unwrap();
271-
assert_eq!(gpsalt.display_value().with_unit(&exif).to_string(),
272-
"0.5 meters below sea level");
273-
// Unit tag is missing but the default is specified.
274-
let xres = exif.get_field(Tag::XResolution, In::PRIMARY).unwrap();
275-
assert_eq!(xres.display_value().with_unit(&exif).to_string(),
276-
"72 pixels per inch");
277-
// Unit tag is missing and the default is not specified.
278-
let gpslat = exif.get_field(Tag::GPSLatitude, In::PRIMARY).unwrap();
279-
assert_eq!(gpslat.display_value().with_unit(&exif).to_string(),
280-
"10 deg 0 min 0 sec [GPSLatitudeRef missing]");
281-
}
282-
283161
#[test]
284162
fn yaminabe() {
285163
let file = File::open("tests/yaminabe.tif").unwrap();

0 commit comments

Comments
 (0)