docs: add png image exporter design spec
This commit is contained in:
130
docs/superpowers/specs/2026-03-23-png-image-exporter-design.md
Normal file
130
docs/superpowers/specs/2026-03-23-png-image-exporter-design.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user