How do I correctly convert colors stored as Y'CrCb (using rec. 709) to sRGB?
I'm working with HDTV video and I'm extracting the raw data using libavcodec. While I have succeeded in making the conversion, I have yet to be confident that I'm doing it correctly. VLC provides one result, converting in Gimp using 'compose' results in another and using code from the web is not consistent either. So I haven't found a reliable reference to compare to.
My research and current best bet is below. (Values are in floating-point with the range 0.0-1.0) What I'm most uncertain about is the gamma-correction. It makes it a bit lighter than I would expect, but I can't say it looks wrong either...
Studio-swing removal
Y' ranges from 16 to 235 for 8-bit. Cr and Cb ranges from 16 to 240, and centers on 128.
y = (y - (16 / 255.0)) * ( 1 + 16.0 / 255.0 + (256-235) / 255.0 );
u = (u - (16 / 255.0)) * ( 1 + 16.0 / 255.0 + (256-240) / 255.0 );
v = (v - (16 / 255.0)) * ( 1 + 16.0 / 255.0 + (256-240) / 255.0 );
//Move chroma
u -= 0.5;
v -= 0.5;
I'm not sure if it is safe to assume that you will never get values outside the ranges, or if you need to clamp it.
For higher bit-depths the spec says that the LSBs are ignored. What does that mean? I'm also working with material encoded in 10-bit, so this is of interest to me.
From Y'CrCb to RGB
The rec. 709 specification tells how to convert RGB to Y'CrCb:
E'y = 0.2126 * E'r + 0.7152 * E'g + 0.0722 * E'b
E'cb = 0.5389 * ( E'b - E'y )
E'cr = 0.6350 * ( E'r - E'y )
Wikipedia provides what appears to be a slightly more accurate definition for Cb and Cr:
Pb = 0.5 * (B' - Y') / (1 - Kb)
Pr = 0.5 * (R' - Y') / (1 - Kr)
where Kb and Kr are the factors for E'b and E'r. The values from the spec. appears to be rounded from these equations.
RGB can be found by reversing the equations (using Wikipedia version):
double r = y + 2*(1.0-kr) * v;
double b = y + 2*(1.0-kb) * u;
double g = ( y - kr * rr - kb*rb ) / kg;
G can be done using Cr and Cb directly:
double g = y - 2*kr*(1-kr)/kg * v - 2*kb*(1-kb)/kg * u;
(The factor for y is (1-kr-kb)/kg, which is kg/kg as kr+kb+kg=1)
RGB to sRGB
I haven't seen any code examples including this step at all. We need to convert the color space specified by rec. 709 to the one specified in sRGB. AFAIK, the only difference between the two is the transfer function (i.e. gamma). The XY coordinates specified by rec. 709 matches sRGB, but I don't know why sRGB includes the 'Z' coordinate while rec. 709 does not. Does this make a difference? (I know nothing about CIE XYZ.)
The rec. 709 specifies how to gamma-encode linear RGB:
V = 1.099 * L^0.45 - 0.099 for 1 >= L >= 0.018
V = 4.500 * L for 0.018 > L >= 0
We need to reverse it, however the linear cutoff 0.018 does not give the same value for V in both equations. So what are the ranges for the reversed version?:
L = ( ( V + 0.099 ) / 1.099 ) ^ (1/0.45) for 1 >= V >= ?
L = V / 4.5000 for ? > V >= 0
sRGB had the same issue, but was revised to be 0.0031308 which is more accurate. I remember someone devised a fraction which precisely represented it for sRGB, but I cannot find it again...
I'm currently using the following:
double cutoff = 1.099 * pow( 0.018, 0.45 ) - 0.099;
v = ( v < cutoff ) ? 1.0/4.5 * v : pow( (v+0.099)/1.099, 1.0/0.45 );
v = ( v <= 0.0031308 ) ? 12.92 * v : 1.055*pow( v, 1.0/2.4 ) - 0.055;