Skip to content

Add APNG read/write support#283

Open
felixbuenemann wants to merge 1 commit intorandy408:masterfrom
felixbuenemann:apng-support
Open

Add APNG read/write support#283
felixbuenemann wants to merge 1 commit intorandy408:masterfrom
felixbuenemann:apng-support

Conversation

@felixbuenemann
Copy link

Summary

  • Add full APNG read and write support via new spng_decode_frame() and spng_encode_frame() APIs, supporting both one-shot and progressive modes
  • Parse acTL, fcTL, and fdAT chunks with full validation (sequence numbers, frame bounds, dispose/blend ops)
  • New public API: spng_get_actl(), spng_set_actl(), spng_get_frame_fctl(), spng_decode_frame(), spng_encode_frame()
  • Upgrade libpng subproject from 1.6.37 to 1.6.55 with vendored APNG patch and fix for hIST chunk ordering bug
  • Add 7 APNG test suites (basic roundtrip, sub-frame regions, error cases, progressive decode/encode, buffer vs stream, malformed input) — all 210 tests pass
  • Update read and write fuzzers for APNG coverage
  • Closes Animated PNG (APNG) read support #252

API

/* Decode frames from an APNG */
struct spng_actl actl;
spng_get_actl(ctx, &actl);  /* num_frames, num_plays */

struct spng_fctl fctl;
while (spng_decode_frame(ctx, buf, len, SPNG_FMT_RGBA8, 0, &fctl) == 0) {
    /* fctl.delay_num / fctl.delay_den = frame delay in seconds */
    display_frame(buf, fctl.width, fctl.height,
                  fctl.x_offset, fctl.y_offset,
                  fctl.delay_num, fctl.delay_den);
}

/* Encode APNG frames */
spng_set_actl(ctx, &(struct spng_actl){.num_frames = 3, .num_plays = 0});

struct spng_fctl fctl = {
    .width = w, .height = h,
    .delay_num = 100, .delay_den = 1000,  /* delay = 100/1000s = 100ms */
    .dispose_op = SPNG_DISPOSE_OP_NONE,
    .blend_op = SPNG_BLEND_OP_SOURCE
};
spng_encode_frame(ctx, img, len, SPNG_FMT_PNG, 0, &fctl);                    /* frame 0 → IDAT */
spng_encode_frame(ctx, img, len, SPNG_FMT_PNG, SPNG_ENCODE_FINALIZE, &fctl); /* last → fdAT + IEND */

Test plan

  • All 210 existing tests pass (169 OK, 41 expected fail, 0 fail)
  • 3-frame encode/decode roundtrip with pixel verification
  • Sub-frame regions with offset verification
  • Error handling (invalid acTL/fcTL, out-of-bounds, bad sequence numbers, duplicate chunks)
  • Progressive decode and encode roundtrips
  • Buffer vs stream output byte-identity
  • Malformed APNG byte sequences (duplicate acTL, acTL after IDAT, bad sequence numbers)
  • Read and write fuzzers updated for APNG paths

Core library changes (spng.h, spng.c):
- Add acTL, fcTL structs and dispose_op/blend_op enums
- Add spng_decode_frame() and spng_encode_frame() APIs
- Add spng_get_actl(), spng_set_actl(), spng_get_frame_fctl()
- Add APNG error codes and state machine extensions
- Add fdAT read/write paths alongside existing IDAT paths
- Fix progressive encode for frame 0 (don't clobber encode state)

Build system:
- Upgrade libpng subproject from 1.6.37 to 1.6.55
- Vendor APNG patch for libpng and fix hIST chunk ordering bug
- Add APNG fallback logic to detect and use patched libpng

Tests:
- Add APNG roundtrip test (3-frame encode/decode with pixel verification)
- Add sub-frame regions test (different frame dimensions and offsets)
- Add error cases test (invalid acTL/fcTL values, non-APNG decode)
- Add progressive decode test (compare scanline-by-scanline vs one-shot)
- Add progressive encode roundtrip test
- Add buffer vs stream encode comparison test
- Add malformed APNG decode tests (duplicate acTL, bad sequence numbers)
- Update read fuzzer to exercise spng_decode_frame() on APNG inputs
- Update write fuzzer to optionally encode multi-frame APNG

Docs:
- Update README feature table (APNG no longer "Planned")
- Add testing section to build docs
@felixbuenemann
Copy link
Author

@randy408 please review

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.

1 participant