mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01: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 yazi_shared::env_exists;
|
||||
|
||||
use super::{Iterm2, Kitty, KittyOld};
|
||||
use super::{Iip, Kitty, KittyOld};
|
||||
use crate::{Chafa, Emulator, Sixel, Ueberzug, SHOWN, TMUX, WSL};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Adapter {
|
||||
Kitty,
|
||||
KittyOld,
|
||||
Iterm2,
|
||||
Iip,
|
||||
Sixel,
|
||||
|
||||
// Supported by Überzug++
|
||||
|
|
@ -26,7 +26,7 @@ impl Display for Adapter {
|
|||
match self {
|
||||
Self::Kitty => write!(f, "kitty"),
|
||||
Self::KittyOld => write!(f, "kitty"),
|
||||
Self::Iterm2 => write!(f, "iterm2"),
|
||||
Self::Iip => write!(f, "iip"),
|
||||
Self::Sixel => write!(f, "sixel"),
|
||||
Self::X11 => write!(f, "x11"),
|
||||
Self::Wayland => write!(f, "wayland"),
|
||||
|
|
@ -44,7 +44,7 @@ impl Adapter {
|
|||
match self {
|
||||
Self::Kitty => Kitty::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::X11 | Self::Wayland => Ueberzug::image_show(path, max).await,
|
||||
Self::Chafa => Chafa::image_show(path, max).await,
|
||||
|
|
@ -59,7 +59,7 @@ impl Adapter {
|
|||
match self {
|
||||
Self::Kitty => Kitty::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::X11 | Self::Wayland => Ueberzug::image_erase(area),
|
||||
Self::Chafa => Chafa::image_erase(area),
|
||||
|
|
@ -76,7 +76,7 @@ impl Adapter {
|
|||
|
||||
#[inline]
|
||||
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();
|
||||
#[cfg(windows)]
|
||||
protocols.retain(|p| *p == Self::Iterm2);
|
||||
protocols.retain(|p| *p == Self::Iip);
|
||||
if env_exists("ZELLIJ_SESSION_NAME") {
|
||||
protocols.retain(|p| *p == Self::Sixel);
|
||||
} else if *TMUX {
|
||||
|
|
|
|||
|
|
@ -35,16 +35,16 @@ impl Emulator {
|
|||
Self::Unknown(adapters) => adapters,
|
||||
Self::Kitty => vec![Adapter::Kitty],
|
||||
Self::Konsole => vec![Adapter::KittyOld],
|
||||
Self::Iterm2 => vec![Adapter::Iterm2, Adapter::Sixel],
|
||||
Self::WezTerm => vec![Adapter::Iterm2, Adapter::Sixel],
|
||||
Self::Iterm2 => vec![Adapter::Iip, Adapter::Sixel],
|
||||
Self::WezTerm => vec![Adapter::Iip, Adapter::Sixel],
|
||||
Self::Foot => vec![Adapter::Sixel],
|
||||
Self::Ghostty => vec![Adapter::Kitty],
|
||||
Self::Microsoft => vec![Adapter::Sixel],
|
||||
Self::BlackBox => vec![Adapter::Sixel],
|
||||
Self::VSCode => vec![Adapter::Iterm2, Adapter::Sixel],
|
||||
Self::Tabby => vec![Adapter::Iterm2, Adapter::Sixel],
|
||||
Self::Hyper => vec![Adapter::Iterm2, Adapter::Sixel],
|
||||
Self::Mintty => vec![Adapter::Iterm2],
|
||||
Self::VSCode => vec![Adapter::Iip, Adapter::Sixel],
|
||||
Self::Tabby => vec![Adapter::Iip, Adapter::Sixel],
|
||||
Self::Hyper => vec![Adapter::Iip, Adapter::Sixel],
|
||||
Self::Mintty => vec![Adapter::Iip],
|
||||
Self::Neovim => vec![],
|
||||
Self::Apple => vec![],
|
||||
Self::Urxvt => vec![],
|
||||
|
|
|
|||
|
|
@ -3,21 +3,22 @@ use std::{io::Write, path::Path};
|
|||
use anyhow::Result;
|
||||
use base64::{engine::{general_purpose::STANDARD, Config}, Engine};
|
||||
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 yazi_config::PREVIEW;
|
||||
|
||||
use super::image::Image;
|
||||
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> {
|
||||
let img = Image::downscale(path, max).await?;
|
||||
let area = Image::pixel_area((img.width(), img.height()), max);
|
||||
let b = Self::encode(img).await?;
|
||||
|
||||
Adapter::Iterm2.image_hide()?;
|
||||
Adapter::Iip.image_hide()?;
|
||||
Adapter::shown_store(area);
|
||||
Emulator::move_lock((max.x, max.y), |stderr| {
|
||||
stderr.write_all(&b)?;
|
||||
|
|
@ -38,20 +39,26 @@ impl Iterm2 {
|
|||
|
||||
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut jpg = vec![];
|
||||
JpegEncoder::new_with_quality(&mut jpg, 75).encode_image(&img)?;
|
||||
let (w, h) = (img.width(), img.height());
|
||||
|
||||
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));
|
||||
|
||||
write!(
|
||||
buf,
|
||||
"{}]1337;File=inline=1;size={};width={}px;height={}px;doNotMoveCursor=1:{}\x07{}",
|
||||
START,
|
||||
jpg.len(),
|
||||
img.width(),
|
||||
img.height(),
|
||||
STANDARD.encode(&jpg),
|
||||
b.len(),
|
||||
w,
|
||||
h,
|
||||
STANDARD.encode(b),
|
||||
CLOSE
|
||||
)?;
|
||||
Ok(buf)
|
||||
|
|
@ -2,7 +2,7 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
|
|||
|
||||
use anyhow::Result;
|
||||
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 yazi_config::{PREVIEW, TASKS};
|
||||
|
||||
|
|
@ -30,19 +30,23 @@ impl Image {
|
|||
img = img.resize(w, h, Self::filter());
|
||||
}
|
||||
|
||||
let mut buf = Vec::new();
|
||||
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();
|
||||
JpegEncoder::new_with_quality(&mut buf, PREVIEW.image_quality).encode(
|
||||
img.as_bytes(),
|
||||
img.width(),
|
||||
img.height(),
|
||||
img.color().into(),
|
||||
)?;
|
||||
Ok::<_, anyhow::Error>(buf)
|
||||
Ok::<_, ImageError>(buf)
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
|
@ -138,7 +142,7 @@ impl Image {
|
|||
|
||||
// https://magnushoff.com/articles/jpeg-orientation/
|
||||
fn rotate(mut img: DynamicImage, orientation: u8) -> DynamicImage {
|
||||
let rgba = img.color().has_alpha();
|
||||
let alpha = img.color().has_alpha();
|
||||
img = match orientation {
|
||||
2 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
|
||||
3 => DynamicImage::ImageRgba8(imageops::rotate180(&img)),
|
||||
|
|
@ -149,7 +153,7 @@ impl Image {
|
|||
8 => DynamicImage::ImageRgba8(imageops::rotate270(&img)),
|
||||
_ => img,
|
||||
};
|
||||
if !rgba {
|
||||
if !alpha {
|
||||
img = DynamicImage::ImageRgb8(img.into_rgb8());
|
||||
}
|
||||
img
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ impl Kitty {
|
|||
tokio::task::spawn_blocking(move || match img {
|
||||
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, 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?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ impl KittyOld {
|
|||
tokio::task::spawn_blocking(move || match img {
|
||||
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, 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?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ mod adapter;
|
|||
mod chafa;
|
||||
mod dimension;
|
||||
mod emulator;
|
||||
mod iip;
|
||||
mod image;
|
||||
mod iterm2;
|
||||
mod kitty;
|
||||
mod kitty_old;
|
||||
mod sixel;
|
||||
|
|
@ -15,7 +15,7 @@ pub use adapter::*;
|
|||
use chafa::*;
|
||||
pub use dimension::*;
|
||||
pub use emulator::*;
|
||||
use iterm2::*;
|
||||
use iip::*;
|
||||
use kitty::*;
|
||||
use kitty_old::*;
|
||||
use sixel::*;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue