mm2 / Little-CMS

A free, open source, CMM engine. It provides fast transforms between ICC profiles.
https://www.littlecms.com
MIT License
572 stars 177 forks source link

Unbounded Transform for integer-to-float formats. #277

Closed jpap closed 3 years ago

jpap commented 3 years ago

Fixes #276.

mm2 commented 3 years ago

Unfortunately this breaks gamut check, gamut warning, cache and uses a flag bit that is already used in yet existing extensions. Please see an alternative way, by using a formatter plug-in (as described in issue #276)

On the other hand, you should get same results as using integers on input. Unbounded mode is used when input values are out of bounds. If you have a sample that gives different output, please let me know.

jpap commented 3 years ago

Thanks for the quick review. The update:

Unbounded mode is used when input values are out of bounds.

It is also useful for computing "extended range" output when the input values are within bounds.

For example, Apple uses an extended range sRGB 16bit half-float pixel format for rendering Display P3 content to their wide-gamut color displays. The deepest red in Display P3 (1.0000, 0.0000, 0.0000) maps to (1.0930, -0.2267, -0.1501) in extended sRGB.

I can generate an image in such a format by transforming (RGBA int 8bpc Display P3 colorspace) to (RGBA float 16bpc sRGB colorspace). Without this PR, lcms maps the deepest red in Display P3 RGBX = (255, 0, 0, X) to sRGB value (1.0000, 0.0000, 0.0000, X) which is incorrect. However, if I were instead to use lcms to transform Display P3 RGBX = (1.000, 0.000, 0.000, X) using a floating point format for the input, lcms yields the correct value of (1.0930, -0.2267, -0.1501, X) in the output image.

This looks like a bug because cmsFLAGS_NONEGATIVES is not set and I get different results depending on the input format. It is for this reason that I suggest this PR be merged into lcms core, rather than use a formatter plug-in workaround.

In Unbounded Color Engines, you write,

If a color transform is setup by using exclusively this kind of profiles, and floating point is used as input and output format, there are no constrains and the color transform is not limited by any bounds. We would name this special mode as unbounded CMM.

and is exactly what has been implemented in lcms. However I would argue that unbounded should only be conditioned on the output format: when the output format is floating point, the "unbounded" mode is still relevant when the input is integer: it is the only way to compute the "extended range" color values used by Apple above.

I see the distinction between "bounded" and "unbounded" CMM as just whether you want to optimize the CMM intermediate values to avoid overflow, maximize dynamic range, and/or adhere to the constraints of a color profile (e.g. those using tables): the desired end result should be the same. If the final result is desired to be in a range outside of [0, 1] or [0, 2^n), like {16, 32}-bit float, or [-2, 2], or even fixed-point Q2.14, then the entire pipeline should carry the required amount of precision throughout, limited only by the constraints of the profiles involved. I really appreciate all the hard work you've done over the years to bring up a floating point pipeline -- I'm not asking for anything more!

If you have a sample that gives different output, please let me know.

The above example shows lcms giving different results when the input and output formats are float, vs. the input being integer (and output float). This is no surprise: the floating-point transform is only enabled in AllocEmptyTransform when both input and output formats are float.

I hope you're able to have another look at the updated PR.

mm2 commented 3 years ago

Already solved by 195c218dd3c8338e12563c9641ef89f84cc6f6b4 Thanks

mm2 commented 3 years ago

However I would argue that unbounded should only be conditioned on the output format: when the output format is floating point, the "unbounded" mode is still relevant when the input is integer: it is the only way to compute the "extended range" color values used by Apple above.

I fully agree. This is now the default behaviour. Thanks for the idea!

butcherg commented 3 years ago

So, following this discussion, I'd surmise that any formatter (Table 37 in the API doc) that ends in FLT or DBL used as the OutputFormat parameter in any of the cmsCreateTransform*() functions will produce an unbounded result... ?

BTW, in Table 37 there are a few integer formatters lurking at the end of the "Floating point" list on page 78...

mm2 commented 3 years ago

So, following this discussion, I'd surmise that any formatter (Table 37 in the API doc) that ends in FLT or DBL used as the OutputFormat parameter in any of the cmsCreateTransform*() functions will produce an unbounded result... ?

Yes, I have taken my time to think about this, and now I believe this is the right behaviour. Just imagine this example: you do a transform from AdobeRGB in 8 bits to sRGB in floats. Then you feed the transform with some flourescent green like (12, 253,20). This is on AdobeRGB gamut but outside any reasonable gamut. Even Lab is pushed to its limits. By this color. Anyway, what would you expect on output? Since you choose floats, you can handle negative or highlight values. Then you still can use the non-negatives flag or clip highlights if you want. Much better that getting results clipped to 0..1.0, which you can always get by using integer types on output. Any thoughts?

BTW, in Table 37 there are a few integer formatters lurking at the end of the "Floating point" list on page 78...

Thanks! I will fix that ASAP

butcherg commented 3 years ago

Makes perfect sense to me; any calculation to a float destination should not be bounded by data container or even black/white constraints. My software doesn't do integer->float transforms, as I translate whatever image input to float for subsequent internal use, including color transforms.

jpap commented 3 years ago

Verified 195c218dd3c8338e12563c9641ef89f84cc6f6b4 also fixes the problem.