Add the preview of images

- New image.rs file in utils to convert images in usable array
- new accepted type of files in utils/files.rs
- the enum PreviewContent has now an Image entry to handle images
- conversion from image file to a usable paragraph into preview
This commit is contained in:
azy 2025-02-01 18:46:59 +08:00
parent 724844ba34
commit 9e1d9baaa4
6 changed files with 86 additions and 50 deletions

View File

@ -10,8 +10,8 @@ pub mod previewers;
// previewer types
use crate::utils::cache::RingSet;
use crate::utils::syntax::HighlightedLines;
use crate::utils::image::Image;
use crate::utils::syntax::HighlightedLines;
pub use previewers::basic::BasicPreviewer;
pub use previewers::basic::BasicPreviewerConfig;
pub use previewers::command::CommandPreviewer;
@ -32,7 +32,7 @@ pub enum PreviewContent {
PlainText(Vec<String>),
PlainTextWrapped(String),
AnsiText(String),
Image(Image)
Image(Image),
}
impl PreviewContent {
@ -170,7 +170,11 @@ impl Previewer {
}
}
fn dispatch_request(&mut self, entry: &Entry, preview_window: Option<Rect>) -> 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)),
@ -189,7 +193,11 @@ impl Previewer {
}
}
pub fn preview(&mut self, entry: &Entry, preview_window: Option<Rect>) -> 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());

View File

