Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 48 additions & 54 deletions src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,27 +356,37 @@ mod tests {
}

assert_eq!(
case("foo.png", &png_with_exif(2, 1, &exif(5)))
.unwrap()
.dimensions,
case(
"foo.png",
&PngBuilder::new().width(2).height(3).exif(&exif(5)).build(),
)
.unwrap()
.dimensions,
Dimensions {
height: 1,
height: 3,
width: 2,
},
);

assert_eq!(
case("foo.jpg", &jpeg_with_exif(1, 2, &exif(5)))
.unwrap()
.dimensions,
case(
"foo.jpg",
&JpegBuilder::new().height(2).exif(&exif(5)).build(),
)
.unwrap()
.dimensions,
Dimensions {
height: 2,
width: 1,
},
);

assert_eq!(
case("foo.jpg", &jpeg_with_exif(2, 1, &exif(6))).unwrap(),
case(
"foo.jpg",
&JpegBuilder::new().width(2).exif(&exif(6)).build(),
)
.unwrap(),
Image {
alpha: false,
bit_depth: 8,
Expand All @@ -396,7 +406,11 @@ mod tests {
);

assert_eq!(
case("foo.png", &png_with_exif(2, 1, &exif(5))).unwrap(),
case(
"foo.png",
&PngBuilder::new().width(2).exif(&exif(5)).build(),
)
.unwrap(),
Image {
alpha: false,
bit_depth: 8,
Expand All @@ -415,36 +429,32 @@ mod tests {
},
);

let image = case("foo.jpg", &jpeg_grayscale(1, 1)).unwrap();
let image = case("foo.jpg", &JpegBuilder::new().grayscale().build()).unwrap();
assert!(!image.alpha);
assert_eq!(image.bit_depth, 8);
assert_eq!(image.chroma_subsampling, Some(ChromaSubsampling::Yuv400));
assert_eq!(image.color_type, ColorType::Grayscale);

assert_eq!(
case("foo.jpg", &jpeg_with_sampling(1, 1, 0x22))
case("foo.jpg", &JpegBuilder::new().sampling(0x22).build())
.unwrap()
.chroma_subsampling,
Some(ChromaSubsampling::Yuv420),
);

assert_matches_regex!(
case("foo.jpg", &jpeg_with_sampling(1, 1, 0x41))
case("foo.jpg", &JpegBuilder::new().sampling(0x41).build())
.unwrap_err()
.to_string(),
r"^unsupported chroma subsampling 4脳1 in image `.*foo\.jpg`$",
);

let image = case(
"foo.png",
&png(
1,
1,
png::ColorType::Rgba,
png::BitDepth::Sixteen,
None,
None,
),
&PngBuilder::new()
.color(png::ColorType::Rgba)
.depth(png::BitDepth::Sixteen)
.build(),
)
.unwrap();
assert!(image.alpha);
Expand All @@ -454,14 +464,10 @@ mod tests {

let image = case(
"foo.png",
&png(
1,
1,
png::ColorType::Indexed,
png::BitDepth::One,
None,
None,
),
&PngBuilder::new()
.color(png::ColorType::Indexed)
.depth(png::BitDepth::One)
.build(),
)
.unwrap();
assert!(!image.alpha);
Expand All @@ -471,44 +477,32 @@ mod tests {
assert!(
case(
"foo.png",
&png(
1,
1,
png::ColorType::Indexed,
png::BitDepth::One,
Some(&[0]),
None,
),
&PngBuilder::new()
.color(png::ColorType::Indexed)
.depth(png::BitDepth::One)
.trns(&[0])
.build(),
)
.unwrap()
.alpha
);

let image = case(
"foo.png",
&png(
1,
1,
png::ColorType::GrayscaleAlpha,
png::BitDepth::Eight,
None,
None,
),
&PngBuilder::new()
.color(png::ColorType::GrayscaleAlpha)
.build(),
)
.unwrap();
assert!(image.alpha);
assert_eq!(image.color_type, ColorType::Grayscale);

let image = case(
"foo.png",
&png(
1,
1,
png::ColorType::Grayscale,
png::BitDepth::Two,
None,
None,
),
&PngBuilder::new()
.color(png::ColorType::Grayscale)
.depth(png::BitDepth::Two)
.build(),
)
.unwrap();
assert!(!image.alpha);
Expand All @@ -526,14 +520,14 @@ mod tests {
);

assert_matches_regex!(
case("foo.jpg", &jpeg_with_exif(2, 1, b"foo"))
case("foo.jpg", &JpegBuilder::new().width(2).exif(b"foo").build())
.unwrap_err()
.to_string(),
r"^invalid EXIF in image `.*foo\.jpg`$",
);

assert_matches_regex!(
case("foo.png", &png_with_exif(2, 1, b"foo"))
case("foo.png", &PngBuilder::new().width(2).exif(b"foo").build())
.unwrap_err()
.to_string(),
r"^invalid EXIF in image `.*foo\.png`$",
Expand Down
87 changes: 87 additions & 0 deletions src/jpeg_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use {
super::*,
::image::{DynamicImage, ImageFormat},
};

pub(crate) struct JpegBuilder {
exif: Option<Vec<u8>>,
grayscale: bool,
height: u32,
sampling: Option<u8>,
width: u32,
}

impl JpegBuilder {
pub(crate) fn build(self) -> Vec<u8> {
let image = if self.grayscale {
DynamicImage::new_luma8(self.width, self.height)
} else {
DynamicImage::new_rgb8(self.width, self.height)
};

let mut buffer = io::Cursor::new(Vec::new());

image.write_to(&mut buffer, ImageFormat::Jpeg).unwrap();

let mut bytes = buffer.into_inner();

if let Some(sampling) = self.sampling {
let sof = bytes.windows(2).position(|w| w == [0xFF, 0xC0]).unwrap();
bytes[sof + 11] = sampling;
}

if let Some(exif) = self.exif {
let mut app1 = b"Exif\0\0".to_vec();
app1.extend_from_slice(&exif);

let mut spliced = bytes[..2].to_vec();
spliced.extend_from_slice(&[0xFF, 0xE1]);
spliced.extend_from_slice(&u16::try_from(app1.len() + 2).unwrap().to_be_bytes());
spliced.extend_from_slice(&app1);
spliced.extend_from_slice(&bytes[2..]);
spliced
} else {
bytes
}
}

#[must_use]
pub(crate) fn exif(mut self, exif: &[u8]) -> Self {
self.exif = Some(exif.into());
self
}

#[must_use]
pub(crate) fn grayscale(mut self) -> Self {
self.grayscale = true;
self
}

#[must_use]
pub(crate) fn height(mut self, height: u32) -> Self {
self.height = height;
self
}

pub(crate) fn new() -> Self {
Self {
exif: None,
grayscale: false,
height: 1,
sampling: None,
width: 1,
}
}

#[must_use]
pub(crate) fn sampling(mut self, sampling: u8) -> Self {
self.sampling = Some(sampling);
self
}

#[must_use]
pub(crate) fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
}
11 changes: 7 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,12 +245,11 @@ pub use self::{

#[cfg(test)]
use {
jpeg_builder::JpegBuilder,
png_builder::PngBuilder,
std::assert_matches,
tempfile::TempDir,
test::{
assert_cbor, assert_cbor_eq, assert_encoding, exif, flac, jpeg_grayscale, jpeg_with_exif,
jpeg_with_sampling, mp3, mp3_frame, png, png_with_exif, tempdir,
},
test::{assert_cbor, assert_cbor_eq, assert_encoding, exif, flac, mp3, mp3_frame, tempdir},
unindent::unindent,
webm_builder::WebmBuilder,
};
Expand Down Expand Up @@ -420,6 +419,10 @@ mod webm_decoder;
#[cfg(test)]
mod derive;
#[cfg(test)]
mod jpeg_builder;
#[cfg(test)]
mod png_builder;
#[cfg(test)]
mod test;
#[cfg(test)]
mod webm_builder;
Expand Down
Loading