mindustry logic execution, map- and schematic- parsing and rendering
dont allocate, store Image<&[u8>
bendn 2023-08-12
parent b427513 · commit d24149b
-rw-r--r--Cargo.toml3
-rw-r--r--build.rs30
-rw-r--r--src/block/distribution.rs19
-rw-r--r--src/block/liquid.rs2
-rw-r--r--src/block/logic.rs68
-rw-r--r--src/block/mod.rs20
-rw-r--r--src/block/payload.rs15
-rw-r--r--src/block/simple.rs2
-rw-r--r--src/block/turrets.rs4
-rw-r--r--src/block/units.rs34
-rw-r--r--src/block/walls.rs2
-rw-r--r--src/content.rs5
-rw-r--r--src/data/autotile.rs12
-rw-r--r--src/data/map.rs17
-rw-r--r--src/data/renderer.rs248
-rw-r--r--src/data/schematic.rs16
-rw-r--r--src/exe/draw.rs3
-rw-r--r--src/exe/map.rs10
-rw-r--r--src/lib.rs10
-rw-r--r--src/team.rs11
-rw-r--r--src/utils/image.rs625
-rw-r--r--src/utils/lazy.rs45
-rw-r--r--src/utils/mod.rs4
23 files changed, 730 insertions, 475 deletions
diff --git a/Cargo.toml b/Cargo.toml
index f2ed34d..69cc73e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,12 +16,13 @@ flate2 = { version = "1.0", features = ["zlib"], default-features = false }
base64 = "0.21"
paste = "1.0"
strconv = "0.1"
-image = { version = "0.24", features = [], default-features = false }
+image = { version = "0.24", features = [], default-features = false, optional = true }
color-hex = "0.2"
thiserror = "1.0"
bobbin-bits = "0.1"
blurslice = { version = "0.1" }
enum_dispatch = "0.3"
+fast_image_resize = "2.7.3"
[features]
bin = ["image/png"]
diff --git a/build.rs b/build.rs
index 8663e85..94230a4 100644
--- a/build.rs
+++ b/build.rs
@@ -35,30 +35,25 @@ fn main() {
// let mut half = File::create(o.join("half.rs")).unwrap();
let mut quar = File::create(o.join("quar.rs")).unwrap();
let mut eigh = File::create(o.join("eigh.rs")).unwrap();
- let mut n = 23usize;
+ let mut n = 22usize;
wr!(full => "pub mod full {{");
- wr!(full => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(32, 32));");
+ wr!(full => "pub static EMPTY: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked(32) }}, unsafe {{ std::num::NonZeroU32::new_unchecked(32) }}, &[0; 32 * 32 * 4]);");
wr!(quar => "pub mod quar {{");
- wr!(quar => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(8, 8));");
+ // forced to do this because try_into isnt const
+ wr!(quar => "pub static EMPTY: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked(8) }}, unsafe {{ std::num::NonZeroU32::new_unchecked(8) }}, &[0; 8 * 8 * 4]);");
wr!(eigh => "pub mod eigh {{");
- wr!(eigh => "pub static EMPTY: LazyLock<RgbaImage> = LazyLock::new(|| RgbaImage::new(4, 4));");
+ wr!(eigh => "pub static EMPTY: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked(4) }}, unsafe {{ std::num::NonZeroU32::new_unchecked(4) }}, &[0; 4 * 4 * 4]);");
for mut file in [&full, &quar, &eigh] {
- file.write_all(b"macro_rules!img{($v:expr)=>{{static TMP:LazyLock<RgbaImage>=LazyLock::new(||$v);&TMP}};}\n").unwrap();
- wr!(file => "use image::RgbaImage;");
- wr!(file => "use crate::utils::Lock as LazyLock;");
- wr!(file => "pub static CLIFF: &LazyLock<RgbaImage> = &EMPTY;");
+ wr!(file => "use crate::utils::Image;");
+ wr!(file => "pub static CLIFF: Image<&[u8], 4> = EMPTY.copy();");
for i in 1..=16 {
- wr!(file => "pub static BUILD{}: &LazyLock<RgbaImage> = &EMPTY;", i);
+ wr!(file => "pub static BUILD{}: Image<&[u8], 4> = EMPTY.copy();", i);
}
}
- let mut warmup = File::create(o.join("warmup.rs")).unwrap();
- wr!(warmup => "/// # Safety\n///\n/// this function must only be called once.");
- wr!(warmup => "pub unsafe fn warmup() {{");
- wr!(warmup => "LazyLock::load(&EMPTY);");
for e in walkdir.into_iter().filter_map(Result::ok) {
let path = e.path();
if path.is_file() && let Some(e) = path.extension() && e == "png" {
@@ -89,7 +84,7 @@ fn main() {
|| matches!(path.as_str(), "YELLOWCORAL" | "WHITE_TREE" | "WHITE_TREE_DEAD" | "REDWEED" | "SPORE_CLUSTER" | "CRYSTAL_BLOCKS" | "CRYSTAL_CLUSTER" | "VIBRANT_CRYSTAL_CLUSTER" | "CRYSTAL_ORBS") {
(32, 32)
} else {
- (p.height(), p.width())
+ (p.height(), p.height())
};
image::imageops::resize(
&p,
@@ -102,7 +97,7 @@ fn main() {
let y = new.height();
buf.write_all(&new.into_raw()).unwrap();
wr!($ext =>
- r#"pub(crate) static {path}: &LazyLock<RgbaImage> = img!(unsafe {{ RgbaImage::from_vec({x}, {y}, include_bytes!(concat!(env!("OUT_DIR"), "/{n}-{}")).to_vec()).unwrap_unchecked() }});"#,
+ r#"pub(crate) static {path}: Image<&[u8], 4> = Image::new(unsafe {{ std::num::NonZeroU32::new_unchecked( {x} ) }}, unsafe {{ std::num::NonZeroU32::new_unchecked( {y} ) }}, include_bytes!(concat!(env!("OUT_DIR"), "/{n}-{}")));"#,
stringify!($ext)
);
};
@@ -111,15 +106,10 @@ fn main() {
// writ!(half + 0.5);
writ!(quar / 4);
writ!(eigh / 8);
- wr!(warmup => "LazyLock::load({path});");
n += 1;
}
}
- warmup.write_all(b"}").unwrap();
for mut f in [full, eigh, quar] {
- // brazillian literal
- f.write_all(br#"include!(concat!(env!("OUT_DIR"), "/warmup.rs"));"#)
- .unwrap();
f.write_all(b"}").unwrap();
}
}
diff --git a/src/block/distribution.rs b/src/block/distribution.rs
index 4cc68c6..62b071f 100644
--- a/src/block/distribution.rs
+++ b/src/block/distribution.rs
@@ -53,18 +53,18 @@ fn draw_stack(
ctx: Option<&RenderingContext>,
rot: Rotation,
s: Scale,
-) -> ImageHolder {
+) -> ImageHolder<4> {
let ctx = ctx.unwrap();
let mask = mask(ctx, rot, name);
let edge = load!(concat edge => name which is ["surge-conveyor" | "plastanium-conveyor"], s);
- let edgify = |skip, to: &mut RgbaImage| {
+ let edgify = |skip, mut to: Image<&mut [u8], 4>| {
for i in 0..4 {
if i == skip {
continue;
}
let mut edge = edge.clone();
edge.rotate(i);
- to.overlay(&edge);
+ to.overlay(edge.borrow());
}
};
let gimme = |n: u8| match n {
@@ -77,18 +77,21 @@ fn draw_stack(
if rot.mirrored(true, true).mask() == mask && empty && name != "surge-conveyor" {
// end
let mut base = gimme(2);
- edgify(rot.mirrored(true, true).rotated(false).count(), &mut base);
+ edgify(
+ rot.mirrored(true, true).rotated(false).count(),
+ base.borrow_mut(),
+ );
base
} else if mask == B0000 && empty {
// single
let mut base = gimme(0);
base.rotate(rot.rotated(false).count());
- edgify(5, &mut base);
+ edgify(5, base.borrow_mut());
base
} else if mask == B0000 {
// input
let mut base = gimme(1);
- edgify(rot.rotated(false).count(), &mut base);
+ edgify(rot.rotated(false).count(), base.borrow_mut());
base
} else {
// directional
@@ -221,7 +224,7 @@ impl BlockLogic for ItemBlock {
_: Option<&RenderingContext>,
rot: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
let mut p = load!(from name which is ["sorter" | "inverted-sorter" | "duct-router" | "duct-unloader" | "unit-cargo-unload-point" | "unloader" | "item-source"], s);
if let Some(state) = state {
if let Some(item) = Self::get_state(state) {
@@ -471,7 +474,7 @@ impl BlockLogic for BridgeBlock {
_: Option<&RenderingContext>,
r: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
match name {
"mass-driver" => {
let mut base = load!("mass-driver-base", s);
diff --git a/src/block/liquid.rs b/src/block/liquid.rs
index 5e40f64..e090724 100644
--- a/src/block/liquid.rs
+++ b/src/block/liquid.rs
@@ -111,7 +111,7 @@ impl BlockLogic for FluidBlock {
_: Option<&RenderingContext>,
_: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
let mut p = load!("liquid-source", s);
if let Some(state) = state {
if let Some(liq) = Self::get_state(state) {
diff --git a/src/block/logic.rs b/src/block/logic.rs
index f76fa5e..5cdae40 100644
--- a/src/block/logic.rs
+++ b/src/block/logic.rs
@@ -2,8 +2,6 @@
use std::borrow::Cow;
use std::string::FromUtf8Error;
-use image::{Rgb, RgbImage};
-
use crate::block::simple::*;
use crate::data::dynamic::{DynSerializer, DynType};
use crate::{block::*, Serializer};
@@ -47,11 +45,12 @@ pub struct CanvasBlock {
}
macro_rules! h {
- ($x:literal) => {
- Rgb(color_hex::color_from_hex!($x))
- };
+ ($x:literal) => {{
+ let v = color_hex::color_from_hex!($x);
+ (v[0], v[1], v[2])
+ }};
}
-const PALETTE: &[Rgb<u8>; 8] = &[
+const PALETTE: &[(u8, u8, u8); 8] = &[
h!("#362944"),
h!("#c45d9f"),
h!("#e39aac"),
@@ -75,11 +74,11 @@ impl CanvasBlock {
}
}
- state_impl!(pub RgbImage);
+ state_impl!(pub Image<Vec<u8>, 1>);
}
-fn deser_canvas_image(b: &[u8], size: usize) -> RgbImage {
- let mut p = RgbImage::new(size as u32, size as u32);
+fn deser_canvas_image(b: &[u8], size: usize) -> Image<Vec<u8>, 1> {
+ let mut p = Image::alloc(size as u32, size as u32);
for i in 0..(size * size) {
let offset = i * 3;
let mut n = 0;
@@ -87,11 +86,7 @@ fn deser_canvas_image(b: &[u8], size: usize) -> RgbImage {
let word = (i + offset) >> 3;
n |= (((b[word] & (1 << ((i + offset) & 7))) != 0) as u8) << i;
}
- p.put_pixel(
- i as u32 % size as u32,
- i as u32 / size as u32,
- PALETTE[n as usize],
- );
+ unsafe { p.set_pixel(i as u32 % size as u32, i as u32 / size as u32, [n]) };
}
p
}
@@ -120,11 +115,12 @@ impl BlockLogic for CanvasBlock {
let mut o = vec![0; self.canvas_size as usize * self.canvas_size as usize * 3];
let p = Self::get_state(state);
for i in 0..(self.canvas_size * self.canvas_size) as usize {
- let color = p.get_pixel(
- i as u32 % self.canvas_size as u32,
- i as u32 / self.canvas_size as u32,
- );
- let index = PALETTE.iter().position(|v| v == color).unwrap();
+ let index = unsafe {
+ p.pixel(
+ i as u32 % self.canvas_size as u32,
+ i as u32 / self.canvas_size as u32,
+ )[0]
+ };
let offset = i * 3;
for i in 0..3 {
let word = (i + offset) >> 3;
@@ -146,7 +142,7 @@ impl BlockLogic for CanvasBlock {
_: Option<&RenderingContext>,
_: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
if let Some(state) = state {
let p = Self::get_state(state);
let offset = match s {
@@ -155,26 +151,22 @@ impl BlockLogic for CanvasBlock {
Scale::Quarter => 2,
Scale::Eigth => 1,
};
- let p = DynamicImage::from(
- RgbImage::from_raw(
- self.canvas_size as u32,
- self.canvas_size as u32,
- p.clone().into_raw(),
- )
- .unwrap(),
- )
- .into_rgba8()
- .scale((s * self.size as u32) - offset * 2);
+ let mut img = Image::alloc(p.width(), p.height());
+ for ([r, g, b], &y) in img.buffer.array_chunks_mut::<3>().zip(p.buffer.iter()) {
+ (*r, *g, *b) = PALETTE[y as usize];
+ }
+ let img = img.as_mut().scale((s * self.size as u32) - offset * 2);
let mut borders = load!("canvas", s);
- borders.overlay_at(&p, offset, offset);
+ borders
+ .borrow_mut()
+ .overlay_at(img.as_ref(), offset, offset);
return borders;
}
- let mut def = RgbaImage::new(s * self.size as u32, s * self.size as u32);
- for image::Rgba([r, g, b, _]) in def.pixels_mut() {
- *r = PALETTE[0][0];
- *g = PALETTE[0][1];
- *b = PALETTE[0][2];
+ let mut def = Image::alloc(s * self.size as u32, s * self.size as u32);
+ for [r, g, b, a] in def.buffer.array_chunks_mut::<4>() {
+ (*r, *g, *b) = PALETTE[0];
+ *a = 255;
}
ImageHolder::from(def)
}
@@ -234,7 +226,7 @@ impl BlockLogic for MessageLogic {
_: Option<&RenderingContext>,
_: Rotation,
_: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
unreachable!()
}
@@ -329,7 +321,7 @@ impl BlockLogic for SwitchLogic {
_: Option<&RenderingContext>,
_: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
let mut base = load!("switch", s);
if let Some(state) = state {
if *Self::get_state(state) {
diff --git a/src/block/mod.rs b/src/block/mod.rs
index 5e3bd50..518e059 100644
--- a/src/block/mod.rs
+++ b/src/block/mod.rs
@@ -13,7 +13,6 @@ use crate::data::{self, renderer::*, CompressError};
use crate::data::{DataRead, GridPos, ReadError as DataReadError};
use crate::item::storage::ItemStorage;
use crate::registry::RegistryEntry;
-use crate::utils::Lock;
macro_rules! mods {
($($mod:ident)*) => {
@@ -151,7 +150,7 @@ stater! {
String(String),
Item(Option<crate::item::Type>),
Fluid(Option<crate::fluid::Type>),
- Image(image::RgbImage),
+ Image(Image<Vec<u8>, 1>),
Point(Option<(i32, i32)>),
Bool(bool),
Processor(logic::ProcessorState),
@@ -205,7 +204,7 @@ pub trait BlockLogic {
context: Option<&RenderingContext>,
rot: Rotation,
scale: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
unimplemented!("{name}")
}
@@ -304,7 +303,7 @@ impl SerializeError {
/// a block. put it in stuff!
pub struct Block {
- image: Option<[&'static Lock<RgbaImage>; 3]>,
+ image: Option<[Image<&'static [u8], 4>; 3]>,
name: &'static str,
logic: BlockLogicEnum,
}
@@ -321,7 +320,7 @@ impl Block {
pub(crate) const fn new(
name: &'static str,
logic: BlockLogicEnum,
- image: Option<[&'static Lock<RgbaImage>; 3]>,
+ image: Option<[Image<&'static [u8], 4>; 3]>,
) -> Self {
Self { image, name, logic }
}
@@ -342,19 +341,16 @@ impl Block {
}
/// draw this block, with this state
- /// # Safety
- ///
- /// UB if called before [`warmup`](crate::warmup)
#[must_use]
- pub unsafe fn image(
+ pub fn image(
&self,
state: Option<&State>,
context: Option<&RenderingContext>,
rot: Rotation,
scale: Scale,
- ) -> ImageHolder {
- if let Some(imgs) = self.image {
- return ImageHolder::from(Lock::get(imgs[scale as usize]));
+ ) -> ImageHolder<4> {
+ if let Some(imgs) = &self.image {
+ return ImageHolder::from((imgs[scale as usize]).copy());
}
self.logic.draw(self.name, state, context, rot, scale)
}
diff --git a/src/block/payload.rs b/src/block/payload.rs
index 64ea9ba..37151e4 100644
--- a/src/block/payload.rs
+++ b/src/block/payload.rs
@@ -20,14 +20,12 @@ 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 {
+ 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",
- })
- .borrow(),
- );
+ }));
base
}
// "payload-loader" | "payload-unloader"
@@ -38,8 +36,7 @@ make_simple!(SimplePayloadBlock, |_, n, _, _, r: Rotation, scl| {
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)
- .borrow(),
+ &load!(concat top => n which is ["payload-loader" | "payload-unloader"], scl),
);
base
}
@@ -114,7 +111,7 @@ impl BlockLogic for PayloadBlock {
_: Option<&RenderingContext>,
r: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
match name {
"payload-router" | "reinforced-payload-router" => {
let mut base =
@@ -133,7 +130,7 @@ impl BlockLogic for PayloadBlock {
});
out.rotate(r.rotated(false).count());
base.overlay(&out);
- base.overlay(load!(concat top => name which is ["constructor" | "large-constructor" | "payload-source"], s).borrow());
+ base.overlay(&load!(concat top => name which is ["constructor" | "large-constructor" | "payload-source"], s));
base
}
}
diff --git a/src/block/simple.rs b/src/block/simple.rs
index 329e58d..af4047e 100644
--- a/src/block/simple.rs
+++ b/src/block/simple.rs
@@ -87,7 +87,7 @@ macro_rules! make_simple {
context: Option<&crate::data::renderer::RenderingContext>,
rot: crate::block::Rotation,
scale: crate::data::renderer::Scale,
- ) -> crate::data::renderer::ImageHolder {
+ ) -> crate::data::renderer::ImageHolder<4> {
#[allow(clippy::redundant_closure_call)]
$draw(self, name, state, context, rot, scale)
}
diff --git a/src/block/turrets.rs b/src/block/turrets.rs
index 0a3cf0e..522906a 100644
--- a/src/block/turrets.rs
+++ b/src/block/turrets.rs
@@ -43,7 +43,7 @@ fn draw_turret(
_: Option<&RenderingContext>,
_: Rotation,
s: Scale,
-) -> ImageHolder {
+) -> ImageHolder<4> {
let size = me.get_size();
let mut base = match name {
"breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre"
@@ -59,7 +59,7 @@ fn draw_turret(
4 => "block-4",
}),
};
- base.overlay(load!(from name which is ["duo" | "scatter" | "scorch" | "hail" | "wave" | "tsunami" | "lancer" | "arc" | "parallax" | "swarmer" | "salvo" | "segment" | "fuse" | "ripple" | "cyclone" | "foreshadow" | "spectre" | "meltdown" | "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" | "smite"], s).borrow());
+ base.overlay(&load!(from name which is ["duo" | "scatter" | "scorch" | "hail" | "wave" | "tsunami" | "lancer" | "arc" | "parallax" | "swarmer" | "salvo" | "segment" | "fuse" | "ripple" | "cyclone" | "foreshadow" | "spectre" | "meltdown" | "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" | "smite"], s));
base
}
diff --git a/src/block/units.rs b/src/block/units.rs
index c34f944..3587c9f 100644
--- a/src/block/units.rs
+++ b/src/block/units.rs
@@ -46,7 +46,7 @@ make_simple!(
}
.rotate(rot.rotated(false).count()),
);
- base.overlay(load!(concat top => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s).borrow());
+ base.overlay(&load!(concat top => name which is ["tank-assembler" | "ship-assembler" | "mech-assembler"], s));
base
},
|_, reg, map, buff| read_assembler(reg, map, buff)
@@ -178,8 +178,6 @@ impl BlockLogic for ConstructorBlock {
}
}
-
-
fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
Ok(Self::get_state(state).map_or(DynData::Empty, DynData::UnitCommand))
}
@@ -191,7 +189,7 @@ impl BlockLogic for ConstructorBlock {
_: Option<&RenderingContext>,
rot: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
let mut base = load!(from name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s);
let times = rot.rotated(false).count();
let mut out = load!(s -> match name {
@@ -236,7 +234,7 @@ impl BlockLogic for ConstructorBlock {
// }
// }
- base.overlay(load!(concat top => name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s).borrow());
+ base.overlay(&load!(concat top => name which is ["additive-reconstructor" | "multiplicative-reconstructor" | "exponential-reconstructor" | "tetrative-reconstructor" | "tank-refabricator" | "mech-refabricator" | "ship-refabricator" | "prime-refabricator"], s));
base
}
@@ -320,8 +318,6 @@ impl BlockLogic for UnitFactory {
}
}
-
-
fn serialize_state(&self, state: &State) -> Result<DynData, SerializeError> {
if let Some(state) = Self::get_state(state) {
for (i, curr) in self.valid.iter().enumerate() {
@@ -344,23 +340,21 @@ impl BlockLogic for UnitFactory {
_: Option<&RenderingContext>,
rot: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
let mut base = load!(from name which is ["ground-factory" | "air-factory" | "naval-factory" | "tank-fabricator" | "ship-fabricator" | "mech-fabricator"], s);
- let mut out = load!(s -> match name {
- "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3",
- _ => "factory-out-3-dark",
- });
- out.rotate(rot.rotated(false).count());
- base.overlay(&out);
base.overlay(
load!(s -> match name {
- "ground-factory" | "air-factory" | "naval-factory" => "factory-top-3",
- "tank-fabricator" => "tank-fabricator-top",
- "ship-fabricator" => "ship-fabricator-top",
- "mech-fabricator" => "mech-fabricator-top",
+ "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3",
+ _ => "factory-out-3-dark",
})
- .borrow(),
- );
+ .rotate(rot.rotated(false).count()),
+ )
+ .overlay(&load!(s -> match name {
+ "ground-factory" | "air-factory" | "naval-factory" => "factory-top-3",
+ "tank-fabricator" => "tank-fabricator-top",
+ "ship-fabricator" => "ship-fabricator-top",
+ "mech-fabricator" => "mech-fabricator-top",
+ }));
base
}
diff --git a/src/block/walls.rs b/src/block/walls.rs
index 0108dc3..f2dcb01 100644
--- a/src/block/walls.rs
+++ b/src/block/walls.rs
@@ -73,7 +73,7 @@ impl BlockLogic for DoorBlock {
_: Option<&RenderingContext>,
_: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
if let Some(state) = state {
if *Self::get_state(state) {
return load!(s -> match name {
diff --git a/src/content.rs b/src/content.rs
index 65619e4..6c2872c 100644
--- a/src/content.rs
+++ b/src/content.rs
@@ -96,10 +96,11 @@ macro_rules! color_content_enum {
impl Type {
#[must_use]
- pub const fn color(&self) -> image::Rgb<u8> {
+ pub const fn color(&self) -> (u8, u8, u8) {
match &self {
$(Self::[<$val:camel>] => {
- image::Rgb(color_hex::color_from_hex!($col))
+ let v = color_hex::color_from_hex!($col);
+ (v[0], v[1], v[2])
},)*
}
}
diff --git a/src/data/autotile.rs b/src/data/autotile.rs
index c7af8e1..2ca069f 100644
--- a/src/data/autotile.rs
+++ b/src/data/autotile.rs
@@ -92,7 +92,7 @@ fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String {
s
}
-pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation, s: Scale) -> ImageHolder {
+pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation, s: Scale) -> ImageHolder<4> {
rotations2tile(mask2rotations(mask(ctx, rot, name), rot), name, s)
}
@@ -212,7 +212,7 @@ 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) {
+pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder<4>) {
if (flip & FLIP_X) != 0 {
with.flip_h();
}
@@ -223,7 +223,11 @@ pub fn flrot(flip: u8, rot: u8, with: &mut ImageHolder) {
}
/// TODO figure out if a flip is cheaper than a `rotate_270`
-pub fn rotations2tile((index, rot, flip): (u8, u8, u8), name: &str, scale: Scale) -> ImageHolder {
+pub fn rotations2tile(
+ (index, rot, flip): (u8, u8, u8),
+ name: &str,
+ scale: Scale,
+) -> ImageHolder<4> {
let mut p = match index {
0 => {
load!(concat 0 => name which is ["reinforced-conduit" | "armored-duct" | "pulse-conduit" | "plated-conduit" | "conduit" | "conveyor" | "titanium-conveyor" | "armored-conveyor" | "duct"], scale)
@@ -241,7 +245,7 @@ pub fn rotations2tile((index, rot, flip): (u8, u8, u8), name: &str, scale: Scale
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, p.borrow_mut());
+ flrot(flip, rot, &mut p);
p
}
diff --git a/src/data/map.rs b/src/data/map.rs
index 6284ac7..4660d90 100644
--- a/src/data/map.rs
+++ b/src/data/map.rs
@@ -146,7 +146,7 @@ impl<'l> Tile<'l> {
1
}
- pub(crate) unsafe fn floor(&self, s: Scale) -> ImageHolder {
+ pub(crate) fn floor(&self, s: Scale) -> ImageHolder<4> {
lo!(self.floor => [
| "darksand"
| "sand-floor"
@@ -186,7 +186,7 @@ impl<'l> Tile<'l> {
}
#[must_use]
- pub(crate) unsafe fn ore(&self, s: Scale) -> ImageHolder {
+ pub(crate) fn ore(&self, s: Scale) -> ImageHolder<4> {
lo!(self.ore => ["ore-copper" | "ore-beryllium" | "ore-lead" | "ore-scrap" | "ore-coal" | "ore-thorium" | "ore-titanium" | "ore-tungsten" | "pebbles" | "tendrils" | "ore-wall-tungsten" | "ore-wall-beryllium" | "ore-wall-thorium" | "spawn" | "ore-crystal-thorium"], s)
}
@@ -196,12 +196,8 @@ impl<'l> Tile<'l> {
}
/// Draw the floor of this tile
- ///
- /// # Safety
- ///
- /// UB if called before [`warmup`](crate::warmup)
#[must_use]
- pub unsafe fn floor_image(&self, s: Scale) -> ImageHolder {
+ pub fn floor_image(&self, s: Scale) -> ImageHolder<4> {
let mut floor = self.floor(s);
if self.has_ore() {
floor.overlay(&self.ore(s));
@@ -210,11 +206,8 @@ impl<'l> Tile<'l> {
}
/// Draw this tiles build.
- ///
- /// # Safety
- /// UB if called before [`warmup`](crate::warmup)
#[must_use]
- pub unsafe fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder {
+ pub fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder<4> {
// building covers floore
let Some(b) = &self.build else {
unreachable!();
@@ -300,7 +293,7 @@ impl<'l> Build<'l> {
}
}
- unsafe fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder {
+ fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder<4> {
self.block
.image(self.state.as_ref(), context, self.rotation, s)
}
diff --git a/src/data/renderer.rs b/src/data/renderer.rs
index 6230e71..93f1a7e 100644
--- a/src/data/renderer.rs
+++ b/src/data/renderer.rs
@@ -2,91 +2,13 @@
pub(crate) use super::autotile::*;
use super::schematic::Schematic;
use super::GridPos;
-use crate::block::environment::METAL_FLOOR;
use crate::block::Rotation;
-pub(crate) use crate::utils::{ImageUtils, Overlay, Repeat};
+pub(crate) use crate::utils::{Image, ImageHolder, ImageUtils, Overlay, Repeat};
use crate::Map;
-pub(crate) use image::{
- DynamicImage, GenericImage, GenericImageView, Pixel, Rgb, RgbImage, Rgba, RgbaImage,
-};
-pub(crate) use std::borrow::{Borrow, BorrowMut};
-use std::ops::{Deref, DerefMut};
include!(concat!(env!("OUT_DIR"), "/full.rs"));
include!(concat!(env!("OUT_DIR"), "/quar.rs"));
include!(concat!(env!("OUT_DIR"), "/eigh.rs"));
-pub enum ImageHolder {
- Borrow(&'static RgbaImage),
- Own(RgbaImage),
-}
-
-impl ImageHolder {
- #[must_use]
- pub fn own(self) -> RgbaImage {
- match self {
- Self::Own(x) => x,
- Self::Borrow(x) => x.clone(),
- }
- }
-
- pub fn rotate(&mut self, times: u8) -> &mut Self {
- if times == 0 {
- return self;
- }
- let p: &mut RgbaImage = self.borrow_mut();
- p.rotate(times);
- self
- }
-}
-
-impl Borrow<RgbaImage> for ImageHolder {
- fn borrow(&self) -> &RgbaImage {
- match self {
- Self::Own(x) => x,
- Self::Borrow(x) => x,
- }
- }
-}
-
-impl BorrowMut<RgbaImage> for ImageHolder {
- fn borrow_mut(&mut self) -> &mut RgbaImage {
- match self {
- Self::Own(x) => x,
- Self::Borrow(_) => {
- *self = Self::from(std::mem::replace(self, Self::from(RgbaImage::new(0, 0))).own());
- self.borrow_mut()
- }
- }
- }
-}
-
-impl Deref for ImageHolder {
- type Target = RgbaImage;
- fn deref(&self) -> &Self::Target {
- self.borrow()
- }
-}
-
-impl DerefMut for ImageHolder {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.borrow_mut()
- }
-}
-
-impl From<&'static RgbaImage> for ImageHolder {
- fn from(value: &'static RgbaImage) -> Self {
- debug_assert_ne!(value.width(), 0);
- debug_assert_ne!(value.height(), 0);
- Self::Borrow(value)
- }
-}
-
-impl From<RgbaImage> for ImageHolder {
- fn from(value: RgbaImage) -> Self {
- Self::Own(value)
- }
-}
-
#[derive(Debug, Copy, Clone)]
#[repr(u8)]
pub enum Scale {
@@ -116,22 +38,22 @@ impl std::ops::Mul<u32> for Scale {
#[macro_export]
macro_rules! load {
- ("empty", $scale:ident) => {
- ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale {
+ ("empty", $scale:expr) => {
+ ImageHolder::from(match $scale {
$crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::EMPTY,
$crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::EMPTY,
$crate::data::renderer::Scale::Full => &$crate::data::renderer::full::EMPTY,
- })})
+ }.copy())
};
- ($name:literal, $scale:ident) => { paste::paste! {
- ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale {
- $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$name:snake:upper>],
- $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$name:snake:upper>],
- $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$name:snake:upper>],
- })})
+ ($name:literal, $scale:expr) => { paste::paste! {
+ ImageHolder::from(match $scale {
+ $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::[<$name:snake:upper>],
+ $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::[<$name:snake:upper>],
+ $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::[<$name:snake:upper>],
+ }.copy())
} };
($name: literal) => { paste::paste! {
- [$crate::data::renderer::full::[<$name:snake:upper>], $crate::data::renderer::quar::[<$name:snake:upper>], $crate::data::renderer::eigh::[<$name:snake:upper>]]
+ [$crate::data::renderer::full::[<$name:snake:upper>].copy(), $crate::data::renderer::quar::[<$name:snake:upper>].copy(), $crate::data::renderer::eigh::[<$name:snake:upper>].copy()]
} };
(from $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => {
$crate::data::renderer::load!($scale -> match $v {
@@ -149,11 +71,11 @@ macro_rules! load {
(concat $x:expr => $v:ident which is [$($k:literal $(|)?)+], $scale: ident) => { paste::paste! {
match $v {
$($k =>
- ImageHolder::from(unsafe { $crate::utils::Lock::get(match $scale {
- $crate::data::renderer::Scale::Quarter => $crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>],
- $crate::data::renderer::Scale::Eigth => $crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>],
- $crate::data::renderer::Scale::Full => $crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>],
- }) }),
+ ImageHolder::from(match $scale {
+ $crate::data::renderer::Scale::Quarter => &$crate::data::renderer::quar::[<$k:snake:upper _ $x:snake:upper>],
+ $crate::data::renderer::Scale::Eigth => &$crate::data::renderer::eigh::[<$k:snake:upper _ $x:snake:upper>],
+ $crate::data::renderer::Scale::Full => &$crate::data::renderer::full::[<$k:snake:upper _ $x:snake:upper>],
+ }.copy()),
)+
#[allow(unreachable_patterns)]
n => unreachable!("{n:?}"),
@@ -165,11 +87,8 @@ pub(crate) use load;
/// trait for renderable objects
pub trait Renderable {
/// create a picture
- ///
- /// # Safety
- ///
- /// UB if called before [`warmup`](crate::warmup)
- unsafe fn render(&self) -> RgbImage;
+ #[must_use = "i did so much work for you"]
+ fn render(&self) -> Image<Vec<u8>, 3>;
}
impl Renderable for Schematic<'_> {
@@ -180,28 +99,15 @@ impl Renderable for Schematic<'_> {
/// s.put(0, 0, &block::distribution::DISTRIBUTOR);
/// s.put(0, 2, &block::distribution::ROUTER);
/// s.put(1, 2, &block::walls::COPPER_WALL);
- /// // warm up the images for the first time
- /// unsafe { warmup(); }
- /// // this is now safe, because we have warmed up
- /// let output /*: RgbImage */ = unsafe { s.render() };
+ /// let output /*: Image */ = s.render();
/// ```
- ///
- /// # Safety
- ///
- /// UB if called before [`warmup`](crate::warmup)
- unsafe fn render(&self) -> RgbImage {
+ fn render(&self) -> Image<Vec<u8>, 3> {
// fill background
- let mut bg = RgbImage::repeated(
- &DynamicImage::from(
- METAL_FLOOR
- .image(None, None, Rotation::Up, Scale::Full)
- .own(),
- )
- .into_rgb8(),
+ let mut bg = load!("metal-floor", Scale::Full).borrow().repeated(
((self.width + 2) * 32) as u32,
((self.height + 2) * 32) as u32,
);
- let mut canvas = RgbaImage::new(
+ let mut canvas = Image::alloc(
((self.width + 2) * 32) as u32,
((self.height + 2) * 32) as u32,
);
@@ -221,7 +127,7 @@ 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.overlay_at(
+ canvas.as_mut().overlay_at(
tile.image(
ctx.as_ref(),
tile.get_rotation().unwrap_or(Rotation::Up),
@@ -232,41 +138,32 @@ impl Renderable for Schematic<'_> {
(y + 1) * 32,
);
}
- canvas.shadow();
+ canvas.as_mut().shadow();
for x in 0..canvas.width() {
for y in 0..canvas.height() {
- let p2 = unsafe { canvas.unsafe_get_pixel(x, y) };
- let Rgb([r2, g2, b2]) = unsafe { bg.unsafe_get_pixel(x, y) };
- let mut p = Rgba([r2, g2, b2, u8::MAX]);
- p.blend(&p2);
- let Rgba([r, g, b, a]) = p;
- let a = a as f32 / 255.;
- let p = Rgb([
- (((r as f32 / 255.) * a) * 255.) as u8,
- (((g as f32 / 255.) * a) * 255.) as u8,
- (((b as f32 / 255.) * a) * 255.) as u8,
- ]);
- unsafe { bg.unsafe_put_pixel(x, y, p) };
+ // canvas has a shadow
+ let p2 = unsafe { canvas.pixel(x, y) };
+ let p = unsafe { bg.pixel_mut(x, y) };
+ crate::utils::image::blend(p.try_into().unwrap(), p2);
}
}
- bg
+ bg.remove_channel()
}
}
impl Renderable for Map<'_> {
/// Draws a map
- ///
- /// # Safety
- /// UB if called before [`warmup`](crate::warmup)
- unsafe fn render(&self) -> RgbImage {
+ fn render(&self) -> Image<Vec<u8>, 3> {
let scale = if self.width + self.height < 2000 {
Scale::Quarter
} else {
Scale::Eigth
};
// todo combine these (beware of floor drawing atop buildings) (planned solution:? ptr blocks)
- let mut floor = RgbImage::new(scale * self.width as u32, scale * self.height as u32);
- let mut top = RgbaImage::new(scale * self.width as u32, scale * self.height as u32);
+ let mut floor: Image<_, 3> =
+ Image::alloc(scale * self.width as u32, scale * self.height as u32);
+ let mut top: Image<_, 4> =
+ Image::alloc(scale * self.width as u32, scale * self.height as u32);
for (x, y, j, tile) in self.tiles.iter().enumerate().map(|(j, t)| {
(
(j % self.width),
@@ -277,16 +174,18 @@ impl Renderable for Map<'_> {
)
}) {
// draw the floor first.
- let flo: &RgbaImage = &tile.floor(scale);
// println!("draw {tile:?} ({x}, {y}) + {scale:?}");
- // debug_assert_eq!(floor.width(), scale.px() as u32);
- // debug_assert_eq!(floor.height(), scale.px() as u32);
- floor.overlay_at(flo, scale * x as u32, scale * y as u32);
+ floor.as_mut().overlay_at(
+ tile.floor(scale).borrow(),
+ scale * x as u32,
+ scale * y as u32,
+ );
if tile.has_ore() {
- let ore: &RgbaImage = &tile.ore(scale);
- // debug_assert_eq!(ore.width(), scale.px() as u32);
- // debug_assert_eq!(ore.height(), scale.px() as u32);
- floor.overlay_at(ore, scale * x as u32, scale * y as u32);
+ floor.as_mut().overlay_at(
+ tile.ore(scale).borrow(),
+ scale * x as u32,
+ scale * y as u32,
+ );
}
if let Some(build) = tile.build() {
@@ -307,41 +206,22 @@ impl Renderable for Map<'_> {
} else {
None
};
- let img: &RgbaImage = &tile.build_image(ctx.as_ref(), scale);
- // debug_assert_eq!(img.width(), scale * build.block.get_size() as u32);
- // debug_assert_eq!(img.height(), scale * build.block.get_size() as u32);
- top.overlay_at(img, scale * x as u32, scale * y as u32);
+ top.as_mut().overlay_at(
+ tile.build_image(ctx.as_ref(), scale).borrow(),
+ scale * x as u32,
+ scale * y as u32,
+ );
}
}
- floor.overlay_at(&top, 0, 0);
+ floor.as_mut().overlay_at(top.as_ref(), 0, 0);
floor
}
}
-#[allow(clippy::needless_doctest_main)]
-/// Loads all the images into memory (about 300mb)
-/// This is a necessary function. Call it once in main.
-///
-/// ```
-/// fn main() {
-/// unsafe { mindus::warmup(); }
-/// }
-/// ```
-///
-/// # Safety
-///
-/// only call once, else UB
-pub unsafe fn warmup() {
- full::warmup();
- quar::warmup();
- eigh::warmup();
-}
-
#[test]
fn all_blocks() {
use crate::block::content::Type;
use crate::content::Content;
- unsafe { warmup() };
let reg = crate::block::build_registry();
for t in 19..Type::WorldMessage as u16 {
let t = Type::try_from(t).unwrap();
@@ -357,20 +237,18 @@ fn all_blocks() {
}
let name = dbg!(t.get_name());
let t = reg.get(name).unwrap();
- let _ = unsafe {
- t.image(
- None,
- Some(&RenderingContext {
- cross: [None; 4],
- position: PositionContext {
- position: GridPos(0, 0),
- width: 5,
- height: 5,
- },
- }),
- Rotation::Up,
- Scale::Quarter,
- )
- };
+ let _ = t.image(
+ None,
+ Some(&RenderingContext {
+ cross: [None; 4],
+ position: PositionContext {
+ position: GridPos(0, 0),
+ width: 5,
+ height: 5,
+ },
+ }),
+ Rotation::Up,
+ Scale::Quarter,
+ );
}
}
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 6dea884..31888c2 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -41,7 +41,7 @@ impl fmt::Debug for Placement<'_> {
impl<'l> Placement<'l> {
/// make a placement from a block
#[must_use]
- pub fn new(block: &'l Block) -> Self {
+ pub const fn new(block: &'l Block) -> Self {
Self {
block,
rot: Rotation::Up,
@@ -51,7 +51,7 @@ impl<'l> Placement<'l> {
/// gets the current state of this placement. you can cast it with `placement.block::get_state(placement.get_state()?)?`
#[must_use]
- pub fn get_state(&self) -> Option<&State> {
+ pub const fn get_state(&self) -> Option<&State> {
self.state.as_ref()
}
@@ -65,12 +65,12 @@ impl<'l> Placement<'l> {
/// # Safety
/// UB if called before [`warmup`](crate::warmup)
#[must_use]
- pub unsafe fn image(
+ pub fn image(
&self,
context: Option<&RenderingContext>,
rot: Rotation,
s: Scale,
- ) -> ImageHolder {
+ ) -> ImageHolder<4> {
self.block.image(self.get_state(), context, rot, s)
}
@@ -716,10 +716,10 @@ mod test {
let parsed2 = unwrap_pretty(ser.deserialize_base64(&unparsed));
println!("\x1b[38;5;2mredeserialized\x1b[0m {}", parsed.tags.get("name").unwrap());
if parsed != parsed2 {
- unsafe { crate::warmup() };
- // SAFETY: we just warmed up, its fine
- unsafe { parsed2.render() }.save("p2.png").unwrap();
- unsafe { parsed.render() }.save("p1.png").unwrap();
+ #[cfg(feature = "bin")]
+ parsed2.render().save("p2.png");
+ #[cfg(feature = "bin")]
+ parsed.render().save("p1.png");
panic!("DIFFERENT! see `p1.png` != `p2.png`")
}
)*
diff --git a/src/exe/draw.rs b/src/exe/draw.rs
index c7de323..964bcac 100644
--- a/src/exe/draw.rs
+++ b/src/exe/draw.rs
@@ -6,7 +6,6 @@ use std::env::Args;
use crate::print_err;
pub fn main(args: Args) {
- unsafe { mindus::warmup() };
let reg = build_registry();
let mut ss = SchematicSerializer(&reg);
@@ -14,7 +13,7 @@ pub fn main(args: Args) {
for curr in args {
match ss.deserialize_base64(&curr) {
Ok(s) => {
- unsafe { s.render() }.save("x.png").unwrap();
+ s.render().save("x.png");
}
// continue processing literals & maybe interactive mode
Err(e) => {
diff --git a/src/exe/map.rs b/src/exe/map.rs
index 51c05c6..cbb6477 100644
--- a/src/exe/map.rs
+++ b/src/exe/map.rs
@@ -1,4 +1,3 @@
-use mindus::data::renderer::warmup;
use mindus::data::DataRead;
use mindus::{build_registry, Renderable};
use mindus::{MapSerializer, Serializer};
@@ -14,8 +13,6 @@ pub fn main(args: Args) {
// process schematics from command line
println!("starting timing");
let then = Instant::now();
- unsafe { warmup() };
- let warmup_took = then.elapsed();
for curr in args {
let Ok(s) = std::fs::read(curr) else {
continue;
@@ -27,22 +24,21 @@ pub fn main(args: Args) {
let deser_took = starting_deser.elapsed();
if let Ok(v) = std::env::var("SAVE") {
if v == "1" {
- unsafe { m.render() }.save("x.png").unwrap();
+ m.render().save("x.png");
continue;
}
}
let starting_render = Instant::now();
for _ in 0..runs {
- unsafe { m.render() };
+ drop(m.render());
}
let renders_took = starting_render.elapsed();
let took = then.elapsed();
println!(
- "μ total: {:.2}s ({} runs) (deser: {}ms, warmup: {}ms, render: {:.2}s) on map {}",
+ "μ total: {:.2}s ({} runs) (deser: {}ms, render: {:.2}s) on map {}",
took.as_secs_f32() / runs as f32,
runs,
deser_took.as_millis(),
- warmup_took.as_millis(),
renders_took.as_secs_f32() / runs as f32,
m.tags.get("mapname").unwrap(),
);
diff --git a/src/lib.rs b/src/lib.rs
index dfc4eef..a753362 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,11 @@
//! crate for dealing with mindustry
-#![feature(array_chunks, const_trait_impl, unchecked_math, core_intrinsics)]
+#![feature(
+ array_chunks,
+ const_trait_impl,
+ unchecked_math,
+ slice_as_chunks,
+ slice_swap_unchecked
+)]
pub mod block;
mod content;
pub mod data;
@@ -17,7 +23,7 @@ pub use {
data::{
dynamic::DynData,
map::{Map, MapSerializer},
- renderer::{warmup, Renderable},
+ renderer::Renderable,
schematic::{Schematic, SchematicSerializer},
Serializer,
},
diff --git a/src/team.rs b/src/team.rs
index c2f8510..35a651f 100644
--- a/src/team.rs
+++ b/src/team.rs
@@ -1,7 +1,5 @@
use std::fmt;
-use image::Rgb;
-
use crate::content::{Content, Type};
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -109,11 +107,12 @@ impl Content for Team {
}
impl Team {
- pub const fn color(self) -> Rgb<u8> {
+ pub const fn color(self) -> (u8, u8, u8) {
macro_rules! h {
- ($x:literal) => {
- Rgb(color_hex::color_from_hex!($x))
- };
+ ($x:literal) => {{
+ let v = color_hex::color_from_hex!($x);
+ (v[0], v[1], v[2])
+ }};
}
match self {
SHARDED => h!("ffd37f"),
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,
+ ]
+}
diff --git a/src/utils/lazy.rs b/src/utils/lazy.rs
deleted file mode 100644
index 4d30aa2..0000000
--- a/src/utils/lazy.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-//! [`LazyLock`](std::sync::LazyLock) copy
-use std::cell::UnsafeCell;
-use std::mem::ManuallyDrop;
-use std::panic::{RefUnwindSafe, UnwindSafe};
-
-union Data<T, F> {
- value: ManuallyDrop<T>,
- f: ManuallyDrop<F>,
-}
-
-pub struct Lock<T, F = fn() -> T> {
- data: UnsafeCell<Data<T, F>>,
-}
-
-impl<T, F: FnOnce() -> T> Lock<T, F> {
- #[inline]
- pub const fn new(f: F) -> Lock<T, F> {
- Lock {
- data: UnsafeCell::new(Data {
- f: ManuallyDrop::new(f),
- }),
- }
- }
-
- #[inline]
- /// SAFETY: CALL ONLY ONCE! NOT CHECKED
- pub unsafe fn load(this: &Lock<T, F>) {
- let data = &mut *this.data.get();
- let f = ManuallyDrop::take(&mut data.f);
- let value = f();
- data.value = ManuallyDrop::new(value);
- }
-}
-
-impl<T, F> Lock<T, F> {
- #[inline]
- /// SAFETY: CALL [load] FIRST!
- pub unsafe fn get(&self) -> &T {
- &(*self.data.get()).value
- }
-}
-
-unsafe impl<T: Sync + Send, F: Send> Sync for Lock<T, F> {}
-impl<T: RefUnwindSafe + UnwindSafe, F: UnwindSafe> RefUnwindSafe for Lock<T, F> {}
-impl<T: UnwindSafe, F: UnwindSafe> UnwindSafe for Lock<T, F> {}
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 7314caa..6e781fe 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,5 +1,3 @@
pub mod array;
pub mod image;
-pub use self::image::{ImageUtils, Overlay, RepeatNew as Repeat};
-pub mod lazy;
-pub use lazy::Lock;
+pub use self::image::{Image, ImageHolder, ImageUtils, Overlay, RepeatNew as Repeat};