feat: image orientation support (#488)

This commit is contained in:
三咲雅 · Misaki Masa 2024-01-08 11:31:22 +08:00 committed by GitHub
parent 1908ff2047
commit 7e752df78a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 18 deletions

16
Cargo.lock generated
View file

@ -988,6 +988,15 @@ dependencies = [
"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]]
name = "kqueue"
version = "1.0.8"
@ -1195,6 +1204,12 @@ dependencies = [
"syn 2.0.46",
]
[[package]]
name = "mutate_once"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
[[package]]
name = "nom"
version = "7.1.3"
@ -2627,6 +2642,7 @@ dependencies = [
"color_quant",
"image",
"imagesize",
"kamadak-exif",
"ratatui",
"tokio",
"tracing",

View file

@ -13,14 +13,15 @@ yazi-config = { path = "../yazi-config", version = "0.1.5" }
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
# External dependencies
anyhow = "^1"
arc-swap = "^1"
base64 = "^0"
color_quant = "^1"
image = "^0"
imagesize = "^0"
ratatui = "^0"
tokio = { version = "^1", features = [ "parking_lot", "io-util", "process" ] }
anyhow = "^1"
arc-swap = "^1"
base64 = "^0"
color_quant = "^1"
image = "^0"
imagesize = "^0"
kamadak-exif = "0"
ratatui = "^0"
tokio = { version = "^1", features = [ "parking_lot", "io-util", "process" ] }
# Logging
tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -1,7 +1,8 @@
use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
use std::{fs::File, io::BufReader, ops::RangeBounds, path::{Path, PathBuf}};
use anyhow::Result;
use image::{imageops::FilterType, io::Limits, DynamicImage, ImageFormat};
use exif::{In, Tag};
use image::{imageops::{self, FilterType}, io::Limits, DynamicImage, ImageFormat};
use ratatui::layout::Rect;
use yazi_config::{PREVIEW, TASKS};
use yazi_shared::term::Term;
@ -10,18 +11,25 @@ pub struct Image;
impl Image {
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
let orientation = Self::orientation(path).await?;
let path = path.to_owned();
let mut img = tokio::task::spawn_blocking(move || {
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode()
})
.await??;
let (mut w, mut h) = (PREVIEW.max_width, PREVIEW.max_height);
tokio::task::spawn_blocking(move || {
let (w, h) = (PREVIEW.max_width, PREVIEW.max_height);
if (5..=8).contains(&orientation) {
(w, h) = (h, w);
}
if img.width() > w || img.height() > h {
img = img.resize(w, h, FilterType::Triangle);
}
img = Self::rotate(img, orientation);
Ok(match img {
DynamicImage::ImageRgb8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
DynamicImage::ImageRgba8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
@ -32,19 +40,25 @@ impl Image {
}
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
let orientation = Self::orientation(path).await?;
let path = path.to_owned();
let img = tokio::task::spawn_blocking(move || {
let mut img = tokio::task::spawn_blocking(move || {
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode()
})
.await??;
let (w, h) = Self::max_size(rect);
let (mut w, mut h) = Self::max_size(rect);
tokio::task::spawn_blocking(move || {
Ok(if img.width() > w || img.height() > h {
img.resize(w, h, FilterType::Triangle)
} else {
img
})
if (5..=8).contains(&orientation) {
(w, h) = (h, w);
}
if img.width() > w || img.height() > h {
img = img.resize(w, h, FilterType::Triangle)
}
Ok(Self::rotate(img, orientation))
})
.await?
}
@ -58,6 +72,53 @@ impl Image {
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
}
async fn orientation(path: &Path) -> Result<u8> {
let path = path.to_owned();
tokio::task::spawn_blocking(move || {
let file = std::fs::File::open(path)?;
let mut reader = std::io::BufReader::new(&file);
let Ok(exif) = exif::Reader::new().read_from_container(&mut reader) else {
return Ok(0);
};
Ok(match exif.get_field(Tag::Orientation, In::PRIMARY) {
Some(orientation) => match orientation.value.get_uint(0) {
Some(v @ 1..=8) => v as u8,
_ => 1,
},
None => 1,
})
})
.await?
}
// https://magnushoff.com/articles/jpeg-orientation/
fn rotate(mut img: DynamicImage, orientation: u8) -> DynamicImage {
let rgba = img.color().has_alpha();
if orientation == 2 {
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
} else if orientation == 3 {
img = DynamicImage::ImageRgba8(imageops::rotate180(&img));
} else if orientation == 4 {
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
} else if orientation == 5 {
img = DynamicImage::ImageRgba8(imageops::rotate90(&img));
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
} else if orientation == 6 {
img = DynamicImage::ImageRgba8(imageops::rotate90(&img));
} else if orientation == 7 {
img = DynamicImage::ImageRgba8(imageops::rotate270(&img));
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
} else if orientation == 8 {
img = DynamicImage::ImageRgba8(imageops::rotate270(&img));
}
if !rgba {
img = DynamicImage::ImageRgb8(img.into_rgb8());
}
img
}
fn set_limits(mut r: image::io::Reader<BufReader<File>>) -> image::io::Reader<BufReader<File>> {
let mut limits = Limits::no_limits();
if TASKS.image_alloc > 0 {