Skip to content

Commit cee1e36

Browse files
committed
parse JFIF APP0 sections and expose Density with DensityUnits
1 parent c8e2525 commit cee1e36

File tree

3 files changed

+93
-9
lines changed

3 files changed

+93
-9
lines changed

src/decoder.rs

+12-7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use byteorder::ReadBytesExt;
22
use error::{Error, Result, UnsupportedFeature};
33
use huffman::{fill_default_mjpeg_tables, HuffmanDecoder, HuffmanTable};
44
use marker::Marker;
5-
use parser::{AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, EntropyCoding, FrameInfo,
5+
use parser::{AdobeColorTransform, AppData, CodingProcess, Component, Dimensions, JfifApp0, Density, EntropyCoding, FrameInfo,
66
parse_app, parse_com, parse_dht, parse_dqt, parse_dri, parse_sof, parse_sos, ScanInfo};
77
use upsampler::Upsampler;
88
use std::cmp;
@@ -45,6 +45,8 @@ pub struct ImageInfo {
4545
pub height: u16,
4646
/// The pixel format of the image.
4747
pub pixel_format: PixelFormat,
48+
/// The density of the image, if available.
49+
pub density: Option<Density>,
4850
}
4951

5052
/// JPEG decoder
@@ -58,7 +60,7 @@ pub struct Decoder<R> {
5860

5961
restart_interval: u16,
6062
color_transform: Option<AdobeColorTransform>,
61-
is_jfif: bool,
63+
jfif_app0: Option<JfifApp0>,
6264
is_mjpeg: bool,
6365

6466
// Used for progressive JPEGs.
@@ -78,7 +80,7 @@ impl<R: Read> Decoder<R> {
7880
quantization_tables: [None, None, None, None],
7981
restart_interval: 0,
8082
color_transform: None,
81-
is_jfif: false,
83+
jfif_app0: None,
8284
is_mjpeg: false,
8385
coefficients: Vec::new(),
8486
coefficients_finished: [0; MAX_COMPONENTS],
@@ -99,10 +101,13 @@ impl<R: Read> Decoder<R> {
99101
_ => panic!(),
100102
};
101103

104+
let density = self.jfif_app0.as_ref().map(|j| j.density.clone());
105+
102106
Some(ImageInfo {
103107
width: frame.image_size.width,
104108
height: frame.image_size.height,
105-
pixel_format: pixel_format,
109+
pixel_format,
110+
density,
106111
})
107112
},
108113
None => None,
@@ -274,7 +279,7 @@ impl<R: Read> Decoder<R> {
274279
if let Some(data) = parse_app(&mut self.reader, marker)? {
275280
match data {
276281
AppData::Adobe(color_transform) => self.color_transform = Some(color_transform),
277-
AppData::Jfif => {
282+
AppData::Jfif(jfif) => {
278283
// From the JFIF spec:
279284
// "The APP0 marker is used to identify a JPEG FIF file.
280285
// The JPEG FIF APP0 marker is mandatory right after the SOI marker."
@@ -286,7 +291,7 @@ impl<R: Read> Decoder<R> {
286291
}
287292
*/
288293

289-
self.is_jfif = true;
294+
self.jfif_app0 = Some(jfif);
290295
},
291296
AppData::Avi1 => self.is_mjpeg = true,
292297
}
@@ -329,7 +334,7 @@ impl<R: Read> Decoder<R> {
329334
}
330335

331336
let frame = self.frame.as_ref().unwrap();
332-
compute_image(&frame.components, &planes, frame.image_size, self.is_jfif, self.color_transform)
337+
compute_image(&frame.components, &planes, frame.image_size, self.jfif_app0.is_some(), self.color_transform)
333338
}
334339

335340
fn read_marker(&mut self) -> Result<Marker> {

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extern crate rayon;
3434

3535
pub use decoder::{Decoder, ImageInfo, PixelFormat};
3636
pub use error::{Error, UnsupportedFeature};
37+
pub use parser::{Density, DensityUnits};
3738

3839
mod decoder;
3940
mod error;

src/parser.rs

+80-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,45 @@ pub struct Dimensions {
1212
pub height: u16,
1313
}
1414

15+
/// The image density, in x and y directions with a common unit
16+
#[derive(Clone, Copy, Debug, PartialEq)]
17+
pub struct Density {
18+
/// The pixel density, measured in `units`, in the x direction
19+
pub x: u16,
20+
/// The pixel density, measured in `units`, in the y direction
21+
pub y: u16,
22+
/// The unit of measurement that both `x` and `y` are specified in.
23+
pub units: DensityUnits,
24+
}
25+
26+
/// The different units that `x` and `y` pixel density can be measured in
27+
#[derive(Clone, Copy, Debug, PartialEq)]
28+
pub enum DensityUnits {
29+
/// no units, `x` and `y` specify the pixel aspect ratio
30+
PixelAspectRatio,
31+
/// `x` and `y` are dots per inch
32+
DotsPerInch,
33+
/// `x` and `y` are dots per cm
34+
DotsPerCm,
35+
}
36+
37+
#[derive(Clone, Copy, Debug, PartialEq)]
38+
struct Thumbnail {
39+
pub width: u8,
40+
pub height: u8,
41+
42+
// XXX: Thumbnail data is "(3n bytes) Packed (24-bit) RGB values for the thumbnail
43+
// pixels, n = Xthumbnail * Ythumbnail".
44+
// data: Vec<u8>
45+
}
46+
47+
#[derive(Clone, Copy, Debug, PartialEq)]
48+
pub struct JfifApp0 {
49+
// version: &[u8; 4],
50+
pub density: Density,
51+
thumbnail: Thumbnail,
52+
}
53+
1554
#[derive(Clone, Copy, Debug, PartialEq)]
1655
pub enum EntropyCoding {
1756
Huffman,
@@ -65,7 +104,7 @@ pub struct Component {
65104
#[derive(Debug)]
66105
pub enum AppData {
67106
Adobe(AdobeColorTransform),
68-
Jfif,
107+
Jfif(JfifApp0),
69108
Avi1,
70109
}
71110

@@ -472,6 +511,43 @@ pub fn parse_com<R: Read>(reader: &mut R) -> Result<Vec<u8>> {
472511
Ok(buffer)
473512
}
474513

514+
// https://www.w3.org/Graphics/JPEG/jfif3.pdf
515+
pub fn parse_jfif_app0<R: Read>(reader: &mut R, length: usize) -> Result<JfifApp0> {
516+
// Total length of APP0 = 16 bytes + 3 * n, where n = Xthumbnail * Ythumbnail
517+
// We already read the 2-byte length and 5-byte identifier, so at least 9 bytes remain.
518+
// We are going to ignore the thumbnail for now.
519+
if length < 9 {
520+
return Err(Error::Format("JFIF APP0 with invalid length".to_owned()));
521+
}
522+
523+
// version = 0x0102
524+
skip_bytes(reader, 2)?;
525+
526+
let units = match reader.read_u8()? {
527+
0 => DensityUnits::PixelAspectRatio,
528+
1 => DensityUnits::DotsPerInch,
529+
2 => DensityUnits::DotsPerCm,
530+
_ => return Err(Error::Format("invalid density units in APP0".to_owned())),
531+
};
532+
let x_density = reader.read_u16::<BigEndian>()?;
533+
let y_density = reader.read_u16::<BigEndian>()?;
534+
535+
let x_thumbnail = reader.read_u8()?;
536+
let y_thumbnail = reader.read_u8()?;
537+
538+
Ok(JfifApp0 {
539+
density: Density {
540+
x: x_density,
541+
y: y_density,
542+
units,
543+
},
544+
thumbnail: Thumbnail {
545+
width: x_thumbnail,
546+
height: y_thumbnail,
547+
}
548+
})
549+
}
550+
475551
// Section B.2.4.6
476552
pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppData>> {
477553
let length = read_length(reader, marker)?;
@@ -487,7 +563,9 @@ pub fn parse_app<R: Read>(reader: &mut R, marker: Marker) -> Result<Option<AppDa
487563

488564
// http://www.w3.org/Graphics/JPEG/jfif3.pdf
489565
if &buffer[0 .. 5] == &[b'J', b'F', b'I', b'F', b'\0'] {
490-
result = Some(AppData::Jfif);
566+
let jfif = parse_jfif_app0(reader, length - bytes_read)?;
567+
bytes_read += 9;
568+
result = Some(AppData::Jfif(jfif));
491569
// https://sno.phy.queensu.ca/~phil/exiftool/TagNames/JPEG.html#AVI1
492570
} else if &buffer[0 .. 5] == &[b'A', b'V', b'I', b'1', b'\0'] {
493571
result = Some(AppData::Avi1);

0 commit comments

Comments
 (0)