feat: Chafa integration

This commit is contained in:
sxyazi 2024-05-21 00:32:13 +08:00
parent a68e151194
commit d51962ce83
No known key found for this signature in database
9 changed files with 144 additions and 85 deletions

View file

@ -36,48 +36,37 @@ impl Display for Adaptor {
}
impl Adaptor {
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
match self {
Self::Kitty => Kitty::image_show(path, rect).await,
Self::KittyOld => KittyOld::image_show(path, rect).await,
Self::Iterm2 => Iterm2::image_show(path, rect).await,
Self::Sixel => Sixel::image_show(path, rect).await,
_ => Ueberzug::image_show(path, rect).await,
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::Sixel => Sixel::image_show(path, max).await,
_ => Ueberzug::image_show(path, max).await,
}
}
pub fn image_hide(self) -> Result<()> {
if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) }
if let Some(area) = SHOWN.swap(None) { self.image_erase(*area) } else { Ok(()) }
}
pub fn image_erase(self, rect: Rect) -> Result<()> {
pub fn image_erase(self, area: Rect) -> Result<()> {
match self {
Self::Kitty => Kitty::image_erase(rect),
Self::Iterm2 => Iterm2::image_erase(rect),
Self::Kitty => Kitty::image_erase(area),
Self::Iterm2 => Iterm2::image_erase(area),
Self::KittyOld => KittyOld::image_erase(),
Self::Sixel => Sixel::image_erase(rect),
_ => Ueberzug::image_erase(rect),
Self::Sixel => Sixel::image_erase(area),
_ => Ueberzug::image_erase(area),
}
}
#[inline]
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) }
pub(super) fn start(self) { Ueberzug::start(self); }
#[inline]
pub(super) fn shown_store(rect: Rect, size: (u32, u32)) {
SHOWN.store(Some(Arc::new(
Term::ratio()
.map(|(r1, r2)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
})
.unwrap_or(rect),
)));
}
pub(super) fn shown_store(area: Rect) { SHOWN.store(Some(Arc::new(area))); }
pub(super) fn start(self) { Ueberzug::start(self); }
#[inline]
pub(super) fn needs_ueberzug(self) -> bool {

63
yazi-adaptor/src/chafa.rs Normal file
View file

@ -0,0 +1,63 @@
use std::{path::Path, process::Stdio};
use anyhow::{bail, Result};
use imagesize::ImageSize;
use ratatui::layout::Rect;
use tokio::process::Command;
use crate::Image;
pub(super) struct Chafa;
impl Chafa {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
let p = path.to_owned();
let ImageSize { width: w, height: h } =
tokio::task::spawn_blocking(move || imagesize::size(p)).await??;
let output = Command::new("chafa")
.args([
"-f",
"symbols",
"--relative",
"off",
"--polite",
"on",
"--passthrough",
"none",
"--animate",
"off",
"--view-size",
])
.arg(format!("{}x{}", rect.width, rect.height))
.arg(path)
.stdin(Stdio::null())
.stderr(Stdio::null())
.stdout(Stdio::piped())
.kill_on_drop(true)
.output()
.await?;
if !output.status.success() {
bail!("chafa failed with status: {}", output.status);
}
// output.stdout
let (max_w, max_h) = Image::max_pixel(rect);
if w <= max_w as usize && h <= max_h as usize {
return Ok((w as u32, h as u32));
}
let ratio = f64::min(max_w as f64 / w as f64, max_h as f64 / h as f64);
Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32))
}
pub(super) fn image_erase(_: Rect) -> Result<()> {
if let Some(tx) = &*DEMON {
Ok(tx.send(None)?)
} else {
bail!("uninitialized ueberzugpp");
}
}
}

View file

