mindustry logic execution, map- and schematic- parsing and rendering
organize image module
| -rw-r--r-- | src/utils/image.rs | 804 | ||||
| -rw-r--r-- | src/utils/image/affine.rs | 194 | ||||
| -rw-r--r-- | src/utils/image/holder.rs | 100 | ||||
| -rw-r--r-- | src/utils/image/mod.rs | 404 | ||||
| -rw-r--r-- | src/utils/image/overlay.rs | 110 |
5 files changed, 808 insertions, 804 deletions
diff --git a/src/utils/image.rs b/src/utils/image.rs deleted file mode 100644 index 972b10e..0000000 --- a/src/utils/image.rs +++ /dev/null @@ -1,804 +0,0 @@ -use fast_image_resize as fr; -use std::simd::SimdInt; -use std::simd::SimdPartialOrd; -use std::simd::{simd_swizzle, Simd}; -use std::{num::NonZeroU32, slice::SliceIndex}; - -pub trait OverlayAt<W> { - /// Overlay with => self at coordinates x, y, without blending - /// # Safety - /// - /// UB if x, y is out of bounds - unsafe fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self; -} - -pub trait Overlay<W> { - /// Overlay with => self (does not blend) - /// # Safety - /// - /// UB if a.width != b.width || a.height != b.height - unsafe fn overlay(&mut self, with: &W) -> &mut Self; -} - -pub trait RepeatNew { - type Output; - /// Repeat self till it fills a new image of size x, y - /// # Safety - /// - /// UB if self's width is not a multiple of x, or self's height is not a multiple of y - unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output; -} - -pub trait ImageUtils { - /// Tint this image with the color - fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self; - /// rotate (squares only) - /// # Safety - /// - /// UB if image is not square - unsafe fn rotate(&mut self, times: u8) -> &mut Self; - /// flip along the horizontal axis - fn flip_h(&mut self) -> &mut Self; - /// flip along the vertical axis - fn flip_v(&mut self) -> &mut Self; - /// shadow - fn shadow(&mut self) -> &mut Self; - /// scale a image - fn scale(self, to: u32) -> Image<Vec<u8>, 4>; -} - -macro_rules! unsafe_assert { - ($cond:expr) => {{ - if !$cond { - #[cfg(debug_assertions)] - panic!("assertion failed: {} returned false", stringify!($cond)); - #[cfg(not(debug_assertions))] - unsafe { - std::hint::unreachable_unchecked() - }; - } - }}; -} - -impl OverlayAt<Image<&[u8], 3>> for Image<&mut [u8], 3> { - unsafe fn overlay_at(&mut self, with: &Image<&[u8], 3>, x: u32, y: u32) -> &mut Self { - for j in 0..with.height() { - for i in 0..with.width() { - let with_index = with.slice(i, j); - let their_px = with.buffer.get_unchecked(with_index); - let our_index = - really_unsafe_index(i.unchecked_add(x), j.unchecked_add(y), self.width()) - .unchecked_mul(3); - let our_px = self - .buffer - .get_unchecked_mut(our_index..our_index.unchecked_add(3)); - std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 3); - } - } - self - } -} - -pub unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) { - unsafe_assert!(rgba.len() % 4 == 0); - unsafe_assert!(rgba.len() / 4 * 3 == rgb.len()); - const LANES: usize = 16; - - let use_old_last4 = Simd::from_array([ - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, - ]); - - let mut srci = 0; - let mut dsti = 0; - while dsti + 16 <= rgb.len() { - let old: Simd<u8, LANES> = Simd::from_slice(unsafe { rgb.get_unchecked(dsti..dsti + 16) }); - let new: Simd<u8, LANES> = Simd::from_slice(rgba.get_unchecked(srci..srci + 16)); - - let threshold = new.simd_ge(Simd::splat(128)).to_int().cast::<u8>(); - let mut mask = simd_swizzle!( - threshold, - [3, 3, 3, 7, 7, 7, 11, 11, 11, 15, 15, 15, 0, 0, 0, 0] - ); - mask &= use_old_last4; - - let new_rgb = simd_swizzle!(new, [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0, 0, 0, 0]); - let blended = (new_rgb & mask) | (old & !mask); - blended.copy_to_slice(rgb.get_unchecked_mut(dsti..dsti + 16)); - - srci += 16; - dsti += 12; - } - - while dsti + 3 <= rgb.len() { - if *rgba.get_unchecked(srci + 3) >= 128 { - std::ptr::copy_nonoverlapping( - rgba.get_unchecked(srci..srci + 3).as_ptr(), - rgb.get_unchecked_mut(dsti..dsti + 3).as_mut_ptr(), - 3, - ); - } - - srci += 4; - dsti += 3; - } -} - -impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> { - unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { - unsafe_assert!(x + with.width() <= self.width()); - unsafe_assert!(y + with.height() <= self.height()); - for j in 0..with.height() { - let i_x = j as usize * with.width() as usize * 4 - ..(j as usize + 1) * with.width() as usize * 4; - let o_x = ((j as usize + y as usize) * self.width() as usize + x as usize) * 3 - ..((j as usize + y as usize) * self.width() as usize - + x as usize - + with.width() as usize) - * 3; - blit( - self.buffer.get_unchecked_mut(o_x), - with.buffer.get_unchecked(i_x), - ) - } - self - } -} - -impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> { - unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self { - unsafe_assert!(self.width() == with.width()); - unsafe_assert!(self.height() == with.height()); - for (i, chunk) in with - .buffer - .chunks_exact(with.width() as usize * 4) - .enumerate() - { - blit( - self.buffer.get_unchecked_mut( - i * with.width() as usize * 3..(i + 1) * with.width() as usize * 3, - ), - chunk, - ); - } - self - } -} - -impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> { - unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { - for j in 0..with.height() { - for i in 0..with.width() { - let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4); - let their_px = with - .buffer - .get_unchecked(with_index..with_index.unchecked_add(4)); - if *their_px.get_unchecked(3) >= 128 { - let our_index = - really_unsafe_index(i.unchecked_add(x), j.unchecked_add(y), self.width()) - .unchecked_mul(4); - let our_px = self - .buffer - .get_unchecked_mut(our_index..our_index.unchecked_add(4)); - std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 4); - } - } - } - - self - } -} - -impl RepeatNew for Image<&[u8], 4> { - type Output = Image<Vec<u8>, 4>; - unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output { - let mut img = Image::alloc(x, y); // could probably optimize this a ton but eh - for x in 0..(x / self.width()) { - for y in 0..(y / self.height()) { - let a: &mut Image<&mut [u8], 4> = &mut img.as_mut(); - a.overlay_at(self, x * self.width(), y * self.height()); - } - } - img - } -} - -pub fn flip_v<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { - for y in 0..img.height() / 2 { - for x in 0..img.width() { - unsafe { - // SAFETY: cant overflow - let y2 = img.height().unchecked_sub(y).unchecked_sub(1); - // SAFETY: within bounds - let p2 = img.pixel(x, y2); - let p = img.pixel(x, y); - img.set_pixel(x, y2, p); - img.set_pixel(x, y, p2); - } - } - } -} - -pub fn flip_h<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { - for y in 0..img.height() { - for x in 0..img.width() / 2 { - // SAFETY: This cannot be out of bounds - unsafe { - let x2 = img.width().unchecked_sub(x).unchecked_sub(1); - let p2 = img.pixel(x2, y); - let p = img.pixel(x, y); - img.set_pixel(x2, y, p); - img.set_pixel(x, y, p2); - } - } - } -} - -pub fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { - for y in 0..img.height() / 2 { - for x in 0..img.width() { - // SAFETY: this is safe because it cannot be out of bounds - unsafe { - let p = img.pixel(x, y); - let x2 = img.width() - x - 1; - let y2 = img.height() - y - 1; - let p2 = img.pixel(x2, y2); - img.set_pixel(x, y, p2); - img.set_pixel(x2, y2, p); - } - } - } - - if img.height() % 2 != 0 { - let middle = img.height() / 2; - - for x in 0..img.width() / 2 { - // SAFETY: this is safe because it cannot be out of bounds - unsafe { - let p = img.pixel(x, middle); - let x2 = img.width() - x - 1; - - let p2 = img.pixel(x2, middle); - img.set_pixel(x, middle, p2); - img.set_pixel(x2, middle, p); - } - } - } -} - -/// # Safety -/// -/// UB if the image is not square -#[inline(never)] -pub unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { - debug_assert_eq!(img.width(), img.height()); - let size = img.width(); - flip_v(img); - for i in 0..size { - for j in i..size { - for c in 0..CHANNELS { - img.buffer.swap_unchecked( - (i * size + j) as usize * CHANNELS + c, - (j * size + i) as usize * CHANNELS + c, - ); - } - } - } -} - -/// # Safety -/// -/// UB if the image is not square -pub unsafe fn rot_270<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { - debug_assert_eq!(img.width(), img.height()); - flip_h(img); - let size = img.width(); - for i in 0..size { - for j in i..size { - for c in 0..CHANNELS { - img.buffer.swap_unchecked( - (i * size + j) as usize * CHANNELS + c, - (j * size + i) as usize * CHANNELS + c, - ); - } - } - } -} - -impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> { - unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self { - unsafe_assert!(self.width() == with.width()); - unsafe_assert!(self.height() == with.height()); - for (i, other_pixels) in with.chunked().enumerate() { - if other_pixels[3] >= 128 { - unsafe { - let own_pixels = self - .buffer - .get_unchecked_mut(i.unchecked_mul(4)..i.unchecked_mul(4).unchecked_add(4)); - std::ptr::copy_nonoverlapping( - other_pixels.as_ptr(), - own_pixels.as_mut_ptr(), - 4, - ); - } - } - } - self - } -} - -impl ImageUtils for Image<&mut [u8], 4> { - unsafe fn rotate(&mut self, times: u8) -> &mut Self { - match times { - 2 => rot_180(self), - 1 => unsafe { rot_90(self) }, - 3 => unsafe { rot_270(self) }, - _ => {} - } - self - } - - fn tint(&mut self, (r, g, b): (u8, u8, u8)) -> &mut Self { - let [tr, tg, tb] = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]; - for [r, g, b, _] in self.buffer.array_chunks_mut::<4>() { - *r = (*r as f32 * tr) as u8; - *g = (*g as f32 * tg) as u8; - *b = (*b as f32 * tb) as u8; - } - self - } - - // this function is very cold but im removing image so might as well use fir - fn scale(self, to: u32) -> Image<Vec<u8>, 4> { - let from = - fr::Image::from_slice_u8(self.width, self.height, self.buffer, fr::PixelType::U8x4) - .unwrap(); - let to = to.try_into().unwrap(); - let mut dst = fr::Image::new(to, to, fr::PixelType::U8x4); - fr::Resizer::new(fr::ResizeAlg::Nearest) - .resize(&from.view(), &mut dst.view_mut()) - .unwrap(); - Image::new(to, to, dst.into_vec()) - } - - fn shadow(&mut self) -> &mut Self { - let mut shadow: Image<Vec<u8>, 4> = - Image::new(self.width, self.height, self.buffer.to_vec()); - for [r, g, b, a] in shadow.buffer.array_chunks_mut() { - if *a < 128 { - *r /= 10; - *g /= 10; - *b /= 10; - } - } - blurslice::gaussian_blur_bytes::<4>( - &mut shadow.buffer, - self.width() as usize, - self.height() as usize, - 9.0, - ) - .unwrap(); - for ([r, g, b, a], &[from_r, from_g, from_b, from_a]) in self - .buffer - .array_chunks_mut() - .zip(shadow.buffer.array_chunks()) - { - if *a == 0 { - (*r, *g, *b, *a) = (from_r, from_g, from_b, from_a); - } - } - self - } - - #[inline] - fn flip_h(&mut self) -> &mut Self { - flip_h(self); - self - } - - #[inline(always)] - fn flip_v(&mut self) -> &mut Self { - flip_v(self); - self - } -} - -#[inline] -unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize { - // y * w + x - (y as usize) - .unchecked_mul(w as usize) - .unchecked_add(x as usize) -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Image<T, const CHANNELS: usize> { - pub buffer: T, - pub width: NonZeroU32, - pub height: NonZeroU32, -} - -impl<const CHANNELS: usize> Default for Image<&'static [u8], CHANNELS> { - fn default() -> Self { - Self { - buffer: &[0; CHANNELS], - width: NonZeroU32::new(1).unwrap(), - height: NonZeroU32::new(1).unwrap(), - } - } -} - -impl<T, const CHANNELS: usize> Image<T, CHANNELS> { - #[inline] - pub fn height(&self) -> u32 { - self.height.into() - } - - #[inline] - pub fn width(&self) -> u32 { - self.width.into() - } - - #[inline] - pub const fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self { - Image { - width, - height, - buffer, - } - } -} - -impl<const CHANNELS: usize> Image<&[u8], CHANNELS> { - #[inline] - pub const fn copy(&self) -> Self { - Self { - width: self.width, - height: self.height, - buffer: self.buffer, - } - } -} - -impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> { - /// # Safety - /// - /// - UB if x, y is out of bounds - /// - UB if buffer is too small - #[inline] - pub unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> { - debug_assert!(x < self.width(), "x out of bounds"); - debug_assert!(y < self.height(), "y out of bounds"); - let index = really_unsafe_index(x, y, self.width()).unchecked_mul(CHANNELS); - debug_assert!(self.buffer.len() > index); - index..index.unchecked_add(CHANNELS) - } - - #[inline] - pub fn chunked(&self) -> impl Iterator<Item = &[u8; CHANNELS]> { - unsafe_assert!(self.buffer.len() > CHANNELS); - unsafe_assert!(self.buffer.len() % CHANNELS == 0); - self.buffer.array_chunks::<CHANNELS>() - } - - /// Return a pixel at (x, y). - /// # Safety - /// - /// Refer to [`slice`] - #[inline] - pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] { - *(self.buffer.get_unchecked(self.slice(x, y)).as_ptr().cast()) - } -} -impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> { - /// Return a mutable reference to a pixel at (x, y). - /// # Safety - /// - /// Refer to [`slice`] - #[inline] - pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] { - let idx = self.slice(x, y); - self.buffer.get_unchecked_mut(idx) - } - - #[inline] - pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> { - self.buffer.array_chunks_mut::<CHANNELS>() - } - - #[inline] - pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) { - std::ptr::copy_nonoverlapping(px.as_ptr(), self.pixel_mut(x, y).as_mut_ptr(), CHANNELS); - } -} - -impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> { - pub fn alloc(width: u32, height: u32) -> Self { - Image { - width: width.try_into().unwrap(), - height: height.try_into().unwrap(), - buffer: vec![0; CHANNELS * width as usize * height as usize], - } - } - - pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { - Image::new(self.width, self.height, &self.buffer) - } - - pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { - Image::new(self.width, self.height, &mut self.buffer) - } -} - -impl Image<Vec<u8>, 4> { - pub fn remove_channel(&mut self) -> Image<Vec<u8>, 3> { - let mut new = vec![0; self.width() as usize * self.height() as usize * 3]; - for (&[r, g, b, _], [nr, ng, nb]) in self - .buffer - .array_chunks::<4>() - .zip(new.array_chunks_mut::<3>()) - { - (*nr, *ng, *nb) = (r, g, b); - } - Image::new(self.width, self.height, new) - } -} - -impl Image<Vec<u8>, 3> { - #[cfg(feature = "bin")] - pub fn save(&self, f: impl AsRef<std::path::Path>) { - let p = std::fs::File::create(f).unwrap(); - let w = &mut std::io::BufWriter::new(p); - let mut enc = png::Encoder::new(w, self.width(), self.height()); - enc.set_color(png::ColorType::Rgb); - enc.set_depth(png::BitDepth::Eight); - enc.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2)); - enc.set_source_chromaticities(png::SourceChromaticities::new( - (0.31270, 0.32900), - (0.64000, 0.33000), - (0.30000, 0.60000), - (0.15000, 0.06000), - )); - let mut writer = enc.write_header().unwrap(); - writer.write_image_data(&self.buffer).unwrap(); - } -} - -#[derive(Clone, Debug)] -pub enum ImageHolder<const CHANNELS: usize> { - Borrow(Image<&'static [u8], CHANNELS>), - Own(Image<Vec<u8>, CHANNELS>), -} - -impl<const CHANNELS: usize> ImageHolder<CHANNELS> { - #[must_use] - pub fn own(self) -> Image<Vec<u8>, CHANNELS> { - match self { - Self::Own(x) => x, - Self::Borrow(x) => Image::new(x.width, x.height, x.buffer.to_vec()), - } - } -} - -impl<const CHANNELS: usize> ImageHolder<CHANNELS> { - #[must_use] - #[inline] - pub fn borrow(&self) -> Image<&[u8], CHANNELS> { - match self { - Self::Own(x) => x.as_ref(), - Self::Borrow(x) => x.clone(), - } - } - - #[must_use] - #[inline] - pub fn borrow_mut(&mut self) -> Image<&mut [u8], CHANNELS> { - match self { - Self::Own(x) => Image::new(x.width, x.height, &mut x.buffer), - Self::Borrow(_) => { - *self = Self::from(std::mem::replace(self, Self::from(Image::default())).own()); - self.borrow_mut() - } - } - } -} - -impl OverlayAt<ImageHolder<4>> for ImageHolder<4> { - unsafe fn overlay_at(&mut self, with: &ImageHolder<4>, x: u32, y: u32) -> &mut Self { - self.borrow_mut().overlay_at(&with.borrow(), x, y); - self - } -} - -impl Overlay<ImageHolder<4>> for ImageHolder<4> { - unsafe fn overlay(&mut self, with: &Self) -> &mut Self { - self.borrow_mut().overlay(&with.borrow()); - self - } -} - -impl ImageUtils for ImageHolder<4> { - fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self { - self.borrow_mut().tint(color); - self - } - - unsafe fn rotate(&mut self, times: u8) -> &mut Self { - if times == 0 { - return self; - } - // borrow mut may clone, so try to avoid - self.borrow_mut().rotate(times); - self - } - - fn flip_h(&mut self) -> &mut Self { - self.borrow_mut().flip_h(); - self - } - - fn flip_v(&mut self) -> &mut Self { - self.borrow_mut().flip_v(); - self - } - - fn shadow(&mut self) -> &mut Self { - self.borrow_mut().shadow(); - self - } - - fn scale(mut self, to: u32) -> Image<Vec<u8>, 4> { - self.borrow_mut().scale(to) - } -} - -impl<const CHANNELS: usize> From<Image<&'static [u8], CHANNELS>> for ImageHolder<CHANNELS> { - fn from(value: Image<&'static [u8], CHANNELS>) -> Self { - Self::Borrow(value) - } -} - -impl<const CHANNELS: usize> From<Image<Vec<u8>, CHANNELS>> for ImageHolder<CHANNELS> { - fn from(value: Image<Vec<u8>, CHANNELS>) -> Self { - Self::Own(value) - } -} - -#[cfg(test)] -mod tests { - use super::*; - macro_rules! img { - [[$($v:literal),+] [$($v2:literal),+]] => {{ - let from: Image<Vec<u8>, 1> = Image::new( - 2.try_into().unwrap(), - 2.try_into().unwrap(), - vec![$($v,)+ $($v2,)+] - ); - from - }} - } - - #[test] - fn rem_chnl_test() { - let mut img: Image<_, 4> = Image::alloc(2, 1); - unsafe { img.set_pixel(1, 0, [255, 165, 0, 241]) }; - assert_eq!(unsafe { img.pixel(1, 0) }, [255, 165, 0, 241]); - assert_eq!(unsafe { img.pixel(0, 0) }, [0, 0, 0, 0]); - let img = img.remove_channel(); - assert_eq!(unsafe { img.pixel(1, 0) }, [255, 165, 0]); - } - - #[test] - fn rotate_90() { - let mut from = img![ - [00, 01] - [02, 10] - ]; - unsafe { rot_90(&mut from.as_mut()) }; - assert_eq!( - from, - img![ - [02, 00] - [10, 01] - ] - ); - } - - #[test] - fn rotate_180() { - let mut from = img![ - [00, 01] - [02, 10] - ]; - rot_180(&mut from.as_mut()); - assert_eq!( - from, - img![ - [10, 02] - [01, 00] - ] - ); - } - - #[test] - fn rotate_270() { - let mut from = img![ - [00, 01] - [20, 10] - ]; - unsafe { rot_270(&mut from.as_mut()) }; - assert_eq!( - from, - img![ - [01, 10] - [00, 20] - ] - ); - } - - #[test] - fn flip_vertical() { - let mut from = img![ - [90, 01] - [21, 42] - ]; - flip_v(&mut from.as_mut()); - assert_eq!( - from, - img![ - [21, 42] - [90, 01] - ] - ) - } - #[test] - fn flip_horizontal() { - let mut from = img![ - [90, 01] - [21, 42] - ]; - flip_h(&mut from.as_mut()); - assert_eq!( - from, - img![ - [01, 90] - [42, 21] - ] - ) - } - - #[test] - fn scale() { - let mut from = Image::alloc(6, 6); - unsafe { from.set_pixel(3, 3, [255, 255, 255, 255]) }; - let from = from.as_mut().scale(12); - assert_eq!(unsafe { from.pixel(6, 6) }, [255, 255, 255, 255]); - } -} - -pub fn blend(bg: &mut [u8; 4], fg: [u8; 4]) { - if fg[3] == 0 { - return; - } - if fg[3] == 255 { - *bg = fg; - return; - } - let bg_a = bg[3] as f32 / 255.0; - let fg_a = fg[3] as f32 / 255.0; - let a = bg_a + fg_a - bg_a * fg_a; - if a == 0.0 { - return; - }; - *bg = [ - (255.0 - * ((((fg[0] as f32 / 255.0) * fg_a) + ((bg[0] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) - / a)) as u8, - (255.0 - * ((((fg[1] as f32 / 255.0) * fg_a) + ((bg[1] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) - / a)) as u8, - (255.0 - * ((((fg[2] as f32 / 255.0) * fg_a) + ((bg[2] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) - / a)) as u8, - (255.0 * a) as u8, - ] -} diff --git a/src/utils/image/affine.rs b/src/utils/image/affine.rs new file mode 100644 index 0000000..04a6180 --- /dev/null +++ b/src/utils/image/affine.rs @@ -0,0 +1,194 @@ +use super::Image; + +/// Rotate a image 180 degrees clockwise. +pub fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + for y in 0..img.height() / 2 { + for x in 0..img.width() { + // SAFETY: this is safe because it cannot be out of bounds + unsafe { + let p = img.pixel(x, y); + let x2 = img.width() - x - 1; + let y2 = img.height() - y - 1; + let p2 = img.pixel(x2, y2); + img.set_pixel(x, y, p2); + img.set_pixel(x2, y2, p); + } + } + } + + if img.height() % 2 != 0 { + let middle = img.height() / 2; + + for x in 0..img.width() / 2 { + // SAFETY: this is safe because it cannot be out of bounds + unsafe { + let p = img.pixel(x, middle); + let x2 = img.width() - x - 1; + + let p2 = img.pixel(x2, middle); + img.set_pixel(x, middle, p2); + img.set_pixel(x2, middle, p); + } + } + } +} + +/// # Safety +/// +/// UB if supplied image rectangular +unsafe fn transpose<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + debug_assert_eq!(img.width(), img.height()); + let size = img.width(); + for i in 0..size { + for j in i..size { + for c in 0..CHANNELS { + img.buffer.swap_unchecked( + (i * size + j) as usize * CHANNELS + c, + (j * size + i) as usize * CHANNELS + c, + ); + } + } + } +} + +/// Rotate a image 90 degrees clockwise. +/// This is done by first [flipping vertically](flip_v), then [transposing](transpose) the image, to save allocations. +/// +/// # Safety +/// +/// UB if the image is not square +#[inline] +pub unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + flip_v(img); + transpose(img); +} + +/// Rotate a image 270 degrees clockwise, or 90 degrees anti clockwise. +/// [horizontal flip](flip_h), then [transpose]. +/// +/// # Safety +/// +/// UB if the image is not square +#[inline] +pub unsafe fn rot_270<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + flip_h(img); + transpose(img); +} + +/// Flip a image vertically. +pub fn flip_v<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + for y in 0..img.height() / 2 { + for x in 0..img.width() { + // SAFETY: cant overflow + unsafe { + let y2 = img.height().unchecked_sub(y).unchecked_sub(1); + // SAFETY: within bounds + let p2 = img.pixel(x, y2); + let p = img.pixel(x, y); + img.set_pixel(x, y2, p); + img.set_pixel(x, y, p2); + } + } + } +} + +/// Flip a image horizontally. +pub fn flip_h<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) { + for y in 0..img.height() { + for x in 0..img.width() / 2 { + // SAFETY: This cannot be out of bounds + unsafe { + let x2 = img.width().unchecked_sub(x).unchecked_sub(1); + let p2 = img.pixel(x2, y); + let p = img.pixel(x, y); + img.set_pixel(x2, y, p); + img.set_pixel(x, y, p2); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::utils::image::img; + + #[test] + fn rotate_90() { + let mut from = img![ + [00, 01] + [02, 10] + ]; + unsafe { rot_90(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [02, 00] + [10, 01] + ] + ); + } + + #[test] + fn rotate_180() { + let mut from = img![ + [00, 01] + [02, 10] + ]; + rot_180(&mut from.as_mut()); + assert_eq!( + from, + img![ + [10, 02] + [01, 00] + ] + ); + } + + #[test] + fn rotate_270() { + let mut from = img![ + [00, 01] + [20, 10] + ]; + unsafe { rot_270(&mut from.as_mut()) }; + assert_eq!( + from, + img![ + [01, 10] + [00, 20] + ] + ); + } + + #[test] + fn flip_vertical() { + let mut from = img![ + [90, 01] + [21, 42] + ]; + flip_v(&mut from.as_mut()); + assert_eq!( + from, + img![ + [21, 42] + [90, 01] + ] + ) + } + #[test] + fn flip_horizontal() { + let mut from = img![ + [90, 01] + [21, 42] + ]; + flip_h(&mut from.as_mut()); + assert_eq!( + from, + img![ + [01, 90] + [42, 21] + ] + ) + } +} diff --git a/src/utils/image/holder.rs b/src/utils/image/holder.rs new file mode 100644 index 0000000..02c9240 --- /dev/null +++ b/src/utils/image/holder.rs @@ -0,0 +1,100 @@ +use super::{Image, ImageUtils, Overlay, OverlayAt}; +#[derive(Clone, Debug)] +pub enum ImageHolder<const CHANNELS: usize> { + Borrow(Image<&'static [u8], CHANNELS>), + Own(Image<Vec<u8>, CHANNELS>), +} + +impl<const CHANNELS: usize> ImageHolder<CHANNELS> { + #[must_use] + pub fn own(self) -> Image<Vec<u8>, CHANNELS> { + match self { + Self::Own(x) => x, + Self::Borrow(x) => Image::new(x.width, x.height, x.buffer.to_vec()), + } + } +} + +impl<const CHANNELS: usize> ImageHolder<CHANNELS> { + #[must_use] + #[inline] + pub fn borrow(&self) -> Image<&[u8], CHANNELS> { + match self { + Self::Own(x) => x.as_ref(), + Self::Borrow(x) => x.clone(), + } + } + + #[must_use] + #[inline] + pub fn borrow_mut(&mut self) -> Image<&mut [u8], CHANNELS> { + match self { + Self::Own(x) => Image::new(x.width, x.height, &mut x.buffer), + Self::Borrow(_) => { + *self = Self::from(std::mem::replace(self, Self::from(Image::default())).own()); + self.borrow_mut() + } + } + } +} + +impl OverlayAt<ImageHolder<4>> for ImageHolder<4> { + unsafe fn overlay_at(&mut self, with: &ImageHolder<4>, x: u32, y: u32) -> &mut Self { + self.borrow_mut().overlay_at(&with.borrow(), x, y); + self + } +} + +impl Overlay<ImageHolder<4>> for ImageHolder<4> { + unsafe fn overlay(&mut self, with: &Self) -> &mut Self { + self.borrow_mut().overlay(&with.borrow()); + self + } +} + +impl ImageUtils for ImageHolder<4> { + fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self { + self.borrow_mut().tint(color); + self + } + + unsafe fn rotate(&mut self, times: u8) -> &mut Self { + if times == 0 { + return self; + } + // borrow mut may clone, so try to avoid + self.borrow_mut().rotate(times); + self + } + + fn flip_h(&mut self) -> &mut Self { + self.borrow_mut().flip_h(); + self + } + + fn flip_v(&mut self) -> &mut Self { + self.borrow_mut().flip_v(); + self + } + + fn shadow(&mut self) -> &mut Self { + self.borrow_mut().shadow(); + self + } + + fn scale(mut self, to: u32) -> Image<Vec<u8>, 4> { + self.borrow_mut().scale(to) + } +} + +impl<const CHANNELS: usize> From<Image<&'static [u8], CHANNELS>> for ImageHolder<CHANNELS> { + fn from(value: Image<&'static [u8], CHANNELS>) -> Self { + Self::Borrow(value) + } +} + +impl<const CHANNELS: usize> From<Image<Vec<u8>, CHANNELS>> for ImageHolder<CHANNELS> { + fn from(value: Image<Vec<u8>, CHANNELS>) -> Self { + Self::Own(value) + } +} diff --git a/src/utils/image/mod.rs b/src/utils/image/mod.rs new file mode 100644 index 0000000..bb89e09 --- /dev/null +++ b/src/utils/image/mod.rs @@ -0,0 +1,404 @@ +use fast_image_resize as fr; +use std::{num::NonZeroU32, slice::SliceIndex}; + +mod affine; +use affine::*; +mod holder; +mod overlay; +pub use holder::*; + +pub trait OverlayAt<W> { + /// Overlay with => self at coordinates x, y, without blending + /// # Safety + /// + /// UB if x, y is out of bounds + unsafe fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self; +} + +pub trait Overlay<W> { + /// Overlay with => self (does not blend) + /// # Safety + /// + /// UB if a.width != b.width || a.height != b.height + unsafe fn overlay(&mut self, with: &W) -> &mut Self; +} + +pub trait RepeatNew { + type Output; + /// Repeat self till it fills a new image of size x, y + /// # Safety + /// + /// UB if self's width is not a multiple of x, or self's height is not a multiple of y + unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output; +} + +pub trait ImageUtils { + /// Tint this image with the color + fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self; + /// rotate (squares only) + /// # Safety + /// + /// UB if image is not square + unsafe fn rotate(&mut self, times: u8) -> &mut Self; + /// flip along the horizontal axis + fn flip_h(&mut self) -> &mut Self; + /// flip along the vertical axis + fn flip_v(&mut self) -> &mut Self; + /// shadow + fn shadow(&mut self) -> &mut Self; + /// scale a image + fn scale(self, to: u32) -> Image<Vec<u8>, 4>; +} + +macro_rules! unsafe_assert { + ($cond:expr) => {{ + if !$cond { + #[cfg(debug_assertions)] + panic!("assertion failed: {} returned false", stringify!($cond)); + #[cfg(not(debug_assertions))] + unsafe { + std::hint::unreachable_unchecked() + }; + } + }}; +} +pub(self) use unsafe_assert; + +impl RepeatNew for Image<&[u8], 4> { + type Output = Image<Vec<u8>, 4>; + unsafe fn repeated(&self, x: u32, y: u32) -> Self::Output { + let mut img = Image::alloc(x, y); // could probably optimize this a ton but eh + for x in 0..(x / self.width()) { + for y in 0..(y / self.height()) { + let a: &mut Image<&mut [u8], 4> = &mut img.as_mut(); + a.overlay_at(self, x * self.width(), y * self.height()); + } + } + img + } +} + +impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> { + unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self { + unsafe_assert!(self.width() == with.width()); + unsafe_assert!(self.height() == with.height()); + for (i, other_pixels) in with.chunked().enumerate() { + if other_pixels[3] >= 128 { + unsafe { + let own_pixels = self + .buffer + .get_unchecked_mut(i.unchecked_mul(4)..i.unchecked_mul(4).unchecked_add(4)); + std::ptr::copy_nonoverlapping( + other_pixels.as_ptr(), + own_pixels.as_mut_ptr(), + 4, + ); + } + } + } + self + } +} + +impl ImageUtils for Image<&mut [u8], 4> { + unsafe fn rotate(&mut self, times: u8) -> &mut Self { + match times { + 2 => rot_180(self), + 1 => unsafe { rot_90(self) }, + 3 => unsafe { rot_270(self) }, + _ => {} + } + self + } + + fn tint(&mut self, (r, g, b): (u8, u8, u8)) -> &mut Self { + let [tr, tg, tb] = [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]; + for [r, g, b, _] in self.buffer.array_chunks_mut::<4>() { + *r = (*r as f32 * tr) as u8; + *g = (*g as f32 * tg) as u8; + *b = (*b as f32 * tb) as u8; + } + self + } + + // this function is very cold but im removing image so might as well use fir + fn scale(self, to: u32) -> Image<Vec<u8>, 4> { + let from = + fr::Image::from_slice_u8(self.width, self.height, self.buffer, fr::PixelType::U8x4) + .unwrap(); + let to = to.try_into().unwrap(); + let mut dst = fr::Image::new(to, to, fr::PixelType::U8x4); + fr::Resizer::new(fr::ResizeAlg::Nearest) + .resize(&from.view(), &mut dst.view_mut()) + .unwrap(); + Image::new(to, to, dst.into_vec()) + } + + fn shadow(&mut self) -> &mut Self { + let mut shadow: Image<Vec<u8>, 4> = + Image::new(self.width, self.height, self.buffer.to_vec()); + for [r, g, b, a] in shadow.buffer.array_chunks_mut() { + if *a < 128 { + *r /= 10; + *g /= 10; + *b /= 10; + } + } + blurslice::gaussian_blur_bytes::<4>( + &mut shadow.buffer, + self.width() as usize, + self.height() as usize, + 9.0, + ) + .unwrap(); + for ([r, g, b, a], &[from_r, from_g, from_b, from_a]) in self + .buffer + .array_chunks_mut() + .zip(shadow.buffer.array_chunks()) + { + if *a == 0 { + (*r, *g, *b, *a) = (from_r, from_g, from_b, from_a); + } + } + self + } + + #[inline] + fn flip_h(&mut self) -> &mut Self { + flip_h(self); + self + } + + #[inline(always)] + fn flip_v(&mut self) -> &mut Self { + flip_v(self); + self + } +} + +#[inline] +unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize { + // y * w + x + (y as usize) + .unchecked_mul(w as usize) + .unchecked_add(x as usize) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Image<T, const CHANNELS: usize> { + pub buffer: T, + pub width: NonZeroU32, + pub height: NonZeroU32, +} + +impl<const CHANNELS: usize> Default for Image<&'static [u8], CHANNELS> { + fn default() -> Self { + Self { + buffer: &[0; CHANNELS], + width: NonZeroU32::new(1).unwrap(), + height: NonZeroU32::new(1).unwrap(), + } + } +} + +impl<T, const CHANNELS: usize> Image<T, CHANNELS> { + #[inline] + pub fn height(&self) -> u32 { + self.height.into() + } + + #[inline] + pub fn width(&self) -> u32 { + self.width.into() + } + + #[inline] + pub const fn new(width: NonZeroU32, height: NonZeroU32, buffer: T) -> Self { + Image { + width, + height, + buffer, + } + } +} + +impl<const CHANNELS: usize> Image<&[u8], CHANNELS> { + #[inline] + pub const fn copy(&self) -> Self { + Self { + width: self.width, + height: self.height, + buffer: self.buffer, + } + } +} + +impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> { + /// # Safety + /// + /// - UB if x, y is out of bounds + /// - UB if buffer is too small + #[inline] + pub unsafe fn slice(&self, x: u32, y: u32) -> impl SliceIndex<[u8], Output = [u8]> { + debug_assert!(x < self.width(), "x out of bounds"); + debug_assert!(y < self.height(), "y out of bounds"); + let index = really_unsafe_index(x, y, self.width()).unchecked_mul(CHANNELS); + debug_assert!(self.buffer.len() > index); + index..index.unchecked_add(CHANNELS) + } + + #[inline] + pub fn chunked(&self) -> impl Iterator<Item = &[u8; CHANNELS]> { + unsafe_assert!(self.buffer.len() > CHANNELS); + unsafe_assert!(self.buffer.len() % CHANNELS == 0); + self.buffer.array_chunks::<CHANNELS>() + } + + /// Return a pixel at (x, y). + /// # Safety + /// + /// Refer to [`slice`] + #[inline] + pub unsafe fn pixel(&self, x: u32, y: u32) -> [u8; CHANNELS] { + *(self.buffer.get_unchecked(self.slice(x, y)).as_ptr().cast()) + } +} + +impl<T: std::ops::DerefMut<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS> { + /// Return a mutable reference to a pixel at (x, y). + /// # Safety + /// + /// Refer to [`slice`] + #[inline] + pub unsafe fn pixel_mut(&mut self, x: u32, y: u32) -> &mut [u8] { + let idx = self.slice(x, y); + self.buffer.get_unchecked_mut(idx) + } + + #[inline] + pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> { + self.buffer.array_chunks_mut::<CHANNELS>() + } + + #[inline] + pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) { + std::ptr::copy_nonoverlapping(px.as_ptr(), self.pixel_mut(x, y).as_mut_ptr(), CHANNELS); + } +} + +impl<const CHANNELS: usize> Image<Vec<u8>, CHANNELS> { + pub fn alloc(width: u32, height: u32) -> Self { + Image { + width: width.try_into().unwrap(), + height: height.try_into().unwrap(), + buffer: vec![0; CHANNELS * width as usize * height as usize], + } + } + + pub fn as_ref(&self) -> Image<&[u8], CHANNELS> { + Image::new(self.width, self.height, &self.buffer) + } + + pub fn as_mut(&mut self) -> Image<&mut [u8], CHANNELS> { + Image::new(self.width, self.height, &mut self.buffer) + } +} + +impl Image<Vec<u8>, 4> { + pub fn remove_channel(&mut self) -> Image<Vec<u8>, 3> { + let mut new = vec![0; self.width() as usize * self.height() as usize * 3]; + for (&[r, g, b, _], [nr, ng, nb]) in self + .buffer + .array_chunks::<4>() + .zip(new.array_chunks_mut::<3>()) + { + (*nr, *ng, *nb) = (r, g, b); + } + Image::new(self.width, self.height, new) + } +} + +impl Image<Vec<u8>, 3> { + #[cfg(feature = "bin")] + pub fn save(&self, f: impl AsRef<std::path::Path>) { + let p = std::fs::File::create(f).unwrap(); + let w = &mut std::io::BufWriter::new(p); + let mut enc = png::Encoder::new(w, self.width(), self.height()); + enc.set_color(png::ColorType::Rgb); + enc.set_depth(png::BitDepth::Eight); + enc.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2)); + enc.set_source_chromaticities(png::SourceChromaticities::new( + (0.31270, 0.32900), + (0.64000, 0.33000), + (0.30000, 0.60000), + (0.15000, 0.06000), + )); + let mut writer = enc.write_header().unwrap(); + writer.write_image_data(&self.buffer).unwrap(); + } +} + +#[cfg(test)] +macro_rules! img { + [[$($v:literal),+] [$($v2:literal),+]] => {{ + let from: Image<Vec<u8>, 1> = Image::new( + 2.try_into().unwrap(), + 2.try_into().unwrap(), + vec![$($v,)+ $($v2,)+] + ); + from + }} +} +#[cfg(test)] +pub(self) use img; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn rem_chnl_test() { + let mut img: Image<_, 4> = Image::alloc(2, 1); + unsafe { img.set_pixel(1, 0, [255, 165, 0, 241]) }; + assert_eq!(unsafe { img.pixel(1, 0) }, [255, 165, 0, 241]); + assert_eq!(unsafe { img.pixel(0, 0) }, [0, 0, 0, 0]); + let img = img.remove_channel(); + assert_eq!(unsafe { img.pixel(1, 0) }, [255, 165, 0]); + } + + #[test] + fn scale() { + let mut from = Image::alloc(6, 6); + unsafe { from.set_pixel(3, 3, [255, 255, 255, 255]) }; + let from = from.as_mut().scale(12); + assert_eq!(unsafe { from.pixel(6, 6) }, [255, 255, 255, 255]); + } +} + +pub fn blend(bg: &mut [u8; 4], fg: [u8; 4]) { + if fg[3] == 0 { + return; + } + if fg[3] == 255 { + *bg = fg; + return; + } + let bg_a = bg[3] as f32 / 255.0; + let fg_a = fg[3] as f32 / 255.0; + let a = bg_a + fg_a - bg_a * fg_a; + if a == 0.0 { + return; + }; + *bg = [ + (255.0 + * ((((fg[0] as f32 / 255.0) * fg_a) + ((bg[0] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) + / a)) as u8, + (255.0 + * ((((fg[1] as f32 / 255.0) * fg_a) + ((bg[1] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) + / a)) as u8, + (255.0 + * ((((fg[2] as f32 / 255.0) * fg_a) + ((bg[2] as f32 / 255.0) * bg_a) * (1.0 - fg_a)) + / a)) as u8, + (255.0 * a) as u8, + ] +} diff --git a/src/utils/image/overlay.rs b/src/utils/image/overlay.rs new file mode 100644 index 0000000..aee251c --- /dev/null +++ b/src/utils/image/overlay.rs @@ -0,0 +1,110 @@ +use super::{really_unsafe_index, unsafe_assert, Image, Overlay, OverlayAt}; +use std::simd::SimdInt; +use std::simd::SimdPartialOrd; +use std::simd::{simd_swizzle, Simd}; + +#[inline] +pub unsafe fn blit(rgb: &mut [u8], rgba: &[u8]) { + const LAST4: Simd<u8, 16> = Simd::from_array([ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, + ]); + + let mut srci = 0; + let mut dsti = 0; + while dsti + 16 <= rgb.len() { + let old: Simd<u8, 16> = Simd::from_slice(rgb.get_unchecked(dsti..dsti + 16)); + let new: Simd<u8, 16> = Simd::from_slice(rgba.get_unchecked(srci..srci + 16)); + + let threshold = new.simd_ge(Simd::splat(128)).to_int().cast::<u8>(); + let mut mask = simd_swizzle!( + threshold, + [3, 3, 3, 7, 7, 7, 11, 11, 11, 15, 15, 15, 0, 0, 0, 0] + ); + mask &= LAST4; + + let new_rgb = simd_swizzle!(new, [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0, 0, 0, 0]); + let blended = (new_rgb & mask) | (old & !mask); + blended.copy_to_slice(rgb.get_unchecked_mut(dsti..dsti + 16)); + + srci += 16; + dsti += 12; + } + + while dsti + 3 <= rgb.len() { + if *rgba.get_unchecked(srci + 3) >= 128 { + std::ptr::copy_nonoverlapping( + rgba.get_unchecked(srci..srci + 3).as_ptr(), + rgb.get_unchecked_mut(dsti..dsti + 3).as_mut_ptr(), + 3, + ); + } + + srci += 4; + dsti += 3; + } +} + +impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 3> { + unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { + unsafe_assert!(x + with.width() <= self.width()); + unsafe_assert!(y + with.height() <= self.height()); + for j in 0..with.height() { + let i_x = j as usize * with.width() as usize * 4 + ..(j as usize + 1) * with.width() as usize * 4; + let o_x = ((j as usize + y as usize) * self.width() as usize + x as usize) * 3 + ..((j as usize + y as usize) * self.width() as usize + + x as usize + + with.width() as usize) + * 3; + blit( + self.buffer.get_unchecked_mut(o_x), + with.buffer.get_unchecked(i_x), + ) + } + self + } +} + +impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> { + unsafe fn overlay(&mut self, with: &Image<&[u8], 4>) -> &mut Self { + unsafe_assert!(self.width() == with.width()); + unsafe_assert!(self.height() == with.height()); + for (i, chunk) in with + .buffer + .chunks_exact(with.width() as usize * 4) + .enumerate() + { + blit( + self.buffer.get_unchecked_mut( + i * with.width() as usize * 3..(i + 1) * with.width() as usize * 3, + ), + chunk, + ); + } + self + } +} + +impl OverlayAt<Image<&[u8], 4>> for Image<&mut [u8], 4> { + unsafe fn overlay_at(&mut self, with: &Image<&[u8], 4>, x: u32, y: u32) -> &mut Self { + for j in 0..with.height() { + for i in 0..with.width() { + let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4); + let their_px = with + .buffer + .get_unchecked(with_index..with_index.unchecked_add(4)); + if *their_px.get_unchecked(3) >= 128 { + let our_index = + really_unsafe_index(i.unchecked_add(x), j.unchecked_add(y), self.width()) + .unchecked_mul(4); + let our_px = self + .buffer + .get_unchecked_mut(our_index..our_index.unchecked_add(4)); + std::ptr::copy_nonoverlapping(their_px.as_ptr(), our_px.as_mut_ptr(), 4); + } + } + } + + self + } +} |