Skip to content

Release#13

Merged
pedroac merged 41 commits into
mainfrom
release
Jun 24, 2026
Merged

Release#13
pedroac merged 41 commits into
mainfrom
release

Conversation

@pedroac

@pedroac pedroac commented Jun 24, 2026

Copy link
Copy Markdown
Owner

No description provided.

pedroac and others added 30 commits March 27, 2026 01:06
- Replace stbi_write_png_to_mem with stbi_write_png_to_func in
  spratunpack_command.cpp to avoid "undeclared identifier" errors in some
  stb_image_write.h versions.
- Reorder CMakeLists.txt to detect dependencies (libarchive, gettext, zopflipng)
  before defining targets. This fixes "archive.h not found" errors on macOS where
  headers are in non-standard brew paths.
- Remove redundant source file inclusion in executable targets by linking to
  spratcore library.
- Replace stbi_write_png_to_mem with stbi_write_png_to_func in
  spratunpack_command.cpp for better portability and fix build errors.
- Remove redundant extern "C" around stb_image_write.h include.
- Reorder CMakeLists.txt to ensure dependencies (libarchive, gettext, zopflipng)
  are detected before target definition, fixing macOS build failures.
- Optimize build by removing redundant source inclusion in executable targets.
- Bump version to v0.2.7 in VERSION file to resolve immutable release error in
  GitHub Actions.
- Exclude temporary documentation snapshots in .gitignore
- Add unit tests for compare_natural() function covering: natural number sorting, mixed alphanumeric, edge cases
- All 14 tests passing (2 unit + 12 integration)
- Add -DBUILD_TESTING=ON to CMake configure steps in both jobs
- Update ctest invocations to emit JUnit XML test results
- Add artifact upload steps for test results (7-day retention)
- Add artifact retention policy (30 days for packages)
- Add dependency caching for Linux apt packages and macOS Homebrew
- Update Linux apt-get to use --no-upgrade flag for cached packages
- Implement --deduplicate flag with FNV-1a content hashing
- Implement --gpu-compress dxt1|dxt5 for GPU-native texture formats
- Implement --dilate N for color bleeding to prevent GPU filtering artifacts
- Add libsquish library integration (optional dependency)
- Bump cache format version (2 → 3)
- Update CMakeLists.txt with squish detection
Release v0.3.0 includes the new --deduplicate feature with support for:
- Exact deduplication (byte-for-byte identical images)
- Perceptual deduplication (visually similar images using dHash)
- Full cache management and downstream compatibility

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Enhance spratlayout command to support three deduplication modes instead of
a boolean flag:

- none: No deduplication (default)
- exact: Hash-detect byte-for-byte identical sprites (FNV-1a)
- perceptual: Detect visually similar sprites (dHash algorithm)

Changes:
- Updated --deduplicate argument to accept mode value
- Modified cache signature to include full mode string
- Added validation for mode values with helpful error messages
- Updated function signatures to pass mode string instead of boolean

This enables the sprat-gui UI to properly use the deduplication modes
as expected by its settings UI.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The spratcore static library includes command implementations that use
libarchive functions, but wasn't linking against libarchive. This caused
unresolved symbol linker errors on Windows when executables (spratlayout,
spratpack, spratunpack) tried to link against spratcore.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Hoist vector/map allocations out of hot loops, replace ostringstream
with string concatenation and snprintf, pre-group sprites by atlas
index, rewrite prune_free_rects with mark-and-sweep, use region-only
copies in dilate, deduplicate pixel extraction in unpack, and replace
queue with reusable vector stack in flood fill.
  - Add support for negated filter attributes in convert/layout commands
  - Improve --threads parameter documentation with clearer behavior and defaults
  - Remove max_combinations from config keys documentation
  - Update transform definitions
…parsing

  When input is a list file, the working_folder (the list file path itself)
  was passed directly as the root for relative path computation, producing
  incorrect sprite paths like "../frames/b.png". Pass parent_path() instead
  at both build_layout_output_text call sites.

  The layout parser also didn't handle the "root" line emitted by
  spratlayout, causing spratpack to fail with "Unknown line: root ...".
  Add root field to Layout struct, parse it in layout_parser, and resolve
  relative sprite/alias paths against it in spratpack.
…ount64

  __builtin_popcountll is a GCC/Clang intrinsic not available on MSVC.
  Add a popcount64() wrapper that uses __popcnt64 via <intrin.h> on Windows
  and __builtin_popcountll elsewhere.
