mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/utils/image/mod.rs')
| -rw-r--r-- | src/utils/image/mod.rs | 259 |
1 files changed, 5 insertions, 254 deletions
diff --git a/src/utils/image/mod.rs b/src/utils/image/mod.rs index 02c1a43..9d853b6 100644 --- a/src/utils/image/mod.rs +++ b/src/utils/image/mod.rs @@ -1,21 +1,8 @@ use fast_image_resize as fr; -use std::{num::NonZeroU32, slice::SliceIndex}; +pub use fimg::*; -mod affine; -use affine::*; mod holder; -mod overlay; -pub use holder::*; -pub use overlay::*; - -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 use holder::ImageHolder; pub trait ImageUtils { /// Tint this image with the color @@ -25,51 +12,18 @@ pub trait ImageUtils { /// /// 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! assert_unchecked { - ($cond:expr) => {{ - if !$cond { - #[cfg(debug_assertions)] - let _ = ::core::ptr::NonNull::<()>::dangling().as_ref(); // force unsafe wrapping block - #[cfg(debug_assertions)] - panic!("assertion failed: {} returned false", stringify!($cond)); - #[cfg(not(debug_assertions))] - std::hint::unreachable_unchecked() - } - }}; -} -use assert_unchecked; - -impl RepeatNew for Image<&[u8], 3> { - type Output = Image<Vec<u8>, 3>; - 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], 3> = &mut img.as_mut(); - // SAFETY: caller upholds - unsafe { a.overlay_at(self, x * self.width(), y * self.height()) }; - } - } - img - } -} - 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) }, + 2 => self.rot_180(), + 1 => unsafe { self.rot_90() }, + 3 => unsafe { self.rot_270() }, _ => {} } self @@ -126,209 +80,6 @@ impl ImageUtils for Image<&mut [u8], 4> { } 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 - let tmp = unsafe { (y as usize).unchecked_mul(w as usize) }; - unsafe { tmp.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 = unsafe { really_unsafe_index(x, y, self.width()) }; - let index = unsafe { index.unchecked_mul(CHANNELS) }; - debug_assert!(self.buffer.len() > index); - index..unsafe { index.unchecked_add(CHANNELS) } - } - - #[inline] - pub fn chunked(&self) -> impl Iterator<Item = &[u8; CHANNELS]> { - // SAFETY: 0 sized images illegal - unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; - // SAFETY: no half pixels! - unsafe { assert_unchecked!(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] { - let idx = unsafe { self.slice(x, y) }; - let ptr = unsafe { self.buffer.get_unchecked(idx).as_ptr().cast() }; - unsafe { *ptr } - } -} - -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 = unsafe { self.slice(x, y) }; - unsafe { self.buffer.get_unchecked_mut(idx) } - } - - #[inline] - pub fn chunked_mut(&mut self) -> impl Iterator<Item = &mut [u8; CHANNELS]> { - // SAFETY: 0 sized images are not allowed - unsafe { assert_unchecked!(self.buffer.len() > CHANNELS) }; - // SAFETY: buffer cannot have half pixels - unsafe { assert_unchecked!(self.buffer.len() % CHANNELS == 0) }; - self.buffer.array_chunks_mut::<CHANNELS>() - } - - /// Set the pixel at x, y - /// - /// # Safety - /// - /// UB if x, y is out of bounds. - #[inline] - pub unsafe fn set_pixel(&mut self, x: u32, y: u32, px: [u8; CHANNELS]) { - // SAFETY: Caller says that x, y is in bounds - let out = unsafe { self.pixel_mut(x, y) }; - // SAFETY: px must be CHANNELS long - unsafe { std::ptr::copy_nonoverlapping(px.as_ptr(), out.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>, 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 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]) { |