mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41: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",
|
"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"
|
||||||
|
|
@ -1195,6 +1204,12 @@ dependencies = [
|
||||||
"syn 2.0.46",
|
"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]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
|
@ -2627,6 +2642,7 @@ dependencies = [
|
||||||
"color_quant",
|
"color_quant",
|
||||||
"image",
|
"image",
|
||||||
"imagesize",
|
"imagesize",
|
||||||
|
"kamadak-exif",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,15 @@ yazi-config = { path = "../yazi-config", version = "0.1.5" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
yazi-shared = { path = "../yazi-shared", version = "0.1.5" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = "^1"
|
anyhow = "^1"
|
||||||
arc-swap = "^1"
|
arc-swap = "^1"
|
||||||
base64 = "^0"
|
base64 = "^0"
|
||||||
color_quant = "^1"
|
color_quant = "^1"
|
||||||
image = "^0"
|
image = "^0"
|
||||||
imagesize = "^0"
|
imagesize = "^0"
|
||||||
ratatui = "^0"
|
kamadak-exif = "0"
|
||||||
tokio = { version = "^1", features = [ "parking_lot", "io-util", "process" ] }
|
ratatui = "^0"
|
||||||
|
tokio = { version = "^1", features = [ "parking_lot", "io-util", "process" ] }
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = { version = "^0", features = [ "max_level_debug", "release_max_level_warn" ] }
|
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 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 ratatui::layout::Rect;
|
||||||
use yazi_config::{PREVIEW, TASKS};
|
use yazi_config::{PREVIEW, TASKS};
|
||||||
use yazi_shared::term::Term;
|
use yazi_shared::term::Term;
|
||||||
|
|
@ -10,18 +11,25 @@ pub struct Image;
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
|
pub async fn precache(path: &Path, cache: PathBuf) -> Result<()> {
|
||||||
|
let orientation = Self::orientation(path).await?;
|
||||||
|
|
||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
let mut 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()
|
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode()
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
let (mut w, mut h) = (PREVIEW.max_width, PREVIEW.max_height);
|
||||||
tokio::task::spawn_blocking(move || {
|
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 {
|
if img.width() > w || img.height() > h {
|
||||||
img = img.resize(w, h, FilterType::Triangle);
|
img = img.resize(w, h, FilterType::Triangle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img = Self::rotate(img, orientation);
|
||||||
Ok(match img {
|
Ok(match img {
|
||||||
DynamicImage::ImageRgb8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
|
DynamicImage::ImageRgb8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
|
||||||
DynamicImage::ImageRgba8(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> {
|
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
|
||||||
|
let orientation = Self::orientation(path).await?;
|
||||||
|
|
||||||
let path = path.to_owned();
|
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()
|
Self::set_limits(image::io::Reader::open(path)?.with_guessed_format()?).decode()
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let (w, h) = Self::max_size(rect);
|
let (mut w, mut h) = Self::max_size(rect);
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
Ok(if img.width() > w || img.height() > h {
|
if (5..=8).contains(&orientation) {
|
||||||
img.resize(w, h, FilterType::Triangle)
|
(w, h) = (h, w);
|
||||||
} else {
|
}
|
||||||
img
|
|
||||||
})
|
if img.width() > w || img.height() > h {
|
||||||
|
img = img.resize(w, h, FilterType::Triangle)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self::rotate(img, orientation))
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +72,53 @@ impl Image {
|
||||||
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
|
.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>> {
|
fn set_limits(mut r: image::io::Reader<BufReader<File>>) -> image::io::Reader<BufReader<File>> {
|
||||||
let mut limits = Limits::no_limits();
|
let mut limits = Limits::no_limits();
|
||||||
if TASKS.image_alloc > 0 {
|
if TASKS.image_alloc > 0 {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue