diff --git a/Cargo.toml b/Cargo.toml index 8a2d866..21a9af8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fimg" -version = "0.4.13" +version = "0.4.14" authors = ["bend-n "] license = "MIT" edition = "2021" diff --git a/benches/affine_transformations.rs b/benches/affine_transformations.rs index 912eff0..8ac03a4 100644 --- a/benches/affine_transformations.rs +++ b/benches/affine_transformations.rs @@ -3,12 +3,13 @@ use fimg::*; macro_rules! bench { (fn $name: ident() { run $fn: ident() } $($namec:ident)?) => { fn $name() { + let mut bytes = *include_bytes!("4_128x128.imgbuf"); let mut img: Image<_, 4> = - Image::build(128, 128).buf(include_bytes!("4_128x128.imgbuf").to_vec()); + Image::build(128, 128).buf(&mut bytes); for _ in 0..256 { #[allow(unused_unsafe)] unsafe { - img.$fn() + img.as_mut().$fn() }; } } diff --git a/benches/drawing.rs b/benches/drawing.rs index 1ad9c97..b0668d3 100644 --- a/benches/drawing.rs +++ b/benches/drawing.rs @@ -1,7 +1,7 @@ use fimg::*; fn tri() { - let mut b = [0u8; 1000 * 1000 * 4]; - let mut i = Image::<&mut [u8], 4>::build(1000, 1000).buf(&mut b); - i.tri((0., 0.), (1000., 500.), (0., 999.), [255, 255, 255, 255]); + let mut i: Image<_, 4> = fimg::make!(4 channels 1000 x 1000); + i.as_mut() + .tri((0., 0.), (1000., 500.), (0., 999.), [255, 255, 255, 255]); } iai::main!(tri); diff --git a/benches/overlays.rs b/benches/overlays.rs index 46e10f6..715a226 100644 --- a/benches/overlays.rs +++ b/benches/overlays.rs @@ -1,7 +1,7 @@ use fimg::*; fn overlay_3on3at() { - let mut a: Image<_, 3> = Image::alloc(128, 128); + let mut a = fimg::make!(3 channels 128 x 128); let b: Image<&[u8], 3> = Image::build(8, 8).buf(include_bytes!("3_8x8.imgbuf")); for x in 0..16 { for y in 0..16 { @@ -11,7 +11,7 @@ fn overlay_3on3at() { } fn overlay_4on3at() { - let mut a: Image<_, 3> = Image::alloc(128, 128); + let mut a = fimg::make!(3 channels 128 x 128); let b: Image<&[u8], 4> = Image::build(8, 8).buf(include_bytes!("4_8x8.imgbuf")); for x in 0..16 { for y in 0..16 { @@ -21,7 +21,7 @@ fn overlay_4on3at() { } fn overlay_4on4at() { - let mut a: Image<_, 4> = Image::alloc(128, 128); + let mut a = fimg::make!(4 channels 128 x 128); let b: Image<&[u8], 4> = Image::build(8, 8).buf(include_bytes!("4_8x8.imgbuf")); for x in 0..16 { for y in 0..16 { diff --git a/src/affine.rs b/src/affine.rs index 2568693..b0152a5 100644 --- a/src/affine.rs +++ b/src/affine.rs @@ -1,6 +1,4 @@ //! Manages the affine image transformations. -use std::ops::DerefMut; - use crate::{cloner::ImageCloner, Image}; impl ImageCloner<'_, CHANNELS> { @@ -45,7 +43,7 @@ impl ImageCloner<'_, CHANNELS> { } } -impl> Image { +impl + AsRef<[u8]>> Image { /// Flip an image vertically. pub fn flip_v(&mut self) { for y in 0..self.height() / 2 { @@ -127,7 +125,7 @@ impl ImageCloner<'_, CHANNELS> { } } -impl> Image { +impl + AsRef<[u8]>> Image { /// Rotate an image 180 degrees clockwise. pub fn rot_180(&mut self) { self.flatten_mut().reverse(); @@ -162,7 +160,7 @@ impl> Image { /// # Safety /// /// UB if supplied image not square -unsafe fn crev>(mut img: Image) { +unsafe fn crev + AsRef<[u8]>>(mut img: Image) { debug_assert_eq!(img.width(), img.height()); let size = img.width() as usize; let b = img.flatten_mut(); @@ -202,7 +200,7 @@ unsafe fn transpose_out( /// # Safety /// /// UB if supplied image rectangular -unsafe fn transpose>( +unsafe fn transpose + AsRef<[u8]>>( img: &mut Image, ) { debug_assert_eq!(img.width(), img.height()); @@ -220,7 +218,7 @@ unsafe fn transpose>( /// # Safety /// /// UB if image not square -unsafe fn transpose_non_power_of_two>( +unsafe fn transpose_non_power_of_two + AsRef<[u8]>>( img: &mut Image, ) { debug_assert_eq!(img.width(), img.height()); @@ -239,7 +237,7 @@ const TILE: usize = 4; /// # Safety /// /// be careful -unsafe fn transpose_tile>( +unsafe fn transpose_tile + AsRef<[u8]>>( img: &mut Image, row: usize, col: usize, @@ -275,7 +273,7 @@ unsafe fn transpose_tile>( /// # Safety /// /// be careful -unsafe fn transpose_diag>( +unsafe fn transpose_diag + AsRef<[u8]>>( img: &mut Image, pos: usize, size: usize, diff --git a/src/drawing/box.rs b/src/drawing/box.rs index d59b01b..b4b1d3e 100644 --- a/src/drawing/box.rs +++ b/src/drawing/box.rs @@ -1,9 +1,9 @@ //! `Box` -use std::ops::{DerefMut, Range}; +use std::ops::Range; use crate::Image; -impl, const CHANNELS: usize> Image { +impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Draw a bordered box /// ``` /// # use fimg::Image; diff --git a/src/drawing/circle.rs b/src/drawing/circle.rs index 793dd92..5f9f105 100644 --- a/src/drawing/circle.rs +++ b/src/drawing/circle.rs @@ -1,8 +1,8 @@ //! draw 2d circles use crate::Image; -use std::ops::DerefMut; -impl, const CHANNELS: usize> Image { + +impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Draws a circle, using the [Bresenham's circle](https://en.wikipedia.org/wiki/Midpoint_circle_algorithm) algorithm. /// ``` /// # use fimg::Image; diff --git a/src/drawing/line.rs b/src/drawing/line.rs index 369e8fd..3498d95 100644 --- a/src/drawing/line.rs +++ b/src/drawing/line.rs @@ -1,7 +1,7 @@ //! adds a `line` function to Image #![allow(clippy::missing_docs_in_private_items)] use crate::Image; -use std::{iter::Iterator, ops::DerefMut}; +use std::iter::Iterator; /// taken from [bresenham-rs](https://github.com/mbr/bresenham-rs) pub struct Bresenham { @@ -127,7 +127,7 @@ impl Iterator for Bresenham { } } -impl, const CHANNELS: usize> Image { +impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Draw a line from point a to point b. /// /// Points not in bounds will not be included. diff --git a/src/drawing/poly.rs b/src/drawing/poly.rs index 3847a56..b73f321 100644 --- a/src/drawing/poly.rs +++ b/src/drawing/poly.rs @@ -2,12 +2,11 @@ use std::{ cmp::{max, min}, f32::consts::TAU, - ops::DerefMut, }; use crate::Image; -impl, const CHANNELS: usize> Image { +impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Draws a filled polygon from a slice of points. Please close your poly. (first == last) /// /// Borrowed from [imageproc](https://docs.rs/imageproc/latest/src/imageproc/drawing/polygon.rs.html#31), modified for less allocations. diff --git a/src/drawing/tri.rs b/src/drawing/tri.rs index c29ad1d..b50dfe4 100644 --- a/src/drawing/tri.rs +++ b/src/drawing/tri.rs @@ -1,9 +1,9 @@ //! trongle drawing -use std::ops::DerefMut; + use crate::Image; -impl, const CHANNELS: usize> Image { +impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Draw a (filled) triangle /// ``` /// # use fimg::*; diff --git a/src/lib.rs b/src/lib.rs index 8049973..bb08357 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,7 +233,7 @@ impl Image<&[u8], CHANNELS> { /// ``` pub const fn make<'a, const WIDTH: u32, const HEIGHT: u32>() -> Image<&'a [u8], CHANNELS> where - [(); CHANNELS * WIDTH as usize * HEIGHT as usize]: Sized, + [(); CHANNELS * WIDTH as usize * HEIGHT as usize]:, { Image { width: NonZeroU32::new(WIDTH).expect("passed zero width to builder"), @@ -243,7 +243,40 @@ impl Image<&[u8], CHANNELS> { } } -impl, const CHANNELS: usize> Image { +#[macro_export] +/// Create a [Image]<[[u8]; N], C> with ease. If your looking for a [Image]<&'static [[u8]]>, try [`Image::make`]. +/// +/// ``` +/// let mut i = fimg::make!(4 channels 128 x 128); +/// ``` +/// +/// Implementation note: +/// This is doable with a const generic fn, but it returns a `fimg::Image<[u8; fimg::::{impl#7}::array::{constant#1}], _>` which means you cant actually type it, so its useless. +macro_rules! make { + ($channels:literal channels $w:literal x $h: literal) => { + unsafe { + Image::<_, $channels>::new( + match ::core::num::NonZeroU32::new($w) { + Some(n) => n, + None => panic!("width is 0"), + }, + match ::core::num::NonZeroU32::new($h) { + ::core::option::Option::Some(n) => n, + ::core::option::Option::None => panic!("height is 0"), + }, + [0_u8; $channels * $w * $h], + ) + } + }; +} + +impl, const CHANNELS: usize> Image { + /// The size of the underlying buffer. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.buffer.as_ref().len() + } + /// # Safety /// /// the output index is not guranteed to be in bounds @@ -271,7 +304,7 @@ impl, const CHANNELS: usize> Image index); + debug_assert!(self.len() > index); index } @@ -284,24 +317,24 @@ impl, const CHANNELS: usize> Image Image<&[u8], CHANNELS> { // SAFETY: we got constructed okay, parameters must be valid - unsafe { Image::new(self.width, self.height, &*self.buffer) } + unsafe { Image::new(self.width, self.height, self.buffer.as_ref()) } } #[inline] /// Returns a iterator over every pixel pub fn chunked(&self) -> impl DoubleEndedIterator { // SAFETY: 0 sized images illegal - unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; + unsafe { assert_unchecked!(self.len() > CHANNELS) }; // SAFETY: no half pixels! - unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) }; - self.buffer.array_chunks::() + unsafe { assert_unchecked!(self.len() % CHANNELS == 0) }; + self.buffer.as_ref().array_chunks::() } #[inline] /// Flatten the chunks of this image into a slice of slices. pub fn flatten(&self) -> &[[u8; CHANNELS]] { // SAFETY: buffer cannot have half pixels - unsafe { self.buffer.as_chunks_unchecked::() } + unsafe { self.buffer.as_ref().as_chunks_unchecked::() } } /// Return a pixel at (x, y). @@ -312,13 +345,19 @@ impl, const CHANNELS: usize> Image [u8; CHANNELS] { // SAFETY: x and y in bounds, slice is okay - let ptr = unsafe { self.buffer.get_unchecked(self.slice(x, y)).as_ptr().cast() }; + let ptr = unsafe { + self.buffer + .as_ref() + .get_unchecked(self.slice(x, y)) + .as_ptr() + .cast() + }; // SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience. unsafe { *ptr } } } -impl, const CHANNELS: usize> Image { +impl + AsRef<[u8]>, const CHANNELS: usize> Image { /// Return a mutable reference to a pixel at (x, y). /// # Safety /// @@ -329,30 +368,30 @@ impl, const CHANNELS: usize> Image impl Iterator { // SAFETY: 0 sized images are not allowed - unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; + unsafe { assert_unchecked!(self.len() > CHANNELS) }; // SAFETY: buffer cannot have half pixels - unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) }; - self.buffer.array_chunks_mut::() + unsafe { assert_unchecked!(self.len() % CHANNELS == 0) }; + self.buffer.as_mut().array_chunks_mut::() } /// Create a mutref to this image pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { // SAFETY: construction went okay - unsafe { Image::new(self.width, self.height, &mut self.buffer) } + unsafe { Image::new(self.width, self.height, self.buffer.as_mut()) } } #[inline] /// Flatten the chunks of this image into a mutable slice of slices. pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] { // SAFETY: buffer cannot have half pixels - unsafe { self.buffer.as_chunks_unchecked_mut::() } + unsafe { self.buffer.as_mut().as_chunks_unchecked_mut::() } } /// # Safety @@ -363,13 +402,13 @@ impl, const CHANNELS: usize> Image, dest: usize) { let std::ops::Range { start, end } = src; debug_assert!( - dest <= self.buffer.len() - end - start, + dest <= self.buffer.as_ref().len() - end - start, "dest is out of bounds" ); #[allow(clippy::multiple_unsafe_ops_per_block)] // SAFETY: the caller better be good unsafe { - let ptr = self.buffer.as_mut_ptr(); + let ptr = self.buffer.as_mut().as_mut_ptr(); std::ptr::copy_nonoverlapping(ptr.add(start), ptr.add(dest), end - start) }; } @@ -399,7 +438,7 @@ impl Image<&mut [u8], CHANNELS> { } impl Image, CHANNELS> { - /// Allocates a new image + /// Allocates a new image. If `width` and `height` are constant, try using [`make`]. /// /// # Panics /// @@ -417,7 +456,7 @@ impl Image, CHANNELS> { /// helper macro for defining the save() method. macro_rules! save { ($channels:literal == $clr:ident ($clrhuman:literal)) => { - impl> Image { + impl> Image { #[cfg(feature = "save")] #[doc = "Save this "] #[doc = $clrhuman] @@ -436,7 +475,7 @@ macro_rules! save { (0.15000, 0.06000), )); let mut writer = enc.write_header().unwrap(); - writer.write_image_data(&self.buffer).unwrap(); + writer.write_image_data(self.buffer.as_ref()).unwrap(); } } }; diff --git a/src/overlay.rs b/src/overlay.rs index 7f2715c..fca17bb 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -2,7 +2,6 @@ use crate::cloner::ImageCloner; use super::{assert_unchecked, Image}; -use std::ops::{Deref, DerefMut}; use std::simd::{simd_swizzle, Simd, SimdInt, SimdPartialOrd}; /// Trait for layering a image ontop of another, with a offset to the second image. @@ -100,7 +99,7 @@ unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) { } } -impl, U: Deref> Overlay> for Image { +impl + AsRef<[u8]>, U: AsRef<[u8]>> Overlay> for Image { #[inline] unsafe fn overlay(&mut self, with: &Image) -> &mut Self { debug_assert!(self.width() == with.width()); @@ -108,7 +107,8 @@ impl, U: Deref> Overlay> f for (i, other_pixels) in with.chunked().enumerate() { if other_pixels[3] >= 128 { // SAFETY: outside are bounds of index from slice - let own_pixels = unsafe { self.buffer.get_unchecked_mut(i * 4..i * 4 + 4) }; + let own_pixels = + unsafe { self.buffer.as_mut().get_unchecked_mut(i * 4..i * 4 + 4) }; own_pixels.copy_from_slice(other_pixels); } } @@ -127,7 +127,7 @@ impl ClonerOverlay<4, 4> for ImageCloner<'_, 4> { } } -impl, U: Deref> OverlayAt> for Image { +impl + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAt> for Image { #[inline] unsafe fn overlay_at(&mut self, with: &Image, x: u32, y: u32) -> &mut Self { // SAFETY: caller upholds this @@ -142,9 +142,9 @@ impl, U: Deref> OverlayAt> + with.width() as usize) * 3; // SAFETY: index is in bounds - let rgb = unsafe { self.buffer.get_unchecked_mut(o_x) }; + let rgb = unsafe { self.buffer.as_mut().get_unchecked_mut(o_x) }; // SAFETY: bounds are outside index - let rgba = unsafe { with.buffer.get_unchecked(i_x) }; + let rgba = unsafe { with.buffer.as_ref().get_unchecked(i_x) }; // SAFETY: arguments are 🟢 unsafe { blit(rgb, rgba) } } @@ -163,7 +163,7 @@ impl ClonerOverlayAt<4, 3> for ImageCloner<'_, 3> { } } -impl, U: Deref> OverlayAt> for Image { +impl + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAt> for Image { /// Overlay a RGB image(with) => self at coordinates x, y. /// As this is a `RGBxRGB` operation, blending is unnecessary, /// and this is simply a copy. @@ -182,12 +182,12 @@ impl, U: Deref> OverlayAt> ..((j + y as usize) * self.width() as usize + x as usize + ($n as usize)) * 3; // <= because ".." range - debug_assert!(o_x.end <= self.buffer().len()); - debug_assert!(i_x.end <= with.buffer().len()); + debug_assert!(o_x.end <= self.buffer().as_ref().len()); + debug_assert!(i_x.end <= with.buffer().as_ref().len()); // SAFETY: bounds are ✅ - let a = unsafe { self.buffer.get_unchecked_mut(o_x) }; + let a = unsafe { self.buffer.as_mut().get_unchecked_mut(o_x) }; // SAFETY: we are in ⬜! - let b = unsafe { with.buffer.get_unchecked(i_x) }; + let b = unsafe { with.buffer.as_ref().get_unchecked(i_x) }; a.copy_from_slice(b); } }}; @@ -202,19 +202,20 @@ impl, U: Deref> OverlayAt> } } -impl, U: Deref> Overlay> for Image { +impl + AsRef<[u8]>, U: AsRef<[u8]>> Overlay> for Image { #[inline] unsafe fn overlay(&mut self, with: &Image) -> &mut Self { debug_assert!(self.width() == with.width()); debug_assert!(self.height() == with.height()); for (i, chunk) in with .buffer + .as_ref() .chunks_exact(with.width() as usize * 4) .enumerate() { // SAFETY: all the bounds are good let rgb = unsafe { - self.buffer.get_unchecked_mut( + self.buffer.as_mut().get_unchecked_mut( i * with.width() as usize * 3..(i + 1) * with.width() as usize * 3, ) }; @@ -236,7 +237,7 @@ impl ClonerOverlay<4, 3> for ImageCloner<'_, 3> { } } -impl, U: Deref> OverlayAt> for Image { +impl + AsRef<[u8]>, U: AsRef<[u8]>> OverlayAt> for Image { #[inline] unsafe fn overlay_at(&mut self, with: &Image, x: u32, y: u32) -> &mut Self { for j in 0..with.height() {