Command-line sprite sheet generator.
This project started after using TexturePacker and looking for free, open-source CLI tools to support a sprite sheet generation pipeline.
- Keep it simple.
- Do one thing, and do it right.
- Prioritize output-driven workflows.
- Work naturally with
|and>. - Keep dependencies minimal.
- Automate setup during compilation when possible (for example, downloading dependencies).
- Input flexibility: accept sprite frames of any size.
- Prioritize generated image optimization for GPU usage over packing algorithm runtime.
- Focus on usefulness.
- No GUI: the CLI can later be used to build one.
- Be usable in command lines, CI/CD, and Git-based pipelines.
- Encourage AI-assisted workflows (for example, Codex) for faster iteration and maintenance.
- Stay open source and free.
Build:
cmake .
makeIf stb/ headers are missing and you want CMake to fetch them:
cmake -DSPRAT_DOWNLOAD_STB=ON -DSTB_REF=master .
makeGenerate layout first (most common workflow):
./spratlayout ./frames > layout.txtInspect layout text:
head -n 20 layout.txtPack PNG from that layout:
./spratpack < layout.txt > spritesheet.pngOptional one-pipe run:
./spratlayout ./frames --trim-transparent --padding 2 | ./spratpack > spritesheet.pngConvert layout to JSON/CSV/XML/CSS:
./spratconvert --transform json < layout.txt > layout.jsonManual page:
man ./man/sprat-cli.1cmake .
makeThis builds three binaries:
spratlayoutspratpackspratconvert
Run the end-to-end pipeline test:
ctest --test-dir tests --output-on-failureThis test generates tiny PNG fixtures, runs spratlayout to produce layout text,
then runs spratpack and verifies the output is a valid PNG.
spratlayout scans a folder of input images (or a plaintext list of image paths) and prints a text layout to stdout:
./spratlayout ./frames > layout.txtIf the first argument is a file, spratlayout treats it as a newline-separated list of image paths. Blank lines and lines beginning with # are ignored. Relative paths are resolved relative to the list file, each path must exist, be a regular image file (.png, .jpg, .bmp, etc.), and they are loaded in the order listed; otherwise the command fails.
Profile differences (concise):
desktop: MaxRects + GPU-oriented selection.mobile:desktopbehavior with default limits2048x2048.space: MaxRects + tighter area preference.fast: shelf-style packing + GPU-oriented selection (faster runtime).css: shelf-style packing + area-oriented selection (stable/simple CSS workflows).legacy: POT output + default limits1024x1024.
spratlayout options:
--profile desktop|mobile|legacy|space|fast|css(default:fast)--padding N(default:0)--max-combinations N(default:0= auto/unlimited; caps compact candidate trials)--scale F(default:1, for example0.5for half-size output)--trim-transparent(auto-crop transparent borders)--max-width N/--max-height N(optional atlas limits)--threads N(override worker count for compact profile search; default: auto)
Layout caching:
spratlayoutkeeps metadata and output caches in the system temp directory (for example/tmpon Linux/macOS,%TEMP%on Windows).- Cache entries are reused when inputs and options are unchanged.
- Cache entries older than one hour are pruned automatically.
Why these options help:
--padding N: avoids texture bleeding/artifacts from sampling and subpixel math.--scale F: generate smaller atlases for lower-resolution targets (for example mobile variants).--trim-transparent: removes empty borders to reduce atlas usage.--max-width/--max-height: enforce hardware/platform texture limits.spratpack --frame-lines: visual debug of sprite bounds, spacing, and overlaps.
Example recipes:
# 1) fast (default): quicker shelf-style packing
./spratlayout ./frames > layout_fast_default.txt
# 2) mobile: desktop behavior + default 2048x2048 atlas limits
./spratlayout ./frames --profile mobile > layout_mobile.txt
# 3) space: tighter area packing
./spratlayout ./frames --profile space > layout_space.txt
# 4) fast: quicker shelf-style packing
./spratlayout ./frames --profile fast > layout_fast.txt
# 5) legacy: POT-oriented output + default 1024x1024 limits
./spratlayout ./frames --profile legacy > layout_legacy.txt
# 6) css: shelf-style profile for CSS sprite workflows
./spratlayout ./frames --profile css > layout_css.txtSize/quality recipes:
# Trim transparent borders before packing
./spratlayout ./frames --profile desktop --trim-transparent > layout_trim.txt
# Add 2px padding between sprites
./spratlayout ./frames --profile desktop --padding 2 > layout_padding.txt
# Hard atlas limits (max-width/max-height)
./spratlayout ./frames --profile desktop --max-width 1024 --max-height 1024 > layout_1024.txt
# Combine trim + padding + explicit limits
./spratlayout ./frames --profile mobile --trim-transparent --padding 2 \
--max-width 2048 --max-height 2048 > layout_mobile_tuned.txtRendering recipes with frame lines:
# Draw sprite outlines on the packed sheet
./spratpack --frame-lines --line-width 1 --line-color 255,0,0 < layout_desktop.txt > spritesheet_lines.png
# End-to-end pipeline: layout + frame lines
./spratlayout ./frames --profile desktop --trim-transparent --padding 2 | \
./spratpack --frame-lines --line-width 2 --line-color 0,255,0 > spritesheet_pipeline_lines.pngTrim benchmark (repeatable local comparison):
./scripts/benchmark-trim.sh ./build/spratlayout ./frames 5Scale recipe (smaller output for lower resolutions):
./spratlayout ./frames --profile mobile --scale 0.5 > layout_mobile_half.txt
./spratpack < layout_mobile_half.txt > spritesheet_mobile_half.pngThe output format is:
atlas <width>,<height>scale <factor>sprite "<path>" <x>,<y> <w>,<h>
When --trim-transparent is enabled, sprite lines include crop offsets:
sprite "<path>" <x>,<y> <w>,<h> <left>,<top> <right>,<bottom>
Example output from:
./spratlayout ./frames --trim-transparent > layout.txtatlas 1631,1963
scale 1
sprite "./tests/png/Run (6).png" 0,0 335,495 109,54 123,7
sprite "./tests/png/RunShoot (6).png" 345,0 373,495 109,54 85,7
sprite "./tests/png/RunShoot (2).png" 728,0 362,492 121,54 84,10spratconvert reads layout text from stdin and writes transformed output to stdout.
The term transform is used because conversion is template-driven and data-oriented.
List built-in transforms:
./spratconvert --list-transformsUse a built-in transform:
./spratconvert --transform json < layout.txt > layout.json
./spratconvert --transform csv < layout.txt > layout.csv
./spratconvert --transform xml < layout.txt > layout.xml
./spratconvert --transform css < layout.txt > layout.cssOptional extra data files:
./spratconvert --transform json --markers markers.json --animations animations.json < layout.txt > layout.jsonBuilt-in transform files live in transforms/:
transforms/json.transformtransforms/csv.transformtransforms/xml.transformtransforms/css.transform
Each transform is section-based:
- Use explicit open/close tags for sections, for example
[meta]...[/meta]. [meta]for metadata likename,description,extension[header]printed once before sprites[if_markers]/[if_no_markers]conditional blocks based on marker items[markers_header],[markers],[marker],[markers_separator],[markers_footer]marker loop sections[sprites]container with[sprite]item template repeated for each sprite (required)[separator]inserted between sprite entries[if_animations]/[if_no_animations]conditional blocks based on animation items[animations_header],[animations],[animation],[animations_separator],[animations_footer]animation loop sections[footer]printed once after sprites
Common placeholders:
{{atlas_width}},{{atlas_height}},{{scale}},{{sprite_count}}{{index}},{{name}},{{path}},{{x}},{{y}},{{w}},{{h}}{{src_x}},{{src_y}},{{trim_left}},{{trim_top}},{{trim_right}},{{trim_bottom}}- Escaped sprite fields:
{{name_json}},{{name_csv}},{{name_xml}},{{name_css}},{{path_json}},{{path_csv}},{{path_xml}},{{path_css}} - Per-sprite markers:
{{sprite_markers_count}},{{sprite_markers_json}},{{sprite_markers_csv}},{{sprite_markers_xml}},{{sprite_markers_css}} - Marker loop placeholders:
{{marker_index}},{{marker_name}},{{marker_type}}{{marker_x}},{{marker_y}},{{marker_radius}},{{marker_w}},{{marker_h}}{{marker_vertices}},{{marker_vertices_json}},{{marker_vertices_csv}},{{marker_vertices_xml}},{{marker_vertices_css}}{{marker_sprite_index}},{{marker_sprite_name}},{{marker_sprite_path}}
- Animation loop placeholders:
{{animation_index}},{{animation_name}}{{animation_sprite_count}},{{animation_sprite_indexes}},{{animation_sprite_indexes_json}},{{animation_sprite_indexes_csv}}
- Extra file placeholders:
{{has_markers}},{{has_animations}},{{marker_count}},{{animation_count}}{{markers_path}},{{animations_path}}{{markers_raw}},{{animations_raw}}{{markers_json}},{{markers_csv}},{{markers_xml}},{{markers_css}}{{animations_json}},{{animations_csv}},{{animations_xml}},{{animations_css}}
Sprite names default to the source file basename without extension (for example ./frames/run_01.png becomes run_01).
--markers expects JSON with sprite associations. markers must be an array of objects with at least name and type.
Supported marker types:
point:x,ycircle:x,y,radiusrectangle:x,y,w,hpolygon:vertices(ordered list of{x,y}objects) Example:{"sprites":{"./frames/a.png":{"markers":[{"name":"hit","type":"point","x":3,"y":5}]}}}.--animationsexpects JSON timelines (for example{"timelines":[{"name":"run","frames":["./frames/a.png","b"]}]}), and frame entries are resolved to sprite indexes by path or sprite name.
Custom transform example:
[meta]
name=compact-log
[/meta]
[header]
atlas={{atlas_width}}x{{atlas_height}} sprites={{sprite_count}}
[/header]
[sprites]
[sprite]
{{index}} {{path}} @ {{x}},{{y}} {{w}}x{{h}}
[/sprite]
[/sprites]
[separator]
;
[/separator]
[footer]
done
[/footer]Run custom transform:
./spratconvert --transform ./my.transform < layout.txt > layout.custom.txtColumn meanings for the sprite line in trim mode:
"<path>": source image path.<x>,<y>: top-left position in the output atlas where the trimmed sprite is placed.<w>,<h>: trimmed width and height written into the atlas.<left>,<top>: pixels trimmed from the left and top of the original image.<right>,<bottom>: pixels trimmed from the right and bottom of the original image.
spratpack reads that layout from stdin and writes the final PNG spritesheet to stdout:
./spratpack < layout.txt > spritesheet.pngOptional frame divider overlay:
--frame-lines(draw sprite rectangle outlines)--line-width N(default:1)--line-color R,G,B[,A](default:255,0,0,255)--threads N(parallel sprite decode/blit when sprite rectangles do not overlap)
Example:
./spratpack --frame-lines --line-width 2 --line-color 0,255,0 < layout.txt > spritesheet.pngYou can also pipe both commands directly:
./spratlayout ./frames | ./spratpack > spritesheet.pngLicense for third-party art is defined by the asset author; verify terms before redistribution.
To regenerate these images:
./scripts/regenerate-readme-assets.shWhat the script does:
- Downloads
https://opengameart.org/sites/default/files/RobotFree.zip - Extracts PNG frames into
README-assets/frames - Reduces frame sizes with ImageMagick (
FRAME_MAX_SIZE, default64x64>) - Generates spritesheets from those reduced frames (without resizing output sheets)
- Generates one image per command in
Example recipes(recipe-01-...pngtorecipe-12-...png)
Sample asset source used in this page: https://opengameart.org/content/the-robot-free-sprite
Recipe 1: Desktop (./spratlayout ./frames --profile desktop > layout_desktop.txt)
Recipe 2: Mobile (./spratlayout ./frames --profile mobile > layout_mobile.txt)
Recipe 3: Space (./spratlayout ./frames --profile space > layout_space.txt)
Recipe 4: Fast (./spratlayout ./frames --profile fast > layout_fast.txt)
Recipe 5: Legacy (./spratlayout ./frames --profile legacy > layout_legacy.txt)
Recipe 6: CSS (./spratlayout ./frames --profile css > layout_css.txt)
Recipe 7: Trim (./spratlayout ./frames --profile desktop --trim-transparent > layout_trim.txt)
Recipe 8: Padding (./spratlayout ./frames --profile desktop --padding 2 > layout_padding.txt)
Recipe 9: Max 1024 (./spratlayout ./frames --profile desktop --max-width 1024 --max-height 1024 > layout_1024.txt)
Recipe 10: Mobile Tuned (./spratlayout ./frames --profile mobile --trim-transparent --padding 2 --max-width 2048 --max-height 2048 > layout_mobile_tuned.txt)
Recipe 11: Frame Lines (./spratpack --frame-lines --line-width 1 --line-color 255,0,0 < layout_desktop.txt > spritesheet_lines.png)
Recipe 12: Pipeline Lines (./spratlayout ./frames --profile desktop --trim-transparent --padding 2 | ./spratpack --frame-lines --line-width 2 --line-color 0,255,0 > spritesheet_pipeline_lines.png)
- https://kenney.nl/assets (CC0/public-domain-style game assets)
- https://opengameart.org/ (mixed licenses, check each pack)
- https://itch.io/game-assets/free/tag-sprites (license varies by author)
Shape and layout:
- https://en.wikipedia.org/wiki/Texture_atlas (texture atlas overview)
- https://github.com/juj/RectangleBinPack (MaxRects and related bin-packing approaches)
- https://www.khronos.org/opengl/wiki/Texture (mipmaps, filtering, and texture behavior)
Color formats and precision:
- https://www.khronos.org/opengl/wiki/Image_Format (normalized, integer, float, and sRGB formats)
- https://learn.microsoft.com/windows/win32/direct3ddds/dx-graphics-dds-pguide (DDS format/container guidance)
Compression formats:
- https://www.khronos.org/opengl/wiki/S3_Texture_Compression (S3TC/BC-style compression in OpenGL)
- https://learn.microsoft.com/windows/win32/direct3d11/texture-block-compression-in-direct3d-11 (BC1-BC7 overview and tradeoffs)
Sampling artifacts and alpha:
- https://learnopengl.com/Advanced-OpenGL/Blending (alpha blending behavior)
- https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing (sampling and edge artifacts)
Platform and engine guidance:
- https://docs.vulkan.org/guide/latest/ (modern cross-platform texture usage guidance)
- https://docs.unity3d.com/Manual/class-TextureImporter.html (Unity import/compression settings)
Suggestions, pull requests, and forks are welcome.
High-impact contribution areas:
- Packaging and distribution:
- Linux packages (deb/rpm), Homebrew formulae, Scoop/Chocolatey, Arch/AUR, Nix, etc.
- Release automation and artifact publication for multiple platforms.
- GUI frontends:
- Desktop/web/mobile wrappers around the CLI pipeline.
- Workflow-focused tools that call
spratlayout,spratpack, andspratconvertunder the hood.
- Engine/runtime integrations:
- Importers/exporters and transform templates for specific game engines or frameworks.
- Community-maintained presets and examples.
- CI/CD and developer tooling:
- Cross-platform build/test matrices.
- Reproducible packaging and versioned release pipelines.
Core scope remains a free UNIX-style CLI. GUI and platform integrations are encouraged as companion projects or optional layers.
MIT. See LICENSE.












