fast image operations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
//! terminal outputs
//! produces output for any terminal supporting one of the
//! ```text
//! Kitty Graphics Protocol
//! Iterm2 Inline Image Protocol
//! Sixel Bitmap Graphics Format
//! ```
//! with a fallback for dumb terminals.
//!
//! the (second?) best way to debug your images.
mod bloc;
mod kitty;
mod sixel;
mod size;
use crate::Image;
pub use bloc::Bloc;
pub use iterm2::Iterm2;
pub use kitty::Kitty;
pub use sixel::Sixel;
use std::fmt::{Result, Write};

mod seal {
    pub trait Sealed {}
}
use seal::Sealed;
#[doc(hidden)]
pub trait Basic: Sealed {}
impl Sealed for [(); 1] {}
impl Basic for [(); 1] {}
impl Sealed for [(); 2] {}
impl Basic for [(); 2] {}
impl Sealed for [(); 3] {}
impl Basic for [(); 3] {}
impl Sealed for [(); 4] {}
impl Basic for [(); 4] {}

mod b64;
mod iterm2;

impl<'a, const N: usize> std::fmt::Display for Image<&'a [u8], N>
where
    [(); N]: Basic,
{
    /// Display an image in the terminal.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
        Display(*self).write(f)
    }
}

/// Print an image in the terminal.
///
/// This is a wrapper for `print!("{}", term::Display(image))`
pub fn print<T: AsRef<[u8]>, const N: usize>(i: Image<T, N>)
where
    [(); N]: Basic,
    Display<Image<T, N>>: std::fmt::Display,
{
    print!("{}", Display(i))
}

#[derive(Copy, Clone)]
/// Display an image in the terminal.
/// This type implements [`Display`](std::fmt::Display) and [`Debug`](std::fmt::Debug).
pub struct Display<T>(pub T);

impl<T> std::ops::Deref for Display<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: AsRef<[u8]>, const N: usize> std::fmt::Debug for Display<Image<T, N>>
where
    [(); N]: Basic,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
        Display(self.as_ref()).write(f)
    }
}

impl<const N: usize> Display<Image<&[u8], N>>
where
    [(); N]: Basic,
{
    /// Write $TERM protocol encoded image data.
    pub fn write(self, f: &mut impl Write) -> Result {
        if let Ok(term) = std::env::var("TERM") {
            match &*term {
                "mlterm" | "yaft-256color" => return Sixel(self.0).write(f),
                x if x.contains("kitty") => return Kitty(self.0).write(f),
                _ => (),
            }
        }
        if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
            match &*term_program {
                "MacTerm" => return Sixel(self.0).write(f),
                "iTerm" | "WezTerm" => return Iterm2(self.0).write(f),
                _ => (),
            }
        }
        if let Ok("iTerm") = std::env::var("LC_TERMINAL").as_deref() {
            return Iterm2(self.0).write(f);
        }
        #[cfg(unix)]
        return self
            .guess_harder(f)
            .unwrap_or_else(|| Bloc(self.0).write(f));
        #[cfg(not(unix))]
        return Bloc(*self).write(f);
    }

    #[cfg(unix)]
    // https://github.com/benjajaja/ratatui-image/blob/master/src/picker.rs#L226
    fn guess_harder(&self, to: &mut impl Write) -> Option<Result> {
        // contains a kitty gfx and sixel query, the `\x1b[c` is for sixels
        let buf = query(r"_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\")?;
        if buf.contains("_Gi=31;OK") {
            Some(Kitty(self.as_ref()).write(to))
        } else if buf.contains(";4;")
            || buf.contains("?4;")
            || buf.contains(";4c")
            || buf.contains("?4c")
        {
            Some(Sixel(self.as_ref()).write(to))
        } else {
            None
        }
    }
}

#[cfg(unix)]
// https://github.com/benjajaja/ratatui-image/blob/master/src/picker.rs#L226
fn query(device_query_code: &'static str) -> Option<String> {
    extern crate libc;
    use std::mem::MaybeUninit;
    fn r(result: i32) -> Option<()> {
        (result != -1).then_some(())
    }

    let mut termios = MaybeUninit::<libc::termios>::uninit();
    // SAFETY: get termios of stdin
    r(unsafe { libc::tcgetattr(0, termios.as_mut_ptr()) })?;
    // SAFETY: gotten
    let termios = unsafe { termios.assume_init() };

    // SAFETY: turn off echo and canonical (requires enter before stdin reads) modes
    unsafe {
        libc::tcsetattr(
            0,
            libc::TCSADRAIN,
            &libc::termios {
                c_lflag: termios.c_lflag & !libc::ICANON & !libc::ECHO,
                ..termios
            },
        )
    };

    let buf = try {
        // SAFETY: linux time out'd reading
        unsafe {
            println!("{device_query_code}");
            let mut buf = Vec::new();
            let mut tmp = [0; 1 << 5];
            loop {
                let mut x = std::mem::zeroed::<libc::fd_set>();
                libc::FD_SET(0, &mut x);
                match libc::select(
                    1,
                    &mut x,
                    0 as _,
                    0 as _,
                    &mut libc::timeval {
                        tv_sec: 0,
                        tv_usec: 5e5 as _,
                    },
                ) {
                    0 => break,
                    -1 => return None,
                    _ => {}
                }
                match libc::read(libc::STDIN_FILENO, tmp.as_mut_ptr().cast(), tmp.len()) {
                    0 => continue,
                    -1 => return None,
                    n => buf.extend_from_slice(&tmp[..n as _]),
                }
            }
            String::from_utf8(buf).ok()?
        }
    };

    // SAFETY: reset attrs to what they were before we became nosy
    unsafe { libc::tcsetattr(0, libc::TCSADRAIN, &termios) };
    buf
}