WICG / color-api

A proposal and draft spec for a Color object for the Web Platform, loosely influenced by the Color.js work. Heavily WIP, if you landed here randomly, please move along.
https://wicg.github.io/color-api/
Other
130 stars 3 forks source link

Exposing `ColorSpaceWhitePoint` is too heavyweight, and doesn't actually fix equality #30

Open LeaVerou opened 1 year ago

LeaVerou commented 1 year ago

Right now, the API defines a ColorSpaceWhitePoint class, with static predefined values ColorSpaceWhitePoint.D50 and ColorSpaceWhitePoint.D65 that authors are supposed to reference in creating ColorSpace objects.

This means that in the case where the white point is something else, code would need to do things like if (c.white.x === ACES.x && c.white.y === ACES.y) to compare with it, not to mention that things like if (c.white === ColorSpaceWhitePoint.D65) do not account for creating ColorSpaceWhitePoint objects with the same x and y values as the predefined ones (which should really be identified as D50/D65).

Also, exposing a whole other interface just for this seems pointlessly heavyweight.

I would propose a different design:

This would allow handling white points as plain strings making equality checks easy.

Thoughts @svgeesus @tabatkins ?

tabatkins commented 1 year ago

Alternate proposal:

This way authors can check if it's the predefined whitepoint by using reference equality: myCS.white === ColorSpace.D65, even if they set it to the predefined whitespace manually. And we don't have to worry about registrations.


A static method on ColorSpace to register a custom white point, which does not allow registration of duplicate values

This is a forward-compatibility hazard, since we'll be prepopulating it. To make it safe to add more predefined whitepoints in the future we'd have to allow new regs to override existing ones.

svgeesus commented 1 year ago

The getter for cs.white checks the x/y values, and if they're equal to D50 or D65 (within a tiny tolerance)

That sounds reasonable (and is what CSS Color 4 was doing, at first) but led to a world of pain and multiple rewrites of conversion matrices over the last couple of years due to weird non-chromatic values on neutrals from color conversions. These were actually visible (yellowing) as well as being unexpected and annoying.

Which is why CSS Color 4: white points now says:

To avoid cumulative round-trip errors, it is important that the identical chromaticity values are used consistently, at all places in a calculation. Thus, for maximum compatibility, for this specification, the following two standard daylight-simulating white points are defined:

and then gives the exact values with 6-digit precision, with zeroes for any subsequent digits.

So it was important for round-trippability that D65 was [0.312700, 0.329000] and we had to recalculate the sRGB matrices which originally used the ASTM E308-01 values [ 0.3127266146810121, 0.32902313032606195]. Similarly we had to recalculate the Oklab conversion, because the original publication used ASTM values rounded to 5 places: [0.31272, 0.32902]. And Oklab also had earlier used slightly different, rounded values.

I'm aware that we can't really compare floats for equality, but the tiny tolerance is going to be 0.0000001 or smaller.

Comparing objects does sound like a better way forward.

tabatkins commented 1 year ago

We should probably be doing the test on set actually, not get, and if it's within a tolerance of the predefined, just correct it to exactly the predefined.

LeaVerou commented 1 year ago

The downside with this approach is that if we have multiple color spaces sharing the same (non D50, non D65) white point, we don't get the convenient "if close enough, cast to the same object" behavior. Perhaps we still need a registration mechanism?

We should probably also not hang these directly on ColorSpace. We also had thoughts about exposing predefined color spaces there, the namespace is starting to get polluted.

tabatkins commented 1 year ago

Yeah, you do lose the easy testing there, but... how important is that? How common are whitepoints other than D50 and D65? And if people have easy testing of those two, is it worth adding a whole registration mechanism over just having people write a convenience method that compares two whitepoints?

LeaVerou commented 1 year ago

How common are whitepoints other than D50 and D65?

That's a question for @svgeesus . IIRC there was only one HDR color space that used a different white point.

