fast image operations
Diffstat (limited to 'src/lib.rs')
| -rw-r--r-- | src/lib.rs | 246 |
1 files changed, 62 insertions, 184 deletions
@@ -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 - } -} |