fast image operations
-rw-r--r--src/builder.rs71
-rw-r--r--src/indexed.rs67
-rw-r--r--src/indexed/builder.rs59
-rw-r--r--src/lib.rs9
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()
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index ed1e884..2c3ea14 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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> {