mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: new image_quality and sixel_fraction options to allow users to configure the image preview quality (#576)
This commit is contained in:
parent
087448fd94
commit
ba0c860b2e
4 changed files with 65 additions and 36 deletions
|
|
@ -2,7 +2,7 @@ use std::{fs::File, io::BufReader, path::{Path, PathBuf}};
|
|||
|
||||
use anyhow::Result;
|
||||
use exif::{In, Tag};
|
||||
use image::{imageops::{self, FilterType}, io::Limits, DynamicImage, ImageFormat};
|
||||
use image::{codecs::jpeg::JpegEncoder, imageops::{self, FilterType}, io::Limits, DynamicImage};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::{PREVIEW, TASKS};
|
||||
use yazi_shared::term::Term;
|
||||
|
|
@ -20,23 +20,32 @@ impl Image {
|
|||
.await??;
|
||||
|
||||
let (mut w, mut h) = (PREVIEW.max_width, PREVIEW.max_height);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if (5..=8).contains(&orientation) {
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
if (5..=8).contains(&orientation) {
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
|
||||
let buf = tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, FilterType::Triangle);
|
||||
}
|
||||
|
||||
img = Self::rotate(img, orientation);
|
||||
Ok(match img {
|
||||
DynamicImage::ImageRgb8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
|
||||
DynamicImage::ImageRgba8(buf) => buf.save_with_format(cache, ImageFormat::Jpeg),
|
||||
buf => buf.into_rgb8().save_with_format(cache, ImageFormat::Jpeg),
|
||||
}?)
|
||||
if !matches!(img, DynamicImage::ImageRgb8(_) | DynamicImage::ImageRgba8(_)) {
|
||||
img = DynamicImage::ImageRgb8(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(),
|
||||
)?;
|
||||
Ok::<_, anyhow::Error>(buf)
|
||||
})
|
||||
.await?
|
||||
.await??;
|
||||
|
||||
Ok(tokio::fs::write(cache, buf).await?)
|
||||
}
|
||||
|
||||
pub(super) async fn downscale(path: &Path, rect: Rect) -> Result<DynamicImage> {
|
||||
|
|
@ -49,15 +58,19 @@ impl Image {
|
|||
.await??;
|
||||
|
||||
let (mut w, mut h) = Self::max_size(rect);
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if (5..=8).contains(&orientation) {
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
if (5..=8).contains(&orientation) {
|
||||
(w, h) = (h, w);
|
||||
}
|
||||
|
||||
// Fast path.
|
||||
if img.width() <= w && img.height() <= h && orientation <= 1 {
|
||||
return Ok(img);
|
||||
}
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if img.width() > w || img.height() > h {
|
||||
img = img.resize(w, h, FilterType::Triangle)
|
||||
}
|
||||
|
||||
Ok(Self::rotate(img, orientation))
|
||||
})
|
||||
.await?
|
||||
|
|
@ -73,6 +86,12 @@ impl Image {
|
|||
}
|
||||
|
||||
async fn orientation(path: &Path) -> Result<u8> {
|
||||
// We don't want to read the orientation of the cached image that has been
|
||||
// rotated in the `Self::precache()` step.
|
||||
if path.parent() == Some(&PREVIEW.cache_dir) {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let path = path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let file = std::fs::File::open(path)?;
|
||||
|
|
@ -96,23 +115,16 @@ impl Image {
|
|||
// https://magnushoff.com/articles/jpeg-orientation/
|
||||
fn rotate(mut img: DynamicImage, orientation: u8) -> DynamicImage {
|
||||
let rgba = img.color().has_alpha();
|
||||
if orientation == 2 {
|
||||
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
|
||||
} else if orientation == 3 {
|
||||
img = DynamicImage::ImageRgba8(imageops::rotate180(&img));
|
||||
} else if orientation == 4 {
|
||||
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
|
||||
} else if orientation == 5 {
|
||||
img = DynamicImage::ImageRgba8(imageops::rotate90(&img));
|
||||
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
|
||||
} else if orientation == 6 {
|
||||
img = DynamicImage::ImageRgba8(imageops::rotate90(&img));
|
||||
} else if orientation == 7 {
|
||||
img = DynamicImage::ImageRgba8(imageops::rotate270(&img));
|
||||
img = DynamicImage::ImageRgba8(imageops::flip_horizontal(&img));
|
||||
} else if orientation == 8 {
|
||||
img = DynamicImage::ImageRgba8(imageops::rotate270(&img));
|
||||
}
|
||||
img = match orientation {
|
||||
2 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
|
||||
3 => DynamicImage::ImageRgba8(imageops::rotate180(&img)),
|
||||
4 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&img)),
|
||||
5 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&imageops::rotate90(&img))),
|
||||
6 => DynamicImage::ImageRgba8(imageops::rotate90(&img)),
|
||||
7 => DynamicImage::ImageRgba8(imageops::flip_horizontal(&imageops::rotate270(&img))),
|
||||
8 => DynamicImage::ImageRgba8(imageops::rotate270(&img)),
|
||||
_ => img,
|
||||
};
|
||||
if !rgba {
|
||||
img = DynamicImage::ImageRgb8(img.into_rgb8());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use anyhow::{bail, Result};
|
|||
use color_quant::NeuQuant;
|
||||
use image::DynamicImage;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::PREVIEW;
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
use crate::{adaptor::Adaptor, Image, CLOSE, ESCAPE, START};
|
||||
|
|
@ -44,7 +45,7 @@ impl Sixel {
|
|||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let img = img.into_rgba8();
|
||||
let nq = NeuQuant::new(10, 256 - alpha as usize, &img);
|
||||
let nq = NeuQuant::new(PREVIEW.sixel_fraction as i32, 256 - alpha as usize, &img);
|
||||
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(1 << 16);
|
||||
write!(buf, "{}P0;1;8q\"1;1;{};{}", START, img.width(), img.height())?;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ tab_size = 2
|
|||
max_width = 600
|
||||
max_height = 900
|
||||
cache_dir = ""
|
||||
image_quality = 75
|
||||
sixel_fraction = 15
|
||||
ueberzug_scale = 1
|
||||
ueberzug_offset = [ 0, 0, 0, 0 ]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::{fs, path::PathBuf, process, time::{self, SystemTime}};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
use yazi_shared::fs::expand_path;
|
||||
|
||||
use crate::{xdg::Xdg, ARGS, MERGED_YAZI};
|
||||
use crate::{validation::check_validation, xdg::Xdg, ARGS, MERGED_YAZI};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Preview {
|
||||
|
|
@ -13,6 +14,9 @@ pub struct Preview {
|
|||
|
||||
pub cache_dir: PathBuf,
|
||||
|
||||
pub image_quality: u8,
|
||||
pub sixel_fraction: u8,
|
||||
|
||||
pub ueberzug_scale: f32,
|
||||
pub ueberzug_offset: (f32, f32, f32, f32),
|
||||
}
|
||||
|
|
@ -23,7 +27,7 @@ impl Default for Preview {
|
|||
struct Outer {
|
||||
preview: Shadow,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Validate)]
|
||||
struct Shadow {
|
||||
tab_size: u8,
|
||||
max_width: u32,
|
||||
|
|
@ -31,11 +35,18 @@ impl Default for Preview {
|
|||
|
||||
cache_dir: Option<String>,
|
||||
|
||||
#[validate(range(min = 50, max = 90))]
|
||||
image_quality: u8,
|
||||
#[validate(range(min = 10, max = 20))]
|
||||
sixel_fraction: u8,
|
||||
|
||||
ueberzug_scale: f32,
|
||||
ueberzug_offset: (f32, f32, f32, f32),
|
||||
}
|
||||
|
||||
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview;
|
||||
check_validation(preview.validate());
|
||||
|
||||
let cache_dir =
|
||||
preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path);
|
||||
|
||||
|
|
@ -63,6 +74,9 @@ impl Default for Preview {
|
|||
|
||||
cache_dir,
|
||||
|
||||
image_quality: preview.image_quality,
|
||||
sixel_fraction: preview.sixel_fraction,
|
||||
|
||||
ueberzug_scale: preview.ueberzug_scale,
|
||||
ueberzug_offset: preview.ueberzug_offset,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue