# Y,Cb,Cr to RGB

1. Again, nice work, Sharc.  Quote
2. I took the matrix coefficients for R'G'B' -> Y'CbCr conversion from ITU-R BT.709-6, formula 3.5. For 8bit n=8.
There is issue with this formula - normally you should use 10 bit (8.2 code) and in 10 bit apply mentioned error rounding - conversion to 8 bit values is performed by truncation of fractional part i.e. 8 bit values without fraction - there is no error rounding there - this is subtle but important difference (if you work with broadcast equipment and sources then those differences begin to be obvious).
Not sure why there is many errors in standards and recommendations...  Quote
3. Here is an interesting tidbit to add to this discussion. Fortunately the calculation results agree with Sharc's calculations for 709.

https://www.silicondust.com/yuv-to-rgb-conversion-for-tv-video/  Quote
4. Here is the ffmpeg code I use to encode to Y,Cb,Cr 709:

Code:
`ffmpeg -y   -loop 1 -t 60  -i "color_bars.bmp"  -pix_fmt yuv420p  -c:v  libx265  -c:a  none  -crf 0  -vf scale=w=1280:h=720:out_color_matrix=bt709:out_range=limited  -color_primaries bt709  -color_trc bt709  -colorspace bt709  -r 59.94    bars.mp4`  Quote
5. Sharc: would you be willing to work out the conversion equation for BT.2020 / BT.2100?

Thanks.  Quote
6. Sharc: would you be willing to work out the conversion equation for BT.2020 / BT.2100?

Thanks.
Do you mean doing the matrix inversions? I have really no experience at all with BT.2020, I am not even sure whether the matrix conversion principle still applies for BT.2020 similar as for 601/709.
Maybe someone else will chime in .....  Quote
7. Do you mean doing the matrix inversions? I have really no experience at all with BT.2020, I am not even sure whether the matrix conversion principle still applies for BT.2020 similar as for 601/709.
Yes. The math is way over my head.

Page 4:

https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2020-2-201510-I!!PDF-E.pdf  Quote
8. https://github.com/sekrit-twc/zimg
conversion library between colorspaces, bitdepths, resolutions ... so while avoiding apps that use it, maybe you can use it directly, there is some API examples  Quote
9. I hate it when people give only the final matrix equations for complex conversions. They're are nice for coding but do not elucidate where all the coefficients come from.

This code gets me close but is not very accurate:

Code:
```Kr = 0.299
Kg = 0.587
Kb = 0.114

r = y + 2*(v-128)*(1-Kr)
g = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg
b = y + 2*(u-128)*(1-Kb)```
75% yellow: Y=168, Pb=44, Pr=136
The Kr, Kg, Kb coefficients are for rec.601 but your "YPbPr" values are for limited range rec.709 YCbCr. And they are rounded to the nearest integer. If you use the reciprocal equation and floating point for all the calculations you'll see that the actual limited range values for R=191, G=191, B=0 are Y=168.192, U=44.110, V=135.692. The quoted conversion equation is for converting full range YUV to full range RGB.

The correct coefficients and equations for converting full range RGB to limited range YUV (YCbCr), and full range RGB to full range YUV are given at the aviSynth.nl:

http://avisynth.nl/index.php/Color_conversions

The correct way convert studio (limited range) RGB to limited range YUV is to first convert the studio RGB to full range RGB then convert to limited range YUV using those equations. To reverse the operation convert the YUV to full range RGB then convert the full range RGB back to limited range RGB.

Code:
```sRGB = each studio RGB component
fRGB = each full range RGB component

sRGB = fRGB * 219 / 255 + 16
fRGB = (sRGB-16) * 255 / 219```
Where do the 255 and 219 values come from? Those are the ranges between the defined full black and full white. 255 = 255-0, 219 = 235-16. They should be familiar from the limited range YUV conversion equations.

Given those sRGB to fRGB equations and full range RGB to limited range YUV equations from from the AviSynth docs:

Code:
```Kr = 0.2126
Kg = 0.7152
Kb = 0.0722

y - 16 = (Kr*219/255)*r + (Kg*219/255)*g + (Kb*219/255)*b
v - 128 = 112/255*r - Kg*112/255*g/(1-Kr) - Kb*112/255*b/(1-Kr)
u - 128 = - Kr*112/255*r/(1-Kb) - Kg*112/255*g/(1-Kb) + 112/255*b

r = (255/219)*y + (255/112)*v*(1-Kr) - (255*16/219 + 255*128/112*(1-Kr))
g = (255/219)*y - (255/112)*u*(1-Kb)*Kb/Kg - (255/112)*v*(1-Kr)*Kr/Kg
- (255*16/219 - 255/112*128*(1-Kb)*Kb/Kg - 255/112*128*(1-Kr)*Kr/Kg)
b = (255/219)*y + (255/112)*u*(1-Kb) - (255*16/219 + 255*128/112*(1-Kb))```
You do the following:

