Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
novomesk opened this issue Feb 22, 2022 · 8 comments
Open

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

novomesk opened this issue Feb 22, 2022 · 8 comments

Comments

@novomesk
Copy link

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
Copy link
Author

darktable:

darktable

krita:

krita

@leo-barnes
Copy link
Collaborator

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

@leo-barnes
Copy link
Collaborator

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
Copy link

joedrago commented Feb 23, 2022

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
Copy link
Author

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants