1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
//! Fast reader for JPEG and PNG comments and dimensions.
//!
//! The `pb-imgsize` crate provides a reader for JPEG and PNG images that can
//! quickly extract the image's dimensions and any comments embedded in the
//! image.
//!
//! For PNG images, the dimensions are extracted from the IHDR chunk, and the
//! comments are extracted from tEXt chunks with the keyword "comment".
//!
//! For JPEG images, the dimensions are extracted from the SOFx chunk, and the
//! comments are extracted from COM chunks.
//!
//! The reader is fast because it only reads the chunks that are necessary to
//! extract the dimensions and comments. It does not decode the image data.
//!
//! The reader does not attempt to read EXIF data.
//!
//! # Example
//!
//! ```
//! let data = include_bytes!("buttercups.jpg");
//! let metadata = pb_imgsize::read_bytes(data).unwrap();
//! assert_eq!(512, metadata.width);
//! assert_eq!(341, metadata.height);
//! assert_eq!(vec![b"Buttercups".to_vec()], metadata.comments);
//! ```
mod jpeg;
mod png;
use std::fmt::Display;
use std::io;
use std::path::Path;
pub use jpeg::JpegDecodingError;
pub use png::PngDecodingError;
/// An error that occurred while reading an image.
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Decoding(DecodingError),
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}
impl From<DecodingError> for Error {
fn from(e: DecodingError) -> Self {
Error::Decoding(e)
}
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
Error::Io(e) => write!(f, "IO error: {}", e),
Error::Decoding(e) => write!(f, "Decoding error: {}", e),
}
}
}
impl std::error::Error for Error {}
/// An error that occurred while decoding an image.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodingError {
// #[error("Unknown magic number in image data: 0x{0:08x}")]
UnknownMagic(u32),
// #[error(transparent)]
Jpeg(jpeg::JpegDecodingError),
// #[error(transparent)]
Png(png::PngDecodingError),
// #[error("Image data too short: {0} bytes")]
TooShort(usize),
}
impl From<jpeg::JpegDecodingError> for DecodingError {
fn from(e: jpeg::JpegDecodingError) -> Self {
DecodingError::Jpeg(e)
}
}
impl From<png::PngDecodingError> for DecodingError {
fn from(e: png::PngDecodingError) -> Self {
DecodingError::Png(e)
}
}
impl Display for DecodingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
DecodingError::UnknownMagic(magic) => {
write!(f, "Unknown magic number: 0x{:08x}", magic)
}
DecodingError::Jpeg(e) => write!(f, "JPEG decoding error: {}", e),
DecodingError::Png(e) => write!(f, "PNG decoding error: {}", e),
DecodingError::TooShort(n) => write!(f, "Image data too short: {} bytes", n),
}
}
}
/// An image's dimensions, along with any comments found in the data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageMetadata {
pub width: u32,
pub height: u32,
pub comments: Vec<Vec<u8>>,
}
/// Reads the dimensions and comments of an image from a file.
///
/// This function reads the dimensions and comments of an image from a file. It
/// returns an `ImageMetadata` struct containing the width and height of the
/// image, as well as any comments found in the image.
///
/// Note: This function works by reading the entire file into memory.
///
/// # Arguments
///
/// * `path` - A path to the image file.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), pb_imgsize::Error> {
/// let metadata = pb_imgsize::read_file("src/buttercups.jpg")?;
/// assert_eq!(metadata, pb_imgsize::ImageMetadata {
/// width: 512,
/// height: 341,
/// comments: vec![b"Buttercups".to_vec()],
/// });
/// # Ok(())
/// # }
pub fn read_file(path: impl AsRef<Path>) -> Result<ImageMetadata, Error> {
let buf = std::fs::read(path)?;
Ok(read_bytes(&buf)?)
}
/// Reads the dimensions and comments of an image from a byte slice.
///
/// This function reads the dimensions and comments of an image from a byte
/// slice. It returns an `ImageMetadata` struct containing the width and height
/// of the image, as well as any comments found in the image.
///
/// # Arguments
///
/// * `data` - A byte slice containing the image data.
///
/// # Examples
///
/// ```
/// # fn main() -> Result<(), pb_imgsize::Error> {
/// use pb_imgsize::read_bytes;
///
/// let data = include_bytes!("buttercups.jpg");
/// let metadata = read_bytes(data)?;
/// assert_eq!(metadata, pb_imgsize::ImageMetadata {
/// width: 512,
/// height: 341,
/// comments: vec![b"Buttercups".to_vec()]
/// });
/// # Ok(())
/// # }
/// ```
pub fn read_bytes(data: &[u8]) -> Result<ImageMetadata, DecodingError> {
if data.len() < 4 {
Err(DecodingError::TooShort(0))
} else if data.starts_with(b"\xff\xd8") {
Ok(jpeg::read_jpeg_data(data)?)
} else if data.starts_with(b"\x89PNG") {
Ok(png::read_png_data(data)?)
} else {
Err(DecodingError::UnknownMagic(u32::from_be_bytes([
data[0], data[1], data[2], data[3],
])))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_jpeg_file() {
let metadata = read_file(Path::new("src/buttercups.jpg")).unwrap();
assert_eq!(512, metadata.width);
assert_eq!(341, metadata.height);
let comments = metadata
.comments
.iter()
.map(|c| String::from_utf8_lossy(c))
.collect::<Vec<_>>();
assert_eq!(vec!["Buttercups".to_string()], comments);
}
#[test]
fn test_png_file() {
let metadata = read_file(Path::new("src/watercolors.png")).unwrap();
assert_eq!(400, metadata.width);
assert_eq!(224, metadata.height);
let comments = metadata
.comments
.iter()
.map(|c| String::from_utf8_lossy(c))
.collect::<Vec<_>>();
assert_eq!(vec!["Abstract watercolors".to_string()], comments);
}
}