fast image operations
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs246
1 files changed, 62 insertions, 184 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 9de0643..fda1cb2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,42 +1,37 @@
-//! # fimg
-//!
-//! Provides fast image operations, such as rotation, flipping, and overlaying.
#![feature(
slice_swap_unchecked,
- generic_const_exprs,
slice_as_chunks,
unchecked_math,
portable_simd,
- const_option,
array_chunks,
test
)]
#![warn(
- clippy::missing_docs_in_private_items,
clippy::multiple_unsafe_ops_per_block,
- clippy::undocumented_unsafe_blocks,
clippy::missing_const_for_fn,
clippy::missing_safety_doc,
unsafe_op_in_unsafe_fn,
clippy::dbg_macro,
- missing_docs
+ clippy::perf
)]
-#![allow(clippy::zero_prefixed_literal, incomplete_features)]
+#![allow(clippy::zero_prefixed_literal)]
use std::{num::NonZeroU32, slice::SliceIndex};
mod affine;
-pub mod builder;
-mod drawing;
mod overlay;
-pub mod scale;
+pub use affine::{Flips, Rotations};
pub use overlay::{Overlay, OverlayAt};
-/// like assert!(), but causes undefined behaviour at runtime when the condition is not met.
-///
-/// # Safety
-///
-/// UB if condition is false.
+pub trait RepeatNew {
+ type Output;
+ /// Repeat self till it fills a new image of size x, y
+ /// # Safety
+ ///
+ /// UB if self's width is not a multiple of x, or self's height is not a multiple of y
+ unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output;
+}
+
macro_rules! assert_unchecked {
($cond:expr) => {{
if !$cond {
@@ -51,12 +46,9 @@ macro_rules! assert_unchecked {
}
use assert_unchecked;
-impl Image<&[u8], 3> {
- /// Repeat self till it fills a new image of size x, y
- /// # Safety
- ///
- /// UB if self's width is not a multiple of x, or self's height is not a multiple of y
- pub unsafe fn repeated(&self, x: u32, y: u32) -> Image<Vec<u8>, 3> {
+impl RepeatNew for Image<&[u8], 3> {
+ type Output = Image<Vec<u8>, 3>;
+ unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output {
let mut img = Image::alloc(x, y); // could probably optimize this a ton but eh
for x in 0..(x / self.width()) {
for y in 0..(y / self.height()) {
@@ -69,96 +61,54 @@ impl Image<&[u8], 3> {
}
}
-/// calculates a column major index, with unchecked math
#[inline]
unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize {
// y * w + x
- // SAFETY: FIXME make safe math
let tmp = unsafe { (y as usize).unchecked_mul(w as usize) };
- // SAFETY: FIXME make safe math
unsafe { tmp.unchecked_add(x as usize) }
}
-/// A image with a variable number of channels, and a nonzero size.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Image<T, const CHANNELS: usize> {
- /// column order 2d slice/vec
- buffer: T,
- /// image horizontal size
- width: NonZeroU32,
- /// image vertical size
- height: NonZeroU32,
+ pub buffer: T,
+ pub width: NonZeroU32,
+ pub height: NonZeroU32,
+}
+
+impl<const CHANNELS: usize> Default for Image<&'static [u8], CHANNELS> {
+ fn default() -> Self {
+ Self {
+ buffer: &[0; CHANNELS],
+ width: NonZeroU32::new(1).unwrap(),
+ height: NonZeroU32::new(1).unwrap(),
+ }
+ }
}
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
#[inline]
- /// get the height as a [`u32`]
pub fn height(&self) -> u32 {
self.height.into()
}
#[inline]
- /// get the width as a [`u32`]
pub fn width(&self) -> u32 {
self.width.into()
}
#[inline]
- /// create a new image
- ///
- /// # Safety
- ///
- /// does not check that buffer.len() == w * h * C
- ///
- /// using this with invalid values may result in future UB
- pub const unsafe fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self {
- Self {
+ pub const fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self {
+ Image {
buffer,
width,
height,
}
}
-
- /// consumes the image, returning the image buffer
- pub fn take_buffer(self) -> T {
- self.buffer
- }
-
- /// returns a immutable reference to the backing buffer
- pub const fn buffer(&self) -> &T {
- &self.buffer
- }
-
- /// returns a mutable(!) reference to the backing buffer
- ///
- /// # Safety
- ///
- /// please do not change buffer size.
- pub unsafe fn buffer_mut(&mut self) -> &mut T {
- &mut self.buffer
- }
-}
-
-impl<const CHANNELS: usize, T: Clone> Image<&[T], CHANNELS> {
- /// Allocate a new `Image<Vec<T>>` from this imageref.
- pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
- // SAFETY: we have been constructed already, so must be valid
- unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) }
- }
-}
-
-impl<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
- /// Allocate a new `Image<Vec<T>>` from this mutable imageref.
- pub fn to_owned(&self) -> Image<Vec<T>, CHANNELS> {
- // SAFETY: we have been constructed already, so must be valid
- unsafe { Image::new(self.width, self.height, self.buffer.to_vec()) }
- }
}
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
#[inline]
#[must_use]
- /// Copy this ref image
pub const fn copy(&self) -> Self {
Self {
width: self.width,
@@ -166,28 +116,6 @@ impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
buffer: self.buffer,
}
}
-
- /// Create a new immutable image of width x, y.
- ///
- /// # Panics
- ///
- /// if width || height == 0
- ///
- /// ```
- /// # use fimg::Image;
- /// let img = Image::make::<5, 5>();
- /// # let img: Image<_, 4> = img;
- /// ```
- pub const fn make<'a, const WIDTH: u32, const HEIGHT: u32>() -> Image<&'a [u8], CHANNELS>
- where
- [(); CHANNELS * WIDTH as usize * HEIGHT as usize]: Sized,
- {
- Image {
- width: NonZeroU32::new(WIDTH).expect("passed zero width to builder"),
- height: NonZeroU32::new(HEIGHT).expect("passed zero height to builder"),
- buffer: &[0; CHANNELS * WIDTH as usize * HEIGHT as usize],
- }
- }
}
impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
@@ -199,12 +127,9 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS
unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> {
debug_assert!(x < self.width(), "x out of bounds");
debug_assert!(y < self.height(), "y out of bounds");
- // SAFETY: me when uncheck math: 😧
let index = unsafe { really_unsafe_index(x, y, self.width()) };
- // SAFETY: 🧐 is unsound? 😖
let index = unsafe { index.unchecked_mul(CHANNELS) };
debug_assert!(self.buffer.len() > index);
- // SAFETY: as long as the buffer isnt wrong, this is 😄
index..unsafe { index.unchecked_add(CHANNELS) }
}
@@ -225,11 +150,8 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS
/// - UB if buffer is too small
#[inline]
pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] {
- // SAFETY: we have been told x, y is in bounds
let idx = unsafe { self.slice(x, y) };
- // SAFETY: slice always returns a valid index
let ptr = unsafe { self.buffer.get_unchecked(idx).as_ptr().cast() };
- // SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience.
unsafe { *ptr }
}
}
@@ -242,9 +164,7 @@ impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN
/// - UB if buffer is too small
#[inline]
pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] {
- // SAFETY: we have been told x, y is in bounds.
let idx = unsafe { self.slice(x, y) };
- // SAFETY: slice should always return a valid index
unsafe { self.buffer.get_unchecked_mut(idx) }
}
@@ -272,39 +192,37 @@ impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN
}
}
-impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
- /// Downcast the mutable reference
- pub fn as_ref(&self) -> Image<&[u8], CHANNELS> {
- // SAFETY: we got constructed okay, parameters must be valid
- unsafe { Image::new(self.width, self.height, self.buffer) }
+pub trait FromRef<const CHANNELS: usize> {
+ /// Reference the buffer
+ fn as_ref(&self) -> Image<&[u8], CHANNELS>;
+}
+
+pub trait FromRefMut<const CHANNELS: usize> {
+ /// Reference the buffer, mutably
+ fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS>;
+}
+
+impl<const CHANNELS: usize> FromRef<CHANNELS> for Image<&mut [u8], CHANNELS> {
+ fn as_ref(&self) -> Image<&[u8], CHANNELS> {
+ Image::new(self.width, self.height, self.buffer)
}
+}
- /// Copy this ref image
- pub fn copy(&mut self) -> Image<&mut [u8], CHANNELS> {
- #[allow(clippy::undocumented_unsafe_blocks)]
- unsafe {
- Image::new(self.width, self.height, self.buffer)
- }
+impl<const CHANNELS: usize> FromRefMut<CHANNELS> for Image<&mut [u8], CHANNELS> {
+ fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> {
+ Image::new(self.width, self.height, self.buffer)
}
}
-impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
- /// Create a reference to this owned image
- pub fn as_ref(&self) -> Image<&[u8], CHANNELS> {
- #[allow(clippy::undocumented_unsafe_blocks)]
- unsafe {
- Image::new(self.width, self.height, &self.buffer)
- }
+impl<const CHANNELS: usize> FromRef<CHANNELS> for Image<Vec<u8>, CHANNELS> {
+ fn as_ref(&self) -> Image<&[u8], CHANNELS> {
+ Image::new(self.width, self.height, &self.buffer)
}
}
-impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
- /// Create a mutable reference to this owned image
- pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> {
- #[allow(clippy::undocumented_unsafe_blocks)]
- unsafe {
- Image::new(self.width, self.height, &mut self.buffer)
- }
+impl<const CHANNELS: usize> FromRefMut<CHANNELS> for Image<Vec<u8>, CHANNELS> {
+ fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> {
+ Image::new(self.width, self.height, &mut self.buffer)
}
}
@@ -316,27 +234,15 @@ impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
/// if width || height == 0
#[must_use]
pub fn alloc(width: u32, height: u32) -> Self {
- Self {
+ Image {
width: width.try_into().unwrap(),
height: height.try_into().unwrap(),
buffer: vec![0; CHANNELS * width as usize * height as usize],
}
}
}
-
-/// helper macro for defining the save() method.
macro_rules! save {
($channels:literal == $clr:ident ($clrhuman:literal)) => {
- impl Image<Vec<u8>, $channels> {
- #[cfg(feature = "save")]
- #[doc = "Save this "]
- #[doc = $clrhuman]
- #[doc = " image."]
- pub fn save(&self, f: impl AsRef<std::path::Path>) {
- self.as_ref().save(f)
- }
- }
-
impl Image<&[u8], $channels> {
#[cfg(feature = "save")]
#[doc = "Save this "]
@@ -362,29 +268,6 @@ macro_rules! save {
};
}
-impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
- #[cfg(feature = "save")]
- /// Open a PNG image
- pub fn open(f: impl AsRef<std::path::Path>) -> Self {
- let p = std::fs::File::open(f).unwrap();
- let r = std::io::BufReader::new(p);
- let dec = png::Decoder::new(r);
- let mut reader = dec.read_info().unwrap();
- let mut buf = vec![0; reader.output_buffer_size()];
- let info = reader.next_frame(&mut buf).unwrap();
- use png::ColorType::*;
- match info.color_type {
- Indexed | Grayscale => {
- assert_eq!(CHANNELS, 1, "indexed | grayscale requires one channel")
- }
- Rgb => assert_eq!(CHANNELS, 3, "rgb requires three channels"),
- Rgba => assert_eq!(CHANNELS, 4, "rgba requires four channels"),
- GrayscaleAlpha => assert_eq!(CHANNELS, 2, "ya requires two channels"),
- }
- Self::build(info.width, info.height).buf(buf)
- }
-}
-
save!(3 == Rgb("RGB"));
save!(4 == Rgba("RGBA"));
save!(2 == GrayscaleAlpha("YA"));
@@ -392,19 +275,14 @@ save!(1 == Grayscale("Y"));
#[cfg(test)]
macro_rules! img {
- [[$($v:literal),+] [$($v2:literal),+]] => {
- Image::<Vec<u8>, 1>::build(2,2).buf(vec![$($v,)+ $($v2,)+])
- }
+ [[$($v:literal),+] [$($v2:literal),+]] => {{
+ let from: Image<Vec<u8>, 1> = Image::new(
+ 2.try_into().unwrap(),
+ 2.try_into().unwrap(),
+ vec![$($v,)+ $($v2,)+]
+ );
+ from
+ }}
}
#[cfg(test)]
use img;
-
-#[cfg(test)]
-mod tests {
- use super::*;
- #[test]
- fn repeat() {
- let x: Image<&[u8], 3> = Image::build(8, 8).buf(include_bytes!("../benches/3_8x8.imgbuf"));
- unsafe { x.repeated(128, 128) }; // repeat 16 times
- }
-}