Support Exif in PNG.
This commit is contained in:
parent
b99c21236e
commit
6e3c37d591
3
README
3
README
|
@ -9,6 +9,7 @@ Exif parsing library written in pure Rust
|
||||||
- TIFF and some RAW image formats based on it
|
- TIFF and some RAW image formats based on it
|
||||||
- JPEG
|
- JPEG
|
||||||
- HEIF and coding-specific variations including HEIC and AVIF
|
- HEIF and coding-specific variations including HEIC and AVIF
|
||||||
|
- PNG
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
@ -42,3 +43,5 @@ Standards
|
||||||
- TIFF Revision 6.0
|
- TIFF Revision 6.0
|
||||||
- ISO/IEC 14496-12:2015
|
- ISO/IEC 14496-12:2015
|
||||||
- ISO/IEC 23008-12:2017
|
- ISO/IEC 23008-12:2017
|
||||||
|
- PNG Specification, Version 1.2
|
||||||
|
- Extensions to the PNG 1.2 Specification, version 1.5.0
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
//!
|
//!
|
||||||
//! This library parses Exif attributes in a raw Exif data block.
|
//! This library parses Exif attributes in a raw Exif data block.
|
||||||
//! It can also read Exif data directly from some image formats
|
//! It can also read Exif data directly from some image formats
|
||||||
//! including TIFF, JPEG, and HEIF.
|
//! including TIFF, JPEG, HEIF, and PNG.
|
||||||
//!
|
//!
|
||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
|
@ -187,6 +187,7 @@ mod endian;
|
||||||
mod error;
|
mod error;
|
||||||
mod isobmff;
|
mod isobmff;
|
||||||
mod jpeg;
|
mod jpeg;
|
||||||
|
mod png;
|
||||||
mod reader;
|
mod reader;
|
||||||
mod tag;
|
mod tag;
|
||||||
mod tiff;
|
mod tiff;
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 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 crate::endian::{Endian, BigEndian};
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::util::discard_exact;
|
||||||
|
|
||||||
|
// PNG file signature [PNG12 12.12].
|
||||||
|
const PNG_SIG: [u8; 8] = *b"\x89PNG\x0d\x0a\x1a\x0a";
|
||||||
|
// The four-byte chunk type for Exif data.
|
||||||
|
const EXIF_CHUNK_TYPE: [u8; 4] = *b"eXIf";
|
||||||
|
|
||||||
|
// Get the contents of the eXIf chunk from a PNG file.
|
||||||
|
pub fn get_exif_attr<R>(reader: &mut R)
|
||||||
|
-> Result<Vec<u8>, 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 PNG file")),
|
||||||
|
r => r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The location of the eXIf chunk is restricted [PNGEXT150 3.7], but this
|
||||||
|
// reader is liberal about it.
|
||||||
|
fn get_exif_attr_sub<R>(reader: &mut R)
|
||||||
|
-> Result<Vec<u8>, Error> where R: io::BufRead {
|
||||||
|
let mut sig = [0u8; 8];
|
||||||
|
reader.read_exact(&mut sig)?;
|
||||||
|
if sig != PNG_SIG {
|
||||||
|
return Err(Error::InvalidFormat("Not a PNG file"));
|
||||||
|
}
|
||||||
|
// Scan the series of chunks.
|
||||||
|
loop {
|
||||||
|
let mut lenbuf = Vec::new();
|
||||||
|
match reader.by_ref().take(4).read_to_end(&mut lenbuf)? {
|
||||||
|
0 => return Err(Error::NotFound("PNG")),
|
||||||
|
1..=3 => return Err(io::Error::new(io::ErrorKind::UnexpectedEof,
|
||||||
|
"truncated chunk").into()),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
let len = BigEndian::loadu32(&lenbuf, 0) as usize;
|
||||||
|
let mut ctype = [0u8; 4];
|
||||||
|
reader.read_exact(&mut ctype)?;
|
||||||
|
if ctype == EXIF_CHUNK_TYPE {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
reader.by_ref().take(len as u64).read_to_end(&mut data)?;
|
||||||
|
if data.len() != len {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::UnexpectedEof,
|
||||||
|
"truncated chunk").into());
|
||||||
|
}
|
||||||
|
return Ok(data);
|
||||||
|
}
|
||||||
|
// Chunk data and CRC.
|
||||||
|
discard_exact(reader, len + 4)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_png(buf: &[u8]) -> bool {
|
||||||
|
buf.starts_with(&PNG_SIG)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::Cursor;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn truncated() {
|
||||||
|
let sets: &[&[u8]] = &[
|
||||||
|
b"",
|
||||||
|
b"\x89",
|
||||||
|
b"\x89PNG\x0d\x0a\x1a",
|
||||||
|
];
|
||||||
|
for &data in sets {
|
||||||
|
assert_err_pat!(get_exif_attr(&mut Cursor::new(data)),
|
||||||
|
Error::InvalidFormat("Broken PNG file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = b"\x89PNG\x0d\x0a\x1a\x0a\0\0\0\x04eXIfExif".to_vec();
|
||||||
|
get_exif_attr(&mut Cursor::new(&data)).unwrap();
|
||||||
|
while let Some(_) = data.pop() {
|
||||||
|
get_exif_attr(&mut &data[..]).unwrap_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_exif() {
|
||||||
|
let data = b"\x89PNG\x0d\x0a\x1a\x0a";
|
||||||
|
assert_err_pat!(get_exif_attr(&mut Cursor::new(data)),
|
||||||
|
Error::NotFound(_));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
let data = b"\x89PNG\x0d\x0a\x1a\x0a\0\0\0\0eXIfCRC_";
|
||||||
|
assert_ok!(get_exif_attr(&mut Cursor::new(data)), []);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_empty() {
|
||||||
|
let data = b"\x89PNG\x0d\x0a\x1a\x0a\0\0\0\x02eXIf\xbe\xadCRC_";
|
||||||
|
assert_ok!(get_exif_attr(&mut Cursor::new(data)), [0xbe, 0xad]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ use std::io::Read;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::isobmff;
|
use crate::isobmff;
|
||||||
use crate::jpeg;
|
use crate::jpeg;
|
||||||
|
use crate::png;
|
||||||
use crate::tag::Tag;
|
use crate::tag::Tag;
|
||||||
use crate::tiff;
|
use crate::tiff;
|
||||||
use crate::tiff::{Field, IfdEntry, In, ProvideUnit};
|
use crate::tiff::{Field, IfdEntry, In, ProvideUnit};
|
||||||
|
@ -89,6 +90,7 @@ impl Reader {
|
||||||
/// - TIFF and some RAW image formats based on it
|
/// - TIFF and some RAW image formats based on it
|
||||||
/// - JPEG
|
/// - JPEG
|
||||||
/// - HEIF and coding-specific variations including HEIC and AVIF
|
/// - HEIF and coding-specific variations including HEIC and AVIF
|
||||||
|
/// - PNG
|
||||||
///
|
///
|
||||||
/// This method is provided for the convenience even though
|
/// This method is provided for the convenience even though
|
||||||
/// parsing containers is basically out of the scope of this library.
|
/// parsing containers is basically out of the scope of this library.
|
||||||
|
@ -100,6 +102,8 @@ impl Reader {
|
||||||
reader.read_to_end(&mut buf)?;
|
reader.read_to_end(&mut buf)?;
|
||||||
} else if jpeg::is_jpeg(&buf) {
|
} else if jpeg::is_jpeg(&buf) {
|
||||||
buf = jpeg::get_exif_attr(&mut buf.chain(reader))?;
|
buf = jpeg::get_exif_attr(&mut buf.chain(reader))?;
|
||||||
|
} else if png::is_png(&buf) {
|
||||||
|
buf = png::get_exif_attr(&mut buf.chain(reader))?;
|
||||||
} else if isobmff::is_heif(&buf) {
|
} else if isobmff::is_heif(&buf) {
|
||||||
reader.seek(io::SeekFrom::Start(0))?;
|
reader.seek(io::SeekFrom::Start(0))?;
|
||||||
buf = isobmff::get_exif_attr(reader)?;
|
buf = isobmff::get_exif_attr(reader)?;
|
||||||
|
@ -241,4 +245,14 @@ mod tests {
|
||||||
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
||||||
assert_eq!(exifver.display_value().to_string(), "2.31");
|
assert_eq!(exifver.display_value().to_string(), "2.31");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn png() {
|
||||||
|
let file = std::fs::File::open("tests/exif.png").unwrap();
|
||||||
|
let exif = Reader::new().read_from_container(
|
||||||
|
&mut std::io::BufReader::new(&file)).unwrap();
|
||||||
|
assert_eq!(exif.fields().len(), 6);
|
||||||
|
let exifver = exif.get_field(Tag::ExifVersion, In::PRIMARY).unwrap();
|
||||||
|
assert_eq!(exifver.display_value().to_string(), "2.32");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/util.rs
10
src/util.rs
|
@ -48,6 +48,16 @@ pub fn read64<R>(reader: &mut R) -> Result<u64, io::Error> where R: io::Read {
|
||||||
Ok(u64::from_be_bytes(buf))
|
Ok(u64::from_be_bytes(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn discard_exact<R>(reader: &mut R, mut len: usize)
|
||||||
|
-> Result<(), io::Error> where R: io::BufRead {
|
||||||
|
while len > 0 {
|
||||||
|
let consume_len = reader.fill_buf()?.len().min(len);
|
||||||
|
reader.consume(consume_len);
|
||||||
|
len -= consume_len;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// This function must not be called with more than 4 bytes.
|
// This function must not be called with more than 4 bytes.
|
||||||
pub fn atou16(bytes: &[u8]) -> Result<u16, Error> {
|
pub fn atou16(bytes: &[u8]) -> Result<u16, Error> {
|
||||||
if cfg!(debug_assertions) && bytes.len() >= 5 {
|
if cfg!(debug_assertions) && bytes.len() >= 5 {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 271 B |
Loading…
Reference in New Issue