mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
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
This commit is contained in:
parent
64aa6179eb
commit
e9f385c8eb
@ -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]
|
||||
|
@ -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<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, 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<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, 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<Rect>,
|
||||
) -> Option<Arc<Preview>> {
|
||||
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<Arc<Preview>>,
|
||||
preview_window: Option<Rect>,
|
||||
partial_preview: Option<Arc<Preview>>
|
||||
) {
|
||||
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);
|
||||
|
@ -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 = '╱';
|
||||
|
@ -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,
|
||||
|
@ -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<u16>` to `Rgba<u8>` (downscaling 16-bit to 8-bit)
|
||||
let rgba_pixels: Vec<u8> = 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<Vec<(ImageColor, ImageColor)>>,
|
||||
}
|
||||
impl ImageDoublePixel {
|
||||
pub fn new(pixel_grid: Vec<Vec<(ImageColor, ImageColor)>>) -> Self {
|
||||
ImageDoublePixel { pixel_grid }
|
||||
}
|
||||
pub fn from_cached_image(cached_image_data: &CachedImageData,
|
||||
new_width: u32,
|
||||
new_height: u32) -> Option<Self>{
|
||||
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::<Vec<(ImageColor, ImageColor)>>()
|
||||
})
|
||||
.collect::<Vec<Vec<(ImageColor, ImageColor)>>>();
|
||||
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<u8>) -> Color {
|
||||
Color::Rgb(color[0], color[1], color[2])
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user