First convert the sRGB to fRGB:

Code:
```R = (180-16) * 255 / 235 ~= 191
G = (180-16) * 255 / 235 ~= 191
B = (16-16) * 255 / 235 = 0```
Then plugging those values in the YUV conversion gives you limited range YUV components (rounded to the nearest integer) Y=168, U=44, V=136. Look familiar? Using the inverse conversion (and rounding) gives you back the r=191, g=191, b=0. And converting that fRGB back to sRGB gives you 180, 180, 16. With some other colors the round trip will be off by 1 or 2 units per component -- from the multiple rounding operations. If you run the equations in floating point (with no rounding) you'll find the conversions are accurate to within the accuracy of the floating point operations (roughly 1 in 10^7 with single precision fp, 1 in 10^16 with double precision fp).
This code returns R=181, G=180, B=12
Code:
```
Kr = 0.2126
Kg = 0.7152
Kb = 0.0722

y.f = 168
u.f = 44
v.f = 136

r = y + 2*(v-128)*(1-Kr)
g = y - 2*(u-128)*(1-Kb)*Kb/Kg - 2*(v-128)*(1-Kr)*Kr/Kg
b = y + 2*(u-128)*(1-Kb)```
The YUV values in RP-219 are full range, apparently. No conversion to limited range is needed. The RP-219 spec was devised long before the 16 - 235 provision.  Quote
10. The YUV values in RP-219 are full range, apparently.
Limited range

75% W is listed as Y 180 in rp219 . 0.75*235 = 180 for limited range.

You would excpect 0.75*255 = 191 for full range 75% white  Quote
11. The YUV values in RP-219 are full range, apparently.
Limited range

75% W is listed as Y 180 in rp219 . 0.75*235 = 180 for limited range.
OK

You would excpect 0.75*255 = 191 for full range 75% white
There could be excursions above 235 on actual video content, not just test signals. These can be clipped off because they are theoretically not important video content.

0 and 255 are reserved for sync.

Here is the broadcast legalizer which works great with lossless libx264 (crf 0). libx265 leaves too many artifacts and is slower than a glacier.

Code:
`ffmpeg -y  -hide_banner  -i "C0015.mp4" -vf  scale=width=1280:height=720:flags='lanczos',limiter=16:235:1,limiter=16:240:6,setparams=range="tv"  -codec:v libx264  -crf 0  -pix_fmt:v yuv422p  -color_range "tv"  -color_primaries bt709  -color_trc bt709 -colorspace bt709  -movflags +write_colr  -r 59.94  -c:a copy  limited.mov`  Quote
12. This code returns R=181, G=180, B=12
Avisynth docs equations and internal convertion like ConvertToRGB/YUV() are not completely correct for narrow (limited) RGB<->YUV - they may miss 224/219 different scaling domains of Y and Cb/Cr data in digital form (equal for all current typical digital colour standards 601/709/2020). At least up to 3.7.3 release. As I understand Pinterf first promises to make small fix but later looks like decide to make big redesign of many internal conversion functions for correct precision and performance but lost before 3.7.3 release.

The typically available in the Internet search equations for RGB<->YUV designed to work with all 3 channels data in equal scale. So when user operates with YUV data (encoded in 601/709/2020 standards) in storage/distribution domain - he need to take into math equations equalizing of Y and UV scaling (using pre-computed float coefficients for better precision) before applying standard RGB<->YUV equations. Also after computing YUV from RGB - the UV must be quantized in 224-scale while Y in 219-scale. The idea of different scale domains (before quantization) may be designed to have a bit lower quantization noise in 8 and 10 bit digital UV data in storage/distributuion.

Also typical equations works in range 0..max RGB and YUV - so user typically need to subtrack black offset from Y and 'zero' offset from UV before apply RGB<->YUV equations.
So 'standard digital' storage domain for YUV data includes:
1. 224 scale for UV and 219 scale for Y
2. 16 offset for Y and 128 offset for UV
And 1 + 2 must be reversed before applying YUV->RGB transform. And result produced with zero black RGB (bipolar with negative under-blacks). To encode into digital narrow range RGB - add +16 offset (all in 8bit).

So in AVS currently for correct YUV->RGB (narrow/limited) only external plugins are completely correct or user need to apply 224/219 (or back 219/224) additional UV gain before conversion with internal functions.

Also as many modern EBU docs call non-full datarange as 'narrow' it may be named nRGB (to be different from sRGB).

Also note about better thread naming (coded or analog R and B colour difference data) may be important because that different scaling domains (analog gain in analog form) may be also different between digital and analog Y and RB colour difference data (or signalling).  Quote