feat: support transparent image preview (#1556)

Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
Diego Dorado 2024-09-14 13:00:43 -03:00 committed by GitHub
parent 276f69128d
commit 0acf5c288e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 52 additions and 41 deletions

View file

@ -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 {

View file

@ -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![],

View file

@ -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)

View file

@ -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

View file

@ -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?
}

View file

@ -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?
}

View file

@ -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::*;