libdcp
colour_conversion.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014-2021 Carl Hetherington <cth@carlh.net>
3 
4  This file is part of libdcp.
5 
6  libdcp is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  libdcp is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with libdcp. If not, see <http://www.gnu.org/licenses/>.
18 
19  In addition, as a special exception, the copyright holders give
20  permission to link the code of portions of this program with the
21  OpenSSL library under certain conditions as described in each
22  individual source file, and distribute linked combinations
23  including the two.
24 
25  You must obey the GNU General Public License in all respects
26  for all of the code used other than OpenSSL. If you modify
27  file(s) with this exception, you may extend this exception to your
28  version of the file(s), but you are not obligated to do so. If you
29  do not wish to do so, delete this exception statement from your
30  version. If you delete this exception statement from all source
31  files in the program, then also delete it here.
32 */
33 
34 
40 #include "colour_conversion.h"
45 #include "dcp_assert.h"
46 #include <boost/numeric/ublas/matrix.hpp>
47 #include <boost/numeric/ublas/lu.hpp>
48 #include <boost/numeric/ublas/io.hpp>
49 
50 
51 using std::shared_ptr;
52 using std::make_shared;
53 using boost::optional;
54 using namespace dcp;
55 
56 
57 ColourConversion const &
58 ColourConversion::srgb_to_xyz ()
59 {
60  static auto c = new ColourConversion (
61  make_shared<ModifiedGammaTransferFunction>(2.4, 0.04045, 0.055, 12.92),
62  YUVToRGB::REC601,
63  Chromaticity (0.64, 0.33),
64  Chromaticity (0.3, 0.6),
65  Chromaticity (0.15, 0.06),
66  Chromaticity::D65 (),
67  optional<Chromaticity> (),
68  make_shared<GammaTransferFunction>(2.6)
69  );
70  return *c;
71 }
72 
73 ColourConversion const &
74 ColourConversion::rec601_to_xyz ()
75 {
76  static auto c = new ColourConversion (
77  make_shared<GammaTransferFunction>(2.2),
78  YUVToRGB::REC601,
79  Chromaticity (0.64, 0.33),
80  Chromaticity (0.3, 0.6),
81  Chromaticity (0.15, 0.06),
82  Chromaticity::D65 (),
83  optional<Chromaticity> (),
84  make_shared<GammaTransferFunction>(2.6)
85  );
86  return *c;
87 }
88 
89 ColourConversion const &
90 ColourConversion::rec709_to_xyz ()
91 {
92  static auto c = new ColourConversion (
93  make_shared<GammaTransferFunction>(2.2),
94  YUVToRGB::REC709,
95  Chromaticity (0.64, 0.33),
96  Chromaticity (0.3, 0.6),
97  Chromaticity (0.15, 0.06),
98  Chromaticity::D65 (),
99  optional<Chromaticity> (),
100  make_shared<GammaTransferFunction>(2.6)
101  );
102  return *c;
103 }
104 
105 ColourConversion const &
106 ColourConversion::p3_to_xyz ()
107 {
108  static auto c = new ColourConversion (
109  make_shared<GammaTransferFunction>(2.6),
110  YUVToRGB::REC709,
111  Chromaticity (0.68, 0.32),
112  Chromaticity (0.265, 0.69),
113  Chromaticity (0.15, 0.06),
114  Chromaticity (0.314, 0.351),
115  optional<Chromaticity> (),
116  make_shared<GammaTransferFunction>(2.6)
117  );
118  return *c;
119 }
120 
121 ColourConversion const &
122 ColourConversion::rec1886_to_xyz ()
123 {
124  /* According to Olivier on DCP-o-matic bug #832, Rec. 1886 is Rec. 709 with
125  2.4 gamma, so here goes ...
126  */
127  static auto c = new ColourConversion (
128  make_shared<GammaTransferFunction>(2.4),
129  YUVToRGB::REC709,
130  Chromaticity (0.64, 0.33),
131  Chromaticity (0.3, 0.6),
132  Chromaticity (0.15, 0.06),
133  Chromaticity::D65 (),
134  optional<Chromaticity> (),
135  make_shared<GammaTransferFunction>(2.6)
136  );
137  return *c;
138 }
139 
140 ColourConversion const &
141 ColourConversion::rec2020_to_xyz ()
142 {
143  static auto c = new ColourConversion (
144  make_shared<GammaTransferFunction>(2.4),
145  YUVToRGB::REC709,
146  Chromaticity (0.708, 0.292),
147  Chromaticity (0.170, 0.797),
148  Chromaticity (0.131, 0.046),
149  Chromaticity::D65 (),
150  optional<Chromaticity> (),
151  make_shared<GammaTransferFunction>(2.6)
152  );
153  return *c;
154 }
155 
157 ColourConversion const &
159 {
160  static auto c = new ColourConversion (
161  make_shared<SGamut3TransferFunction>(),
162  YUVToRGB::REC709,
163  Chromaticity (0.73, 0.280),
164  Chromaticity (0.140, 0.855),
165  Chromaticity (0.100, -0.050),
166  Chromaticity::D65 (),
167  optional<Chromaticity> (),
168  make_shared<IdentityTransferFunction>()
169  );
170  return *c;
171 }
172 
173 
174 
175 ColourConversion::ColourConversion (
176  shared_ptr<const TransferFunction> in,
177  YUVToRGB yuv_to_rgb,
178  Chromaticity red,
179  Chromaticity green,
180  Chromaticity blue,
181  Chromaticity white,
182  optional<Chromaticity> adjusted_white,
183  shared_ptr<const TransferFunction> out
184  )
185  : _in (in)
186  , _yuv_to_rgb (yuv_to_rgb)
187  , _red (red)
188  , _green (green)
189  , _blue (blue)
190  , _white (white)
191  , _adjusted_white (adjusted_white)
192  , _out (out)
193 {
194 
195 }
196 
197 
198 bool
199 ColourConversion::about_equal (ColourConversion const & other, float epsilon) const
200 {
201  if (!_in->about_equal (other._in, epsilon) ||
202  _yuv_to_rgb != other._yuv_to_rgb ||
203  !_red.about_equal (other._red, epsilon) ||
204  !_green.about_equal (other._green, epsilon) ||
205  !_blue.about_equal (other._blue, epsilon) ||
206  !_white.about_equal (other._white, epsilon) ||
207  (!_out && other._out) ||
208  (_out && !other._out) ||
209  (_out && !_out->about_equal (other._out, epsilon))) {
210  return false;
211  }
212 
213  if (!_adjusted_white && !other._adjusted_white) {
214  return true;
215  }
216 
217  if (
218  _adjusted_white && other._adjusted_white &&
219  fabs (_adjusted_white.get().x - other._adjusted_white.get().x) < epsilon &&
220  fabs (_adjusted_white.get().y - other._adjusted_white.get().y) < epsilon
221  ) {
222  return true;
223  }
224 
225  /* Otherwise one has an adjusted white and other hasn't, or they both have but different */
226  return false;
227 }
228 
229 
230 boost::numeric::ublas::matrix<double>
231 ColourConversion::rgb_to_xyz () const
232 {
233  /* See doc/design/colour.tex */
234 
235  double const D = (_red.x - _white.x) * (_white.y - _blue.y) - (_white.x - _blue.x) * (_red.y - _white.y);
236  double const E = (_white.x - _green.x) * (_red.y - _white.y) - (_red.x - _white.x) * (_white.y - _green.y);
237  double const F = (_white.x - _green.x) * (_white.y - _blue.y) - (_white.x - _blue.x) * (_white.y - _green.y);
238  double const P = _red.y + _green.y * D / F + _blue.y * E / F;
239 
240  boost::numeric::ublas::matrix<double> C (3, 3);
241  C(0, 0) = _red.x / P;
242  C(0, 1) = _green.x * D / (F * P);
243  C(0, 2) = _blue.x * E / (F * P);
244  C(1, 0) = _red.y / P;
245  C(1, 1) = _green.y * D / (F * P);
246  C(1, 2) = _blue.y * E / (F * P);
247  C(2, 0) = _red.z() / P;
248  C(2, 1) = _green.z() * D / (F * P);
249  C(2, 2) = _blue.z() * E / (F * P);
250  return C;
251 }
252 
253 
254 boost::numeric::ublas::matrix<double>
255 ColourConversion::xyz_to_rgb () const
256 {
257  boost::numeric::ublas::matrix<double> A (rgb_to_xyz());
258 
259  /* permutation matrix for the LU-factorization */
260  boost::numeric::ublas::permutation_matrix<std::size_t> pm (A.size1());
261 
262  /* perform LU-factorization */
263  int const r = lu_factorize (A, pm);
264  DCP_ASSERT (r == 0);
265 
266  /* create identity matrix of inverse */
267  boost::numeric::ublas::matrix<double> xyz_to_rgb (3, 3);
268  xyz_to_rgb.assign (boost::numeric::ublas::identity_matrix<double> (A.size1()));
269 
270  /* backsubstitute to get the inverse */
271  lu_substitute (A, pm, xyz_to_rgb);
272 
273  return xyz_to_rgb;
274 }
275 
276 
277 boost::numeric::ublas::matrix<double>
278 ColourConversion::bradford () const
279 {
280  if (!_adjusted_white || fabs (_adjusted_white.get().x) < 1e-6 || fabs (_adjusted_white.get().y) < 1e-6) {
281  boost::numeric::ublas::matrix<double> B = boost::numeric::ublas::zero_matrix<double> (3, 3);
282  B(0, 0) = 1;
283  B(1, 1) = 1;
284  B(2, 2) = 1;
285  return B;
286  }
287 
288  /* See doc/design/colour.tex */
289 
290  boost::numeric::ublas::matrix<double> M (3, 3);
291  M(0, 0) = 0.8951;
292  M(0, 1) = 0.2664;
293  M(0, 2) = -0.1614;
294  M(1, 0) = -0.7502;
295  M(1, 1) = 1.7135;
296  M(1, 2) = 0.0367;
297  M(2, 0) = 0.0389;
298  M(2, 1) = -0.0685;
299  M(2, 2) = 1.0296;
300 
301  boost::numeric::ublas::matrix<double> Mi (3, 3);
302  Mi(0, 0) = 0.9869929055;
303  Mi(0, 1) = -0.1470542564;
304  Mi(0, 2) = 0.1599626517;
305  Mi(1, 0) = 0.4323052697;
306  Mi(1, 1) = 0.5183602715;
307  Mi(1, 2) = 0.0492912282;
308  Mi(2, 0) = -0.0085286646;
309  Mi(2, 1) = 0.0400428217;
310  Mi(2, 2) = 0.9684866958;
311 
312  boost::numeric::ublas::matrix<double> Gp (3, 1);
313  Gp(0, 0) = _white.x / _white.y;
314  Gp(1, 0) = 1;
315  Gp(2, 0) = (1 - _white.x - _white.y) / _white.y;
316 
317  boost::numeric::ublas::matrix<double> G = boost::numeric::ublas::prod (M, Gp);
318 
319  boost::numeric::ublas::matrix<double> Hp (3, 1);
320  Hp(0, 0) = _adjusted_white.get().x / _adjusted_white.get().y;
321  Hp(1, 0) = 1;
322  Hp(2, 0) = (1 - _adjusted_white.get().x - _adjusted_white.get().y) / _adjusted_white.get().y;
323 
324  boost::numeric::ublas::matrix<double> H = boost::numeric::ublas::prod (M, Hp);
325 
326  boost::numeric::ublas::matrix<double> C = boost::numeric::ublas::zero_matrix<double> (3, 3);
327  C(0, 0) = H(0, 0) / G(0, 0);
328  C(1, 1) = H(1, 0) / G(1, 0);
329  C(2, 2) = H(2, 0) / G(2, 0);
330 
331  boost::numeric::ublas::matrix<double> CM = boost::numeric::ublas::prod (C, M);
332  return boost::numeric::ublas::prod (Mi, CM);
333 }
A representation of a x,y,z chromaticity, where z = 1 - x - y.
Definition: chromaticity.h:54
bool about_equal(Chromaticity const &other, float epsilon) const
Definition: chromaticity.h:71
A representation of all the parameters involved the colourspace conversion of a YUV image to XYZ (via...
std::shared_ptr< const TransferFunction > _out
static ColourConversion const & s_gamut3_to_xyz()
boost::optional< Chromaticity > _adjusted_white
std::shared_ptr< const TransferFunction > _in
ColourConversion class.
DCP_ASSERT macro.
GammaTransferFunction class.
IdentityTransferFunction class.
ModifiedGammaTransferFunction class.
Namespace for everything in libdcp.
Definition: array_data.h:50
SGamut3TransferFunction class.