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