Skip to content

Commit

Permalink
Merge pull request #1078 from lovasoa/next
Browse files Browse the repository at this point in the history
Allow setting a custom pixel density in the JPEG encoder
  • Loading branch information
fintelia committed Nov 23, 2019
2 parents 27e3fa1 + 76abfcf commit 2e35996
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 7 deletions.
95 changes: 89 additions & 6 deletions src/jpeg/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,59 @@ impl<'a, W: Write + 'a> BitWriter<'a, W> {
}
}

/// Represents a unit in which the density of an image is measured
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PixelDensityUnit {
/// Represents the absence of a unit, the values indicate only a
/// [pixel aspect ratio](https://en.wikipedia.org/wiki/Pixel_aspect_ratio)
PixelAspectRatio,

/// Pixels per inch (2.54 cm)
Inches,

/// Pixels per centimeter
Centimeters,
}

/// Represents the pixel density of an image
///
/// For example, a 300 DPI image is represented by:
///
/// ```rust
/// use image::jpeg::*;
/// let hdpi = PixelDensity::dpi(300);
/// assert_eq!(hdpi, PixelDensity {density: (300,300), unit: PixelDensityUnit::Inches})
/// ```
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct PixelDensity {
/// A couple of values for (Xdensity, Ydensity)
pub density: (u16, u16),
/// The unit in which the density is measured
pub unit: PixelDensityUnit,
}

impl PixelDensity {
/// Creates the most common pixel density type:
/// the horizontal and the vertical density are equal,
/// and measured in pixels per inch.
pub fn dpi(density: u16) -> Self {
PixelDensity {
density: (density, density),
unit: PixelDensityUnit::Inches,
}
}
}

impl Default for PixelDensity {
/// Returns a pixel density with a pixel aspect ratio of 1
fn default() -> Self {
PixelDensity {
density: (1, 1),
unit: PixelDensityUnit::PixelAspectRatio,
}
}
}

/// The representation of a JPEG encoder
pub struct JPEGEncoder<'a, W: 'a> {
writer: BitWriter<'a, W>,
Expand All @@ -285,6 +338,8 @@ pub struct JPEGEncoder<'a, W: 'a> {
luma_actable: Vec<(u8, u16)>,
chroma_dctable: Vec<(u8, u16)>,
chroma_actable: Vec<(u8, u16)>,

pixel_density: PixelDensity,
}

impl<'a, W: Write> JPEGEncoder<'a, W> {
Expand Down Expand Up @@ -360,9 +415,18 @@ impl<'a, W: Write> JPEGEncoder<'a, W> {
luma_actable: la,
chroma_dctable: cd,
chroma_actable: ca,

pixel_density: PixelDensity::default(),
}
}

/// Set the pixel density of the images the encoder will encode.
/// If this method is not called, then a default pixel aspect ratio of 1x1 will be applied,
/// and no DPI information will be stored in the image.
pub fn set_pixel_density(&mut self, pixel_density: PixelDensity) {
self.pixel_density = pixel_density;
}

/// Encodes the image ```image```
/// that has dimensions ```width``` and ```height```
/// and ```ColorType``` ```c```
Expand All @@ -382,7 +446,7 @@ impl<'a, W: Write> JPEGEncoder<'a, W> {

let mut buf = Vec::new();

build_jfif_header(&mut buf);
build_jfif_header(&mut buf, self.pixel_density);
self.writer.write_segment(APP0, Some(&buf))?;

build_frame_header(
Expand Down Expand Up @@ -567,16 +631,20 @@ impl<'a, W: Write> JPEGEncoder<'a, W> {
}
}

fn build_jfif_header(m: &mut Vec<u8>) {
fn build_jfif_header(m: &mut Vec<u8>, density: PixelDensity) {
m.clear();

let _ = write!(m, "JFIF");
let _ = m.write_all(&[0]);
let _ = m.write_all(&[0x01]);
let _ = m.write_all(&[0x02]);
let _ = m.write_all(&[0]);
let _ = m.write_u16::<BigEndian>(1);
let _ = m.write_u16::<BigEndian>(1);
let _ = m.write_all(&[match density.unit {
PixelDensityUnit::PixelAspectRatio => 0x00,
PixelDensityUnit::Inches => 0x01,
PixelDensityUnit::Centimeters => 0x02,
}]);
let _ = m.write_u16::<BigEndian>(density.density.0);
let _ = m.write_u16::<BigEndian>(density.density.1);
let _ = m.write_all(&[0]);
let _ = m.write_all(&[0]);
}
Expand Down Expand Up @@ -752,7 +820,7 @@ fn copy_blocks_gray(
#[cfg(test)]
mod tests {
use super::super::JpegDecoder;
use super::JPEGEncoder;
use super::{JPEGEncoder, PixelDensity, build_jfif_header};
use color::ColorType;
use image::ImageDecoder;
use std::io::Cursor;
Expand Down Expand Up @@ -818,4 +886,19 @@ mod tests {
assert!(decoded[3] > 0x80);
}
}

#[test]
fn jfif_header_density_check() {
let mut buffer = Vec::new();
build_jfif_header(&mut buffer, PixelDensity::dpi(300));
assert_eq!(buffer, vec![
b'J', b'F', b'I', b'F',
0, 1, 2, // JFIF version 1.2
1, // density is in dpi
300u16.to_be_bytes()[0], 300u16.to_be_bytes()[1],
300u16.to_be_bytes()[0], 300u16.to_be_bytes()[1],
0, 0, // No thumbnail
]
);
}
}
2 changes: 1 addition & 1 deletion src/jpeg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//!

pub use self::decoder::JpegDecoder;
pub use self::encoder::JPEGEncoder;
pub use self::encoder::{JPEGEncoder, PixelDensity, PixelDensityUnit};

mod decoder;
mod encoder;
Expand Down

0 comments on commit 2e35996

Please sign in to comment.