Washed-out or tinted video can be the result of bad color conversion. Video typically uses YCbCr, but YCbCr may be represented in several different color spaces. Understanding the various parameters is key to using the correct calculations.

If you get lost in this tutorial, read Color: YCbCr and RGB first.

**tl;dr** Convert RGB and YCbCr using the following:

**RGB to YCbCr full-range**

y = 0.2126 * r + 0.7152 * g + 0.0722 * b cb = -0.1146 * r - 0.3854 * g + 0.5 * b + 128 cr = 0.5 * r - 0.4542 * g - 0.0458 * b + 128

**RGB to YCbCr video-range**

y 0.1826 * r + 0.6142 * g + 0.0620 * b + 16

cb = -0.1006 * r - 0.3386 * g + 0.4392 * b + 128

cr = 0.4392 * r - 0.3989 * g - 0.0403 * b + 128

**YCbCr full-range to RGB**

r = y + 1.5747 * cr - 201.5506

g = y - 0.1873 * cb - 0.4681 * cr + 83.8918

b = y + 1.8556 * cb - 0.0001 * cr - 237.5316

**YCbCr video-range to RGB**

r = 1.1644 * y + 0.0001 * cb + 1.7928 * cr - 248.1231

g = 1.1644 * y - 0.2133 * cb - 0.5330 * cr + 76.8886

b = 1.1644 * y + 2.1125 * cb - 0.0002 * cr - 288.9952

## Deriving from BT.709

BT.709 is the HDTV color specification and describes exactly what red, green, and blue look like, and how to convert to YCbCr.

There are other color standards, like BT.601 (SDTV) and BT.2020 (The new UHD color space).

We will only look at converting between BT.709 RGB (a.k.a. sRGB) and BT.709 YCbCr.

## RGB to YCbCr

First, let’s look at converting 8-bit sRGB to 8-bit BT.709 YCbCr full range.

That YCbCr bit is a mouthful. BT.709 defines a color space which is also shared with sRGB. Full range means the 8-bit samples use the full 0-255 range that can be represented with 8 bits. Video range limits the values and can only represent 219 luma levels and 224 chroma levels.

The calculation is simplified because both this YCbCr variant and sRGB are defined by the same color primaries, transfer function, and gamma correction function.

In BT.709 Table 3.2 and 3.3, it defines how to convert between Er, Eg, Eb, and Ey, Ecb, and Ecr. In the spec, the Ey maps to the range [0, 1], and Ecb and Ecr map to [-0.5, 0.5].

So to convert, we first need to map the integer values to this range.

Er = r / 255.0;

Eg = g / 255.0;

Eb = b / 255.0;

This can be represented as the matrix multiplication.

A = |1/255 0 0 0|

|0 1/255 0 0|

|0 0 1/255 0|

|0 0 0 1|

From here on out, everything will be represented as a matrix to make converting YCbCr back to RGB easier in the second half of this tutorial.

Then convert the normalized RGB values to normalized YCbCr values, using the values from BT.709.

B = | 0.2126 0.7152 0.0722 0 |

| -0.1146 -0.3854 0.5 0 |

| 0.5 -0.4542 0.0458 0 |

| 0 0 0 1 |

Then scale the values up by a factor of 255 (note: this will scale Cb and Cr to [-128, 127] because their “normalized” values are in the range [0.5, 0.5)).

C = | 255 0 0 0 |

| 0 255 0 0 |

| 0 0 255 0 |

| 0 0 0 1 |

And finally, shift the Cb and Cr values up by 128 to move it into the range [0, 255].

D = | 1 0 0 0 |

| 0 1 0 128 |

| 0 0 1 128 |

| 0 0 0 1 |

The conversion can be calculated as:

ycbcr = D * C * B * A * rgb

The effect of the matrix multiplications are right-to-left.

Simplifying (y, cb, cr, 1) = D * C * B * A * (r, g, b, 1):

| y | | 0.2126 0.7152 0.0722 0 | | r |

| cb | = | -0.1146 -0.3854 0.5 128 | * | g |

| cr | = | 0.5 -0.4542 -0.0458 128 | | b |

| 1 | = | 0 0 0 1 | | 1 |

Now clamp each Y, Cb, and Cr value to [0, 255] so that 0 <= y, cb, cr <= 255.

Video-range YCbCr follows the same process to get the normalized Y, Cb, and Cr values. Since the range of values is smaller, the matrix that scales the YCbCr values to integer range, **C**, is different, and the translation matrix **D** is somewhat different.

Video range, change matrix **C** to:

C = | 219 0 0 0 |

| 0 224 0 0 |

| 0 0 224 0 |

| 0 0 0 1 |

Now translate Y by 16 (Y is in the range [16, 235]), Cb and Cr by 128 (final range is [16, 240]).

D = | 1 0 0 16 |

| 0 1 0 128 |

| 0 0 1 128 |

| 0 0 0 1 |

For RGB to video-range YCbCr, D * C * B * A can be simplified to:

| y | | 0.1826 0.6142 0.0620 16 | | r |

| cb | = | -0.1006 -0.3386 0.4392 128 | * | g |

| cr | = | 0.4392 -0.3989 -0.0403 128 | | b |

| 1 | = | 0 0 0 1 | | 1 |

Then round each y, cb, and cr to the nearest integer. Clamp y to [16, 235] and clamp cb and cr to [16, 240].

## YCbCr to RGB

The spec only gives equations for converting RGB to YCbCr, not the other way around. The reason we’ve been using 4×4 matrix multiplication for color conversion is to invert the matrix so we can do the conversion from YCbCr to YCbCr.

The conversion matrix for RGB to full-range YCbCr calculated above is

| 0.2126 0.7152 0.0722 0 |

| -0.1146 -0.3854 0.5 128 |

| 0.5 -0.4542 -0.0458 128 |

| 0 0 0 1 |

Taking the inverse gives us

| 1 0 1.5747 -201.5506 |

| 1 -0.1873 -0.4681 83.8918 |

| 1 1.8556 -0.000105 -237.5316 |

| 0 0 0 1 |

And recalling the matrix to convert RGB to video-range YCbCr:

| 0.1826 0.6142 0.0620 16 |

| -0.1006 -0.3386 0.4392 128 |

| 0.4392 -0.3989 -0.0403 128 |

| 0 0 0 1 |

Calculating the inverse to convert video-range YCbCr to RGB:

| 1.1644 0.0001 1.7928 -248.1231 |

| 1.1644 -0.2133 -0.5330 76.8886 |

| 1.1644 2.1125 -0.0002 -288.9952 |

| 0 0 0 1 |

Each value needs to be rounded to the nearest integer. Clamp r, g, and b to [0, 255].

In practice, you can drop the bottom row of the conversion matrix. This was only necessary to calculate the inverse of the matrix.

Bonus: There is such a thing as video-range RGB. The range for each R, G, and B value is [16, 235].