fix: use the layout information from television.ui_state to resize with the optimal size

- add argument preview_window in those functions/methods:
	Previewer.preview -> Previewer.dispatch_request -> FilePreviewer.preview ->  FilePreviewer.handle_preview_request -> try_preview -> Image.from_dynamic_image
   for try_preview, #[allow(clippy::too_many_arguments)] has been added since it has 8 arguments, pehraph would it be better to create a struct instead
 - resize image if possible with the size of the preview window
 - get_known_image_file_extensions function has been changed to follow the way of the text equivalent
This commit is contained in:
azy 2025-03-05 16:56:40 +09:00
parent 32c4c042e7
commit 6e6ce28bf5
5 changed files with 84 additions and 48 deletions

View File

@ -2,6 +2,7 @@ use std::sync::Arc;
use crate::channels::entry::{Entry, PreviewType};
use devicons::FileIcon;
use ratatui::layout::Rect;
pub mod ansi;
pub mod cache;
@ -208,11 +209,15 @@ impl Previewer {
}
}
fn dispatch_request(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
fn dispatch_request(
&mut self,
entry: &Entry,
preview_window: Option<Rect>,
) -> Option<Arc<Preview>> {
match &entry.preview_type {
PreviewType::Basic => Some(self.basic.preview(entry)),
PreviewType::EnvVar => Some(self.env_var.preview(entry)),
PreviewType::Files => self.file.preview(entry),
PreviewType::Files => self.file.preview(entry, preview_window),
PreviewType::Command(cmd) => self.command.preview(entry, cmd),
PreviewType::None => Some(Arc::new(Preview::default())),
}
@ -222,17 +227,23 @@ impl Previewer {
// faster, but since it's already running in the background and quite
// fast for most standard file sizes, plus we're caching the previews,
// I'm not sure the extra complexity is worth it.
pub fn preview(&mut self, entry: &Entry) -> Option<Arc<Preview>> {
pub fn preview(
&mut self,
entry: &Entry,
preview_window: Option<Rect>,
) -> Option<Arc<Preview>> {
// if we haven't acknowledged the request yet, acknowledge it
self.requests.push(entry.clone());
if let Some(preview) = self.dispatch_request(entry) {
if let Some(preview) = self.dispatch_request(entry, preview_window) {
return Some(preview);
}
// lookup request stack and return the most recent preview available
for request in self.requests.back_to_front() {
if let Some(preview) = self.dispatch_request(&request) {
if let Some(preview) =
self.dispatch_request(&request, preview_window)
{
return Some(preview);
}
}

View File

@ -2,6 +2,7 @@ use crate::utils::files::{read_into_lines_capped, ReadResult};
use crate::utils::syntax::HighlightedLines;
use image::ImageReader;
use parking_lot::Mutex;
use ratatui::layout::Rect;
use rustc_hash::{FxBuildHasher, FxHashSet};
use std::collections::HashSet;
use std::fs::File;
@ -11,7 +12,6 @@ use std::sync::{
atomic::{AtomicU8, Ordering},
Arc,
};
use syntect::{highlighting::Theme, parsing::SyntaxSet};
use tracing::{debug, trace, warn};
@ -80,20 +80,28 @@ impl FilePreviewer {
self.cache.lock().get(&entry.name)
}
pub fn preview(&mut self, entry: &entry::Entry) -> Option<Arc<Preview>> {
pub fn preview(
&mut self,
entry: &entry::Entry,
preview_window: Option<Rect>,
) -> Option<Arc<Preview>> {
if let Some(preview) = self.cached(entry) {
trace!("Preview cache hit for {:?}", entry.name);
if preview.partial_offset.is_some() {
// preview is partial, spawn a task to compute the next chunk
// and return the partial preview
debug!("Spawning partial preview task for {:?}", entry.name);
self.handle_preview_request(entry, Some(preview.clone()));
self.handle_preview_request(
entry,
Some(preview.clone()),
preview_window,
);
}
Some(preview)
} else {
// preview is not in cache, spawn a task to compute the preview
trace!("Preview cache miss for {:?}", entry.name);
self.handle_preview_request(entry, None);
self.handle_preview_request(entry, None, preview_window);
None
}
}
@ -102,6 +110,7 @@ impl FilePreviewer {
&mut self,
entry: &entry::Entry,
partial_preview: Option<Arc<Preview>>,
preview_window: Option<Rect>,
) {
if self.in_flight_previews.lock().contains(&entry.name) {
trace!("Preview already in flight for {:?}", entry.name);
@ -128,6 +137,7 @@ impl FilePreviewer {
&syntax_theme,
&concurrent_tasks,
&in_flight_previews,
preview_window,
);
});
}
@ -143,6 +153,7 @@ impl FilePreviewer {
/// This ends up being the max size of partial previews.
const PARTIAL_BUFREAD_SIZE: usize = 5 * 1024 * 1024;
#[allow(clippy::too_many_arguments)]
pub fn try_preview(
entry: &entry::Entry,
partial_preview: Option<Arc<Preview>>,
@ -151,6 +162,7 @@ pub fn try_preview(
syntax_theme: &Arc<Theme>,
concurrent_tasks: &Arc<AtomicU8>,
in_flight_previews: &Arc<Mutex<FxHashSet<String>>>,
preview_window: Option<Rect>,
) {
debug!("Computing preview for {:?}", entry.name);
let path = PathBuf::from(&entry.name);
@ -246,8 +258,14 @@ pub fn try_preview(
debug!("File {:?} is an image", entry.name);
match ImageReader::open(path).unwrap().decode() {
Ok(image) => {
let preview_window_dimension = preview_window.map(|rect| {
(u32::from(rect.width), u32::from(rect.height))
});
let image_preview_widget =
ImagePreviewWidget::from_dynamic_image(image);
ImagePreviewWidget::from_dynamic_image(
image,
preview_window_dimension,
);
let total_lines = image_preview_widget
.height()
.try_into()

View File

@ -304,7 +304,10 @@ impl Television {
&& !matches!(selected_entry.preview_type, PreviewType::None)
{
// preview content
if let Some(preview) = self.previewer.preview(selected_entry) {
if let Some(preview) = self
.previewer
.preview(selected_entry, self.ui_state.layout.preview_window)
{
// only update if the preview content has changed
if self.preview_state.preview.title != preview.title {
self.preview_state.update(

View File

@ -499,31 +499,26 @@ where
path.as_ref()
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| KNOWN_IMAGE_FILE_EXTENSIONS.contains(ext))
.is_some_and(|ext| get_known_image_file_extensions().contains(ext))
}
lazy_static! {
static ref KNOWN_IMAGE_FILE_EXTENSIONS: FxHashSet<&'static str> = [
pub static KNOWN_IMAGE_FILE_EXTENSIONS: OnceLock<FxHashSet<&'static str>> =
OnceLock::new();
pub fn get_known_image_file_extensions() -> &'static FxHashSet<&'static str> {
KNOWN_IMAGE_FILE_EXTENSIONS.get_or_init(|| {
[
//"avif",
//"bmp",
//"dds",
//"farbfeld",
"gif",
//"hdr",
"ico",
"jpeg",
"jpg",
//"exr",
"png",
//"pnm",
"gif", //"hdr",
"ico", "jpeg", "jpg", //"exr",
"png", //"pnm",
//"qoi",
//"tga",
"tif",
"tiff",
"webp",
"tif", "tiff", "webp",
]
.iter()
.copied()
.collect();
.collect()
})
}

View File

@ -8,11 +8,11 @@ use std::fmt::Debug;
use std::hash::Hash;
static PIXEL_STRING: &str = "";
const FILTER_TYPE: FilterType = FilterType::Lanczos3;
const FILTER_TYPE: FilterType = FilterType::Triangle;
// use to reduce the size of the image before storing it
const CACHED_WIDTH: u32 = 50;
const CACHED_HEIGHT: u32 = 100;
const DEFAULT_CACHED_WIDTH: u32 = 50;
const DEFAULT_CACHED_HEIGHT: u32 = 100;
const GRAY: Rgba<u8> = Rgba([242, 242, 242, 255]);
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
@ -70,24 +70,33 @@ impl ImagePreviewWidget {
}
}
pub fn from_dynamic_image(dynamic_image: DynamicImage) -> Self {
pub fn from_dynamic_image(
dynamic_image: DynamicImage,
dimension: Option<(u32, u32)>,
) -> Self {
// first quick resize
let big_resized_image = if dynamic_image.width() > CACHED_WIDTH * 4
|| dynamic_image.height() > CACHED_HEIGHT * 4
let (window_width, window_height) =
dimension.unwrap_or((DEFAULT_CACHED_WIDTH, DEFAULT_CACHED_HEIGHT));
let big_resized_image = if dynamic_image.width() > window_width * 4
|| dynamic_image.height() > window_height * 4
{
dynamic_image.resize(
CACHED_WIDTH * 4,
CACHED_HEIGHT * 4,
window_width * 4,
window_height * 4,
FilterType::Nearest,
)
} else {
dynamic_image
};
// this time resize with the filter
let resized_image = if big_resized_image.width() > CACHED_WIDTH
|| big_resized_image.height() > CACHED_HEIGHT
let resized_image = if big_resized_image.width() > window_width
|| big_resized_image.height() > window_height
{
big_resized_image.resize(CACHED_WIDTH, CACHED_HEIGHT, FILTER_TYPE)
big_resized_image.resize(
window_width,
DEFAULT_CACHED_HEIGHT,
FILTER_TYPE,
)
} else {
big_resized_image
};