fast image operations
use clipline crate
bendn 2023-11-01
parent c02376b · commit 1e98d95
-rw-r--r--Cargo.toml1
-rw-r--r--benches/drawing.rs10
-rw-r--r--src/convert.rs4
-rw-r--r--src/drawing/line.rs175
-rw-r--r--src/drawing/poly.rs6
-rw-r--r--tdata/border_pentagon.imgbufbin10000 -> 10000 bytes
-rw-r--r--tdata/enneagon.imgbufbin90000 -> 90000 bytes
7 files changed, 29 insertions, 167 deletions
diff --git a/Cargo.toml b/Cargo.toml
index ed1b715..87f4489 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@ vecto = "0.1.0"
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"
[dev-dependencies]
iai = { git = "https://github.com/bend-n/iai.git" }
diff --git a/benches/drawing.rs b/benches/drawing.rs
index 8375bf3..61bf4d9 100644
--- a/benches/drawing.rs
+++ b/benches/drawing.rs
@@ -2,6 +2,12 @@ use fimg::*;
fn tri() {
let mut i: Image<_, 4> = fimg::make!(4 channels 1000 x 1000).boxed();
i.tri((0., 0.), (1000., 500.), (0., 999.), [255, 255, 255, 255]);
- std::hint::black_box(i);
+ iai::black_box(i);
}
-iai::main!(tri);
+fn line() {
+ let mut i: Image<_, 4> = fimg::make!(4 channels 500 x 750).boxed();
+ i.line((-50, 20), (550, 800), [255, 165, 0, 255]);
+ i.save("z.png");
+ iai::black_box(i);
+}
+iai::main!(tri, line);
diff --git a/src/convert.rs b/src/convert.rs
index 6f44116..8a6d7a4 100644
--- a/src/convert.rs
+++ b/src/convert.rs
@@ -64,12 +64,12 @@ boxconv!(4 => 2);
boxconv!(4 => 3);
#[inline]
-fn pack([r, g, b, a]: [u8; 4]) -> u32 {
+const fn pack([r, g, b, a]: [u8; 4]) -> u32 {
((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
}
#[inline]
-fn unpack(n: u32) -> [u8; 4] {
+const fn unpack(n: u32) -> [u8; 4] {
[
((n >> 16) & 0xFF) as u8,
((n >> 8) & 0xFF) as u8,
diff --git a/src/drawing/line.rs b/src/drawing/line.rs
index e1dde96..ef30d08 100644
--- a/src/drawing/line.rs
+++ b/src/drawing/line.rs
@@ -1,146 +1,21 @@
//! adds a `line` function to Image
-#![allow(clippy::missing_docs_in_private_items)]
use crate::Image;
-use std::iter::Iterator;
use vecto::Vec2;
-/// taken from [bresenham-rs](https://github.com/mbr/bresenham-rs)
-pub struct Bresenham {
- x: i32,
- y: i32,
- dx: i32,
- dy: i32,
- x1: i32,
- diff: i32,
- octant: Octant,
-}
-
-#[derive(Copy, Clone)]
-struct Octant(u8);
-
-impl Octant {
- #[inline]
- const fn from_points(start: (i32, i32), end: (i32, i32)) -> Self {
- let mut dx = end.0 - start.0;
- let mut dy = end.1 - start.1;
-
- let mut octant = 0;
-
- if dy < 0 {
- dx = -dx;
- dy = -dy;
- octant += 4;
- }
-
- if dx < 0 {
- let tmp = dx;
- dx = dy;
- dy = -tmp;
- octant += 2;
- }
-
- if dx < dy {
- octant += 1;
- }
-
- Self(octant)
- }
-
- #[inline]
- const fn to_octant0(self, p: (i32, i32)) -> (i32, i32) {
- match self.0 {
- 0 => (p.0, p.1),
- 1 => (p.1, p.0),
- 2 => (p.1, -p.0),
- 3 => (-p.0, p.1),
- 4 => (-p.0, -p.1),
- 5 => (-p.1, -p.0),
- 6 => (-p.1, p.0),
- 7 => (p.0, -p.1),
- _ => unreachable!(),
- }
- }
-
- #[inline]
- #[allow(clippy::wrong_self_convention)]
- fn from_octant0(self, p: (i32, i32)) -> (i32, i32) {
- match self.0 {
- 0 => (p.0, p.1),
- 1 => (p.1, p.0),
- 2 => (-p.1, p.0),
- 3 => (-p.0, p.1),
- 4 => (-p.0, -p.1),
- 5 => (-p.1, -p.0),
- 6 => (p.1, -p.0),
- 7 => (p.0, -p.1),
- _ => unreachable!(),
- }
- }
-}
-
-impl Bresenham {
- /// Creates a new iterator. Yields intermediate points between `start`
- /// and `end`. Includes `start` and `end`.
- #[inline]
- pub const fn new(start: (i32, i32), end: (i32, i32)) -> Self {
- let octant = Octant::from_points(start, end);
-
- let start = octant.to_octant0(start);
- let end = octant.to_octant0(end);
-
- let dx = end.0 - start.0;
- let dy = end.1 - start.1;
-
- Self {
- x: start.0,
- y: start.1,
- dy,
- dx,
- x1: end.0,
- diff: dy - dx,
- octant,
- }
- }
-}
-
-impl Iterator for Bresenham {
- type Item = (i32, i32);
-
- #[inline]
- fn next(&mut self) -> Option<Self::Item> {
- if self.x > self.x1 {
- return None;
- }
-
- let p = (self.x, self.y);
-
- if self.diff >= 0 {
- self.y += 1;
- self.diff -= self.dx;
- }
-
- self.diff += self.dy;
-
- // loop inc
- self.x += 1;
-
- Some(self.octant.from_octant0(p))
- }
-}
-
impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// Draw a line from point a to point b.
///
/// Points not in bounds will not be included.
- ///
- /// Uses [bresenshams](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm) line algorithm.
pub fn line(&mut self, a: (i32, i32), b: (i32, i32), color: [u8; CHANNELS]) {
- for (x, y) in Bresenham::new(a, b).map(|(x, y)| (x as u32, y as u32)) {
- if x < self.width() && y < self.height() {
- // SAFETY: bound are checked ^
- unsafe { self.set_pixel(x, y, color) };
- }
- }
+ clipline::clipline(
+ ((a.0 as isize, a.1 as isize), (b.0 as isize, b.1 as isize)),
+ (
+ (0, 0),
+ (self.width() as isize - 1, self.height() as isize - 1),
+ ),
+ // SAFETY: clipline clips
+ |x, y| unsafe { self.set_pixel(x as u32, y as u32, color) },
+ );
}
/// Draw a thick line from point a to point b.
@@ -185,32 +60,12 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
}
}
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn bresenham() {
- macro_rules! test_bresenham {
- ($a:expr, $b:expr => [$(($x:expr, $y:expr)),+]) => {{
- let mut bi = Bresenham::new($a, $b);
- $(assert_eq!(bi.next(), Some(($x, $y)));)+
- assert_eq!(bi.next(), None);
- }}
- }
- test_bresenham!((6, 4), (0, 1) => [(6, 4), (5, 4), (4, 3), (3, 3), (2, 2), (1, 2), (0, 1)]);
- test_bresenham!((2, 3), (2, 6) => [(2, 3), (2, 4), (2, 5), (2, 6)]);
- test_bresenham!((2, 3), (5, 3) => [(2, 3), (3, 3), (4, 3), (5, 3)]);
- test_bresenham!((0, 1), (6, 4) => [(0, 1), (1, 1), (2, 2), (3, 2), (4, 3), (5, 3), (6, 4)]);
- }
-
- #[test]
- fn line() {
- let mut a = Image::build(5, 5).alloc();
- a.as_mut().line((0, 1), (6, 4), [255]);
- assert_eq!(
+#[test]
+fn line() {
+ let mut a = Image::build(5, 5).alloc();
+ a.as_mut().line((0, 1), (6, 4), [255]);
+ assert_eq!(
a.buffer,
- b"\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00"
+ b"\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00"
)
- }
}
diff --git a/src/drawing/poly.rs b/src/drawing/poly.rs
index 0b8f3ac..31632f3 100644
--- a/src/drawing/poly.rs
+++ b/src/drawing/poly.rs
@@ -14,7 +14,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
/// # use fimg::Image;
/// let mut i = Image::alloc(10, 10);
/// i.points(&[(1, 8), (3, 1), (8, 1), (6, 6), (8, 8), (1, 8)], [255]);
- /// # assert_eq!(i.buffer(), b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
+ /// # assert_eq!(i.buffer(), b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00");
/// ```
pub fn points(&mut self, poly: &[(i32, i32)], c: [u8; CHANNELS]) {
if poly.len() <= 1 {
@@ -110,7 +110,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
match sides {
3 => {
let space = TAU / 3.0;
- self.tri(
+ self.tri::<f32>(
trans(space + rotation) + pos,
trans(rotation) + pos,
trans(madd(space, 2.0, rotation)) + pos,
@@ -132,7 +132,7 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
if sides % 2 != 0 && sides > 4 {
let i = (sides - 1) as f32;
// the missing piece
- self.tri(
+ self.tri::<f32>(
pos,
trans(madd(space, i, rotation)) + pos,
trans(madd(space, i + 1., rotation)) + pos,
diff --git a/tdata/border_pentagon.imgbuf b/tdata/border_pentagon.imgbuf
index 39004de..a711027 100644
--- a/tdata/border_pentagon.imgbuf
+++ b/tdata/border_pentagon.imgbuf
Binary files differ
diff --git a/tdata/enneagon.imgbuf b/tdata/enneagon.imgbuf
index cf4a797..57bb6b3 100644
--- a/tdata/enneagon.imgbuf
+++ b/tdata/enneagon.imgbuf
Binary files differ