mirror of
https://github.com/bend-n/fimg.git
synced 2024-12-22 10:28:21 -06:00
add Image::crop
This commit is contained in:
parent
df2c7d2436
commit
5839881e25
104
src/lib.rs
104
src/lib.rs
|
@ -35,6 +35,7 @@
|
||||||
//! - [`Image::repeated`]
|
//! - [`Image::repeated`]
|
||||||
//! - [`Image::overlay`](Overlay), [`Image::overlay_at`](OverlayAt), [`Image::overlay_blended`](BlendingOverlay)
|
//! - [`Image::overlay`](Overlay), [`Image::overlay_at`](OverlayAt), [`Image::overlay_blended`](BlendingOverlay)
|
||||||
//! - [`Image::blur`]
|
//! - [`Image::blur`]
|
||||||
|
//! - [`Image::crop`]
|
||||||
//!
|
//!
|
||||||
//! ## feature flags
|
//! ## feature flags
|
||||||
//!
|
//!
|
||||||
|
@ -70,12 +71,12 @@
|
||||||
missing_docs
|
missing_docs
|
||||||
)]
|
)]
|
||||||
#![allow(clippy::zero_prefixed_literal, incomplete_features)]
|
#![allow(clippy::zero_prefixed_literal, incomplete_features)]
|
||||||
use std::{num::NonZeroU32, slice::SliceIndex};
|
use std::{num::NonZeroU32, ops::Range};
|
||||||
|
|
||||||
mod affine;
|
mod affine;
|
||||||
#[cfg(feature = "blur")]
|
#[cfg(feature = "blur")]
|
||||||
mod blur;
|
mod blur;
|
||||||
#[doc(hidden)]
|
pub use sub::{Cropper, SubImage};
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod cloner;
|
pub mod cloner;
|
||||||
|
@ -85,6 +86,7 @@ mod r#dyn;
|
||||||
pub(crate) mod math;
|
pub(crate) mod math;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
mod pack;
|
mod pack;
|
||||||
|
mod sub;
|
||||||
pub use pack::Pack;
|
pub use pack::Pack;
|
||||||
pub mod pixels;
|
pub mod pixels;
|
||||||
#[cfg(feature = "scale")]
|
#[cfg(feature = "scale")]
|
||||||
|
@ -383,56 +385,69 @@ macro_rules! make {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
impl<T, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
/// The size of the underlying buffer.
|
/// The size of the underlying buffer.
|
||||||
#[allow(clippy::len_without_is_empty)]
|
#[allow(clippy::len_without_is_empty)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len<U>(&self) -> usize
|
||||||
self.bytes().len()
|
where
|
||||||
}
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
/// Bytes of this image.
|
self.buffer().as_ref().len()
|
||||||
pub fn bytes(&self) -> &[u8] {
|
|
||||||
self.buffer.as_ref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// the output index is not guranteed to be in bounds
|
/// the output index is not guranteed to be in bounds
|
||||||
#[inline]
|
#[inline]
|
||||||
fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> {
|
fn slice<U>(&self, x: u32, y: u32) -> Range<usize>
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
let index = self.at(x, y);
|
let index = self.at(x, y);
|
||||||
debug_assert!(self.len() > index);
|
debug_assert!(self.len() > index);
|
||||||
// SAFETY: as long as the buffer isnt wrong, this is 😄
|
// SAFETY: as long as the buffer isnt wrong, this is 😄
|
||||||
index..unsafe { index.unchecked_add(CHANNELS) }
|
index..unsafe { index.unchecked_add(CHANNELS) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Procure a [`ImageCloner`].
|
|
||||||
#[must_use = "function does not modify the original image"]
|
|
||||||
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.bytes()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns a iterator over every pixel
|
/// Returns a iterator over every pixel
|
||||||
pub fn chunked(&self) -> impl DoubleEndedIterator<Item = &[u8; CHANNELS]> {
|
pub fn chunked<'a, U: 'a>(&'a self) -> impl DoubleEndedIterator<Item = &'a [U; CHANNELS]>
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
// SAFETY: 0 sized images illegal
|
// SAFETY: 0 sized images illegal
|
||||||
unsafe { assert_unchecked!(self.len() > CHANNELS) };
|
unsafe { assert_unchecked!(self.len() > CHANNELS) };
|
||||||
// SAFETY: no half pixels!
|
// SAFETY: no half pixels!
|
||||||
unsafe { assert_unchecked!(self.len() % CHANNELS == 0) };
|
unsafe { assert_unchecked!(self.len() % CHANNELS == 0) };
|
||||||
self.bytes().array_chunks::<CHANNELS>()
|
self.buffer().as_ref().array_chunks::<CHANNELS>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Flatten the chunks of this image into a slice of slices.
|
/// Flatten the chunks of this image into a slice of slices.
|
||||||
pub fn flatten(&self) -> &[[u8; CHANNELS]] {
|
pub fn flatten<U>(&self) -> &[[U; CHANNELS]]
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
// SAFETY: buffer cannot have half pixels
|
// SAFETY: buffer cannot have half pixels
|
||||||
unsafe { self.bytes().as_chunks_unchecked::<CHANNELS>() }
|
unsafe { self.buffer().as_ref().as_chunks_unchecked::<CHANNELS>() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a mutref to this image
|
||||||
|
pub fn as_mut<U>(&mut self) -> Image<&mut [U], CHANNELS>
|
||||||
|
where
|
||||||
|
T: AsMut<[U]>,
|
||||||
|
{
|
||||||
|
// SAFETY: construction went okay
|
||||||
|
unsafe { Image::new(self.width, self.height, self.buffer.as_mut()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reference this image.
|
||||||
|
pub fn as_ref<U>(&self) -> Image<&[U], CHANNELS>
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
|
// SAFETY: we got constructed okay, parameters must be valid
|
||||||
|
unsafe { Image::new(self.width, self.height, self.buffer().as_ref()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a pixel at (x, y).
|
/// Return a pixel at (x, y).
|
||||||
|
@ -441,10 +456,13 @@ impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
/// - UB if x, y is out of bounds
|
/// - UB if x, y is out of bounds
|
||||||
/// - UB if buffer is too small
|
/// - UB if buffer is too small
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] {
|
pub unsafe fn pixel<U: Copy>(&self, x: u32, y: u32) -> [U; CHANNELS]
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
// SAFETY: x and y in bounds, slice is okay
|
// SAFETY: x and y in bounds, slice is okay
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
self.buffer
|
self.buffer()
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.get_unchecked(self.slice(x, y))
|
.get_unchecked(self.slice(x, y))
|
||||||
.as_ptr()
|
.as_ptr()
|
||||||
|
@ -453,22 +471,38 @@ impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
// SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience.
|
// SAFETY: slice always returns a length of `CHANNELS`, so we `cast()` it for convenience.
|
||||||
unsafe { *ptr }
|
unsafe { *ptr }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
|
||||||
/// Return a mutable reference to a pixel at (x, y).
|
/// Return a mutable reference to a pixel at (x, y).
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// - UB if x, y is out of bounds
|
/// - UB if x, y is out of bounds
|
||||||
/// - UB if buffer is too small
|
/// - UB if buffer is too small
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] {
|
pub unsafe fn pixel_mut<U: Copy>(&mut self, x: u32, y: u32) -> &mut [U]
|
||||||
|
where
|
||||||
|
T: AsMut<[U]> + AsRef<[U]>,
|
||||||
|
{
|
||||||
// SAFETY: we have been told x, y is in bounds.
|
// SAFETY: we have been told x, y is in bounds.
|
||||||
let idx = self.slice(x, y);
|
let idx = self.slice(x, y);
|
||||||
// SAFETY: slice should always return a valid index
|
// SAFETY: slice should always return a valid index
|
||||||
unsafe { self.buffer.as_mut().get_unchecked_mut(idx) }
|
unsafe { self.buffer.as_mut().get_unchecked_mut(idx) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
|
/// Bytes of this image.
|
||||||
|
pub fn bytes(&self) -> &[u8] {
|
||||||
|
self.buffer.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Procure a [`ImageCloner`].
|
||||||
|
#[must_use = "function does not modify the original image"]
|
||||||
|
pub fn cloner(&self) -> ImageCloner<'_, CHANNELS> {
|
||||||
|
ImageCloner::from(self.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Returns a iterator over every pixel, mutably
|
/// Returns a iterator over every pixel, mutably
|
||||||
pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> {
|
pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> {
|
||||||
|
@ -479,12 +513,6 @@ impl<T: AsMut<[u8]> + AsRef<[u8]>, const CHANNELS: usize> Image<T, CHANNELS> {
|
||||||
self.buffer.as_mut().array_chunks_mut::<CHANNELS>()
|
self.buffer.as_mut().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, self.buffer.as_mut()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// Flatten the chunks of this image into a mutable slice of slices.
|
/// Flatten the chunks of this image into a mutable slice of slices.
|
||||||
pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] {
|
pub fn flatten_mut(&mut self) -> &mut [[u8; CHANNELS]] {
|
||||||
|
|
183
src/sub.rs
Normal file
183
src/sub.rs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
use std::{marker::PhantomData, num::NonZeroU32};
|
||||||
|
|
||||||
|
use crate::Image;
|
||||||
|
|
||||||
|
/// A smaller part of a larger image.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// ┏━━━━━━━━━━━━━━┓ hard borders represent the full image
|
||||||
|
/// ┃ 1 2 3 1 ┃ vvvv the top left of the new image
|
||||||
|
/// ┃ ┌──────┐ ┃ crop(2, 2).from(1, 1)
|
||||||
|
/// ┃ 4 │ 5 6 │ 2 ┃ ^^^^ width and height
|
||||||
|
/// ┃ │ │ ┃
|
||||||
|
/// ┃ 7 │ 8 9 │ 3 ┃
|
||||||
|
/// ┗━━━┷━━━━━━┷━━━┛ soft borders represent the new image
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubImage<T, const CHANNELS: usize> {
|
||||||
|
inner: Image<T, CHANNELS>,
|
||||||
|
/// in pixels
|
||||||
|
offset_x: u32,
|
||||||
|
real_width: NonZeroU32,
|
||||||
|
real_height: NonZeroU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for cropping a image.
|
||||||
|
pub trait Cropper<T, const C: usize> {
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if w - y == 0
|
||||||
|
fn from(self, x: u32, y: u32) -> SubImage<T, C>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone, const N: usize> Copy for SubImage<T, N> where Image<T, N>: Copy {}
|
||||||
|
|
||||||
|
macro_rules! def {
|
||||||
|
($t:ty, $($what:ident)?) => {
|
||||||
|
struct Crop<'a, T, const C: usize> {
|
||||||
|
dimensions: (NonZeroU32, NonZeroU32),
|
||||||
|
_d: PhantomData<SubImage<$t, C>>,
|
||||||
|
image: Image<$t, C>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, const C: usize> Cropper<$t, C> for Crop<'a, T, C> {
|
||||||
|
fn from(self, x: u32, y: u32) -> SubImage<$t, C> {
|
||||||
|
let w = self.image.width();
|
||||||
|
// SAFETY: ctor
|
||||||
|
let i = unsafe {
|
||||||
|
Image::new(
|
||||||
|
self.image.width,
|
||||||
|
NonZeroU32::new(self.image.height() - y).unwrap(),
|
||||||
|
&$($what)?(self.image.take_buffer()[(y as usize * C) * w as usize..]),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
SubImage {
|
||||||
|
offset_x: x,
|
||||||
|
inner: i,
|
||||||
|
real_width: self.dimensions.0,
|
||||||
|
real_height: self.dimensions.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const C: usize> Image<T, C> {
|
||||||
|
/// Crop a image.
|
||||||
|
///
|
||||||
|
/// The signature looks something like: `i.crop(width, height).from(top_left_x, top_left_y)`, which gives you a <code>[SubImage]<&\[T\], _></code>
|
||||||
|
///
|
||||||
|
/// If you want a owned image, `i.crop(w, h).from(x, y).own()` gets you a <code>[`Image`]<[Box]<\[T\], _>></code> back.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use fimg::{Image, Cropper};
|
||||||
|
/// let mut i = Image::<_, 1>::build(4, 3).buf([
|
||||||
|
/// 1, 2, 3, 1,
|
||||||
|
/// 4, 5, 6, 2,
|
||||||
|
/// 7, 8, 9, 3,
|
||||||
|
/// ]);
|
||||||
|
/// let c = i.crop(2, 2).from(1, 1);
|
||||||
|
/// # unsafe {
|
||||||
|
/// assert_eq!(c.pixel(0, 0), [5]);
|
||||||
|
/// assert_eq!(c.pixel(1, 1), [9]);
|
||||||
|
/// assert_eq!(
|
||||||
|
/// c.own().bytes(),
|
||||||
|
/// &[5, 6,
|
||||||
|
/// 8, 9]
|
||||||
|
/// );
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if width == 0 || height == 0
|
||||||
|
pub fn crop<'a, U: 'a>(&'a self, width: u32, height: u32) -> impl Cropper<&'a [U], C>
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
{
|
||||||
|
def!(&'a [T],);
|
||||||
|
Crop {
|
||||||
|
dimensions: (
|
||||||
|
NonZeroU32::new(width).expect("Image::crop panics when width == 0"),
|
||||||
|
NonZeroU32::new(height).expect("Image::crop panics when height == 0"),
|
||||||
|
),
|
||||||
|
_d: PhantomData,
|
||||||
|
image: self.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like [`Image::crop`], but returns a mutable [`SubImage`].
|
||||||
|
pub fn crop_mut<'a, U: 'a>(
|
||||||
|
&'a mut self,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> impl Cropper<&'a mut [U], C>
|
||||||
|
where
|
||||||
|
T: AsMut<[U]> + AsRef<[U]>,
|
||||||
|
{
|
||||||
|
def!(&'a mut [T], mut);
|
||||||
|
Crop {
|
||||||
|
dimensions: (
|
||||||
|
NonZeroU32::new(width).expect("Image::crop panics when width == 0"),
|
||||||
|
NonZeroU32::new(height).expect("Image::crop panics when height == 0"),
|
||||||
|
),
|
||||||
|
_d: PhantomData,
|
||||||
|
image: self.as_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone, const C: usize> SubImage<&[T], C> {
|
||||||
|
/// Clones this [`SubImage`] into its own [`Image`]
|
||||||
|
pub fn own(&self) -> Image<Box<[T]>, C> {
|
||||||
|
let mut out =
|
||||||
|
Vec::with_capacity(self.real_width.get() as usize * self.inner.height() as usize * C);
|
||||||
|
for row in self
|
||||||
|
.inner
|
||||||
|
.buffer
|
||||||
|
.chunks_exact(self.inner.width.get() as usize)
|
||||||
|
.take(self.real_height.get() as usize)
|
||||||
|
{
|
||||||
|
out.extend_from_slice(
|
||||||
|
&row[self.offset_x as usize
|
||||||
|
..self.offset_x as usize + self.real_width.get() as usize],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// SAFETY: ctor
|
||||||
|
unsafe { Image::new(self.real_width, self.real_height, out.into()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO crop()
|
||||||
|
impl<W, const C: usize> SubImage<W, C> {
|
||||||
|
/// Get a pixel.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// this pixel must be in bounds.
|
||||||
|
pub unsafe fn pixel<U: Copy>(&self, x: u32, y: u32) -> [U; C]
|
||||||
|
where
|
||||||
|
W: AsRef<[U]>,
|
||||||
|
{
|
||||||
|
// note: if you get a pixel, in release mode, that is in bounds of the outer image, but not the sub image, that would be library-ub.
|
||||||
|
debug_assert!(x < self.real_width.get());
|
||||||
|
debug_assert!(y < self.real_height.get());
|
||||||
|
// SAFETY: caller
|
||||||
|
unsafe { self.inner.pixel(x + self.offset_x, y) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a pixel, mutably.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// this pixel must be in bounds.
|
||||||
|
pub unsafe fn pixel_mut<U: Copy>(&mut self, x: u32, y: u32) -> &mut [U]
|
||||||
|
where
|
||||||
|
W: AsMut<[U]> + AsRef<[U]>,
|
||||||
|
{
|
||||||
|
debug_assert!(x < self.real_width.get());
|
||||||
|
debug_assert!(y < self.real_height.get());
|
||||||
|
// SAFETY: caller
|
||||||
|
unsafe { self.inner.pixel_mut(x, y) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue