onefetch_image/
iterm.rs

1use crate::get_dimensions;
2use anyhow::Result;
3use base64::{engine, Engine};
4use image::{imageops::FilterType, DynamicImage};
5use std::env;
6use std::io::Cursor;
7
8pub struct ITermBackend {}
9
10impl ITermBackend {
11    pub fn new() -> Self {
12        ITermBackend {}
13    }
14
15    pub fn supported() -> bool {
16        let term_program = env::var("TERM_PROGRAM").unwrap_or_else(|_| "".to_string());
17        term_program == "iTerm.app"
18    }
19}
20
21impl Default for ITermBackend {
22    fn default() -> Self {
23        Self::new()
24    }
25}
26
27impl super::ImageBackend for ITermBackend {
28    fn add_image(
29        &self,
30        lines: Vec<String>,
31        image: &DynamicImage,
32        _colors: usize,
33    ) -> Result<String> {
34        let tty_size = unsafe { get_dimensions() };
35        let width_ratio = f64::from(tty_size.ws_col) / f64::from(tty_size.ws_xpixel);
36        let height_ratio = f64::from(tty_size.ws_row) / f64::from(tty_size.ws_ypixel);
37
38        // resize image to fit the text height with the Lanczos3 algorithm
39        let image = image.resize(
40            u32::MAX,
41            (lines.len() as f64 / height_ratio) as u32,
42            FilterType::Lanczos3,
43        );
44        let _image_columns = width_ratio * f64::from(image.width());
45        let image_rows = height_ratio * f64::from(image.height());
46
47        let mut bytes: Vec<u8> = Vec::new();
48        image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?;
49        let encoded_image = engine::general_purpose::STANDARD.encode(bytes);
50        let mut image_data = Vec::<u8>::new();
51
52        image_data.extend(b"\x1B]1337;File=inline=1:");
53        image_data.extend(encoded_image.bytes());
54        image_data.extend(b"\x07");
55
56        image_data.extend(format!("\x1B[{}A", image_rows as u32 - 1).as_bytes()); // move cursor to start of image
57        let mut i = 0;
58        for line in &lines {
59            image_data.extend(format!("\x1B[s{line}\x1B[u\x1B[1B").as_bytes());
60            i += 1;
61        }
62        image_data
63            .extend(format!("\n\x1B[{}B", lines.len().max(image_rows as usize) - i).as_bytes()); // move cursor to end of image
64
65        Ok(String::from_utf8(image_data)?)
66    }
67}