Skip to content

Use info disposal and blend when calculating APNG frame deltas - #10061

Open
glaziermag wants to merge 2 commits into
python-pillow:mainfrom
glaziermag:fix/apng-info-disposal-delta
Open

glaziermag wants to merge 2 commits into
python-pillow:mainfrom
glaziermag:fix/apng-info-disposal-delta

Conversation

@glaziermag

Copy link
Copy Markdown

If disposal or blend is not passed to save(), the APNG writer uses the im.info value for the fcTL chunk (L1306-L1307). The frame delta that decides how much of each frame is written still reads previous.encoderinfo.get("disposal") (L1236-L1237), which is None there, so it assumes OP_NONE. A frame that changes only part of the canvas is cropped to that part, while the previous frame is disposed to transparent, so the rest of the image disappears.

from PIL import Image

red = Image.new("RGBA", (8, 8), "red")
frame = red.copy()
frame.paste("green", (0, 0, 2, 2))

red.save("explicit.png", save_all=True, append_images=[frame], disposal=1)
red.info["disposal"] = 1
red.save("info.png", save_all=True, append_images=[frame])

for name in ("explicit.png", "info.png"):
    with Image.open(name) as im:
        im.seek(1)
        print(name, im.getpixel((4, 4)))
# explicit.png (255, 0, 0, 255)
# info.png (0, 0, 0, 0)

Since Image.open() sets info["disposal"], this also affects re-saving an opened APNG. Image.open("Tests/images/iss634.apng").save(out, save_all=True) currently changes 40 of the 41 rendered frames. With this change, it round-trips unchanged.

Changes proposed in this pull request:

  • When calculating the delta from the previous APNG frame, fall back to the same disposal and blend defaults that are written to the fcTL chunk. The "test info disposal" part of test_apng_save_disposal already relies on info["disposal"] being used as that default.
  • Add a test that fails on main for OP_BACKGROUND and OP_PREVIOUS.

Tests/test_file_apng.py and Tests/test_file_png.py pass locally (Python 3.14, macOS).

This was found and prepared by an AI agent (Claude Code) working from the glaziermag account.

Generated with Claude Code

When disposal or blend were not passed to save(), the fcTL chunk used the
im.info fallback, but the frame delta assumed OP_NONE and OP_SOURCE, so frames
were cropped against the wrong base image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@radarhere radarhere added the 🤖-assisted AI-assisted label Sep 26, 2026
@radarhere

Copy link
Copy Markdown
Member

Is there any particular reason there's no test for blend?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖-assisted AI-assisted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants