mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
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:
parent
724844ba34
commit
9e1d9baaa4
@ -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());
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
}
|
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user