]> git.sesse.net Git - movit/blob - ycbcr.cpp
f0124eaab178048a2ae3a00c758355fa275668a2
[movit] / ycbcr.cpp
1 #include <Eigen/Core>
2 #include <Eigen/LU>
3
4 #include "ycbcr.h"
5
6 using namespace Eigen;
7
8 namespace movit {
9
10 // OpenGL has texel center in (0.5, 0.5), but different formats have
11 // chroma in various other places. If luma samples are X, the chroma
12 // sample is *, and subsampling is 3x3, the situation with chroma
13 // center in (0.5, 0.5) looks approximately like this:
14 //
15 //   X   X
16 //     *   
17 //   X   X
18 //
19 // If, on the other hand, chroma center is in (0.0, 0.5) (common
20 // for e.g. MPEG-4), the figure changes to:
21 //
22 //   X   X
23 //   *      
24 //   X   X
25 //
26 // In other words, (0.0, 0.0) means that the chroma sample is exactly
27 // co-sited on top of the top-left luma sample. Note, however, that
28 // this is _not_ 0.5 texels to the left, since the OpenGL's texel center
29 // is in (0.5, 0.5); it is in (0.25, 0.25). In a sense, the four luma samples
30 // define a square where chroma position (0.0, 0.0) is in texel position
31 // (0.25, 0.25) and chroma position (1.0, 1.0) is in texel position (0.75, 0.75)
32 // (the outer border shows the borders of the texel itself, ie. from
33 // (0, 0) to (1, 1)):
34 //
35 //  ---------
36 // |         |
37 // |  X---X  |
38 // |  | * |  |
39 // |  X---X  |
40 // |         |
41 //  ---------
42 //
43 // Also note that if we have no subsampling, the square will have zero
44 // area and the chroma position does not matter at all.
45 float compute_chroma_offset(float pos, unsigned subsampling_factor, unsigned resolution)
46 {
47         float local_chroma_pos = (0.5 + pos * (subsampling_factor - 1)) / subsampling_factor;
48         return (0.5 - local_chroma_pos) / resolution;
49 }
50
51 // Given <ycbcr_format>, compute the values needed to turn Y'CbCr into R'G'B';
52 // first subtract the returned offset, then left-multiply the returned matrix
53 // (the scaling is already folded into it).
54 void compute_ycbcr_matrix(YCbCrFormat ycbcr_format, float* offset, Matrix3d* ycbcr_to_rgb)
55 {
56         double coeff[3], scale[3];
57
58         switch (ycbcr_format.luma_coefficients) {
59         case YCBCR_REC_601:
60                 // Rec. 601, page 2.
61                 coeff[0] = 0.299;
62                 coeff[1] = 0.587;
63                 coeff[2] = 0.114;
64                 break;
65
66         case YCBCR_REC_709:
67                 // Rec. 709, page 19.
68                 coeff[0] = 0.2126;
69                 coeff[1] = 0.7152;
70                 coeff[2] = 0.0722;
71                 break;
72
73         case YCBCR_REC_2020:
74                 // Rec. 2020, page 4.
75                 coeff[0] = 0.2627;
76                 coeff[1] = 0.6780;
77                 coeff[2] = 0.0593;
78                 break;
79
80         default:
81                 assert(false);
82         }
83
84         if (ycbcr_format.full_range) {
85                 offset[0] = 0.0 / 255.0;
86                 offset[1] = 128.0 / 255.0;
87                 offset[2] = 128.0 / 255.0;
88
89                 scale[0] = 1.0;
90                 scale[1] = 1.0;
91                 scale[2] = 1.0;
92         } else {
93                 // Rec. 601, page 4; Rec. 709, page 19; Rec. 2020, page 4.
94                 offset[0] = 16.0 / 255.0;
95                 offset[1] = 128.0 / 255.0;
96                 offset[2] = 128.0 / 255.0;
97
98                 scale[0] = 255.0 / 219.0;
99                 scale[1] = 255.0 / 224.0;
100                 scale[2] = 255.0 / 224.0;
101         }
102
103         // Matrix to convert RGB to YCbCr. See e.g. Rec. 601.
104         Matrix3d rgb_to_ycbcr;
105         rgb_to_ycbcr(0,0) = coeff[0];
106         rgb_to_ycbcr(0,1) = coeff[1];
107         rgb_to_ycbcr(0,2) = coeff[2];
108
109         float cb_fac = (224.0 / 219.0) / (coeff[0] + coeff[1] + 1.0f - coeff[2]);
110         rgb_to_ycbcr(1,0) = -coeff[0] * cb_fac;
111         rgb_to_ycbcr(1,1) = -coeff[1] * cb_fac;
112         rgb_to_ycbcr(1,2) = (1.0f - coeff[2]) * cb_fac;
113
114         float cr_fac = (224.0 / 219.0) / (1.0f - coeff[0] + coeff[1] + coeff[2]);
115         rgb_to_ycbcr(2,0) = (1.0f - coeff[0]) * cr_fac;
116         rgb_to_ycbcr(2,1) = -coeff[1] * cr_fac;
117         rgb_to_ycbcr(2,2) = -coeff[2] * cr_fac;
118
119         // Inverting the matrix gives us what we need to go from YCbCr back to RGB.
120         *ycbcr_to_rgb = rgb_to_ycbcr.inverse();
121
122         // Fold in the scaling.
123         *ycbcr_to_rgb *= Map<const Vector3d>(scale).asDiagonal();
124 }
125
126 }  // namespace movit