…placeholders

  - Replace specialized conditional blocks ([atlas_path], [rotated],
    [if_animations], etc.) with a general [if ATTR="VALUE"]...[/if]
    syntax usable at any nesting level
  - Restructure JSON transform: move sprites to a flat top-level array;
    group spatial data into nested rect, pivot, and trim objects
  - Add {{name_css}} placeholder for CSS-safe sprite class names; CSS
    transform now uses .sprite-{{name_css}} instead of .sprite-{{index}}
  - Add {{sprite_names}}/{{sprite_names_json}}/{{sprite_names_csv}}
    placeholders for animation frame display names
  - Add --transforms-dir and --default-profiles-config diagnostic flags
  - Add label= field to profile definitions; adjust desktop/legacy
    default padding; enable rotate in space profile
  - Fix parallel CMake build race when copying transforms/ by
    consolidating into a single sprat_copy_transforms custom target
  - Improve image cache: validate dimensions on save, evict oldest
    entries when over the size limit instead of refusing to save
  - Fix sprite path resolution: index all path suffixes so absolute
    or temp-prefixed paths match layout entries correctly
  - Capitalize built-in transform names (JSON, CSV, XML, CSS)
  - Update man page date and document new features
  On multi-config generators (MSVC, Xcode) CMake places binaries in a
  per-config subdirectory (e.g. build/Release/), but sprat_copy_transforms
  was copying transforms/ to the bare CMAKE_RUNTIME_OUTPUT_DIRECTORY or
  CMAKE_CURRENT_BINARY_DIR without the config suffix. At runtime
  find_transforms_dir() looks for transforms/ next to the executable and
  could not find them, causing the convert and output_pattern CI tests to
  fail on Windows with "Missing transform in list" and "Failed to open
  transform file".

  Detect multi-config generators via GENERATOR_IS_MULTI_CONFIG and append
  /$<CONFIG> to the destination path so transforms land in the same
  directory as the binaries, matching the behaviour of the old per-target
  $<TARGET_FILE_DIR:...> commands.
  Search order is now: executable directory → user directory →
  global system directory. The CWD candidate for profiles config
  is removed. The bare relative "transforms" fallback is replaced
  with the global install path.

  Platform user directories are now correct:
  - Linux: XDG_CONFIG_HOME (profiles) and XDG_DATA_HOME (transforms),
    defaulting to ~/.config and ~/.local/share respectively
  - macOS: ~/Library/Application Support/sprat/ for both
    (was ~/Library/Preferences/ which is for .plist files only)
  - Windows: %APPDATA%\sprat\ (unchanged)
  spratlayout no longer sorts sprites by filename when reading from a
  folder. The default is now none for all input types, allowing the
  packer to reorder sprites freely for better packing efficiency.
  Use --sort name to restore alphabetical ordering.

  Man page updates:
  - spratlayout synopsis now shows folder|file|- to document all
    three input modes (directory, list file, TAR from stdin)
  - Input modes section explains each mode including the - stdin TAR
    path and its use with spratunpack output
  - spratunpack atlas input and output documented as separate sections
  - Profile config search order updated: beside executable, then user
    directory (with correct per-platform paths), then global
  - Removed --max-combinations (feature was removed)
  - --sort description clarified; default updated to none
  - Transform file search path documented in spratconvert section
  - Examples expanded: list file input, full pipeline with conversion,
    stdin TAR round-trip (spratunpack | spratlayout -), tar cf - pipe
    - Parallelize sprite loading and optimize compact packing search.
    Implement dynamic scheduling (atomic work index) for packing threads.
    - Merge guided and shelf packing passes and add early-exit pruning.
    - Enable recursive directory scanning by default in spratlayout.
    - Add support for .spratlayoutignore and 'exclude' directive in list files.
    - Introduce 'root' directive for animations, markers, and list files.
    - Document changes in README.md and man pages.
    - Add regression tests for recursive scanning and exclusions.
  Replace the animation alias h-flip/v-flip token pair with a single
  flip keyword followed by a value: "h", "v", or "vh". The layout file
  syntax changes from:

    alias "run" h-flip v-flip

  to:

    alias "run" flip vh

  The template variable is similarly unified: the two boolean vars
  h_flip/v_flip are replaced by a single string var flip. Built-in
  transforms (JSON, CSV, XML) are updated accordingly.

  Add --sort stable[:<metric>] to spratlayout. This mode sorts sprites
  deterministically by a geometric metric (area, maxside, height, width,
  or perimeter; default: area) with natural path order as the tiebreaker.
  Sources are always sorted by path first, fixing filesystem
  non-determinism. The stable order is preserved through the packing loop
  without trying alternative sort modes, so the layout and file assignment
  are reproducible across runs. Adding or removing sprites only locally
  disturbs the atlas rather than scrambling the whole ordering.

  Update spratprofiles.cfg with more sensible defaults: desktop gains
  explicit max dimensions (8192×8192), legacy switches to optimize=gpu for
  square POT textures preferred by old hardware and bumps its limit to
  2048, css switches from fast to compact mode for better packing quality,
  and redundant scale=1 entries are removed throughout.
  Add --stdin-list option to read image paths from stdin

  Introduces a new `--stdin-list` flag that allows piping a list of image
  paths (one per line) directly to spratlayout without requiring a folder
  argument or a list file on disk.

  Refactors the list-parsing logic into a shared `parse_list_stream`
  lambda, eliminating duplication between the ListFile and new StdinList
  input paths. Both now support the same `exclude` and `root` directives.
  On Windows, stdin is explicitly set to text mode when using --stdin-list.
  Every frame is placed in a uniform cell (max frame width × max frame
  height), arranged left-to-right, top-to-bottom, so any sprite can be
  looked up by (col, row) without parsing the layout file.

  Column count targets a square atlas and widens automatically when
  --max-width or --max-height are set.  Multipack splits the sequence
  into equal-capacity grid atlases.  Tight-bounds trimming is suppressed
  so cell dimensions stay uniform even with padding > 0.

  New profile "grid" added to spratprofiles.cfg; man page updated.
   - Added new template placeholders: {{unity_y}}, {{pivot_x_norm}}, {{pivot_y_norm}}, {{pivot_y_norm_raw}},
     {{name_hash}}, and {{name_hash_hex}}.
   - Created unity.json and unity.meta transforms for seamless Unity engine integration.
   - Updated Phaser and Godot transforms to include normalized pivot data.
   - Updated README.md and man pages with documentation for the new fields.
   - Added automated test/unity_test.sh to the CMake test suite.
