fast image operations
add blending overlay at
bendn 9 months ago
parent d732cb6 · commit 871060a
-rw-r--r--Cargo.toml3
-rw-r--r--src/affine.rs2
-rw-r--r--src/lib.rs9
-rw-r--r--src/overlay.rs29
-rw-r--r--src/pixels/blending.rs15
-rw-r--r--src/pixels/mod.rs2
-rw-r--r--src/pixels/utility.rs19
-rw-r--r--src/pixels/wam.rs28
8 files changed, 65 insertions, 42 deletions
diff --git a/Cargo.toml b/Cargo.toml
index d90ab3d..ce5bd3b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ name = "fimg"
version = "0.4.44"
authors = ["bend-n <[email protected]>"]
license = "MIT"
-edition = "2021"
+edition = "2024"
description = "fast image operations"
repository = "https://github.com/bend-n/fimg"
exclude = ["tdata", "benches/", ".gitignore"]
@@ -28,6 +28,7 @@ atools = "0.1.4"
qwant = { version = "1.0.0", optional = true }
libc = "0.2.154"
hinted = { version = "0.0.2", features = ["nightly"] }
+lower = "0.2.0"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.53.0", features = [
diff --git a/src/affine.rs b/src/affine.rs
index d18d210..7a971e5 100644
--- a/src/affine.rs
+++ b/src/affine.rs
@@ -1,5 +1,5 @@
//! Manages the affine image transformations.
-use crate::{cloner::ImageCloner, Image};
+use crate::{Image, cloner::ImageCloner};
impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
/// Flip an image vertically.
diff --git a/src/lib.rs b/src/lib.rs
index b0bc694..1c9dfa6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,11 +53,12 @@
#![feature(
type_changing_struct_update,
maybe_uninit_write_slice,
+ custom_inner_attributes,
slice_swap_unchecked,
generic_const_exprs,
+ proc_macro_hygiene,
iter_array_chunks,
core_intrinsics,
- slice_as_chunks,
rustc_private,
portable_simd,
array_windows,
@@ -116,8 +117,10 @@ mod show;
#[cfg(feature = "term")]
pub mod term;
pub use cloner::ImageCloner;
-pub use overlay::{BlendingOverlay, ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt};
pub use r#dyn::DynImage;
+pub use overlay::{
+ BlendingOverlay, BlendingOverlayAt, ClonerOverlay, ClonerOverlayAt, Overlay, OverlayAt,
+};
trait CopyWithinUnchecked {
/// # Safety
@@ -621,7 +624,7 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
pub fn cols<U: Copy>(
&self,
) -> impl DoubleEndedIterator
- + ExactSizeIterator<
+ + ExactSizeIterator<
Item = impl ExactSizeIterator + DoubleEndedIterator<Item = [U; CHANNELS]> + '_,
>
where
diff --git a/src/overlay.rs b/src/overlay.rs
index 8733f20..b21985d 100644
--- a/src/overlay.rs
+++ b/src/overlay.rs
@@ -2,7 +2,7 @@
// TODO Y/YA
use crate::{cloner::ImageCloner, uninit};
-use super::{assert_unchecked, Image};
+use super::{Image, assert_unchecked};
use crate::pixels::Blend;
use std::{mem::transmute, simd::prelude::*};
@@ -52,6 +52,14 @@ pub trait BlendingOverlay<W> {
/// UB if a.width != b.width || a.height != b.height
unsafe fn overlay_blended(&mut self, with: &W) -> &mut Self;
}
+/// Blending overlay at.
+pub trait BlendingOverlayAt<W> {
+ /// See [BlendingOverlay::overlay_blended].
+ /// # Safety
+ ///
+ /// UB if x, y is out of bounds
+ unsafe fn overlay_blended_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self;
+}
/// [`Overlay`] but owned
pub trait ClonerOverlay<const W: usize, const C: usize>: Sealed {
@@ -136,6 +144,25 @@ where
}
}
+impl<const A: usize, const B: usize, T: AsMut<[u8]> + AsRef<[u8]>, U: AsRef<[u8]>>
+ BlendingOverlayAt<Image<U, B>> for Image<T, A>
+where
+ [u8; A]: Blend<B>,
+{
+ #[inline]
+ unsafe fn overlay_blended_at(&mut self, with: &Image<U, B>, x: u32, y: u32) -> &mut Self {
+ for j in 0..with.height() {
+ for i in 0..with.width() {
+ // SAFETY: i, j is in bounds.
+ let their_px = unsafe { &with.pixel(i, j) };
+ let our_px = unsafe { self.pixel_mut(i + x, j + y) };
+ our_px.blend(*their_px);
+ }
+ }
+ self
+ }
+}
+
impl ClonerOverlay<4, 4> for ImageCloner<'_, 4> {
#[inline]
#[must_use = "function does not modify the original image"]
diff --git a/src/pixels/blending.rs b/src/pixels/blending.rs
index ee22001..20d312e 100644
--- a/src/pixels/blending.rs
+++ b/src/pixels/blending.rs
@@ -1,7 +1,7 @@
//! module for pixel blending ops
-use super::{convert::PFrom, unfloat, Floatify, Unfloatify};
+#![allow(redundant_semicolons)]
+use super::{Floatify, Unfloatify, convert::PFrom, unfloat};
use atools::prelude::*;
-use umath::FF32;
/// Trait for blending pixels together.
pub trait Blend<const W: usize> {
@@ -10,6 +10,7 @@ pub trait Blend<const W: usize> {
}
impl Blend<4> for [u8; 4] {
+ #[lower::apply(algebraic)]
fn blend(&mut self, fg: [u8; 4]) {
if fg[3] == 0 {
return;
@@ -26,12 +27,11 @@ impl Blend<4> for [u8; 4] {
};
self[..3].copy_from_slice(
&fg.trunc()
- // SAFETY: no u8 can possibly become INF / NAN
.zip(bg.trunc())
- .map(|(f, b)| unsafe { (f * fg[3] + b * bg[3] * (FF32::new(1.0) - fg[3])) / a })
+ .map(|(f, b)| (f * fg[3] + b * bg[3] * (1.0 - fg[3])) / a)
.unfloat(),
);
- self[3] = unfloat(a);
+ self[3] = unfloat(a)
}
}
@@ -50,6 +50,7 @@ impl Blend<4> for [u8; 3] {
}
impl Blend<2> for [u8; 2] {
+ #[lower::apply(algebraic)]
fn blend(&mut self, with: [u8; 2]) {
let bg = self.float();
let fg = with.float();
@@ -60,10 +61,10 @@ impl Blend<2> for [u8; 2] {
}
*self = [
// SAFETY: no u8 can do transform bad
- (fg[0] * fg[1] + bg[0] * bg[1] * (unsafe { FF32::new(1.0) } - fg[1])) / a,
+ (fg[0] * fg[1] + bg[0] * bg[1] * (unsafe { (1.0) } - fg[1])) / a,
a,
]
- .unfloat();
+ .unfloat()
}
}
diff --git a/src/pixels/mod.rs b/src/pixels/mod.rs
index d757368..ceaa3d4 100644
--- a/src/pixels/mod.rs
+++ b/src/pixels/mod.rs
@@ -4,6 +4,6 @@ pub mod blending;
mod utility;
mod wam;
pub use blending::Blend;
-pub(crate) use utility::{float, unfloat, Floatify, Unfloatify};
+pub(crate) use utility::{Floatify, Unfloatify, float, unfloat};
pub(crate) use wam::Wam;
pub mod convert;
diff --git a/src/pixels/utility.rs b/src/pixels/utility.rs
index e9cca62..78795ac 100644
--- a/src/pixels/utility.rs
+++ b/src/pixels/utility.rs
@@ -1,4 +1,4 @@
-use umath::FF32;
+#[lower::apply(algebraic)]
pub trait Unfloatify<const N: usize> {
/// computes 255 * n, for all elements
fn unfloat(self) -> [u8; N];
@@ -6,12 +6,12 @@ pub trait Unfloatify<const N: usize> {
#[inline(always)]
/// computes 255 * n
-pub fn unfloat(n: FF32) -> u8 {
+pub fn unfloat(n: f32) -> u8 {
// SAFETY: n is 0..=1
- unsafe { *(FF32::new(255.0) * n) as u8 }
+ (255.0 * n) as u8
}
-impl<const N: usize> Unfloatify<N> for [FF32; N] {
+impl<const N: usize> Unfloatify<N> for [f32; N] {
fn unfloat(self) -> [u8; N] {
self.map(unfloat)
}
@@ -22,20 +22,21 @@ impl<const N:usize>Unfloatify<N>for[u8; N]{fn unfloat(self)->[u8;N]{self}}
pub trait Floatify<const N: usize> {
/// computes n / 255, for all elements
- fn float(self) -> [FF32; N];
+ fn float(self) -> [f32; N];
}
/// computes n / 255
-pub fn float(n: u8) -> FF32 {
+#[lower::apply(algebraic)]
+pub fn float(n: u8) -> f32 {
// SAFETY: 0..=255 / 0..=255 mayn't ever be NAN / INF
- unsafe { FF32::new(n as f32) / FF32::new(255.0) }
+ n as f32 / 255.0
}
impl<const N: usize> Floatify<N> for [u8; N] {
- fn float(self) -> [FF32; N] {
+ fn float(self) -> [f32; N] {
self.map(float)
}
}
#[rustfmt::skip]
-impl<const N:usize>Floatify<N>for[FF32;N]{fn float(self)->[FF32;N]{self}}
+impl<const N:usize>Floatify<N>for[f32;N]{fn float(self)->[f32;N]{self}}
diff --git a/src/pixels/wam.rs b/src/pixels/wam.rs
index ccf7d9f..e33d496 100644
--- a/src/pixels/wam.rs
+++ b/src/pixels/wam.rs
@@ -1,37 +1,27 @@
use super::{float, unfloat};
use atools::prelude::*;
-use umath::{generic_float::Constructors, FF32};
#[allow(dead_code)]
pub trait Wam {
/// this function weighs the sides and combines
- ///
- /// # Safety
- ///
- /// pls make l = <code>0..=[f32::MAX]/2</code>, r = <code>0..=[f32::MAX]/2</code>
- unsafe fn wam(self, b: Self, l: FF32, r: FF32) -> Self;
+ fn wam(self, b: Self, l: f32, r: f32) -> Self;
}
impl<const N: usize> Wam for [u8; N] {
- unsafe fn wam(self, b: Self, l: FF32, r: FF32) -> Self {
+ fn wam(self, b: Self, l: f32, r: f32) -> Self {
// SAFETY: read [`weigh`]
- self.zip(b).map(|(a, b)| unsafe { weigh(a, b, l, r) })
+ self.zip(b).map(|(a, b)| weigh(a, b, l, r))
}
}
#[inline(always)]
-/// # Safety
-///
-/// floats must be smart
-unsafe fn weigh(a: u8, b: u8, l: FF32, r: FF32) -> u8 {
- // SAFETY: float(x) returns 0..=1, 0..=1 * f32::MAX isn't Inf, but if you add 1.0 and then mul by max again, you get inf (big bad, hence unsafe fn)
- unsafe { unfloat((float(a) * l + float(b) * r).clamp(FF32::zero(), FF32::one())) }
+#[lower::apply(algebraic)]
+fn weigh(a: u8, b: u8, l: f32, r: f32) -> u8 {
+ unfloat((float(a) * l + float(b) * r).clamp(0., 1.))
}
#[test]
fn weig() {
- unsafe {
- assert_eq!(weigh(10, 20, FF32::new(0.5), FF32::new(0.5)), 15);
- assert_eq!(weigh(10, 20, FF32::new(0.9), FF32::new(0.1)), 11);
- assert_eq!(weigh(150, 150, FF32::new(1.8), FF32::new(0.8)), 255);
- }
+ assert_eq!(weigh(10, 20, 0.5, 0.5), 15);
+ assert_eq!(weigh(10, 20, 0.9, 0.1), 11);
+ assert_eq!(weigh(150, 150, 1.8, 0.8), 255);
}