fast image operations
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs246
1 files changed, 184 insertions, 62 deletions
diff --git a/src/lib.rs b/src/lib.rs
index fda1cb2..9de0643 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,37 +1,42 @@
+//! # 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,
- clippy::perf
+ missing_docs
)]
-#![allow(clippy::zero_prefixed_literal)]
+#![allow(clippy::zero_prefixed_literal, incomplete_features)]
use std::{num::NonZeroU32, slice::SliceIndex};
mod affine;
+pub mod builder;
+mod drawing;
mod overlay;
-pub use affine::{Flips, Rotations};
+pub mod scale;
pub use overlay::{Overlay, OverlayAt};
-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;
-}
-
+/// like assert!(), but causes undefined behaviour at runtime when the condition is not met.
+///
+/// # Safety
+///
+/// UB if condition is false.
macro_rules! assert_unchecked {
($cond:expr) => {{
if !$cond {
@@ -46,9 +51,12 @@ macro_rules! assert_unchecked {
}
use assert_unchecked;
-impl RepeatNew for Image<&[u8], 3> {
- type Output = Image<Vec<u8>, 3>;
- unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output {
+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> {
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()) {
@@ -61,54 +69,96 @@ impl RepeatNew for 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> {
- 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(),
- }
- }
+ /// column order 2d slice/vec
+ buffer: T,
+ /// image horizontal size
+ width: NonZeroU32,
+ /// image vertical size
+ height: NonZeroU32,
}
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]
- pub const fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self {
- Image {
+ /// 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 {
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,
@@ -116,6 +166,28 @@ 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> {
@@ -127,9 +199,12 @@ 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) }
}
@@ -150,8 +225,11 @@ 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 }
}
}
@@ -164,7 +242,9 @@ 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) }
}
@@ -192,37 +272,39 @@ impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN
}
}
-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)
+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) }
}
-}
-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)
+ /// 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> 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 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> 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)
+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)
+ }
}
}
@@ -234,15 +316,27 @@ impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
/// if width || height == 0
#[must_use]
pub fn alloc(width: u32, height: u32) -> Self {
- Image {
+ Self {
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 "]
@@ -268,6 +362,29 @@ 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"));
@@ -275,14 +392,19 @@ save!(1 == Grayscale("Y"));
#[cfg(test)]
macro_rules! img {
- [[$($v:literal),+] [$($v2:literal),+]] => {{
- let from: Image<Vec<u8>, 1> = Image::new(
- 2.try_into().unwrap(),
- 2.try_into().unwrap(),
- vec![$($v,)+ $($v2,)+]
- );
- from
- }}
+ [[$($v:literal),+] [$($v2:literal),+]] => {
+ Image::<Vec<u8>, 1>::build(2,2).buf(vec![$($v,)+ $($v2,)+])
+ }
}
#[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
+ }
+}