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.
4 #include <Eigen/Core>
5 #include <Eigen/LU>
7 #include "ycbcr.h"
9 using namespace Eigen;
11 namespace movit {
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:
17 //
18 //   X   X
19 //     *
20 //   X   X
21 //
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:
24 //
25 //   X   X
26 //   *
27 //   X   X
28 //
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
36 // (0, 0) to (1, 1)):
37 //
38 //  ---------
39 // |         |
40 // |  X---X  |
41 // |  | * |  |
42 // |  X---X  |
43 // |         |
44 //  ---------
45 //
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)
49 {
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.
53                 return -0.0;
54         } else {
55                 return (0.5 - local_chroma_pos) / resolution;
56         }
57 }
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, GLenum type, double *scale_factor)
63 {
64         double coeff, scale;
66         switch (ycbcr_format.luma_coefficients) {
67         case YCBCR_REC_601:
68                 // Rec. 601, page 2.
69                 coeff = 0.299;
70                 coeff = 0.587;
71                 coeff = 0.114;
72                 break;
74         case YCBCR_REC_709:
75                 // Rec. 709, page 19.
76                 coeff = 0.2126;
77                 coeff = 0.7152;
78                 coeff = 0.0722;
79                 break;
81         case YCBCR_REC_2020:
82                 // Rec. 2020, page 4.
83                 coeff = 0.2627;
84                 coeff = 0.6780;
85                 coeff = 0.0593;
86                 break;
88         default:
89                 assert(false);
90         }
92         int num_levels = ycbcr_format.num_levels;
93         if (num_levels == 0) {
94                 // For the benefit of clients using old APIs, but still zeroing out the structure.
95                 num_levels = 256;
96         }
97         if (ycbcr_format.full_range) {
98                 offset = 0.0 / (num_levels - 1);
99                 offset = double(num_levels / 2) / (num_levels - 1);  // E.g. 128/255.
100                 offset = double(num_levels / 2) / (num_levels - 1);
102                 scale = 1.0;
103                 scale = 1.0;
104                 scale = 1.0;
105         } else {
106                 // Rec. 601, page 4; Rec. 709, page 19; Rec. 2020, page 5.
107                 // Rec. 2020 contains the most generic formulas, which we use here.
108                 const double s = num_levels / 256.0;  // 2^(n-8) in Rec. 2020 parlance.
109                 offset = (s * 16.0) / (num_levels - 1);
110                 offset = (s * 128.0) / (num_levels - 1);
111                 offset = (s * 128.0) / (num_levels - 1);
113                 scale = double(num_levels - 1) / (s * 219.0);
114                 scale = double(num_levels - 1) / (s * 224.0);
115                 scale = double(num_levels - 1) / (s * 224.0);
116         }
118         // Matrix to convert RGB to YCbCr. See e.g. Rec. 601.
119         Matrix3d rgb_to_ycbcr;
120         rgb_to_ycbcr(0,0) = coeff;
121         rgb_to_ycbcr(0,1) = coeff;
122         rgb_to_ycbcr(0,2) = coeff;
124         float cb_fac = 1.0 / (coeff + coeff + 1.0f - coeff);
125         rgb_to_ycbcr(1,0) = -coeff * cb_fac;
126         rgb_to_ycbcr(1,1) = -coeff * cb_fac;
127         rgb_to_ycbcr(1,2) = (1.0f - coeff) * cb_fac;
129         float cr_fac = 1.0 / (1.0f - coeff + coeff + coeff);
130         rgb_to_ycbcr(2,0) = (1.0f - coeff) * cr_fac;
131         rgb_to_ycbcr(2,1) = -coeff * cr_fac;
132         rgb_to_ycbcr(2,2) = -coeff * cr_fac;
134         // Inverting the matrix gives us what we need to go from YCbCr back to RGB.
135         *ycbcr_to_rgb = rgb_to_ycbcr.inverse();
137         // Fold in the scaling.
138         *ycbcr_to_rgb *= Map<const Vector3d>(scale).asDiagonal();
140         if (type == GL_UNSIGNED_SHORT) {
141                 // For 10-bit or 12-bit packed into 16-bit, we need to scale the values
142                 // so that the max value goes from 1023 (or 4095) to 65535. We do this
143                 // by folding the scaling into the conversion matrix, so it comes essentially
144                 // for free. However, the offset is before the scaling (and thus assumes
145                 // correctly scaled values), so we need to adjust that the other way.
146                 double scale = 65535.0 / (ycbcr_format.num_levels - 1);
147                 offset /= scale;
148                 offset /= scale;
149                 offset /= scale;
150                 *ycbcr_to_rgb *= scale;
151                 if (scale_factor != NULL) {
152                         *scale_factor = scale;
153                 }
154         } else if (scale_factor != NULL) {
155                 *scale_factor = 1.0;
156         }
157 }
159 }  // namespace movit