Skip to content

Unified PNG/APNG decoder - #3099

Open
RunDevelopment wants to merge 4 commits into
image-rs:mainfrom
RunDevelopment:apng-decoder-unified
Open

Unified PNG/APNG decoder#3099
RunDevelopment wants to merge 4 commits into
image-rs:mainfrom
RunDevelopment:apng-decoder-unified

Conversation

@RunDevelopment

@RunDevelopment RunDevelopment commented Aug 7, 2026

Copy link
Copy Markdown
Member

resolves #3038

My original plan was to slowly prepare for #3038 via a series of small PRs for PNG that would build the foundation for this more complex change. That did not work, because the first PR didn't get reviewed after being open for a month. So I got impatient and did all of this in 4 hours.

Commits

This PR is structured into 4 commits for reviewability. The first 3 prepare for the last commit which contains the main change.

  1. Commit 1: I split the decoder and encoder logic into separate files. png.rs was getting quite large and I wanted to make it obvious that only the decoder is changed in later commits.

  2. Commit 2: I changed the way color type/bits are parsed into image color types to make it more obvious which color types are actually supported by the decoder. Since PngDecoder and ApngDecoder are tightly coupled, I also removed the check in ApngDecoder that re-verified the parsed color in PngDecoder. Both decoders support the same colors, so this check didn't do anything.

  3. Commit 3: I replaced the internal state PngDecoder { decoder: Option<...>, reader: Option<...>, ... } with a 3-state enum. This removes the invalid state PngDecoder { decoder: Some(_), reader: Some(_) } and makes the logic of ensure_reader_and_header a lot easier to understand.

  4. Commit 4: Add the unified decoder and make the other two private.

The unified decoder

The basic idea is that the unified decoder is a wrapper around both PngDecoder (single image) and ApngDecoder (animation). It uses one of them depending on whether the underlying file is animated.

Changes:

  • Rename PngDecoder -> BasePngDecoder
  • Remove impl ImageDecoder for ApngDecoder. (It wasn't necessary anymore.)
  • Make BasePngDecoder and ApngDecoder private.
  • Add the unified decoder as PngDecoder. This has (almost) the same API as before. Only PngDecoder::apng was removed.
  • A few minor changes.
    • Renamed ApngDecoder::read_sequence_data -> ApngDecoder::new and take an animation control struct as an argument to ensure an APNG decoder can only be constructed for animated files.
    • Store num_plays in ApngDecoder. This removes a possible error path in PngDecoder::animation_attributes (unified decoder).

Notes:

  • impl ImageDecoder for BasePngDecoder got to stay, because it makes the implementation of (unified) PngDecoder easier.
  • One reference file changed because thumbnails are handled differently now. See open question 2.

Open questions

  1. Right now, sequence control is only done for APNG. This is how it was before, but this makes the new unified PngDecoder awkward to use. ImageReader::into_frames will work for APNG files but it will error for normal PNGs. Should sequence control work properly for single images too? (This essentially just means keeping track of whether BasePngDecoder has read its one and only image.) Related to ImageReader::into_frames will always error for most formats #3037
  2. How to handle thumbnails? Previously, the single-image PngDecoder would read the thumbnail if present and the first frame otherwise. Now the unified PngDecoder will always read the first frame. This means that it's currently impossible to read the thumbnail (if present). Given that there was no (real) way to tell whether a file had a thumbnail in the first place, the previous behavior was probably unintentional. Nonetheless, being able to read the thumbnail should be a feature IMO. This should be done with a dedicated API for it, though.

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.

Can't read APNG with ImageReader

1 participant