fix: clean version with static size for image

This commit is contained in:
azy 2025-02-24 21:59:46 +09:00
parent e2260f8110
commit e71506e64c
4 changed files with 52 additions and 70 deletions

View File

@ -9,7 +9,7 @@ pub mod previewers;
// previewer types // previewer types
use crate::utils::cache::RingSet; use crate::utils::cache::RingSet;
use crate::utils::image::CachedImageData; use crate::utils::image::ImagePreviewWidget;
use crate::utils::syntax::HighlightedLines; use crate::utils::syntax::HighlightedLines;
pub use previewers::basic::BasicPreviewer; pub use previewers::basic::BasicPreviewer;
pub use previewers::basic::BasicPreviewerConfig; pub use previewers::basic::BasicPreviewerConfig;
@ -31,7 +31,7 @@ pub enum PreviewContent {
PlainText(Vec<String>), PlainText(Vec<String>),
PlainTextWrapped(String), PlainTextWrapped(String),
AnsiText(String), AnsiText(String),
Image(CachedImageData), Image(ImagePreviewWidget),
} }
impl PreviewContent { impl PreviewContent {

View File

@ -18,7 +18,7 @@ use tracing::{debug, trace, warn};
use crate::channels::entry; use crate::channels::entry;
use crate::preview::cache::PreviewCache; use crate::preview::cache::PreviewCache;
use crate::preview::{previewers::meta, Preview, PreviewContent}; use crate::preview::{previewers::meta, Preview, PreviewContent};
use crate::utils::image::CachedImageData; use crate::utils::image::ImagePreviewWidget;
use crate::utils::{ use crate::utils::{
files::FileType, files::FileType,
strings::preprocess_line, strings::preprocess_line,
@ -246,11 +246,13 @@ pub fn try_preview(
debug!("File {:?} is an image", entry.name); debug!("File {:?} is an image", entry.name);
match ImageReader::open(path).unwrap().decode() { match ImageReader::open(path).unwrap().decode() {
Ok(image) => { Ok(image) => {
let cached_image_data = let image_preview_widget =
CachedImageData::from_dynamic_image(image); ImagePreviewWidget::from_dynamic_image(image);
let total_lines = let total_lines = image_preview_widget
cached_image_data.height().try_into().unwrap_or(u16::MAX); .height()
let content = PreviewContent::Image(cached_image_data); .try_into()
.unwrap_or(u16::MAX);
let content = PreviewContent::Image(image_preview_widget);
let preview = Arc::new(Preview::new( let preview = Arc::new(Preview::new(
entry.name.clone(), entry.name.clone(),
content, content,

View File

@ -67,6 +67,7 @@ pub fn draw_preview_content_block(
colorscheme, colorscheme,
); );
f.render_widget(rp, inner); f.render_widget(rp, inner);
Ok(()) Ok(())
} }
@ -116,9 +117,7 @@ pub fn build_preview_widget<'a>(
inner.height, inner.height,
)) ))
} }
PreviewContent::Image(image) => { PreviewContent::Image(image) => PreviewWidget::Image(image.clone()),
PreviewWidget::Image(image.image_preview_widget(inner))
}
// meta // meta
PreviewContent::Loading => PreviewWidget::Paragraph( PreviewContent::Loading => PreviewWidget::Paragraph(

View File

@ -5,34 +5,27 @@ use ratatui::layout::{Position, Rect};
use ratatui::prelude::Color; use ratatui::prelude::Color;
use ratatui::widgets::Widget; use ratatui::widgets::Widget;
use std::fmt::Debug; use std::fmt::Debug;
use std::hash::{Hash, Hasher}; use std::hash::Hash;
static PIXEL_STRING: &str = ""; static PIXEL_STRING: &str = "";
const FILTER_TYPE: FilterType = FilterType::Triangle; const FILTER_TYPE: FilterType = FilterType::Lanczos3;
// use to reduce the size of the image before storing it // use to reduce the size of the image before storing it
const CACHED_WIDTH: u32 = 128; const CACHED_WIDTH: u32 = 50;
const CACHED_HEIGHT: u32 = 128; const CACHED_HEIGHT: u32 = 100;
const GRAY: Rgba<u8> = Rgba([242, 242, 242, 255]); const GRAY: Rgba<u8> = Rgba([242, 242, 242, 255]);
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]); const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct ImagePreviewWidget { pub struct ImagePreviewWidget {
cells: Vec<Vec<Cell>>, cells: Vec<Vec<Cell>>,
} }
impl ImagePreviewWidget {
pub fn new(cells: Vec<Vec<Cell>>) -> ImagePreviewWidget {
ImagePreviewWidget { cells }
}
}
impl Widget for &ImagePreviewWidget { impl Widget for &ImagePreviewWidget {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let height = self.cells.len(); let height = self.height();
if height == 0 { let width = self.width();
return;
}
let width = self.cells[0].len();
// offset of the left top corner where the image is centered // offset of the left top corner where the image is centered
let total_width = usize::from(area.width) + 2 * usize::from(area.x); let total_width = usize::from(area.width) + 2 * usize::from(area.x);
let x_offset = total_width.saturating_sub(width) / 2; let x_offset = total_width.saturating_sub(width) / 2;
@ -45,11 +38,11 @@ impl Widget for &ImagePreviewWidget {
(area.x, area.x + area.width); (area.x, area.x + area.width);
for (y, row) in self.cells.iter().enumerate() { for (y, row) in self.cells.iter().enumerate() {
let pos_y = u16::try_from(y_offset + y).unwrap_or(u16::MAX); let pos_y = u16::try_from(y_offset + y).unwrap_or(u16::MAX);
if pos_y >= area_border_up && pos_y < area_border_down{ if pos_y >= area_border_up && pos_y < area_border_down {
for (x, cell) in row.iter().enumerate() { for (x, cell) in row.iter().enumerate() {
let pos_x = u16::try_from(x_offset + x).unwrap_or(u16::MAX); let pos_x =
if pos_x >= area_border_left && pos_x < area_border_right u16::try_from(x_offset + x).unwrap_or(u16::MAX);
{ if pos_x >= area_border_left && pos_x < area_border_right {
if let Some(buf_cell) = if let Some(buf_cell) =
buf.cell_mut(Position::new(pos_x, pos_y)) buf.cell_mut(Position::new(pos_x, pos_y))
{ {
@ -61,63 +54,51 @@ impl Widget for &ImagePreviewWidget {
} }
} }
} }
#[derive(Clone, PartialEq, Debug)] impl ImagePreviewWidget {
pub struct CachedImageData { pub fn new(cells: Vec<Vec<Cell>>) -> ImagePreviewWidget {
image: DynamicImage, ImagePreviewWidget { cells }
}
impl Hash for CachedImageData {
fn hash<H: Hasher>(&self, state: &mut H) {
self.image.as_rgb8().expect("to be rgba image").hash(state);
} }
}
impl CachedImageData { pub fn height(&self) -> usize {
pub fn new(image: DynamicImage) -> Self { self.cells.len()
//convert the buffer pixels into rgba8 }
let rgba_image = image.into_rgba8(); pub fn width(&self) -> usize {
CachedImageData { if self.height() > 0 {
image: DynamicImage::from(rgba_image), self.cells[0].len()
} else {
0
} }
} }
pub fn height(&self) -> u32 {
self.image.height()
}
pub fn width(&self) -> u32 {
self.image.width()
}
pub fn from_dynamic_image(dynamic_image: DynamicImage) -> Self { pub fn from_dynamic_image(dynamic_image: DynamicImage) -> Self {
// if the image is smaller than the preview window, keep it small // first quick resize
let resized_image = if dynamic_image.width() > CACHED_WIDTH let big_resized_image = if dynamic_image.width() > CACHED_WIDTH * 4
|| dynamic_image.height() > CACHED_HEIGHT || dynamic_image.height() > CACHED_HEIGHT * 4
{ {
dynamic_image.resize( dynamic_image.resize(
CACHED_WIDTH, CACHED_WIDTH * 4,
CACHED_HEIGHT, CACHED_HEIGHT * 4,
FilterType::Nearest, FilterType::Nearest,
) )
} else { } else {
dynamic_image dynamic_image
}; };
CachedImageData::new(resized_image) // this time resize with the filter
} let resized_image = if big_resized_image.width() > CACHED_WIDTH
pub fn image_preview_widget(&self, inner: Rect) -> ImagePreviewWidget { || big_resized_image.height() > CACHED_HEIGHT
ImagePreviewWidget::new(self.cells_for_area(inner))
}
pub fn cells_for_area(&self, inner: Rect) -> Vec<Vec<Cell>> {
// size of the available area
let preview_width = u32::from(inner.width);
let preview_height = u32::from(inner.height) * 2; // *2 because 2 pixels per character
// resize if it doesn't fit in
let image_rgba = if self.image.width() > preview_width
|| self.image.height() > preview_height
{ {
self.image big_resized_image.resize(CACHED_WIDTH, CACHED_HEIGHT, FILTER_TYPE)
.resize(preview_width, preview_height, FILTER_TYPE)
.into_rgba8()
} else { } else {
self.image.to_rgba8() big_resized_image
}; };
let cells = Self::cells_from_dynamic_image(resized_image);
ImagePreviewWidget::new(cells)
}
fn cells_from_dynamic_image(image: DynamicImage) -> Vec<Vec<Cell>> {
let image_rgba = image.into_rgba8();
//creation of the grid of cell //creation of the grid of cell
image_rgba image_rgba
// iter over pair of rows // iter over pair of rows