Skip to content

Commit d1e0088

Browse files
committed
feat: Flipped Exif orientations (#8057)
Before, sending of images flipped in Exif led to images having wrong orientation.
1 parent a5e41b0 commit d1e0088

3 files changed

Lines changed: 41 additions & 23 deletions

File tree

src/blob.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use anyhow::{Context as _, Result, ensure, format_err};
1010
use base64::Engine as _;
1111
use futures::StreamExt;
1212
use image::ImageReader;
13-
use image::codecs::jpeg::JpegEncoder;
1413
use image::{DynamicImage, GenericImage, GenericImageView, ImageFormat, Pixel, Rgba};
14+
use image::{codecs::jpeg::JpegEncoder, metadata::Orientation};
1515
use num_traits::FromPrimitive;
1616
use tokio::{fs, task};
1717
use tokio_stream::wrappers::ReadDirStream;
@@ -362,7 +362,10 @@ impl<'a> BlobObject<'a> {
362362
return Ok(name);
363363
}
364364
let mut img = imgreader.decode().context("image decode failure")?;
365-
let orientation = exif.as_ref().map(|exif| exif_orientation(exif, context));
365+
let orientation = exif
366+
.as_ref()
367+
.map(|exif| exif_orientation(exif, context))
368+
.unwrap_or(Orientation::NoTransforms);
366369
let mut encoded = Vec::new();
367370

368371
if *vt == Viewtype::Sticker {
@@ -381,13 +384,7 @@ impl<'a> BlobObject<'a> {
381384
return Ok(name);
382385
}
383386
}
384-
385-
img = match orientation {
386-
Some(90) => img.rotate90(),
387-
Some(180) => img.rotate180(),
388-
Some(270) => img.rotate270(),
389-
_ => img,
390-
};
387+
img.apply_orientation(orientation);
391388

392389
// max_wh is the maximum image width and height, i.e. the resolution-limit.
393390
// target_wh target-resolution for resizing the image.
@@ -551,18 +548,17 @@ fn image_metadata(file: &std::fs::File) -> Result<(u64, Option<exif::Exif>)> {
551548
Ok((len, exif))
552549
}
553550

554-
fn exif_orientation(exif: &exif::Exif, context: &Context) -> i32 {
555-
if let Some(orientation) = exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY) {
556-
// possible orientation values are described at http://sylvana.net/jpegcrop/exif_orientation.html
557-
// we only use rotation, in practise, flipping is not used.
558-
match orientation.value.get_uint(0) {
559-
Some(3) => return 180,
560-
Some(6) => return 90,
561-
Some(8) => return 270,
562-
other => warn!(context, "Exif orientation value ignored: {other:?}."),
563-
}
551+
fn exif_orientation(exif: &exif::Exif, context: &Context) -> Orientation {
552+
if let Some(orientation) = exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY)
553+
&& let Some(val) = orientation.value.get_uint(0)
554+
&& let Ok(val) = TryInto::<u8>::try_into(val)
555+
{
556+
return Orientation::from_exif(val).unwrap_or({
557+
warn!(context, "Exif orientation value ignored: {val:?}.");
558+
Orientation::NoTransforms
559+
});
564560
}
565-
0
561+
Orientation::NoTransforms
566562
}
567563

568564
/// All files in the blobdir.

src/blob/blob_tests.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ async fn test_recode_image_2() {
305305
has_exif: true,
306306
original_width: 2000,
307307
original_height: 1800,
308-
orientation: 270,
308+
orientation: Some(Orientation::Rotate270),
309309
compressed_width: 1800,
310310
compressed_height: 2000,
311311
..Default::default()
@@ -336,6 +336,28 @@ async fn test_recode_image_2() {
336336
assert_correct_rotation(&img_rotated);
337337
}
338338

339+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
340+
async fn test_recode_image_vflipped() {
341+
let bytes = include_bytes!("../../test-data/image/rectangle200x180-vflipped.jpg");
342+
let img_rotated = SendImageCheckMediaquality {
343+
viewtype: Viewtype::Image,
344+
media_quality_config: "0",
345+
bytes,
346+
extension: "jpg",
347+
has_exif: true,
348+
original_width: 200,
349+
original_height: 180,
350+
orientation: Some(Orientation::FlipVertical),
351+
compressed_width: 200,
352+
compressed_height: 180,
353+
..Default::default()
354+
}
355+
.test()
356+
.await
357+
.unwrap();
358+
assert_correct_rotation(&img_rotated);
359+
}
360+
339361
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
340362
async fn test_recode_image_bad_exif() {
341363
// `exiftool` reports for this file "Bad offset for IFD0 XResolution", still Exif must be
@@ -530,7 +552,7 @@ struct SendImageCheckMediaquality<'a> {
530552
pub(crate) has_exif: bool,
531553
pub(crate) original_width: u32,
532554
pub(crate) original_height: u32,
533-
pub(crate) orientation: i32,
555+
pub(crate) orientation: Option<Orientation>,
534556
pub(crate) res_viewtype: Option<Viewtype>,
535557
pub(crate) compressed_width: u32,
536558
pub(crate) compressed_height: u32,
@@ -546,7 +568,7 @@ impl SendImageCheckMediaquality<'_> {
546568
let has_exif = self.has_exif;
547569
let original_width = self.original_width;
548570
let original_height = self.original_height;
549-
let orientation = self.orientation;
571+
let orientation = self.orientation.unwrap_or(Orientation::NoTransforms);
550572
let res_viewtype = self.res_viewtype.unwrap_or(Viewtype::Image);
551573
let compressed_width = self.compressed_width;
552574
let compressed_height = self.compressed_height;
1.52 KB
Loading

0 commit comments

Comments
 (0)