mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
133 lines
3.3 KiB
Rust
133 lines
3.3 KiB
Rust
use std::{io::Write, path::PathBuf};
|
|
|
|
use anyhow::{Result, bail};
|
|
use image::{DynamicImage, GenericImageView, RgbImage};
|
|
use palette::{Srgb, cast::ComponentsAs};
|
|
use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
|
|
use ratatui_core::layout::Rect;
|
|
use yazi_config::THEME;
|
|
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
|
use yazi_tty::sequence::{MoveTo, ResetAttrs, SetBg};
|
|
|
|
use crate::{ADAPTOR, Image};
|
|
|
|
pub(super) struct Sixel;
|
|
|
|
struct QuantizeOutput<T> {
|
|
indices: Vec<u8>,
|
|
palette: Vec<T>,
|
|
}
|
|
|
|
impl Sixel {
|
|
pub(super) async fn image_show(path: PathBuf, 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.image_hide()?;
|
|
ADAPTOR.shown_store(area);
|
|
Emulator::move_lock((area.x, area.y), |w| {
|
|
w.write_all(&b)?;
|
|
Ok(area)
|
|
})
|
|
}
|
|
|
|
pub(super) fn image_erase(area: Rect) -> Result<()> {
|
|
let s = " ".repeat(area.width as usize);
|
|
Emulator::move_lock((0, 0), |w| {
|
|
if let Some(c) = THEME.app.overall.get().bg {
|
|
write!(w, "{}", SetBg(c))?;
|
|
}
|
|
for y in area.top()..area.bottom() {
|
|
write!(w, "{}", MoveTo(area.x, y))?;
|
|
write!(w, "{s}")?;
|
|
}
|
|
Ok(write!(w, "{ResetAttrs}")?)
|
|
})
|
|
}
|
|
|
|
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
|
|
let alpha = img.color().has_alpha();
|
|
if img.width() == 0 || img.height() == 0 {
|
|
bail!("image is empty");
|
|
}
|
|
|
|
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??;
|
|
|
|
tokio::task::spawn_blocking(move || {
|
|
let mut buf = vec![];
|
|
write!(buf, "{START}P9;1q\"1;1;{};{}", img.width(), img.height())?;
|
|
|
|
// Palette
|
|
for (i, c) in qo.palette.iter().enumerate() {
|
|
write!(
|
|
buf,
|
|
"#{};2;{};{};{}",
|
|
i + alpha as usize,
|
|
c.red as u16 * 100 / 255,
|
|
c.green as u16 * 100 / 255,
|
|
c.blue as u16 * 100 / 255
|
|
)?;
|
|
}
|
|
|
|
for y in 0..img.height() {
|
|
let c = (b'?' + (1 << (y % 6))) as char;
|
|
|
|
let mut last = 0;
|
|
let mut repeat = 0usize;
|
|
for x in 0..img.width() {
|
|
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);
|
|
continue;
|
|
}
|
|
|
|
if repeat > 1 {
|
|
write!(buf, "#{last}!{repeat}{c}")?;
|
|
} else {
|
|
write!(buf, "#{last}{c}")?;
|
|
}
|
|
|
|
(last, repeat) = (idx, 1);
|
|
}
|
|
|
|
if repeat > 1 {
|
|
write!(buf, "#{last}!{repeat}{c}")?;
|
|
} else {
|
|
write!(buf, "#{last}{c}")?;
|
|
}
|
|
|
|
write!(buf, "$")?;
|
|
if y % 6 == 5 {
|
|
write!(buf, "-")?;
|
|
}
|
|
}
|
|
|
|
write!(buf, "{ESCAPE}\\{CLOSE}")?;
|
|
Ok(buf)
|
|
})
|
|
.await?
|
|
}
|
|
|
|
fn quantify(rgb: &RgbImage, alpha: bool) -> Result<QuantizeOutput<Srgb<u8>>> {
|
|
let buf = &rgb.as_raw()[..(rgb.pixels().len() * 3)];
|
|
let colors: &[Srgb<u8>] = buf.components_as();
|
|
|
|
let wu = WuU8x3::run_slice(colors, BinnerU8x3::rgb())?;
|
|
let color_map = wu.color_map(PaletteSize::try_from(256u16 - alpha as u16)?);
|
|
|
|
Ok(QuantizeOutput {
|
|
indices: color_map.map_to_indices(colors),
|
|
palette: color_map.into_palette().into_vec(),
|
|
})
|
|
}
|
|
}
|