mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-07 12:05:34 +00:00
fix: Render images as Widgets instead of paragraphs in Ratatui
- Replaced paragraph-based image rendering with the Widget trait - Renamed `build_preview_paragraph` to `build_preview_widget`, returning an enum wrapper for rendering - Updated image cache to store a grid of Ratatui `Cell` instead of text - Implemented dedicated image rendering to eliminate text-based workarounds
This commit is contained in:
parent
51eef8ba8d
commit
b35304f1c3
@ -4,13 +4,17 @@ use crate::preview::{
|
|||||||
PREVIEW_NOT_SUPPORTED_MSG, TIMEOUT_MSG,
|
PREVIEW_NOT_SUPPORTED_MSG, TIMEOUT_MSG,
|
||||||
};
|
};
|
||||||
use crate::screen::colors::{Colorscheme, PreviewColorscheme};
|
use crate::screen::colors::{Colorscheme, PreviewColorscheme};
|
||||||
|
use crate::utils::image::ImagePreviewWidget;
|
||||||
use crate::utils::strings::{
|
use crate::utils::strings::{
|
||||||
replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig,
|
replace_non_printable, shrink_with_ellipsis, ReplaceNonPrintableConfig,
|
||||||
EMPTY_STRING,
|
EMPTY_STRING,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use devicons::FileIcon;
|
use devicons::FileIcon;
|
||||||
use ratatui::widgets::{Block, BorderType, Borders, Padding, Paragraph, Wrap};
|
use ratatui::buffer::Buffer;
|
||||||
|
use ratatui::widgets::{
|
||||||
|
Block, BorderType, Borders, Padding, Paragraph, Widget, Wrap,
|
||||||
|
};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
@ -22,6 +26,22 @@ use std::str::FromStr;
|
|||||||
const FILL_CHAR_SLANTED: char = '╱';
|
const FILL_CHAR_SLANTED: char = '╱';
|
||||||
const FILL_CHAR_EMPTY: char = ' ';
|
const FILL_CHAR_EMPTY: char = ' ';
|
||||||
|
|
||||||
|
pub enum PreviewWidget<'a> {
|
||||||
|
Paragraph(Paragraph<'a>),
|
||||||
|
Image(ImagePreviewWidget<'a>),
|
||||||
|
}
|
||||||
|
impl Widget for PreviewWidget<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
PreviewWidget::Paragraph(p) => p.render(area, buf),
|
||||||
|
PreviewWidget::Image(image) => image.render(area, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn draw_preview_content_block(
|
pub fn draw_preview_content_block(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
@ -40,7 +60,7 @@ pub fn draw_preview_content_block(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// render the preview content
|
// render the preview content
|
||||||
let rp = build_preview_paragraph(
|
let rp = build_preview_widget(
|
||||||
inner,
|
inner,
|
||||||
&preview_state.preview.content,
|
&preview_state.preview.content,
|
||||||
preview_state.target_line,
|
preview_state.target_line,
|
||||||
@ -51,13 +71,13 @@ pub fn draw_preview_content_block(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_preview_paragraph<'a>(
|
pub fn build_preview_widget<'a>(
|
||||||
inner: Rect,
|
inner: Rect,
|
||||||
preview_content: &'a PreviewContent,
|
preview_content: &'a PreviewContent,
|
||||||
target_line: Option<u16>,
|
target_line: Option<u16>,
|
||||||
preview_scroll: u16,
|
preview_scroll: u16,
|
||||||
colorscheme: &'a Colorscheme,
|
colorscheme: &'a Colorscheme,
|
||||||
) -> Paragraph<'a> {
|
) -> PreviewWidget<'a> {
|
||||||
let preview_block =
|
let preview_block =
|
||||||
Block::default().style(Style::default()).padding(Padding {
|
Block::default().style(Style::default()).padding(Padding {
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -65,67 +85,79 @@ pub fn build_preview_paragraph<'a>(
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 1,
|
left: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
match preview_content {
|
match preview_content {
|
||||||
PreviewContent::AnsiText(text) => {
|
PreviewContent::AnsiText(text) => PreviewWidget::Paragraph(
|
||||||
build_ansi_text_paragraph(text, preview_block, preview_scroll)
|
build_ansi_text_paragraph(text, preview_block, preview_scroll),
|
||||||
}
|
|
||||||
PreviewContent::PlainText(content) => build_plain_text_paragraph(
|
|
||||||
content,
|
|
||||||
preview_block,
|
|
||||||
target_line,
|
|
||||||
preview_scroll,
|
|
||||||
colorscheme.preview,
|
|
||||||
),
|
),
|
||||||
PreviewContent::PlainTextWrapped(content) => {
|
PreviewContent::PlainText(content) => {
|
||||||
|
PreviewWidget::Paragraph(build_plain_text_paragraph(
|
||||||
|
content,
|
||||||
|
preview_block,
|
||||||
|
target_line,
|
||||||
|
preview_scroll,
|
||||||
|
colorscheme.preview,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
PreviewContent::PlainTextWrapped(content) => PreviewWidget::Paragraph(
|
||||||
build_plain_text_wrapped_paragraph(
|
build_plain_text_wrapped_paragraph(
|
||||||
content,
|
content,
|
||||||
preview_block,
|
preview_block,
|
||||||
colorscheme.preview,
|
colorscheme.preview,
|
||||||
)
|
)
|
||||||
.scroll((preview_scroll, 0))
|
.scroll((preview_scroll, 0)),
|
||||||
}
|
),
|
||||||
PreviewContent::SyntectHighlightedText(highlighted_lines) => {
|
PreviewContent::SyntectHighlightedText(highlighted_lines) => {
|
||||||
build_syntect_highlighted_paragraph(
|
PreviewWidget::Paragraph(build_syntect_highlighted_paragraph(
|
||||||
&highlighted_lines.lines,
|
&highlighted_lines.lines,
|
||||||
preview_block,
|
preview_block,
|
||||||
target_line,
|
target_line,
|
||||||
preview_scroll,
|
preview_scroll,
|
||||||
colorscheme.preview,
|
colorscheme.preview,
|
||||||
inner.height,
|
inner.height,
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
PreviewContent::Image(image) => image.paragraph(inner, preview_block),
|
PreviewContent::Image(image) => PreviewWidget::Image(
|
||||||
|
image.image_preview_widget(inner, preview_block),
|
||||||
|
),
|
||||||
|
|
||||||
// meta
|
// meta
|
||||||
PreviewContent::Loading => {
|
PreviewContent::Loading => PreviewWidget::Paragraph(
|
||||||
build_meta_preview_paragraph(inner, LOADING_MSG, FILL_CHAR_EMPTY)
|
build_meta_preview_paragraph(inner, LOADING_MSG, FILL_CHAR_EMPTY)
|
||||||
.block(preview_block)
|
.block(preview_block)
|
||||||
.alignment(Alignment::Left)
|
.alignment(Alignment::Left)
|
||||||
.style(Style::default().add_modifier(Modifier::ITALIC))
|
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||||
}
|
),
|
||||||
PreviewContent::NotSupported => build_meta_preview_paragraph(
|
PreviewContent::NotSupported => PreviewWidget::Paragraph(
|
||||||
inner,
|
build_meta_preview_paragraph(
|
||||||
PREVIEW_NOT_SUPPORTED_MSG,
|
inner,
|
||||||
FILL_CHAR_EMPTY,
|
PREVIEW_NOT_SUPPORTED_MSG,
|
||||||
)
|
FILL_CHAR_EMPTY,
|
||||||
.block(preview_block)
|
)
|
||||||
.alignment(Alignment::Left)
|
.block(preview_block)
|
||||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
.alignment(Alignment::Left)
|
||||||
PreviewContent::FileTooLarge => build_meta_preview_paragraph(
|
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||||
inner,
|
),
|
||||||
FILE_TOO_LARGE_MSG,
|
PreviewContent::FileTooLarge => PreviewWidget::Paragraph(
|
||||||
FILL_CHAR_EMPTY,
|
build_meta_preview_paragraph(
|
||||||
)
|
inner,
|
||||||
.block(preview_block)
|
FILE_TOO_LARGE_MSG,
|
||||||
.alignment(Alignment::Left)
|
FILL_CHAR_EMPTY,
|
||||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
)
|
||||||
PreviewContent::Timeout => {
|
.block(preview_block)
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||||
|
),
|
||||||
|
|
||||||
|
PreviewContent::Timeout => PreviewWidget::Paragraph(
|
||||||
build_meta_preview_paragraph(inner, TIMEOUT_MSG, FILL_CHAR_EMPTY)
|
build_meta_preview_paragraph(inner, TIMEOUT_MSG, FILL_CHAR_EMPTY)
|
||||||
|
.block(preview_block)
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
||||||
|
),
|
||||||
|
PreviewContent::Empty => {
|
||||||
|
PreviewWidget::Paragraph(Paragraph::new(Text::raw(EMPTY_STRING)))
|
||||||
}
|
}
|
||||||
.block(preview_block)
|
|
||||||
.alignment(Alignment::Left)
|
|
||||||
.style(Style::default().add_modifier(Modifier::ITALIC)),
|
|
||||||
PreviewContent::Empty => Paragraph::new(Text::raw(EMPTY_STRING)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
use image::imageops::FilterType;
|
use image::imageops::FilterType;
|
||||||
use image::{DynamicImage, GenericImageView, Pixel, Rgba, RgbaImage};
|
use image::{DynamicImage, GenericImageView, Pixel, Rgba};
|
||||||
use ratatui::layout::{Alignment, Rect};
|
use ratatui::buffer::{Buffer, Cell};
|
||||||
use ratatui::prelude::{Color, Span, Style, Text};
|
use ratatui::layout::{Position, Rect};
|
||||||
use ratatui::text::Line;
|
use ratatui::prelude::Color;
|
||||||
use ratatui::widgets::{Block, Paragraph};
|
use ratatui::widgets::{Block, Widget};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
const PIXEL: char = '▀';
|
static PIXEL_STRING: &str = "▀";
|
||||||
const FILTER_TYPE: FilterType = FilterType::Lanczos3;
|
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
|
||||||
@ -18,14 +18,71 @@ const CACHED_HEIGHT: u32 = 128;
|
|||||||
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]);
|
||||||
|
|
||||||
struct Cache {
|
pub struct ImagePreviewWidget<'a> {
|
||||||
area: Rect,
|
block: Block<'a>,
|
||||||
image: RgbaImage,
|
displayed_image: Arc<Mutex<Option<DisplayedImage>>>,
|
||||||
|
}
|
||||||
|
impl<'a> ImagePreviewWidget<'a> {
|
||||||
|
pub fn new(
|
||||||
|
displayed_image: Arc<Mutex<Option<DisplayedImage>>>,
|
||||||
|
block: ratatui::widgets::Block<'a>,
|
||||||
|
) -> ImagePreviewWidget<'a> {
|
||||||
|
ImagePreviewWidget {
|
||||||
|
block,
|
||||||
|
displayed_image,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Widget for ImagePreviewWidget<'_> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
// I also put the render of the block to be similar has the preview paragraph, but I believe they are both useless since the block is already rendered
|
||||||
|
let inner = self.block.inner(area);
|
||||||
|
self.block.render(area, buf);
|
||||||
|
self.displayed_image
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.render(inner, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct DisplayedImage {
|
||||||
|
pub area: Rect,
|
||||||
|
cells: Vec<Vec<Cell>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &DisplayedImage {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let height = self.cells.len();
|
||||||
|
if height == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let width = self.cells[0].len();
|
||||||
|
// offset of the left top corner where the image is centered
|
||||||
|
let x_offset = (usize::from(area.width + 2 * area.x) - width) / 2;
|
||||||
|
let y_offset = (usize::from(area.height + 2 * area.y) - height) / 2;
|
||||||
|
|
||||||
|
for (y, row) in self.cells.iter().enumerate() {
|
||||||
|
for (x, cell) in row.iter().enumerate() {
|
||||||
|
if let Some(buf_cell) = buf.cell_mut(Position::new(
|
||||||
|
u16::try_from(x_offset + x).unwrap_or(u16::MAX),
|
||||||
|
u16::try_from(y_offset + y).unwrap_or(u16::MAX),
|
||||||
|
)) {
|
||||||
|
*buf_cell = cell.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CachedImageData {
|
pub struct CachedImageData {
|
||||||
image: DynamicImage,
|
image: DynamicImage,
|
||||||
inner_cache: Arc<Mutex<Option<Cache>>>,
|
inner_cache: Arc<Mutex<Option<DisplayedImage>>>,
|
||||||
}
|
}
|
||||||
impl Hash for CachedImageData {
|
impl Hash for CachedImageData {
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
@ -64,16 +121,16 @@ impl CachedImageData {
|
|||||||
self.image.width()
|
self.image.width()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cache(&self) -> &Arc<Mutex<Option<Cache>>> {
|
fn cache(&self) -> &Arc<Mutex<Option<DisplayedImage>>> {
|
||||||
&self.inner_cache
|
&self.inner_cache
|
||||||
}
|
}
|
||||||
pub fn set_cache(&self, area: Rect, image: RgbaImage) {
|
pub fn set_cache(&self, area: Rect, cells: Vec<Vec<Cell>>) {
|
||||||
let mut mutex_cache = self.cache().lock().unwrap();
|
let mut mutex_cache = self.cache().lock().unwrap();
|
||||||
if let Some(cache) = mutex_cache.as_mut() {
|
if let Some(cache) = mutex_cache.as_mut() {
|
||||||
cache.area = area;
|
cache.area = area;
|
||||||
cache.image = image;
|
cache.cells = cells;
|
||||||
} else {
|
} else {
|
||||||
*mutex_cache = Some(Cache { area, image });
|
*mutex_cache = Some(DisplayedImage { area, cells });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn from_dynamic_image(dynamic_image: DynamicImage) -> Self {
|
pub fn from_dynamic_image(dynamic_image: DynamicImage) -> Self {
|
||||||
@ -91,98 +148,108 @@ impl CachedImageData {
|
|||||||
};
|
};
|
||||||
CachedImageData::new(resized_image)
|
CachedImageData::new(resized_image)
|
||||||
}
|
}
|
||||||
fn text_from_rgba_image_ref(image_rgba: &RgbaImage) -> Text<'static> {
|
pub fn image_preview_widget<'a>(
|
||||||
let lines = image_rgba
|
&self,
|
||||||
|
inner: Rect,
|
||||||
|
preview_block: Block<'a>,
|
||||||
|
) -> ImagePreviewWidget<'a> {
|
||||||
|
// inner has to be the inner of the preview_block given
|
||||||
|
|
||||||
|
// if nothing in the cache of the image, or the area has changed, generate a new image to be displayed and cache it
|
||||||
|
if self.cache().lock().unwrap().is_none()
|
||||||
|
|| self.cache().lock().unwrap().as_ref().unwrap().area != inner
|
||||||
|
{
|
||||||
|
self.set_cache(inner, self.cells_for_area(inner));
|
||||||
|
}
|
||||||
|
ImagePreviewWidget::new(self.inner_cache.clone(), preview_block)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
.resize(preview_width, preview_height, FILTER_TYPE)
|
||||||
|
.into_rgba8()
|
||||||
|
} else {
|
||||||
|
self.image.to_rgba8()
|
||||||
|
};
|
||||||
|
//creation of the grid of cell
|
||||||
|
image_rgba
|
||||||
// iter over pair of rows
|
// iter over pair of rows
|
||||||
.rows()
|
.rows()
|
||||||
.step_by(2)
|
.step_by(2)
|
||||||
.zip(image_rgba.rows().skip(1).step_by(2))
|
.zip(image_rgba.rows().skip(1).step_by(2))
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(double_row_y, (row_1, row_2))| {
|
.map(|(double_row_y, (row_1, row_2))| {
|
||||||
Line::from_iter(row_1.into_iter().zip(row_2).enumerate().map(
|
// create rows of cells
|
||||||
|(x, (color_up, color_down))| {
|
row_1
|
||||||
convert_pixel_to_span(
|
.into_iter()
|
||||||
color_up,
|
.zip(row_2)
|
||||||
color_down,
|
.enumerate()
|
||||||
(x, double_row_y),
|
.map(|(x, (color_up, color_down))| {
|
||||||
)
|
let position = (x, double_row_y);
|
||||||
},
|
DoublePixel::new(*color_up, *color_down)
|
||||||
))
|
.add_grid_background(position)
|
||||||
|
.into_cell()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Cell>>()
|
||||||
})
|
})
|
||||||
.collect::<Vec<Line>>();
|
.collect::<Vec<Vec<Cell>>>()
|
||||||
|
|
||||||
Text::from(lines).centered()
|
|
||||||
}
|
}
|
||||||
pub fn paragraph<'a>(
|
}
|
||||||
&self,
|
|
||||||
inner: Rect,
|
|
||||||
preview_block: Block<'a>,
|
|
||||||
) -> Paragraph<'a> {
|
|
||||||
let preview_width = u32::from(inner.width);
|
|
||||||
let preview_height = u32::from(inner.height) * 2; // *2 because 2 pixels per character
|
|
||||||
let text_image = if self.cache().lock().unwrap().is_none()
|
|
||||||
|| self.cache().lock().unwrap().as_ref().unwrap().area != inner
|
|
||||||
{
|
|
||||||
let image_rgba = if self.image.width() > preview_width
|
|
||||||
|| self.image.height() > preview_height
|
|
||||||
{
|
|
||||||
//warn!("===========================");
|
|
||||||
self.image
|
|
||||||
.resize(preview_width, preview_height, FILTER_TYPE)
|
|
||||||
.into_rgba8()
|
|
||||||
} else {
|
|
||||||
self.image.to_rgba8()
|
|
||||||
};
|
|
||||||
|
|
||||||
// transform it into text
|
// util to convert Rgba into ratatui's Cell
|
||||||
let text = Self::text_from_rgba_image_ref(&image_rgba);
|
struct DoublePixel {
|
||||||
// cached resized image
|
color_up: Rgba<u8>,
|
||||||
self.set_cache(inner, image_rgba);
|
color_down: Rgba<u8>,
|
||||||
text
|
}
|
||||||
|
impl DoublePixel {
|
||||||
|
pub fn new(color_up: Rgba<u8>, color_down: Rgba<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
color_up,
|
||||||
|
color_down,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_grid_background(mut self, position: (usize, usize)) -> Self {
|
||||||
|
let color_up = self.color_up.0;
|
||||||
|
let color_down = self.color_down.0;
|
||||||
|
self.color_up = Self::blend_with_background(color_up, position, 0);
|
||||||
|
self.color_down = Self::blend_with_background(color_down, position, 1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blend_with_background(
|
||||||
|
color: impl Into<Rgba<u8>>,
|
||||||
|
position: (usize, usize),
|
||||||
|
offset: usize,
|
||||||
|
) -> Rgba<u8> {
|
||||||
|
let color = color.into();
|
||||||
|
if color[3] == 255 {
|
||||||
|
color
|
||||||
} else {
|
} else {
|
||||||
let cache = self.cache().lock().unwrap();
|
let is_white = (position.0 + position.1 * 2 + offset) % 2 == 0;
|
||||||
let image = &cache.as_ref().unwrap().image;
|
let mut base = if is_white { WHITE } else { GRAY };
|
||||||
Self::text_from_rgba_image_ref(image)
|
base.blend(&color);
|
||||||
};
|
base
|
||||||
Paragraph::new(text_image)
|
}
|
||||||
.block(preview_block)
|
}
|
||||||
.alignment(Alignment::Center)
|
|
||||||
|
pub fn into_cell(self) -> Cell {
|
||||||
|
let mut cell = Cell::new(PIXEL_STRING);
|
||||||
|
cell.set_bg(Self::convert_image_color_to_ratatui_color(
|
||||||
|
self.color_down,
|
||||||
|
))
|
||||||
|
.set_fg(Self::convert_image_color_to_ratatui_color(self.color_up));
|
||||||
|
cell
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_image_color_to_ratatui_color(color: Rgba<u8>) -> Color {
|
||||||
|
Color::Rgb(color[0], color[1], color[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_pixel_to_span<'a>(
|
|
||||||
color_up: &Rgba<u8>,
|
|
||||||
color_down: &Rgba<u8>,
|
|
||||||
position: (usize, usize),
|
|
||||||
) -> Span<'a> {
|
|
||||||
let color_up = color_up.0;
|
|
||||||
let color_down = color_down.0;
|
|
||||||
|
|
||||||
let color_up = blend_with_background(color_up, position, 0);
|
|
||||||
let color_down = blend_with_background(color_down, position, 1);
|
|
||||||
|
|
||||||
let color_up = convert_image_color_to_ratatui_color(color_up);
|
|
||||||
let color_down = convert_image_color_to_ratatui_color(color_down);
|
|
||||||
|
|
||||||
let style = Style::default().fg(color_up).bg(color_down);
|
|
||||||
Span::styled(String::from(PIXEL), style)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blend_with_background(
|
|
||||||
color: impl Into<Rgba<u8>>,
|
|
||||||
position: (usize, usize),
|
|
||||||
offset: usize,
|
|
||||||
) -> Rgba<u8> {
|
|
||||||
let color = color.into();
|
|
||||||
if color[3] == 255 {
|
|
||||||
color
|
|
||||||
} else {
|
|
||||||
let is_white = (position.0 + position.1 * 2 + offset) % 2 == 0;
|
|
||||||
let mut base = if is_white { WHITE } else { GRAY };
|
|
||||||
base.blend(&color);
|
|
||||||
base
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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