hsluv / hsluv

Human-friendly HSL, website and math
https://www.hsluv.org/
MIT License
1.28k stars 55 forks source link

D65 values are incorrect? #79

Closed makew0rld closed 3 years ago

makew0rld commented 3 years ago

The standard D65 XYZ values are (0.95047, 1.00000, 1.08883). This is written in Wikipedia and by Bruce Lindbloom (see here and search for D65).

But HSLuv defines them like so:

https://github.com/hsluv/hsluv/blob/005e50b7cf79eff8c62ccfc09a5707692451758e/math/cie.mac#L40

If I remove the rat and run that calculation, the output XYZ values are (0.95045592705167, 1, 1.089057750759878). This is quite a bit off from what I believe are the correct values, and it is throwing off my calculations that use the standard D65 as I defined at the beginning. Here's an example of the difference between what using the standard D65 will output, versus the HSLuv D65:

    hsluv_test.go:98: Testing public methods for test case #ffff00
    hsluv_test.go:67: result: [1.000054 *, 0.999991 *, -0.002083 *] expected: [1 1 0], testing HsluvToRGB with test case #ffff00
    hsluv_test.go:67: result: [85.880145 *, 1.000332 *, 0.971386] expected: [85.87432021817473 1.0000000000007272 0.9713855934179674], testing HsluvFromHex with test case #ffff00
    hsluv_test.go:67: result: [85.880145 *, 1.000332 *, 0.971386] expected: [85.87432021817473 1.0000000000007272 0.9713855934179674], testing HsluvFromRGB with test case #ffff00
    hsluv_test.go:117: Testing internal methods for test case #ffff00
    hsluv_test.go:67: result: [1.000054 *, 0.999991 *, -0.002083 *] expected: [1 1 0], testing convLchRgb with test case #ffff00
    hsluv_test.go:67: result: [0.971386, 1.070642 *, 85.880145 *] expected: [0.9713855934179674 1.0708560884692067 85.87432021817473], testing convRgbLch with test case #ffff00
    hsluv_test.go:67: result: [0.971386, 0.076918 *, 1.067875 *] expected: [0.9713855934179674 0.07704219177275 1.06808111250898], testing convXyzLuv with test case #ffff00
    hsluv_test.go:67: result: [0.769989 *, 0.927808, 0.138372 *] expected: [0.76997513864982 0.92780768463926 0.13852559851021098], testing convLuvXyz with test case #ffff00

The D65 values affect these variables:

https://github.com/hsluv/hsluv/blob/005e50b7cf79eff8c62ccfc09a5707692451758e/math/cie.mac#L48-L49

Which goes on to affect everything else.

I believe this could all be fixed by manually setting the [ref_X, ref_Y, ref_Z] variables to (0.95047, 1.00000, 1.08883). I know that I am quite inexperienced with color theory though, and so I'd appreciate learning if I'm wrong here.

For now I will specify a custom white reference for HSLuv calculations only.

boronine commented 3 years ago

D65 XYZ values are derived from D65 xyY values using the standard formula (with Y being set to 1). It seems that the discrepancy comes from whether to use 4- or 5-decimal space rounding for the x and y numbers. A lot of sources use 5 decimal place rounding 1 2. Bruce Lindbloom references ASTM E308 - 01 which I assume also rounds like this.

HSLuv uses 4 decimal place rounding which seems to be encoded in the sRGB standard: https://en.wikipedia.org/wiki/SRGB#The_sRGB_gamut https://en.wikipedia.org/wiki/Rec._709

Even if 5 decimal place rounding is better, it's certaintly not worth making a revision of the color space for this. I added some comments about this to cie.mac for future reference. If there is every a revision 5 of HSLuv we can consider sneaking higher precision numbers into it.

makew0rld commented 3 years ago

So you're saying if I used a white reference equal to (0.9505, 1.0000, 1.0888), that would be more correct? And the problem with the one I was using was that it had one extra decimal place?

If that's true than I'm confused why using the longer one (0.95045592705167, 1, 1.089057750759878) would make my code work. Especially since that one isn't even accurate to 4 decimal places, when compared to the other one.

boronine commented 3 years ago

With regards to your code, I can't really comment since I don't know what exactly you're testing.

I'm urging you to think practically though. What are you trying to achieve where this difference is an issue?

Numbers generated by all these algorithms are always approximations. In the case of colors, remember that the general standard is 24-bit color, which means 8 bits per channel, which means the smallest difference that can be encoded is 1 / 256 ~ 0.004. That means all decimal places after that are noise.

Another thing to keep is mind is that the precision of your output numbers can only be as good as the least precise of your input numbers. (depending on the calculation.. see: https://en.wikipedia.org/wiki/Propagation_of_uncertainty)

So any benefit of increasing the precision of these: https://github.com/hsluv/hsluv/blob/master/math/cie.mac#L26-L27

... might be lost unless you also increase the precision of these: https://github.com/hsluv/hsluv/blob/master/math/cie.mac#L20-L25

makew0rld commented 3 years ago

If I use a D65 value of (0.95047, 1.00000, 1.08883) and run the HSLuv test suite with a delta of 1.0/256.0, none of the tests that measure the conversion to RGB fail. But I get errors for the conversions from RGB. Obviously this does not really matter practically, but it'd be nice to get these to pass as well. So I used the different white point.

Here's all the errors for one color. Keep in mind this library scales the values from 0-1, rather than 0-255 or 0-100.

    hsluv_test.go:65: result: [85.885855 *, 0.504866, 0.423764] expected: [85.87432021817236 0.5050715546882035 0.4237638616967416], testing HsluvFromHex with test case #666644
    hsluv_test.go:65: result: [85.885855 *, 0.504866, 0.423764] expected: [85.87432021817236 0.5050715546882035 0.4237638616967416], testing HsluvFromRGB with test case #666644
    hsluv_test.go:65: result: [85.885855 *, 0.706252, 0.423764] expected: [85.87432021817236 0.7065317593121714 0.4237638616967416], testing HpluvFromHex with test case #666644
    hsluv_test.go:65: result: [85.885855 *, 0.706252, 0.423764] expected: [85.87432021817236 0.7065317593121714 0.4237638616967416], testing HpluvFromRGB with test case #666644
    hsluv_test.go:65: result: [0.423764, 0.235854, 85.885855 *] expected: [0.4237638616967416 0.23594798873422232 85.87432021817236], testing convRgbLch with test case #666644

I think I'd rather keep the accuracy and those tests, and I don't think using a different white point will really cause any issues.

Tynach commented 2 years ago

So you're saying if I used a white reference equal to (0.9505, 1.0000, 1.0888), that would be more correct? And the problem with the one I was using was that it had one extra decimal place?

@makeworld-the-better-one, I think they're talking about the rounding applied to the chromaticity coordinates, not the final XYZ value for the white point. The sRGB standard states that D65 should be considered to have the chromaticity coordinates '0.3127, 0.3290', which approximately expands to an XYZ value of (0.9504559270516717, 1, 1.089057750759878).

If that's true than I'm confused why using the longer one (0.95045592705167, 1, 1.089057750759878) would make my code work. Especially since that one isn't even accurate to 4 decimal places, when compared to the other one.

Personally, I'm confused why nobody else just defines it as '0.3127, 0.3290' and then compute the more useful parameters at compile time at maximum precision.