mindustry logic execution, map- and schematic- parsing and rendering
| -rw-r--r-- | src/block/distribution.rs | 19 | ||||
| -rw-r--r-- | src/block/drills.rs | 6 | ||||
| -rw-r--r-- | src/block/logic.rs | 8 | ||||
| -rw-r--r-- | src/block/payload.rs | 26 | ||||
| -rw-r--r-- | src/block/power.rs | 8 | ||||
| -rw-r--r-- | src/block/production.rs | 22 | ||||
| -rw-r--r-- | src/block/units.rs | 30 | ||||
| -rw-r--r-- | src/data/autotile.rs | 7 | ||||
| -rw-r--r-- | src/data/renderer.rs | 69 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/utils/image.rs | 227 |
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 } } @@ -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![ |