mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
perf: apply rotate in place to images with orientation (#1807)
This commit is contained in:
parent
df78827af5
commit
11c5f3fed4
4 changed files with 72 additions and 134 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -123,9 +123,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.89"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
|
|
@ -288,9 +288,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.7.4"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
|
||||
checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
|
|
@ -1084,9 +1084,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.2"
|
||||
version = "0.25.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
||||
checksum = "bc144d44a31d753b02ce64093d532f55ff8dc4ebf2ffb8a63c0dda691385acae"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
|
|
@ -1106,9 +1106,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.1.3"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
|
||||
checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
|
|
@ -1235,15 +1235,6 @@ 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"
|
||||
|
|
@ -1278,9 +1269,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.159"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
|
|
@ -1484,12 +1475,6 @@ dependencies = [
|
|||
"syn 2.0.79",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mutate_once"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
|
|
@ -1833,9 +1818,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.87"
|
||||
version = "1.0.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
@ -1980,9 +1965,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ravif"
|
||||
version = "0.11.10"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd"
|
||||
checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"imgref",
|
||||
|
|
@ -2066,9 +2051,6 @@ name = "rgb"
|
|||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
|
|
@ -2154,9 +2136,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
version = "1.0.131"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
checksum = "67d42a0bd4ac281beff598909bb56a86acaf979b84483e1c79c10dcaf98f8cf3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
|
@ -3202,7 +3184,6 @@ dependencies = [
|
|||
"futures",
|
||||
"image",
|
||||
"imagesize",
|
||||
"kamadak-exif",
|
||||
"ratatui",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ strip = true
|
|||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "6.0.0"
|
||||
anyhow = "1.0.89"
|
||||
anyhow = "1.0.90"
|
||||
arc-swap = "1.7.1"
|
||||
base64 = "0.22.1"
|
||||
bitflags = "2.6.0"
|
||||
|
|
@ -20,7 +20,7 @@ crossterm = { version = "0.28.1", features = [ "event-stream" ] }
|
|||
dirs = "5.0.1"
|
||||
futures = "0.3.31"
|
||||
globset = "0.4.15"
|
||||
libc = "0.2.159"
|
||||
libc = "0.2.161"
|
||||
md-5 = "0.10.6"
|
||||
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
|
||||
parking_lot = "0.12.3"
|
||||
|
|
@ -28,7 +28,7 @@ ratatui = { version = "0.28.1", features = [ "unstable-rendered-line-info"
|
|||
regex = "1.11.0"
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.210", features = [ "derive" ] }
|
||||
serde_json = "1.0.128"
|
||||
serde_json = "1.0.131"
|
||||
shell-words = "1.1.0"
|
||||
tokio = { version = "1.40.0", features = [ "full" ] }
|
||||
tokio-stream = "0.1.16"
|
||||
|
|
|
|||
|
|
@ -14,20 +14,19 @@ yazi-macro = { path = "../yazi-macro", version = "0.3.3" }
|
|||
yazi-shared = { path = "../yazi-shared", version = "0.3.3" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
color_quant = "1.1.0"
|
||||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
image = { version = "0.25.2", default-features = false, features = [ "rayon", "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
imagesize = "0.13.0"
|
||||
kamadak-exif = "0.5.5"
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
color_quant = "1.1.0"
|
||||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
image = { version = "0.25.4", default-features = false, features = [ "rayon", "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
imagesize = "0.13.0"
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use exif::{In, Tag};
|
||||
use image::{DynamicImage, ExtendedColorType, ImageEncoder, ImageError, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::{self, FilterType}};
|
||||
use image::{DynamicImage, ExtendedColorType, ImageDecoder, ImageEncoder, ImageError, ImageReader, ImageResult, Limits, codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::FilterType, metadata::Orientation};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::{PREVIEW, TASKS};
|
||||
|
||||
|
|
@ -12,27 +11,18 @@ 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::ImageReader::open(path)?.with_guessed_format()?).decode()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let (mut w, mut h) = (PREVIEW.max_width, PREVIEW.max_height);
|
||||
if (5..=8).contains(&orientation) {
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
let (mut img, orientation) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, (PREVIEW.max_width, PREVIEW.max_height));
|
||||
|
||||
let buf = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter());
|
||||
}
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
img = Self::rotate(img, orientation);
|
||||
|
||||
if img.color().has_alpha() {
|
||||
let rgba = img.into_rgba8();
|
||||
PngEncoder::new(&mut buf).write_image(
|
||||
|
|
@ -54,31 +44,26 @@ 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 mut img = tokio::task::spawn_blocking(move || {
|
||||
Self::set_limits(image::ImageReader::open(path)?.with_guessed_format()?).decode()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let (mut w, mut h) = Self::max_pixel(rect);
|
||||
if (5..=8).contains(&orientation) {
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
let (mut img, orientation) = Self::decode_from(path).await?;
|
||||
let (w, h) = Self::flip_size(orientation, Self::max_pixel(rect));
|
||||
|
||||
// Fast path.
|
||||
if img.width() <= w && img.height() <= h && orientation <= 1 {
|
||||
if img.width() <= w && img.height() <= h && orientation == Orientation::NoTransforms {
|
||||
return Ok(img);
|
||||
}
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let img = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, Self::filter())
|
||||
}
|
||||
Ok(Self::rotate(img, orientation))
|
||||
if orientation != Orientation::NoTransforms {
|
||||
img.apply_orientation(orientation);
|
||||
}
|
||||
img
|
||||
})
|
||||
.await?
|
||||
.await?;
|
||||
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
|
||||
|
|
@ -113,53 +98,7 @@ impl Image {
|
|||
}
|
||||
}
|
||||
|
||||
async fn orientation(path: &Path) -> Result<u8> {
|
||||
// We don't want to read the orientation of the cached image that has been
|
||||
// rotated in the `Self::precache()` step.
|
||||
if path.parent() == Some(&PREVIEW.cache_dir) {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
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 alpha = img.color().has_alpha();
|
||||
img = match orientation {
|
||||
2 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
|
||||
3 => DynamicImage::ImageRgba8(imageops::rotate180(&img)),
|
||||
4 => DynamicImage::ImageRgba8(imageops::flip_vertical(&img)),
|
||||
5 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&imageops::rotate90(&img))),
|
||||
6 => DynamicImage::ImageRgba8(imageops::rotate90(&img)),
|
||||
7 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&imageops::rotate270(&img))),
|
||||
8 => DynamicImage::ImageRgba8(imageops::rotate270(&img)),
|
||||
_ => img,
|
||||
};
|
||||
if !alpha {
|
||||
img = DynamicImage::ImageRgb8(img.into_rgb8());
|
||||
}
|
||||
img
|
||||
}
|
||||
|
||||
fn set_limits(mut r: image::ImageReader<BufReader<File>>) -> image::ImageReader<BufReader<File>> {
|
||||
async fn decode_from(path: &Path) -> ImageResult<(DynamicImage, Orientation)> {
|
||||
let mut limits = Limits::no_limits();
|
||||
if TASKS.image_alloc > 0 {
|
||||
limits.max_alloc = Some(TASKS.image_alloc as u64);
|
||||
|
|
@ -170,7 +109,26 @@ impl Image {
|
|||
if TASKS.image_bound[1] > 0 {
|
||||
limits.max_image_height = Some(TASKS.image_bound[1] as u32);
|
||||
}
|
||||
r.limits(limits);
|
||||
r
|
||||
|
||||
let path = path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut reader = ImageReader::open(path)?;
|
||||
reader.limits(limits);
|
||||
|
||||
let mut decoder = reader.with_guessed_format()?.into_decoder()?;
|
||||
let orientation = decoder.orientation().unwrap_or(Orientation::NoTransforms);
|
||||
|
||||
Ok((DynamicImage::from_decoder(decoder)?, orientation))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| ImageError::IoError(e.into()))?
|
||||
}
|
||||
|
||||
fn flip_size(orientation: Orientation, (w, h): (u32, u32)) -> (u32, u32) {
|
||||
use image::metadata::Orientation::{Rotate90, Rotate90FlipH, Rotate270, Rotate270FlipH};
|
||||
match orientation {
|
||||
Rotate90 | Rotate270 | Rotate90FlipH | Rotate270FlipH => (h, w),
|
||||
_ => (w, h),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue