Files
minecraft-text-generator/docs/superpowers/specs/2026-03-23-png-image-exporter-design.md

4.8 KiB
Raw Blame History

PNG Image Exporter — Design Spec

Date: 2026-03-23 Status: Approved

Problem

Users generating Minecraft text structures need a visual reference showing which blocks to place and where. Current exporters (mcfunction, litematica) produce machine-readable files; there is no human-readable image guide.

Solution

Add a "png" format to the exporter registry. The output is a PNG image with a color-coded grid — one cell per block position — plus a legend mapping colors to Minecraft block names.

Visual Design

Cell style

  • Each cell: cell_size × cell_size pixels with 1px gaps between cells (gaps act as grid lines)
  • Filled cells show a letter code in the center (B = Body, O = Outline, S = Shadow)
  • Empty/air positions: dark background color, no letter

Colors

VoxelType Color Hex Letter
Body Blue #4a90d9 B
Outline Gray #888888 O
Shadow Tan/gold #c0a060 S
Empty Dark #222222
Background Dark #222222

Depth handling

All z-layers are stacked vertically in one PNG. Each layer has a "Layer N" header row above its grid.

Legend

Below all layers: one row per active VoxelType showing a color swatch, the letter code, and the full Minecraft block name (e.g. B minecraft:stone).

CLI Interface

New flag added to crates/bin:

--cell-size <PIXELS>    Cell size in pixels for PNG export (16 or 32) [default: 16]

The --format png flag selects the image exporter. --format all includes PNG at 16px (default).

Architecture

New file: crates/exporters/src/image_export.rs

pub struct PngExporter {
    palette: BlockPalette,
    cell_size: u32,
}

impl PngExporter {
    pub fn new(palette: &BlockPalette, cell_size: u32) -> Self
}

impl StructureExporter for PngExporter {
    fn export(&self, grid: &VoxelGrid) -> anyhow::Result<Vec<u8>>;
    fn file_extension(&self) -> &'static str; // "png"
}

Private helpers:

  • image_dimensions(grid, cell_size) -> (u32, u32) — pure dimension calculation
  • draw_cell(img, px, py, cell_size, color) — fill a rectangle
  • draw_letter(img, px, py, cell_size, ch, color) — render one char from embedded bitmap font
  • draw_layer_label(img, y, z_index, cell_size) — "Layer N" header row
  • draw_text_row(img, x, y, text, cell_size) — render a text string
  • draw_legend(img, y_start, palette, cell_size) — legend section

Text rendering

Letters are rendered using an embedded 5×7 bitmap font (const FONT_5X7: [[u8; 7]; 128]). Each font pixel is scaled by cell_size / 8 (so 2× at 16px, 4× at 32px). No external font files or font-rendering crates required.

Registry integration

crates/exporters/src/lib.rs changes:

  • Register ("png", |p| Box::new(PngExporter::new(p, 16))) — enables --format all
  • Add pub fn build_png(palette: &BlockPalette, cell_size: u32) -> Box<dyn StructureExporter> — used by CLI for user-specified cell size

The generic ExporterFactory = fn(&BlockPalette) -> Box<dyn StructureExporter> signature is unchanged. No changes to McFunctionExporter or LitematicaExporter.

CLI dispatch (crates/bin/src/cli.rs)

When "png" appears in format_names, the CLI calls exporters::build_png(&palette, cli.cell_size) directly (to honour --cell-size) rather than the generic registry path.

Image layout (per-pixel coordinates)

gap = 1

layer_block_height = (cell_size + 4) + gap + height * (cell_size + gap) - gap
total_grid_height  = depth * layer_block_height + (depth - 1) * gap
img_width          = gap + width * (cell_size + gap)
img_height         = total_grid_height + gap + legend_height

Row y=0 in VoxelGrid is the bottom row; the image renders row height-1-y from the top so text reads naturally top-to-bottom.

Dependencies

Only crates/exporters/Cargo.toml gains a new dependency:

image = { version = "0.25", default-features = false, features = ["png"] }

No changes to crates/lib or crates/bin dependencies.

Tests

All in crates/exporters/src/image_export.rs:

  1. dimensions_single_layer — formula correct for 1 layer
  2. dimensions_multi_layer — height increases per additional layer
  3. export_produces_valid_png — output starts with PNG magic bytes [137, 80, 78, 71, ...]
  4. export_empty_grid_no_panic — all-air grid produces valid output
  5. export_multi_layer_larger_than_single — 3-layer > 1-layer byte count
  6. letter_for_each_voxel_type — B/O/S mapping is correct

Verification

cargo build
cargo test -p exporters
cargo run -- --text "Hi" --font /path/to/font.ttf --format png --cell-size 32 --out test_out
# Verify: test_out.png is a valid image showing the block grid + legend