- From: John Bowler <john.cunningham.bowler@gmail.com>
- Date: Mon, 7 Jul 2025 14:14:08 -0700
- To: Fares Alhassen <falhassen@google.com>, "Chris Blume (ProgramMax)" <programmax@gmail.com>
- Cc: "Portable Network Graphics (PNG) Working Group" <public-png@w3.org>
- Message-ID: <CAP7U399EPSN2k-T1SbeK8huj+ajdqYi=3mwYGOOkwmy3dEeemQ@mail.gmail.com>
On Mon, Jul 7, 2025 at 9:57 AM Fares Alhassen <falhassen@google.com> wrote: > Dear all, > > Just to follow up on an action item in the minutes: > > Android has documentation about its use of PNG gainmaps for the screenshot > use case: > https://source.android.com/docs/core/graphics/hdr-screenshots#android16-hdr-shot > > While it provides a general gainmap framework that we might want to > consider for PNG 4e, the spec does not document how ISO metadata is encoded. > The problem with that approach is that it really is just a packaged PNG - IHDR all the way, I assume, to IEND whereas the established way of including ancillary images in PNG is APNG. In APNG the encoding is a separate 'IHDR' in fcTL with the 'IDAT' data split across multiple chunks. I do think APNG would have been better implemented the way gdAT is implemented; it's much easier to write a nested parser, however it wasn't done that way. So I'd like to propose a _general_ solution which is consistent with existing APNG parsers but applies not just to gainmaps but any of the possible ancillary image requirements that have been raised and, indeed, most that I am aware of. The extension is simply two new chunks called 'iHDR' and 'iDAT'. These chunks are, indeed, IHDR and IDAT chunks but with a header which identifies the ancillary image purpose and allows for the 'iDAT' chunks to be interpreted in the correct order. The proposal for the two chunks is: *iHDR *[text keyword][instance id][IHDR data] *text keyword*: an 8 byte registered keyword containing 8 "keyword" characters as defined for tEXt etc. The registration space is separate from that of text chunks (and iCCP and so on) and the length is exactly 8 characters. * instance id*: a 31-bit positive (greater than 0) unsigned integral value. *IHDR data*: exactly as IHDR *iDAT *[text keyword][instance id][sequence number][IDAT data] The first two fields must match an iHDR. *sequence number*: a PER INSTANCE 31-bit unsigned sequence number for ordering starting at 0 *IDAT data*: exactly as IDAT; deflate compressed data for the image I use a fixed size header because it makes writing code to handle the checking far easier. This format can be understood independently of the specific "keyword", so a library implementation does not need new code for each new ancillary image type. The format also allows for parallel decode and this is important for gain maps and other ancillary images that might provide a per-pixel *transform *of the data. A streaming implementation of PNG decodes rows on-the-fly and, in many cases, transforms row-by-row as well to obtain a convenient format for processing. Thus it is desirable to be able to parallel stream the gain map row-by-row as the image it modifies is read row-by-row. An implementation can buffer the gainmap iDAT chunks (reordering them as required) then run two separate decoders in parallel. I'm working on this for APNG support; it's not a requirement for APNG but it is a good engineering solution and cleans up a lot of libpng code. The format also allows for additional chunks containing ancillary information for the ancillary image. The twelve byte header should be enough for a library implementation to match other, possibly unknown, chunks to the correct ancillary image but my basic idea is an "lABL" (label) chunk: *lABL *[text keyword][instance id][sub-chunk][data] The twelve byte header identifies the corresponding ancillary image or, when [instance id] is 0, all of the corresponding ancillary images with the given keyword *sub-chunk* is a four-byte PNG chunk name with the "reserved" bit set, for example *gAmP*, which is either publicly approved (as in gAmP) or a privately defined (preferably with the definition itself published) format, *gamP*. Once again this is extensible without requiring additional code in any library implementation. The intention here is that a library should not have to implement any more than the minimum support for reading and/or writing PNG files. In this case the library can extract ancillary images (the difficult bit) without needing to know what is *in* them, or what they mean! The approach also allows for extensions which have been discussed but previously rejected because they break existing encoders. IHDR cannot have new values for bit depth, colour type, filter method or compression method because adding to those fields causes the canonical PNG image, the one in IDAT, to be undecodable without explicit change to all the decoders. This is not true of iHDR because it *is* ancillary; a library can still store the chunks even though it doesn't know how to decode them and, more important, a decoder can simply discard them and decode what it knows how to decode. John Bowler <jbowler@acm.org> >
Received on Monday, 7 July 2025 21:14:23 UTC