AOMediaCodec / av1-avif

AV1 Image File Format Specification - ISO-BMFF/HEIF derivative
https://aomediacodec.github.io/av1-avif/
BSD 2-Clause "Simplified" License
463 stars 40 forks source link

What is the correct rendering of images with PQ curve? #186

Open novomesk opened 2 years ago

novomesk commented 2 years ago

Hello,

I am confused regarding different rendering of hdr_cosmos12920_cicp9-16-0_lossless.avif

When I open https://github.com/AOMediaCodec/av1-avif/raw/master/testFiles/Netflix/avif/hdr_cosmos12920_cicp9-16-0_lossless.avif in Firefox and in Google Chrome, I get different results:

firefox-chrome

Which one is correct?

BTW, same file in eog viewer displays this way:

eog

novomesk commented 2 years ago

darktable:

darktable

krita:

krita

leo-barnes commented 2 years ago

Going by the source PNGs, the output in Chroma/krita is correct.

leo-barnes commented 2 years ago

Hmm... Now that I look closer it's actually a bit confusing. There is original_hdr_cosmos12920.png, which matches the Chrome output. But there's also the corresponding hdr_cosmos12920_cicp9-16-*.png files, which match the eog output.

@cconcolato I'm assuming the expected output is to look the same as the original_* PNGs?

joedrago commented 2 years ago

The Chrome/krita renders are what is expected.

The PNG source data is simply a means to ensure you're receiving the correct RGB values. If you compare the converted RGB output of the AVIF (normalized to float) against the RGB values in the PNG (also normalized to float), you will get the same values. This is more of a decode sanity check than a "does this match render", as there's no official (guaranteed to be accepted everywhere) way to signal BT.2020 PQ in a PNG, yet.

As the README says:

It will not contain an ICC profile but should be considered to have the same color profile as the associated AVIF.

In any typical image viewer, if you try to view a PNG with no embedded color profile, it will simply assume the RGB values in there are SRGB, which will give you the wrong display in this situation. As a simple example, let's take the upper left pixel of hdr_cosmos01650_cicp9-16-0_lossless. The AVIF emits the following information for pixel (0, 0):

image

As you can see, the normalized value of this pixel is (0.36, 0.39, 0.45), which maps to a really specific, 27nits blue in BT2020 PQ. If I take the PNG version, I get:

image

As you can see here, once we normalize the RGB values, we also get (0.36, 0.39, 0.45), which is your proof as an AVIF decoder that you've successfully arrived back at the intended RGB values. However, because that normalized color is being interpreted as SRGB @ 80 nits, it interprets it as a grayer hue and only 9.7 nits.

If I force the profile to be BT.2020 PQ, however:

image

You can see here that the normalized value (0.36, 0.39, 0.45) hasn't changed, but it is now being correctly interpreted as BT.2020 PQ and we see the same hue and 27 nits output.

Once the HDR PNG (cICP block in PNG) tech linked above is blessed and actually implemented by (some browsers/viewers), perhaps I can jam such a block into these PNGs and they'll start looking correct in the same places HDR AVIFs look correct. For now though, these PNGs simply serve to let you know that you've arrived back at the right RGB triplet, and nothing else.

novomesk commented 2 years ago

Please correct me if I understood the situation correctly.

Majority of the apps (except eog) above recalculate decoded data via following EOTF to linear TRC:

EOTF_PQ

However monitor cannot display whole range (0 - 10000nits). In case darktable and/or firefox attempts to display maximal RGB value, monitor will not emit 10000nits but only 80? (as sRGB suggests) That's probably why the image looks dark.

Following code is from krita. They also use the well known PQ EOTF. But they multiply the result with 125 constant. But where the 125 comes from? Is it 10000 / 80 ? (maxPQnits / maxSRGBnits or it is a pure coincidence?)

ALWAYS_INLINE float removeSmpte2048Curve(float x) noexcept
{
    const float m1_r = 4096.0f * 4.0f / 2610.0f;
    const float m2_r = 4096.0f / 2523.0f / 128.0f;
    const float a1 = 3424.0f / 4096.0f;
    const float c2 = 2413.0f / 4096.0f * 32.0f;
    const float c3 = 2392.0f / 4096.0f * 32.0f;

    const float x_p = powf(x, m2_r);
    const float res = powf(qMax(0.0f, x_p - a1) / (c2 - c3 * x_p), m1_r);
    return res * 125.0f;
}

What is recommended to do with too bright values which exceeds possibility of typical sRGB displays? Is is better to clip each of the R,G,B channels individually or to normalize the triplet to maximal possible brightness?

leo-barnes commented 2 years ago

What is recommended to do with too bright values which exceeds possibility of typical sRGB displays? Is is better to clip each of the R,G,B channels individually or to normalize the triplet to maximal possible brightness?

Let me check if there's a document that has recommendations for how to handle this. I would guess that some kind of more gradual tone mapping is preferred over clipping.

leo-barnes commented 2 years ago

The HDR folks recommend the following: https://www.itu.int/pub/R-REP-BT.2446

You might also want to check out HDRTools. From what I've been told it doesn't fully implement the document above since that came later, but it should have good starting points.

novomesk commented 2 years ago

Thank you Leo, that's very interesting material to study.