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
//! 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;
pub use bloc::Bloc;
pub use iterm2::Iterm2;
pub use kitty::Kitty;
pub use sixel::Sixel;
use std::fmt::{Result, Write};

use crate::{pixels::convert::PFrom, Image, WritePng};

mod b64;
mod iterm2;

impl<'a, const N: usize> std::fmt::Display for Image<&'a [u8], N>
where
    [u8; 3]: PFrom<N>,
    [u8; 4]: PFrom<N>,
    Image<&'a [u8], N>: kitty::Data + WritePng,
    Image<&'a [u8], N>: bloc::Scaled<N>,
{
    /// 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<'a, const N: usize>(i: Image<&'a [u8], N>)
where
    [u8; 3]: PFrom<N>,
    [u8; 4]: PFrom<N>,
    Image<&'a [u8], N>: bloc::Scaled<N>,
    Image<&'a [u8], N>: kitty::Data + WritePng,
{
    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<'a, const N: usize>(pub Image<&'a [u8], N>);

impl<'a, const N: usize> std::ops::Deref for Display<'a, N> {
    type Target = Image<&'a [u8], N>;

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

impl<'a, const N: usize> std::fmt::Debug for Display<'a, N>
where
    [u8; 3]: PFrom<N>,
    [u8; 4]: PFrom<N>,
    Image<&'a [u8], N>: bloc::Scaled<N>,
    Image<&'a [u8], N>: kitty::Data + WritePng,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
        self.write(f)
    }
}

impl<'a, const N: usize> std::fmt::Display for Display<'a, N>
where
    Image<&'a [u8], N>: bloc::Scaled<N>,
    [u8; 4]: PFrom<N>,
    [u8; 3]: PFrom<N>,
    Image<&'a [u8], N>: kitty::Data + WritePng,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.write(f)
    }
}

impl<'a, const N: usize> Display<'a, N>
where
    [u8; 4]: PFrom<N>,
    [u8; 3]: PFrom<N>,
    Image<&'a [u8], N>: bloc::Scaled<N>,
    Image<&'a [u8], N>: kitty::Data + WritePng,
{
    /// 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).write(f),
                x if x.contains("kitty") => return Kitty(*self).write(f),
                _ => (),
            }
        }
        if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
            match &*term_program {
                "MacTerm" => return Sixel(*self).write(f),
                "iTerm" | "WezTerm" => return Iterm2(*self).write(f),
                _ => (),
            }
        }
        if let Ok("iTerm") = std::env::var("LC_TERMINAL").as_deref() {
            return Iterm2(*self).write(f);
        }
        #[cfg(unix)]
        return self.guess_harder(f).unwrap_or_else(|| Bloc(*self).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> {
        extern crate libc;
        use std::{io::Read, 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 = {
            // contains a kitty gfx and sixel query, the `\x1b[c` is for sixels
            println!(r"_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\");
            let mut stdin = std::io::stdin();
            let mut buf = String::new();

            let mut b = [0; 16];
            'l: loop {
                let n = stdin.read(&mut b).ok()?;
                if n == 0 {
                    continue;
                }
                for b in b {
                    buf.push(b as char);
                    if b == b'c' {
                        break 'l;
                    }
                }
            }
            buf
        };

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

        if buf.contains("_Gi=31;OK") {
            Some(Kitty(*self).write(to))
        } else if buf.contains(";4;")
            || buf.contains("?4;")
            || buf.contains(";4c")
            || buf.contains("?4c")
        {
            Some(Sixel(*self).write(to))
        } else {
            None
        }
    }
}