mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/utils/image.rs')
-rw-r--r--src/utils/image.rs625
1 files changed, 539 insertions, 86 deletions
diff --git a/src/utils/image.rs b/src/utils/image.rs
index 262f1e1..70f6261 100644
--- a/src/utils/image.rs
+++ b/src/utils/image.rs
@@ -1,21 +1,24 @@
-use image::*;
+use fast_image_resize as fr;
+use std::{num::NonZeroU32, slice::SliceIndex};
pub trait Overlay<W> {
/// Overlay with => self at coordinates x, y, without blending
- fn overlay_at(&mut self, with: &W, x: u32, y: u32) -> &mut Self;
+ fn overlay_at(&mut self, with: W, x: u32, y: u32) -> &mut Self;
}
pub trait RepeatNew {
- /// Repeat with over self
- fn repeated(with: &Self, x: u32, y: u32) -> Self;
+ type Output;
+ /// Repeat self till it fills x, y
+ fn repeated(self, x: u32, y: u32) -> Self::Output;
}
pub trait ImageUtils {
+ type With<'a>;
/// Tint this image with the color
- fn tint(&mut self, color: Rgb<u8>) -> &mut Self;
+ fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self;
/// Overlay with => self (does not blend)
- fn overlay(&mut self, with: &Self) -> &mut Self;
- /// rotate
+ fn overlay(&mut self, with: Self::With<'_>) -> &mut Self;
+ /// rotate (squares only)
fn rotate(&mut self, times: u8) -> &mut Self;
/// flip along the horizontal axis
fn flip_h(&mut self) -> &mut Self;
@@ -23,10 +26,8 @@ pub trait ImageUtils {
fn flip_v(&mut self) -> &mut Self;
/// shadow
fn shadow(&mut self) -> &mut Self;
- /// silhouette
- fn silhouette(&mut self) -> &mut Self;
/// scale a image
- fn scale(&self, to: u32) -> Self;
+ fn scale(self, to: u32) -> Image<Vec<u8>, 4>;
}
macro_rules! unsafe_assert {
@@ -37,19 +38,19 @@ macro_rules! unsafe_assert {
}};
}
-impl Overlay<RgbImage> for RgbImage {
- fn overlay_at(&mut self, with: &RgbImage, x: u32, y: u32) -> &mut Self {
- unsafe_assert!(with.height() != 0);
- unsafe_assert!(with.width() != 0);
+impl Overlay<Image<&[u8], 3>> for Image<&mut [u8], 3> {
+ 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() {
unsafe {
- let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(3);
- let their_px = with.get_unchecked(with_index..with_index.unchecked_add(3));
+ 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.get_unchecked_mut(our_index..our_index.unchecked_add(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);
}
}
@@ -58,25 +59,26 @@ impl Overlay<RgbImage> for RgbImage {
}
}
-impl Overlay<RgbaImage> for RgbImage {
- fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self {
- // TODO NonZeroU32
- unsafe_assert!(with.height() != 0);
- unsafe_assert!(with.width() != 0);
+impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> {
+ 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() {
unsafe {
let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4);
// solidity
- if with.get_unchecked(with_index.unchecked_add(3)) > &128 {
- let their_px = with.get_unchecked(with_index..with_index.unchecked_add(3));
+ if *with.buffer.get_unchecked(with_index.unchecked_add(3)) > 128 {
+ let their_px = with
+ .buffer
+ .get_unchecked(with_index..with_index.unchecked_add(3));
let our_index = really_unsafe_index(
i.unchecked_add(x),
j.unchecked_add(y),
self.width(),
)
.unchecked_mul(3);
- let our_px = self.get_unchecked_mut(our_index..our_index.unchecked_add(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);
}
}
@@ -86,15 +88,15 @@ impl Overlay<RgbaImage> for RgbImage {
}
}
-impl Overlay<RgbaImage> for RgbaImage {
- fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self {
- unsafe_assert!(with.height() != 0);
- unsafe_assert!(with.width() != 0);
+impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 4> {
+ 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() {
unsafe {
let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4);
- let their_px = with.get_unchecked(with_index..with_index.unchecked_add(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),
@@ -102,7 +104,9 @@ impl Overlay<RgbaImage> for RgbaImage {
self.width(),
)
.unchecked_mul(4);
- let our_px = self.get_unchecked_mut(our_index..our_index.unchecked_add(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);
}
}
@@ -112,54 +116,136 @@ impl Overlay<RgbaImage> for RgbaImage {
}
}
-impl RepeatNew for RgbImage {
- fn repeated(with: &Self, x: u32, y: u32) -> Self {
- let mut img = RgbImage::new(x, y); // could probably optimize this a ton but eh
- for x in 0..(x / with.width()) {
- for y in 0..(y / with.height()) {
- img.overlay_at(with, x * with.width(), y * with.height());
+impl RepeatNew for Image<&[u8], 4> {
+ type Output = Image<Vec<u8>, 4>;
+ 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.copy(), x * self.width(), y * self.height());
}
}
img
}
}
-impl ImageUtils for RgbaImage {
+unsafe 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() {
+ let y2 = img.height() - y - 1;
+ let p2 = img.pixel(x, y2);
+ let p = img.pixel(x, y);
+ img.set_pixel(x, y2, p);
+ img.set_pixel(x, y, p2);
+ }
+ }
+}
+
+unsafe 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 {
+ let x2 = img.width() - x - 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);
+ }
+ }
+}
+
+unsafe 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() {
+ 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 {
+ 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);
+ }
+ }
+}
+
+/// only works with squares!
+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(
+ (i * size + j) as usize * CHANNELS + c,
+ (j * size + i) as usize * CHANNELS + c,
+ );
+ }
+ }
+ }
+}
+
+/// only works with squares!
+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(
+ (i * size + j) as usize * CHANNELS + c,
+ (j * size + i) as usize * CHANNELS + c,
+ );
+ }
+ }
+ }
+}
+impl ImageUtils for Image<&mut [u8], 4> {
fn rotate(&mut self, times: u8) -> &mut Self {
- use image::imageops::{rotate180, rotate270, rotate90};
- match times {
- 2 => *self = rotate180(self),
- 1 => *self = rotate90(self),
- 3 => *self = rotate270(self),
- _ => {}
+ unsafe {
+ match times {
+ 2 => rot_180(self),
+ 1 => rot_90(self),
+ 3 => rot_270(self),
+ _ => {}
+ }
}
self
}
- fn tint(&mut self, color: Rgb<u8>) -> &mut Self {
- let [tr, tg, tb] = [
- color[0] as f32 / 255.0,
- color[1] as f32 / 255.0,
- color[2] as f32 / 255.0,
- ];
- for Rgba([r, g, b, _]) in self.pixels_mut() {
+ 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
}
-
- fn overlay(&mut self, with: &RgbaImage) -> &mut Self {
+ type With<'a> = Image<&'a [u8], 4>;
+ fn overlay(&mut self, with: Image<&[u8], 4>) -> &mut Self {
debug_assert_eq!(self.width(), with.width());
debug_assert_eq!(self.height(), with.height());
- if self.len() % 4 != 0 || with.len() % 4 != 0 || with.height() == 0 || with.width() == 0 {
- unsafe { std::hint::unreachable_unchecked() };
- }
- for (i, other_pixels) in with.array_chunks::<4>().enumerate() {
+ unsafe_assert!(self.buffer.len() % 4 == 0);
+ unsafe_assert!(with.buffer.len() % 4 == 0);
+ for (i, other_pixels) in with.buffer.array_chunks::<4>().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(),
@@ -172,56 +258,60 @@ impl ImageUtils for RgbaImage {
self
}
- fn scale(&self, to: u32) -> Self {
- imageops::resize(self, to, to, imageops::Nearest)
+ // 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 mut dst = fr::Image::new(
+ to.try_into().unwrap(),
+ to.try_into().unwrap(),
+ fr::PixelType::U8x4,
+ );
+ fr::Resizer::new(fr::ResizeAlg::Nearest)
+ .resize(&from.view(), &mut dst.view_mut())
+ .unwrap();
+ Image::new(self.width, self.height, dst.into_vec())
}
- fn silhouette(&mut self) -> &mut Self {
- for pixel in self.pixels_mut() {
- if pixel[3] < 128 {
- pixel[2] /= 10;
- pixel[1] /= 10;
- pixel[0] /= 10;
+ 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;
}
}
- self
- }
-
- fn shadow(&mut self) -> &mut Self {
- let mut shadow = self.clone();
- shadow.silhouette();
- let samples = shadow.as_flat_samples_mut();
blurslice::gaussian_blur_bytes::<4>(
- samples.samples,
+ &mut shadow.buffer,
self.width() as usize,
self.height() as usize,
9.0,
)
.unwrap();
- for x in 0..shadow.width() {
- for y in 0..shadow.height() {
- let Rgba([r, g, b, a]) = self.get_pixel_mut(x, y);
- if *a == 0 {
- let p = unsafe { shadow.unsafe_get_pixel(x, y) };
- *r = p[0];
- *g = p[0];
- *b = p[0];
- *a = p[1];
- }
+ 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(always)]
+ #[inline]
fn flip_h(&mut self) -> &mut Self {
- imageops::flip_horizontal_in_place(self);
+ unsafe { flip_h(self) };
self
}
#[inline(always)]
fn flip_v(&mut self) -> &mut Self {
- imageops::flip_vertical_in_place(self);
+ unsafe { flip_v(self) };
self
}
}
@@ -232,3 +322,366 @@ unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> 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
+ 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]
+ /// Return a pixel at (x, y).
+ /// # Safety
+ ///
+ /// Refer to [`slice`]
+ 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> {
+ #[inline]
+ /// Return a mutable reference to a pixel at (x, y).
+ /// # Safety
+ ///
+ /// Refer to [`slice`]
+ 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 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>) {
+ image::save_buffer(
+ f,
+ &self.buffer,
+ self.width(),
+ self.height(),
+ image::ColorType::Rgb8,
+ )
+ .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<'a> Overlay<&'a ImageHolder<4>> for ImageHolder<4> {
+ fn overlay_at(&mut self, with: &'a ImageHolder<4>, x: u32, y: u32) -> &mut Self {
+ self.borrow_mut().overlay_at(with.borrow(), x, y);
+ self
+ }
+}
+
+impl ImageUtils for ImageHolder<4> {
+ fn tint(&mut self, color: (u8, u8, u8)) -> &mut Self {
+ self.borrow_mut().tint(color);
+ self
+ }
+ type With<'a> = &'a Self;
+ fn overlay(&mut self, with: &Self) -> &mut Self {
+ self.borrow_mut().overlay(with.borrow());
+ self
+ }
+
+ 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]
+ ];
+ unsafe { 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]
+ ];
+ unsafe { 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]
+ ];
+ unsafe { flip_h(&mut from.as_mut()) };
+ assert_eq!(
+ from,
+ img![
+ [01, 90]
+ [42, 21]
+ ]
+ )
+ }
+}
+
+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,
+ ]
+}