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

131 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
```rust
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:
```toml
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
```bash
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
```