ratatui_image/protocol/
halfblocks.rs1#[cfg(all(feature = "chafa-static", feature = "chafa-dyn"))]
12compile_error!("features `chafa-static` and `chafa-dyn` are mutually exclusive");
13
14use image::DynamicImage;
15use ratatui::{
16 buffer::{Buffer, Cell},
17 layout::Rect,
18 style::Color,
19};
20
21use super::{ProtocolTrait, StatefulProtocolTrait};
22use crate::Result;
23
24#[cfg(any(feature = "chafa-dyn", feature = "chafa-static",))]
25mod chafa;
26
27#[cfg(not(any(feature = "chafa-dyn", feature = "chafa-static",)))]
28mod primitive;
29
30#[derive(Clone, Default)]
32pub struct Halfblocks {
33 data: Vec<HalfBlock>,
34 area: Rect,
35}
36
37#[derive(Clone, Debug)]
38pub(crate) struct HalfBlock {
39 pub upper: Color,
40 pub lower: Color,
41 pub char: char,
42}
43
44impl HalfBlock {
45 fn set_cell(&self, cell: &mut Cell) {
46 cell.set_fg(self.upper)
47 .set_bg(self.lower)
48 .set_char(self.char);
49 }
50}
51
52impl Halfblocks {
53 pub fn new(image: DynamicImage, area: Rect) -> Result<Self> {
61 let data = encode(&image, area);
62 Ok(Self { data, area })
63 }
64}
65
66#[cfg(any(feature = "chafa-static", feature = "chafa-dyn"))]
68fn encode(img: &DynamicImage, rect: Rect) -> Vec<HalfBlock> {
69 chafa::encode(img, rect).expect("chafa is always available with compile-time linking")
70}
71
72#[cfg(not(any(feature = "chafa-dyn", feature = "chafa-static")))]
74fn encode(img: &DynamicImage, rect: Rect) -> Vec<HalfBlock> {
75 primitive::encode(img, rect)
76}
77
78impl ProtocolTrait for Halfblocks {
79 fn render(&self, area: Rect, buf: &mut Buffer) {
80 for (i, hb) in self.data.iter().enumerate() {
81 let x = i as u16 % self.area.width;
82 let y = i as u16 / self.area.width;
83 if x >= area.width || y >= area.height {
84 continue;
85 }
86
87 if let Some(cell) = buf.cell_mut((area.x + x, area.y + y)) {
88 hb.set_cell(cell);
89 }
90 }
91 }
92 fn area(&self) -> Rect {
93 self.area
94 }
95}
96
97impl StatefulProtocolTrait for Halfblocks {
98 fn resize_encode(&mut self, img: DynamicImage, area: Rect) -> Result<()> {
99 let data = encode(&img, area);
100 *self = Halfblocks { data, area };
101 Ok(())
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use image::{Rgb, RgbImage};
108 use insta::assert_snapshot;
109 use ratatui::{Terminal, backend::TestBackend, layout::Rect};
110
111 use crate::{
112 Image,
113 protocol::{Protocol, halfblocks::Halfblocks},
114 };
115
116 #[test]
117 fn render_image() {
118 let mut img = RgbImage::new(2, 2);
119 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();
125 terminal
126 .draw(|frame| {
127 let image = image::ImageReader::open("./assets/NixOS.png")
128 .unwrap()
129 .decode()
130 .unwrap();
131 let area = Rect::new(0, 0, 40, 20);
132 let hbs = Halfblocks::new(image, area).unwrap();
133 frame.render_widget(Image::new(&Protocol::Halfblocks(hbs)), frame.area());
134 })
135 .unwrap();
136
137 #[cfg(any(feature = "chafa-static", feature = "chafa-dyn",))]
138 {
139 assert_snapshot!("chafa", terminal.backend());
140 }
141 #[cfg(not(any(feature = "chafa-static", feature = "chafa-dyn",)))]
142 assert_snapshot!("halfblocks", terminal.backend());
143 #[cfg(any(feature = "chafa-static", feature = "chafa-dyn",))]
144 assert_snapshot!("chafa", terminal.backend());
145 }
146}