Skip to content

Negative chromaticity values #527

Closed

Description

@svgeesus

Originally raised by John Bowler on the email list:

I would also like to see some discussion of the issue raised by the now
well established ACES AP0 profile. It is described here:
https://acescentral.com/knowledge-base-2/aces-working-spaces. Because one
of the AP0 end-points has a negative y the colour space can't be
represented in cHRM. There is also no support in cICP (fixing this raises
a boatload of issues.)

The specification of the cHRM chunk is, currently, that it only allows
unsigned values for the 4 chromaticities however the encoding supports
signed values as well (the encoding is a 32 bit encoding, unsigned values
only use 31 bits). As I've said simply extending the definition to allow
signed values; changing the word "unsigned" to "signed" at the relevant
place in the cHRM specification, does not invalidate any existing PNG cHRM
chunks and, while initially only applicable to ACES and other, similar,
workflows is easy to accommodate in all PNG implementations that I've seen
(perhaps some already do through lack of checking!)

Subsequent email thread

Activity

  1. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    John also wrote

    The maximum PNG unsigned value is 0x7FFFFFFF. All the PNG
    32-bit signed values are encoded with bit sequences outside this range
    and are therefore rejected as invalid. All valid PNG unsigned values
    are also valid signed values positive values and all the the
    unsigned values have exactly the same 32-bit (4-byte) representation as the
    identical PNG signed values.

    All the 32-bit sequences which correspond to negative PNG signed values are
    invalid PNG 32-bit unsigned encodings.

    I'm simply pointing out that the "dead" space in the PNG 32-bit encoding of
    PNG unsigned values (which currently results in the cHRM chunk being
    rejected if used) can simply be used to fix this serious defect in the PNG
    specification.

    @leo-barnes wrote

    Given the existing wording in the PNG spec, I would consider the highest bit in the cHRM to be considered to be a reserved bit. Its value shall be zero (which is enforced by current implementations) and parsers shall reject files where it's not zero.
    But it's perfectly acceptable for future versions of the spec to assign a new meaning to the reserved bit being one. Existing implementations will reject these files as not conforming to the current version of the spec, but that is fine, this is a new feature after all.
    Writers that want to make use of it need to take care that older implementations may reject their files, but this is true with all new features.

    And @ProgramMax agreed

    OH! You're right!
    I forgot that the PNG spec saves the high bit of a 4-byte unsigned int.
    Well then, that is SUPER convenient. And sorry I missed that when you
    originally said it above. (Side note: This isn't true for 2-byte unsigned
    ints.)

    As did I

    You can't, of course, because of the negative y component for blue. AP0
    is even wider than ProPhoto (which /can /be encoded in PNG). From SMPTE
    ST2065-1.2021:
    https://doi.org/10.5594/SMPTE.ST2065-1.2021

    Table 1 — ACES RGB primaries chromaticity values

    Red     x  0.7347     y  0.2653
    Green   x  0.0        y  1.0
    Blue    x  0.0001     y –0.077
    
  2. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    Agreed at 7 July 2025 meeting to add this correction to PNG 4th edition

  3. linked a pull request that will close this issueNegative values allowed in `cHRM` #528on Jul 7, 2025
  4. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    Note that, while this fixes the four-byte values in cHRM it does not fix the two-byte chromaticity values in mDCV. However, there we are constrained by SMPTE-ST 2086 which forbids them:

    5.3 xy Chromaticity Coordinates
    xy chromaticity coordinates shall be calculated as specified in ISO 11664-3:2012 Section 7, and shall be specified with four decimal places. The x coordinate shall be in the range [0.0001, 0.7400] and the y coordinate shall be in the range [0.0001, 0.8400]

    We are also constrained by physics, since a real mastering display cannot have imaginary primaries!

  5. self-assigned this
    on Jul 7, 2025
  6. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    We need two test files. One with AP0 primaries, which explicitly encode a negative y component for blue. The other with AP1 primaries; the x and y values are all positive but the (computed) z value is 1 - 0.713 - 0.293 = -0.006

  7. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    Hmm, problem. TweakPNG allows me to input the AP0 primaries, but when I try to edit them again it displays the negative value as unsigned huge number.

    It does let me save though and stock pngcheck 4.0.0 then complains - about green, not blue!

    Image

    chris@SuperNomad:/mnt/c/Users/chris/Documents/PNG/cHRM$ pngcheck -c -v cHRM-AP0.png
    File: cHRM-AP0.png (1569 bytes)
      chunk IHDR at offset 0x0000c, length 13
        32 x 32 image, 24-bit RGB, non-interlaced
      chunk gAMA at offset 0x00025, length 4: 1.0000
      chunk cHRM at offset 0x00035, length 32:  invalid green point 0 1
    ERRORS DETECTED in cHRM-AP0.png
  8. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    Oh, I see pngcheck gives an error (incorrectly) for any non-real primaries (outside the spectral locus):

            if (wx < 0 || wx > 0.8 || wy < 0 || wy > 0.8 || wx + wy > 1.0) {
              printf("%s  invalid %swhite point %0g %0g\n",
                     verbose? ":":fname, verbose? "":"cHRM ", wx, wy);
              set_err(kMinorError);
            } else if (rx < 0 || rx > 0.8 || ry < 0 || ry > 0.8 || rx + ry > 1.0) {
              printf("%s  invalid %sred point %0g %0g\n",
                     verbose? ":":fname, verbose? "":"cHRM ", rx, ry);
              set_err(kMinorError);
            } else if (gx < 0 || gx > 0.8 || gy < 0 || gy > 0.8 || gx + gy > 1.0) {
              printf("%s  invalid %sgreen point %0g %0g\n",
                     verbose? ":":fname, verbose? "":"cHRM ", gx, gy);
              set_err(kMinorError);
            } else if (bx < 0 || bx > 0.8 || by < 0 || by > 0.8 || bx + by > 1.0) {
              printf("%s  invalid %sblue point %0g %0g\n",
                     verbose? ":":fname, verbose? "":"cHRM ", bx, by);
              set_err(kMinorError);
            }
            else 
  9. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    Test files (not yet in WPT, what are the pass criteria?) posted

    cHRM-AP0.png
    cHRM-AP1.png

  10. svgeesus commented on Jul 7, 2025

    @svgeesus
    ContributorAuthor

    My patched pngcheck shows the correct values.

    chris@SuperNomad:/mnt/c/Users/chris/Documents/PNG/cHRM$ pngcheck -c -v cHRM-AP0.png
    File: cHRM-AP0.png (1569 bytes)
      chunk IHDR at offset 0x0000c, length 13
        32 x 32 image, 24-bit RGB, non-interlaced
      chunk gAMA at offset 0x00025, length 4: 1.0000
      chunk cHRM at offset 0x00035, length 32
        White x = 0.32168 y = 0.33767,  Red x = 0.73470 y = 0.26530
        Green x = 0.00000 y = 1.00000,  Blue x = 0.00010 y = -0.07699
      chunk IDAT at offset 0x00061, length 1397
        zlib: deflated, 32K window, default compression
      chunk tEXt at offset 0x005e2, length 43, keyword: Comment
      chunk IEND at offset 0x00619, length 0
    No errors detected in cHRM-AP0.png (6 chunks, 48.9% compression).
    chris@SuperNomad:/mnt/c/Users/chris/Documents/PNG/cHRM$ pngcheck -c -v cHRM-AP1.png
    File: cHRM-AP1.png (1563 bytes)
      chunk IHDR at offset 0x0000c, length 13
        32 x 32 image, 24-bit RGB, non-interlaced
      chunk gAMA at offset 0x00025, length 4: 1.0000
      chunk cHRM at offset 0x00035, length 32
        White x = 0.32168 y = 0.33767,  Red x = 0.71300 y = 0.29300
        Green x = 0.16500 y = 0.83000,  Blue x = 0.12800 y = 0.04400
      chunk IDAT at offset 0x00061, length 1397
        zlib: deflated, 32K window, default compression
      chunk tEXt at offset 0x005e2, length 37, keyword: Comment
      chunk IEND at offset 0x00613, length 0
    No errors detected in cHRM-AP1.png (6 chunks, 49.1% compression).
  11. svgeesus commented on Jul 8, 2025

    @svgeesus
    ContributorAuthor

    (not yet in WPT, what are the pass criteria?)

    I guess the pass criterion is simply that the image is not rejected as invalid.

  12. added a commit that references this issue on Jul 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions