mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: support transparent image preview (#1556)
Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
parent
276f69128d
commit
0acf5c288e
7 changed files with 52 additions and 41 deletions
|
|
@ -5,14 +5,14 @@ use ratatui::layout::Rect;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use yazi_shared::env_exists;
|
use yazi_shared::env_exists;
|
||||||
|
|
||||||
use super::{Iterm2, Kitty, KittyOld};
|
use super::{Iip, Kitty, KittyOld};
|
||||||
use crate::{Chafa, Emulator, Sixel, Ueberzug, SHOWN, TMUX, WSL};
|
use crate::{Chafa, Emulator, Sixel, Ueberzug, SHOWN, TMUX, WSL};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum Adapter {
|
pub enum Adapter {
|
||||||
Kitty,
|
Kitty,
|
||||||
KittyOld,
|
KittyOld,
|
||||||
Iterm2,
|
Iip,
|
||||||
Sixel,
|
Sixel,
|
||||||
|
|
||||||
// Supported by Überzug++
|
// Supported by Überzug++
|
||||||
|
|
@ -26,7 +26,7 @@ impl Display for Adapter {
|
||||||
match self {
|
match self {
|
||||||
Self::Kitty => write!(f, "kitty"),
|
Self::Kitty => write!(f, "kitty"),
|
||||||
Self::KittyOld => write!(f, "kitty"),
|
Self::KittyOld => write!(f, "kitty"),
|
||||||
Self::Iterm2 => write!(f, "iterm2"),
|
Self::Iip => write!(f, "iip"),
|
||||||
Self::Sixel => write!(f, "sixel"),
|
Self::Sixel => write!(f, "sixel"),
|
||||||
Self::X11 => write!(f, "x11"),
|
Self::X11 => write!(f, "x11"),
|
||||||
Self::Wayland => write!(f, "wayland"),
|
Self::Wayland => write!(f, "wayland"),
|
||||||
|
|
@ -44,7 +44,7 @@ impl Adapter {
|
||||||
match self {
|
match self {
|
||||||
Self::Kitty => Kitty::image_show(path, max).await,
|
Self::Kitty => Kitty::image_show(path, max).await,
|
||||||
Self::KittyOld => KittyOld::image_show(path, max).await,
|
Self::KittyOld => KittyOld::image_show(path, max).await,
|
||||||
Self::Iterm2 => Iterm2::image_show(path, max).await,
|
Self::Iip => Iip::image_show(path, max).await,
|
||||||
Self::Sixel => Sixel::image_show(path, max).await,
|
Self::Sixel => Sixel::image_show(path, max).await,
|
||||||
Self::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
|
Self::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
|
||||||
Self::Chafa => Chafa::image_show(path, max).await,
|
Self::Chafa => Chafa::image_show(path, max).await,
|
||||||
|
|
@ -59,7 +59,7 @@ impl Adapter {
|
||||||
match self {
|
match self {
|
||||||
Self::Kitty => Kitty::image_erase(area),
|
Self::Kitty => Kitty::image_erase(area),
|
||||||
Self::KittyOld => KittyOld::image_erase(area),
|
Self::KittyOld => KittyOld::image_erase(area),
|
||||||
Self::Iterm2 => Iterm2::image_erase(area),
|
Self::Iip => Iip::image_erase(area),
|
||||||
Self::Sixel => Sixel::image_erase(area),
|
Self::Sixel => Sixel::image_erase(area),
|
||||||
Self::X11 | Self::Wayland => Ueberzug::image_erase(area),
|
Self::X11 | Self::Wayland => Ueberzug::image_erase(area),
|
||||||
Self::Chafa => Chafa::image_erase(area),
|
Self::Chafa => Chafa::image_erase(area),
|
||||||
|
|
@ -76,7 +76,7 @@ impl Adapter {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn needs_ueberzug(self) -> bool {
|
pub(super) fn needs_ueberzug(self) -> bool {
|
||||||
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel)
|
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iip | Self::Sixel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ impl Adapter {
|
||||||
|
|
||||||
let mut protocols = emulator.adapters();
|
let mut protocols = emulator.adapters();
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
protocols.retain(|p| *p == Self::Iterm2);
|
protocols.retain(|p| *p == Self::Iip);
|
||||||
if env_exists("ZELLIJ_SESSION_NAME") {
|
if env_exists("ZELLIJ_SESSION_NAME") {
|
||||||
protocols.retain(|p| *p == Self::Sixel);
|
protocols.retain(|p| *p == Self::Sixel);
|
||||||
} else if *TMUX {
|
} else if *TMUX {
|
||||||
|
|
|
||||||
|
|
@ -35,16 +35,16 @@ impl Emulator {
|
||||||
Self::Unknown(adapters) => adapters,
|
Self::Unknown(adapters) => adapters,
|
||||||
Self::Kitty => vec![Adapter::Kitty],
|
Self::Kitty => vec![Adapter::Kitty],
|
||||||
Self::Konsole => vec![Adapter::KittyOld],
|
Self::Konsole => vec![Adapter::KittyOld],
|
||||||
Self::Iterm2 => vec![Adapter::Iterm2, Adapter::Sixel],
|
Self::Iterm2 => vec![Adapter::Iip, Adapter::Sixel],
|
||||||
Self::WezTerm => vec![Adapter::Iterm2, Adapter::Sixel],
|
Self::WezTerm => vec![Adapter::Iip, Adapter::Sixel],
|
||||||
Self::Foot => vec![Adapter::Sixel],
|
Self::Foot => vec![Adapter::Sixel],
|
||||||
Self::Ghostty => vec![Adapter::Kitty],
|
Self::Ghostty => vec![Adapter::Kitty],
|
||||||
Self::Microsoft => vec![Adapter::Sixel],
|
Self::Microsoft => vec![Adapter::Sixel],
|
||||||
Self::BlackBox => vec![Adapter::Sixel],
|
Self::BlackBox => vec![Adapter::Sixel],
|
||||||
Self::VSCode => vec![Adapter::Iterm2, Adapter::Sixel],
|
Self::VSCode => vec![Adapter::Iip, Adapter::Sixel],
|
||||||
Self::Tabby => vec![Adapter::Iterm2, Adapter::Sixel],
|
Self::Tabby => vec![Adapter::Iip, Adapter::Sixel],
|
||||||
Self::Hyper => vec![Adapter::Iterm2, Adapter::Sixel],
|
Self::Hyper => vec![Adapter::Iip, Adapter::Sixel],
|
||||||
Self::Mintty => vec![Adapter::Iterm2],
|
Self::Mintty => vec![Adapter::Iip],
|
||||||
Self::Neovim => vec![],
|
Self::Neovim => vec![],
|
||||||
Self::Apple => vec![],
|
Self::Apple => vec![],
|
||||||
Self::Urxvt => vec![],
|
Self::Urxvt => vec![],
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,22 @@ use std::{io::Write, path::Path};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{engine::{general_purpose::STANDARD, Config}, Engine};
|
use base64::{engine::{general_purpose::STANDARD, Config}, Engine};
|
||||||
use crossterm::{cursor::MoveTo, queue};
|
use crossterm::{cursor::MoveTo, queue};
|
||||||
use image::{codecs::jpeg::JpegEncoder, DynamicImage};
|
use image::{codecs::{jpeg::JpegEncoder, png::PngEncoder}, DynamicImage, ExtendedColorType, ImageEncoder};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
use yazi_config::PREVIEW;
|
||||||
|
|
||||||
use super::image::Image;
|
use super::image::Image;
|
||||||
use crate::{adapter::Adapter, Emulator, CLOSE, START};
|
use crate::{adapter::Adapter, Emulator, CLOSE, START};
|
||||||
|
|
||||||
pub(super) struct Iterm2;
|
pub(super) struct Iip;
|
||||||
|
|
||||||
impl Iterm2 {
|
impl Iip {
|
||||||
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
|
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
|
||||||
let img = Image::downscale(path, max).await?;
|
let img = Image::downscale(path, max).await?;
|
||||||
let area = Image::pixel_area((img.width(), img.height()), max);
|
let area = Image::pixel_area((img.width(), img.height()), max);
|
||||||
let b = Self::encode(img).await?;
|
let b = Self::encode(img).await?;
|
||||||
|
|
||||||
Adapter::Iterm2.image_hide()?;
|
Adapter::Iip.image_hide()?;
|
||||||
Adapter::shown_store(area);
|
Adapter::shown_store(area);
|
||||||
Emulator::move_lock((max.x, max.y), |stderr| {
|
Emulator::move_lock((max.x, max.y), |stderr| {
|
||||||
stderr.write_all(&b)?;
|
stderr.write_all(&b)?;
|
||||||
|
|
@ -38,20 +39,26 @@ impl Iterm2 {
|
||||||
|
|
||||||
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let mut jpg = vec![];
|
let (w, h) = (img.width(), img.height());
|
||||||
JpegEncoder::new_with_quality(&mut jpg, 75).encode_image(&img)?;
|
|
||||||
|
|
||||||
let len = base64::encoded_len(jpg.len(), STANDARD.config().encode_padding());
|
let mut b = vec![];
|
||||||
|
if img.color().has_alpha() {
|
||||||
|
PngEncoder::new(&mut b).write_image(&img.into_rgba8(), w, h, ExtendedColorType::Rgba8)?;
|
||||||
|
} else {
|
||||||
|
JpegEncoder::new_with_quality(&mut b, PREVIEW.image_quality).encode_image(&img)?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = base64::encoded_len(b.len(), STANDARD.config().encode_padding());
|
||||||
let mut buf = Vec::with_capacity(200 + len.unwrap_or(1 << 16));
|
let mut buf = Vec::with_capacity(200 + len.unwrap_or(1 << 16));
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
buf,
|
buf,
|
||||||
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
|
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
|
||||||
START,
|
START,
|
||||||
jpg.len(),
|
b.len(),
|
||||||
img.width(),
|
w,
|
||||||
img.height(),
|
h,
|
||||||
STANDARD.encode(&jpg),
|
STANDARD.encode(b),
|
||||||
CLOSE
|
CLOSE
|
||||||
)?;
|
)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use exif::{In, Tag};
|
use exif::{In, Tag};
|
||||||
use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, DynamicImage, Limits};
|
use image::{codecs::{jpeg::JpegEncoder, png::PngEncoder}, imageops::{self, FilterType}, DynamicImage, ExtendedColorType, ImageEncoder, ImageError, Limits};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use yazi_config::{PREVIEW, TASKS};
|
use yazi_config::{PREVIEW, TASKS};
|
||||||
|
|
||||||
|
|
@ -30,19 +30,23 @@ impl Image {
|
||||||
img = img.resize(w, h, Self::filter());
|
img = img.resize(w, h, Self::filter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
img = Self::rotate(img, orientation);
|
img = Self::rotate(img, orientation);
|
||||||
if !matches!(img, DynamicImage::ImageRgb8(_)) {
|
|
||||||
img = DynamicImage::ImageRgb8(img.into_rgb8());
|
if img.color().has_alpha() {
|
||||||
|
let rgba = img.into_rgba8();
|
||||||
|
PngEncoder::new(&mut buf).write_image(
|
||||||
|
&rgba,
|
||||||
|
rgba.width(),
|
||||||
|
rgba.height(),
|
||||||
|
ExtendedColorType::Rgba8,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality)
|
||||||
|
.encode_image(&img.into_rgb8())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
Ok::<_, ImageError>(buf)
|
||||||
JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality).encode(
|
|
||||||
img.as_bytes(),
|
|
||||||
img.width(),
|
|
||||||
img.height(),
|
|
||||||
img.color().into(),
|
|
||||||
)?;
|
|
||||||
Ok::<_, anyhow::Error>(buf)
|
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
|
@ -138,7 +142,7 @@ impl Image {
|
||||||
|
|
||||||
// https://magnushoff.com/articles/jpeg-orientation/
|
// https://magnushoff.com/articles/jpeg-orientation/
|
||||||
fn rotate(mut img: DynamicImage, orientation: u8) -> DynamicImage {
|
fn rotate(mut img: DynamicImage, orientation: u8) -> DynamicImage {
|
||||||
let rgba = img.color().has_alpha();
|
let alpha = img.color().has_alpha();
|
||||||
img = match orientation {
|
img = match orientation {
|
||||||
2 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
|
2 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
|
||||||
3 => DynamicImage::ImageRgba8(imageops::rotate180(&img)),
|
3 => DynamicImage::ImageRgba8(imageops::rotate180(&img)),
|
||||||
|
|
@ -149,7 +153,7 @@ impl Image {
|
||||||
8 => DynamicImage::ImageRgba8(imageops::rotate270(&img)),
|
8 => DynamicImage::ImageRgba8(imageops::rotate270(&img)),
|
||||||
_ => img,
|
_ => img,
|
||||||
};
|
};
|
||||||
if !rgba {
|
if !alpha {
|
||||||
img = DynamicImage::ImageRgb8(img.into_rgb8());
|
img = DynamicImage::ImageRgb8(img.into_rgb8());
|
||||||
}
|
}
|
||||||
img
|
img
|
||||||
|
|
|
||||||
|
|
@ -383,7 +383,7 @@ impl Kitty {
|
||||||
tokio::task::spawn_blocking(move || match img {
|
tokio::task::spawn_blocking(move || match img {
|
||||||
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),
|
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),
|
||||||
DynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),
|
DynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),
|
||||||
v => output(v.to_rgb8().as_raw(), 24, size),
|
v => output(v.into_rgb8().as_raw(), 24, size),
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl KittyOld {
|
||||||
tokio::task::spawn_blocking(move || match img {
|
tokio::task::spawn_blocking(move || match img {
|
||||||
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),
|
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),
|
||||||
DynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),
|
DynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),
|
||||||
v => output(v.to_rgb8().as_raw(), 24, size),
|
v => output(v.into_rgb8().as_raw(), 24, size),
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ mod adapter;
|
||||||
mod chafa;
|
mod chafa;
|
||||||
mod dimension;
|
mod dimension;
|
||||||
mod emulator;
|
mod emulator;
|
||||||
|
mod iip;
|
||||||
mod image;
|
mod image;
|
||||||
mod iterm2;
|
|
||||||
mod kitty;
|
mod kitty;
|
||||||
mod kitty_old;
|
mod kitty_old;
|
||||||
mod sixel;
|
mod sixel;
|
||||||
|
|
@ -15,7 +15,7 @@ pub use adapter::*;
|
||||||
use chafa::*;
|
use chafa::*;
|
||||||
pub use dimension::*;
|
pub use dimension::*;
|
||||||
pub use emulator::*;
|
pub use emulator::*;
|
||||||
use iterm2::*;
|
use iip::*;
|
||||||
use kitty::*;
|
use kitty::*;
|
||||||
use kitty_old::*;
|
use kitty_old::*;
|
||||||
use sixel::*;
|
use sixel::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue