Skip to content

Add APNG R/W Support via libpng#4944

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

Add APNG R/W Support via libpng#4944
felixbuenemann wants to merge 1 commit intolibvips:masterfrom
felixbuenemann:apng-support

Conversation

@felixbuenemann
Copy link
Copy Markdown
Collaborator

@felixbuenemann felixbuenemann commented Mar 11, 2026

Summary

Add APNG (animated PNG) load/save support via libpng.

Adds animated PNG to the existing libpng backend, gated behind PNG_APNG_SUPPORTED (libpng 1.6+APNG patch or libpng 1.8+). The loader supports page/n parameters matching the GIF/WebP loaders, full frame compositing (all dispose and blend ops, 8-bit and 16-bit), and sequential access. The saver writes APNG via sink_disc with palette mode support. Non-animated PNGs are unaffected — the only overhead is an acTL chunk check.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

Btw. I also opened a PR for spng (which libvips also supports) to add APNG R/W support at randy408/libspng#283, but it looks like the library is unmaintained since 2023.

@jcupitt
Copy link
Copy Markdown
Member

jcupitt commented Mar 11, 2026

This is great @felixbuenemann!

Yes, libspng seems to have gone, sadly, so libpng only is fine.

I'm trapped in a horrible deadline right now (I must submit a paper in a month presenting work I've still not finished), but after that, and a beer, I'll be back on libvips.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

@jcupitt That's fine. Do you prefer if I split the quantiser into a separate PR?

@jcupitt
Copy link
Copy Markdown
Member

jcupitt commented Mar 12, 2026

I'm ok with one PR, but maybe someone else will review. I'll defer to them.

@jcupitt
Copy link
Copy Markdown
Member

jcupitt commented Mar 12, 2026

Getting rid of a dependency sounds great, especially if we save time and memory.

There's an issue somewhere asking for a "find $N most significant colours" operation, which is difficult with libimagequant because of the API. If we had our own cluster finder we could (probably?) add stuff like this relatively easily.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

Getting rid of a dependency sounds great, especially if we save time and memory.

There's an issue somewhere asking for a "find $N most significant colours" operation, which is difficult with libimagequant because of the API. If we had our own cluster finder we could (probably?) add stuff like this relatively easily.

That shouldn't be too hard to do, but currently the built-in quantizer is only used as a fallback, when neither quantizr nor libimagequant are found.

I also have another optimization in the pipeline, that remaps pixels with a fully transparent alpha, but different rgb values to a single 0,0,0,0 alpha palette entry to ensure remaining palette entries are not wasted. I just haven;t come around to generating good test images to exercise the code.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

I pushed the optimisation that replaces all fully transparent pixels with a single {0,0,0,0} palette entry at position 0, which also improves PNG compression.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

I also discovered #4971 while working on this.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

I fixed the fuzzer errors by checking sub-frame dimensions and frame counts before allocating.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

@jcupitt you said:

There's an issue somewhere asking for a "find $N most significant colours" operation, which is difficult with libimagequant because of the API. If we had our own cluster finder we could (probably?) add stuff like this relatively easily.

We can get just the palette with all three quantizers (built-in, libimagequant, quantizr) and return it as RGBA strip or what was the intended API?
Or is "most significant colours" something else than the palette, when quantizing to $N colors?

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

I amended the quantizer commit, because cgif was disabled with the built-in quantizer and also added an error handling bugfix for the quantizr lib in a separate commit.

@felixbuenemann
Copy link
Copy Markdown
Collaborator Author

@jcupitt I've split the quantizer to PR #4987 since both changes are independent of each other.

Add animated PNG (APNG) support to the libpng backend, gated behind
PNG_APNG_SUPPORTED (libpng 1.6+APNG patch or libpng 1.8+).

Load:
- Extend Read struct with APNG fields (page/n, canvas, dispose state)
- Add page/n parameters to pngload (matching GIF/WebP loaders)
- Detect acTL chunk and set animation metadata (n-pages, page-height,
  delay, loop)
- Scan raw PNG chunks via vips_source_map to extract fcTL delays at
  header time, matching how GIF/WebP loaders handle delay metadata
- Frame compositing with DISPOSE_OP_NONE/BACKGROUND/PREVIOUS and
  BLEND_OP_SOURCE/OVER for both 8-bit and 16-bit
- Sequential generate callback for frame-by-frame reading
- Compat defines for constant naming differences between libpng 1.6
  APNG patch (PNG_DISPOSE_OP_NONE) and 1.8+ (PNG_fcTL_DISPOSE_OP_NONE)
- Non-animated PNG fallthrough avoids duplicate png_read_update_info
  call (rejected by libpng 1.8)

Save:
- Detect animation via page-height metadata
- Write APNG with acTL/fcTL/fdAT chunks via sink_disc callback
- Disable interlace for animated output with warning
- Palette mode support via quantisation

Tests:
- Add cogs-apng.png test image (indexed APNG converted from cogs.gif)
- Add hand-crafted APNG test images for compositing: dispose-background,
  dispose-previous, and alpha blend-over
- Add test_apng_load: metadata, page handling, single/multi-frame
- Add test_apng_dispose_background: verify BACKGROUND dispose clears
  canvas to transparent
- Add test_apng_dispose_previous: verify PREVIOUS dispose restores
  canvas, including spec rule that PREVIOUS on frame 0 = BACKGROUND
- Add test_apng_blend_over: verify alpha-over compositing of
  semi-transparent frames
- Add test_apng_save: lossless round-trip, palette round-trip, GIF
  to APNG conversion
- Add have_apng() / skip_if_no_apng for builds without APNG support
@felixbuenemann felixbuenemann changed the title Add APNG R/W Support via libpng & Built-in Quantizer Add APNG R/W Support via libpng Mar 30, 2026
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