Renames the --output flag to --atlas in both spratconvert and spratpack
commands to better reflect its purpose as an atlas path pattern.

   - Adds -a as the new preferred short flag.
   - Maintains backward compatibility for --output and -o.
   - Updates man pages, README, and help messages to reflect new naming.
   - Updates regression tests to verify both new and legacy flags.
claude added 10 commits May 31, 2026 14:15
  Remove the custom section-based DSL ({{placeholders}},
  [meta]/[header]/
  [sprites] tags) and replace it with google/jsonnet v0.20.0 as the
  transform engine. Transforms are now .jsonnet files that receive the
  full layout data as std.extVar("sprat") and return a JSON object with
  a content or files field.

  - Add google/jsonnet via CMake FetchContent, pinned to SHA
    f45e01d (v0.20.0); link libjsonnet++ and libjsonnet_static for
    correct static linking on all platforms
  - Remove ~1400 lines of DSL parser/renderer from
  spratconvert_command.cpp;
    add build_sprat_json(), evaluate_transform(), and
  parse_transform_result()
  - Add sprat.libsonnet with format_double() (works around a %g bug in
    Jsonnet v0.20) and consecutive_runs() for non-contiguous frameTags
  - Replace all 11 .transform files with 14 .jsonnet files: json, csv,
    xml, css, aseprite, libgdx, godot, phaser-hash, phaser-array,
    phaser-anims, plist, unity.json, unity.meta, unity.anim
  - Multi-file transforms (unity.anim) return a files array instead of
    content and write one file per animation under --output-dir
  - Update tests to Jsonnet format and add coverage for non-contiguous
    animations (aseprite frameTags), multipack atlas_index, and empty
    animations file
  - Update README: replace DSL placeholder reference with Jsonnet data
    model and built-in transform table
  Grid mode was silently accepting sprites of different sizes and
  placing
  them using a cell stride derived from the largest sprite, while each
  sprite's w/h in the layout remained its own original dimensions. This
  made the layout inconsistent: coordinates implied one cell size,
  metadata
  implied another.

  Grid mode now validates that all sprites have the same dimensions
  before
  packing. If any sprite differs, a diagnostic names the offending file
   and
  the expected size, and the layout is rejected. The max-dimension loop
   is
  also replaced with a direct cell_w = sprite_w + padding since the
  sizes
  are guaranteed equal after validation.

  The fix applies to both the single-atlas path (pack_grid) and the
  multipack path (pack_atlases).
  fix: patch jsonnet to C++17 on all compilers, not just MSVC

  jsonnet hardcodes CMAKE_CXX_STANDARD 11, but its headers use nested
  namespace definitions (namespace foo::bar {}), a C++17 feature. This
  caused -Wc++17-extensions warnings on GCC/Clang. The patch that raises
  the standard to 17 was previously only applied under if(MSVC); move it
  outside so it runs unconditionally.
