#[cfg(all(feature = "chafa-static", feature = "chafa-dyn"))]
compile_error!("features `chafa-static` and `chafa-dyn` are mutually exclusive");
use image::DynamicImage;
use ratatui::{
buffer::{Buffer, Cell},
layout::Rect,
style::Color,
};
use super::{ProtocolTrait, StatefulProtocolTrait};
use crate::Result;
#[cfg(any(feature = "chafa-dyn", feature = "chafa-static",))]
mod chafa;
#[cfg(not(any(feature = "chafa-dyn", feature = "chafa-static",)))]
mod primitive;
#[derive(Clone, Default)]
pub struct Halfblocks {
data: Vec<HalfBlock>,
area: Rect,
}
#[derive(Clone, Debug)]
pub(crate) struct HalfBlock {
pub upper: Color,
pub lower: Color,
pub char: char,
}
impl HalfBlock {
fn set_cell(&self, cell: &mut Cell) {
cell.set_fg(self.upper)
.set_bg(self.lower)
.set_char(self.char);
}
}
impl Halfblocks {
pub fn new(image: DynamicImage, area: Rect) -> Result<Self> {
let data = encode(&image, area);
Ok(Self { data, area })
}
}
#[cfg(any(feature = "chafa-static", feature = "chafa-dyn"))]
fn encode(img: &DynamicImage, rect: Rect) -> Vec<HalfBlock> {
chafa::encode(img, rect).expect("chafa is always available with compile-time linking")
}
#[cfg(not(any(feature = "chafa-dyn", feature = "chafa-static")))]
fn encode(img: &DynamicImage, rect: Rect) -> Vec<HalfBlock> {
primitive::encode(img, rect)
}
impl ProtocolTrait for Halfblocks {
fn render(&self, area: Rect, buf: &mut Buffer) {
for (i, hb) in self.data.iter().enumerate() {
let x = i as u16 % self.area.width;
let y = i as u16 / self.area.width;
if x >= area.width || y >= area.height {
continue;
}
if let Some(cell) = buf.cell_mut((area.x + x, area.y + y)) {
hb.set_cell(cell);
}
}
}
fn area(&self) -> Rect {
self.area
}
}
impl StatefulProtocolTrait for Halfblocks {
fn resize_encode(&mut self, img: DynamicImage, area: Rect) -> Result<()> {
let data = encode(&img, area);
*self = Halfblocks { data, area };
Ok(())
}
}
#[cfg(test)]
mod tests {
use image::{Rgb, RgbImage};
use insta::assert_snapshot;
use ratatui::{Terminal, backend::TestBackend, layout::Rect};
use crate::{
Image,
protocol::{Protocol, halfblocks::Halfblocks},
};
#[test]
fn render_image() {
let mut img = RgbImage::new(2, 2);
img.put_pixel(0, 0, Rgb([255, 0, 0])); img.put_pixel(1, 0, Rgb([0, 255, 0])); img.put_pixel(0, 1, Rgb([0, 0, 255])); img.put_pixel(1, 1, Rgb([255, 255, 0]));
let mut terminal = Terminal::new(TestBackend::new(80, 20)).unwrap();
terminal
.draw(|frame| {
let image = image::ImageReader::open("./assets/NixOS.png")
.unwrap()
.decode()
.unwrap();
let area = Rect::new(0, 0, 40, 20);
let hbs = Halfblocks::new(image, area).unwrap();
frame.render_widget(Image::new(&Protocol::Halfblocks(hbs)), frame.area());
})
.unwrap();
#[cfg(any(feature = "chafa-static", feature = "chafa-dyn",))]
{
assert_snapshot!("chafa", terminal.backend());
}
#[cfg(not(any(feature = "chafa-static", feature = "chafa-dyn",)))]
assert_snapshot!("halfblocks", terminal.backend());
#[cfg(any(feature = "chafa-static", feature = "chafa-dyn",))]
assert_snapshot!("chafa", terminal.backend());
}
}