#png #wasm


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

1 unstable release

0.1.0 Oct 18, 2021

#264 in Multimedia

35 downloads per month

MIT license

551 lines

PNG to WASM-4 Source (png2wasm4src)

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


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.


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.

├── 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.

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

use anyhow::Result;

use png2wasm4src::build_sprite_modules_tree;

fn main() -> Result<()> {
    let module = build_sprite_modules_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)?;


fn open_output_file() -> Result<File> {
    let output_directory = PathBuf::from(var("OUT_DIR")?);
    let output_path = output_directory.join("sprites.rs");
    let output_file = OpenOptions::new()

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

pub mod sprites {
    pub mod fonts {
        pub const LETTERS_WIDTH: u32 = 320;
        pub const LETTERS_HEIGHT: u32 = 32;
        pub const LETTERS_FLAGS: u32 = 1; // BLIT_2BPP
        pub const LETTERS: [u8; 200] = [0x12, 0x34, 0x56...];
    pub mod tiles {
        pub const TILES_WIDTH: u32 = 32;
        pub const TILES_HEIGHT: u32 = 32;
        pub const TILES_FLAGS: u32 = 0; // BLIT_1BPP
        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.

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 draw_sprite() {


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.


~75K SLoC