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]) } - -