In this series of blog posts, I'm going to cover various parts of the research and development behind Bink 2 HDR.
There are 4 different color standards, namely Rec.601, Rec.709, Rec.2020, and Rec.2100. Rec.601, Rec.709 and Rec.2020 all have different definitions of R,G and B. Rec.2020 notably defines a "wide color gamut". Rec.2100 has the same color gamut as Rec.2020, but defines Percentual Quantiers (PQ) and Hybrid Log-Gamma (HLG) for UHDR. First, when working with HDR color spaces, be sure to use linear RGB inputs. No sRGB, Adobe RGB, etc...What about if your loading a floating point format like EXR or HDR? These are both linear, so no conversion *should* be necessary. There are three color spaces in Rec.2100. - Non-Constant Luminance (NCL) YCbCr (most common)
- Constant Luminance ICtCp
## Non-Constant Luminance (NCL) YCbCr
This color space is very similar to LDR YCbCr after tone mapping from 10k Luma to LDR range.
The code for converting to/from linear RGB to NCL Y'Cb'Cr' is as follows... void RGBtoYCbCr2100(float *rgbx, float *ycbcr, int num) { for(int i = 0; i < num*4; i+=4) { float r = rgbx[i+0], g = rgbx[i+1], b = rgbx[i+2]; r = smpte2084_encodef(r); g = smpte2084_encodef(g); b = smpte2084_encodef(b); ycbcr[i+0] = 0.2627f*r + 0.6780f*g + 0.0593f*b; ycbcr[i+1] = -0.13963f*r - 0.36037f*g + 0.5f*b; ycbcr[i+2] = 0.5f*r - 0.45979f*g - 0.040214f*b; ycbcr[i+3] = rgbx[i+3]; } } void YCbCrtoRGB2100(float *ycbcrx, float *rgbx, int num) { for(int i = 0; i < num*4; i+=4) { float Y = ycbcrx[i+0], Cb = ycbcrx[i+1], Cr = ycbcrx[i+2]; float r = Y + 1.4746f*Cr; float g = Y - 0.164552f*Cb - 0.571352f*Cr; float b = Y + 1.8814f*Cb; r = smpte2084_decodef(r); g = smpte2084_decodef(g); b = smpte2084_decodef(b); rgbx[i+0] = r; rgbx[i+1] = g; rgbx[i+2] = b; rgbx[i+3] = ycbcrx[i+3]; } }
We can display these color spaces using LDR images via tone mapping - as shown below.
## Constant Luminance ICtCp
Rec.2100, the latest standard, defines a new color space for HDR which improves on the NCL & CL YCbCr called ICtCp.
The code for converting from Rec.2020 linear RGB to ICtCp is as follows... void RGBtoICtCp(float *rgbx, float *ictcpx, int num) { for(int i = 0; i < num*4; i+=4) { double r = rgbx[i+0], g = rgbx[i+1], b = rgbx[i+2]; double L = 0.4121093750000000*r + 0.5239257812500000*g + 0.0639648437500000*b; double M = 0.1667480468750000*r + 0.7204589843750000*g + 0.1127929687500000*b; double S = 0.0241699218750000*r + 0.0754394531250000*g + 0.9003906250000000*b; L = smpte2084_encodef(L); M = smpte2084_encodef(M); S = smpte2084_encodef(S); ictcpx[i+0] = 0.5*L + 0.5*M; ictcpx[i+1] = 1.613769531250000*L - 3.323486328125000*M + 1.709716796875000*S; ictcpx[i+2] = 4.378173828125000*L - 4.245605468750000*M - 0.132568359375000*S; ictcpx[i+3] = rgbx[i+3]; } } The inverse transform of ICtCp back to Rec.2020 RGB is as follows: void ICtCptoRGB(float *ictcpx, float *rgbx, int num) { for(int i = 0; i < num*4; i+=4) { double I = ictcpx[i+0], T = ictcpx[i+1], P = ictcpx[i+2]; double L = I + 0.00860903703793281*T + 0.11102962500302593*P; double M = I - 0.00860903703793281*T - 0.11102962500302593*P; double S = I + 0.56003133571067909*T - 0.32062717498731880*P; L = smpte2084_decodef(L); M = smpte2084_decodef(M); S = smpte2084_decodef(S); rgbx[i+0] = 3.4366066943330793*L - 2.5064521186562705*M + 0.0698454243231915*S; rgbx[i+1] = -0.7913295555989289*L + 1.9836004517922909*M - 0.1922708961933620*S; rgbx[i+2] = -0.0259498996905927*L - 0.0989137147117265*M + 1.1248636144023192*S; rgbx[i+3] = ictcpx[i+3]; } }
ICtCp claims that it provides an improved color representation that is designed for high dynamic range (HDR) and wide color gamut (WCG). It also claims that for CIEDE2000 color quantization errors 10-bit ICtCp would be equal to 11.5 bit YCbCr. Constant luminance is also improved with ICtCp which has a luminance relationship of 0.998 between the luma and encoded brightness while YCbCr has a luminance relationship of 0.819. An improved constant luminance is an advantage for color processing operations such as chroma subsampling and gamut mapping where only color information is changed.
Note, that I haven't verified these claims yet... Again, here is this color space converted to LDR via tone mapping. ## Evaluation of Color Spaces
To evaluate the color spaces, I am looking for a few different properties (that come to mind)
- When interpolating between colors, we want the CIE Luma to be reasonably constant. This is especially important when subsampling to 4:2:2 or 4:2:0 - as well as important when decoding if we stretch the video beyond its 1:1 pixel size (for example when rendering to a texture, or rendering a lower resolution video to a higher resolution display).
- Ringing artifacts must be evaluated which result naturally from a DCT transform common among video codecs. For example, if you have a really bright point on a darker background, how would the ringing artifacts appear?
- Quantization artifacts must also be considered as a result of normal video encoding stuffs.
- etc
## Additional Snippets
static double smpte2084_decodef(double fv) { double num, denom; fv = pow(fv, 1/SMPTE_2084_M2); num = fv - SMPTE_2084_C1; num = num < 0 ? 0 : num; denom = SMPTE_2084_C2 - SMPTE_2084_C3 * fv; return pow(num / denom, 1/SMPTE_2084_M1); } static double smpte2084_encodef(double v) { double tmp = pow(v, SMPTE_2084_M1); return pow((SMPTE_2084_C1 + SMPTE_2084_C2 * tmp) / (1 + SMPTE_2084_C3 * tmp), SMPTE_2084_M2); } ## References
Probably others, but these are the important ones.
0 Comments
## Leave a Reply. |
## Archives
October 2021
## Categories |