mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: image orientation support (#488)
This commit is contained in:
parent
1908ff2047
commit
7e752df78a
3 changed files with 96 additions and 18 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" ] }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue