Skip to content

Commit 593b6dc

Browse files
committed
Implement iptc_metadata for TiffDecoder
1 parent fb1df28 commit 593b6dc

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

src/codecs/tiff.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::io::{self, BufRead, Cursor, Read, Seek, Write};
99
use std::marker::PhantomData;
1010
use std::mem;
1111

12-
use tiff::decoder::{Decoder, DecodingResult};
12+
use tiff::decoder::{Decoder, DecodingResult, ifd::Value};
1313
use tiff::tags::Tag;
1414

1515
use crate::color::{ColorType, ExtendedColorType};
@@ -21,6 +21,8 @@ use crate::metadata::Orientation;
2121
use crate::{utils, ImageDecoder, ImageEncoder, ImageFormat};
2222

2323
const TAG_XML_PACKET: Tag = Tag::Unknown(700);
24+
const TAG_PHOTOSHOP: Tag = Tag::Unknown(34377);
25+
const TAG_RICHTIFF_IPTC: Tag = Tag::Unknown(33723);
2426

2527
/// Decoder for TIFF images.
2628
pub struct TiffDecoder<R>
@@ -284,6 +286,30 @@ impl<R: BufRead + Seek> ImageDecoder for TiffDecoder<R> {
284286
.map_err(ImageError::from_tiff_decode)
285287
}
286288

289+
fn iptc_metadata(&mut self) -> ImageResult<Option<Vec<u8>>> {
290+
let Some(decoder) = &mut self.inner else {
291+
return Ok(None);
292+
};
293+
294+
if let Ok(value) = decoder.get_tag(TAG_PHOTOSHOP) {
295+
let mut result = Vec::new();
296+
value_to_bytes(value, &mut result)?;
297+
return Ok(Some(result));
298+
}
299+
300+
let value = match decoder.get_tag(TAG_RICHTIFF_IPTC) {
301+
Ok(value) => value,
302+
Err(tiff::TiffError::FormatError(tiff::TiffFormatError::RequiredTagNotFound(_))) => {
303+
return Ok(None);
304+
}
305+
Err(err) => return Err(ImageError::from_tiff_decode(err)),
306+
};
307+
308+
let mut result = Vec::new();
309+
value_to_bytes(value, &mut result)?;
310+
Ok(Some(result))
311+
}
312+
287313
fn orientation(&mut self) -> ImageResult<Orientation> {
288314
if let Some(decoder) = &mut self.inner {
289315
Ok(decoder
@@ -510,3 +536,42 @@ impl<W: Write + Seek> ImageEncoder for TiffEncoder<W> {
510536
self.encode(buf, width, height, color_type)
511537
}
512538
}
539+
540+
/// This method converts a `Value` to a vector of bytes. A `Value` in Tiff can have different
541+
/// types, e.g. a byte, a short or a float. This method intents to convert all these types to
542+
/// a vector of bytes (e.g. a u32 can be represented as a [u8; 4]). However, since this is only
543+
/// intended to parse values stored in XMP, IPTC and EXIF metadata sections, we ignore / return
544+
/// an error on a few Values that don't make sense in this context (e.g. Value::Ifd).
545+
fn value_to_bytes(value: Value, bytes: &mut Vec<u8>) -> ImageResult<()> {
546+
match value {
547+
Value::Byte(byte) => bytes.push(byte),
548+
Value::SignedByte(sbyte) => bytes.push(sbyte as u8),
549+
Value::Short(short) => bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 2]>(short)),
550+
Value::SignedShort(sshort) => {
551+
bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 2]>(sshort))
552+
}
553+
Value::Signed(signed) => bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 4]>(signed)),
554+
Value::SignedBig(big) => bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 8]>(big)),
555+
Value::Unsigned(unsigned) => {
556+
bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 4]>(unsigned))
557+
}
558+
Value::UnsignedBig(big) => bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 8]>(big)),
559+
Value::Float(float) => bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 4]>(float)),
560+
Value::Double(double) => bytes.extend_from_slice(&bytemuck::cast::<_, [u8; 8]>(double)),
561+
Value::List(values) => {
562+
for value in values {
563+
value_to_bytes(value, bytes)?;
564+
}
565+
}
566+
Value::Ascii(str) => bytes.extend_from_slice(str.as_bytes()),
567+
_ => {
568+
return Err(ImageError::Unsupported(
569+
UnsupportedError::from_format_and_kind(
570+
ImageFormat::Tiff.into(),
571+
UnsupportedErrorKind::GenericFeature("unable to process metadata".to_owned()),
572+
),
573+
))
574+
}
575+
}
576+
Ok(())
577+
}
1.55 KB
Binary file not shown.

tests/metadata.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,22 @@ fn test_read_iptc_png() -> Result<(), image::ImageError> {
116116

117117
Ok(())
118118
}
119+
120+
#[test]
121+
#[cfg(feature = "tiff")]
122+
fn test_read_iptc_tiff() -> Result<(), image::ImageError> {
123+
const IPTC_TIFF_PATH: &str = "tests/images/tiff/testsuite/l1_iptc.tiff";
124+
const EXPECTED_METADATA: &[u8] = &[
125+
28, 2, 90, 0, 8, 75, 105, 110, 103, 115, 116, 111, 110, 28, 2, 0, 0, 2, 0, 4,
126+
];
127+
128+
let img_path = PathBuf::from_str(IPTC_TIFF_PATH).unwrap();
129+
130+
let data = fs::read(img_path)?;
131+
let mut tiff_decoder = TiffDecoder::new(std::io::Cursor::new(data))?;
132+
let metadata = tiff_decoder.iptc_metadata()?;
133+
assert!(metadata.is_some());
134+
assert_eq!(EXPECTED_METADATA, metadata.unwrap());
135+
136+
Ok(())
137+
}

0 commit comments

Comments
 (0)