mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'src/utils/image.rs')
-rw-r--r--src/utils/image.rs227
1 files changed, 125 insertions, 102 deletions
diff --git a/src/utils/image.rs b/src/utils/image.rs
index c89f8cb..69842b4 100644
--- a/src/utils/image.rs
+++ b/src/utils/image.rs
@@ -3,13 +3,19 @@ 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;
+ /// # 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 RepeatNew {
type Output;
- /// Repeat self till it fills x, y
- fn repeated(&self, x: u32, y: u32) -> Self::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 {
@@ -19,7 +25,10 @@ pub trait ImageUtils {
/// Overlay with => self (does not blend)
fn overlay(&mut self, with: Self::With<'_>) -> &mut Self;
/// rotate (squares only)
- fn rotate(&mut self, times: u8) -> &mut Self;
+ /// # 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
@@ -33,26 +42,29 @@ pub trait ImageUtils {
macro_rules! unsafe_assert {
($cond:expr) => {{
if !$cond {
- unsafe { std::hint::unreachable_unchecked() }
+ #[cfg(debug_assertions)]
+ panic!("assertion failed: {} returned false", stringify!($cond));
+ #[cfg(not(debug_assertions))]
+ unsafe {
+ std::hint::unreachable_unchecked()
+ };
}
}};
}
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 {
+ 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() {
- unsafe {
- 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);
- }
+ 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
@@ -60,27 +72,22 @@ impl Overlay<Image<&[u8], 3>> for Image<&mut [u8], 3> {
}
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 {
+ 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() {
- unsafe {
- let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4);
- // solidity
- 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
- .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);
- }
+ let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4);
+ // solidity
+ 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
+ .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);
}
}
}
@@ -89,36 +96,32 @@ impl Overlay<Image<&[u8], 4>> for Image<&mut [u8], 3> {
}
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 {
+ 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() {
- unsafe {
- let with_index = really_unsafe_index(i, j, with.width()).unchecked_mul(4);
- let their_px = with
+ 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(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);
- }
+ .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>;
- fn repeated(&self, x: u32, y: u32) -> Self::Output {
+ 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()) {
@@ -130,39 +133,49 @@ impl RepeatNew for Image<&[u8], 4> {
}
}
-unsafe fn flip_v<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
+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() {
- 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 {
+ // 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);
+ }
}
}
}
-unsafe fn flip_h<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
+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 {
- 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);
+ // 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);
+ }
}
}
}
-unsafe fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
+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() {
- 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);
+ // 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);
+ }
}
}
@@ -170,25 +183,31 @@ unsafe fn rot_180<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
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);
+ // 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);
+ }
}
}
}
-/// only works with squares!
-unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
+/// # 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(
+ img.buffer.swap_unchecked(
(i * size + j) as usize * CHANNELS + c,
(j * size + i) as usize * CHANNELS + c,
);
@@ -197,15 +216,17 @@ unsafe fn rot_90<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
}
}
-/// only works with squares!
-unsafe fn rot_270<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
+/// # 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(
+ img.buffer.swap_unchecked(
(i * size + j) as usize * CHANNELS + c,
(j * size + i) as usize * CHANNELS + c,
);
@@ -213,15 +234,14 @@ unsafe fn rot_270<const CHANNELS: usize>(img: &mut Image<&mut [u8], CHANNELS>) {
}
}
}
+
impl ImageUtils for Image<&mut [u8], 4> {
- fn rotate(&mut self, times: u8) -> &mut Self {
- unsafe {
- match times {
- 2 => rot_180(self),
- 1 => rot_90(self),
- 3 => rot_270(self),
- _ => {}
- }
+ 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
}
@@ -237,8 +257,9 @@ impl ImageUtils for Image<&mut [u8], 4> {
}
type With<'a> = &'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());
+ unsafe_assert!(self.width() == with.width());
+ unsafe_assert!(self.height() == with.height());
+ unsafe_assert!(with.buffer.len() > 4);
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() {
@@ -305,17 +326,18 @@ impl ImageUtils for Image<&mut [u8], 4> {
#[inline]
fn flip_h(&mut self) -> &mut Self {
- unsafe { flip_h(self) };
+ flip_h(self);
self
}
#[inline(always)]
fn flip_v(&mut self) -> &mut Self {
- unsafe { flip_v(self) };
+ flip_v(self);
self
}
}
+#[inline]
unsafe fn really_unsafe_index(x: u32, y: u32, w: u32) -> usize {
// y * w + x
(y as usize)
@@ -377,6 +399,7 @@ impl<T: std::ops::Deref<Target = [u8]>, const CHANNELS: usize> Image<T, CHANNELS
///
/// - 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");
@@ -496,7 +519,7 @@ impl<const CHANNELS: usize> ImageHolder<CHANNELS> {
}
impl Overlay<ImageHolder<4>> for ImageHolder<4> {
- fn overlay_at(&mut self, with: &ImageHolder<4>, x: u32, y: u32) -> &mut Self {
+ 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
}
@@ -513,7 +536,7 @@ impl ImageUtils for ImageHolder<4> {
self
}
- fn rotate(&mut self, times: u8) -> &mut Self {
+ unsafe fn rotate(&mut self, times: u8) -> &mut Self {
if times == 0 {
return self;
}
@@ -600,7 +623,7 @@ mod tests {
[00, 01]
[02, 10]
];
- unsafe { rot_180(&mut from.as_mut()) };
+ rot_180(&mut from.as_mut());
assert_eq!(
from,
img![
@@ -632,7 +655,7 @@ mod tests {
[90, 01]
[21, 42]
];
- unsafe { flip_v(&mut from.as_mut()) };
+ flip_v(&mut from.as_mut());
assert_eq!(
from,
img![
@@ -647,7 +670,7 @@ mod tests {
[90, 01]
[21, 42]
];
- unsafe { flip_h(&mut from.as_mut()) };
+ flip_h(&mut from.as_mut());
assert_eq!(
from,
img![