…cache hash

  - spratpack: add --scale-filter (nearest/bilinear/bicubic/mitchell) via
    stb_image_resize2; separate scale and rotate steps; fix DXT size overflow
  - spratlayout/spratunpack: guard tar extraction and frame names against path
    traversal (Zip Slip); use unique temp dirs with atomic counters
  - spratlayout: return actual error status from extract_tar_*; switch cache
    path hash to fnv1a for cross-platform determinism
  - spratunpack: validate frame bounds before blitting; use std::from_chars;
    handle create_directories errors
  - Minor: remove dead pixel_is_opaque, fix parse_non_negative_int redundancy,
    reformat brace blocks, update compile comments to c++20
…arsing

  - Fix Emscripten build issues in jsonnet dependencies (BUILD_STATIC_LIBS, c4core, fast_float)
  - Refactor profile section header parsing into dedicated functions
  - Move parsing utilities from layout_parser to cli_parse
  - Add comprehensive profile entry validation
  - Bump version to v0.10.3
…s-json

  Each transform now declares an optional `icon` field with a relative path
  to its SVG (e.g. `"icons/aseprite-svgrepo-com.svg"`). The C++ side parses
  this field and resolves it to an absolute path at listing time.

  `--list-transforms` now prints the resolved icon path below each entry.
  `--list-transforms-json` is a new flag that outputs all transform metadata
  (name, description, extension, icon) as a JSON array, suitable for GUI
  consumers. Group entries are included with `"group": true`.
  Add installation code to remove any .transform files left by older
  versions. The current transform format is .jsonnet, which supports
  richer metadata like icons.
…dows

  Set stdout to binary mode unconditionally at startup, and stdin to binary
  mode only when --stdin-list is active. Previously both were set before
  argument parsing, which could cause issues when stdin binary mode is
  unnecessary. Also suppress _setmode failures non-fatally, as GUI subprocess
  pipe handles may not support it.
  The pre-output _setmode(stdout, _O_BINARY) call reported "Failed to set
  stdout to binary mode" to stderr whenever the handle didn't support it
  (e.g. when running embedded in a GUI application). This was non-fatal —
  output was still written correctly via the redirected std::cout — but
  the message leaked into the error display. Suppress the failure
  consistently with the startup _setmode call fixed in v0.11.2.
  That's all there is to say — there are no code changes, just the version number. If you want to
  also tag and push this as a release so that the sprat-gui CI can fetch it via FetchContent, you'd
  also need to update DEPENDENCIES in sprat-gui from v0.11.3 → v0.11.4.
@pedroac pedroac self-assigned this Jun 24, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a major migration of the template-driven layout transforms to Jsonnet, adding built-in transforms for engines like Godot, Unity, Phaser, and Cocos2d-x, alongside a shared helper library. It also updates the build system to fetch and patch Google's Jsonnet, introduces a new grid packing mode, optimizes performance (such as replacing std::queue with a reusable vector stack in flood fill), and enhances robustness with path traversal guards and bounds validation. The review feedback highlights a potential segmentation fault in spratunpack_command.cpp if extract_sprite_pixels returns an empty vector, and suggests replacing the locale-dependent std::strtod with std::from_chars in cli_parse.cpp for locale-independent double parsing.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

const int out_w = frame.rotated ? frame.frame.h : frame.frame.w;
const int out_h = frame.rotated ? frame.frame.w : frame.frame.h;

std::vector<unsigned char> sprite_data = extract_sprite_pixels(frame);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

If extract_sprite_pixels returns an empty vector (e.g., due to invalid frame bounds), sprite_data will be empty. Passing sprite_data.data() (which is nullptr) to the PNG encoder will cause a segmentation fault. Add a check to ensure sprite_data is not empty before proceeding.

        std::vector<unsigned char> sprite_data = extract_sprite_pixels(frame);
        if (sprite_data.empty()) {
            std::cerr << tr("Error: Frame ") << to_quoted(frame.name) << tr(" references pixels outside the atlas bounds\\n");
            return false;
        }

Comment thread src/core/cli_parse.cpp
Comment on lines +75 to 81
const char* begin = token.c_str();
char* end = nullptr;
errno = 0;
const double value = std::strtod(begin, &end);
if (end != begin + token.size() || errno == ERANGE) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

std::strtod is locale-dependent. In locales where the decimal separator is a comma (e.g., German or French), parsing floating-point numbers like 0.5 will fail. Use std::from_chars from <charconv> instead, which is locale-independent and faster.

    double value = 0.0;
    const auto [ptr, ec] = std::from_chars(token.data(), token.data() + token.size(), value);
    if (ec != std::errc() || ptr != token.data() + token.size()) {
        return false;
    }

@pedroac pedroac merged commit 613fc5b into main Jun 24, 2026
10 checks passed
@pedroac pedroac deleted the release branch June 24, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants