mindustry logic execution, map- and schematic- parsing and rendering
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | build.rs | 59 | ||||
| -rw-r--r-- | src/block/distribution.rs | 55 | ||||
| -rw-r--r-- | src/block/drills.rs | 14 | ||||
| -rw-r--r-- | src/block/environment.rs | 8 | ||||
| -rw-r--r-- | src/block/liquid.rs | 17 | ||||
| -rw-r--r-- | src/block/logic.rs | 45 | ||||
| -rw-r--r-- | src/block/mod.rs | 6 | ||||
| -rw-r--r-- | src/block/payload.rs | 3 | ||||
| -rw-r--r-- | src/block/power.rs | 12 | ||||
| -rw-r--r-- | src/block/simple.rs | 7 | ||||
| -rw-r--r-- | src/block/turrets.rs | 5 | ||||
| -rw-r--r-- | src/block/units.rs | 76 | ||||
| -rw-r--r-- | src/block/walls.rs | 11 | ||||
| -rw-r--r-- | src/data/autotile.rs | 8 | ||||
| -rw-r--r-- | src/data/map.rs | 14 | ||||
| -rw-r--r-- | src/data/renderer.rs | 112 | ||||
| -rw-r--r-- | src/data/schematic.rs | 9 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/utils/image.rs | 31 |
20 files changed, 296 insertions, 201 deletions
@@ -1,6 +1,6 @@ [package] name = "mindus" -version = "1.4.6" +version = "1.4.7" edition = "2021" description = "A library for working with mindustry data formats (eg schematics and maps) (fork of plandustry)" authors = [ @@ -21,7 +21,6 @@ image = { version = "0.24", features = ["png"], default-features = false } color-hex = "0.2" tinyrand = "0.5" tinyrand-std = "0.5" -fast_image_resize = "2.7" thiserror = "1.0" bobbin-bits = "0.1" blurslice = { version = "0.1", optional = true } @@ -13,25 +13,60 @@ fn main() { println!("cargo:rerun-if-changed=assets/"); println!("cargo:rerun-if-changed=build.rs"); let o = std::env::var("OUT_DIR").unwrap(); - let mut f = File::create(Path::new(&o).join("asset")).unwrap(); - let mut n = 1usize; - f.write_all(b"phf::phf_map! {").unwrap(); - let mut s = String::new(); // idk write_all / write wasnt working + let o = Path::new(&o); + let mut full = File::create(o.join("full.rs")).unwrap(); + // 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 = 2usize; + for mut f in [&full, &eigh, &quar] { + f.write_all(b"phf::phf_map! {\n").unwrap(); + } for e in walkdir.into_iter().filter_map(|e| e.ok()) { let path = e.path(); if path.is_file() && let Some(e) = path.extension() && e == "png" { let p = DynamicImage::from_decoder(PngDecoder::new(BufReader::new(File::open(path).unwrap())).unwrap()).unwrap().into_rgba8(); - let x = p.width(); - let y = p.height(); let path = path.with_extension(""); let path = path.file_name().unwrap().to_str().unwrap(); - let mut f = File::create(Path::new(&o).join(n.to_string())).unwrap(); - f.write_all(&p.into_raw()).unwrap(); - println!("writing {path:?}"); - s += &format!("\t\"{path}\" => r!(LazyLock::new(|| RgbaImage::from_vec({x}, {y}, include_bytes!(concat!(env!(\"OUT_DIR\"), \"/{n}\")).to_vec()).unwrap())),\n"); + macro_rules! writ { + ($ext:ident / $scale:literal) => { + let mut buf = File::create(o.join(n.to_string() + "-" + stringify!($ext))).unwrap(); + let new = if $scale == 1 { + p.clone() + } else { + // boulders + let (mx, my) = if p.width() + p.height() == 48+48 { + (32, 32) + // vents + } else if path.contains("vent") { + (32, 32) + } else { + (p.height(), p.width()) + }; + image::imageops::resize( + &p, + mx / $scale, + my / $scale, + image::imageops::Nearest, + ) + }; + let x = new.width(); + let y = new.height(); + buf.write_all(&new.into_raw()).unwrap(); + writeln!($ext, + r#" "{path}" => r!(LazyLock::new(|| RgbaImage::from_vec({x}, {y}, include_bytes!(concat!(env!("OUT_DIR"), "/{n}-{}")).to_vec()).unwrap())),"#, + stringify!($ext) + ).unwrap(); + }; + } + writ!(full / 1); + // writ!(half + 0.5); + writ!(quar / 4); + writ!(eigh / 8); n += 1; } } - f.write_all(s.as_bytes()).unwrap(); - f.write_all(b"}").unwrap(); + for mut f in [full, eigh, quar] { + f.write_all(b"}").unwrap(); + } } diff --git a/src/block/distribution.rs b/src/block/distribution.rs index 06ce5b8..aaa9528 100644 --- a/src/block/distribution.rs +++ b/src/block/distribution.rs @@ -8,10 +8,7 @@ use crate::item; make_simple!( ConveyorBlock, - |_, name, _, ctx: Option<&RenderingContext>, rot: Rotation| { - let ctx = ctx.unwrap(); // we set want_context to true - tile(ctx, name, rot) - }, + |_, name, _, ctx: Option<&RenderingContext>, rot, s| tile(ctx.unwrap(), name, rot, s), |_, _, _, buff: &mut DataRead| { // format: // - amount: `i32` @@ -31,10 +28,7 @@ make_simple!( make_simple!( DuctBlock, - |_, name, _, ctx: Option<&RenderingContext>, rot| { - let ctx = ctx.unwrap(); - tile(ctx, name, rot) - }, + |_, name, _, ctx: Option<&RenderingContext>, rot, s| tile(ctx.unwrap(), name, rot, s), |_, _, _, buff: &mut DataRead| { // format: // - rec_dir: `i8` @@ -43,10 +37,10 @@ make_simple!( true ); -make_simple!(JunctionBlock => |_, _, _, buff: &mut DataRead| { read_directional_item_buffer(buff) }); -make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation| { - let mut base = load("duct-base"); - let mut top = load(name); +make_simple!(JunctionBlock => |_, _, _, buff| { read_directional_item_buffer(buff) }); +make_simple!(SimpleDuctBlock, |_, name, _, _, rot: Rotation, s| { + let mut base = load("duct-base", s); + let mut top = load(name, s); top.rotate(rot.rotated(false).count()); base.overlay(&top); base @@ -58,10 +52,11 @@ fn draw_stack( _: Option<&State>, ctx: Option<&RenderingContext>, rot: Rotation, + s: Scale, ) -> ImageHolder { let ctx = ctx.unwrap(); let mask = mask(ctx, rot, name); - let edge = load(&format!("{name}-edge")); + let edge = load(&format!("{name}-edge"), s); let edgify = |skip, to: &mut RgbaImage| { for i in 0..4 { if i == skip { @@ -72,7 +67,7 @@ fn draw_stack( to.overlay(&edge); } }; - let gimme = |n: u8| load(&format!("{name}-{n}")); + let gimme = |n: u8| load(&format!("{name}-{n}"), s); let empty = ctx.cross[rot.count() as usize].map_or(true, |(v, _)| v.name != name); // mindustry says fuck this and just draws the arrow convs in schems but im better than that @@ -220,16 +215,20 @@ impl BlockLogic for ItemBlock { state: Option<&State>, _: Option<&RenderingContext>, rot: Rotation, + s: Scale, ) -> ImageHolder { - let mut p = load(name); + let mut p = load(name, s); if let Some(state) = state { - if let Some(s) = Self::get_state(state) { - let mut top = load(match name { - "unit-cargo-unload-point" => "unit-cargo-unload-point-top", - "unloader" => "unloader-center", - _ => "center", - }); - p.overlay(top.tint(s.color())); + if let Some(item) = Self::get_state(state) { + let mut top = load( + match name { + "unit-cargo-unload-point" => "unit-cargo-unload-point-top", + "unloader" => "unloader-center", + _ => "center", + }, + s, + ); + p.overlay(top.tint(item.color())); return p; } } @@ -237,20 +236,20 @@ impl BlockLogic for ItemBlock { return p; } if name == "duct-router" { - let mut arrow = load("top"); + let mut arrow = load("top", s); arrow.rotate(rot.rotated(false).count()); p.overlay(&arrow); p } else if name == "duct-unloader" { - let mut top = load("duct-unloader-top"); + let mut top = load("duct-unloader-top", s); top.rotate(rot.rotated(false).count()); p.overlay(&top); - let mut arrow = load("top"); + let mut arrow = load("top", s); arrow.rotate(rot.rotated(false).count()); p.overlay(&arrow); p } else { - let mut null = load("cross-full"); + let mut null = load("cross-full", s); null.overlay(&p); null } @@ -455,15 +454,15 @@ impl BlockLogic for BridgeBlock { Ok(()) } - fn draw( &self, name: &str, _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } } diff --git a/src/block/drills.rs b/src/block/drills.rs index f46788c..8bd6960 100644 --- a/src/block/drills.rs +++ b/src/block/drills.rs @@ -5,22 +5,22 @@ use crate::block::*; make_simple!( DrillBlock, - |_, name, _, _, rot: Rotation| { + |_, name, _, _, rot: Rotation, s| { if matches!(name, "large-plasma-bore" | "plasma-bore") { - let mut base = load(name); - let mut top = load(&format!("{name}-top")); + let mut base = load(name, s); + let mut top = load(&format!("{name}-top"), s); top.rotate(rot.rotated(false).count()); base.overlay(&top); return base; } - load(name) + load(name, s) }, |_, _, _, buff: &mut DataRead| read_drill(buff) ); make_simple!(ExtractorBlock); -make_simple!(WallCrafter, |_, _, _, _, rot: Rotation| { - let mut base = load("cliff-crusher"); - let mut top = load("cliff-crusher-top"); +make_simple!(WallCrafter, |_, _, _, _, rot: Rotation, s| { + let mut base = load("cliff-crusher", s); + let mut top = load("cliff-crusher-top", s); top.rotate(rot.rotated(false).count()); base.overlay(&top); base diff --git a/src/block/environment.rs b/src/block/environment.rs index ef38266..5e73499 100644 --- a/src/block/environment.rs +++ b/src/block/environment.rs @@ -11,15 +11,15 @@ macro_rules! register_env { $($field => EnvironmentBlock::new($size, true, &[]);)* ); - make_simple!(EnvironmentBlock, |_, name, _, _, _| { + make_simple!(EnvironmentBlock, |_, name, _, _, _, s| { let mut rand = StdRand::seed(ClockSeed::default().next_u64()); match name { $($field => { #[allow(clippy::reversed_empty_ranges)] match $variations { - 2..=6 => load(&format!("{}{}", $field, rand.next_range(1usize..$variations))), - 1 => load($field), - 0 => ImageHolder::from(RgbaImage::new($size * 32, $size * 32)), + 2..=6 => load(&format!("{}{}", $field, rand.next_range(1usize..$variations)), s), + 1 => load($field, s), + 0 => ImageHolder::from(RgbaImage::new(s * $size, s * $size)), _ => unreachable!(), } },)* diff --git a/src/block/liquid.rs b/src/block/liquid.rs index a4cadb6..4bb8106 100644 --- a/src/block/liquid.rs +++ b/src/block/liquid.rs @@ -13,12 +13,12 @@ use crate::utils::ImageUtils; make_simple!(LiquidBlock); make_simple!( ConduitBlock, - |_, name, _, ctx: Option<&RenderingContext>, rot| { + |_, name, _, ctx: Option<&RenderingContext>, rot, s| { let ctx = ctx.unwrap(); let mask = mask(ctx, rot, name); let (index, rot, flip) = mask2rotations(mask, rot); - let tile = rotations2tile((index, rot, flip), &format!("{name}-top")); - let mut bottom = load(&format!("conduit-bottom-{index}")); + let tile = rotations2tile((index, rot, flip), &format!("{name}-top"), s); + let mut bottom = load(&format!("conduit-bottom-{index}"), s); flrot(flip, rot, &mut bottom); bottom.tint(image::Rgb([74, 75, 83])); bottom.overlay(tile.borrow()); @@ -119,16 +119,17 @@ impl BlockLogic for FluidBlock { state: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - let mut p = load(name); + let mut p = load(name, s); if let Some(state) = state { - if let Some(s) = Self::get_state(state) { - let mut top = load("center"); - p.overlay(top.tint(s.color())); + if let Some(liq) = Self::get_state(state) { + let mut top = load("center", s); + p.overlay(top.tint(liq.color())); return p; } } - let mut null = load("cross-full"); + let mut null = load("cross-full", s); null.overlay(&p); null } diff --git a/src/block/logic.rs b/src/block/logic.rs index 9595673..b2f2fb1 100644 --- a/src/block/logic.rs +++ b/src/block/logic.rs @@ -150,29 +150,33 @@ impl BlockLogic for CanvasBlock { state: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { if let Some(state) = state { let state = self.clone_state(state); let p = state.downcast::<RgbImage>().unwrap(); - // SAFETY: canvas_size cannot be 0, so width & height musnt be 0, and size cannot be 0 - let p = unsafe { - DynamicImage::from( - RgbImage::from_raw( - self.canvas_size as u32, - self.canvas_size as u32, - p.into_raw(), - ) - .unwrap(), - ) - .into_rgba8() - .scale((self.size as u32 * 32) - 14) + let offset = match s { + Scale::Full => 7, + // Scale::Half => 3, + Scale::Quarter => 2, + Scale::Eigth => 1, }; - let mut borders = load(n); - borders.overlay_at(&p, 7, 7); + let p = DynamicImage::from( + RgbImage::from_raw( + self.canvas_size as u32, + self.canvas_size as u32, + p.into_raw(), + ) + .unwrap(), + ) + .into_rgba8() + .scale((s * self.size as u32) - offset * 2); + let mut borders = load(n, s); + borders.overlay_at(&p, offset, offset); return borders; } - let mut def = RgbaImage::new(self.size as u32 * 32, self.size as u32 * 32); + 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]; @@ -235,8 +239,9 @@ impl BlockLogic for MessageLogic { _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } fn deserialize_state(&self, data: DynData) -> Result<Option<State>, DeserializeError> { @@ -337,11 +342,12 @@ impl BlockLogic for SwitchLogic { state: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - let mut base = load("switch"); + let mut base = load("switch", s); if let Some(state) = state { if *Self::get_state(state) { - let on = load("switch-on"); + let on = load("switch-on", s); base.overlay(&on); return base; } @@ -409,8 +415,9 @@ impl BlockLogic for ProcessorLogic { _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { diff --git a/src/block/mod.rs b/src/block/mod.rs index c7feb9a..c1440c5 100644 --- a/src/block/mod.rs +++ b/src/block/mod.rs @@ -63,6 +63,7 @@ pub trait BlockLogic { state: Option<&State>, context: Option<&RenderingContext>, rot: Rotation, + scale: Scale, ) -> ImageHolder; fn want_context(&self) -> bool { @@ -199,8 +200,11 @@ impl Block { state: Option<&State>, context: Option<&RenderingContext>, rot: Rotation, + scale: Scale, ) -> ImageHolder { - self.logic.as_ref().draw(&self.name, state, context, rot) + self.logic + .as_ref() + .draw(&self.name, state, context, rot, scale) } /// size. diff --git a/src/block/payload.rs b/src/block/payload.rs index 20485bb..c9961a1 100644 --- a/src/block/payload.rs +++ b/src/block/payload.rs @@ -72,8 +72,9 @@ impl BlockLogic for PayloadBlock { _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { diff --git a/src/block/power.rs b/src/block/power.rs index 529282e..2ef86f5 100644 --- a/src/block/power.rs +++ b/src/block/power.rs @@ -10,12 +10,12 @@ make_simple!(NuclearGeneratorBlock => |_, _, _, buff: &mut DataRead| read_nuclea make_simple!(ImpactReactorBlock => |_, _, _, buff: &mut DataRead| read_impact(buff)); make_simple!(HeaterGeneratorBlock => |_, _, _, buff: &mut DataRead| read_heater(buff)); make_simple!(BatteryBlock); -make_simple!(DiodeBlock, |_, _, _, _, rot: Rotation| { - let mut base = load("diode"); +make_simple!(DiodeBlock, |_, _, _, _, rot: Rotation, s| { + let mut base = load("diode",s); if rot == Rotation::Right { return base; } - let mut top = load("diode-arrow"); + let mut top = load("diode-arrow",s); top.rotate(rot.rotated(false).count()); base.overlay(&top); base @@ -136,8 +136,9 @@ impl BlockLogic for ConnectorBlock { _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } } @@ -222,8 +223,9 @@ impl BlockLogic for LampBlock { _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } fn clone_state(&self, state: &State) -> State { diff --git a/src/block/simple.rs b/src/block/simple.rs index 40cacf5..20a9f22 100644 --- a/src/block/simple.rs +++ b/src/block/simple.rs @@ -90,9 +90,10 @@ macro_rules! make_simple { state: Option<&crate::block::State>, context: Option<&crate::data::renderer::RenderingContext>, rot: crate::block::Rotation, + scale: crate::data::renderer::Scale, ) -> crate::data::renderer::ImageHolder { #[allow(clippy::redundant_closure_call)] - $draw(self, name, state, context, rot) + $draw(self, name, state, context, rot, scale) } fn want_context(&self) -> bool { @@ -123,14 +124,14 @@ macro_rules! make_simple { ($name: ident => $read: expr) => { crate::block::simple::make_simple!( $name, - |m: &Self, n: &str, _, _, _| crate::data::renderer::read(n, m.get_size()), + |m: &Self, n, _, _, _, s| crate::data::renderer::read(n, m.get_size(), s), $read ); }; ($name: ident) => { crate::block::simple::make_simple!( $name, - |m: &Self, n: &str, _, _, _| crate::data::renderer::read(n, m.get_size()), + |m: &Self, n, _, _, _, s| crate::data::renderer::read(n, m.get_size(), s), |_, _, _, _| Ok(()), false ); diff --git a/src/block/turrets.rs b/src/block/turrets.rs index d1617fc..6510d5c 100644 --- a/src/block/turrets.rs +++ b/src/block/turrets.rs @@ -43,14 +43,15 @@ fn draw_turret( _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { let path = match name { "breach" | "diffuse" | "sublimate" | "titan" | "disperse" | "afflict" | "lustre" | "scathe" | "malign" | "smite" => format!("reinforced-block-{}", me.get_size()), _ => format!("block-{}", me.get_size()), }; - let mut base = load(&path); - base.overlay(&load(name)); + let mut base = load(&path, s); + base.overlay(&load(name, s)); base } diff --git a/src/block/units.rs b/src/block/units.rs index 21a7b3b..536446a 100644 --- a/src/block/units.rs +++ b/src/block/units.rs @@ -33,27 +33,38 @@ use crate::unit; // ) // } -make_simple!(ConstructorBlock, |me: &Self, name, _, _, rot: Rotation| { - let mut base = load(name); +make_simple!(ConstructorBlock, |me: &Self, + name, + _, + _, + rot: Rotation, + s| { + let mut base = load(name, s); let times = rot.rotated(false).count(); - let mut out = load(&match name { - "additive-reconstructor" - | "multiplicative-reconstructor" - | "exponential-reconstructor" - | "tetrative-reconstructor" => format!("factory-out-{}", me.size), - _ => format!("factory-out-{}-dark", me.size), - }); + let mut out = load( + &match name { + "additive-reconstructor" + | "multiplicative-reconstructor" + | "exponential-reconstructor" + | "tetrative-reconstructor" => format!("factory-out-{}", me.size), + _ => format!("factory-out-{}-dark", me.size), + }, + s, + ); out.rotate(times); base.overlay(&out); - let mut input = load(&match name { - "additive-reconstructor" - | "multiplicative-reconstructor" - | "exponential-reconstructor" - | "tetrative-reconstructor" => format!("factory-in-{}", me.size), - _ => format!("factory-in-{}-dark", me.size), - }); + let mut input = load( + &match name { + "additive-reconstructor" + | "multiplicative-reconstructor" + | "exponential-reconstructor" + | "tetrative-reconstructor" => format!("factory-in-{}", me.size), + _ => format!("factory-in-{}-dark", me.size), + }, + s, + ); input.rotate(times); base.overlay(&input); @@ -77,9 +88,9 @@ make_simple!(ConstructorBlock, |me: &Self, name, _, _, rot: Rotation| { // } // } - base.overlay(&load(&format!("{name}-top"))); + base.overlay(&load(&format!("{name}-top"), s)); if matches!(name, "mech-assembler" | "tank-assembler" | "ship-assembler") { - let mut side = load(&format!("{name}-side")); + let mut side = load(&format!("{name}-side"), s); side.rotate(times); base.overlay(&side); } @@ -207,20 +218,27 @@ impl BlockLogic for AssemblerBlock { _: Option<&State>, _: Option<&RenderingContext>, rot: Rotation, + s: Scale, ) -> ImageHolder { - let mut base = load(name); - let mut out = load(match name { - "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", - _ => "factory-out-3-dark", - }); + let mut base = load(name, s); + let mut out = load( + match name { + "ground-factory" | "air-factory" | "naval-factory" => "factory-out-3", + _ => "factory-out-3-dark", + }, + s, + ); out.rotate(rot.rotated(false).count()); base.overlay(&out); - base.overlay(&load(&match name { - "ground-factory" | "air-factory" | "naval-factory" => { - format!("factory-top-{}", self.size) - } - _ => format!("{name}-top"), - })); + base.overlay(&load( + &match name { + "ground-factory" | "air-factory" | "naval-factory" => { + format!("factory-top-{}", self.size) + } + _ => format!("{name}-top"), + }, + s, + )); base } diff --git a/src/block/walls.rs b/src/block/walls.rs index 6b782a7..7ae1d22 100644 --- a/src/block/walls.rs +++ b/src/block/walls.rs @@ -6,22 +6,22 @@ use crate::data::renderer::{load, read_with}; use tinyrand::{Rand, RandRange, Seeded, StdRand}; use tinyrand_std::clock_seed::ClockSeed; -make_simple!(WallBlock, |_, name, _, _, _| { +make_simple!(WallBlock, |_, name, _, _, _, s| { macro_rules! pick { ($name: literal => load $n: literal) => {{ let mut rand = StdRand::seed(ClockSeed::default().next_u64()); - load(&format!("{}{}", $name, rand.next_range(1usize..$n))) + load(&format!("{}{}", $name, rand.next_range(1usize..$n)), s) }}; } match name { "thruster" => { const SFX: &[&str; 1] = &["-top"]; - read_with("thruster", SFX, 4u32) + read_with("thruster", SFX, 4u32, s) } "scrap-wall" => pick!("scrap-wall" => load 5), "scrap-wall-large" => pick!("scrap-wall-large" => load 3), "scrap-wall-huge" => pick!("scrap-wall-huge" => load 3), - _ => load(name), + _ => load(name, s), } }); @@ -87,8 +87,9 @@ impl BlockLogic for DoorBlock { _: Option<&State>, _: Option<&RenderingContext>, _: Rotation, + s: Scale, ) -> ImageHolder { - read(name, self.size) + read(name, self.size, s) } fn data_from_i32(&self, _: i32, _: GridPos) -> Result<DynData, DataConvertError> { diff --git a/src/data/autotile.rs b/src/data/autotile.rs index 76b0e5b..a79195e 100644 --- a/src/data/autotile.rs +++ b/src/data/autotile.rs @@ -92,8 +92,8 @@ fn print_crosses(v: Vec<Cross<'_>>, height: usize) -> String { s } -pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation) -> ImageHolder { - rotations2tile(mask2rotations(mask(ctx, rot, name), rot), name) +pub fn tile(ctx: &RenderingContext<'_>, name: &str, rot: Rotation, s: Scale) -> ImageHolder { + rotations2tile(mask2rotations(mask(ctx, rot, name), rot), name, s) } pub fn mask2rotations(mask: U4, rot: Rotation) -> (u8, u8, u8) { @@ -220,8 +220,8 @@ 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) -> ImageHolder { - let mut p = load(&format!("{name}-{index}")); +pub fn rotations2tile((index, rot, flip): (u8, u8, u8), name: &str, scale: Scale) -> ImageHolder { + let mut p = load(&format!("{name}-{index}"), scale); flrot(flip, rot, p.borrow_mut()); p } diff --git a/src/data/map.rs b/src/data/map.rs index 7478a66..355aaf3 100644 --- a/src/data/map.rs +++ b/src/data/map.rs @@ -134,20 +134,20 @@ impl<'l> Tile<'l> { 1 } - pub fn floor_image(&self, context: Option<&RenderingContext>) -> ImageHolder { - let mut i = self.floor.image(None, context, Rotation::Up); + pub fn floor_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { + let mut i = self.floor.image(None, context, Rotation::Up, s); if let Some(ore) = self.ore { - i.overlay(ore.image(None, context, Rotation::Up).borrow()); + i.overlay(ore.image(None, context, Rotation::Up, s).borrow()); } i } - pub fn build_image(&self, context: Option<&RenderingContext>) -> ImageHolder { + pub fn build_image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { // building covers floore let Some(b) = &self.build else { unreachable!(); }; - b.image(context) + b.image(context, s) } } @@ -243,9 +243,9 @@ impl<'l> Build<'l> { } } - pub fn image(&self, context: Option<&RenderingContext>) -> ImageHolder { + pub fn image(&self, context: Option<&RenderingContext>, s: Scale) -> ImageHolder { self.block - .image(self.state.as_ref(), context, self.rotation) + .image(self.state.as_ref(), context, self.rotation, s) } pub fn name(&self) -> &str { diff --git a/src/data/renderer.rs b/src/data/renderer.rs index b1b2e0a..de64e3f 100644 --- a/src/data/renderer.rs +++ b/src/data/renderer.rs @@ -20,8 +20,11 @@ macro_rules! r { }}; } -type Cache = phf::Map<&'static str, &'static LazyLock<RgbaImage>>; -static CACHE: Cache = include!(concat!(env!("OUT_DIR"), "/asset")); +type Images = phf::Map<&'static str, &'static LazyLock<RgbaImage>>; +static FULL: Images = include!(concat!(env!("OUT_DIR"), "/full.rs")); +// static HALF: Images = include!(concat!(env!("OUT_DIR"), "/half.rs")); +static QUAR: Images = include!(concat!(env!("OUT_DIR"), "/quar.rs")); +static EIGH: Images = include!(concat!(env!("OUT_DIR"), "/eigh.rs")); pub enum ImageHolder { Borrow(&'static RgbaImage), @@ -91,14 +94,44 @@ impl From<RgbaImage> for ImageHolder { } } -pub(crate) fn try_load(name: &str) -> Option<&'static RgbaImage> { +#[derive(Debug, Copy, Clone)] +pub enum Scale { + Full, + // Half, + Quarter, + Eigth, +} + +impl Scale { + fn px(self) -> u8 { + match self { + Self::Full => 32, + Self::Quarter => 32 / 4, + Self::Eigth => 32 / 8, + } + } +} + +impl std::ops::Mul<u32> for Scale { + type Output = u32; + fn mul(self, rhs: u32) -> u32 { + self.px() as u32 * rhs + } +} + +pub(crate) fn try_load(name: &str, scale: Scale) -> Option<&'static RgbaImage> { let key = name.to_string(); - CACHE.get(&key).map(|v| LazyLock::force(v)) + match scale { + Scale::Quarter => QUAR.get(&key).map(|v| LazyLock::force(v)), + Scale::Eigth => EIGH.get(&key).map(|v| LazyLock::force(v)), + Scale::Full => FULL.get(&key).map(|v| LazyLock::force(v)), + // Scale::Half => HALF.get(&key).map(|v| LazyLock::force(v)), + } } -pub(crate) fn load(name: &str) -> ImageHolder { +pub(crate) fn load(name: &str, scale: Scale) -> ImageHolder { ImageHolder::from( - try_load(name) + try_load(name, scale) .ok_or_else(|| format!("failed to load {name}")) .unwrap(), ) @@ -107,20 +140,28 @@ pub(crate) fn load(name: &str) -> ImageHolder { const SUFFIXES: &[&str; 9] = &[ "-bottom", "-mid", "-base", "", "-left", "-right", "-top", "-over", "-team", ]; -pub(crate) fn read<S>(name: &str, size: S) -> ImageHolder +pub(crate) fn read<S>(name: &str, size: S, scale: Scale) -> ImageHolder where S: Into<u32> + Copy, { - read_with(name, SUFFIXES, size) + read_with(name, SUFFIXES, size, scale) } -pub(crate) fn read_with<S>(name: &str, suffixes: &'static [&'static str], size: S) -> ImageHolder +pub(crate) fn read_with<S>( + name: &str, + suffixes: &'static [&'static str], + size: S, + scale: Scale, +) -> ImageHolder where S: Into<u32> + Copy, { - let mut c = RgbaImage::new(size.into() * 32, size.into() * 32); + let mut c = RgbaImage::new( + size.into() * scale.px() as u32, + size.into() * scale.px() as u32, + ); for suffix in suffixes { - if let Some(p) = try_load(&format!("{name}{suffix}")) { + if let Some(p) = try_load(&format!("{name}{suffix}"), scale) { if suffix == &"-team" { c.overlay(p.clone().tint(SHARDED.color())); continue; @@ -152,7 +193,11 @@ impl Renderable for Schematic<'_> { ((self.width + 2) * 32) as u32, ((self.height + 2) * 32) as u32, ); - bg.repeat(METAL_FLOOR.image(None, None, Rotation::Up).borrow()); + 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, @@ -174,8 +219,12 @@ 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( - tile.image(ctx.as_ref(), tile.get_rotation().unwrap_or(Rotation::Up)) - .borrow(), + tile.image( + ctx.as_ref(), + tile.get_rotation().unwrap_or(Rotation::Up), + Scale::Full, + ) + .borrow(), (x + 1) * 32, (y + 1) * 32, ); @@ -192,12 +241,12 @@ impl Renderable for Schematic<'_> { impl Renderable for Map<'_> { fn render(&self) -> RgbaImage { let scale = if self.width + self.height < 2000 { - 8 + Scale::Quarter } else { - 4 + Scale::Eigth }; - let mut floor = RgbaImage::new(self.width as u32 * scale, self.height as u32 * scale); - let mut top = RgbaImage::new(self.width as u32 * scale, self.height as u32 * scale); + let mut floor = RgbaImage::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)| { ( (j % self.width), @@ -208,12 +257,11 @@ impl Renderable for Map<'_> { ) }) { // draw the floor first. - floor.overlay_at( - // SAFETY: [`load_raw`] forces nonzero image size - unsafe { &tile.floor_image(None).own().scale(scale) }, - x as u32 * scale, - y as u32 * scale, - ); + let img = 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); if let Some(build) = tile.build() { let s = build.block.get_size(); let x = x - ((s - 1) / 2) as usize; @@ -233,17 +281,10 @@ impl Renderable for Map<'_> { }; Some(rctx) })(); - top.overlay_at( - // SAFETY: tile.size can never be 0, and [`load_raw`] forces nonzero. - unsafe { - &tile - .build_image(ctx.as_ref()) - .own() - .scale(tile.size() as u32 * scale) - }, - x as u32 * scale, - y as u32 * scale, - ); + let img = 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); } } #[cfg(feature = "map_shadow")] @@ -284,6 +325,7 @@ fn all_blocks() { }, }), Rotation::Up, + Scale::Quarter, ); } } diff --git a/src/data/schematic.rs b/src/data/schematic.rs index 5c34bcb..1e3e513 100644 --- a/src/data/schematic.rs +++ b/src/data/schematic.rs @@ -59,8 +59,13 @@ impl<'l> Placement<'l> { } /// draws this placement in particular - pub fn image(&self, context: Option<&RenderingContext>, rot: Rotation) -> ImageHolder { - self.block.image(self.get_state(), context, rot) + pub fn image( + &self, + context: Option<&RenderingContext>, + rot: Rotation, + s: Scale, + ) -> ImageHolder { + self.block.image(self.get_state(), context, rot, s) } /// set the state @@ -1,5 +1,5 @@ //! crate for dealing with mindustry -#![feature(lazy_cell, slice_as_chunks)] +#![feature(lazy_cell, array_chunks)] mod access; pub mod block; mod content; diff --git a/src/utils/image.rs b/src/utils/image.rs index 6abe314..8291c7d 100644 --- a/src/utils/image.rs +++ b/src/utils/image.rs @@ -1,6 +1,4 @@ -use fast_image_resize as fr; use image::{imageops, Rgb, Rgba, RgbaImage}; -use std::num::NonZeroU32; pub trait ImageUtils { /// Tint this image with the color @@ -24,9 +22,7 @@ pub trait ImageUtils { #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))] fn silhouette(&mut self) -> &mut Self; /// scale a image - /// - /// SAFETY: to and width and height cannot be 0. - unsafe fn scale(self, to: u32) -> Self; + fn scale(&self, to: u32) -> Self; } impl ImageUtils for RgbaImage { @@ -82,10 +78,8 @@ impl ImageUtils for RgbaImage { let local = std::mem::take(self); let mut own = local.into_raw(); let other = with.as_raw(); - for (i, other_pixels) in unsafe { other.as_chunks_unchecked::<4>() } - .iter() - .enumerate() - { + assert!(own.len() % 4 == 0 && other.len() % 4 == 0); + for (i, other_pixels) in other.array_chunks::<4>().enumerate() { if other_pixels[3] > 128 { let own_pixels = unsafe { own.get_unchecked_mut(i * 4..i * 4 + 4) }; own_pixels.copy_from_slice(other_pixels); @@ -95,23 +89,8 @@ impl ImageUtils for RgbaImage { self } - unsafe fn scale(self, to: u32) -> Self { - debug_assert_ne!(to, 0); - debug_assert_ne!(self.width(), 0); - debug_assert_ne!(self.height(), 0); - let to = NonZeroU32::new_unchecked(to); - let src = fr::Image::from_vec_u8( - NonZeroU32::new_unchecked(self.width()), - NonZeroU32::new_unchecked(self.height()), - self.into_vec(), - fr::PixelType::U8x4, - ) - .unwrap(); - let mut dst = fr::Image::new(to, to, fr::PixelType::U8x4); - fr::Resizer::new(fr::ResizeAlg::Nearest) - .resize(&src.view(), &mut dst.view_mut()) - .unwrap(); - RgbaImage::from_raw(to.get(), to.get(), dst.into_vec()).unwrap() + fn scale(&self, to: u32) -> Self { + imageops::resize(self, to, to, imageops::Nearest) } #[cfg(any(feature = "map_shadow", feature = "schem_shadow"))] |