From 5f81c29e307735bbadb3c0cb06500af627b2e57a Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Wed, 9 Sep 2015 01:28:40 +0200 Subject: [PATCH] Prepare for better understanding of 10- and 12-bit Y'CbCr. Seemingly there is trickiness in how to interpret the integer values that is different from what you'll typically see in R'G'B' (or just GPUs and TV standards differ on that point as well). Add an explanatory comment, and add a data member to YCbCrFormat to prepare for correct 10/12-bit level handlings. We'll stay 8-bit only for now, though, to avoid an API break for existing clients for no good reason (there's no 10-bit input, really). --- ycbcr.cpp | 2 ++ ycbcr.h | 33 +++++++++++++++++++++++++++++ ycbcr_422interleaved_input_test.cpp | 4 ++++ ycbcr_input_test.cpp | 8 +++++++ 4 files changed, 47 insertions(+) diff --git a/ycbcr.cpp b/ycbcr.cpp index 277ea9c..dc223e7 100644 --- a/ycbcr.cpp +++ b/ycbcr.cpp @@ -90,6 +90,7 @@ void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float* offset, Matrix3d* ycb } if (ycbcr_format.full_range) { + // TODO: Use num_levels. offset[0] = 0.0 / 255.0; offset[1] = 128.0 / 255.0; offset[2] = 128.0 / 255.0; @@ -99,6 +100,7 @@ void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float* offset, Matrix3d* ycb scale[2] = 1.0; } else { // Rec. 601, page 4; Rec. 709, page 19; Rec. 2020, page 4. + // TODO: Use num_levels. offset[0] = 16.0 / 255.0; offset[1] = 128.0 / 255.0; offset[2] = 128.0 / 255.0; diff --git a/ycbcr.h b/ycbcr.h index 7e5891f..9179b19 100644 --- a/ycbcr.h +++ b/ycbcr.h @@ -2,6 +2,35 @@ #define _MOVIT_YCBCR_H 1 // Shared utility functions between YCbCrInput and YCbCr422InterleavedInput. +// +// Conversion from integer to floating-point representation in case of +// Y'CbCr is seemingly tricky: +// +// BT.601 page 8 has a table that says that for luma, black is at 16.00_d and +// white is at 235.00_d. _d seemingly means “on a floating-point scale from 0 +// to 255.75”, see §2.4. The .75 is because BT.601 wants to support 10-bit, +// but all values are scaled for 8-bit since that's the most common; it is +// specified that conversion from 8-bit to 10-bit is done by inserting two +// binary zeroes at the end (not repeating bits as one would often do +// otherwise). It would seem that BT.601 lives in a world where the idealized +// range is really [0,256), not [0,255]. +// +// However, GPUs (and by extension Movit) don't work this way. For them, +// typically 1.0 maps to the largest possible representable value in the +// framebuffer, ie., the range [0.0,1.0] maps to [0,255] for 8-bit +// and to [0,1023] (or [0_d,255.75_d] in BT.601 parlance) for 10-bit. +// +// BT.701 (page 5) seems to agree with BT.601; it specifies range 16–235 for +// 8-bit luma, and 64–940 for 10-bit luma. This would indicate, for a GPU, +// that that for 8-bit mode, the range would be 16/255 to 235/255 +// (0.06275 to 0.92157), while for 10-bit, it should be 64/1023 to 940/1023 +// (0.06256 to 0.91887). There's no good compromise here; if you select 8-bit +// range, 10-bit goes out of range (white gets to 942), while if you select +// 10-bit range, 8-bit gets only to 234, making true white impossible. +// +// We currently support the 8-bit ranges only, since all of our Y'CbCr +// handling effects happen to support only 8-bit at the moment. We will need +// to fix this eventually, though, with an added field to YCbCrFormat. #include "image_format.h" @@ -18,6 +47,10 @@ struct YCbCrFormat { // JPEG uses the Rec. 601 luma coefficients, but full range. bool full_range; + // Currently unused, but should be set to 256 for future expansion, + // indicating 8-bit interpretation (see file-level comment). + int num_levels; + // Sampling factors for chroma components. For no subsampling (4:4:4), // set both to 1. unsigned chroma_subsampling_x, chroma_subsampling_y; diff --git a/ycbcr_422interleaved_input_test.cpp b/ycbcr_422interleaved_input_test.cpp index 290885e..9a56fb1 100644 --- a/ycbcr_422interleaved_input_test.cpp +++ b/ycbcr_422interleaved_input_test.cpp @@ -45,6 +45,7 @@ TEST(YCbCr422InterleavedInputTest, Simple422) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.0f; // Doesn't really matter here, since Y is constant. @@ -90,6 +91,7 @@ TEST(YCbCr422InterleavedInputTest, LumaLinearInterpolation) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.0f; // Doesn't really matter here, since U/V are constant. @@ -150,6 +152,7 @@ TEST(YCbCr422InterleavedInputTest, DifferentCbAndCrPositioning) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.0f; @@ -208,6 +211,7 @@ TEST(YCbCr422InterleavedInputTest, PBO) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.0f; // Doesn't really matter here, since Y is constant. diff --git a/ycbcr_input_test.cpp b/ycbcr_input_test.cpp index ee77e4c..873a6c5 100644 --- a/ycbcr_input_test.cpp +++ b/ycbcr_input_test.cpp @@ -44,6 +44,7 @@ TEST(YCbCrInputTest, Simple444) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 1; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.5f; @@ -98,6 +99,7 @@ TEST(YCbCrInputTest, FullRangeRec601) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = true; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 1; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.5f; @@ -151,6 +153,7 @@ TEST(YCbCrInputTest, Rec709) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_709; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 1; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.5f; @@ -206,6 +209,7 @@ TEST(YCbCrInputTest, Rec2020) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_2020; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 1; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.5f; @@ -267,6 +271,7 @@ TEST(YCbCrInputTest, Subsampling420) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 2; ycbcr_format.cb_x_position = 0.5f; @@ -328,6 +333,7 @@ TEST(YCbCrInputTest, Subsampling420WithNonCenteredSamples) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 2; ycbcr_format.cb_x_position = 0.0f; @@ -397,6 +403,7 @@ TEST(YCbCrInputTest, DifferentCbAndCrPositioning) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.0f; @@ -454,6 +461,7 @@ TEST(YCbCrInputTest, PBO) { YCbCrFormat ycbcr_format; ycbcr_format.luma_coefficients = YCBCR_REC_601; ycbcr_format.full_range = false; + ycbcr_format.num_levels = 256; ycbcr_format.chroma_subsampling_x = 1; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.cb_x_position = 0.5f; -- 2.39.2