libjxl icon indicating copy to clipboard operation
libjxl copied to clipboard

apply 8x8 ordered dither to uint decode output

Open jonsneyers opened this issue 3 years ago • 8 comments

When converting the internal float sample values to uint ones (when JXL_TYPE_UINT8 or JXL_TYPE_UINT16 pixel formats are requested), currently this is done simply by picking the nearest integer value. This can be a problem, especially with 8-bit output on sufficiently bright displays, as noted by @Matt-Gore in https://github.com/libjxl/libjxl/issues/541. It can lead to quite noticeable banding in slow dark gradients.

This adds 8x8 ordered dithering when converting to uint output. Even just 2x2 or 4x4 dithering already alleviates most of the banding issues, but 8x8 is still very cheap. I couldn't measure any speed difference compared to non-dithered.

jonsneyers avatar Apr 20 '22 14:04 jonsneyers

(btw this was inspired by @veluca93's code from a year ago, see https://gitlab.com/wg1/jpeg-xlm/-/merge_requests/3457)

jonsneyers avatar Apr 20 '22 16:04 jonsneyers

(btw this was inspired by @veluca93's code from a year ago, see https://gitlab.com/wg1/jpeg-xlm/-/merge_requests/3457)

Unless we are very much in a hurry, I'd prefer to first refactor the Write stages to handle all the cases that are currently handled in decode.cc.

Also, I guess we should decide whether dithering should be mandatory, the default, or explicitly requested when writing to U8.

Finally, and it's a bit of an aside, we should explore how to allow people to use U8 output more for this to be really useful, because i.e. you don't want to dither before color conversions.

veluca93 avatar Apr 20 '22 16:04 veluca93

(btw this was inspired by @veluca93's code from a year ago, see https://gitlab.com/wg1/jpeg-xlm/-/merge_requests/3457)

Unless we are very much in a hurry, I'd prefer to first refactor the Write stages to handle all the cases that are currently handled in decode.cc.

Also, I guess we should decide whether dithering should be mandatory, the default, or explicitly requested when writing to U8.

Finally, and it's a bit of an aside, we should explore how to allow people to use U8 output more for this to be really useful, because i.e. you don't want to dither before color conversions.

Sure, no urgent need to merge this, I just wanted to put it here so people can try it if they want, and so we don't forget about it.

Probably we need a color management stage before conversion to u8 for u8 output to be more useful. That's true regardless of dithering, but indeed it makes more sense to do dithering after color conversion instead of before.

jonsneyers avatar Apr 20 '22 16:04 jonsneyers

Dithering should always be used when decreasing the bit depth because this is way more accurate than dropping the least significant bits. If the original was lossless 8 bit, then dithering also won't harm since the pixels will round back to the same values.

And yes, color management should not be done at 8 bit precision if possible. So it should be unrelated to dither and happen earlier on the float values.

Matt-Gore avatar Apr 20 '22 17:04 Matt-Gore

Does this need to be included in 1.0?

mo271 avatar Jun 22 '22 13:06 mo271

Does this need to be included in 1.0?

Yes, it's essential for (lossy) 8 bit output and color management. Dithering was also part of Pik and so it should definitely be included in 1.0.

Matt-Gore avatar Jun 23 '22 10:06 Matt-Gore

I agree that dithering makes enough of a difference for the common use case of 8-bit sRGB output to consider it essential for 1.0. It's also simple enough to implement and doesn't really come at any decode speed cost, so I see no reason to not do it.

jonsneyers avatar Jun 23 '22 12:06 jonsneyers

I suggest to use a blue noise matrix instead of an ordered matrix. Precomputed CC0 blue noise textures are available via http://momentsingraphics.de/BlueNoise.html

cfeck avatar Sep 09 '22 18:09 cfeck

Superceded by #3090

veluca93 avatar Jan 03 '24 16:01 veluca93