mirror of
https://github.com/alexpasmantier/television.git
synced 2025-07-24 19:10:01 +00:00
139 lines
4.3 KiB
Rust
139 lines
4.3 KiB
Rust
use std::hash::{Hash, Hasher};
|
|
|
|
use image::{DynamicImage, Pixel, Rgba};
|
|
|
|
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};
|
|
|
|
const PIXEL: char = '▀';
|
|
const FILTER_TYPE: FilterType = FilterType::Lanczos3;
|
|
|
|
// use to reduce the size of the image before storing it
|
|
const CACHED_WIDTH: u32 = 128;
|
|
const CACHED_HEIGHT: u32 = 128;
|
|
|
|
const GRAY: Rgba<u8> = Rgba([242, 242, 242, 255]);
|
|
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct CachedImageData {
|
|
image: DynamicImage,
|
|
}
|
|
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 new(image: DynamicImage) -> Self {
|
|
//convert the buffer pixels into rgba8
|
|
let rgba_image = image.into_rgba8();
|
|
CachedImageData {
|
|
image: DynamicImage::from(rgba_image),
|
|
}
|
|
}
|
|
|
|
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 {
|
|
// 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
|
|
};
|
|
CachedImageData::new(resized_image)
|
|
}
|
|
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 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.as_rgba8().expect("to be rgba 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()
|
|
.map(|(double_row_y, (row_1, row_2))| {
|
|
Line::from_iter(row_1.into_iter().zip(row_2).enumerate().map(
|
|
|(x, (color_up, color_down))| {
|
|
convert_pixel_to_span(
|
|
color_up,
|
|
color_down,
|
|
(x, double_row_y),
|
|
)
|
|
},
|
|
))
|
|
})
|
|
.collect::<Vec<Line>>();
|
|
let text_image = Text::from(lines);
|
|
Paragraph::new(text_image)
|
|
.block(preview_block)
|
|
.alignment(Alignment::Center)
|
|
}
|
|
}
|
|
|
|
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])
|
|
}
|