fast image operations
add `Image::show`
bendn 2023-11-05
parent 16fbab6 · commit dd74d73
-rw-r--r--Cargo.toml5
-rw-r--r--src/drawing/tri.rs1
-rw-r--r--src/lib.rs13
-rw-r--r--src/show.rs125
4 files changed, 144 insertions, 0 deletions
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<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
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<Box<[u32]>, 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<u8>);
+show!(Box<[u8]>);
+show!(&[u8]);
+
+impl Image<Box<[u32]>, 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::<Box<[u8]>, 4>::from(r(&self)));
+ self
+ }
+}