Yeah, you do lose the easy testing there, but... how important is that? And if people have easy testing of those two, is it worth adding a whole registration mechanism over just having people write a convenience method that compares two whitepoints?

It's not so much the inconvenience, but the inconsistency that rubs me the wrong way. There is a certain "magic" built-in behavior that is not exposed and cannot be extended. I worry it's a bit of a footgun. I also cannot think of any other API in the Web Platform that does something similar.


Another direction we could go is to only have predefined whitepoints in L1 (either as objects or as strings), and see what happens and design L2 based on input from use cases.

LeaVerou commented 1 year ago

The downside of having predefined static attributes directly on a class is that there's no easy way to enumerate these whitepoints. You'd need to enumerate all static properties and filter out those that don't match. And if there is no class for white points this can only be done with duck typing (if (o.x && o.y) ...). It's a tradeoff though, cause you also don't want some super long thing like ColorSpaceWhitePoint.common.D50.

What about the following design:

Then Color API would use ColorSpace.WhitePoint#equals() internally at color space construction time to cast whitepoints to predefined ones. ColorSpace specs could also use strings like "D50", but colorSpace.white will still be an object.

Note that how this is done is important, not just for developers wishing to compare two ColorSpace objects for whitepoints (rather uncommon), but primarily internally: Color API needs to know when two color spaces have the same or different white point, to see if it needs to do chromatic adaptation when converting between these spaces. @svgeesus suggested that if whitepoints are marginally different, chromatic adaptation won't change the color much, but I really don't like this solution. It's wasteful wrt resources, and authors don't like seeing their numbers being fudged.

tabatkins commented 1 year ago

Sounds good to me.

svgeesus commented 1 year ago

How common are whitepoints other than D50 and D65?

The Digital Cinema Initiative colorspace, DCI-P3, uses a weird greenish white (0.314, 0.351) for reasons to do with the xenon bulb used in digital projectors.

details The DCI white is an ‘ugly green-white’ that was only there because projector manufacturers were concerned about achieving maximum brightness at 14fL for colors on the daylight axis. Getting to D60 or D65 was considered 5-12% less efficient. D65 comes across as both a bit too ‘cool’ in appearance for live action content in a dim theatrical grading environment, and at the same time with Xenon projection lamps can have a magenta bias when looking at a full grey field. This is a reaction between the spectrum of Xenon and the eye color sensitivities. Testing at the Academy of Motion Pictures Arts and Sciences showed that D60 is a more appropriate ‘creative white’ (also called adopted white) for live action content. This is why it is chosen as the white point of the ACES system. [source](https://www.liftgammagain.com/forum/index.php?threads/dci-p3-theatrical-vs-d65-different-white-points.11109/#post-110749)

The Academy Color Encoding Specification (ACES) colorspaces use (0.32168, 0.33767) which is similar to D60.

So, not very, but a fully-fledged system does need to account for them. An MVP system can do just fine with D65 and D50.

svgeesus commented 1 year ago

Oh, and I am answering the more specific question "How common are color spaces that use whitepoints other than D50 and D65?" here.

In general color science they are a lot more common, used for tasks like "I measured these printed colors with this illuminant, now adapt them to D50 please". That is well outside the scope of this API, but that is why we support arbitrary white points and multiple chromatic adaptation algorithms in color.js.

svgeesus commented 1 year ago

Color API needs to know when two color spaces have the same or different white point, to see if it needs to do chromatic adaptation when converting between these spaces.

Yes, this is crucial otherwise all the affected conversions are just plain wrong.

if whitepoints are marginally different, chromatic adaptation won't change the color much, but I really don't like this solution. It's wasteful wrt resources, and authors don't like seeing their numbers being fudged

My point was that if someone registers myD65 which is similar but not identical to D65, the system will treat that as different, compute a chromatic adaptation matrix which is similar but not identical to unity, and apply it to get the right result. In other words it accepts it gracefully and gives the correct result.