PNG to WASM-4 Source (png2wasm4src)

Convert indexed PNG images to Rust source code for WASM-4 engine.

https://gitlab.com/claudiomattera/png2wasm4src

[WASM-4] is an old-style fantasy game console implemented in WebAssembly. Games can be developed in Rust (and in other languages), and the runtime has support for drawing sprites. Sprites must either have a bit depth of one bit per pixel, or two bits per pixel, and must be properly encoded in variables, which can be done using the WASM-4 w4 command-line application.

This crate allows to perform the conversion from within Rust code, which allows to dynamically create variables from PNG images using a build.rs [build script].

Usage

This crate can be used to automatically generate Rust variables from PNG images on cargo build. This way the Rust variables always reflect the current PNG image, and there is no risk of forgetting to update them.

Assume the following crate structure. Directory assets contains a subdirectory sprites, which contains all the sprites. Sprites are organized in subdirectories: sprite letters.png is inside directory fonts, and sprite tiles.png is inside directory tiles.

~~~~plain . ├── assets │ └── sprites │ ├── fonts │ │ └── letters.png │ └── tiles │ └── tiles.png ├── build.rs ├── Cargo.lock ├── Cargo.toml └── src └── lib.rs ~~~~

Now it is possible to generate the Rust code from the sprites PNG image inside a build.rs build script.

~~~~rust use std::env::var; use std::fs::{File, OpenOptions}; use std::io::Write; use std::path::PathBuf;

use anyhow::Result;

use png2wasm4src::buildspritemodules_tree;

fn main() -> Result<()> { let module = buildspritemodules_tree("assets/sprites")?;

// Instruct cargo to re-run the build script if any source PNGs are changed
// cargo:rerun-if-changed=assets/sprites/player.png
// cargo:rerun-if-changed=assets/sprites/monsters/slime.png
// cargo:rerun-if-changed=assets/sprites/monsters/bandit.png
let mut cargo_instructions = String::default();
module.generate_cargo_build_instructions(&mut cargo_instructions)?;
println!("{}", cargo_instructions);

let mut output_file = open_output_file()?;
let module = module.parse()?;
writeln!(output_file, "{}", module)?;

Ok(())

}

fn openoutputfile() -> Result { let outputdirectory = PathBuf::from(var("OUTDIR")?); let outputpath = outputdirectory.join("sprites.rs"); let outputfile = OpenOptions::new() .write(true) .create(true) .open(outputpath)?; Ok(output_file) } ~~~~

The build script generates the following code, and writes it into the file ${OUT_DIR}/sprites.rs.

~~~~rust pub mod sprites { pub mod fonts { pub const LETTERSWIDTH: u32 = 320; pub const LETTERSHEIGHT: u32 = 32; pub const LETTERSFLAGS: u32 = 1; // BLIT2BPP pub const LETTERS: [u8; 200] = [0x12, 0x34, 0x56...]; } pub mod tiles { pub const TILESWIDTH: u32 = 32; pub const TILESHEIGHT: u32 = 32; pub const TILESFLAGS: u32 = 0; // BLIT1BPP pub const TILES: [u8; 30] = [0x12, 0x34, 0x56...]; } } ~~~~

From any of the crate modules (for instance in lib.rs) it is possible to include that file, and use all entities defined there.

~~~~rust use wasm::*;

// Include the generated file in the current module. // // Note: this is done at top level, not inside any function (but it could be // inside a module). include!(concat!(env!("OUT_DIR"), "/sprites.rs"));

fn drawsprite() { blit( sprites::tiles::TILES, 10, 10, sprites::tiles::TILESWIDTH, sprites::tiles::TILESHEIGHT, sprites::tiles::TILESFLAGS, ); blit( sprites::fonts::LETTERS, 10, 10, sprites::fonts::LETTERSWIDTH, sprites::fonts::LETTERSHEIGHT, sprites::fonts::LETTERS_FLAGS, ); } ~~~~

License

Copyright Claudio Mattera 2021

You are free to copy, modify, and distribute this application with attribution under the terms of the [MIT license]. See the License.txt file for details.