fast image operations
-rw-r--r--Cargo.toml3
-rw-r--r--src/lib.rs1
-rw-r--r--src/term.rs116
-rw-r--r--src/term/b64.rs4
-rw-r--r--src/term/sixel.rs35
5 files changed, 104 insertions, 55 deletions
diff --git a/Cargo.toml b/Cargo.toml
index f973a48..773e435 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ minifb = { version = "0.25.0", default-features = false, features = [
wgpu = { version = "0.19.1", default-features = false, optional = true }
atools = "0.1.0"
qwant = { version = "1.0.0", optional = true }
+libc = "0.2.153"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.53.0", features = [
@@ -67,7 +68,7 @@ text = ["fontdue"]
blur = ["slur"]
term = ["qwant", "save", "scale", "windows"]
real-show = ["minifb", "text"]
-default = ["save", "scale"]
+default = ["save", "scale", "term"]
wgpu-convert = ["dep:wgpu"]
[profile.release]
diff --git a/src/lib.rs b/src/lib.rs
index 3953c08..eb2baef 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -68,6 +68,7 @@
const_option,
array_chunks,
let_chains,
+ try_blocks,
test
)]
#![warn(
diff --git a/src/term.rs b/src/term.rs
index 5f2df4c..c211340 100644
--- a/src/term.rs
+++ b/src/term.rs
@@ -114,55 +114,8 @@ where
#[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) };
-
+ // 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;")
@@ -176,3 +129,68 @@ where
}
}
}
+
+#[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
+}
diff --git a/src/term/b64.rs b/src/term/b64.rs
index 0d46d65..1038d45 100644
--- a/src/term/b64.rs
+++ b/src/term/b64.rs
@@ -39,7 +39,7 @@ where
LaneCount<N>: SupportedLaneCount,
{
fn cas<U>(self) -> U {
- assert!(std::mem::size_of::<U>() == std::mem::size_of::<Simd<T, N>>());
+ assert!(std::mem::size_of::<U>() == std::mem::size_of::<Self>());
unsafe { transmute_unchecked(self) }
}
}
@@ -49,7 +49,7 @@ where
LaneCount<N>: SupportedLaneCount,
{
fn cas<U>(self) -> U {
- assert!(std::mem::size_of::<U>() == std::mem::size_of::<Mask<T, N>>());
+ assert!(std::mem::size_of::<U>() == std::mem::size_of::<Self>());
unsafe { transmute_unchecked(self) }
}
}
diff --git a/src/term/sixel.rs b/src/term/sixel.rs
index 7091968..2304f51 100644
--- a/src/term/sixel.rs
+++ b/src/term/sixel.rs
@@ -40,6 +40,34 @@ impl<T: AsRef<[u8]>, const N: usize> Sixel<T, N> {
where
[(); N]: Basic,
{
+ #[cfg(unix)]
+ let q = {
+ extern crate libc;
+ // SAFETY: is stdout a tty
+ (unsafe { libc::isatty(0) } == 1)
+ };
+ #[cfg(not(unix))]
+ let q = true;
+ let colors = q
+ .then_some(super::query("[?1;1;0S").and_then(|x| {
+ // [?1;0;65536S
+ if let [b'?', b'1', b';', b'0', b';', n @ ..] = x.as_bytes() {
+ Some(
+ n.iter()
+ .copied()
+ .take_while(u8::is_ascii_digit)
+ .fold(0u16, |acc, x| {
+ acc.saturating_mul(10).saturating_add((x - b'0') as u16)
+ })
+ .max(64)
+ .min(0xfff),
+ )
+ } else {
+ None
+ }
+ }))
+ .flatten()
+ .unwrap_or(255);
to.write_str("Pq")?;
write!(to, r#""1;1;{};{}"#, self.width(), self.height())?;
let buf;
@@ -63,14 +91,15 @@ impl<T: AsRef<[u8]>, const N: usize> Sixel<T, N> {
&*buf
};
- let q = qwant::NeuQuant::new(15, 255, rgba);
+ let q = qwant::NeuQuant::new(15, colors as _, rgba);
+
// TODO: don't colllect
- let pixels: Vec<u8> = rgba.iter().map(|&pix| q.index_of(pix) as u8).collect();
+ let pixels: Vec<u16> = rgba.iter().map(|&pix| q.index_of(pix) as _).collect();
for ([r, g, b], i) in q
.color_map_rgb()
.map(|x| x.map(|x| (x as f32 * (100. / 255.)) as u32))
- .zip(0u8..)
+ .zip(0u64..)
{
write!(to, "#{i};2;{r};{g};{b}")?;
}