mindustry logic execution, map- and schematic- parsing and rendering
make Render return RGB images
bendn 2023-08-05
parent abec237 · commit cfbb676
-rw-r--r--Cargo.toml8
-rw-r--r--src/block/logic.rs3
-rw-r--r--src/data/renderer.rs87
-rw-r--r--src/data/schematic.rs7
-rw-r--r--src/utils/image.rs105
-rw-r--r--src/utils/mod.rs2
6 files changed, 129 insertions, 83 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a7e2ad8..8221619 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mindus"
-version = "1.5.2"
+version = "2.0.0"
edition = "2021"
description = "A library for working with mindustry data formats (eg schematics and maps) (fork of plandustry)"
authors = [
@@ -20,14 +20,12 @@ image = { version = "0.24", features = [], default-features = false }
color-hex = "0.2"
thiserror = "1.0"
bobbin-bits = "0.1"
-blurslice = { version = "0.1", optional = true }
+blurslice = { version = "0.1" }
phf = { version = "0.11", features = ["macros"] }
[features]
-schem_shadow = ["dep:blurslice"]
-map_shadow = ["dep:blurslice"]
bin = ["image/png"]
-default = ["schem_shadow", "bin"]
+default = ["bin"]
[build-dependencies]
image = { version = "0.24", features = ["png"], default-features = false }
diff --git a/src/block/logic.rs b/src/block/logic.rs
index b2f2fb1..0b5afd4 100644
--- a/src/block/logic.rs
+++ b/src/block/logic.rs
@@ -386,8 +386,7 @@ fn read_decompressed(buff: &mut DataRead) -> Result<ProcessorState, ProcessorDes
if !(0..=500 * 1024).contains(&code_len) {
return Err(ProcessorDeserializeError::CodeLength(code_len));
}
- let mut code = vec![];
- code.resize(code_len, 0);
+ let mut code = vec![0; code_len];
buff.read_bytes(&mut code)?;
let code = String::from_utf8(code)?;
let link_cnt = buff.read_u32()? as usize;
diff --git a/src/data/renderer.rs b/src/data/renderer.rs
index 89a1b89..b103deb 100644
--- a/src/data/renderer.rs
+++ b/src/data/renderer.rs
@@ -3,9 +3,11 @@ pub(crate) use super::autotile::*;
use crate::block::environment::METAL_FLOOR;
use crate::block::Rotation;
use crate::team::SHARDED;
-pub(crate) use crate::utils::ImageUtils;
+pub(crate) use crate::utils::{ImageUtils, Overlay, Repeat};
use crate::Map;
-pub(crate) use image::{DynamicImage, RgbaImage};
+pub(crate) use image::{
+ DynamicImage, GenericImage, GenericImageView, Pixel, Rgb, RgbImage, Rgba, RgbaImage,
+};
pub(crate) use std::borrow::{Borrow, BorrowMut};
use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
@@ -125,9 +127,9 @@ impl std::ops::Mul<u32> for Scale {
pub(crate) fn try_load(name: &str, scale: Scale) -> Option<&'static RgbaImage> {
match scale {
- Scale::Quarter => QUAR.get(&name).map(|v| LazyLock::force(v)),
- Scale::Eigth => EIGH.get(&name).map(|v| LazyLock::force(v)),
- Scale::Full => FULL.get(&name).map(|v| LazyLock::force(v)),
+ Scale::Quarter => QUAR.get(name).map(|v| LazyLock::force(v)),
+ Scale::Eigth => EIGH.get(name).map(|v| LazyLock::force(v)),
+ Scale::Full => FULL.get(name).map(|v| LazyLock::force(v)),
// Scale::Half => HALF.get(&name).map(|v| LazyLock::force(v)),
}
}
@@ -169,7 +171,7 @@ where
c.overlay(p.clone().tint(SHARDED.color()));
continue;
}
- c.overlay(&p);
+ c.overlay(p);
}
}
ImageHolder::from(c)
@@ -177,30 +179,32 @@ where
/// trait for renderable objects
pub trait Renderable {
- /// creates a picture of a schematic. Bridges and node connections are not drawn.
- fn render(&self) -> RgbaImage;
+ /// create a picture
+ fn render(&self) -> RgbImage;
}
impl Renderable for Schematic<'_> {
+ /// creates a picture of a schematic. Bridges and node connections are not drawn.
/// ```
/// use mindus::*;
/// let mut s = Schematic::new(2, 3);
/// s.put(0, 0, &block::distribution::DISTRIBUTOR);
/// s.put(0, 2, &block::distribution::ROUTER);
/// s.put(1, 2, &block::walls::COPPER_WALL);
- /// let output /*: RgbaImage */ = s.render();
+ /// let output /*: RgbImage */ = s.render();
/// ```
- fn render(&self) -> RgbaImage {
+ fn render(&self) -> RgbImage {
// fill background
- let mut bg = RgbaImage::new(
+ let mut bg = RgbImage::repeated(
+ &DynamicImage::from(
+ METAL_FLOOR
+ .image(None, None, Rotation::Up, Scale::Full)
+ .own(),
+ )
+ .into_rgb8(),
((self.width + 2) * 32) as u32,
((self.height + 2) * 32) as u32,
);
- bg.repeat(
- METAL_FLOOR
- .image(None, None, Rotation::Up, Scale::Full)
- .borrow(),
- );
let mut canvas = RgbaImage::new(
((self.width + 2) * 32) as u32,
((self.height + 2) * 32) as u32,
@@ -232,23 +236,36 @@ impl Renderable for Schematic<'_> {
(y + 1) * 32,
);
}
-
- #[cfg(feature = "schem_shadow")]
- image::imageops::overlay(&mut bg, canvas.shadow(), 0, 0);
- #[cfg(not(feature = "schem_shadow"))]
- bg.overlay(&canvas);
+ canvas.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) };
+ }
+ }
bg
}
}
impl Renderable for Map<'_> {
- fn render(&self) -> RgbaImage {
+ fn render(&self) -> RgbImage {
let scale = if self.width + self.height < 2000 {
Scale::Quarter
} else {
Scale::Eigth
};
- let mut floor = RgbaImage::new(scale * self.width as u32, scale * self.height as u32);
+ // 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);
for (x, y, j, tile) in self.tiles.iter().enumerate().map(|(j, t)| {
(
@@ -260,19 +277,16 @@ impl Renderable for Map<'_> {
)
}) {
// draw the floor first.
- let img = tile.floor_image(None, scale);
+ let img: &RgbaImage = &tile.floor_image(None, scale);
// println!("draw {tile:?} ({x}, {y}) + {scale:?}");
// assert_eq!(img.width(), scale.px() as u32);
// assert_eq!(img.height(), scale.px() as u32);
- floor.overlay_at(&img, scale * x as u32, scale * y as u32);
+ floor.overlay_at(img, scale * x as u32, scale * y as u32);
if let Some(build) = tile.build() {
let s = build.block.get_size();
let x = x - ((s - 1) / 2) as usize;
let y = y - (s / 2) as usize;
- let ctx = (|| {
- if !build.block.wants_context() {
- return None;
- }
+ let ctx = if build.block.wants_context() {
let pctx = PositionContext {
position: GridPos(x, y),
width: self.width,
@@ -283,22 +297,21 @@ impl Renderable for Map<'_> {
position: pctx,
};
Some(rctx)
- })();
- let img = tile.build_image(ctx.as_ref(), scale);
+ } else {
+ None
+ };
+ let img: &RgbaImage = &tile.build_image(ctx.as_ref(), scale);
// assert_eq!(img.width(), scale * build.block.get_size() as u32);
// assert_eq!(img.height(), scale * build.block.get_size() as u32);
- top.overlay_at(&img, scale * x as u32, scale * y as u32);
+ top.overlay_at(img, scale * x as u32, scale * y as u32);
}
}
- #[cfg(feature = "map_shadow")]
- image::imageops::overlay(&mut floor, top.shadow(), 0, 0);
- #[cfg(not(feature = "map_shadow"))]
- floor.overlay(&top);
+ floor.overlay_at(&top, 0, 0);
floor
}
}
-/// Loads all the images into memory
+/// Loads all the images into memory (about 300mb)
pub fn warmup() {
for map in [&FULL, &QUAR, &EIGH] {
for val in map.values() {
diff --git a/src/data/schematic.rs b/src/data/schematic.rs
index 1e3e513..6d00dcc 100644
--- a/src/data/schematic.rs
+++ b/src/data/schematic.rs
@@ -660,8 +660,7 @@ impl<'l> SchematicSerializer<'l> {
/// assert!(s.get(1, 1).unwrap().unwrap().block.name() == "payload-router");
/// ```
pub fn deserialize_base64(&mut self, data: &str) -> Result<Schematic<'l>, R64Error> {
- let mut buff = Vec::<u8>::new();
- buff.resize(data.len() / 4 * 3 + 1, 0);
+ let mut buff = vec![0; data.len() / 4 * 3 + 1];
let n_out = base64::decode(data.as_bytes(), buff.as_mut())?;
Ok(self.deserialize(&mut DataRead::new(&buff[..n_out]))?)
}
@@ -672,9 +671,7 @@ impl<'l> SchematicSerializer<'l> {
self.serialize(&mut buff, data)?;
let buff = buff.get_written();
// round up because of padding
- let required = 4 * (buff.len() / 3 + usize::from(buff.len() % 3 != 0));
- let mut text = Vec::<u8>::new();
- text.resize(required, 0);
+ let mut text = vec![0; 4 * (buff.len() / 3 + usize::from(buff.len() % 3 != 0))];
let n_out = base64::encode(buff, text.as_mut())?;
// trailing zeros are valid UTF8, but not valid base64
assert_eq!(n_out, text.len());
diff --git a/src/utils/image.rs b/src/utils/image.rs
index 97df689..4095404 100644
--- a/src/utils/image.rs
+++ b/src/utils/image.rs
@@ -1,14 +1,20 @@
-use image::{imageops, Rgb, Rgba, RgbaImage};
+use image::*;
+
+pub trait Overlay<W> {
+ /// Overlay with onto self at coordinates x, y, without blending
+ 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;
+}
pub trait ImageUtils {
/// Tint this image with the color
fn tint(&mut self, color: Rgb<u8>) -> &mut Self;
- /// Repeat with over self
- fn repeat(&mut self, with: &Self) -> &mut Self;
/// Overlay with onto self (does not blend)
fn overlay(&mut self, with: &Self) -> &mut Self;
- /// Overlay with onto self at coordinates x, y, without blending
- fn overlay_at(&mut self, with: &Self, x: u32, y: u32) -> &mut Self;
/// rotate
fn rotate(&mut self, times: u8) -> &mut Self;
/// flip along the horizontal axis
@@ -16,15 +22,74 @@ pub trait ImageUtils {
/// flip along the vertical axis
fn flip_v(&mut self) -> &mut Self;
/// shadow
- #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))]
fn shadow(&mut self) -> &mut Self;
/// silhouette
- #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))]
fn silhouette(&mut self) -> &mut Self;
/// scale a image
fn scale(&self, to: u32) -> Self;
}
+impl Overlay<RgbImage> for RgbImage {
+ fn overlay_at(&mut self, with: &RgbImage, x: u32, y: u32) -> &mut Self {
+ for j in 0..with.height() {
+ for i in 0..with.width() {
+ #[cfg(debug_assertions)]
+ {
+ let get = *with.get_pixel(i, j);
+ self.put_pixel(i + x, j + y, get);
+ }
+ #[cfg(not(debug_assertions))]
+ {
+ let get = unsafe { with.unsafe_get_pixel(i, j) };
+ unsafe { self.unsafe_put_pixel(i + x, j + y, get) };
+ }
+ }
+ }
+ self
+ }
+}
+
+impl Overlay<RgbaImage> for RgbImage {
+ fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self {
+ for j in 0..with.height() {
+ for i in 0..with.width() {
+ let get = unsafe { with.unsafe_get_pixel(i, j) };
+ // solidity
+ if get[3] > 128 {
+ unsafe { self.unsafe_put_pixel(i + x, j + y, Rgb([get[0], get[1], get[2]])) };
+ }
+ }
+ }
+ self
+ }
+}
+
+impl Overlay<RgbaImage> for RgbaImage {
+ fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self {
+ for j in 0..with.height() {
+ for i in 0..with.width() {
+ let get = unsafe { with.unsafe_get_pixel(i, j) };
+ if get[3] > 128 {
+ unsafe { self.unsafe_put_pixel(i + x, j + y, get) };
+ }
+ }
+ }
+ self
+ }
+}
+
+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());
+ }
+ }
+ img
+ }
+}
+
impl ImageUtils for RgbaImage {
fn rotate(&mut self, times: u8) -> &mut Self {
use image::imageops::{rotate180, rotate270, rotate90};
@@ -51,28 +116,6 @@ impl ImageUtils for RgbaImage {
self
}
- fn repeat(&mut self, with: &RgbaImage) -> &mut Self {
- for x in 0..(self.width() / with.width()) {
- for y in 0..(self.height() / with.height()) {
- self.overlay_at(with, x * with.width(), y * with.height());
- }
- }
- self
- }
-
- fn overlay_at(&mut self, with: &RgbaImage, x: u32, y: u32) -> &mut Self {
- for j in 0..with.height() {
- for i in 0..with.width() {
- use image::{GenericImage, GenericImageView};
- let get = unsafe { with.unsafe_get_pixel(i, j) };
- if get[3] > 128 {
- unsafe { self.unsafe_put_pixel(i + x, j + y, get) };
- }
- }
- }
- self
- }
-
fn overlay(&mut self, with: &RgbaImage) -> &mut Self {
if self.len() % 4 != 0 || with.len() % 4 != 0 {
unsafe { std::hint::unreachable_unchecked() };
@@ -90,7 +133,6 @@ impl ImageUtils for RgbaImage {
imageops::resize(self, to, to, imageops::Nearest)
}
- #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))]
fn silhouette(&mut self) -> &mut Self {
for pixel in self.pixels_mut() {
if pixel[3] < 128 {
@@ -102,7 +144,6 @@ impl ImageUtils for RgbaImage {
self
}
- #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))]
fn shadow(&mut self) -> &mut Self {
let mut shadow = self.clone();
shadow.silhouette();
@@ -118,8 +159,6 @@ impl ImageUtils for RgbaImage {
for y in 0..shadow.height() {
let Rgba([r, g, b, a]) = self.get_pixel_mut(x, y);
if *a == 0 {
- use image::GenericImageView;
- // SAFETY: yes
let p = unsafe { shadow.unsafe_get_pixel(x, y) };
*r = p[0];
*g = p[0];
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index b9aa815..4018850 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1,3 +1,3 @@
pub mod array;
pub mod image;
-pub use self::image::ImageUtils;
+pub use self::image::{ImageUtils, Overlay, RepeatNew as Repeat};