mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Read EXIF data and apply image rotation (#3413)
This commit is contained in:
parent
fcf64d0ee0
commit
b89348b92a
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -1175,6 +1175,15 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kamadak-exif"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077"
|
||||||
|
dependencies = [
|
||||||
|
"mutate_once",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kqueue"
|
name = "kqueue"
|
||||||
version = "1.0.8"
|
version = "1.0.8"
|
||||||
@ -1375,6 +1384,12 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mutate_once"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@ -2556,6 +2571,7 @@ dependencies = [
|
|||||||
"icu_segmenter",
|
"icu_segmenter",
|
||||||
"image",
|
"image",
|
||||||
"indexmap 2.1.0",
|
"indexmap 2.1.0",
|
||||||
|
"kamadak-exif",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"lipsum",
|
"lipsum",
|
||||||
"log",
|
"log",
|
||||||
|
@ -58,6 +58,7 @@ if_chain = "1"
|
|||||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
|
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
|
||||||
include_dir = "0.7"
|
include_dir = "0.7"
|
||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
|
kamadak-exif = "0.5"
|
||||||
kurbo = "0.9" # in sync with usvg
|
kurbo = "0.9" # in sync with usvg
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
lipsum = "0.9"
|
lipsum = "0.9"
|
||||||
|
BIN
assets/files/f2t.jpg
Normal file
BIN
assets/files/f2t.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 441 B |
@ -36,6 +36,7 @@ icu_provider_blob = { workspace = true }
|
|||||||
icu_segmenter = { workspace = true }
|
icu_segmenter = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
|
kamadak-exif = { workspace = true }
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
lipsum = { workspace = true }
|
lipsum = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
@ -7,7 +7,7 @@ use image::codecs::gif::GifDecoder;
|
|||||||
use image::codecs::jpeg::JpegDecoder;
|
use image::codecs::jpeg::JpegDecoder;
|
||||||
use image::codecs::png::PngDecoder;
|
use image::codecs::png::PngDecoder;
|
||||||
use image::io::Limits;
|
use image::io::Limits;
|
||||||
use image::{guess_format, ImageDecoder, ImageResult};
|
use image::{guess_format, DynamicImage, ImageDecoder, ImageResult};
|
||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{Bytes, Cast};
|
use crate::foundations::{Bytes, Cast};
|
||||||
@ -39,13 +39,17 @@ impl RasterImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cursor = io::Cursor::new(&data);
|
let cursor = io::Cursor::new(&data);
|
||||||
let (dynamic, icc) = match format {
|
let (mut dynamic, icc) = match format {
|
||||||
RasterFormat::Jpg => decode_with(JpegDecoder::new(cursor)),
|
RasterFormat::Jpg => decode_with(JpegDecoder::new(cursor)),
|
||||||
RasterFormat::Png => decode_with(PngDecoder::new(cursor)),
|
RasterFormat::Png => decode_with(PngDecoder::new(cursor)),
|
||||||
RasterFormat::Gif => decode_with(GifDecoder::new(cursor)),
|
RasterFormat::Gif => decode_with(GifDecoder::new(cursor)),
|
||||||
}
|
}
|
||||||
.map_err(format_image_error)?;
|
.map_err(format_image_error)?;
|
||||||
|
|
||||||
|
if let Some(rotation) = exif_rotation(&data) {
|
||||||
|
apply_rotation(&mut dynamic, rotation);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self(Arc::new(Repr { data, format, dynamic, icc })))
|
Ok(Self(Arc::new(Repr { data, format, dynamic, icc })))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +133,36 @@ impl TryFrom<image::ImageFormat> for RasterFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get rotation from EXIF metadata.
|
||||||
|
fn exif_rotation(data: &[u8]) -> Option<u32> {
|
||||||
|
let reader = exif::Reader::new();
|
||||||
|
let mut cursor = std::io::Cursor::new(data);
|
||||||
|
let exif = reader.read_from_container(&mut cursor).ok()?;
|
||||||
|
let orient = exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY)?;
|
||||||
|
orient.value.get_uint(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply an EXIF rotation to a dynamic image.
|
||||||
|
fn apply_rotation(image: &mut DynamicImage, rotation: u32) {
|
||||||
|
use image::imageops as ops;
|
||||||
|
match rotation {
|
||||||
|
2 => ops::flip_horizontal_in_place(image),
|
||||||
|
3 => ops::rotate180_in_place(image),
|
||||||
|
4 => ops::flip_vertical_in_place(image),
|
||||||
|
5 => {
|
||||||
|
ops::flip_horizontal_in_place(image);
|
||||||
|
*image = image.rotate270();
|
||||||
|
}
|
||||||
|
6 => *image = image.rotate90(),
|
||||||
|
7 => {
|
||||||
|
ops::flip_horizontal_in_place(image);
|
||||||
|
*image = image.rotate90();
|
||||||
|
}
|
||||||
|
8 => *image = image.rotate270(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Format the user-facing raster graphic decoding error message.
|
/// Format the user-facing raster graphic decoding error message.
|
||||||
fn format_image_error(error: image::ImageError) -> EcoString {
|
fn format_image_error(error: image::ImageError) -> EcoString {
|
||||||
match error {
|
match error {
|
||||||
|
BIN
tests/ref/bugs/870-image-rotation.png
Normal file
BIN
tests/ref/bugs/870-image-rotation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 296 B |
6
tests/typ/bugs/870-image-rotation.typ
Normal file
6
tests/typ/bugs/870-image-rotation.typ
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Ensure that EXIF rotation is applied.
|
||||||
|
// https://github.com/image-rs/image/issues/1045
|
||||||
|
|
||||||
|
---
|
||||||
|
// Files is from https://magnushoff.com/articles/jpeg-orientation/
|
||||||
|
#image("/files/f2t.jpg", width: 10pt)
|
Loading…
x
Reference in New Issue
Block a user