mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat!: 9x faster Sixel image preview (#2707)
This commit is contained in:
parent
d6a01a63ef
commit
9037f8b8c0
7 changed files with 171 additions and 33 deletions
145
Cargo.lock
generated
145
Cargo.lock
generated
|
|
@ -122,6 +122,15 @@ version = "1.0.98"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.1"
|
||||
|
|
@ -253,6 +262,18 @@ version = "2.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
|
@ -293,6 +314,12 @@ version = "3.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "by_address"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.0"
|
||||
|
|
@ -579,7 +606,7 @@ dependencies = [
|
|||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 1.0.5",
|
||||
"rustix 1.0.7",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
|
|
@ -822,6 +849,12 @@ dependencies = [
|
|||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
|
|
@ -895,6 +928,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
|
|
@ -1058,9 +1097,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
|
|
@ -1726,12 +1765,44 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "palette"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"fast-srgb8",
|
||||
"palette_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette_derive"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30"
|
||||
dependencies = [
|
||||
"by_address",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
|
|
@ -1891,6 +1962,19 @@ dependencies = [
|
|||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quantette"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3ba8b34ab55d43cea1f7c183ef3c124cb96fe935c6ec5c6d97127ee340c96e"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"num-traits",
|
||||
"ordered-float 4.6.0",
|
||||
"palette",
|
||||
"wide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
|
|
@ -1921,6 +2005,12 @@ version = "5.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
|
@ -2137,9 +2227,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.5"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
|
|
@ -2160,6 +2250,15 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "safe_arch"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
|
|
@ -2196,7 +2295,7 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||
dependencies = [
|
||||
"ordered-float",
|
||||
"ordered-float 2.10.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -2420,6 +2519,12 @@ dependencies = [
|
|||
"version-compare",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "target-lexicon"
|
||||
version = "0.12.16"
|
||||
|
|
@ -2974,10 +3079,20 @@ checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762"
|
|||
dependencies = [
|
||||
"either",
|
||||
"env_home",
|
||||
"rustix 1.0.5",
|
||||
"rustix 1.0.7",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wide"
|
||||
version = "0.7.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"safe_arch",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
@ -3205,9 +3320,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.7"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5"
|
||||
checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
@ -3227,6 +3342,15 @@ dependencies = [
|
|||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi-adapter"
|
||||
version = "25.4.8"
|
||||
|
|
@ -3234,9 +3358,10 @@ dependencies = [
|
|||
"ansi-to-tui",
|
||||
"anyhow",
|
||||
"base64",
|
||||
"color_quant",
|
||||
"crossterm 0.29.0",
|
||||
"image",
|
||||
"palette",
|
||||
"quantette",
|
||||
"ratatui",
|
||||
"scopeguard",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ yazi-term = { path = "../yazi-term", version = "25.4.8" }
|
|||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
color_quant = "1.1.0"
|
||||
crossterm = { workspace = true }
|
||||
image = { version = "0.25.6", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "webp" ] }
|
||||
palette = { version = "0.7.6", default-features = false }
|
||||
quantette = { version = "0.3.0", default-features = false }
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use std::{io::Write, path::Path};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use color_quant::NeuQuant;
|
||||
use crossterm::{cursor::MoveTo, queue};
|
||||
use image::DynamicImage;
|
||||
use image::{DynamicImage, GenericImageView, RgbImage};
|
||||
use palette::{Srgb, cast::ComponentsAs};
|
||||
use quantette::{ColorSlice, PaletteSize, QuantizeOutput, wu::UIntBinner};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::YAZI;
|
||||
|
||||
use crate::{CLOSE, ESCAPE, Emulator, Image, START, adapter::Adapter};
|
||||
|
||||
|
|
@ -42,22 +42,25 @@ impl Sixel {
|
|||
bail!("image is empty");
|
||||
}
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let img = img.into_rgba8();
|
||||
let nq = NeuQuant::new(YAZI.preview.sixel_fraction as i32, 256 - alpha as usize, &img);
|
||||
let (qo, img) = tokio::task::spawn_blocking(move || match &img {
|
||||
DynamicImage::ImageRgb8(rgb) => Self::quantify(rgb, false).map(|q| (q, img)),
|
||||
_ => Self::quantify(&img.to_rgb8(), alpha).map(|q| (q, img)),
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(1 << 16);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut buf = vec![];
|
||||
write!(buf, "{START}P0;1;8q\"1;1;{};{}", img.width(), img.height())?;
|
||||
|
||||
// Palette
|
||||
for (i, c) in nq.color_map_rgba().chunks(4).enumerate() {
|
||||
for (i, c) in qo.palette.iter().enumerate() {
|
||||
write!(
|
||||
buf,
|
||||
"#{};2;{};{};{}",
|
||||
i + alpha as usize,
|
||||
c[0] as u16 * 100 / 255,
|
||||
c[1] as u16 * 100 / 255,
|
||||
c[2] as u16 * 100 / 255
|
||||
c.red as u16 * 100 / 255,
|
||||
c.green as u16 * 100 / 255,
|
||||
c.blue as u16 * 100 / 255
|
||||
)?;
|
||||
}
|
||||
|
||||
|
|
@ -67,8 +70,11 @@ impl Sixel {
|
|||
let mut last = 0;
|
||||
let mut repeat = 0usize;
|
||||
for x in 0..img.width() {
|
||||
let pixel = img.get_pixel(x, y).0;
|
||||
let idx = if pixel[3] == 0 { 0 } else { nq.index_of(&pixel) as u8 + alpha as u8 };
|
||||
let idx = if img.get_pixel(x, y)[3] == 0 {
|
||||
0
|
||||
} else {
|
||||
qo.indices[y as usize * img.width() as usize + x as usize] + alpha as u8
|
||||
};
|
||||
|
||||
if idx == last || repeat == 0 {
|
||||
(last, repeat) = (idx, repeat + 1);
|
||||
|
|
@ -101,4 +107,15 @@ impl Sixel {
|
|||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
fn quantify(rgb: &RgbImage, alpha: bool) -> Result<QuantizeOutput<Srgb<u8>>> {
|
||||
let buf = &rgb.as_raw()[..(rgb.pixels().len() * 3)];
|
||||
let slice: ColorSlice<Srgb<u8>> = buf.components_as().try_into()?;
|
||||
|
||||
Ok(quantette::wu::indexed_palette(
|
||||
&slice,
|
||||
PaletteSize::try_from(256u16 - alpha as u16)?,
|
||||
&UIntBinner::<32>,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ cache_dir = ""
|
|||
image_delay = 30
|
||||
image_filter = "triangle"
|
||||
image_quality = 75
|
||||
sixel_fraction = 20
|
||||
ueberzug_scale = 1
|
||||
ueberzug_offset = [ 0, 0, 0, 0 ]
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,9 @@ pub struct Preview {
|
|||
|
||||
pub cache_dir: PathBuf,
|
||||
|
||||
pub image_delay: u8,
|
||||
pub image_filter: String,
|
||||
pub image_quality: u8,
|
||||
pub sixel_fraction: u8,
|
||||
pub image_delay: u8,
|
||||
pub image_filter: String,
|
||||
pub image_quality: u8,
|
||||
|
||||
pub ueberzug_scale: f32,
|
||||
pub ueberzug_offset: (f32, f32, f32, f32),
|
||||
|
|
@ -50,8 +49,6 @@ impl Preview {
|
|||
bail!("[preview].image_delay must be between 0 and 100.");
|
||||
} else if self.image_quality < 50 || self.image_quality > 90 {
|
||||
bail!("[preview].image_quality must be between 50 and 90.");
|
||||
} else if self.sixel_fraction < 10 || self.sixel_fraction > 30 {
|
||||
bail!("[preview].sixel_fraction must be between 10 and 30.");
|
||||
}
|
||||
|
||||
self.cache_dir = if self.cache_dir.as_os_str().is_empty() {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ impl Runtime {
|
|||
b"image_delay" => lua.to_value_with(&p.image_delay, OPTS)?,
|
||||
b"image_filter" => lua.to_value_with(&p.image_filter, OPTS)?,
|
||||
b"image_quality" => lua.to_value_with(&p.image_quality, OPTS)?,
|
||||
b"sixel_fraction" => lua.to_value_with(&p.sixel_fraction, OPTS)?,
|
||||
|
||||
b"ueberzug_scale" => lua.to_value_with(&p.ueberzug_scale, OPTS)?,
|
||||
b"ueberzug_offset" => lua.to_value_with(&p.ueberzug_offset, OPTS)?,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::mem;
|
||||
|
||||
use ansi_to_tui::IntoText;
|
||||
use mlua::{ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, ObjectLike, Table, UserData, Value};
|
||||
use mlua::{ExternalError, ExternalResult, IntoLua, Lua, MetaMethod, Table, UserData, Value};
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
use super::{Area, Line, Span};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue