diff --git a/Cargo.toml b/Cargo.toml index 51b4638..f973a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.39" +version = "0.4.41" authors = ["bend-n "] license = "MIT" edition = "2021" @@ -67,7 +67,7 @@ text = ["fontdue"] blur = ["slur"] term = ["qwant", "save", "scale", "windows"] real-show = ["minifb", "text"] -default = ["save", "scale", "term"] +default = ["save", "scale"] wgpu-convert = ["dep:wgpu"] [profile.release] diff --git a/src/dyn/mod.rs b/src/dyn/mod.rs index 52feaea..ccf19f9 100644 --- a/src/dyn/mod.rs +++ b/src/dyn/mod.rs @@ -40,9 +40,24 @@ macro_rules! e { } use e; -impl<'a> std::fmt::Display for DynImage<&'a [u8]> { +#[cfg(feature = "term")] +impl> std::fmt::Display for crate::term::Display> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - e!(self, |x| crate::term::Display(*x).write(f)) + e!(&self.0, |x| crate::term::Display(x.as_ref()).write(f)) + } +} + +#[cfg(feature = "term")] +impl> std::fmt::Debug for crate::term::Display> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + e!(&self.0, |x| crate::term::Display(x.as_ref()).write(f)) + } +} + +#[cfg(feature = "term")] +impl> std::fmt::Display for DynImage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + e!(&self, |x| crate::term::Display(x.as_ref()).write(f)) } } diff --git a/src/lib.rs b/src/lib.rs index b642d36..336d3f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,6 +57,7 @@ generic_const_exprs, iter_array_chunks, split_at_checked, + core_intrinsics, slice_as_chunks, unchecked_math, slice_flatten, @@ -82,7 +83,8 @@ clippy::zero_prefixed_literal, mixed_script_confusables, incomplete_features, - confusable_idents + confusable_idents, + internal_features )] use std::{hint::assert_unchecked, num::NonZeroU32, ops::Range}; @@ -208,6 +210,7 @@ impl Image<&[u8], 3> { /// A image with a variable number of channels, and a nonzero size. #[derive(Debug, PartialEq, Eq)] +#[repr(C)] pub struct Image { /// column order 2d slice/vec buffer: T, diff --git a/src/term.rs b/src/term.rs index 0e54219..5f2df4c 100644 --- a/src/term.rs +++ b/src/term.rs @@ -12,23 +12,34 @@ 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}; -use crate::{pixels::convert::PFrom, Image, WritePng}; +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 - [u8; 3]: PFrom, - [u8; 4]: PFrom, - Image<&'a [u8], N>: kitty::Data + WritePng, - Image<&'a [u8], N>: bloc::Scaled, + [(); N]: Basic, { /// Display an image in the terminal. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result { @@ -39,12 +50,10 @@ where /// 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>) +pub fn print, const N: usize>(i: Image) where - [u8; 3]: PFrom, - [u8; 4]: PFrom, - Image<&'a [u8], N>: bloc::Scaled, - Image<&'a [u8], N>: kitty::Data + WritePng, + [(); N]: Basic, + Display>: std::fmt::Display, { print!("{}", Display(i)) } @@ -52,75 +61,59 @@ where #[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>); +pub struct Display(pub T); -impl<'a, const N: usize> std::ops::Deref for Display<'a, N> { - type Target = Image<&'a [u8], N>; +impl std::ops::Deref for Display { + type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a, const N: usize> std::fmt::Debug for Display<'a, N> +impl, const N: usize> std::fmt::Debug for Display> where - [u8; 3]: PFrom, - [u8; 4]: PFrom, - Image<&'a [u8], N>: bloc::Scaled, - Image<&'a [u8], N>: kitty::Data + WritePng, + [(); N]: Basic, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result { - self.write(f) + Display(self.as_ref()).write(f) } } -impl<'a, const N: usize> std::fmt::Display for Display<'a, N> +impl Display> where - Image<&'a [u8], N>: bloc::Scaled, - [u8; 4]: PFrom, - [u8; 3]: PFrom, - 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, - [u8; 3]: PFrom, - Image<&'a [u8], N>: bloc::Scaled, - Image<&'a [u8], N>: kitty::Data + WritePng, + [(); 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).write(f), - x if x.contains("kitty") => return Kitty(*self).write(f), + "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).write(f), - "iTerm" | "WezTerm" => return Iterm2(*self).write(f), + "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).write(f); + return Iterm2(self.0).write(f); } #[cfg(unix)] - return self.guess_harder(f).unwrap_or_else(|| Bloc(*self).write(f)); + 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 { + fn guess_harder(&self, to: &mut impl Write) -> Option { extern crate libc; use std::{io::Read, mem::MaybeUninit}; @@ -171,13 +164,13 @@ where unsafe { libc::tcsetattr(0, libc::TCSADRAIN, &termios) }; if buf.contains("_Gi=31;OK") { - Some(Kitty(*self).write(to)) + Some(Kitty(self.as_ref()).write(to)) } else if buf.contains(";4;") || buf.contains("?4;") || buf.contains(";4c") || buf.contains("?4c") { - Some(Sixel(*self).write(to)) + Some(Sixel(self.as_ref()).write(to)) } else { None } diff --git a/src/term/bloc.rs b/src/term/bloc.rs index 8601388..5f6347b 100644 --- a/src/term/bloc.rs +++ b/src/term/bloc.rs @@ -1,4 +1,6 @@ +use super::Basic; use crate::{pixels::convert::PFrom, scale, term::size::fit, Image}; +use core::intrinsics::transmute_unchecked as transmute; use std::fmt::{Debug, Display, Formatter, Result, Write}; /// Colored `▀`s. The simple, stupid solution. @@ -14,8 +16,7 @@ impl, const N: usize> std::ops::Deref for Bloc { impl, const N: usize> Display for Bloc where - Image: Scaled, - [u8; 3]: PFrom, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) @@ -24,44 +25,16 @@ where impl, const N: usize> Debug for Bloc where - Image: Scaled, - [u8; 3]: PFrom, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) } } -#[doc(hidden)] -pub trait Scaled { - fn scaled(&self, to: (u32, u32)) -> Image, N>; -} - -macro_rules! n { - ($n:literal) => { - impl> Scaled<$n> for Image { - fn scaled(&self, (w, h): (u32, u32)) -> Image, $n> { - self.scale::(w, h) - } - } - }; - (o $n:literal) => { - impl> Scaled<$n> for Image { - fn scaled(&self, (w, h): (u32, u32)) -> Image, $n> { - self.as_ref().to_owned().scale::(w, h) - } - } - }; -} -n!(1); -n!(o 2); -n!(3); -n!(o 4); - impl, const N: usize> Bloc where - [u8; 3]: PFrom, - Image: Scaled, + [(); N]: Basic, { /// Write out halfblocks. pub fn write(&self, to: &mut impl Write) -> Result { @@ -74,7 +47,31 @@ where } let buf; let i = if !cfg!(test) { - buf = self.scaled(fit((self.width(), self.height()))); + let (w, h) = fit((self.width(), self.height())); + macro_rules! n { + ($n:literal) => { + transmute::, $n>, Image, N>>( + transmute::, Image<&[u8], $n>>(self.as_ref()) + .scale::(w, h), + ) + }; + (o $n:literal) => { + transmute::, 1>, Image, N>>( + transmute::, N>, Image<&[u8], 1>>(self.as_ref().to_owned()) + .scale::(w, h), + ) + }; + } + // SAFETY: #[allow(clippy::undocumented_unsafe_blocks)] + buf = unsafe { + match N { + 1 => n![1], + 2 => n![o 2], + 3 => n![3], + 4 => n![o 4], + _ => unreachable!(), + } + }; buf.as_ref() } else { self.as_ref() @@ -83,7 +80,18 @@ where for [a, b] in i .flatten() .chunks_exact(i.width() as _) - .map(|x| x.iter().copied().map(<[u8; 3] as PFrom>::pfrom)) + .map(|x| { + #[allow(clippy::undocumented_unsafe_blocks)] + x.iter().copied().map(|x| unsafe { + match N { + 1 => <[u8; 3] as PFrom<1>>::pfrom(transmute(x)), + 2 => <[u8; 3] as PFrom<2>>::pfrom(transmute(x)), + 3 => <[u8; 3] as PFrom<3>>::pfrom(transmute(x)), + 4 => <[u8; 3] as PFrom<4>>::pfrom(transmute(x)), + _ => unreachable!(), + } + }) + }) .array_chunks::<2>() { for (a, b) in a.zip(b) { diff --git a/src/term/iterm2.rs b/src/term/iterm2.rs index b6a9663..a6e5bac 100644 --- a/src/term/iterm2.rs +++ b/src/term/iterm2.rs @@ -1,5 +1,6 @@ -use super::b64; +use super::{b64, Basic}; use crate::{Image, WritePng}; +use core::intrinsics::transmute_unchecked as transmute; use std::fmt::{Debug, Display, Formatter, Result, Write}; /// Outputs [Iterm2 Inline image protocol](https://iterm2.com/documentation-images.html) encoded data. @@ -14,7 +15,7 @@ impl, const N: usize> std::ops::Deref for Iterm2 { impl, const N: usize> Display for Iterm2 where - Image: WritePng, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) @@ -23,7 +24,7 @@ where impl, const N: usize> Debug for Iterm2 where - Image: WritePng, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) @@ -32,12 +33,28 @@ where impl, const N: usize> Iterm2 where - Image: WritePng, + [(); N]: Basic, { /// Write out kitty gfx data. pub fn write(&self, to: &mut impl Write) -> Result { let mut d = Vec::with_capacity(1024); - WritePng::write(&**self, &mut d).unwrap(); + macro_rules! n { + ($n:literal) => { + WritePng::write( + // SAFETY: ... i renounce traits + &unsafe { transmute::, Image<&[u8], $n>>(self.as_ref()) }, + &mut d, + ) + .unwrap() + }; + } + match N { + 1 => n![1], + 2 => n![2], + 3 => n![3], + 4 => n![4], + _ => unreachable!(), + } let mut e = Vec::with_capacity(b64::size(&d)); b64::encode(&d, &mut e).unwrap(); writeln!( diff --git a/src/term/kitty.rs b/src/term/kitty.rs index b1e4797..16ea2f8 100644 --- a/src/term/kitty.rs +++ b/src/term/kitty.rs @@ -1,5 +1,6 @@ -use super::b64; +use super::{b64, Basic}; use crate::Image; +use core::intrinsics::transmute_unchecked as transmute; use std::borrow::Cow; use std::fmt::{Debug, Display, Formatter, Result, Write}; @@ -16,7 +17,7 @@ impl, const N: usize> std::ops::Deref for Kitty { impl, const N: usize> Display for Kitty where - Image: Data, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) @@ -25,52 +26,43 @@ where impl, const N: usize> Debug for Kitty where - Image: Data, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) } } -mod seal { - pub trait Sealed {} -} -use seal::Sealed; - -pub trait Data: Sealed { - #[doc(hidden)] - fn get(&self) -> (Cow<[u8]>, &'static str); -} -macro_rules! imp { - ($n:literal, $f:expr) => { - impl> Sealed for Image {} - impl> Data for Image { - fn get(&self) -> (Cow<[u8]>, &'static str) { - const fn castor< - T: AsRef<[u8]>, - F: FnMut(&Image) -> (Cow<[u8]>, &'static str), - >( - f: F, - ) -> F { - f - } - castor($f)(self) - } - } - }; -} -imp! { 4, |x| (Cow::from(x.bytes()), "32") } -imp! { 3, |x| (Cow::from(x.bytes()), "24") } -imp! { 2, |x| (Cow::Owned(, 3>>::from(x.as_ref()).take_buffer().to_vec()), "24") } -imp! { 1, |x| (Cow::Owned(, 3>>::from(x.as_ref()).take_buffer().to_vec()), "24") } - impl, const N: usize> Kitty { /// Write out kitty gfx data. pub fn write(&self, to: &mut impl Write) -> Result where - Image: Data, + [(); N]: Basic, { - let (bytes, dtype) = self.get(); + macro_rules! cast { + ($n:literal) => { + ( + Cow::Owned( + , 3>>::from({ + // SAFETY: ... + unsafe { transmute::, Image<&[u8], $n>>(self.as_ref()) } + }) + .take_buffer() + .to_vec(), + ), + "24", + ) + }; + } + let (bytes, dtype) = { + match N { + 1 => cast!(1), + 2 => cast!(2), + 3 => (Cow::from(self.bytes()), "24"), + 4 => (Cow::from(self.bytes()), "32"), + _ => unreachable!(), + } + }; let (w, h) = (self.width(), self.height()); let mut enc = Vec::with_capacity(b64::size(&bytes)); diff --git a/src/term/sixel.rs b/src/term/sixel.rs index 46f2b55..7091968 100644 --- a/src/term/sixel.rs +++ b/src/term/sixel.rs @@ -1,7 +1,10 @@ +use core::intrinsics::transmute_unchecked as transmute; use std::fmt::{Debug, Display, Formatter, Result, Write}; use crate::{pixels::convert::PFrom, Image}; +use super::Basic; + /// Outputs [sixel](https://en.wikipedia.org/wiki/Sixel) encoded data in its [`Display`] and [`Debug`] implementations, for easy visual debugging. pub struct Sixel, const N: usize>(pub Image); @@ -15,7 +18,7 @@ impl, const N: usize> std::ops::Deref for Sixel { impl, const N: usize> Display for Sixel where - [u8; 4]: PFrom, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) @@ -24,7 +27,7 @@ where impl, const N: usize> Debug for Sixel where - [u8; 4]: PFrom, + [(); N]: Basic, { fn fmt(&self, f: &mut Formatter<'_>) -> Result { self.write(f) @@ -35,7 +38,7 @@ impl, const N: usize> Sixel { /// Write out sixel data. pub fn write(&self, to: &mut impl Write) -> Result where - [u8; 4]: PFrom, + [(); N]: Basic, { to.write_str("Pq")?; write!(to, r#""1;1;{};{}"#, self.width(), self.height())?; @@ -47,7 +50,15 @@ impl, const N: usize> Sixel { buf = self .chunked() .copied() - .map(<[u8; 4] as PFrom>::pfrom) + // SAFETY: #[allow(clippy::undocumented_unsafe_blocks)] + .map(|x| unsafe { + match N { + 1 => <[u8; 4] as PFrom<1>>::pfrom(transmute(x)), + 2 => <[u8; 4] as PFrom<2>>::pfrom(transmute(x)), + 3 => <[u8; 4] as PFrom<3>>::pfrom(transmute(x)), + _ => unreachable!(), + } + }) .collect::>(); &*buf }; diff --git a/src/term/size/unix.rs b/src/term/size/unix.rs index 718373e..2fefc8f 100644 --- a/src/term/size/unix.rs +++ b/src/term/size/unix.rs @@ -10,6 +10,7 @@ struct Fd(i32, bool); impl Drop for Fd { fn drop(&mut self) { if self.1 { + // SAFETY: #[allow(clippy::undocumented_unsafe_blocks)] unsafe { close(self.0) }; } } @@ -43,7 +44,7 @@ pub fn size() -> Option<(u16, u16)> { *File::open("/dev/tty") .map(Fd::new) .unwrap_or(Fd::from(STDIN_FILENO)), - TIOCGWINSZ.into(), + TIOCGWINSZ, size.as_mut_ptr(), ) != -1 {