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