diff --git a/Cargo.toml b/Cargo.toml index 87f4489..38baac3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,10 @@ umath = "0.0.7" fr = { version = "0.1.1", package = "fer", optional = true } stackblur-iter = { version = "0.2.0", features = ["simd"], optional = true } clipline = "0.1.1" +minifb = { version = "0.25.0", default-features = false, features = [ + "x11", + "wayland", +], optional = true } [dev-dependencies] iai = { git = "https://github.com/bend-n/iai.git" } @@ -53,6 +57,7 @@ scale = ["fr"] save = ["png"] text = ["fontdue"] blur = ["stackblur-iter"] +real-show = ["minifb"] default = ["save", "scale"] [profile.release] diff --git a/src/drawing/tri.rs b/src/drawing/tri.rs index 2a6a90b..ab89acd 100644 --- a/src/drawing/tri.rs +++ b/src/drawing/tri.rs @@ -47,6 +47,7 @@ impl + AsRef<[u8]>, const CHANNELS: usize> Image { for y in ymin..ymax { for x in xmin..xmax { // algorithm from https://web.archive.org/web/20050408192410/http://sw-shader.sourceforge.net/rasterizer.html, but im too dumb to implement the faster ones + // SAFETY: nNaN if unsafe { (x1 - x2) * (F::new(y as f32) - y1) + (-(y1 - y2) * (F::new(x as f32) - x1)) > 0. diff --git a/src/lib.rs b/src/lib.rs index 52bc7ed..1882368 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,17 @@ //! - [`Image::repeated`] //! - [`Image::overlay`](Overlay), [`Image::overlay_at`](OverlayAt), [`Image::overlay_blended`](BlendingOverlay) //! - [`Image::blur`] +//! +//! ## feature flags +//! +//! - `scale`: enables the [`scale`] module. +//! - `save`: enables [`Image::save`], via the [`png`](https://crates.io/crates/png) crate. +//! - `text`: enables [`Image::text`], via the [`fontdue`](https://crates.io/crates/fontdue) crate. +//! - `blur`: enables [`Image::blur`], via the [`stackblur`](https://crates.io/crates/stackblur-iter) crate. +//! - `real-show`: [`Image::show`], if the `save` feature is enabled, will, by default, simply open the appropriate image viewing program. +//! if, for some reason, this is inadequate/you dont have a good image viewer, enable the `real-show` feature to make [`Image::show`] open up a window of its own. +//! without the `real-show` feature, [`Image::show`] will save itself to your temp directory, which you may not want. +//! - `default`: \[`save`, `scale`\]. #![feature( slice_swap_unchecked, generic_const_exprs, @@ -75,6 +86,8 @@ mod overlay; pub mod pixels; #[cfg(feature = "scale")] pub mod scale; +#[cfg(any(feature = "save", feature = "real-show"))] +mod show; pub use cloner::ImageCloner; pub use overlay::{BlendingOverlay, ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt}; pub use r#dyn::DynImage; diff --git a/src/show.rs b/src/show.rs new file mode 100644 index 0000000..a7a1220 --- /dev/null +++ b/src/show.rs @@ -0,0 +1,125 @@ +use crate::Image; + +#[cfg(feature = "real-show")] +mod real { + use crate::Image; + use minifb::{Key, Window}; + + pub fn show(i: Image<&[u32], 1>) { + let mut win = Window::new( + "show", + i.width() as usize, + i.height() as usize, + Default::default(), + ) + .unwrap(); + win.limit_update_rate(Some(std::time::Duration::from_millis(100))); + while win.is_open() && !win.is_key_down(Key::Q) && !win.is_key_down(Key::Escape) { + win.update_with_buffer(&i.buffer, i.width() as usize, i.height() as usize) + .expect("window update fail"); + } + } +} + +#[cfg(not(feature = "real-show"))] +mod fake { + use std::process::{Command, Stdio}; + + macro_rules! c { + ($p:literal) => { + std::process::Command::new($p) + }; + ($p:literal $($args:expr)+) => { + std::process::Command::new($p).args([$($args,)+]) + } + } + pub(crate) use c; + + pub fn has(c: &'static str) -> bool { + complete(c!("which").arg(c)) + } + + pub fn complete(c: &mut Command) -> bool { + c.stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .expect("ok") + .success() + } + + macro_rules! show { + ($me:expr) => { + let file = std::env::temp_dir().join("viewing.png"); + $me.save(&file); + #[cfg(target_family = "windows")] + assert!(fake::complete(fake::c!("start" "%Temp%/viewing.png")), "command should complete successfully."); + #[cfg(target_family = "unix")] + assert!( + if fake::has("feh") { fake::complete(fake::c!("feh" file)) + } else if fake::has("xdg-open") { fake::complete(fake::c!("xdg-open" file)) + } else if fake::has("gio") { fake::complete(fake::c!("gio" file)) + } else if fake::has("gnome-open") { fake::complete(fake::c!("gnome-open" file)) + } else if fake::has("kde-open") { fake::complete(fake::c!("kde-open" file)) + } else if fake::has("open") { fake::complete(fake::c!("open" file)) + } else { panic!("no image viewer found, please enable the `real-show` feature.") }, + "command should complete successfully."); + }; + + } + pub(crate) use show; +} + +fn r(i: &Image, 1>) -> Image<&[u32], 1> { + // SAFETY: ctor + unsafe { Image::new(i.width, i.height, &*i.buffer) } +} + +macro_rules! show { + ($buf:ty) => { + show!($buf, 1); + show!($buf, 2); + show!($buf, 3); + show!($buf, 4); + }; + ($buf:ty, $n:literal) => { + impl Image<$buf, $n> { + /// Open a window showing this image. + /// Blocks until the window finishes. + /// + /// This is like [`dbg!`] for images. + /// + /// # Panics + /// + /// if the window is un creatable + pub fn show(self) -> Self { + #[cfg(feature = "real-show")] + real::show(r(&self.as_ref().into())); + #[cfg(not(feature = "real-show"))] + fake::show!(self); + self + } + } + }; +} + +show!(Vec); +show!(Box<[u8]>); +show!(&[u8]); + +impl Image, 1> { + /// Open a window showing this image. + /// Blocks until the window finishes. + /// + /// This is like [`dbg!`] for images. + /// + /// # Panics + /// + /// if the window is un creatable + pub fn show(self) -> Self { + #[cfg(feature = "real-show")] + real::show(r(&self)); + #[cfg(not(feature = "real-show"))] + fake::show!(Image::, 4>::from(r(&self))); + self + } +}