The UNIX-way sprite sheet generator.
sprat-cli is a modular toolkit for generating sprite sheets (texture atlases) from the command line. Unlike monolithic GUI tools, it splits the packing process into discrete, pipeable commands. This makes it perfect for:
- CI/CD Pipelines: Automate asset generation in your build process.
- Shell Scripting: Integrate naturally with
|,>, and standard text tools. - Game Development: Optimized packing algorithms for GPU memory.
- Web & Apps: Export to CSS, JSON, XML, or custom formats.
Build and Install:
sh build.shGenerate layout first:
./spratlayout ./frames > layout.txtInspect layout text:
cat layout.txtPack PNG from that layout:
./spratpack < layout.txt > spritesheet.pngOptional one-pipe run:
./spratlayout ./frames --trim-transparent --padding 2 | ./spratpack > spritesheet.pngNew: Tar File Support
spratlayout now accepts tar archives as input. This is useful for bundling sprite assets or working with compressed archives.
# Regular tar file
./spratlayout sprites.tar > layout.txt
# Compressed tar files (gzip, bzip2, xz)
./spratlayout sprites.tar.gz > layout.txt
./spratlayout sprites.tar.bz2 > layout.txt
./spratlayout sprites.tar.xz > layout.txtThe tool automatically extracts the archive to a temporary directory and processes all image files found within. Temporary directories are cleaned up automatically after processing.
Convert layout to JSON/CSV/XML/CSS:
./spratconvert --transform json < layout.txt > layout.jsonDetect sprite frames in spritesheets:
./spratframes --spratframes spritesheet.png > frames.spratframesExtract sprites from spritesheets using frame coordinates:
./spratunpack spritesheet.png frames.spratframes output/Manual page:
man ./man/sprat-cli.1Install binaries, man page, and global profile config:
sudo cmake --install .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.
Profiles are flexible named rule sets. A profile groups packing rules (for example mode, optimize target, limits, padding, trim, scale, threads) under one name, so you can run --profile NAME instead of repeating many options each time.
Profile definitions are driven by spratprofiles.cfg. The lookup order is:
--profiles-config PATH(when provided)~/.config/sprat/spratprofiles.cfgspratprofiles.cfgin the same directory asspratlayout- Global installed config (from
make install, typically${prefix}/share/sprat/spratprofiles.cfg)
Each [profile name] section can define:
mode=compact|pot|fastoptimize=gpu|spacemax_widthandmax_height(optional atlas limits)padding(integer >= 0)max_combinations(integer >= 0)scale(number > 0 and <= 1)trim_transparent=true|falsethreads(integer > 0)source_resolution(WxH)target_resolution(WxH orsource)resolution_reference=largest|smallest
Add new sections to define custom profiles and refer to them with --profile NAME. Profile values are defaults; command options override them per run.
Examples (concept):
--profile mobileapplies the rules stored undermobile.--profile mobile --padding 4usesmobileand overrides only padding for that run.
spratlayout options:
--profile NAME(default:fast)--profiles-config PATH(override the config file path; can be relative or absolute)--mode compact|pot|fast--optimize gpu|space--padding N(default:0)--max-combinations N(default:0= auto/unlimited; caps compact candidate trials)--scale F(default:1, valid range:(0, 1]; applies before resolution mapping)--trim-transparent/--no-trim-transparent--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: normalize intentionally oversized source sprites before target resolution mapping.--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.
Default behavior. Tries to keep the atlas square-ish but prioritizes width/height that fits well in GPU memory.
!Compact GPU
./spratlayout ./frames --mode compact --optimize gpu --padding 2 > layout.txt
./spratpack < layout.txt > compact_gpu_pad2.pngTries to minimize total area, regardless of aspect ratio.
./spratlayout ./frames --mode compact --optimize space --padding 2 > layout.txt
./spratpack < layout.txt > compact_space.pngUses a shelf packing algorithm. Much faster for huge datasets, but less efficient packing.
./spratlayout ./frames --mode fast --padding 2 > layout.txt
./spratpack < layout.txt > fast.pngForces the output atlas to be a power of two (e.g., 512x512, 1024x512).
./spratlayout ./frames --mode pot --padding 2 > layout.txt
./spratpack < layout.txt > pot.pngRemoves transparent pixels from sprite edges. spratpack can draw frame lines to visualize the trimmed bounds.
./spratlayout ./frames --trim-transparent --padding 2 > layout.txt
./spratpack --frame-lines --line-color 0,255,0 < layout.txt > trim.pngAutomatically scales sprites based on a target resolution. Useful for multi-platform builds (e.g., designing for 4K, building for 1080p).
# Scale = 1920 / 3840 = 0.5
./spratlayout ./frames \
--source-resolution 3840x2160 \
--target-resolution 1920x1080 \
--padding 2 > layout.txt
./spratpack < layout.txt > resolution.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.pngResolution-aware scale recipe:
./spratlayout ./frames --profile mobile \
--source-resolution 3840x2160 --target-resolution 1920x1080 --scale 0.5 \
> layout_mobile_targeted.txt
./spratpack < layout_mobile_targeted.txt > spritesheet_mobile_targeted.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.
Sample asset source used in this page: https://opengameart.org/content/the-robot-free-sprite
- 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 the sprat-cli commands under 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.






