diff --git a/src/decoder.rs b/src/decoder.rs index 5c52fbe7..c592eba1 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -2,7 +2,7 @@ use byteorder::ReadBytesExt; use error::{Error, Result, UnsupportedFeature}; use huffman::{fill_default_mjpeg_tables, HuffmanDecoder, HuffmanTable}; use marker::Marker; -use parser::{AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, EntropyCoding, FrameInfo, +use parser::{AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, JfifApp0, Density, EntropyCoding, FrameInfo, parse_app, parse_com, parse_dht, parse_dqt, parse_dri, parse_sof, parse_sos, ScanInfo}; use upsampler::Upsampler; use std::cmp; @@ -45,6 +45,8 @@ pub struct ImageInfo { pub height: u16, /// The pixel format of the image. pub pixel_format: PixelFormat, + /// The density of the image, if available. + pub density: Option, } /// JPEG decoder @@ -58,7 +60,7 @@ pub struct Decoder { restart_interval: u16, color_transform: Option, - is_jfif: bool, + jfif_app0: Option, is_mjpeg: bool, // Used for progressive JPEGs. @@ -78,7 +80,7 @@ impl Decoder { quantization_tables: [None, None, None, None], restart_interval: 0, color_transform: None, - is_jfif: false, + jfif_app0: None, is_mjpeg: false, coefficients: Vec::new(), coefficients_finished: [0; MAX_COMPONENTS], @@ -99,10 +101,13 @@ impl Decoder { _ => panic!(), }; + let density = self.jfif_app0.as_ref().map(|j| j.density.clone()); + Some(ImageInfo { width: frame.image_size.width, height: frame.image_size.height, - pixel_format: pixel_format, + pixel_format, + density, }) }, None => None, @@ -274,7 +279,7 @@ impl Decoder { if let Some(data) = parse_app(&mut self.reader, marker)? { match data { AppData::Adobe(color_transform) => self.color_transform = Some(color_transform), - AppData::Jfif => { + AppData::Jfif(jfif) => { // From the JFIF spec: // "The APP0 marker is used to identify a JPEG FIF file. // The JPEG FIF APP0 marker is mandatory right after the SOI marker." @@ -286,7 +291,7 @@ impl Decoder { } */ - self.is_jfif = true; + self.jfif_app0 = Some(jfif); }, AppData::Avi1 => self.is_mjpeg = true, } @@ -329,7 +334,7 @@ impl Decoder { } let frame = self.frame.as_ref().unwrap(); - compute_image(&frame.components, &planes, frame.image_size, self.is_jfif, self.color_transform) + compute_image(&frame.components, &planes, frame.image_size, self.jfif_app0.is_some(), self.color_transform) } fn read_marker(&mut self) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 48e76719..4d377094 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,7 @@ extern crate rayon; pub use decoder::{Decoder, ImageInfo, PixelFormat}; pub use error::{Error, UnsupportedFeature}; +pub use parser::{Density, DensityUnits}; mod decoder; mod error; diff --git a/src/parser.rs b/src/parser.rs index 039a75c8..139e1482 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -12,6 +12,45 @@ pub struct Dimensions { pub height: u16, } +/// The image density, in x and y directions with a common unit +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Density { + /// The pixel density, measured in `units`, in the x direction + pub x: u16, + /// The pixel density, measured in `units`, in the y direction + pub y: u16, + /// The unit of measurement that both `x` and `y` are specified in. + pub units: DensityUnits, +} + +/// The different units that `x` and `y` pixel density can be measured in +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DensityUnits { + /// no units, `x` and `y` specify the pixel aspect ratio + PixelAspectRatio, + /// `x` and `y` are dots per inch + DotsPerInch, + /// `x` and `y` are dots per cm + DotsPerCm, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +struct Thumbnail { + pub width: u8, + pub height: u8, + + // XXX: Thumbnail data is "(3n bytes) Packed (24-bit) RGB values for the thumbnail + // pixels, n = Xthumbnail * Ythumbnail". + // data: Vec +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct JfifApp0 { + // version: &[u8; 4], + pub density: Density, + thumbnail: Thumbnail, +} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum EntropyCoding { Huffman, @@ -65,7 +104,7 @@ pub struct Component { #[derive(Debug)] pub enum AppData { Adobe(AdobeColorTransform), - Jfif, + Jfif(JfifApp0), Avi1, } @@ -472,6 +511,43 @@ pub fn parse_com(reader: &mut R) -> Result> { Ok(buffer) } +// https://www.w3.org/Graphics/JPEG/jfif3.pdf +pub fn parse_jfif_app0(reader: &mut R, length: usize) -> Result { + // Total length of APP0 = 16 bytes + 3 * n, where n = Xthumbnail * Ythumbnail + // We already read the 2-byte length and 5-byte identifier, so at least 9 bytes remain. + // We are going to ignore the thumbnail for now. + if length < 9 { + return Err(Error::Format("JFIF APP0 with invalid length".to_owned())); + } + + // version = 0x0102 + skip_bytes(reader, 2)?; + + let units = match reader.read_u8()? { + 0 => DensityUnits::PixelAspectRatio, + 1 => DensityUnits::DotsPerInch, + 2 => DensityUnits::DotsPerCm, + _ => return Err(Error::Format("invalid density units in APP0".to_owned())), + }; + let x_density = reader.read_u16::()?; + let y_density = reader.read_u16::()?; + + let x_thumbnail = reader.read_u8()?; + let y_thumbnail = reader.read_u8()?; + + Ok(JfifApp0 { + density: Density { + x: x_density, + y: y_density, + units, + }, + thumbnail: Thumbnail { + width: x_thumbnail, + height: y_thumbnail, + } + }) +} + // Section B.2.4.6 pub fn parse_app(reader: &mut R, marker: Marker) -> Result> { let length = read_length(reader, marker)?; @@ -487,7 +563,9 @@ pub fn parse_app(reader: &mut R, marker: Marker) -> Result