diff --git a/Cargo.lock b/Cargo.lock index 8a1510de..2d85117c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/yazi-adapter/Cargo.toml b/yazi-adapter/Cargo.toml index a2925f84..007f0a07 100644 --- a/yazi-adapter/Cargo.toml +++ b/yazi-adapter/Cargo.toml @@ -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 } diff --git a/yazi-adapter/src/drivers/sixel.rs b/yazi-adapter/src/drivers/sixel.rs index c922ac0c..6d7db947 100644 --- a/yazi-adapter/src/drivers/sixel.rs +++ b/yazi-adapter/src/drivers/sixel.rs @@ -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 = 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>> { + let buf = &rgb.as_raw()[..(rgb.pixels().len() * 3)]; + let slice: ColorSlice> = buf.components_as().try_into()?; + + Ok(quantette::wu::indexed_palette( + &slice, + PaletteSize::try_from(256u16 - alpha as u16)?, + &UIntBinner::<32>, + )) + } } diff --git a/yazi-config/preset/yazi-default.toml b/yazi-config/preset/yazi-default.toml index 12100e71..024ea0d0 100644 --- a/yazi-config/preset/yazi-default.toml +++ b/yazi-config/preset/yazi-default.toml @@ -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 ] diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index 16a703b8..6be496f2 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -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() { diff --git a/yazi-plugin/src/config/runtime.rs b/yazi-plugin/src/config/runtime.rs index ff008c35..0a9409ea 100644 --- a/yazi-plugin/src/config/runtime.rs +++ b/yazi-plugin/src/config/runtime.rs @@ -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)?, diff --git a/yazi-plugin/src/elements/text.rs b/yazi-plugin/src/elements/text.rs index fcbf4fe9..5aa0b355 100644 --- a/yazi-plugin/src/elements/text.rs +++ b/yazi-plugin/src/elements/text.rs @@ -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};