Skip to content

pedroac/sprat-cli

Repository files navigation

sprat-cli

The UNIX-way sprite sheet generator.

sprat-cli screenshot

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.

🚀 Quick Start

Build and Install:

sh build.sh

Generate layout first:

./spratlayout ./frames > layout.txt

Inspect layout text:

cat layout.txt

Pack PNG from that layout:

./spratpack < layout.txt > spritesheet.png

Optional one-pipe run:

./spratlayout ./frames --trim-transparent --padding 2 | ./spratpack > spritesheet.png

New: 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.txt

The 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.json

Detect sprite frames in spritesheets:

./spratframes --spratframes spritesheet.png > frames.spratframes

Extract sprites from spritesheets using frame coordinates:

./spratunpack spritesheet.png frames.spratframes output/

Manual page:

man ./man/sprat-cli.1

Installation

Install binaries, man page, and global profile config:

sudo cmake --install .

Workflow

spratlayout scans a folder of input images (or a plaintext list of image paths) and prints a text layout to stdout:

./spratlayout ./frames > layout.txt

If 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:

  1. --profiles-config PATH (when provided)
  2. ~/.config/sprat/spratprofiles.cfg
  3. spratprofiles.cfg in the same directory as spratlayout
  4. Global installed config (from make install, typically ${prefix}/share/sprat/spratprofiles.cfg)

Each [profile name] section can define:

  • mode=compact|pot|fast
  • optimize=gpu|space
  • max_width and max_height (optional atlas limits)
  • padding (integer >= 0)
  • max_combinations (integer >= 0)
  • scale (number > 0 and <= 1)
  • trim_transparent=true|false
  • threads (integer > 0)
  • source_resolution (WxH)
  • target_resolution (WxH or source)
  • 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 mobile applies the rules stored under mobile.
  • --profile mobile --padding 4 uses mobile and 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:

  • spratlayout keeps metadata and output caches in the system temp directory (for example /tmp on 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.

Recipes

Compact Mode (GPU Optimized)

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.png

compact gpu

Compact Mode (Space Optimized)

Tries to minimize total area, regardless of aspect ratio.

./spratlayout ./frames --mode compact --optimize space --padding 2 > layout.txt
./spratpack < layout.txt > compact_space.png

compact space

Fast Mode

Uses 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.png

fast

Power of Two (POT)

Forces 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.png

pot

Trimming Transparency

Removes 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.png

trim

Resolution Mapping

Automatically 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.png

resolutions

Benchmarking

Trim benchmark (repeatable local comparison):

./scripts/benchmark-trim.sh ./build/spratlayout ./frames 5

Scale 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.png

Resolution-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.png

The 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.txt
atlas 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,10

Layout transforms (spratconvert)

spratconvert 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-transforms

Use 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.css

Optional extra data files:

./spratconvert --transform json --markers markers.json --animations animations.json < layout.txt > layout.json

Built-in transform files live in transforms/:

  • transforms/json.transform
  • transforms/csv.transform
  • transforms/xml.transform
  • transforms/css.transform

Each transform is section-based:

  • Use explicit open/close tags for sections, for example [meta] ... [/meta].
  • [meta] for metadata like name, 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, y
  • circle: x, y, radius
  • rectangle: x, y, w, h
  • polygon: vertices (ordered list of {x,y} objects) Example: {"sprites":{"./frames/a.png":{"markers":[{"name":"hit","type":"point","x":3,"y":5}]}}}. --animations expects 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.txt

Column 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.png

Optional 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.png

You can also pipe both commands directly:

./spratlayout ./frames | ./spratpack > spritesheet.png

License for third-party art is defined by the asset author; verify terms before redistribution.

Free Sprite Sources

Sample asset source used in this page: https://opengameart.org/content/the-robot-free-sprite

Texture Optimization References

Shape and layout:

Color formats and precision:

Compression formats:

Sampling artifacts and alpha:

Platform and engine guidance:

Contributing

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.

License

MIT. See LICENSE.

Support

Buy Me A Coffee

About

Command-line sprite sheet generator

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •