 # YUV to RGB conversion for TV video

[Update 2020/12/20]

At Silicondust we have a long standing goal of providing the best possible picture quality for viewing TV within the home. Problem – we recently discovered some issues with the YUV to RGB implementation within the HDHomeRun app and launched an effort to get the colorspace conversion right across a wide range of platforms and devices.

Source content – in theory the source video could use any of many possible colorspace standards. ATSC 1.0 follows ITU-R BT.709-6 unless otherwise specified in the MPEG2 headers. We have found through real-world testing that broadcasters tend to always use BT.709-6 for ATSC 1.0 MPEG2 content regardless of the resolution. In the future we expect to see ATSC 3.0 HEVC 4K content utilizing the BT.2020 colorspace standard. Any app doing colorspace conversion should detect the colorspace standard the source content follows.

RGB output path – in theory the RGB output path could use any of many possible colorspace standards. In practice until you get to 4K and configure BT.2020 colorspace or some HDR variant you can expect computer and TV displays to follow BT.709-6 as well. Any app doing colorspace conversion should query the colorspace standard the output path follows.

This post documents the most common situation of BT.709-6 8-bit YCbCr (YUV) to RGB conversion.

The BT.709-6 standard can be found here: https://www.itu.int/rec/R-REC-BT.709-6-201506-I/en

YUV to RGB from BT.709-6:

The BT.709-6 standard specifies the equations for RGB to YUV conversion:

Y = R * 0.2126 + G * 0.7152 + B * 0.0722
Cb = (R * -0.2126/1.8556 + G * -0.7152/1.8556 + B * 0.9278/1.8556) * 224/219 + 128
Cr = (R * 0.7874/1.5748 + G * -0.7152/1.5748 + B *-0.0722/1.5748) * 224/219 + 128

The reverse takes a bit of rearranging. The equations here show this reversal using the original numbers from the BT.709-6 standard so as not to introduce rounding errors.

R = Y + (Cr – 128) * 219/224 * 1.5748
G = Y + (Cb – 128) * 219/224 * -0.0722*1.8556/0.7152 + (Cr – 128) * 219/224 * -0.2126*1.5748/0.7152
B = Y + (Cb – 128) * 219/224 * 1.8556

Note that these RGB values have a valid range of 16 to 235.
These equations can be self tested by converting back and forth between RGB and YCrCb.

PCs use full range values (0,0,0 for black and 255,255,255 for white) so the RGB values need to be rescaled to full range:

Rfull = (R – 16) * 255/219
Gfull = (G – 16) * 255/219
Bfull = (B – 16) * 255/219

The fullrange rescale can be rolled into the conversion equations to get:

Rfull = Y * 1.1643835616 + Cr * 1.7927410714 + -248.100994
Gfull = Y * 1.1643835616 + Cb * -0.2132486143 + Cr * -0.5329093286 + 76.878080
Bfull = Y * 1.1643835616 + Cb * 2.1124017857 + -289.017566

Clamp the resulting RGB values to min 0 max 255. To convert to integer add 0.5 and truncate (cast to int). Some possible values are critically close to the 0.5 rounding point – if calculated using float precision some integer values will be off by 1 compared to the double precision reference. This won’t matter for most applications. The numbers shown have the minimum required precision to ensure an exact integer match to the reference when using double precision floats.

GPU notes:

YCbCr to RGB conversion is typically done using GPU shaders so the 0-255 YCbCr values are automatically converted into 0.0-1.0 range floats (ie gpuY = Y / 255). This doesn’t affect the multiplication numbers but the offset numbers need to be divided by 255.

const mat3 YCbCrToRGBmatrix = mat3(
1.1643835616, 0.0000000000, 1.7927410714,
1.1643835616, -0.2132486143, -0.5329093286,
1.1643835616, 2.1124017857, 0.0000000000
);

const vec3 YCbCrToRGBzero = vec3(-0.972945075, 0.301482665, -1.133402218);

vec3 RGBFullRange = YCbCr * YCbCrToRGBmatrix + YCbCrToRGBzero;
vec3 RGBFullRangeClamped = clamp(RGBFullRange, vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 1.0));

Factor generation code
The code used to generate the factors and zero offsets can be found here. Values are calculated using the original numbers from the BT.709-6 standard utilizing double-precision mathematics. The printed values have sufficient resolution to meet or exceed float-precision.

Testing