add a ImageCloner

This commit is contained in:
bendn 2023-09-25 13:48:58 +07:00
parent 1b5b06c36f
commit 829cfa680e
No known key found for this signature in database
GPG key ID: 0D9D3A2A3B2A93D6
5 changed files with 261 additions and 73 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "fimg"
version = "0.4.3"
version = "0.4.4"
authors = ["bend-n <bend.n@outlook.com>"]
license = "MIT"
edition = "2021"

View file

@ -1,19 +1,58 @@
//! Manages the affine image transformations.
use crate::Image;
use crate::{cloner::ImageCloner, Image};
impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
/// Flip a image horizontally.
/// Flip an image horizontally.
pub fn flip_h(&mut self) {
self.as_mut().flip_h();
}
/// Flip a image vertically.
/// Flip an image vertically.
pub fn flip_v(&mut self) {
self.as_mut().flip_v();
}
}
impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
/// Flip an image vertically.
/// ```
/// # use fimg::Image;
/// let a = Image::<_, 1>::build(2,2).buf(vec![21,42,90,01]);
/// assert_eq!(a.cloner().flip_v().take_buffer(), [90,01,21,42]);
/// ```
pub fn flip_v(&self) -> Image<Vec<u8>, CHANNELS> {
let mut out = self.alloc();
for y in 0..self.height() {
for x in 0..self.width() {
// SAFETY: looping over self, all ok (could be safe versions, bounds would be elided)
let p = unsafe { self.pixel(x, y) };
// SAFETY: looping over self.
unsafe { out.set_pixel(x, self.height() - y - 1, p) };
}
}
out
}
/// Flip an image horizontally
/// ```
/// # use fimg::Image;
/// let a = Image::<_,1>::build(2,2).buf(vec![90,01,21,42]);
/// assert_eq!(a.cloner().flip_h().take_buffer(), [01,90,42,21]);
/// ```
pub fn flip_h(&self) -> Image<Vec<u8>, CHANNELS> {
let mut out = self.alloc();
for y in 0..self.height() {
for x in 0..self.width() {
// SAFETY: looping over self, all ok
let p = unsafe { self.pixel(x, y) };
// SAFETY: looping over self, all ok
unsafe { out.set_pixel(self.width() - x - 1, y, p) };
}
}
out
}
}
impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
/// Flip a image vertically.
/// Flip an image vertically.
pub fn flip_v(&mut self) {
for y in 0..self.height() / 2 {
for x in 0..self.width() {
@ -30,7 +69,7 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
}
}
/// Flip a image horizontally.
/// Flip an image horizontally.
pub fn flip_h(&mut self) {
for y in 0..self.height() {
for x in 0..self.width() / 2 {
@ -49,12 +88,12 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
}
impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
/// Rotate a image 180 degrees clockwise.
/// Rotate an image 180 degrees clockwise.
pub fn rot_180(&mut self) {
self.as_mut().rot_180();
}
/// Rotate a image 90 degrees clockwise.
/// Rotate an image 90 degrees clockwise.
/// # Safety
///
/// UB if the image is not square
@ -63,7 +102,7 @@ impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
unsafe { self.as_mut().rot_90() }
}
/// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise.
/// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise.
/// # Safety
///
/// UB if the image is not square
@ -73,42 +112,60 @@ impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> {
}
}
impl<const CHANNELS: usize> ImageCloner<'_, CHANNELS> {
/// Rotate an image 180 degrees clockwise.
///
/// ```
/// # use fimg::Image;
/// let a = Image::<_,1>::build(2,2).buf(vec![00,01,02,10]);
/// assert_eq!(a.cloner().rot_180().take_buffer(), vec![10,02,01,00]);
/// ```
pub fn rot_180(&self) -> Image<Vec<u8>, CHANNELS> {
let s = (self.width() * self.height()) as usize;
let mut v: Vec<[u8; CHANNELS]> = Vec::with_capacity(s);
for (x, y) in self.chunked().rev().zip(&mut v.spare_capacity_mut()[..]) {
y.write(*x);
}
// SAFETY: we just wrote the right amount
unsafe { v.set_len(s) };
let (v, _, c) = v.into_raw_parts();
let s = s * CHANNELS;
// SAFETY: init with with_cap, set len to s, s is init amount, chunked returns nm, capacity handled, flatten vec
let v = unsafe { Vec::from_raw_parts(v.cast::<u8>(), s, c * CHANNELS) };
// SAFETY: s is w * h.
unsafe { Image::new(self.width, self.height, v) }
}
/// Rotate an image 90 degrees clockwise.
/// # Safety
///
/// UB if the image is not square
pub unsafe fn rot_90(&self) -> Image<Vec<u8>, CHANNELS> {
let mut out = self.flip_v();
// SAFETY: sqar
unsafe { transpose(&mut out.as_mut()) };
out
}
/// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise.
/// # Safety
///
/// UB if the image is not square
pub unsafe fn rot_270(&self) -> Image<Vec<u8>, CHANNELS> {
let mut out = self.flip_h();
// SAFETY: sqar
unsafe { transpose(&mut out.as_mut()) };
out
}
}
impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
/// Rotate a image 180 degrees clockwise.
/// Rotate an image 180 degrees clockwise.
pub fn rot_180(&mut self) {
for y in 0..self.height() / 2 {
for x in 0..self.width() {
// SAFETY: x, y come from the loop, must be ok
let p = unsafe { self.pixel(x, y) };
let x2 = self.width() - x - 1;
let y2 = self.height() - y - 1;
// SAFETY: values are good
let p2 = unsafe { self.pixel(x2, y2) };
// SAFETY: swapping would be cool, alas.
unsafe { self.set_pixel(x, y, p2) };
// SAFETY: although maybe i can cast it to a `[[u8; CHANNELS]]` and swap that 🤔
unsafe { self.set_pixel(x2, y2, p) };
}
self.flatten_mut().reverse();
}
if self.height() % 2 != 0 {
let middle = self.height() / 2;
for x in 0..self.width() / 2 {
let x2 = self.width() - x - 1;
#[allow(clippy::multiple_unsafe_ops_per_block)]
// SAFETY: its just doing the swappy
unsafe {
let p = self.pixel(x, middle);
let p2 = self.pixel(x2, middle);
self.set_pixel(x, middle, p2);
self.set_pixel(x2, middle, p);
}
}
}
}
/// Rotate a image 90 degrees clockwise.
/// Rotate an image 90 degrees clockwise.
/// # Safety
///
/// UB if the image is not square
@ -121,7 +178,7 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
unsafe { transpose(self) };
}
/// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise.
/// Rotate an image 270 degrees clockwise, or 90 degrees anti clockwise.
/// # Safety
///
/// UB if the image is not square
@ -156,8 +213,7 @@ unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>)
unsafe fn transpose_non_power_of_two<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
debug_assert_eq!(img.width(), img.height());
let size = img.width() as usize;
// SAFETY: no half pixels
let b = unsafe { img.buffer.as_chunks_unchecked_mut::<CHANNELS>() };
let b = img.flatten_mut();
for i in 0..size {
for j in i..size {
// SAFETY: caller ensures squarity

39
src/cloner.rs Normal file
View file

@ -0,0 +1,39 @@
//! provides a [`ImageCloner`]
//!
//! ```
//! # use fimg::Image;
//! # let i = Image::<_, 1>::alloc(5, 5);
//! unsafe { i.cloner().rot_270() };
//! ```
use crate::Image;
/// A neat way to clone a image.
///
/// Consider it a way to clone->apply a image operation, but better.
/// Please note that some methods may(although none at current) have different safety invariants from their in place counterparts.
pub struct ImageCloner<'a, const C: usize>(Image<&'a [u8], C>);
impl<'a, const C: usize> ImageCloner<'a, C> {
/// duplicate the inner image.
pub(crate) fn dup(&self) -> Image<Vec<u8>, C> {
self.0.to_owned()
}
/// Create a [`ImageCloner`] from a <code>[Image]<&\[[u8]\]></code>
pub const fn from(i: Image<&'a [u8], C>) -> Self {
Self(i)
}
/// Alloc a buffer the right size for use
pub(crate) fn alloc(&self) -> Image<Vec<u8>, C> {
Image::alloc(self.width(), self.height())
}
}
impl<'a, const C: usize> std::ops::Deref for ImageCloner<'a, C> {
type Target = Image<&'a [u8], C>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View file

@ -5,6 +5,7 @@
slice_swap_unchecked,
stmt_expr_attributes,
generic_const_exprs,
vec_into_raw_parts,
slice_as_chunks,
unchecked_math,
portable_simd,
@ -28,9 +29,11 @@ use std::{num::NonZeroU32, slice::SliceIndex};
mod affine;
pub mod builder;
pub mod cloner;
mod drawing;
mod overlay;
pub mod scale;
use cloner::ImageCloner;
pub use overlay::{Overlay, OverlayAt};
/// like assert!(), but causes undefined behaviour at runtime when the condition is not met.
@ -81,7 +84,7 @@ unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize {
}
/// A image with a variable number of channels, and a nonzero size.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq)]
pub struct Image<T, const CHANNELS: usize> {
/// column order 2d slice/vec
buffer: T,
@ -91,6 +94,37 @@ pub struct Image<T, const CHANNELS: usize> {
height: NonZeroU32,
}
impl<T: Clone, const CHANNELS: usize> Clone for Image<T, CHANNELS> {
/// Returns a duplicate of this image.
/// ```
/// # use fimg::Image;
/// # let i = Image::<Vec<_>, 1>::alloc(5,5);
/// let new_i = i.clone();
/// ```
/// If you find yourself in the pattern of
/// ```
/// # use fimg::Image;
/// # let i = Image::<Vec<_>, 1>::alloc(5,5);
/// let mut i = i.clone();
/// unsafe { i.rot_90() };
/// ```
/// STOP!
///
/// Instead use
/// ```
/// # use fimg::Image;
/// # let i = Image::<Vec<_>, 1>::alloc(5,5);
/// let i = unsafe { i.cloner().rot_90() };
/// ```
fn clone(&self) -> Self {
Self {
buffer: self.buffer.clone(),
width: self.width,
height: self.height,
}
}
}
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
#[inline]
/// get the height as a [`u32`]
@ -156,6 +190,8 @@ impl<const CHANNELS: usize, T: Clone> Image<&mut [T], CHANNELS> {
}
}
impl<const CHANNELS: usize> Copy for Image<&[u8], CHANNELS> {}
impl<const CHANNELS: usize> Image<&[u8], CHANNELS> {
#[inline]
#[must_use]
@ -209,9 +245,20 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS
index..unsafe { index.unchecked_add(CHANNELS) }
}
/// Procure a [`ImageCloner`].
pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> {
ImageCloner::from(self.as_ref())
}
/// Reference this image.
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) }
}
#[inline]
/// Returns a iterator over every pixel
pub fn chunked(&self) -> impl Iterator<Item = &[u8; CHANNELS]> {
pub fn chunked(&self) -> impl DoubleEndedIterator<Item = &[u8; CHANNELS]> {
// SAFETY: 0 sized images illegal
unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) };
// SAFETY: no half pixels!
@ -266,6 +313,12 @@ impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANN
self.buffer.array_chunks_mut::<CHANNELS>()
}
/// 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) }
}
#[inline]
/// Flatten the chunks of this image into a mutable slice of slices.
pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] {
@ -288,12 +341,6 @@ 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) }
}
/// Copy this ref image
pub fn copy(&mut self) -> Image<&mut [u8], CHANNELS> {
#[allow(clippy::undocumented_unsafe_blocks)]
@ -303,26 +350,6 @@ impl<const CHANNELS: usize> Image<&mut [u8], CHANNELS> {
}
}
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> 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> Image<Vec<u8>, CHANNELS> {
/// Allocates a new image
///

View file

@ -1,4 +1,6 @@
//! Handles image overlay
use crate::cloner::ImageCloner;
use super::{assert_unchecked, really_unsafe_index, Image};
use std::simd::SimdInt;
use std::simd::SimdPartialOrd;
@ -12,6 +14,16 @@ pub trait OverlayAt<W> {
/// UB if x, y is out of bounds
unsafe fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self;
}
/// [`OverlayAt`] but owned
pub trait ClonerOverlayAt<const W: usize, const C: usize> {
/// Overlay with => self at coordinates x, y, without blending, and returning a new image.
/// # Safety
///
/// UB if x, y is out of bounds
unsafe fn overlay_at(&self, with: &Image<&[u8], W>, x: u32, y: u32) -> Image<Vec<u8>, C>;
}
/// Trait for layering images ontop of each other.
/// Think `magick a b -layers flatten a`
pub trait Overlay<W> {
@ -22,6 +34,15 @@ pub trait Overlay<W> {
unsafe fn overlay(&mut self, with: &W) -> &mut Self;
}
/// [`Overlay`] but owned
pub trait ClonerOverlay<const W: usize, const C: usize> {
/// Overlay with => self (does not blend)
/// # Safety
///
/// UB if a.width != b.width || a.height != b.height
unsafe fn overlay(&self, with: &Image<&[u8], W>) -> Image<Vec<u8>, C>;
}
#[inline]
/// SIMD accelerated rgba => rgb overlay.
///
@ -88,6 +109,16 @@ impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> {
}
}
impl ClonerOverlay<4, 4> for ImageCloner<'_, 4> {
#[inline]
unsafe fn overlay(&self, with: &Image<&[u8], 4>) -> Image<Vec<u8>, 4> {
let mut out = self.dup();
// SAFETY: same
unsafe { out.as_mut().overlay(with) };
out
}
}
impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> {
#[inline]
unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self {
@ -113,6 +144,15 @@ impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> {
}
}
impl ClonerOverlayAt<4, 3> for ImageCloner<'_, 3> {
unsafe fn overlay_at(&self, with: &Image<&[u8], 4>, x: u32, y: u32) -> Image<Vec<u8>, 3> {
let mut new = self.dup();
// SAFETY: same
unsafe { new.as_mut().overlay_at(with, x, y) };
new
}
}
impl OverlayAt<Image<&[u8], 3>> for Image<&mut [u8], 3> {
/// Overlay a RGB image(with) => self at coordinates x, y.
/// As this is a `RGBxRGB` operation, blending is unnecessary,
@ -175,6 +215,16 @@ impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> {
}
}
impl ClonerOverlay<4, 3> for ImageCloner<'_, 3> {
#[inline]
unsafe fn overlay(&self, with: &Image<&[u8], 4>) -> Image<Vec<u8>, 3> {
let mut out = self.dup();
// SAFETY: same
unsafe { out.as_mut().overlay(with) };
out
}
}
impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> {
#[inline]
/// Overlay with => self at coordinates x, y, without blending
@ -210,3 +260,19 @@ impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> {
self
}
}
impl ClonerOverlayAt<4, 4> for ImageCloner<'_, 4> {
#[inline]
/// Overlay with => self at coordinates x, y, without blending, returning a new Image
///
/// # Safety
/// - UB if x, y is out of bounds
/// - UB if x + with.width() > [`u32::MAX`]
/// - UB if y + with.height() > [`u32::MAX`]
unsafe fn overlay_at(&self, with: &Image<&[u8], 4>, x: u32, y: u32) -> Image<Vec<u8>, 4> {
let mut out = self.dup();
// SAFETY: same
unsafe { out.as_mut().overlay_at(with, x, y) };
out
}
}