@ -57,7 +57,7 @@ impl Image {
})
.await??;
let (mut w, mut h) = Self::max_size(rect);
let (mut w, mut h) = Self::max_pixel(rect);
if (5..=8).contains(&orientation) {
(w, h) = (h, w);
}
@ -76,7 +76,7 @@ impl Image {
.await?
}
pub(super) fn max_size(rect: Rect) -> (u32, u32) {
pub(super) fn max_pixel(rect: Rect) -> (u32, u32) {
Term::ratio()
.map(|(r1, r2)| {
let (w, h) = ((rect.width as f64 * r1) as u32, (rect.height as f64 * r2) as u32);
@ -85,6 +85,17 @@ impl Image {
.unwrap_or((PREVIEW.max_width, PREVIEW.max_height))
}
pub(super) fn pixel_area(size: (u32, u32), rect: Rect) -> Rect {
Term::ratio()
.map(|(r1, r2)| Rect {
x: rect.x,
y: rect.y,
width: (size.0 as f64 / r1).ceil() as u16,
height: (size.1 as f64 / r2).ceil() as u16,
})
.unwrap_or(rect)
}
#[inline]
fn filter() -> FilterType {
match PREVIEW.image_filter.as_str() {

View file

@ -12,24 +12,24 @@ use crate::{adaptor::Adaptor, CLOSE, START};
pub(super) struct Iterm2;
impl Iterm2 {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
let img = Image::downscale(path, rect).await?;
let size = (img.width(), img.height());
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?;
Adaptor::Iterm2.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adaptor::shown_store(area);
Term::move_lock((max.x, max.y), |stderr| {
stderr.write_all(&b)?;
Ok(size)
Ok(area)
})
}
pub(super) fn image_erase(rect: Rect) -> Result<()> {
let s = " ".repeat(rect.width as usize);
pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Term::move_lock((0, 0), |stderr| {
for y in rect.top()..rect.bottom() {
Term::move_to(stderr, rect.x, y)?;
for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?;
write!(stderr, "{s}")?;
}
Ok(())

View file

@ -3,7 +3,7 @@ use std::{io::Write, path::Path};
use anyhow::Result;
use base64::{engine::general_purpose, Engine};
use image::DynamicImage;
use image::{DynamicImage, GenericImageView};
use ratatui::layout::Rect;
use yazi_shared::term::Term;
@ -313,27 +313,27 @@ static DIACRITICS: [char; 297] = [
pub(super) struct Kitty;
impl Kitty {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
let img = Image::downscale(path, rect).await?;
let size = (img.width(), img.height());
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 b1 = Self::encode(img).await?;
let b2 = Self::place(&rect)?;
let b2 = Self::place(&area)?;
Adaptor::Kitty.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adaptor::shown_store(area);
Term::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b1)?;
stderr.write_all(&b2)?;
Ok(size)
Ok(area)
})
}
pub(super) fn image_erase(rect: Rect) -> Result<()> {
let s = " ".repeat(rect.width as usize);
pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Term::move_lock((0, 0), |stderr| {
for y in rect.top()..rect.bottom() {
Term::move_to(stderr, rect.x, y)?;
for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?;
write!(stderr, "{s}")?;
}
@ -388,11 +388,11 @@ impl Kitty {
.await?
}
fn place(rect: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(rect.width as usize * rect.height as usize * 3 + 50);
for y in 0..rect.height {
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", rect.y + y + 1, rect.x + 1)?;
for x in 0..rect.width {
fn place(area: &Rect) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(area.width as usize * area.height as usize * 3 + 50);
for y in 0..area.height {
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", area.y + y + 1, area.x + 1)?;
for x in 0..area.width {
write!(buf, "\u{10EEEE}")?;
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;

View file

@ -13,16 +13,16 @@ use crate::{adaptor::Adaptor, CLOSE, ESCAPE, START};
pub(super) struct KittyOld;
impl KittyOld {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
let img = Image::downscale(path, rect).await?;
let size = (img.width(), img.height());
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?;
Adaptor::KittyOld.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adaptor::shown_store(area);
Term::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b)?;
Ok(size)
Ok(area)
})
}

View file

@ -1,6 +1,7 @@
#![allow(clippy::unit_arg)]
mod adaptor;
mod chafa;
mod emulator;
mod image;
mod iterm2;

View file

@ -12,24 +12,24 @@ use crate::{adaptor::Adaptor, Image, CLOSE, ESCAPE, START};
pub(super) struct Sixel;
impl Sixel {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
let img = Image::downscale(path, rect).await?;
let size = (img.width(), img.height());
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?;
Adaptor::Sixel.image_hide()?;
Adaptor::shown_store(rect, size);
Term::move_lock((rect.x, rect.y), |stderr| {
Adaptor::shown_store(area);
Term::move_lock((area.x, area.y), |stderr| {
stderr.write_all(&b)?;
Ok(size)
Ok(area)
})
}
pub(super) fn image_erase(rect: Rect) -> Result<()> {
let s = " ".repeat(rect.width as usize);
pub(super) fn image_erase(area: Rect) -> Result<()> {
let s = " ".repeat(area.width as usize);
Term::move_lock((0, 0), |stderr| {
for y in rect.top()..rect.bottom() {
Term::move_to(stderr, rect.x, y)?;
for y in area.top()..area.bottom() {
Term::move_to(stderr, area.x, y)?;
write!(stderr, "{s}")?;
}
Ok(())

View file

@ -41,25 +41,20 @@ impl Ueberzug {
DEMON.init(Some(tx))
}
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<(u32, u32)> {
if let Some(tx) = &*DEMON {
tx.send(Some((path.to_path_buf(), rect)))?;
Adaptor::shown_store(rect, (0, 0));
} else {
pub(super) async fn image_show(path: &Path, max: Rect) -> Result<Rect> {
let Some(tx) = &*DEMON else {
bail!("uninitialized ueberzugpp");
}
};
let path = path.to_owned();
let p = path.to_owned();
let ImageSize { width: w, height: h } =
tokio::task::spawn_blocking(move || imagesize::size(path)).await??;
tokio::task::spawn_blocking(move || imagesize::size(p)).await??;
let (max_w, max_h) = Image::max_size(rect);
if w <= max_w as usize && h <= max_h as usize {
return Ok((w as u32, h as u32));
}
let area = Image::pixel_area((w as u32, h as u32), max);
tx.send(Some((path.to_owned(), max)))?;
let ratio = f64::min(max_w as f64 / w as f64, max_h as f64 / h as f64);
Ok(((w as f64 * ratio).round() as u32, (h as f64 * ratio).round() as u32))
Adaptor::shown_store(area);
Ok(area)
}
pub(super) fn image_erase(_: Rect) -> Result<()> {