@ -1,6 +1,8 @@
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;
@ -10,17 +12,15 @@ use std::sync::{
atomic::{AtomicU8, Ordering},
Arc,
};
use ratatui::layout::Rect;
use syntect::{highlighting::Theme, parsing::SyntaxSet};
use tracing::{debug, warn};
use image::ImageReader;
use crate::channels::entry;
use crate::preview::cache::PreviewCache;
use crate::preview::{previewers::meta, Preview, PreviewContent};
use crate::utils::{
image::Image,
files::FileType,
image::Image,
strings::preprocess_line,
syntax::{self, load_highlighting_assets, HighlightingAssetsExt},
};
@ -80,14 +80,22 @@ impl FilePreviewer {
self.cache.lock().get(&entry.name)
}
pub fn preview(&mut self, entry: &entry::Entry, preview_window: Option<Rect>) -> 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) {
debug!("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()), preview_window);
self.handle_preview_request(
entry,
Some(preview.clone()),
preview_window,
);
}
Some(preview)
} else {
@ -241,15 +249,18 @@ pub fn try_preview(
cache.lock().insert(entry.name.clone(), &p);
}
}
} else if matches!(FileType::from(&path), FileType::Image)
{
} else if matches!(FileType::from(&path), FileType::Image) {
debug!("File is an image: {:?}", entry.name);
let (window_height, window_width) = if let Some(preview_window) = preview_window{
let (window_height, window_width) = if let Some(preview_window) =
preview_window
{
// it should be a better way to know the size of the border to remove than this magic number
let padding = 5;
((preview_window.height - padding /2 ) * 2, preview_window.width - padding)
}else{
(
(preview_window.height - padding / 2) * 2,
preview_window.width - padding,
)
} else {
warn!("Error opening image, impossible to display without information about the size of the preview window");
let p = meta::not_supported(&entry.name);
cache.lock().insert(entry.name.clone(), &p);
@ -259,8 +270,13 @@ pub fn try_preview(
Ok(image) => {
debug!("Width: {:}", window_width);
let image = Image::from_dynamic_image(image, window_height as u32, window_width as u32);
let total_lines = image.pixel_grid.len().try_into().unwrap_or(u16::MAX);
let image = Image::from_dynamic_image(
image,
u32::from(window_height),
u32::from(window_width),
);
let total_lines =
image.pixel_grid.len().try_into().unwrap_or(u16::MAX);
let content = PreviewContent::Image(image);
let preview = Arc::new(Preview::new(
entry.name.clone(),
@ -277,7 +293,6 @@ 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);

View File

@ -7,6 +7,7 @@ use crate::screen::{
cache::RenderedPreviewCache,
colors::{Colorscheme, PreviewColorscheme},
};
use crate::utils::image::{Image, ImageColor, PIXEL};
use crate::utils::strings::{
replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig,
EMPTY_STRING,
@ -21,7 +22,6 @@ use ratatui::{
};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use crate::utils::image::{Image, ImageColor, PIXEL};
#[allow(dead_code)]
const FILL_CHAR_SLANTED: char = '';
@ -70,7 +70,7 @@ pub fn build_preview_paragraph<'a>(
)
}
PreviewContent::Image(image) => {
build_image_paragraph(image, preview_block,colorscheme.preview)
build_image_paragraph(image, preview_block, colorscheme.preview)
}
// meta
@ -226,16 +226,25 @@ fn build_image_paragraph(
preview_block: Block<'_>,
colorscheme: PreviewColorscheme,
) -> Paragraph<'_> {
let lines = image.pixel_grid.iter().map(|double_pixel_line|
Line::from_iter(
double_pixel_line.iter().map(|(color_up, color_down)|
convert_pixel_to_span(*color_up, *color_down, Some(colorscheme.highlight_bg)))
)
).collect::<Vec<Line>>();
let lines = image
.pixel_grid
.into_iter()
.map(|double_pixel_line| {
Line::from_iter(double_pixel_line.iter().map(
|(color_up, color_down)| {
convert_pixel_to_span(
*color_up,
*color_down,
Some(colorscheme.highlight_bg),
)
},
))
})
.collect::<Vec<Line>>();
let text = Text::from(lines);
Paragraph::new(text)
.block(preview_block)
.wrap(Wrap { trim: true })
.wrap(Wrap { trim: false })
}
pub fn build_meta_preview_paragraph<'a>(
@ -538,8 +547,8 @@ pub fn convert_pixel_to_span<'a>(
background: Option<Color>,
) -> Span<'a> {
let bg_color = match background {
Some(Color::Rgb(r, g, b)) => Some((r,g,b)),
_ => {None}
Some(Color::Rgb(r, g, b)) => Some((r, g, b)),
_ => None,
};
let (color_up, color_down) = if let Some(bg_color) = bg_color {
let color_up_with_alpha = Color::Rgb(
@ -548,25 +557,26 @@ pub fn convert_pixel_to_span<'a>(
(color_up.b * color_up.a + bg_color.2 * 255 - color_up.a) / 255,
);
let color_down_with_alpha = Color::Rgb(
(color_down.r * color_down.a + bg_color.0 * 255 - color_down.a) / 255,
(color_down.b * color_down.a + bg_color.1 * 255 - color_down.a) / 255,
(color_down.b * color_down.a + bg_color.2 * 255 - color_down.a) / 255,
(color_down.r * color_down.a + bg_color.0 * 255 - color_down.a)
/ 255,
(color_down.b * color_down.a + bg_color.1 * 255 - color_down.a)
/ 255,
(color_down.b * color_down.a + bg_color.2 * 255 - color_down.a)
/ 255,
);
(color_up_with_alpha,color_down_with_alpha)
}else{
(convert_image_color_to_ratatui_color(color_up),
convert_image_color_to_ratatui_color(color_down))
(color_up_with_alpha, color_down_with_alpha)
} else {
(
convert_image_color_to_ratatui_color(color_up),
convert_image_color_to_ratatui_color(color_down),
)
};
let style = Style::default()
.fg(color_up)
.bg(color_down);
let style = Style::default().fg(color_up).bg(color_down);
Span::styled(String::from(PIXEL), style)
}
fn convert_image_color_to_ratatui_color(
color: ImageColor,
) -> Color {
fn convert_image_color_to_ratatui_color(color: ImageColor) -> Color {
Color::Rgb(color.r, color.g, color.b)
}

View File

@ -587,11 +587,14 @@ impl Television {
if self.config.ui.show_preview_panel
&& !matches!(selected_entry.preview_type, PreviewType::None)
{
// preview content
let maybe_preview = self.previewer.preview(&selected_entry, layout.preview_window);
let maybe_preview = self
.previewer
.preview(&selected_entry, layout.preview_window);
let _ = self.previewer.preview(&selected_entry, layout.preview_window);
let _ = self
.previewer
.preview(&selected_entry, layout.preview_window);
if let Some(preview) = &maybe_preview {
self.current_preview_total_lines = preview.total_lines;

View File

@ -119,7 +119,7 @@ where
if is_known_text_extension(p) {
return FileType::Text;
}
if is_accepted_image_extension(p){
if is_accepted_image_extension(p) {
return FileType::Image;
}
if let Ok(mut f) = File::open(p) {
@ -503,7 +503,7 @@ lazy_static! {
//"farbfeld",
"gif",
//"hdr",
//"ico",
"ico",
"jpeg",
"jpg",
//"exr",

View File

@ -1,6 +1,7 @@
pub mod cache;
pub mod command;
pub mod files;
pub mod image;
pub mod indices;
pub mod input;
pub mod metadata;
@ -9,4 +10,3 @@ pub mod stdin;
pub mod strings;
pub mod syntax;
pub mod threads;
pub mod image;