chafa/
lib.rs

1use std::ffi::CString;
2
3pub struct ChafaCanvas {
4    symbol_map: *mut chafa_sys::ChafaSymbolMap,
5    config: *mut chafa_sys::ChafaCanvasConfig,
6    canvas: *mut chafa_sys::ChafaCanvas,
7}
8
9impl ChafaCanvas {
10    pub fn new(width: u32, height: u32) -> Self {
11        let symbol_map = unsafe {
12            let symbol_map = chafa_sys::chafa_symbol_map_new();
13            chafa_sys::chafa_symbol_map_add_by_tags(
14                symbol_map,
15                chafa_sys::ChafaSymbolTags_CHAFA_SYMBOL_TAG_ALL,
16            );
17            symbol_map
18        };
19
20        let config = unsafe {
21            let config = chafa_sys::chafa_canvas_config_new();
22            chafa_sys::chafa_canvas_config_set_geometry(config, width as i32, height as i32);
23            chafa_sys::chafa_canvas_config_set_symbol_map(config, symbol_map);
24            config
25        };
26
27        let canvas = unsafe { chafa_sys::chafa_canvas_new(config) };
28
29        Self {
30            symbol_map,
31            config,
32            canvas,
33        }
34    }
35
36    pub fn from_term(src_width: u32, src_height: u32) -> Self {
37        let symbol_map = unsafe {
38            let symbol_map = chafa_sys::chafa_symbol_map_new();
39            chafa_sys::chafa_symbol_map_add_by_tags(
40                symbol_map,
41                chafa_sys::ChafaSymbolTags_CHAFA_SYMBOL_TAG_ALL,
42            );
43            symbol_map
44        };
45
46        let (terminal_size::Width(cols), terminal_size::Height(rows)) =
47            terminal_size::terminal_size()
48                .unwrap_or((terminal_size::Width(100), terminal_size::Height(50)));
49
50        let width_ptr: *mut i32 = &mut (cols as i32);
51        let height_ptr: *mut i32 = &mut (rows as i32);
52
53        unsafe {
54            chafa_sys::chafa_calc_canvas_geometry(
55                src_width as i32,
56                src_height as i32,
57                width_ptr,
58                height_ptr,
59                0.5,
60                false.into(),
61                false.into(),
62            );
63        }
64
65        let config = unsafe {
66            let config = chafa_sys::chafa_canvas_config_new();
67            chafa_sys::chafa_canvas_config_set_geometry(config, *width_ptr, *height_ptr);
68            chafa_sys::chafa_canvas_config_set_symbol_map(config, symbol_map);
69            config
70        };
71
72        let canvas = unsafe { chafa_sys::chafa_canvas_new(config) };
73
74        Self {
75            symbol_map,
76            config,
77            canvas,
78        }
79    }
80
81    pub fn draw(&self, pixels: &[u8], pix_width: u32, pix_height: u32) -> String {
82        let channels = 4; // four channels: red, green, blue, alpha
83
84        unsafe {
85            chafa_sys::chafa_canvas_draw_all_pixels(
86                self.canvas,
87                chafa_sys::ChafaPixelType_CHAFA_PIXEL_RGBA8_UNASSOCIATED,
88                pixels.as_ptr(),
89                pix_width as i32,
90                pix_height as i32,
91                (pix_width * channels) as i32,
92            );
93        }
94
95        let gstring = unsafe { chafa_sys::chafa_canvas_build_ansi(self.canvas) };
96        let ansistr = unsafe { (*gstring).str_ };
97        let ansistr = unsafe { CString::from_raw(ansistr) };
98        let ansistr = ansistr.to_string_lossy();
99
100        ansistr.to_string()
101    }
102}
103
104impl Drop for ChafaCanvas {
105    fn drop(&mut self) {
106        unsafe {
107            chafa_sys::chafa_canvas_unref(self.canvas);
108            chafa_sys::chafa_canvas_config_unref(self.config);
109            chafa_sys::chafa_symbol_map_unref(self.symbol_map);
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::ChafaCanvas;
117
118    #[test]
119    fn usage_example() {
120        const PIX_WIDTH: usize = 3;
121        const PIX_HEIGHT: usize = 3;
122        const N_CHANNELS: usize = 4;
123
124        let pixels: [u8; PIX_WIDTH * PIX_HEIGHT * N_CHANNELS] = [
125            0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00,
126            0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
127            0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff,
128        ];
129
130        let canvas_width = 25;
131        let canvas_height = 10;
132        let canvas = ChafaCanvas::new(canvas_width, canvas_height);
133        let ansi = canvas.draw(&pixels, PIX_WIDTH as u32, PIX_HEIGHT as u32);
134        println!("{ansi}");
135    }
136
137    #[test]
138    fn frectonz_image() {
139        let img = image::open("/home/frectonz/workspace/chafa/chafa-sys/frectonz.png")
140            .expect("failed to open image");
141
142        let canvas = ChafaCanvas::from_term(img.width(), img.height());
143        let pixels = img.to_rgba8();
144        let ansi = canvas.draw(&pixels, img.width(), img.height());
145
146        println!("{ansi}");
147    }
148}