mindustry logic execution, map- and schematic- parsing and rendering
| -rw-r--r-- | Cargo.toml | 8 | ||||
| -rw-r--r-- | src/block/logic.rs | 3 | ||||
| -rw-r--r-- | src/data/renderer.rs | 87 | ||||
| -rw-r--r-- | src/data/schematic.rs | 7 | ||||
| -rw-r--r-- | src/utils/image.rs | 105 | ||||
| -rw-r--r-- | src/utils/mod.rs | 2 |
6 files changed, 129 insertions, 83 deletions
@@ -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}; |