mindustry logic execution, map- and schematic- parsing and rendering
make image.rs sound
bendn 2023-08-13
parent 7f0e8b4 · commit 68b083c
-rw-r--r--src/block/distribution.rs19
-rw-r--r--src/block/drills.rs6
-rw-r--r--src/block/logic.rs8
-rw-r--r--src/block/payload.rs26
-rw-r--r--src/block/power.rs8
-rw-r--r--src/block/production.rs22
-rw-r--r--src/block/units.rs30
-rw-r--r--src/data/autotile.rs7
-rw-r--r--src/data/renderer.rs69
-rw-r--r--src/lib.rs1
-rw-r--r--src/utils/image.rs227
11 files changed, 235 insertions, 188 deletions
diff --git a/src/block/distribution.rs b/src/block/distribution.rs
index e65cf98..8000dec 100644
--- a/src/block/distribution.rs
+++ b/src/block/distribution.rs
@@ -41,7 +41,8 @@ make_simple!(JunctionBlock => |_, _, _, buff| { read_directional_item_buffer(buf
make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation, s| {
let mut base = load!("duct-base", s);
let mut top = load!(from name which is ["overflow-duct" "underflow-duct"], s);
- top.rotate(rot.rotated(false).count());
+ // SAFETY: any load() is square
+ unsafe { top.rotate(rot.rotated(false).count()) };
base.overlay(&top);
base
});
@@ -63,7 +64,7 @@ fn draw_stack(
continue;
}
let mut edge = edge.clone();
- edge.rotate(i);
+ unsafe { edge.rotate(i) };
to.overlay(&edge.borrow());
}
};
@@ -85,7 +86,7 @@ fn draw_stack(
} else if mask == B0000 && empty {
// single
let mut base = gimme(0);
- base.rotate(rot.rotated(false).count());
+ unsafe { base.rotate(rot.rotated(false).count()) };
edgify(5, &mut base.borrow_mut());
base
} else if mask == B0000 {
@@ -97,11 +98,11 @@ fn draw_stack(
// directional
let mut base = gimme(0);
let going = rot.rotated(false).count();
- base.rotate(going);
+ unsafe { base.rotate(going) };
for [r, i] in [[3, 0b1000], [0, 0b0100], [1, 0b0010], [2, 0b0001]] {
if (mask.into_u8() & i) == 0 && (going != r || empty) {
let mut edge = edge.clone();
- edge.rotate(r);
+ unsafe { edge.rotate(r) };
base.overlay(&edge);
}
}
@@ -242,15 +243,15 @@ impl BlockLogic for ItemBlock {
}
if name == "duct-router" {
let mut arrow = load!("top", s);
- arrow.rotate(rot.rotated(false).count());
+ unsafe { arrow.rotate(rot.rotated(false).count()) };
p.overlay(&arrow);
p
} else if name == "duct-unloader" {
let mut top = load!("duct-unloader-top", s);
- top.rotate(rot.rotated(false).count());
+ unsafe { top.rotate(rot.rotated(false).count()) };
p.overlay(&top);
let mut arrow = load!("duct-unloader-arrow", s);
- arrow.rotate(rot.rotated(false).count());
+ unsafe { arrow.rotate(rot.rotated(false).count()) };
p.overlay(&arrow);
p
} else {
@@ -490,7 +491,7 @@ impl BlockLogic for BridgeBlock {
_ => "reinforced-bridge-conduit-dir",
}
);
- arrow.rotate(r.rotated(false).count());
+ unsafe { arrow.rotate(r.rotated(false).count()) };
base.overlay(&arrow);
base
}
diff --git a/src/block/drills.rs b/src/block/drills.rs
index ceffc70..e19912e 100644
--- a/src/block/drills.rs
+++ b/src/block/drills.rs
@@ -8,14 +8,16 @@ make_simple!(
|_, name, _, _, rot: Rotation, s| {
let mut base =
load!(from name which is ["large-plasma-bore" | "plasma-bore" | "cliff-crusher"], s);
- base.overlay(load!(concat top => name which is ["large-plasma-bore" | "plasma-bore" | "cliff-crusher"], s).rotate(rot.rotated(false).count()));
+ unsafe {
+ base.overlay(load!(concat top => name which is ["large-plasma-bore" | "plasma-bore" | "cliff-crusher"], s).rotate(rot.rotated(false).count()) )
+ };
base
},
|_, _, _, buff: &mut DataRead| read_drill(buff)
);
make_simple!(WallDrillBlock, |_, _, _, _, rot: Rotation, scl| {
let mut base = load!("cliff-crusher", scl);
- base.overlay(load!("cliff-crusher-top", scl).rotate(rot.rotated(false).count()));
+ unsafe { base.overlay(load!("cliff-crusher-top", scl).rotate(rot.rotated(false).count())) };
base
});
diff --git a/src/block/logic.rs b/src/block/logic.rs
index da7c47b..7aa9eb1 100644
--- a/src/block/logic.rs
+++ b/src/block/logic.rs
@@ -157,9 +157,11 @@ impl BlockLogic for CanvasBlock {
}
let img = img.as_mut().scale((s * self.size as u32) - offset * 2);
let mut borders = load!("canvas", s);
- borders
- .borrow_mut()
- .overlay_at(&img.as_ref(), offset, offset);
+ unsafe {
+ borders
+ .borrow_mut()
+ .overlay_at(&img.as_ref(), offset, offset)
+ };
return borders;
}
diff --git a/src/block/payload.rs b/src/block/payload.rs
index 37151e4..93a8b7f 100644
--- a/src/block/payload.rs
+++ b/src/block/payload.rs
@@ -20,12 +20,14 @@ make_simple!(SimplePayloadBlock, |_, n, _, _, r: Rotation, scl| {
"small-deconstructor" => "factory-in-3",
_ => "factory-in-5",
});
- base.overlay(r#in.rotate(r.rotated(false).count()))
- .overlay(&load!(scl -> match n {
- "small-deconstructor" => "small-deconstructor-top",
- "deconstructor" => "deconstructor-top",
- _ => "payload-void-top",
- }));
+ unsafe {
+ base.overlay(r#in.rotate(r.rotated(false).count()))
+ .overlay(&load!(scl -> match n {
+ "small-deconstructor" => "small-deconstructor-top",
+ "deconstructor" => "deconstructor-top",
+ _ => "payload-void-top",
+ }))
+ };
base
}
// "payload-loader" | "payload-unloader"
@@ -33,11 +35,13 @@ make_simple!(SimplePayloadBlock, |_, n, _, _, r: Rotation, scl| {
let mut base = load!(from n which is ["payload-loader" | "payload-unloader"], scl);
let mut input = load!("factory-in-3-dark", scl);
let mut output = load!("factory-out-3-dark", scl);
- base.overlay(input.rotate(r.rotated(false).count()))
+ unsafe {
+ base.overlay(input.rotate(r.rotated(false).count()))
.overlay(output.rotate(r.rotated(false).count()))
.overlay(
&load!(concat top => n which is ["payload-loader" | "payload-unloader"], scl),
- );
+ )
+ };
base
}
}
@@ -47,7 +51,7 @@ make_simple!(
|_, n, _, _, r: Rotation, s| {
let mut base =
load!(from n which is ["payload-conveyor" | "reinforced-payload-conveyor"], s);
- base.rotate(r.rotated(false).count());
+ unsafe { base.rotate(r.rotated(false).count()) };
base
},
read_payload_conveyor
@@ -116,7 +120,7 @@ impl BlockLogic for PayloadBlock {
"payload-router" | "reinforced-payload-router" => {
let mut base =
load!(from name which is ["payload-router" | "reinforced-payload-router"], s);
- base.rotate(r.rotated(false).count());
+ unsafe { base.rotate(r.rotated(false).count()) };
let over = load!(concat over => name which is ["payload-router" | "reinforced-payload-router"], s);
base.overlay(&over);
base
@@ -128,7 +132,7 @@ impl BlockLogic for PayloadBlock {
"large-constructor" => "factory-out-5-dark",
_ => "factory-out-5",
});
- out.rotate(r.rotated(false).count());
+ unsafe { out.rotate(r.rotated(false).count()) };
base.overlay(&out);
base.overlay(&load!(concat top => name which is ["constructor" | "large-constructor" | "payload-source"], s));
base
diff --git a/src/block/power.rs b/src/block/power.rs
index 3cb9165..ba29cb9 100644
--- a/src/block/power.rs
+++ b/src/block/power.rs
@@ -12,13 +12,13 @@ make_simple!(
Neoplasia,
|_, _, _, _, rot: Rotation, scl| {
let mut base = load!("neoplasia-reactor", scl);
- base.overlay(
+ base.overlay(unsafe {
load!(scl -> match rot {
Rotation::Up | Rotation::Right => "neoplasia-reactor-top1",
Rotation::Down | Rotation::Left => "neoplasia-reactor-top2",
})
- .rotate(rot.rotated(false).count()),
- );
+ .rotate(rot.rotated(false).count())
+ });
base
},
|_, _, _, buff: &mut DataRead| read_heater(buff)
@@ -29,7 +29,7 @@ make_simple!(DiodeBlock, |_, _, _, _, rot: Rotation, s| {
return base;
}
let mut top = load!("diode-arrow", s);
- top.rotate(rot.rotated(false).count());
+ unsafe { top.rotate(rot.rotated(false).count()) };
base.overlay(&top);
base
});
diff --git a/src/block/production.rs b/src/block/production.rs
index 6037427..333290a 100644
--- a/src/block/production.rs
+++ b/src/block/production.rs
@@ -52,20 +52,20 @@ make_simple!(
// electrolyzer exclusive
// ozone <- e(^) -> hydrogen
let mut base = load!("electrolyzer", s);
- base.overlay(
+ base.overlay(unsafe {
load!(s -> match r {
Rotation::Up | Rotation::Left => "electrolyzer-hydrogen-output1"
Rotation::Down | Rotation::Right => "electrolyzer-hydrogen-output2"
})
- .rotate(r.count()),
- );
- base.overlay(
+ .rotate(r.count())
+ });
+ base.overlay(unsafe {
load!(s -> match r {
Rotation::Down | Rotation::Right => "electrolyzer-ozone-output1"
Rotation::Up | Rotation::Left => "electrolyzer-ozone-output2"
})
- .rotate(r.mirrored(true, true).count()),
- );
+ .rotate(r.mirrored(true, true).count())
+ });
base
},
|b: &mut Build<'_>, _, _, buff: &mut DataRead| {
@@ -82,10 +82,10 @@ make_simple!(
HeatCrafter,
|_, n, _, _, r: Rotation, s| {
let mut base = load!(from n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s);
- base.overlay(match r {
+ base.overlay(unsafe { match r {
Rotation::Up | Rotation::Right => load!(concat top1 => n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s),
Rotation::Down | Rotation::Left => load!(concat top2 => n which is ["phase-heater" | "electric-heater" | "oxidation-chamber" | "slag-heater" | "heat-source"], s)
- }.rotate(r.rotated(false).count()));
+ }.rotate(r.rotated(false).count())});
base
},
|_, _, _, buff: &mut DataRead| {
@@ -99,7 +99,7 @@ make_simple!(
);
make_simple!(HeatConduit, |_, n, _, _, r: Rotation, s| {
let mut base = load!(from n which is ["heat-router" | "heat-redirector"], s);
- base.overlay(
+ base.overlay(unsafe {
match r {
Rotation::Up | Rotation::Right => {
load!(concat top1 => n which is ["heat-router" | "heat-redirector"], s)
@@ -108,7 +108,7 @@ make_simple!(HeatConduit, |_, n, _, _, r: Rotation, s| {
load!(concat top2 => n which is ["heat-router" | "heat-redirector"], s)
}
}
- .rotate(r.rotated(false).count()),
- );
+ .rotate(r.rotated(false).count())
+ });
base
});
diff --git a/src/block/units.rs b/src/block/units.rs
index 175fe56..877e81b 100644
--- a/src/block/units.rs
+++ b/src/block/units.rs
@@ -39,13 +39,13 @@ make_simple!(
|_, name, _, _, rot: Rotation, s| {
let mut base =
load!(from name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s);
- base.overlay(
- match rot {
- Rotation::Up | Rotation::Right => load!(concat side1 => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s),
- Rotation::Down | Rotation::Left => load!(concat side2 => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s)
- }
- .rotate(rot.rotated(false).count()),
- );
+ base.overlay(unsafe {
+ match rot {
+ Rotation::Up | Rotation::Right => load!(concat side1 => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s),
+ Rotation::Down | Rotation::Left => load!(concat side2 => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s)
+ }
+ .rotate(rot.rotated(false).count())
+ });
base.overlay(&load!(concat top => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s));
base
},
@@ -76,13 +76,13 @@ make_simple!(
AssemblerModule,
|_, _, _, _, rot: Rotation, scl| {
let mut base = load!("basic-assembler-module", scl);
- base.overlay(
+ base.overlay(unsafe {
load!(scl -> match rot {
Rotation::Up | Rotation::Right => "basic-assembler-module-side1",
_ => "basic-assembler-module-side2",
})
- .rotate(rot.rotated(false).count()),
- );
+ .rotate(rot.rotated(false).count())
+ });
base
},
|_, reg, map, buff| read_payload_block(reg, map, buff)
@@ -186,7 +186,7 @@ impl BlockLogic for ConstructorBlock {
"prime-refabricator" => "factory-out-5-dark",
"tetrative-reconstructor" => "factory-out-9",
});
- base.overlay(out.rotate(times));
+ base.overlay(unsafe { out.rotate(times) });
let mut r#in = load!(s -> match name {
"additive-reconstructor" => "factory-in-3",
@@ -197,7 +197,7 @@ impl BlockLogic for ConstructorBlock {
"prime-refabricator" => "factory-in-5-dark",
"tetrative-reconstructor" => "factory-in-9",
});
- base.overlay(r#in.rotate(times));
+ base.overlay(unsafe { r#in.rotate(times) });
// TODO: the context cross is too small
// for i in 0..4u8 {
@@ -327,13 +327,13 @@ impl BlockLogic for UnitFactory {
s: Scale,
) -> ImageHolder<4> {
let mut base = load!(from name which is ["ground-factory" | "air-factory" | "naval-factory" | "tank-fabricator" | "ship-fabricator" | "mech-fabricator"], s);
- base.overlay(
+ base.overlay(unsafe {
load!(s -> match name {
"ground-factory" | "air-factory" | "naval-factory" => "factory-out-3",
_ => "factory-out-3-dark",
})
- .rotate(rot.rotated(false).count()),
- )
+ .rotate(rot.rotated(false).count())
+ })
.overlay(&load!(s -> match name {
"ground-factory" | "air-factory" | "naval-factory" => "factory-top-3",
"tank-fabricator" => "tank-fabricator-top",
diff --git a/src/data/autotile.rs b/src/data/autotile.rs
index 2ca069f..b284b6c 100644
--- a/src/data/autotile.rs
+++ b/src/data/autotile.rs
@@ -212,7 +212,10 @@ pub fn mask2rotations(mask: U4, rot: Rotation) -> (u8, u8, u8) {
pub const FLIP_X: u8 = 1;
pub const FLIP_Y: u8 = 2;
-pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder<4>) {
+/// # Safety
+///
+/// `with` must be square
+pub unsafe fn flrot(flip: u8, rot: u8, with: &mut ImageHolder<4>) {
if (flip & FLIP_X) != 0 {
with.flip_h();
}
@@ -245,7 +248,7 @@ pub fn rotations2tile(
load!(concat 4 => name which is ["reinforced-conduit" | "armored-duct" | "pulse-conduit" | "plated-conduit" | "conduit" | "conveyor" | "titanium-conveyor" | "armored-conveyor" | "duct"], scale)
}
};
- flrot(flip, rot, &mut p);
+ unsafe { flrot(flip, rot, &mut p) };
p
}
diff --git a/src/data/renderer.rs b/src/data/renderer.rs
index 1731802..a455161 100644
--- a/src/data/renderer.rs
+++ b/src/data/renderer.rs
@@ -103,10 +103,13 @@ impl Renderable for Schematic<'_> {
/// ```
fn render(&self) -> Image<Vec<u8>, 3> {
// fill background
- let mut bg = load!("metal-floor", Scale::Full).borrow().repeated(
- ((self.width + 2) * 32) as u32,
- ((self.height + 2) * 32) as u32,
- );
+ // SAFETY: metal-floor is 32x32, the output is a multiple of 32
+ let mut bg = unsafe {
+ load!("metal-floor", Scale::Full).borrow().repeated(
+ ((self.width + 2) * 32) as u32,
+ ((self.height + 2) * 32) as u32,
+ )
+ };
let mut canvas = Image::alloc(
((self.width + 2) * 32) as u32,
((self.height + 2) * 32) as u32,
@@ -127,17 +130,19 @@ impl Renderable for Schematic<'_> {
};
let x = x as u32 - ((tile.block.get_size() - 1) / 2) as u32;
let y = self.height as u32 - y as u32 - ((tile.block.get_size() / 2) + 1) as u32;
- canvas.as_mut().overlay_at(
- &tile
- .image(
- ctx.as_ref(),
- tile.get_rotation().unwrap_or(Rotation::Up),
- Scale::Full,
- )
- .borrow(),
- (x + 1) * 32,
- (y + 1) * 32,
- );
+ unsafe {
+ canvas.as_mut().overlay_at(
+ &tile
+ .image(
+ ctx.as_ref(),
+ tile.get_rotation().unwrap_or(Rotation::Up),
+ Scale::Full,
+ )
+ .borrow(),
+ (x + 1) * 32,
+ (y + 1) * 32,
+ )
+ };
}
canvas.as_mut().shadow();
for x in 0..canvas.width() {
@@ -176,17 +181,21 @@ impl Renderable for Map<'_> {
}) {
// draw the floor first.
// println!("draw {tile:?} ({x}, {y}) + {scale:?}");
- floor.as_mut().overlay_at(
- &tile.floor(scale).borrow(),
- scale * x as u32,
- scale * y as u32,
- );
- if tile.has_ore() {
+ unsafe {
floor.as_mut().overlay_at(
- &tile.ore(scale).borrow(),
+ &tile.floor(scale).borrow(),
scale * x as u32,
scale * y as u32,
- );
+ )
+ };
+ if tile.has_ore() {
+ unsafe {
+ floor.as_mut().overlay_at(
+ &tile.ore(scale).borrow(),
+ scale * x as u32,
+ scale * y as u32,
+ )
+ };
}
if let Some(build) = tile.build() {
@@ -207,14 +216,16 @@ impl Renderable for Map<'_> {
} else {
None
};
- top.as_mut().overlay_at(
- &tile.build_image(ctx.as_ref(), scale).borrow(),
- scale * x as u32,
- scale * y as u32,
- );
+ unsafe {
+ top.as_mut().overlay_at(
+ &tile.build_image(ctx.as_ref(), scale).borrow(),
+ scale * x as u32,
+ scale * y as u32,
+ )
+ };
}
}
- floor.as_mut().overlay_at(&top.as_ref(), 0, 0);
+ unsafe { floor.as_mut().overlay_at(&top.as_ref(), 0, 0) };
floor
}
}
diff --git a/src/lib.rs b/src/lib.rs
index a753362..70f60cc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@
slice_as_chunks,
slice_swap_unchecked
)]
+#![allow(clippy::missing_safety_doc, clippy::missing_const_for_fn, clippy::perf)]
pub mod block;
mod content;
pub mod data;
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![