From e9f385c8eb2bf897980abf55463efdf6c5c1c986 Mon Sep 17 00:00:00 2001 From: azy Date: Tue, 18 Feb 2025 15:02:29 +0800 Subject: [PATCH] fix: - the more time consuming operation is in fact to open jpeg, tried to find more efficient way to open file than the default way of the image crate but nothing really worked - removed warnings - clean some part of the code --- Cargo.toml | 3 +- television/preview/mod.rs | 9 +- television/preview/previewers/files.rs | 21 ++- television/screen/preview.rs | 1 - television/television.rs | 3 +- television/utils/image.rs | 206 ++----------------------- 6 files changed, 29 insertions(+), 214 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ebf69d0..d83f704 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,8 +59,7 @@ bat = { version = "0.25", default-features = false, features = ["regex-onig"] } gag = "1.0" nucleo = "0.5" toml = "0.8" -image = "0.25.5" -fast_image_resize = { version = "5.1.2", features = ["image"]} +image = "0.25" [target.'cfg(windows)'.dependencies] diff --git a/television/preview/mod.rs b/television/preview/mod.rs index 4e178d0..2ca49db 100644 --- a/television/preview/mod.rs +++ b/television/preview/mod.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use crate::channels::entry::{Entry, PreviewType}; use devicons::FileIcon; -use ratatui::layout::Rect; pub mod ansi; pub mod cache; @@ -212,12 +211,11 @@ impl Previewer { fn dispatch_request( &mut self, entry: &Entry, - preview_window: Option, ) -> Option> { 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, preview_window), + PreviewType::Files => self.file.preview(entry), PreviewType::Command(cmd) => self.command.preview(entry, cmd), PreviewType::None => Some(Arc::new(Preview::default())), } @@ -230,18 +228,17 @@ impl Previewer { pub fn preview( &mut self, entry: &Entry, - preview_window: Option, ) -> Option> { // if we haven't acknowledged the request yet, acknowledge it self.requests.push(entry.clone()); - if let Some(preview) = self.dispatch_request(entry, preview_window) { + if let Some(preview) = self.dispatch_request(entry) { 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, preview_window) { + if let Some(preview) = self.dispatch_request(&request) { return Some(preview); } } diff --git a/television/preview/previewers/files.rs b/television/preview/previewers/files.rs index ed3cbea..f0f4d83 100644 --- a/television/preview/previewers/files.rs +++ b/television/preview/previewers/files.rs @@ -1,8 +1,7 @@ use crate::utils::files::{read_into_lines_capped, ReadResult}; use crate::utils::syntax::HighlightedLines; -use image::ImageReader; +use image::{ImageReader}; use parking_lot::Mutex; -use ratatui::layout::Rect; use rustc_hash::{FxBuildHasher, FxHashSet}; use std::collections::HashSet; use std::fs::File; @@ -84,7 +83,6 @@ impl FilePreviewer { pub fn preview( &mut self, entry: &entry::Entry, - preview_window: Option, ) -> Option> { if let Some(preview) = self.cached(entry) { trace!("Preview cache hit for {:?}", entry.name); @@ -94,15 +92,14 @@ impl FilePreviewer { debug!("Spawning partial preview task for {:?}", entry.name); self.handle_preview_request( entry, - Some(preview.clone()), - preview_window, + Some(preview.clone()) ); } 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, preview_window); + self.handle_preview_request(entry, None); None } } @@ -110,8 +107,7 @@ impl FilePreviewer { pub fn handle_preview_request( &mut self, entry: &entry::Entry, - partial_preview: Option>, - preview_window: Option, + partial_preview: Option> ) { if self.in_flight_previews.lock().contains(&entry.name) { trace!("Preview already in flight for {:?}", entry.name); @@ -249,13 +245,13 @@ pub fn try_preview( } } } else if matches!(FileType::from(&path), FileType::Image) { + cache.lock().insert( + entry.name.clone(), + &meta::loading(&format!("Loading {}", entry.name)), + ); debug!("File {:?} is an image", entry.name); match ImageReader::open(path).unwrap().decode() { Ok(image) => { - cache.lock().insert( - entry.name.clone(), - &meta::loading(&format!("Loading {}", entry.name)), - ); let cached_image_data = CachedImageData::from_dynamic_image(image); let total_lines = cached_image_data.height().try_into().unwrap_or(u16::MAX); @@ -275,6 +271,7 @@ pub fn try_preview( cache.lock().insert(entry.name.clone(), &p); } } + } else { debug!("File isn't text-based: {:?}", entry.name); let preview = meta::not_supported(&entry.name); diff --git a/television/screen/preview.rs b/television/screen/preview.rs index 54e1551..c048934 100644 --- a/television/screen/preview.rs +++ b/television/screen/preview.rs @@ -19,7 +19,6 @@ use ratatui::{ prelude::{Color, Line, Modifier, Span, Style, Stylize, Text}, }; use std::str::FromStr; -use crate::utils::image::{PIXEL, CachedImageData}; #[allow(dead_code)] const FILL_CHAR_SLANTED: char = '╱'; diff --git a/television/television.rs b/television/television.rs index 3a58e82..c5acc44 100644 --- a/television/television.rs +++ b/television/television.rs @@ -20,7 +20,6 @@ use copypasta::{ClipboardContext, ClipboardProvider}; use rustc_hash::{FxBuildHasher, FxHashSet}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; -use ratatui::layout::Rect; use tokio::sync::mpsc::{Receiver, Sender, UnboundedSender}; use tracing::error; @@ -316,7 +315,7 @@ impl Television { && !matches!(selected_entry.preview_type, PreviewType::None) { // preview content - if let Some(preview) = self.previewer.preview(selected_entry, Some(Rect::new(0,0,50,50))) { + if let Some(preview) = self.previewer.preview(selected_entry) { if self.preview_state.preview.title != preview.title { self.preview_state.update( preview, diff --git a/television/utils/image.rs b/television/utils/image.rs index f6676c4..1b02d1a 100644 --- a/television/utils/image.rs +++ b/television/utils/image.rs @@ -1,24 +1,17 @@ -use std::cmp; -use std::cmp::max; use std::hash::{Hash, Hasher}; -use image::{DynamicImage, GenericImageView, ImageBuffer, ImageReader, Luma, Rgb, RgbImage, Rgba, RgbaImage}; +use image::{DynamicImage, Rgba }; + -use fast_image_resize::{ ImageBufferError, ImageView, IntoImageView, PixelType, ResizeAlg, ResizeOptions, Resizer}; -use fast_image_resize::images::Image; -use fast_image_resize::pixels::{Pixel, U8x3}; -use image::buffer::ConvertBuffer; -use image::imageops; use image::imageops::FilterType; use ratatui::layout::{Alignment, Rect}; use ratatui::prelude::{Color, Span, Style, Text}; use ratatui::text::Line; use ratatui::widgets::{Block, Paragraph}; -use tracing::{debug, trace, warn}; + pub const PIXEL: char = '▀'; -pub const PIXEL_TYPE: PixelType = PixelType::U8x4; -const RESIZE_ALGORITHM: ResizeAlg = ResizeAlg::Convolution(fast_image_resize::FilterType::Lanczos3); +const FILTER_TYPE: FilterType = FilterType::Lanczos3; // use to reduce the size of the image before storing it const CACHED_WIDTH: u32 = 256; @@ -51,87 +44,28 @@ impl CachedImageData { pub fn from_dynamic_image( dynamic_image: DynamicImage, ) -> Self { - /* - let (new_width, new_height) = calculate_fit_dimensions(dynamic_image.width(), dynamic_image.height(), CACHED_WIDTH,CACHED_HEIGHT); - - // fixme resize even if useless and should just change the type - let mut dst_image = Image::new( - new_width, - new_height, - dynamic_image.pixel_type()?, - ); - - let resize_option = ResizeOptions::new().resize_alg(ResizeAlg::Nearest); - let mut resizer = Resizer::new(); - resizer.resize(&dynamic_image, &mut dst_image, Some(&resize_option)).unwrap(); - - - // convert the resized image into rgba - let rgba_image: RgbaImage = match dst_image.pixel_type() { - PixelType::U8x3 => { - let rgb_image: RgbImage = RgbImage::from_raw(new_width, new_height, dst_image.into_vec()).unwrap(); - rgb_image.convert() - } - PixelType::U16 => { - // Convert `Luma` to `Rgba` (downscaling 16-bit to 8-bit) - let rgba_pixels: Vec = dst_image.into_vec().chunks_exact(2).flat_map(|b| { - let luma16 = u16::from_le_bytes([b[0], b[1]]); - let luma8 = (luma16 >> 8) as u8; // Downscale 16-bit to 8-bit - vec![luma8, luma8, luma8, 255] - }).collect(); - ImageBuffer::from_raw(new_width, new_height, rgba_pixels) - .expect("Failed to create Rgba8 ImageBuffer from Luma16") - } - PixelType::U8x4 => { - // Directly use the buffer since it's already RGBA8 - ImageBuffer::from_raw(new_width, new_height, dst_image.into_vec()) - .expect("Failed to create Rgba8 ImageBuffer from U8x4") - } - _ => panic!("Unsupported pixel type"), - }; - */ + // if the image is smaller than the preview window, keep it small let resized_image = if dynamic_image.width() > CACHED_WIDTH || dynamic_image.height() > CACHED_HEIGHT { dynamic_image.resize(CACHED_WIDTH, CACHED_HEIGHT , FilterType::Nearest) } else { dynamic_image }; + + //convert the buffer pixels into rgba8 let rgba_image = resized_image.into_rgba8(); CachedImageData::new(DynamicImage::from(rgba_image)) } pub fn paragraph<'a>(&self, inner: Rect, preview_block: Block<'a>) -> Paragraph<'a> { - // resize it for the preview window - //let mut data = self.image.clone().into_raw(); - //let src_image = DynamicImage::from(self.image.clone()); - /* - let src_image = if let Some(src_image) = Image::from_slice_u8(self.image.width(), self.image.height(), &mut data, PixelType::U8x4) - .map_err(|error| warn!("Failed to resize cached image: {error}")) - .ok(){ - src_image - } else { - return Paragraph::new(Text::raw("Failed to resize cached image")); - }; - - let (new_width, new_height) = calculate_fit_dimensions(self.image.width(), self.image.height(), u32::from(inner.width), u32::from(inner.height*2)); - let mut dst_image = Image::new( - new_width, - new_height, - PixelType::U8x4, - ); - let resize_option = ResizeOptions::new().resize_alg(RESIZE_ALGORITHM); - let mut resizer = Resizer::new(); - resizer.resize(&src_image, &mut dst_image, &Some(resize_option)).unwrap(); - - let image_rgba: RgbaImage = ImageBuffer::from_raw( dst_image.width(), dst_image.height(), dst_image.into_vec()).unwrap(); - */ let preview_width = u32::from(inner.width); - let preview_height = u32::from(inner.height) * 2; + let preview_height = u32::from(inner.height) * 2; // *2 because 2 pixels per character let image_rgba = if self.image.width() > preview_width || self.image.height() > preview_height { - self.image.resize(preview_width, preview_height, FilterType::Triangle).to_rgba8() + &self.image.resize(preview_width, preview_height, FILTER_TYPE).into_rgba8() } else{ - self.image.to_rgba8() + self.image.as_rgba8().expect("to be rgba8 image") // converted into rgba8 before being put into the cache, so it should never enter the expect }; // transform it into text let lines = image_rgba + // iter over pair of rows .rows() .step_by(2) .zip(image_rgba.rows().skip(1).step_by(2)).enumerate() @@ -152,119 +86,9 @@ impl CachedImageData { .alignment(Alignment::Center) } - } -/* -#[derive(Clone, Debug, Hash, PartialEq)] -pub struct ImageDoublePixel { - pub pixel_grid: Vec>, -} -impl ImageDoublePixel { - pub fn new(pixel_grid: Vec>) -> Self { - ImageDoublePixel { pixel_grid } - } - pub fn from_cached_image(cached_image_data: &CachedImageData, - new_width: u32, - new_height: u32) -> Option{ - let mut binding = cached_image_data.data.clone(); - let src_image = Image::from_slice_u8(cached_image_data.width, cached_image_data.height, &mut binding, cached_image_data.pixel_type) - .map_err(|error| warn!("Failed to resize cached image: {error}")) - .ok()?; - let (new_width, new_height) = calculate_fit_dimensions(cached_image_data.width, cached_image_data.height, new_width, new_height); - let mut dst_image = Image::new( - new_width, - new_height, - cached_image_data.pixel_type, - ); - let resize_option = ResizeOptions::new().resize_alg(RESIZE_ALGORITHM); - let mut resizer = Resizer::new(); - resizer.resize(&src_image, &mut dst_image, &Some(resize_option)).unwrap(); - - - match cached_image_data.pixel_type { - PixelType::U8x4 => { - let rgba_image = ImageBuffer::from_raw(new_width, new_height, dst_image.into_vec())?; - Some(Self::from_rgba_image(rgba_image)) - }, - _ => { - warn!("Unsupported pixel type: {:?}", cached_image_data.pixel_type); - println!("Unsupported pixel type: {:?}", cached_image_data.pixel_type); - None - } - } - - } - - pub fn from_rgba_image( - image: RgbaImage - ) -> Self { - let pixel_grid = image - .rows() - .step_by(2) - .zip(image.rows().skip(1).step_by(2)) - .map(|(row_1, row_2)| { - row_1 - .zip(row_2) - .map(|(pixel_1, pixel_2)| { - ( - ImageColor { - r: pixel_1.0[0], - g: pixel_1.0[1], - b: pixel_1.0[2], - a: pixel_1.0[3], - }, - ImageColor { - r: pixel_2.0[0], - g: pixel_2.0[1], - b: pixel_2.0[2], - a: pixel_1.0[3], - }, - ) - }) - .collect::>() - }) - .collect::>>(); - ImageDoublePixel::new(pixel_grid) - } - - -} - */ -/* -#[derive(Clone, Copy, Debug, Hash, PartialEq)] -pub struct ImageColor { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, -} -impl ImageColor { - pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { - ImageColor { r, g, b, a } - } - pub const BLACK: ImageColor = ImageColor { - r: 0, - g: 0, - b: 0, - a: 255, - }; - pub const WHITE: ImageColor = ImageColor { - r: 255, - g: 255, - b: 255, - a: 255, - }; - pub const GRAY: ImageColor = ImageColor { - r: 242, - g: 242, - b: 242, - a: 255, - }; -} -*/ - - +#[allow(dead_code)] fn calculate_fit_dimensions(original_width: u32, original_height: u32, max_width: u32, max_height: u32) -> (u32, u32) { if original_width <= max_width && original_height <= max_height { return (original_width, original_height); @@ -292,8 +116,10 @@ pub fn convert_pixel_to_span<'a>( let color_up = color_up.0; let color_down = color_down.0; + // there is no in between, ether it is transparent, either it use the color let alpha_threshold = 30; let color_up = if color_up[3] <= alpha_threshold { + // choose the good color for the background if transparent if (position.0 + position.1 * 2) % 2 == 0 { WHITE } else { @@ -312,7 +138,7 @@ pub fn convert_pixel_to_span<'a>( Rgba::from(color_down) }; - let color_up = convert_image_color_to_ratatui_color(color_up); + let color_up = convert_image_color_to_ratatui_color(color_up); let color_down = convert_image_color_to_ratatui_color(color_down); @@ -323,5 +149,3 @@ pub fn convert_pixel_to_span<'a>( fn convert_image_color_to_ratatui_color(color: Rgba) -> Color { Color::Rgb(color[0], color[1], color[2]) } - -