1 // Note: These functions are tested in ycbcr_input_test.cpp; both through some
2 // direct matrix tests, but most of all through YCbCrInput's unit tests.
13 // OpenGL has texel center in (0.5, 0.5), but different formats have
14 // chroma in various other places. If luma samples are X, the chroma
15 // sample is *, and subsampling is 3x3, the situation with chroma
16 // center in (0.5, 0.5) looks approximately like this:
22 // If, on the other hand, chroma center is in (0.0, 0.5) (common
23 // for e.g. MPEG-4), the figure changes to:
29 // In other words, (0.0, 0.0) means that the chroma sample is exactly
30 // co-sited on top of the top-left luma sample. Note, however, that
31 // this is _not_ 0.5 texels to the left, since the OpenGL's texel center
32 // is in (0.5, 0.5); it is in (0.25, 0.25). In a sense, the four luma samples
33 // define a square where chroma position (0.0, 0.0) is in texel position
34 // (0.25, 0.25) and chroma position (1.0, 1.0) is in texel position (0.75, 0.75)
35 // (the outer border shows the borders of the texel itself, ie. from
46 // Also note that if we have no subsampling, the square will have zero
47 // area and the chroma position does not matter at all.
48 float compute_chroma_offset(float pos, unsigned subsampling_factor, unsigned resolution)
50 float local_chroma_pos = (0.5 + pos * (subsampling_factor - 1)) / subsampling_factor;
51 if (fabs(local_chroma_pos - 0.5) < 1e-10) {
52 // x + (-0) can be optimized away freely, as opposed to x + 0.
55 return (0.5 - local_chroma_pos) / resolution;
59 // Given <ycbcr_format>, compute the values needed to turn Y'CbCr into R'G'B';
60 // first subtract the returned offset, then left-multiply the returned matrix
61 // (the scaling is already folded into it).
62 void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float* offset, Matrix3d* ycbcr_to_rgb)
64 double coeff[3], scale[3];
66 switch (ycbcr_format.luma_coefficients) {
92 if (ycbcr_format.full_range) {
93 // TODO: Use num_levels.
94 offset[0] = 0.0 / 255.0;
95 offset[1] = 128.0 / 255.0;
96 offset[2] = 128.0 / 255.0;
102 // Rec. 601, page 4; Rec. 709, page 19; Rec. 2020, page 4.
103 // TODO: Use num_levels.
104 offset[0] = 16.0 / 255.0;
105 offset[1] = 128.0 / 255.0;
106 offset[2] = 128.0 / 255.0;
108 scale[0] = 255.0 / 219.0;
109 scale[1] = 255.0 / 224.0;
110 scale[2] = 255.0 / 224.0;
113 // Matrix to convert RGB to YCbCr. See e.g. Rec. 601.
114 Matrix3d rgb_to_ycbcr;
115 rgb_to_ycbcr(0,0) = coeff[0];
116 rgb_to_ycbcr(0,1) = coeff[1];
117 rgb_to_ycbcr(0,2) = coeff[2];
119 float cb_fac = 1.0 / (coeff[0] + coeff[1] + 1.0f - coeff[2]);
120 rgb_to_ycbcr(1,0) = -coeff[0] * cb_fac;
121 rgb_to_ycbcr(1,1) = -coeff[1] * cb_fac;
122 rgb_to_ycbcr(1,2) = (1.0f - coeff[2]) * cb_fac;
124 float cr_fac = 1.0 / (1.0f - coeff[0] + coeff[1] + coeff[2]);
125 rgb_to_ycbcr(2,0) = (1.0f - coeff[0]) * cr_fac;
126 rgb_to_ycbcr(2,1) = -coeff[1] * cr_fac;
127 rgb_to_ycbcr(2,2) = -coeff[2] * cr_fac;
129 // Inverting the matrix gives us what we need to go from YCbCr back to RGB.
130 *ycbcr_to_rgb = rgb_to_ycbcr.inverse();
132 // Fold in the scaling.
133 *ycbcr_to_rgb *= Map<const Vector3d>(scale).asDiagonal();