A texture packing CLI written in Rust. Packs .png sprites from a directory into a single texture atlas image and generates a sprite map describing where each sprite landed (C, JSON, SSM, or binary SPR formats).
- Recursively discovers PNG sprites in a directory (deterministic, sorted order).
- Packs them into a configurable atlas using one of seven packing strategies (
skyline,skyline-bf,skyline-fast,genetic,maxrects,guillotine,best) from the bundledlib/rect_packcrate. - Configurable per-sprite padding to prevent edge bleeding.
- Outputs the atlas as a PNG plus a sprite map in one of several formats (C, JSON, SSM, binary SPR).
- Parallel sprite decoding and atlas compositing via
--threads(defaults to every available core); placement itself remains sequential and deterministic. - No-overwrite guard and progress reporting to stderr.
- A recent Rust toolchain (tested with Rust 1.99).
- No system libraries: PNG decoding/encoding uses the pure-Rust
imagecrate.
cargo build # produces ./target/debug/tpack
cargo run -- <args> # build and run in one step
cargo build --release # produces ./target/release/tpack
The binary can also be built and installed with a Makefile (see below).
tpack [options] <sprites_dir> <atlas_path> <sprite_map_path>
Arguments:
| Argument | Description |
|---|---|
sprites_dir |
Directory to scan recursively for .png sprites |
atlas_path |
Output atlas image path (PNG) |
sprite_map_path |
Output sprite map path |
Options:
| Flag | Default | Description |
|---|---|---|
--width <n> |
2048 |
Atlas width (8–4096; ignored with --auto-size) |
--height <n> |
2048 |
Atlas height (8–4096; ignored with --auto-size) |
--auto-size |
on |
Pick the smallest square power-of-two atlas that fits all sprites (default) |
--no-auto-size |
Use the explicit/default --width/--height instead of auto-sizing |
|
--spr-root <dir> |
Project root: spr hash paths relative to this directory |
|
--sprite-padding <n> |
0 |
Padding pixels around each sprite (0–128) |
--packing-mode <m> |
skyline |
Packing algorithm: skyline, skyline-bf, skyline-fast, genetic, maxrects, guillotine, or best; aliases --algorithm / --pack-algorithm |
--seed <n> |
123456789 |
Seed for the genetic packing mode |
--genetic-pop <n> |
40 |
Population size for the genetic packing mode |
--genetic-gens <n> |
60 |
Generations for the genetic packing mode |
--threads <n> |
all cores | Worker threads for decoding/compositing (minimum: 1) |
--scale-mode <m> |
linear |
SPR atlas scale mode: nearest, linear, or pixel-art |
--no-sort |
Disable largest-first pre-sorting | |
--map-format <f> |
auto |
Sprite map format: auto, c, structured-c, json, ssm, spr |
--map-enum-prefix <p> |
SPRITE_ |
Enum prefix for structured-c output |
--map-array-name <n> |
sprites |
Array name for c / structured-c output |
--map-array-type <t> |
SDL_Rect |
Element type for structured-c output |
--no-overwrite |
Refuse to overwrite existing output files | |
--incremental |
Skip the rebuild when the atlas and sprite map are newer than every source sprite | |
-h, -help |
Show help |
The sprite map format is inferred from the output file extension when --map-format auto (the default): .json, .ssm, .c, and .spr are recognized; structured-c requires the explicit flag.
Auto-sizing is the default: unless --no-auto-size is given (or --width/--height are explicitly provided, which disables it), the tool ignores --width/--height and probes increasing square power-of-two sizes (64 → 128 → … up to 4096) until the configured packing mode fits every sprite, then uses the smallest size that works. This is handy when the sprite set varies or you always want the tightest GPU-friendly texture. A sprite wider or taller than 4096 (or a set that outgrows 4096×4096) can never fit and is reported as an AtlasTooSmall error.
- JSON: an object keyed by sprite image path with
x,y,w,hrectangles. - SSM: tab-separated records, one per sprite:
S\t<path>\t<x>\t<y>\t<w>\t<h>. - C: a plain C array of
{ filename, x, y, w, h }entries. - structured-c: a C header with a generated
enumof sprite identifiers plus an array indexed by those enum values, guarded by<prefix>_IMPLEMENTATIONso it can be included for declarations and instantiated in one translation unit. - SPR: a compact binary atlas sprite map (see
docs/spr-format-spec.md). Each sprite is identified by the FNV-1a 32-bit hash of its source path, so lookups areO(log n)binary searches over the hash array. By default the FULL discovered path is hashed (e.g.assets/units/christina/0001.png); pass--spr-root <dir>to hash paths relative to a project root instead (e.g. with--spr-root assets,assets/units/christina/0001.pnghashes asunits/christina/0001.png). The atlas scale mode for SPR is emitted as the configured--scale-mode(linear by default).
With --incremental, tpack first checks whether a rebuild is actually needed. The atlas PNG and the sprite map file are both required to exist and be newer than every source sprite under sprites_dir (source directory mtimes are counted too, so adding, removing, or renaming a sprite triggers a rebuild). If the outputs are already current, tpack prints that they are up to date and exits 0 without re-reading any sprite or rewriting any file.
Build configuration (atlas size, padding, heuristic, map format, ...) is assumed not to change between runs; staleness is detected purely from filesystem modification times of the source files, so only source edits force a repack. Without --incremental, tpack always does a full rebuild (the default, unchanged behavior).
tpack --width 2048 --height 2048 --map-format json \
assets/sprites assets/out/atlas.png assets/out/sprites.json
0— success (including-h/-help)1— packing or I/O failure (e.g. atlas too small, output exists with--no-overwrite), or an image failed to load2— invalid command-line arguments
Progress and diagnostics are written to stderr so stdout remains clean for scripting.
A Makefile provides common targets:
make build # compile ./target/release/tpack
make vet # run clippy, denying warnings
make test # run unit tests for the whole workspace
make harness # run the integration harness (pack test/sprites)
make clean # remove build artifacts
make install # install to $(PREFIX)/bin (default /usr/local)
make help # list targets
Override the toolchain with make CARGO=/path/to/cargo.
Unit tests cover the bundled lib crates (lib/rect_pack, lib/cli-parser, lib/hashing); run them with cargo test --workspace. Integration coverage is provided by tests/harness.rs, a harness that spawns the built tool against the fixtures in test/sprites/:
make harness # or: cargo test --test harness -- --nocapture
This regenerates the harness outputs under test/textures/. Reference outputs (e.g. test/textures/parse_json.c) illustrate how the JSON/SSM maps are consumed.
The tool is a single Rust crate (tpack) split across modules by concern, plus three bundled general-purpose lib crates:
| File | Responsibility |
|---|---|
src/main.rs |
CLI entry point + option resolution |
src/cli.rs |
CLI surface: Options struct, flag table, usage text |
src/config.rs |
constants, Map_Format enum, string→format helpers |
src/atlas.rs |
packing orchestration + atlas PNG output |
src/sprites.rs |
sprite discovery + decoding |
src/packer.rs |
skyline rect packing + parallel pixel compositing |
src/parallel.rs |
scoped-thread helpers for decoding/compositing |
src/sprite_map.rs |
sprite map output writers (C/JSON/SSM/SPR) |
src/incremental.rs |
mtime-based output staleness check for --incremental |
src/util.rs |
shared helpers (escaping, identifier checks) |
lib/rect_pack/ |
standalone rectangle packer crate (skyline + more) |
lib/cli-parser/ |
generic option parser (no tpack flag knowledge) |
lib/hashing/ |
FNV-1a hashing (used by the spr format) |
tests/harness.rs |
process-spawning integration harness |