fast image operations
| -rw-r--r-- | src/builder.rs | 71 | ||||
| -rw-r--r-- | src/indexed.rs | 67 | ||||
| -rw-r--r-- | src/indexed/builder.rs | 59 | ||||
| -rw-r--r-- | src/lib.rs | 9 |
4 files changed, 146 insertions, 60 deletions
diff --git a/src/builder.rs b/src/builder.rs index 363183c..c3e4164 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,9 +5,12 @@ use std::marker::PhantomData; use crate::Image; -impl<B: buf::Buffer, const C: usize> Image<B, C> { +impl<B, const C: usize> Image<B, C> { /// creates a builder - pub const fn build(w: u32, h: u32) -> Builder<B, C> { + pub const fn build<I>(w: u32, h: u32) -> Builder<B, C> + where + B: AsRef<[I]>, + { Builder::new(w, h) } } @@ -22,7 +25,7 @@ pub struct Builder<B, const C: usize> { #[allow(clippy::missing_docs_in_private_items)] _buffer: PhantomData<B>, } -impl<B: buf::Buffer, const C: usize> Builder<B, C> { +impl<B, const C: usize> Builder<B, C> { /// create new builder pub const fn new(w: u32, h: u32) -> Self { Self { @@ -35,12 +38,15 @@ impl<B: buf::Buffer, const C: usize> Builder<B, C> { /// apply a buffer, and build #[track_caller] #[must_use = "what is it going to do?"] - pub fn buf(self, buffer: B) -> Image<B, C> { + pub fn buf<I>(self, buffer: B) -> Image<B, C> + where + B: AsRef<[I]>, + { let len = C as u32 * self.width * self.height; assert!( - buffer.len() as u32 == len, + buffer.as_ref().len() as u32 == len, "invalid buffer size (expected {len}, got {})", - buffer.len() + buffer.as_ref().len() ); Image { buffer, @@ -74,56 +80,3 @@ impl<T: Copy, const C: usize> Builder<Box<[T]>, C> { .buf((0..self.width * self.height).flat_map(|_| with).collect()) } } - -/// seals the [`Buffer`] trait -mod buf { - /// A valid buffer for use in the builder - #[diagnostic::on_unimplemented( - message = "this type is not a buffer", - note = "if you think this type is a buffer, please open an issue.\nYou can manually circumvent this warning via Image::new." - )] - pub trait Buffer { - #[doc(hidden)] - fn len(&self) -> usize; - } - impl<T> Buffer for Vec<T> { - fn len(&self) -> usize { - self.len() - } - } - impl<T> Buffer for &[T] { - fn len(&self) -> usize { - <[T]>::len(self) - } - } - impl<T> Buffer for &mut [T] { - fn len(&self) -> usize { - <[T]>::len(self) - } - } - impl<T, const N: usize> Buffer for [T; N] { - fn len(&self) -> usize { - N - } - } - impl<T, const N: usize> Buffer for &[T; N] { - fn len(&self) -> usize { - N - } - } - impl<T, const N: usize> Buffer for &mut [T; N] { - fn len(&self) -> usize { - N - } - } - impl<T, const N: usize> Buffer for Box<[T; N]> { - fn len(&self) -> usize { - N - } - } - impl<T> Buffer for Box<[T]> { - fn len(&self) -> usize { - <[T]>::len(self) - } - } -} diff --git a/src/indexed.rs b/src/indexed.rs new file mode 100644 index 0000000..ac56eb6 --- /dev/null +++ b/src/indexed.rs @@ -0,0 +1,67 @@ +//! indexed images! whoo! (palette and `Image<[u8], 1>`, basically.) +#![allow(private_bounds)] +mod builder; + +use crate::Image; + +#[allow(non_camel_case_types)] +trait uint: Copy + TryInto<usize> { + fn nat(self) -> usize { + self.try_into().ok().unwrap() + } +} + +macro_rules! int { + ($($t:ty)+) => { + $(impl uint for $t {})+ + }; +} +int!(u8 u16 u32 u64 u128); + +/// An image with a palette. +#[derive(Clone)] +pub struct IndexedImage<INDEX, PALETTE> { + // likely Box<[u8]>, … + // safety invariant: when INDEX<impl uint>, and PALETTE: Buffer, U must be < len(PALETTE) + buffer: Image<INDEX, 1>, + palette: PALETTE, // likely Box<[[f32; 4]]>, … +} + +impl<I, P> IndexedImage<I, P> { + /// Indexes the palette with each index. + pub fn to<PIXEL: Copy, INDEX: uint, const CHANNELS: usize>( + &self, + ) -> Image<Box<[PIXEL]>, CHANNELS> + where + P: AsRef<[[PIXEL; CHANNELS]]>, + I: AsRef<[INDEX]>, + { + unsafe { + self.buffer.map(|x| { + x.as_ref() + .iter() + .flat_map(|x| self.palette.as_ref()[x.nat()]) + .collect() + }) + } + } + + /// Provides the buffer and palette of this image. + pub fn into_raw_parts(self) -> (Image<I, 1>, P) { + (self.buffer, self.palette) + } + + /// Creates a indexed image from its 1 channel image representation and palette. + pub fn from_raw_parts<INDEX: uint, PIXEL>( + buffer: Image<I, 1>, + palette: P, + ) -> Result<Self, &'static str> + where + I: AsRef<[INDEX]>, + P: AsRef<[PIXEL]>, + { + let good = buffer.chunked().all(|[x]| x.nat() < palette.as_ref().len()); + good.then_some(Self { buffer, palette }) + .ok_or("not all indexes are in palette") + } +} diff --git a/src/indexed/builder.rs b/src/indexed/builder.rs new file mode 100644 index 0000000..90bf3c5 --- /dev/null +++ b/src/indexed/builder.rs @@ -0,0 +1,59 @@ +use super::{uint, IndexedImage as Image}; +use std::marker::PhantomData; +impl<B, P> Image<B, P> { + /// creates a builder + pub const fn build<I, J>(w: u32, h: u32) -> Builder<B, P> + where + B: AsRef<[I]>, + P: AsRef<[J]>, + { + Builder::new(w, h) + } +} + +/// Safe [`Image`] builder. +#[must_use = "builder must be consumed"] +pub struct Builder<B, P> { + /// the width in a zeroable type. zeroable so as to make the check in [`buf`] easier. + width: u32, + /// the height in a zeroable type. + height: u32, + palette: Option<P>, + #[allow(clippy::missing_docs_in_private_items)] + _buffer: PhantomData<B>, +} +impl<B, P> Builder<B, P> { + /// create new builder + pub const fn new(w: u32, h: u32) -> Self { + Self { + width: w, + height: h, + _buffer: PhantomData, + palette: None, + } + } + + /// add a palette + pub fn pal(self, p: P) -> Self { + Self { + palette: Some(p), + ..self + } + } + + /// apply a buffer, and build + #[track_caller] + #[must_use = "what is it going to do?"] + #[allow(private_bounds)] + pub fn buf<I: uint, J>(self, buffer: B) -> Image<B, P> + where + B: AsRef<[I]>, + P: AsRef<[J]>, + { + Image::from_raw_parts( + crate::Image::build(self.width, self.height).buf(buffer), + self.palette.expect("require palette"), + ) + .unwrap() + } +} @@ -99,6 +99,7 @@ pub mod cloner; mod convert; mod drawing; mod r#dyn; +pub mod indexed; pub(crate) mod math; mod overlay; mod pack; @@ -309,9 +310,15 @@ impl<T, const CHANNELS: usize> Image<T, CHANNELS> { /// # Safety /// keep the buffer size the same + unsafe fn with<U, const N: usize>(&self, x: U) -> Image<U, N> { + unsafe { Image::new(self.width, self.height, x) } + } + + /// # Safety + /// keep the buffer size the same unsafe fn map<U, const N: usize, F: FnOnce(&T) -> U>(&self, f: F) -> Image<U, N> { // SAFETY: we dont change anything, why check - unsafe { Image::new(self.width, self.height, f(self.buffer())) } + unsafe { self.with(f(self.buffer())) } } unsafe fn mapped<U, const N: usize, F: FnOnce(T) -> U>(self, f: F) -> Image<U, N> { |