1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/gfx/color_space.h"
6
7 #include <iomanip>
8 #include <limits>
9 #include <map>
10 #include <sstream>
11
12 #include "base/atomic_sequence_num.h"
13 #include "base/lazy_instance.h"
14 #include "base/logging.h"
15 #include "base/synchronization/lock.h"
16 #include "third_party/skia/include/core/SkColorSpace.h"
17 #include "third_party/skia/include/core/SkData.h"
18 #include "third_party/skia/include/core/SkICC.h"
19 #include "ui/gfx/display_color_spaces.h"
20 #include "ui/gfx/icc_profile.h"
21 #include "ui/gfx/skia_color_space_util.h"
22
23 namespace gfx {
24
25 namespace {
26
IsAlmostZero(float value)27 static bool IsAlmostZero(float value) {
28 return std::abs(value) < std::numeric_limits<float>::epsilon();
29 }
30
FloatsEqualWithinTolerance(const float * a,const float * b,int n,float tol)31 static bool FloatsEqualWithinTolerance(const float* a,
32 const float* b,
33 int n,
34 float tol) {
35 for (int i = 0; i < n; ++i) {
36 if (std::abs(a[i] - b[i]) > tol) {
37 return false;
38 }
39 }
40 return true;
41 }
42
GetPQSkTransferFunction(float sdr_white_level)43 skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) {
44 // Note that SkColorSpace doesn't have the notion of an unspecified SDR white
45 // level.
46 if (sdr_white_level == 0.f)
47 sdr_white_level = ColorSpace::kDefaultSDRWhiteLevel;
48
49 // The generic PQ transfer function produces normalized luminance values i.e.
50 // the range 0-1 represents 0-10000 nits for the reference display, but we
51 // want to map 1.0 to |sdr_white_level| nits so we need to scale accordingly.
52 const double w = 10000. / sdr_white_level;
53 // Distribute scaling factor W by scaling A and B with X ^ (1/F):
54 // ((A + Bx^C) / (D + Ex^C))^F * W = ((A + Bx^C) / (D + Ex^C) * W^(1/F))^F
55 // See https://crbug.com/1058580#c32 for discussion.
56 skcms_TransferFunction fn = SkNamedTransferFn::kPQ;
57 const double ws = pow(w, 1. / fn.f);
58 fn.a = ws * fn.a;
59 fn.b = ws * fn.b;
60 return fn;
61 }
62
GetHLGSkTransferFunction(float sdr_white_level)63 skcms_TransferFunction GetHLGSkTransferFunction(float sdr_white_level) {
64 // Note that SkColorSpace doesn't have the notion of an unspecified SDR white
65 // level.
66 if (sdr_white_level == 0.f)
67 sdr_white_level = ColorSpace::kDefaultSDRWhiteLevel;
68
69 // The reference white level for HLG is 100 nits. We want to setup the
70 // returned transfer function such that output values are scaled by the white
71 // level; Skia uses the |f| transfer function parameter for this.
72 skcms_TransferFunction fn = SkNamedTransferFn::kHLG;
73 fn.f = ColorSpace::kDefaultSDRWhiteLevel / sdr_white_level - 1;
74 return fn;
75 }
76
GetSDRWhiteLevelFromPQSkTransferFunction(const skcms_TransferFunction & fn)77 float GetSDRWhiteLevelFromPQSkTransferFunction(
78 const skcms_TransferFunction& fn) {
79 DCHECK_EQ(fn.g, SkNamedTransferFn::kPQ.g);
80 const double ws_a = static_cast<double>(fn.a) / SkNamedTransferFn::kPQ.a;
81 const double w_a = pow(ws_a, fn.f);
82 const double sdr_white_level_a = 10000.0f / w_a;
83 return sdr_white_level_a;
84 }
85
GetSDRWhiteLevelFromHLGSkTransferFunction(const skcms_TransferFunction & fn)86 float GetSDRWhiteLevelFromHLGSkTransferFunction(
87 const skcms_TransferFunction& fn) {
88 DCHECK_EQ(fn.g, SkNamedTransferFn::kHLG.g);
89 if (fn.f == 0)
90 return ColorSpace::kDefaultSDRWhiteLevel;
91 return 1.0f / ((fn.f + 1) / ColorSpace::kDefaultSDRWhiteLevel);
92 }
93
PrimaryIdContainsSRGB(ColorSpace::PrimaryID id)94 bool PrimaryIdContainsSRGB(ColorSpace::PrimaryID id) {
95 DCHECK(id != ColorSpace::PrimaryID::INVALID &&
96 id != ColorSpace::PrimaryID::CUSTOM);
97
98 switch (id) {
99 case ColorSpace::PrimaryID::BT709:
100 case ColorSpace::PrimaryID::BT2020:
101 case ColorSpace::PrimaryID::SMPTEST428_1:
102 case ColorSpace::PrimaryID::SMPTEST431_2:
103 case ColorSpace::PrimaryID::SMPTEST432_1:
104 case ColorSpace::PrimaryID::XYZ_D50:
105 case ColorSpace::PrimaryID::ADOBE_RGB:
106 case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN:
107 return true;
108 default:
109 return false;
110 }
111 }
112
113 } // namespace
114
115 // static
116 constexpr float ColorSpace::kDefaultSDRWhiteLevel;
117
ColorSpace(PrimaryID primaries,TransferID transfer,MatrixID matrix,RangeID range,const skcms_Matrix3x3 * custom_primary_matrix,const skcms_TransferFunction * custom_transfer_fn)118 ColorSpace::ColorSpace(PrimaryID primaries,
119 TransferID transfer,
120 MatrixID matrix,
121 RangeID range,
122 const skcms_Matrix3x3* custom_primary_matrix,
123 const skcms_TransferFunction* custom_transfer_fn)
124 : primaries_(primaries),
125 transfer_(transfer),
126 matrix_(matrix),
127 range_(range) {
128 if (custom_primary_matrix) {
129 DCHECK_EQ(PrimaryID::CUSTOM, primaries_);
130 SetCustomPrimaries(*custom_primary_matrix);
131 }
132 if (custom_transfer_fn)
133 SetCustomTransferFunction(*custom_transfer_fn);
134 }
135
ColorSpace(const SkColorSpace & sk_color_space)136 ColorSpace::ColorSpace(const SkColorSpace& sk_color_space)
137 : ColorSpace(PrimaryID::INVALID,
138 TransferID::INVALID,
139 MatrixID::RGB,
140 RangeID::FULL) {
141 skcms_TransferFunction fn;
142 if (sk_color_space.isNumericalTransferFn(&fn)) {
143 transfer_ = TransferID::CUSTOM;
144 SetCustomTransferFunction(fn);
145 } else if (skcms_TransferFunction_isHLGish(&fn)) {
146 transfer_ = TransferID::ARIB_STD_B67;
147 transfer_params_[0] = GetSDRWhiteLevelFromHLGSkTransferFunction(fn);
148 } else if (skcms_TransferFunction_isPQish(&fn)) {
149 transfer_ = TransferID::SMPTEST2084;
150 transfer_params_[0] = GetSDRWhiteLevelFromPQSkTransferFunction(fn);
151 } else {
152 // Construct an invalid result: Unable to extract necessary parameters
153 return;
154 }
155
156 skcms_Matrix3x3 to_XYZD50;
157 if (!sk_color_space.toXYZD50(&to_XYZD50)) {
158 // Construct an invalid result: Unable to extract necessary parameters
159 return;
160 }
161 SetCustomPrimaries(to_XYZD50);
162 }
163
IsValid() const164 bool ColorSpace::IsValid() const {
165 return primaries_ != PrimaryID::INVALID && transfer_ != TransferID::INVALID &&
166 matrix_ != MatrixID::INVALID && range_ != RangeID::INVALID;
167 }
168
169 // static
CreateSCRGBLinear(float sdr_white_level)170 ColorSpace ColorSpace::CreateSCRGBLinear(float sdr_white_level) {
171 skcms_TransferFunction fn = {0};
172 fn.g = 1.0f;
173 fn.a = kDefaultScrgbLinearSdrWhiteLevel / sdr_white_level;
174 return ColorSpace(PrimaryID::BT709, TransferID::CUSTOM_HDR, MatrixID::RGB,
175 RangeID::FULL, nullptr, &fn);
176 }
177
178 // static
CreateHDR10(float sdr_white_level)179 ColorSpace ColorSpace::CreateHDR10(float sdr_white_level) {
180 ColorSpace result(PrimaryID::BT2020, TransferID::SMPTEST2084, MatrixID::RGB,
181 RangeID::FULL);
182 result.transfer_params_[0] = sdr_white_level;
183 return result;
184 }
185
186 // static
CreateHLG()187 ColorSpace ColorSpace::CreateHLG() {
188 return ColorSpace(PrimaryID::BT2020, TransferID::ARIB_STD_B67, MatrixID::RGB,
189 RangeID::FULL);
190 }
191
192 // static
CreatePiecewiseHDR(PrimaryID primaries,float sdr_joint,float hdr_level,const skcms_Matrix3x3 * custom_primary_matrix)193 ColorSpace ColorSpace::CreatePiecewiseHDR(
194 PrimaryID primaries,
195 float sdr_joint,
196 float hdr_level,
197 const skcms_Matrix3x3* custom_primary_matrix) {
198 // If |sdr_joint| is 1, then this is just sRGB (and so |hdr_level| must be 1).
199 // An |sdr_joint| higher than 1 breaks.
200 DCHECK_LE(sdr_joint, 1.f);
201 if (sdr_joint == 1.f)
202 DCHECK_EQ(hdr_level, 1.f);
203 // An |hdr_level| of 1 has no HDR. An |hdr_level| less than 1 breaks.
204 DCHECK_GE(hdr_level, 1.f);
205 ColorSpace result(primaries, TransferID::PIECEWISE_HDR, MatrixID::RGB,
206 RangeID::FULL, custom_primary_matrix, nullptr);
207 result.transfer_params_[0] = sdr_joint;
208 result.transfer_params_[1] = hdr_level;
209 return result;
210 }
211
212 // static
CreateCustom(const skcms_Matrix3x3 & to_XYZD50,const skcms_TransferFunction & fn)213 ColorSpace ColorSpace::CreateCustom(const skcms_Matrix3x3& to_XYZD50,
214 const skcms_TransferFunction& fn) {
215 ColorSpace result(ColorSpace::PrimaryID::CUSTOM,
216 ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB,
217 ColorSpace::RangeID::FULL, &to_XYZD50, &fn);
218 return result;
219 }
220
221 // static
CreateCustom(const skcms_Matrix3x3 & to_XYZD50,TransferID transfer)222 ColorSpace ColorSpace::CreateCustom(const skcms_Matrix3x3& to_XYZD50,
223 TransferID transfer) {
224 ColorSpace result(ColorSpace::PrimaryID::CUSTOM, transfer,
225 ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL,
226 &to_XYZD50, nullptr);
227 return result;
228 }
229
SetCustomPrimaries(const skcms_Matrix3x3 & to_XYZD50)230 void ColorSpace::SetCustomPrimaries(const skcms_Matrix3x3& to_XYZD50) {
231 const PrimaryID kIDsToCheck[] = {
232 PrimaryID::BT709,
233 PrimaryID::BT470M,
234 PrimaryID::BT470BG,
235 PrimaryID::SMPTE170M,
236 PrimaryID::SMPTE240M,
237 PrimaryID::FILM,
238 PrimaryID::BT2020,
239 PrimaryID::SMPTEST428_1,
240 PrimaryID::SMPTEST431_2,
241 PrimaryID::SMPTEST432_1,
242 PrimaryID::XYZ_D50,
243 PrimaryID::ADOBE_RGB,
244 PrimaryID::APPLE_GENERIC_RGB,
245 PrimaryID::WIDE_GAMUT_COLOR_SPIN,
246 };
247 for (PrimaryID id : kIDsToCheck) {
248 skcms_Matrix3x3 matrix;
249 GetPrimaryMatrix(id, &matrix);
250 if (FloatsEqualWithinTolerance(&to_XYZD50.vals[0][0], &matrix.vals[0][0], 9,
251 0.001f)) {
252 primaries_ = id;
253 return;
254 }
255 }
256
257 memcpy(custom_primary_matrix_, &to_XYZD50, 9 * sizeof(float));
258 primaries_ = PrimaryID::CUSTOM;
259 }
260
SetCustomTransferFunction(const skcms_TransferFunction & fn)261 void ColorSpace::SetCustomTransferFunction(const skcms_TransferFunction& fn) {
262 DCHECK(transfer_ == TransferID::CUSTOM ||
263 transfer_ == TransferID::CUSTOM_HDR);
264 // These are all TransferIDs that will return a transfer function from
265 // GetTransferFunction. When multiple ids map to the same function, this list
266 // prioritizes the most common name (eg IEC61966_2_1). This applies only to
267 // SDR transfer functions.
268 if (transfer_ == TransferID::CUSTOM) {
269 const TransferID kIDsToCheck[] = {
270 TransferID::IEC61966_2_1, TransferID::LINEAR,
271 TransferID::GAMMA18, TransferID::GAMMA22,
272 TransferID::GAMMA24, TransferID::GAMMA28,
273 TransferID::SMPTE240M, TransferID::BT709_APPLE,
274 TransferID::SMPTEST428_1,
275 };
276 for (TransferID id : kIDsToCheck) {
277 skcms_TransferFunction id_fn;
278 GetTransferFunction(id, &id_fn);
279 if (FloatsEqualWithinTolerance(&fn.g, &id_fn.g, 7, 0.001f)) {
280 transfer_ = id;
281 return;
282 }
283 }
284 }
285 transfer_params_[0] = fn.a;
286 transfer_params_[1] = fn.b;
287 transfer_params_[2] = fn.c;
288 transfer_params_[3] = fn.d;
289 transfer_params_[4] = fn.e;
290 transfer_params_[5] = fn.f;
291 transfer_params_[6] = fn.g;
292 }
293
294 // static
TransferParamCount(TransferID transfer)295 size_t ColorSpace::TransferParamCount(TransferID transfer) {
296 switch (transfer) {
297 case TransferID::CUSTOM:
298 return 7;
299 case TransferID::CUSTOM_HDR:
300 return 7;
301 case TransferID::PIECEWISE_HDR:
302 return 2;
303 case TransferID::SMPTEST2084:
304 case TransferID::ARIB_STD_B67:
305 return 1;
306 default:
307 return 0;
308 }
309 }
310
operator ==(const ColorSpace & other) const311 bool ColorSpace::operator==(const ColorSpace& other) const {
312 if (primaries_ != other.primaries_ || transfer_ != other.transfer_ ||
313 matrix_ != other.matrix_ || range_ != other.range_) {
314 return false;
315 }
316 if (primaries_ == PrimaryID::CUSTOM) {
317 if (memcmp(custom_primary_matrix_, other.custom_primary_matrix_,
318 sizeof(custom_primary_matrix_))) {
319 return false;
320 }
321 }
322 if (size_t param_count = TransferParamCount(transfer_)) {
323 if (memcmp(transfer_params_, other.transfer_params_,
324 param_count * sizeof(float))) {
325 return false;
326 }
327 }
328 return true;
329 }
330
IsWide() const331 bool ColorSpace::IsWide() const {
332 // These HDR transfer functions are always wide
333 if (transfer_ == TransferID::IEC61966_2_1_HDR ||
334 transfer_ == TransferID::LINEAR_HDR ||
335 transfer_ == TransferID::CUSTOM_HDR)
336 return true;
337
338 if (primaries_ == PrimaryID::BT2020 ||
339 primaries_ == PrimaryID::SMPTEST431_2 ||
340 primaries_ == PrimaryID::SMPTEST432_1 ||
341 primaries_ == PrimaryID::ADOBE_RGB ||
342 primaries_ == PrimaryID::WIDE_GAMUT_COLOR_SPIN ||
343 // TODO(cblume/ccameron): Compute if the custom primaries actually are
344 // wide. For now, assume so.
345 primaries_ == PrimaryID::CUSTOM)
346 return true;
347
348 return false;
349 }
350
IsHDR() const351 bool ColorSpace::IsHDR() const {
352 return transfer_ == TransferID::SMPTEST2084 ||
353 transfer_ == TransferID::ARIB_STD_B67 ||
354 transfer_ == TransferID::LINEAR_HDR ||
355 transfer_ == TransferID::IEC61966_2_1_HDR ||
356 transfer_ == TransferID::CUSTOM_HDR ||
357 transfer_ == TransferID::PIECEWISE_HDR;
358 }
359
FullRangeEncodedValues() const360 bool ColorSpace::FullRangeEncodedValues() const {
361 return transfer_ == TransferID::LINEAR_HDR ||
362 transfer_ == TransferID::IEC61966_2_1_HDR ||
363 transfer_ == TransferID::CUSTOM_HDR ||
364 transfer_ == TransferID::PIECEWISE_HDR ||
365 transfer_ == TransferID::BT1361_ECG ||
366 transfer_ == TransferID::IEC61966_2_4;
367 }
368
operator !=(const ColorSpace & other) const369 bool ColorSpace::operator!=(const ColorSpace& other) const {
370 return !(*this == other);
371 }
372
operator <(const ColorSpace & other) const373 bool ColorSpace::operator<(const ColorSpace& other) const {
374 if (primaries_ < other.primaries_)
375 return true;
376 if (primaries_ > other.primaries_)
377 return false;
378 if (transfer_ < other.transfer_)
379 return true;
380 if (transfer_ > other.transfer_)
381 return false;
382 if (matrix_ < other.matrix_)
383 return true;
384 if (matrix_ > other.matrix_)
385 return false;
386 if (range_ < other.range_)
387 return true;
388 if (range_ > other.range_)
389 return false;
390 if (primaries_ == PrimaryID::CUSTOM) {
391 int primary_result =
392 memcmp(custom_primary_matrix_, other.custom_primary_matrix_,
393 sizeof(custom_primary_matrix_));
394 if (primary_result < 0)
395 return true;
396 if (primary_result > 0)
397 return false;
398 }
399 if (size_t param_count = TransferParamCount(transfer_)) {
400 int transfer_result = memcmp(transfer_params_, other.transfer_params_,
401 param_count * sizeof(float));
402 if (transfer_result < 0)
403 return true;
404 if (transfer_result > 0)
405 return false;
406 }
407 return false;
408 }
409
GetHash() const410 size_t ColorSpace::GetHash() const {
411 size_t result = (static_cast<size_t>(primaries_) << 0) |
412 (static_cast<size_t>(transfer_) << 8) |
413 (static_cast<size_t>(matrix_) << 16) |
414 (static_cast<size_t>(range_) << 24);
415 if (primaries_ == PrimaryID::CUSTOM) {
416 const uint32_t* params =
417 reinterpret_cast<const uint32_t*>(custom_primary_matrix_);
418 result ^= params[0];
419 result ^= params[4];
420 result ^= params[8];
421 }
422 {
423 // Note that |transfer_params_| must be zero when they are unused.
424 const uint32_t* params =
425 reinterpret_cast<const uint32_t*>(transfer_params_);
426 result ^= params[3];
427 result ^= params[6];
428 }
429 return result;
430 }
431
432 #define PRINT_ENUM_CASE(TYPE, NAME) \
433 case TYPE::NAME: \
434 ss << #NAME; \
435 break;
436
ToString() const437 std::string ColorSpace::ToString() const {
438 std::stringstream ss;
439 ss << std::fixed << std::setprecision(4);
440 if (primaries_ != PrimaryID::CUSTOM)
441 ss << "{primaries:";
442 switch (primaries_) {
443 PRINT_ENUM_CASE(PrimaryID, INVALID)
444 PRINT_ENUM_CASE(PrimaryID, BT709)
445 PRINT_ENUM_CASE(PrimaryID, BT470M)
446 PRINT_ENUM_CASE(PrimaryID, BT470BG)
447 PRINT_ENUM_CASE(PrimaryID, SMPTE170M)
448 PRINT_ENUM_CASE(PrimaryID, SMPTE240M)
449 PRINT_ENUM_CASE(PrimaryID, FILM)
450 PRINT_ENUM_CASE(PrimaryID, BT2020)
451 PRINT_ENUM_CASE(PrimaryID, SMPTEST428_1)
452 PRINT_ENUM_CASE(PrimaryID, SMPTEST431_2)
453 PRINT_ENUM_CASE(PrimaryID, SMPTEST432_1)
454 PRINT_ENUM_CASE(PrimaryID, XYZ_D50)
455 PRINT_ENUM_CASE(PrimaryID, ADOBE_RGB)
456 PRINT_ENUM_CASE(PrimaryID, APPLE_GENERIC_RGB)
457 PRINT_ENUM_CASE(PrimaryID, WIDE_GAMUT_COLOR_SPIN)
458 case PrimaryID::CUSTOM:
459 // |custom_primary_matrix_| is in row-major order.
460 const float sum_R = custom_primary_matrix_[0] +
461 custom_primary_matrix_[3] + custom_primary_matrix_[6];
462 const float sum_G = custom_primary_matrix_[1] +
463 custom_primary_matrix_[4] + custom_primary_matrix_[7];
464 const float sum_B = custom_primary_matrix_[2] +
465 custom_primary_matrix_[5] + custom_primary_matrix_[8];
466 if (IsAlmostZero(sum_R) || IsAlmostZero(sum_G) || IsAlmostZero(sum_B))
467 break;
468
469 ss << "{primaries_d50_referred: [[" << (custom_primary_matrix_[0] / sum_R)
470 << ", " << (custom_primary_matrix_[3] / sum_R) << "], "
471 << " [" << (custom_primary_matrix_[1] / sum_G) << ", "
472 << (custom_primary_matrix_[4] / sum_G) << "], "
473 << " [" << (custom_primary_matrix_[2] / sum_B) << ", "
474 << (custom_primary_matrix_[5] / sum_B) << "]]";
475 break;
476 }
477 ss << ", transfer:";
478 switch (transfer_) {
479 PRINT_ENUM_CASE(TransferID, INVALID)
480 PRINT_ENUM_CASE(TransferID, BT709)
481 PRINT_ENUM_CASE(TransferID, BT709_APPLE)
482 PRINT_ENUM_CASE(TransferID, GAMMA18)
483 PRINT_ENUM_CASE(TransferID, GAMMA22)
484 PRINT_ENUM_CASE(TransferID, GAMMA24)
485 PRINT_ENUM_CASE(TransferID, GAMMA28)
486 PRINT_ENUM_CASE(TransferID, SMPTE170M)
487 PRINT_ENUM_CASE(TransferID, SMPTE240M)
488 PRINT_ENUM_CASE(TransferID, LINEAR)
489 PRINT_ENUM_CASE(TransferID, LOG)
490 PRINT_ENUM_CASE(TransferID, LOG_SQRT)
491 PRINT_ENUM_CASE(TransferID, IEC61966_2_4)
492 PRINT_ENUM_CASE(TransferID, BT1361_ECG)
493 PRINT_ENUM_CASE(TransferID, IEC61966_2_1)
494 PRINT_ENUM_CASE(TransferID, BT2020_10)
495 PRINT_ENUM_CASE(TransferID, BT2020_12)
496 PRINT_ENUM_CASE(TransferID, SMPTEST428_1)
497 PRINT_ENUM_CASE(TransferID, IEC61966_2_1_HDR)
498 PRINT_ENUM_CASE(TransferID, LINEAR_HDR)
499 case TransferID::ARIB_STD_B67:
500 ss << "HLG (SDR white point ";
501 if (transfer_params_[0] == 0.f)
502 ss << "default " << kDefaultSDRWhiteLevel;
503 else
504 ss << transfer_params_[0];
505 ss << " nits)";
506 break;
507 case TransferID::SMPTEST2084:
508 ss << "PQ (SDR white point ";
509 if (transfer_params_[0] == 0.f)
510 ss << "default " << kDefaultSDRWhiteLevel;
511 else
512 ss << transfer_params_[0];
513 ss << " nits)";
514 break;
515 case TransferID::CUSTOM: {
516 skcms_TransferFunction fn;
517 GetTransferFunction(&fn);
518 ss << fn.c << "*x + " << fn.f << " if x < " << fn.d << " else (" << fn.a
519 << "*x + " << fn.b << ")**" << fn.g << " + " << fn.e;
520 break;
521 }
522 case TransferID::CUSTOM_HDR: {
523 skcms_TransferFunction fn;
524 GetTransferFunction(&fn);
525 if (fn.g == 1.0f && fn.a > 0.0f && fn.b == 0.0f && fn.c == 0.0f &&
526 fn.d == 0.0f && fn.e == 0.0f && fn.f == 0.0f) {
527 ss << "LINEAR_HDR (slope " << fn.a << ", SDR white point "
528 << kDefaultScrgbLinearSdrWhiteLevel / fn.a << " nits)";
529 break;
530 }
531 ss << fn.c << "*x + " << fn.f << " if |x| < " << fn.d << " else sign(x)*("
532 << fn.a << "*|x| + " << fn.b << ")**" << fn.g << " + " << fn.e;
533 break;
534 }
535 case TransferID::PIECEWISE_HDR: {
536 skcms_TransferFunction fn;
537 GetTransferFunction(&fn);
538 ss << "sRGB to 1 at " << transfer_params_[0] << ", linear to "
539 << transfer_params_[1] << " at 1";
540 break;
541 }
542 }
543 ss << ", matrix:";
544 switch (matrix_) {
545 PRINT_ENUM_CASE(MatrixID, INVALID)
546 PRINT_ENUM_CASE(MatrixID, RGB)
547 PRINT_ENUM_CASE(MatrixID, BT709)
548 PRINT_ENUM_CASE(MatrixID, FCC)
549 PRINT_ENUM_CASE(MatrixID, BT470BG)
550 PRINT_ENUM_CASE(MatrixID, SMPTE170M)
551 PRINT_ENUM_CASE(MatrixID, SMPTE240M)
552 PRINT_ENUM_CASE(MatrixID, YCOCG)
553 PRINT_ENUM_CASE(MatrixID, BT2020_NCL)
554 PRINT_ENUM_CASE(MatrixID, BT2020_CL)
555 PRINT_ENUM_CASE(MatrixID, YDZDX)
556 PRINT_ENUM_CASE(MatrixID, GBR)
557 }
558 ss << ", range:";
559 switch (range_) {
560 PRINT_ENUM_CASE(RangeID, INVALID)
561 PRINT_ENUM_CASE(RangeID, LIMITED)
562 PRINT_ENUM_CASE(RangeID, FULL)
563 PRINT_ENUM_CASE(RangeID, DERIVED)
564 }
565 ss << "}";
566 return ss.str();
567 }
568
569 #undef PRINT_ENUM_CASE
570
GetAsFullRangeRGB() const571 ColorSpace ColorSpace::GetAsFullRangeRGB() const {
572 ColorSpace result(*this);
573 if (!IsValid())
574 return result;
575 result.matrix_ = MatrixID::RGB;
576 result.range_ = RangeID::FULL;
577 return result;
578 }
579
GetContentColorUsage() const580 ContentColorUsage ColorSpace::GetContentColorUsage() const {
581 if (IsHDR())
582 return ContentColorUsage::kHDR;
583 if (IsWide())
584 return ContentColorUsage::kWideColorGamut;
585 return ContentColorUsage::kSRGB;
586 }
587
GetAsRGB() const588 ColorSpace ColorSpace::GetAsRGB() const {
589 ColorSpace result(*this);
590 if (IsValid())
591 result.matrix_ = MatrixID::RGB;
592 return result;
593 }
594
GetScaledColorSpace(float factor) const595 ColorSpace ColorSpace::GetScaledColorSpace(float factor) const {
596 ColorSpace result(*this);
597 skcms_Matrix3x3 to_XYZD50;
598 GetPrimaryMatrix(&to_XYZD50);
599 for (int row = 0; row < 3; ++row) {
600 for (int col = 0; col < 3; ++col) {
601 to_XYZD50.vals[row][col] *= factor;
602 }
603 }
604 result.SetCustomPrimaries(to_XYZD50);
605 return result;
606 }
607
IsSuitableForBlending() const608 bool ColorSpace::IsSuitableForBlending() const {
609 switch (transfer_) {
610 case TransferID::SMPTEST2084:
611 // PQ is not an acceptable space to do blending in -- blending 0 and 1
612 // evenly will get a result of sRGB 0.259 (instead of 0.5).
613 return false;
614 case TransferID::ARIB_STD_B67:
615 case TransferID::LINEAR_HDR:
616 // If the color space is nearly-linear, then it is not suitable for
617 // blending -- blending 0 and 1 evenly will get a result of sRGB 0.735
618 // (instead of 0.5).
619 return false;
620 case TransferID::CUSTOM_HDR: {
621 // A gamma close enough to linear is treated as linear.
622 skcms_TransferFunction fn;
623 if (GetTransferFunction(&fn)) {
624 constexpr float kMinGamma = 1.25;
625 if (fn.g < kMinGamma)
626 return false;
627 }
628 break;
629 }
630 default:
631 break;
632 }
633 return true;
634 }
635
GetWithMatrixAndRange(MatrixID matrix,RangeID range) const636 ColorSpace ColorSpace::GetWithMatrixAndRange(MatrixID matrix,
637 RangeID range) const {
638 ColorSpace result(*this);
639 if (!IsValid())
640 return result;
641
642 result.matrix_ = matrix;
643 result.range_ = range;
644 return result;
645 }
646
GetWithSDRWhiteLevel(float sdr_white_level) const647 ColorSpace ColorSpace::GetWithSDRWhiteLevel(float sdr_white_level) const {
648 ColorSpace result = *this;
649 if (transfer_ == TransferID::SMPTEST2084 ||
650 transfer_ == TransferID::ARIB_STD_B67) {
651 result.transfer_params_[0] = sdr_white_level;
652 } else if (transfer_ == TransferID::LINEAR_HDR) {
653 result.transfer_ = TransferID::CUSTOM_HDR;
654 skcms_TransferFunction fn = {0};
655 fn.g = 1.f;
656 fn.a = kDefaultScrgbLinearSdrWhiteLevel / sdr_white_level;
657 result.SetCustomTransferFunction(fn);
658 }
659 return result;
660 }
661
ToSkColorSpace() const662 sk_sp<SkColorSpace> ColorSpace::ToSkColorSpace() const {
663 // Unspecified color spaces correspond to the null SkColorSpace.
664 if (!IsValid())
665 return nullptr;
666
667 // Handle only full-range RGB spaces.
668 if (matrix_ != MatrixID::RGB) {
669 DLOG(ERROR) << "Not creating non-RGB SkColorSpace";
670 return nullptr;
671 }
672 if (range_ != RangeID::FULL) {
673 DLOG(ERROR) << "Not creating non-full-range SkColorSpace";
674 return nullptr;
675 }
676
677 // Use the named SRGB and linear-SRGB instead of the generic constructors.
678 if (primaries_ == PrimaryID::BT709) {
679 if (transfer_ == TransferID::IEC61966_2_1)
680 return SkColorSpace::MakeSRGB();
681 if (transfer_ == TransferID::LINEAR || transfer_ == TransferID::LINEAR_HDR)
682 return SkColorSpace::MakeSRGBLinear();
683 }
684
685 skcms_TransferFunction transfer_fn = SkNamedTransferFn::kSRGB;
686 switch (transfer_) {
687 case TransferID::IEC61966_2_1:
688 break;
689 case TransferID::LINEAR:
690 case TransferID::LINEAR_HDR:
691 transfer_fn = SkNamedTransferFn::kLinear;
692 break;
693 case TransferID::ARIB_STD_B67:
694 transfer_fn = GetHLGSkTransferFunction(transfer_params_[0]);
695 break;
696 case TransferID::SMPTEST2084:
697 transfer_fn = GetPQSkTransferFunction(transfer_params_[0]);
698 break;
699 default:
700 if (!GetTransferFunction(&transfer_fn)) {
701 DLOG(ERROR) << "Failed to get transfer function for SkColorSpace";
702 return nullptr;
703 }
704 break;
705 }
706 skcms_Matrix3x3 gamut = SkNamedGamut::kSRGB;
707 switch (primaries_) {
708 case PrimaryID::BT709:
709 break;
710 case PrimaryID::ADOBE_RGB:
711 gamut = SkNamedGamut::kAdobeRGB;
712 break;
713 case PrimaryID::SMPTEST432_1:
714 gamut = SkNamedGamut::kDisplayP3;
715 break;
716 case PrimaryID::BT2020:
717 gamut = SkNamedGamut::kRec2020;
718 break;
719 default:
720 GetPrimaryMatrix(&gamut);
721 break;
722 }
723
724 sk_sp<SkColorSpace> sk_color_space =
725 SkColorSpace::MakeRGB(transfer_fn, gamut);
726 if (!sk_color_space)
727 DLOG(ERROR) << "SkColorSpace::MakeRGB failed.";
728
729 return sk_color_space;
730 }
731
AsGLColorSpace() const732 const struct _GLcolorSpace* ColorSpace::AsGLColorSpace() const {
733 return reinterpret_cast<const struct _GLcolorSpace*>(this);
734 }
735
GetPrimaryID() const736 ColorSpace::PrimaryID ColorSpace::GetPrimaryID() const {
737 return primaries_;
738 }
739
GetTransferID() const740 ColorSpace::TransferID ColorSpace::GetTransferID() const {
741 return transfer_;
742 }
743
GetMatrixID() const744 ColorSpace::MatrixID ColorSpace::GetMatrixID() const {
745 return matrix_;
746 }
747
GetRangeID() const748 ColorSpace::RangeID ColorSpace::GetRangeID() const {
749 return range_;
750 }
751
HasExtendedSkTransferFn() const752 bool ColorSpace::HasExtendedSkTransferFn() const {
753 return transfer_ == TransferID::LINEAR_HDR ||
754 transfer_ == TransferID::IEC61966_2_1_HDR;
755 }
756
Contains(const ColorSpace & other) const757 bool ColorSpace::Contains(const ColorSpace& other) const {
758 if (primaries_ == PrimaryID::INVALID ||
759 other.primaries_ == PrimaryID::INVALID)
760 return false;
761
762 // Contains() is commonly used to check if a color space contains sRGB. The
763 // computation can be bypassed for known primary IDs.
764 if (primaries_ != PrimaryID::CUSTOM && other.primaries_ == PrimaryID::BT709)
765 return PrimaryIdContainsSRGB(primaries_);
766
767 // |matrix| is the primary transform matrix from |other| to this color space.
768 skcms_Matrix3x3 other_to_xyz;
769 skcms_Matrix3x3 this_to_xyz;
770 skcms_Matrix3x3 xyz_to_this;
771 other.GetPrimaryMatrix(&other_to_xyz);
772 GetPrimaryMatrix(&this_to_xyz);
773 skcms_Matrix3x3_invert(&this_to_xyz, &xyz_to_this);
774 skcms_Matrix3x3 matrix = skcms_Matrix3x3_concat(&xyz_to_this, &other_to_xyz);
775
776 // Return true iff each primary is in the range [0, 1] after transforming.
777 // Transforming a primary vector by |matrix| always results in a column of
778 // |matrix|. So the multiplication can be skipped, and we can just check if
779 // each value in the matrix is in the range [0, 1].
780 constexpr float epsilon = 0.001f;
781 for (int r = 0; r < 3; r++) {
782 for (int c = 0; c < 3; c++) {
783 if (matrix.vals[r][c] < -epsilon || matrix.vals[r][c] > 1 + epsilon)
784 return false;
785 }
786 }
787 return true;
788 }
789
790 // static
GetPrimaryMatrix(PrimaryID primary_id,skcms_Matrix3x3 * to_XYZD50)791 void ColorSpace::GetPrimaryMatrix(PrimaryID primary_id,
792 skcms_Matrix3x3* to_XYZD50) {
793 SkColorSpacePrimaries primaries = {0};
794 switch (primary_id) {
795 case ColorSpace::PrimaryID::CUSTOM:
796 case ColorSpace::PrimaryID::INVALID:
797 *to_XYZD50 = SkNamedGamut::kXYZ; // Identity
798 return;
799
800 case ColorSpace::PrimaryID::BT709:
801 // BT709 is our default case. Put it after the switch just
802 // in case we somehow get an id which is not listed in the switch.
803 // (We don't want to use "default", because we want the compiler
804 // to tell us if we forgot some enum values.)
805 primaries.fRX = 0.640f;
806 primaries.fRY = 0.330f;
807 primaries.fGX = 0.300f;
808 primaries.fGY = 0.600f;
809 primaries.fBX = 0.150f;
810 primaries.fBY = 0.060f;
811 primaries.fWX = 0.3127f;
812 primaries.fWY = 0.3290f;
813 break;
814
815 case ColorSpace::PrimaryID::BT470M:
816 primaries.fRX = 0.67f;
817 primaries.fRY = 0.33f;
818 primaries.fGX = 0.21f;
819 primaries.fGY = 0.71f;
820 primaries.fBX = 0.14f;
821 primaries.fBY = 0.08f;
822 primaries.fWX = 0.31f;
823 primaries.fWY = 0.316f;
824 break;
825
826 case ColorSpace::PrimaryID::BT470BG:
827 primaries.fRX = 0.64f;
828 primaries.fRY = 0.33f;
829 primaries.fGX = 0.29f;
830 primaries.fGY = 0.60f;
831 primaries.fBX = 0.15f;
832 primaries.fBY = 0.06f;
833 primaries.fWX = 0.3127f;
834 primaries.fWY = 0.3290f;
835 break;
836
837 case ColorSpace::PrimaryID::SMPTE170M:
838 case ColorSpace::PrimaryID::SMPTE240M:
839 primaries.fRX = 0.630f;
840 primaries.fRY = 0.340f;
841 primaries.fGX = 0.310f;
842 primaries.fGY = 0.595f;
843 primaries.fBX = 0.155f;
844 primaries.fBY = 0.070f;
845 primaries.fWX = 0.3127f;
846 primaries.fWY = 0.3290f;
847 break;
848
849 case ColorSpace::PrimaryID::APPLE_GENERIC_RGB:
850 primaries.fRX = 0.63002f;
851 primaries.fRY = 0.34000f;
852 primaries.fGX = 0.29505f;
853 primaries.fGY = 0.60498f;
854 primaries.fBX = 0.15501f;
855 primaries.fBY = 0.07701f;
856 primaries.fWX = 0.3127f;
857 primaries.fWY = 0.3290f;
858 break;
859
860 case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN:
861 primaries.fRX = 0.01f;
862 primaries.fRY = 0.98f;
863 primaries.fGX = 0.01f;
864 primaries.fGY = 0.01f;
865 primaries.fBX = 0.98f;
866 primaries.fBY = 0.01f;
867 primaries.fWX = 0.3127f;
868 primaries.fWY = 0.3290f;
869 break;
870
871 case ColorSpace::PrimaryID::FILM:
872 primaries.fRX = 0.681f;
873 primaries.fRY = 0.319f;
874 primaries.fGX = 0.243f;
875 primaries.fGY = 0.692f;
876 primaries.fBX = 0.145f;
877 primaries.fBY = 0.049f;
878 primaries.fWX = 0.310f;
879 primaries.fWY = 0.136f;
880 break;
881
882 case ColorSpace::PrimaryID::BT2020:
883 primaries.fRX = 0.708f;
884 primaries.fRY = 0.292f;
885 primaries.fGX = 0.170f;
886 primaries.fGY = 0.797f;
887 primaries.fBX = 0.131f;
888 primaries.fBY = 0.046f;
889 primaries.fWX = 0.3127f;
890 primaries.fWY = 0.3290f;
891 break;
892
893 case ColorSpace::PrimaryID::SMPTEST428_1:
894 primaries.fRX = 1.0f;
895 primaries.fRY = 0.0f;
896 primaries.fGX = 0.0f;
897 primaries.fGY = 1.0f;
898 primaries.fBX = 0.0f;
899 primaries.fBY = 0.0f;
900 primaries.fWX = 1.0f / 3.0f;
901 primaries.fWY = 1.0f / 3.0f;
902 break;
903
904 case ColorSpace::PrimaryID::SMPTEST431_2:
905 primaries.fRX = 0.680f;
906 primaries.fRY = 0.320f;
907 primaries.fGX = 0.265f;
908 primaries.fGY = 0.690f;
909 primaries.fBX = 0.150f;
910 primaries.fBY = 0.060f;
911 primaries.fWX = 0.314f;
912 primaries.fWY = 0.351f;
913 break;
914
915 case ColorSpace::PrimaryID::SMPTEST432_1:
916 primaries.fRX = 0.680f;
917 primaries.fRY = 0.320f;
918 primaries.fGX = 0.265f;
919 primaries.fGY = 0.690f;
920 primaries.fBX = 0.150f;
921 primaries.fBY = 0.060f;
922 primaries.fWX = 0.3127f;
923 primaries.fWY = 0.3290f;
924 break;
925
926 case ColorSpace::PrimaryID::XYZ_D50:
927 primaries.fRX = 1.0f;
928 primaries.fRY = 0.0f;
929 primaries.fGX = 0.0f;
930 primaries.fGY = 1.0f;
931 primaries.fBX = 0.0f;
932 primaries.fBY = 0.0f;
933 primaries.fWX = 0.34567f;
934 primaries.fWY = 0.35850f;
935 break;
936
937 case ColorSpace::PrimaryID::ADOBE_RGB:
938 primaries.fRX = 0.6400f;
939 primaries.fRY = 0.3300f;
940 primaries.fGX = 0.2100f;
941 primaries.fGY = 0.7100f;
942 primaries.fBX = 0.1500f;
943 primaries.fBY = 0.0600f;
944 primaries.fWX = 0.3127f;
945 primaries.fWY = 0.3290f;
946 break;
947 }
948 primaries.toXYZD50(to_XYZD50);
949 }
950
GetPrimaryMatrix(skcms_Matrix3x3 * to_XYZD50) const951 void ColorSpace::GetPrimaryMatrix(skcms_Matrix3x3* to_XYZD50) const {
952 if (primaries_ == PrimaryID::CUSTOM) {
953 memcpy(to_XYZD50, custom_primary_matrix_, 9 * sizeof(float));
954 } else {
955 GetPrimaryMatrix(primaries_, to_XYZD50);
956 }
957 }
958
GetPrimaryMatrix(SkMatrix44 * to_XYZD50) const959 void ColorSpace::GetPrimaryMatrix(SkMatrix44* to_XYZD50) const {
960 skcms_Matrix3x3 toXYZ_3x3;
961 GetPrimaryMatrix(&toXYZ_3x3);
962 to_XYZD50->set3x3RowMajorf(&toXYZ_3x3.vals[0][0]);
963 }
964
965 // static
GetTransferFunction(TransferID transfer,skcms_TransferFunction * fn)966 bool ColorSpace::GetTransferFunction(TransferID transfer,
967 skcms_TransferFunction* fn) {
968 // Default to F(x) = pow(x, 1)
969 fn->a = 1;
970 fn->b = 0;
971 fn->c = 0;
972 fn->d = 0;
973 fn->e = 0;
974 fn->f = 0;
975 fn->g = 1;
976
977 switch (transfer) {
978 case ColorSpace::TransferID::LINEAR:
979 case ColorSpace::TransferID::LINEAR_HDR:
980 return true;
981 case ColorSpace::TransferID::GAMMA18:
982 fn->g = 1.801f;
983 return true;
984 case ColorSpace::TransferID::GAMMA22:
985 fn->g = 2.2f;
986 return true;
987 case ColorSpace::TransferID::GAMMA24:
988 fn->g = 2.4f;
989 return true;
990 case ColorSpace::TransferID::GAMMA28:
991 fn->g = 2.8f;
992 return true;
993 case ColorSpace::TransferID::SMPTE240M:
994 fn->a = 0.899626676224f;
995 fn->b = 0.100373323776f;
996 fn->c = 0.250000000000f;
997 fn->d = 0.091286342118f;
998 fn->g = 2.222222222222f;
999 return true;
1000 case ColorSpace::TransferID::BT709:
1001 case ColorSpace::TransferID::SMPTE170M:
1002 case ColorSpace::TransferID::BT2020_10:
1003 case ColorSpace::TransferID::BT2020_12:
1004 // With respect to rendering BT709
1005 // * SMPTE 1886 suggests that we should be using gamma 2.4.
1006 // * Most displays actually use a gamma of 2.2, and most media playing
1007 // software uses the sRGB transfer function.
1008 // * User studies shows that users don't really care.
1009 // * Apple's CoreVideo uses gamma=1.961.
1010 // Bearing all of that in mind, use the same transfer function as sRGB,
1011 // which will allow more optimization, and will more closely match other
1012 // media players.
1013 case ColorSpace::TransferID::IEC61966_2_1:
1014 case ColorSpace::TransferID::IEC61966_2_1_HDR:
1015 fn->a = 0.947867345704f;
1016 fn->b = 0.052132654296f;
1017 fn->c = 0.077399380805f;
1018 fn->d = 0.040449937172f;
1019 fn->g = 2.400000000000f;
1020 return true;
1021 case ColorSpace::TransferID::BT709_APPLE:
1022 fn->g = 1.961000000000f;
1023 return true;
1024 case ColorSpace::TransferID::SMPTEST428_1:
1025 fn->a = 1.034080527699f; // (52.37 / 48.0) ^ (1.0 / 2.6) per ITU-T H.273.
1026 fn->g = 2.600000000000f;
1027 return true;
1028 case ColorSpace::TransferID::IEC61966_2_4:
1029 // This could potentially be represented the same as IEC61966_2_1, but
1030 // it handles negative values differently.
1031 break;
1032 case ColorSpace::TransferID::ARIB_STD_B67:
1033 case ColorSpace::TransferID::BT1361_ECG:
1034 case ColorSpace::TransferID::LOG:
1035 case ColorSpace::TransferID::LOG_SQRT:
1036 case ColorSpace::TransferID::SMPTEST2084:
1037 case ColorSpace::TransferID::CUSTOM:
1038 case ColorSpace::TransferID::CUSTOM_HDR:
1039 case ColorSpace::TransferID::PIECEWISE_HDR:
1040 case ColorSpace::TransferID::INVALID:
1041 break;
1042 }
1043
1044 return false;
1045 }
1046
GetTransferFunction(skcms_TransferFunction * fn) const1047 bool ColorSpace::GetTransferFunction(skcms_TransferFunction* fn) const {
1048 if (transfer_ == TransferID::CUSTOM || transfer_ == TransferID::CUSTOM_HDR) {
1049 fn->a = transfer_params_[0];
1050 fn->b = transfer_params_[1];
1051 fn->c = transfer_params_[2];
1052 fn->d = transfer_params_[3];
1053 fn->e = transfer_params_[4];
1054 fn->f = transfer_params_[5];
1055 fn->g = transfer_params_[6];
1056 return true;
1057 } else {
1058 return GetTransferFunction(transfer_, fn);
1059 }
1060 }
1061
GetInverseTransferFunction(skcms_TransferFunction * fn) const1062 bool ColorSpace::GetInverseTransferFunction(skcms_TransferFunction* fn) const {
1063 if (!GetTransferFunction(fn))
1064 return false;
1065 *fn = SkTransferFnInverse(*fn);
1066 return true;
1067 }
1068
GetSDRWhiteLevel(float * sdr_white_level) const1069 bool ColorSpace::GetSDRWhiteLevel(float* sdr_white_level) const {
1070 if (transfer_ != TransferID::SMPTEST2084 &&
1071 transfer_ != TransferID::ARIB_STD_B67) {
1072 return false;
1073 }
1074 if (transfer_params_[0] == 0.0f)
1075 *sdr_white_level = kDefaultSDRWhiteLevel;
1076 else
1077 *sdr_white_level = transfer_params_[0];
1078 return true;
1079 }
1080
GetPiecewiseHDRParams(float * sdr_joint,float * hdr_level) const1081 bool ColorSpace::GetPiecewiseHDRParams(float* sdr_joint,
1082 float* hdr_level) const {
1083 if (transfer_ != TransferID::PIECEWISE_HDR)
1084 return false;
1085 *sdr_joint = transfer_params_[0];
1086 *hdr_level = transfer_params_[1];
1087 return true;
1088 }
1089
GetTransferMatrix(SkMatrix44 * matrix) const1090 void ColorSpace::GetTransferMatrix(SkMatrix44* matrix) const {
1091 float Kr = 0;
1092 float Kb = 0;
1093 switch (matrix_) {
1094 case ColorSpace::MatrixID::RGB:
1095 case ColorSpace::MatrixID::INVALID:
1096 matrix->setIdentity();
1097 return;
1098
1099 case ColorSpace::MatrixID::BT709:
1100 Kr = 0.2126f;
1101 Kb = 0.0722f;
1102 break;
1103
1104 case ColorSpace::MatrixID::FCC:
1105 Kr = 0.30f;
1106 Kb = 0.11f;
1107 break;
1108
1109 case ColorSpace::MatrixID::BT470BG:
1110 case ColorSpace::MatrixID::SMPTE170M:
1111 Kr = 0.299f;
1112 Kb = 0.114f;
1113 break;
1114
1115 case ColorSpace::MatrixID::SMPTE240M:
1116 Kr = 0.212f;
1117 Kb = 0.087f;
1118 break;
1119
1120 case ColorSpace::MatrixID::YCOCG: {
1121 float data[16] = {
1122 0.25f, 0.5f, 0.25f, 0.0f, // Y
1123 -0.25f, 0.5f, -0.25f, 0.5f, // Cg
1124 0.5f, 0.0f, -0.5f, 0.5f, // Co
1125 0.0f, 0.0f, 0.0f, 1.0f
1126 };
1127 matrix->setRowMajorf(data);
1128 return;
1129 }
1130
1131 // BT2020_CL is a special case.
1132 // Basically we return a matrix that transforms RYB values
1133 // to YUV values. (Note that the green component have been replaced
1134 // with the luminance.)
1135 case ColorSpace::MatrixID::BT2020_CL: {
1136 Kr = 0.2627f;
1137 Kb = 0.0593f;
1138 float data[16] = {1.0f, 0.0f, 0.0f, 0.0f, // R
1139 Kr, 1.0f - Kr - Kb, Kb, 0.0f, // Y
1140 0.0f, 0.0f, 1.0f, 0.0f, // B
1141 0.0f, 0.0f, 0.0f, 1.0f};
1142 matrix->setRowMajorf(data);
1143 return;
1144 }
1145
1146 case ColorSpace::MatrixID::BT2020_NCL:
1147 Kr = 0.2627f;
1148 Kb = 0.0593f;
1149 break;
1150
1151 case ColorSpace::MatrixID::YDZDX: {
1152 float data[16] = {
1153 0.0f, 1.0f, 0.0f, 0.0f, // Y
1154 0.0f, -0.5f, 0.986566f / 2.0f, 0.5f, // DX or DZ
1155 0.5f, -0.991902f / 2.0f, 0.0f, 0.5f, // DZ or DX
1156 0.0f, 0.0f, 0.0f, 1.0f,
1157 };
1158 matrix->setRowMajorf(data);
1159 return;
1160 }
1161 case ColorSpace::MatrixID::GBR: {
1162 float data[16] = {0.0f, 1.0f, 0.0f, 0.0f, // G
1163 0.0f, 0.0f, 1.0f, 0.0f, // B
1164 1.0f, 0.0f, 0.0f, 0.0f, // R
1165 0.0f, 0.0f, 0.0f, 1.0f};
1166 matrix->setRowMajorf(data);
1167 return;
1168 }
1169 }
1170 float Kg = 1.0f - Kr - Kb;
1171 float u_m = 0.5f / (1.0f - Kb);
1172 float v_m = 0.5f / (1.0f - Kr);
1173 float data[16] = {
1174 Kr, Kg, Kb, 0.0f, // Y
1175 u_m * -Kr, u_m * -Kg, u_m * (1.0f - Kb), 0.5f, // U
1176 v_m * (1.0f - Kr), v_m * -Kg, v_m * -Kb, 0.5f, // V
1177 0.0f, 0.0f, 0.0f, 1.0f,
1178 };
1179 matrix->setRowMajorf(data);
1180 }
1181
GetRangeAdjustMatrix(int bit_depth,SkMatrix44 * matrix) const1182 void ColorSpace::GetRangeAdjustMatrix(int bit_depth, SkMatrix44* matrix) const {
1183 DCHECK_GE(bit_depth, 8);
1184 switch (range_) {
1185 case RangeID::FULL:
1186 case RangeID::INVALID:
1187 matrix->setIdentity();
1188 return;
1189
1190 case RangeID::DERIVED:
1191 case RangeID::LIMITED:
1192 break;
1193 }
1194
1195 // See ITU-T H.273 (2016), Section 8.3. The following is derived from
1196 // Equations 20-31.
1197 const int shift = bit_depth - 8;
1198 const float a_y = 219 << shift;
1199 const float c = (1 << bit_depth) - 1;
1200 const float scale_y = c / a_y;
1201 switch (matrix_) {
1202 case MatrixID::RGB:
1203 case MatrixID::GBR:
1204 case MatrixID::INVALID:
1205 case MatrixID::YCOCG: {
1206 matrix->setScale(scale_y, scale_y, scale_y);
1207 matrix->postTranslate(-16.0f / 219.0f, -16.0f / 219.0f, -16.0f / 219.0f);
1208 break;
1209 }
1210
1211 case MatrixID::BT709:
1212 case MatrixID::FCC:
1213 case MatrixID::BT470BG:
1214 case MatrixID::SMPTE170M:
1215 case MatrixID::SMPTE240M:
1216 case MatrixID::BT2020_NCL:
1217 case MatrixID::BT2020_CL:
1218 case MatrixID::YDZDX: {
1219 const float a_uv = 224 << shift;
1220 const float scale_uv = c / a_uv;
1221 const float translate_uv = (a_uv - c) / (2.0f * a_uv);
1222 matrix->setScale(scale_y, scale_uv, scale_uv);
1223 matrix->postTranslate(-16.0f / 219.0f, translate_uv, translate_uv);
1224 break;
1225 }
1226 }
1227 }
1228
ToSkYUVColorSpace(int bit_depth,SkYUVColorSpace * out) const1229 bool ColorSpace::ToSkYUVColorSpace(int bit_depth, SkYUVColorSpace* out) const {
1230 switch (matrix_) {
1231 case MatrixID::BT709:
1232 *out = range_ == RangeID::FULL ? kRec709_Full_SkYUVColorSpace
1233 : kRec709_Limited_SkYUVColorSpace;
1234 return true;
1235
1236 case MatrixID::BT470BG:
1237 case MatrixID::SMPTE170M:
1238 *out = range_ == RangeID::FULL ? kJPEG_SkYUVColorSpace
1239 : kRec601_Limited_SkYUVColorSpace;
1240 return true;
1241
1242 case MatrixID::BT2020_NCL:
1243 if (bit_depth == 8) {
1244 *out = range_ == RangeID::FULL ? kBT2020_8bit_Full_SkYUVColorSpace
1245 : kBT2020_8bit_Limited_SkYUVColorSpace;
1246 return true;
1247 }
1248 if (bit_depth == 10) {
1249 *out = range_ == RangeID::FULL ? kBT2020_10bit_Full_SkYUVColorSpace
1250 : kBT2020_10bit_Limited_SkYUVColorSpace;
1251 return true;
1252 }
1253 if (bit_depth == 12) {
1254 *out = range_ == RangeID::FULL ? kBT2020_12bit_Full_SkYUVColorSpace
1255 : kBT2020_12bit_Limited_SkYUVColorSpace;
1256 return true;
1257 }
1258 return false;
1259
1260 default:
1261 break;
1262 }
1263 return false;
1264 }
1265
operator <<(std::ostream & out,const ColorSpace & color_space)1266 std::ostream& operator<<(std::ostream& out, const ColorSpace& color_space) {
1267 return out << color_space.ToString();
1268 }
1269
1270 } // namespace gfx
1271