| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/gfx/color_space.h" |
| |
| #include <iomanip> |
| #include <limits> |
| #include <map> |
| #include <sstream> |
| |
| #include "base/atomic_sequence_num.h" |
| #include "base/compiler_specific.h" |
| #include "base/debug/crash_logging.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "base/synchronization/lock.h" |
| #include "skia/ext/skcolorspace_primaries.h" |
| #include "skia/ext/skcolorspace_trfn.h" |
| #include "skia/ext/skia_utils_base.h" |
| #include "third_party/skia/include/core/SkColorSpace.h" |
| #include "third_party/skia/include/core/SkData.h" |
| #include "third_party/skia/include/core/SkImageInfo.h" |
| #include "third_party/skia/include/core/SkM44.h" |
| #include "third_party/skia/modules/skcms/skcms.h" |
| #include "ui/gfx/display_color_spaces.h" |
| #include "ui/gfx/icc_profile.h" |
| #include "ui/gfx/skia_color_space_util.h" |
| |
| namespace gfx { |
| |
| namespace { |
| |
| static bool FloatsEqualWithinTolerance(const float* a, |
| const float* b, |
| int n, |
| float tol) { |
| for (int i = 0; i < n; ++i) { |
| if (std::abs(UNSAFE_TODO(a[i]) - UNSAFE_TODO(b[i])) > tol) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool PrimaryIdContainsSRGB(ColorSpace::PrimaryID id) { |
| DCHECK(id != ColorSpace::PrimaryID::INVALID && |
| id != ColorSpace::PrimaryID::CUSTOM); |
| |
| switch (id) { |
| case ColorSpace::PrimaryID::BT709: |
| case ColorSpace::PrimaryID::BT2020: |
| case ColorSpace::PrimaryID::SMPTEST428_1: |
| case ColorSpace::PrimaryID::SMPTEST431_2: |
| case ColorSpace::PrimaryID::P3: |
| case ColorSpace::PrimaryID::XYZ_D50: |
| case ColorSpace::PrimaryID::ADOBE_RGB: |
| case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| float GetSDRWhiteLevelFromPQSkTransferFunction( |
| const skcms_TransferFunction& fn) { |
| DCHECK_EQ(fn.g, SkNamedTransferFn::kPQ.g); |
| const double ws_a = static_cast<double>(fn.a) / SkNamedTransferFn::kPQ.a; |
| const double w_a = pow(ws_a, fn.f); |
| const double sdr_white_level_a = 10000.0f / w_a; |
| return sdr_white_level_a; |
| } |
| |
| } // namespace |
| |
| // static |
| constexpr float ColorSpace::kDefaultSDRWhiteLevel; |
| constexpr float kDefaultPeakWhite = 1000.f; |
| constexpr float kDefaultSystemGamma = 1.2f; |
| |
| ColorSpace::ColorSpace(PrimaryID primaries, |
| TransferID transfer, |
| MatrixID matrix, |
| RangeID range, |
| const skcms_Matrix3x3* custom_primary_matrix, |
| const skcms_TransferFunction* custom_transfer_fn) |
| : primaries_(primaries), |
| transfer_(transfer), |
| matrix_(matrix), |
| range_(range) { |
| if (custom_primary_matrix) { |
| DCHECK_EQ(PrimaryID::CUSTOM, primaries_); |
| SetCustomPrimaries(*custom_primary_matrix); |
| } |
| if (custom_transfer_fn) { |
| DCHECK(transfer_ == TransferID::CUSTOM || |
| transfer_ == TransferID::CUSTOM_HDR); |
| SetCustomTransferFunction(*custom_transfer_fn, |
| transfer_ == TransferID::CUSTOM_HDR); |
| } |
| } |
| |
| ColorSpace::ColorSpace(const SkColorSpace& sk_color_space, bool is_hdr) |
| : ColorSpace(PrimaryID::INVALID, |
| TransferID::INVALID, |
| MatrixID::RGB, |
| RangeID::FULL) { |
| skcms_TransferFunction fn; |
| if (sk_color_space.isNumericalTransferFn(&fn)) { |
| SetCustomTransferFunction(fn, is_hdr); |
| } else if (skcms_TransferFunction_isHLGish(&fn)) { |
| transfer_ = TransferID::HLG; |
| transfer_params_[0] = 203.f; |
| transfer_params_[1] = 1000.f; |
| transfer_params_[2] = 1.2f; |
| } else if (skcms_TransferFunction_isPQish(&fn)) { |
| transfer_ = TransferID::PQ; |
| transfer_params_[0] = GetSDRWhiteLevelFromPQSkTransferFunction(fn); |
| if (transfer_params_[0] == 10000.f) { |
| transfer_params_[0] = 203.f; |
| } |
| } else if (skcms_TransferFunction_isHLG(&fn)) { |
| transfer_ = TransferID::HLG; |
| transfer_params_[0] = fn.a; |
| transfer_params_[1] = fn.b; |
| transfer_params_[2] = fn.c; |
| } else if (skcms_TransferFunction_isPQ(&fn)) { |
| transfer_ = TransferID::PQ; |
| transfer_params_[0] = fn.a; |
| } else { |
| // Construct an invalid result: Unable to extract necessary parameters |
| return; |
| } |
| |
| skcms_Matrix3x3 to_XYZD50; |
| if (!sk_color_space.toXYZD50(&to_XYZD50)) { |
| // Construct an invalid result: Unable to extract necessary parameters |
| return; |
| } |
| SetCustomPrimaries(to_XYZD50); |
| } |
| |
| bool ColorSpace::IsValid() const { |
| return primaries_ != PrimaryID::INVALID && transfer_ != TransferID::INVALID && |
| matrix_ != MatrixID::INVALID && range_ != RangeID::INVALID; |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateExtendedSRGB10Bit() { |
| return ColorSpace(PrimaryID::P3, TransferID::CUSTOM_HDR, MatrixID::RGB, |
| RangeID::FULL, nullptr, |
| &SkNamedTransferFnExt::kSRGBExtended1023Over510); |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateCustom(const skcms_Matrix3x3& to_XYZD50, |
| const skcms_TransferFunction& fn) { |
| ColorSpace result(ColorSpace::PrimaryID::CUSTOM, |
| ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB, |
| ColorSpace::RangeID::FULL, &to_XYZD50, &fn); |
| return result; |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateCustom(const skcms_Matrix3x3& to_XYZD50, |
| TransferID transfer) { |
| ColorSpace result(ColorSpace::PrimaryID::CUSTOM, transfer, |
| ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL, |
| &to_XYZD50, nullptr); |
| return result; |
| } |
| |
| void ColorSpace::SetCustomPrimaries(const skcms_Matrix3x3& to_XYZD50) { |
| const PrimaryID kIDsToCheck[] = { |
| PrimaryID::BT709, |
| PrimaryID::BT470M, |
| PrimaryID::BT470BG, |
| PrimaryID::SMPTE170M, |
| PrimaryID::SMPTE240M, |
| PrimaryID::FILM, |
| PrimaryID::BT2020, |
| PrimaryID::SMPTEST428_1, |
| PrimaryID::SMPTEST431_2, |
| PrimaryID::P3, |
| PrimaryID::XYZ_D50, |
| PrimaryID::ADOBE_RGB, |
| PrimaryID::APPLE_GENERIC_RGB, |
| PrimaryID::WIDE_GAMUT_COLOR_SPIN, |
| PrimaryID::EBU_3213_E, |
| }; |
| for (PrimaryID id : kIDsToCheck) { |
| skcms_Matrix3x3 matrix; |
| GetPrimaryMatrix(id, &matrix); |
| if (FloatsEqualWithinTolerance(&to_XYZD50.vals[0][0], &matrix.vals[0][0], 9, |
| 0.001f)) { |
| primaries_ = id; |
| return; |
| } |
| } |
| |
| UNSAFE_TODO(memcpy(custom_primary_matrix_, &to_XYZD50, 9 * sizeof(float))); |
| primaries_ = PrimaryID::CUSTOM; |
| } |
| |
| void ColorSpace::SetCustomTransferFunction(const skcms_TransferFunction& fn, |
| bool is_hdr) { |
| auto check_transfer_fn = [this, &fn](TransferID id) { |
| skcms_TransferFunction id_fn; |
| GetTransferFunction(id, &id_fn); |
| if (!FloatsEqualWithinTolerance(&fn.g, &id_fn.g, 7, 0.001f)) { |
| return false; |
| } |
| transfer_ = id; |
| return true; |
| }; |
| |
| transfer_ = is_hdr ? TransferID::CUSTOM_HDR : TransferID::CUSTOM; |
| |
| if (transfer_ == TransferID::CUSTOM) { |
| // These are all TransferIDs that will return a transfer function from |
| // GetTransferFunction. When multiple ids map to the same function, this |
| // list prioritizes the most common name (eg SRGB). |
| const TransferID kIDsToCheck[] = { |
| TransferID::SRGB, TransferID::LINEAR, |
| TransferID::GAMMA18, TransferID::GAMMA22, |
| TransferID::GAMMA24, TransferID::GAMMA28, |
| TransferID::SMPTE240M, TransferID::BT709_APPLE, |
| TransferID::SMPTEST428_1, |
| }; |
| for (TransferID id : kIDsToCheck) { |
| if (check_transfer_fn(id)) |
| return; |
| } |
| } |
| |
| if (transfer_ == TransferID::CUSTOM_HDR) { |
| // This list is the same as above, but for HDR TransferIDs. |
| const TransferID kIDsToCheckHDR[] = { |
| TransferID::SRGB_HDR, |
| TransferID::LINEAR_HDR, |
| }; |
| for (TransferID id : kIDsToCheckHDR) { |
| if (check_transfer_fn(id)) { |
| return; |
| } |
| } |
| } |
| |
| transfer_params_[0] = fn.a; |
| transfer_params_[1] = fn.b; |
| transfer_params_[2] = fn.c; |
| transfer_params_[3] = fn.d; |
| transfer_params_[4] = fn.e; |
| transfer_params_[5] = fn.f; |
| transfer_params_[6] = fn.g; |
| } |
| |
| // static |
| size_t ColorSpace::TransferParamCount(TransferID transfer) { |
| switch (transfer) { |
| case TransferID::CUSTOM: |
| return 7; |
| case TransferID::CUSTOM_HDR: |
| return 7; |
| case TransferID::PQ: |
| return 1; |
| case TransferID::HLG: |
| return 3; |
| default: |
| return 0; |
| } |
| } |
| |
| bool ColorSpace::operator==(const ColorSpace& other) const { |
| if (primaries_ != other.primaries_ || transfer_ != other.transfer_ || |
| matrix_ != other.matrix_ || range_ != other.range_) { |
| return false; |
| } |
| if (primaries_ == PrimaryID::CUSTOM) { |
| if (UNSAFE_TODO(memcmp(custom_primary_matrix_, other.custom_primary_matrix_, |
| sizeof(custom_primary_matrix_)))) { |
| return false; |
| } |
| } |
| if (size_t param_count = TransferParamCount(transfer_)) { |
| if (UNSAFE_TODO(memcmp(transfer_params_, other.transfer_params_, |
| param_count * sizeof(float)))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ColorSpace::IsWide() const { |
| // These HDR transfer functions are always wide |
| if (transfer_ == TransferID::SRGB_HDR || |
| transfer_ == TransferID::LINEAR_HDR || |
| transfer_ == TransferID::CUSTOM_HDR) |
| return true; |
| |
| if (primaries_ == PrimaryID::BT2020 || |
| primaries_ == PrimaryID::SMPTEST431_2 || primaries_ == PrimaryID::P3 || |
| primaries_ == PrimaryID::ADOBE_RGB || |
| primaries_ == PrimaryID::WIDE_GAMUT_COLOR_SPIN || |
| // TODO(cblume/ccameron): Compute if the custom primaries actually are |
| // wide. For now, assume so. |
| primaries_ == PrimaryID::CUSTOM) |
| return true; |
| |
| return false; |
| } |
| |
| bool ColorSpace::IsHDR() const { |
| return transfer_ == TransferID::PQ || transfer_ == TransferID::HLG || |
| transfer_ == TransferID::LINEAR_HDR || |
| transfer_ == TransferID::SRGB_HDR || |
| transfer_ == TransferID::CUSTOM_HDR || |
| transfer_ == TransferID::SCRGB_LINEAR_80_NITS; |
| } |
| |
| bool ColorSpace::IsToneMappedByDefault() const { |
| switch (transfer_) { |
| case TransferID::PQ: |
| case TransferID::HLG: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool ColorSpace::IsAffectedBySDRWhiteLevel() const { |
| switch (transfer_) { |
| case TransferID::PQ: |
| case TransferID::HLG: |
| case TransferID::SCRGB_LINEAR_80_NITS: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool ColorSpace::FullRangeEncodedValues() const { |
| return transfer_ == TransferID::LINEAR_HDR || |
| transfer_ == TransferID::SRGB_HDR || |
| transfer_ == TransferID::CUSTOM_HDR || |
| transfer_ == TransferID::SCRGB_LINEAR_80_NITS || |
| transfer_ == TransferID::BT1361_ECG || |
| transfer_ == TransferID::IEC61966_2_4; |
| } |
| |
| bool ColorSpace::operator<(const ColorSpace& other) const { |
| if (primaries_ < other.primaries_) |
| return true; |
| if (primaries_ > other.primaries_) |
| return false; |
| if (transfer_ < other.transfer_) |
| return true; |
| if (transfer_ > other.transfer_) |
| return false; |
| if (matrix_ < other.matrix_) |
| return true; |
| if (matrix_ > other.matrix_) |
| return false; |
| if (range_ < other.range_) |
| return true; |
| if (range_ > other.range_) |
| return false; |
| if (primaries_ == PrimaryID::CUSTOM) { |
| int primary_result = |
| UNSAFE_TODO(memcmp(custom_primary_matrix_, other.custom_primary_matrix_, |
| sizeof(custom_primary_matrix_))); |
| if (primary_result < 0) |
| return true; |
| if (primary_result > 0) |
| return false; |
| } |
| if (size_t param_count = TransferParamCount(transfer_)) { |
| int transfer_result = UNSAFE_TODO(memcmp( |
| transfer_params_, other.transfer_params_, param_count * sizeof(float))); |
| if (transfer_result < 0) |
| return true; |
| if (transfer_result > 0) |
| return false; |
| } |
| return false; |
| } |
| |
| size_t ColorSpace::GetHash() const { |
| size_t result = (static_cast<size_t>(primaries_) << 0) | |
| (static_cast<size_t>(transfer_) << 8) | |
| (static_cast<size_t>(matrix_) << 16) | |
| (static_cast<size_t>(range_) << 24); |
| if (primaries_ == PrimaryID::CUSTOM) { |
| const uint32_t* params = |
| reinterpret_cast<const uint32_t*>(custom_primary_matrix_); |
| result ^= params[0]; |
| result ^= UNSAFE_TODO(params[4]); |
| result ^= UNSAFE_TODO(params[8]); |
| } |
| { |
| // Note that |transfer_params_| must be zero when they are unused. |
| const uint32_t* params = |
| reinterpret_cast<const uint32_t*>(transfer_params_); |
| result ^= UNSAFE_TODO(params[3]); |
| result ^= UNSAFE_TODO(params[6]); |
| } |
| return result; |
| } |
| |
| #define PRINT_ENUM_CASE(TYPE, NAME) \ |
| case TYPE::NAME: \ |
| ss << #NAME; \ |
| break; |
| |
| std::string ColorSpace::ToString() const { |
| std::stringstream ss; |
| ss << std::fixed << std::setprecision(4); |
| if (primaries_ != PrimaryID::CUSTOM) |
| ss << "{primaries:"; |
| switch (primaries_) { |
| PRINT_ENUM_CASE(PrimaryID, INVALID) |
| PRINT_ENUM_CASE(PrimaryID, BT709) |
| PRINT_ENUM_CASE(PrimaryID, BT470M) |
| PRINT_ENUM_CASE(PrimaryID, BT470BG) |
| PRINT_ENUM_CASE(PrimaryID, SMPTE170M) |
| PRINT_ENUM_CASE(PrimaryID, SMPTE240M) |
| PRINT_ENUM_CASE(PrimaryID, FILM) |
| PRINT_ENUM_CASE(PrimaryID, BT2020) |
| PRINT_ENUM_CASE(PrimaryID, SMPTEST428_1) |
| PRINT_ENUM_CASE(PrimaryID, SMPTEST431_2) |
| PRINT_ENUM_CASE(PrimaryID, P3) |
| PRINT_ENUM_CASE(PrimaryID, XYZ_D50) |
| PRINT_ENUM_CASE(PrimaryID, ADOBE_RGB) |
| PRINT_ENUM_CASE(PrimaryID, APPLE_GENERIC_RGB) |
| PRINT_ENUM_CASE(PrimaryID, WIDE_GAMUT_COLOR_SPIN) |
| PRINT_ENUM_CASE(PrimaryID, EBU_3213_E) |
| case PrimaryID::CUSTOM: |
| ss << skia::SkColorSpacePrimariesToString(GetPrimaries()); |
| break; |
| } |
| ss << ", transfer:"; |
| switch (transfer_) { |
| PRINT_ENUM_CASE(TransferID, INVALID) |
| PRINT_ENUM_CASE(TransferID, BT709) |
| PRINT_ENUM_CASE(TransferID, BT709_APPLE) |
| PRINT_ENUM_CASE(TransferID, GAMMA18) |
| PRINT_ENUM_CASE(TransferID, GAMMA22) |
| PRINT_ENUM_CASE(TransferID, GAMMA24) |
| PRINT_ENUM_CASE(TransferID, GAMMA28) |
| PRINT_ENUM_CASE(TransferID, SMPTE170M) |
| PRINT_ENUM_CASE(TransferID, SMPTE240M) |
| PRINT_ENUM_CASE(TransferID, LINEAR) |
| PRINT_ENUM_CASE(TransferID, LOG) |
| PRINT_ENUM_CASE(TransferID, LOG_SQRT) |
| PRINT_ENUM_CASE(TransferID, IEC61966_2_4) |
| PRINT_ENUM_CASE(TransferID, BT1361_ECG) |
| PRINT_ENUM_CASE(TransferID, SRGB) |
| PRINT_ENUM_CASE(TransferID, BT2020_10) |
| PRINT_ENUM_CASE(TransferID, BT2020_12) |
| PRINT_ENUM_CASE(TransferID, SMPTEST428_1) |
| PRINT_ENUM_CASE(TransferID, SRGB_HDR) |
| PRINT_ENUM_CASE(TransferID, LINEAR_HDR) |
| case TransferID::HLG: |
| ss << "HLG (white:" |
| << (transfer_params_[0] <= 0.f ? kDefaultSDRWhiteLevel |
| : transfer_params_[0]) |
| << " nits, peak:" |
| << (transfer_params_[1] <= 0.f ? kDefaultPeakWhite |
| : transfer_params_[1]) |
| << " nits, gamma:" |
| << (transfer_params_[2] <= 0.f ? kDefaultSystemGamma |
| : transfer_params_[2]) |
| << ")"; |
| break; |
| case TransferID::PQ: |
| ss << "PQ (white:" |
| << (transfer_params_[0] <= 0.f ? kDefaultSDRWhiteLevel |
| : transfer_params_[0]) |
| << " nits)"; |
| break; |
| case TransferID::CUSTOM: { |
| skcms_TransferFunction fn; |
| GetTransferFunction(&fn); |
| ss << skia::SkcmsTransferFunctionToString(fn); |
| break; |
| } |
| case TransferID::CUSTOM_HDR: { |
| skcms_TransferFunction fn; |
| GetTransferFunction(&fn); |
| if (fn.g == 1.0f && fn.a > 0.0f && fn.b == 0.0f && fn.c == 0.0f && |
| fn.d == 0.0f && fn.e == 0.0f && fn.f == 0.0f) { |
| ss << "LINEAR_HDR (slope " << fn.a << ")"; |
| break; |
| } |
| ss << skia::SkcmsTransferFunctionToString(fn); |
| break; |
| } |
| case TransferID::SCRGB_LINEAR_80_NITS: |
| ss << "scRGB linear (80 nit white)"; |
| break; |
| } |
| ss << ", matrix:"; |
| switch (matrix_) { |
| PRINT_ENUM_CASE(MatrixID, INVALID) |
| PRINT_ENUM_CASE(MatrixID, RGB) |
| PRINT_ENUM_CASE(MatrixID, BT709) |
| PRINT_ENUM_CASE(MatrixID, FCC) |
| PRINT_ENUM_CASE(MatrixID, BT470BG) |
| PRINT_ENUM_CASE(MatrixID, SMPTE170M) |
| PRINT_ENUM_CASE(MatrixID, SMPTE240M) |
| PRINT_ENUM_CASE(MatrixID, YCOCG) |
| PRINT_ENUM_CASE(MatrixID, BT2020_NCL) |
| PRINT_ENUM_CASE(MatrixID, YDZDX) |
| PRINT_ENUM_CASE(MatrixID, GBR) |
| } |
| ss << ", range:"; |
| switch (range_) { |
| PRINT_ENUM_CASE(RangeID, INVALID) |
| PRINT_ENUM_CASE(RangeID, LIMITED) |
| PRINT_ENUM_CASE(RangeID, FULL) |
| PRINT_ENUM_CASE(RangeID, DERIVED) |
| } |
| ss << "}"; |
| return ss.str(); |
| } |
| |
| #undef PRINT_ENUM_CASE |
| |
| ColorSpace ColorSpace::GetAsFullRangeRGB() const { |
| ColorSpace result(*this); |
| if (!IsValid()) |
| return result; |
| result.matrix_ = MatrixID::RGB; |
| result.range_ = RangeID::FULL; |
| return result; |
| } |
| |
| ContentColorUsage ColorSpace::GetContentColorUsage() const { |
| if (IsHDR()) |
| return ContentColorUsage::kHDR; |
| if (IsWide()) |
| return ContentColorUsage::kWideColorGamut; |
| return ContentColorUsage::kSRGB; |
| } |
| |
| ColorSpace ColorSpace::GetAsRGB() const { |
| ColorSpace result(*this); |
| if (IsValid()) |
| result.matrix_ = MatrixID::RGB; |
| return result; |
| } |
| |
| ColorSpace ColorSpace::GetScaledColorSpace(float factor) const { |
| ColorSpace result(*this); |
| skcms_Matrix3x3 to_XYZD50; |
| GetPrimaryMatrix(&to_XYZD50); |
| for (int row = 0; row < 3; ++row) { |
| for (int col = 0; col < 3; ++col) { |
| UNSAFE_TODO(to_XYZD50.vals[row][col]) *= factor; |
| } |
| } |
| result.SetCustomPrimaries(to_XYZD50); |
| return result; |
| } |
| |
| bool ColorSpace::IsSuitableForBlending() const { |
| switch (transfer_) { |
| case TransferID::PQ: |
| // PQ is not an acceptable space to do blending in -- blending 0 and 1 |
| // evenly will get a result of sRGB 0.259 (instead of 0.5). |
| return false; |
| case TransferID::HLG: |
| case TransferID::LINEAR_HDR: |
| case TransferID::SCRGB_LINEAR_80_NITS: |
| // If the color space is nearly-linear, then it is not suitable for |
| // blending -- blending 0 and 1 evenly will get a result of sRGB 0.735 |
| // (instead of 0.5). |
| return false; |
| case TransferID::CUSTOM_HDR: { |
| // A gamma close enough to linear is treated as linear. |
| skcms_TransferFunction fn; |
| if (GetTransferFunction(&fn)) { |
| constexpr float kMinGamma = 1.25; |
| if (fn.g < kMinGamma) |
| return false; |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| ColorSpace ColorSpace::GetWithMatrixAndRange(MatrixID matrix, |
| RangeID range) const { |
| ColorSpace result(*this); |
| if (!IsValid()) |
| return result; |
| |
| result.matrix_ = matrix; |
| result.range_ = range; |
| return result; |
| } |
| |
| ColorSpace ColorSpace::GetAsHDR() const { |
| ColorSpace result = *this; |
| skcms_TransferFunction fn; |
| if (result.GetTransferFunction(&fn)) { |
| result.SetCustomTransferFunction(fn, /*is_hdr=*/true); |
| } |
| return result; |
| } |
| |
| ColorSpace ColorSpace::GetWithTransferFunction(TransferID transfer) const { |
| DCHECK_NE(transfer, TransferID::CUSTOM); |
| DCHECK_NE(transfer, TransferID::CUSTOM_HDR); |
| ColorSpace result(*this); |
| result.transfer_ = transfer; |
| return result; |
| } |
| |
| ColorSpace ColorSpace::GetWithTransferFunction(const skcms_TransferFunction& fn, |
| bool is_hdr) const { |
| ColorSpace result(*this); |
| result.SetCustomTransferFunction(fn, is_hdr); |
| return result; |
| } |
| |
| ColorSpace ColorSpace::GetWithSdrWhiteLevel(float sdr_white_level) const { |
| if (!IsAffectedBySDRWhiteLevel()) |
| return *this; |
| |
| return gfx::ColorSpace(*ToSkColorSpace(sdr_white_level), /*is_hdr=*/true); |
| } |
| |
| sk_sp<SkColorSpace> ColorSpace::ToSkColorSpace( |
| std::optional<float> sdr_white_level) const { |
| // Handle only valid, full-range RGB spaces. |
| if (!IsValid() || matrix_ != MatrixID::RGB || range_ != RangeID::FULL) |
| return nullptr; |
| |
| // Use the named SRGB and linear-SRGB instead of the generic constructors. |
| if (primaries_ == PrimaryID::BT709) { |
| if (transfer_ == TransferID::SRGB) |
| return SkColorSpace::MakeSRGB(); |
| if (transfer_ == TransferID::LINEAR || transfer_ == TransferID::LINEAR_HDR) |
| return SkColorSpace::MakeSRGBLinear(); |
| } |
| |
| // This is almost equal to SkNamedTransferFunction::kSRGB, but has some slight |
| // rounding differences that some tests depend on. These tests should be |
| // updated. |
| skcms_TransferFunction transfer_fn = {2.4f, 0.947867345704f, 0.052132654296f, |
| 0.077399380805f, 0.040449937172f}; |
| switch (transfer_) { |
| case TransferID::SRGB: |
| break; |
| case TransferID::LINEAR: |
| case TransferID::LINEAR_HDR: |
| transfer_fn = SkNamedTransferFn::kLinear; |
| break; |
| case TransferID::HLG: { |
| const float hdr_reference_white = sdr_white_level.value_or( |
| transfer_params_[0] > 0.f ? transfer_params_[0] |
| : kDefaultSDRWhiteLevel); |
| const float peak_white = |
| transfer_params_[1] > 0.f ? transfer_params_[1] : kDefaultPeakWhite; |
| const float system_gamma = |
| transfer_params_[2] > 0.f ? transfer_params_[2] : kDefaultSystemGamma; |
| skcms_TransferFunction_makeHLG(&transfer_fn, hdr_reference_white, |
| peak_white, system_gamma); |
| break; |
| } |
| case TransferID::PQ: { |
| const float hdr_reference_white = sdr_white_level.value_or( |
| transfer_params_[0] > 0.f ? transfer_params_[0] |
| : kDefaultSDRWhiteLevel); |
| skcms_TransferFunction_makePQ(&transfer_fn, hdr_reference_white); |
| break; |
| } |
| default: |
| if (!GetTransferFunction(&transfer_fn, sdr_white_level)) { |
| DLOG(ERROR) << "Failed to get transfer function for SkColorSpace"; |
| return nullptr; |
| } |
| break; |
| } |
| skcms_Matrix3x3 gamut = SkNamedGamut::kSRGB; |
| switch (primaries_) { |
| case PrimaryID::BT709: |
| break; |
| case PrimaryID::ADOBE_RGB: |
| gamut = SkNamedGamut::kAdobeRGB; |
| break; |
| case PrimaryID::P3: |
| gamut = SkNamedGamut::kDisplayP3; |
| break; |
| case PrimaryID::BT2020: |
| gamut = SkNamedGamut::kRec2020; |
| break; |
| default: |
| GetPrimaryMatrix(&gamut); |
| break; |
| } |
| sk_sp<SkColorSpace> sk_color_space = |
| SkColorSpace::MakeRGB(transfer_fn, gamut); |
| if (!sk_color_space) |
| DLOG(ERROR) << "SkColorSpace::MakeRGB failed."; |
| |
| return sk_color_space; |
| } |
| |
| const struct _GLcolorSpace* ColorSpace::AsGLColorSpace() const { |
| return reinterpret_cast<const struct _GLcolorSpace*>(this); |
| } |
| |
| ColorSpace::PrimaryID ColorSpace::GetPrimaryID() const { |
| return primaries_; |
| } |
| |
| ColorSpace::TransferID ColorSpace::GetTransferID() const { |
| return transfer_; |
| } |
| |
| ColorSpace::MatrixID ColorSpace::GetMatrixID() const { |
| return matrix_; |
| } |
| |
| ColorSpace::RangeID ColorSpace::GetRangeID() const { |
| return range_; |
| } |
| |
| bool ColorSpace::HasExtendedSkTransferFn() const { |
| return matrix_ == MatrixID::RGB; |
| } |
| |
| bool ColorSpace::IsTransferFunctionEqualTo( |
| const skcms_TransferFunction& fn) const { |
| if (transfer_ == TransferID::PQ) |
| return skcms_TransferFunction_isPQish(&fn) || |
| skcms_TransferFunction_isPQ(&fn); |
| if (transfer_ == TransferID::HLG) |
| return skcms_TransferFunction_isHLGish(&fn) || |
| skcms_TransferFunction_isHLG(&fn); |
| if (!skcms_TransferFunction_isSRGBish(&fn)) |
| return false; |
| skcms_TransferFunction transfer_fn; |
| GetTransferFunction(&transfer_fn); |
| return fn.a == transfer_fn.a && fn.b == transfer_fn.b && |
| fn.c == transfer_fn.c && fn.d == transfer_fn.d && |
| fn.e == transfer_fn.e && fn.f == transfer_fn.f && |
| fn.g == transfer_fn.g; |
| } |
| |
| bool ColorSpace::Contains(const ColorSpace& other) const { |
| if (primaries_ == PrimaryID::INVALID || |
| other.primaries_ == PrimaryID::INVALID) |
| return false; |
| |
| // Contains() is commonly used to check if a color space contains sRGB. The |
| // computation can be bypassed for known primary IDs. |
| if (primaries_ != PrimaryID::CUSTOM && other.primaries_ == PrimaryID::BT709) |
| return PrimaryIdContainsSRGB(primaries_); |
| |
| // |matrix| is the primary transform matrix from |other| to this color space. |
| skcms_Matrix3x3 other_to_xyz; |
| skcms_Matrix3x3 this_to_xyz; |
| skcms_Matrix3x3 xyz_to_this; |
| other.GetPrimaryMatrix(&other_to_xyz); |
| GetPrimaryMatrix(&this_to_xyz); |
| skcms_Matrix3x3_invert(&this_to_xyz, &xyz_to_this); |
| skcms_Matrix3x3 matrix = skcms_Matrix3x3_concat(&xyz_to_this, &other_to_xyz); |
| |
| // Return true iff each primary is in the range [0, 1] after transforming. |
| // Transforming a primary vector by |matrix| always results in a column of |
| // |matrix|. So the multiplication can be skipped, and we can just check if |
| // each value in the matrix is in the range [0, 1]. |
| constexpr float epsilon = 0.001f; |
| for (int r = 0; r < 3; r++) { |
| for (int c = 0; c < 3; c++) { |
| if (UNSAFE_TODO(matrix.vals[r][c]) < -epsilon || |
| UNSAFE_TODO(matrix.vals[r][c]) > 1 + epsilon) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // static |
| SkColorSpacePrimaries ColorSpace::GetColorSpacePrimaries( |
| PrimaryID primary_id, |
| const skcms_Matrix3x3* custom_primary_matrix = nullptr) { |
| SkColorSpacePrimaries primaries = SkNamedPrimariesExt::kInvalid; |
| |
| if (custom_primary_matrix && primary_id == PrimaryID::CUSTOM) |
| return skia::GetD65PrimariesFromToXYZD50Matrix(*custom_primary_matrix); |
| |
| switch (primary_id) { |
| case ColorSpace::PrimaryID::CUSTOM: |
| case ColorSpace::PrimaryID::INVALID: |
| break; |
| |
| case ColorSpace::PrimaryID::BT709: |
| // BT709 is our default case. Put it after the switch just |
| // in case we somehow get an id which is not listed in the switch. |
| // (We don't want to use "default", because we want the compiler |
| // to tell us if we forgot some enum values.) |
| return SkNamedPrimaries::kRec709; |
| |
| case ColorSpace::PrimaryID::BT470M: |
| return SkNamedPrimaries::kRec470SystemM; |
| |
| case ColorSpace::PrimaryID::BT470BG: |
| return SkNamedPrimaries::kRec470SystemBG; |
| |
| case ColorSpace::PrimaryID::SMPTE170M: |
| return SkNamedPrimaries::kRec601; |
| |
| case ColorSpace::PrimaryID::SMPTE240M: |
| return SkNamedPrimaries::kSMPTE_ST_240; |
| |
| case ColorSpace::PrimaryID::APPLE_GENERIC_RGB: |
| return SkNamedPrimariesExt::kAppleGenericRGB; |
| |
| case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN: |
| return SkNamedPrimariesExt::kWideGamutColorSpin; |
| |
| case ColorSpace::PrimaryID::FILM: |
| return SkNamedPrimaries::kGenericFilm; |
| |
| case ColorSpace::PrimaryID::BT2020: |
| return SkNamedPrimaries::kRec2020; |
| |
| case ColorSpace::PrimaryID::SMPTEST428_1: |
| return SkNamedPrimaries::kSMPTE_ST_428_1; |
| |
| case ColorSpace::PrimaryID::SMPTEST431_2: |
| return SkNamedPrimaries::kSMPTE_RP_431_2; |
| |
| case ColorSpace::PrimaryID::P3: |
| return SkNamedPrimariesExt::kP3; |
| |
| case ColorSpace::PrimaryID::XYZ_D50: |
| return SkNamedPrimariesExt::kXYZD50; |
| |
| case ColorSpace::PrimaryID::ADOBE_RGB: |
| return SkNamedPrimariesExt::kA98RGB; |
| |
| case ColorSpace::PrimaryID::EBU_3213_E: |
| return SkNamedPrimaries::kITU_T_H273_Value22; |
| } |
| return primaries; |
| } |
| |
| SkColorSpacePrimaries ColorSpace::GetPrimaries() const { |
| skcms_Matrix3x3 matrix; |
| UNSAFE_TODO(memcpy(&matrix, custom_primary_matrix_, 9 * sizeof(float))); |
| return GetColorSpacePrimaries(primaries_, &matrix); |
| } |
| |
| // static |
| void ColorSpace::GetPrimaryMatrix(PrimaryID primary_id, |
| skcms_Matrix3x3* to_XYZD50) { |
| SkColorSpacePrimaries primaries = GetColorSpacePrimaries(primary_id); |
| |
| if (primary_id == PrimaryID::CUSTOM || primary_id == PrimaryID::INVALID) { |
| *to_XYZD50 = SkNamedGamut::kXYZ; // Identity |
| return; |
| } |
| primaries.toXYZD50(to_XYZD50); |
| } |
| |
| void ColorSpace::GetPrimaryMatrix(skcms_Matrix3x3* to_XYZD50) const { |
| if (primaries_ == PrimaryID::CUSTOM) { |
| UNSAFE_TODO(memcpy(to_XYZD50, custom_primary_matrix_, 9 * sizeof(float))); |
| } else { |
| GetPrimaryMatrix(primaries_, to_XYZD50); |
| } |
| } |
| |
| SkM44 ColorSpace::GetPrimaryMatrix() const { |
| skcms_Matrix3x3 toXYZ_3x3; |
| GetPrimaryMatrix(&toXYZ_3x3); |
| return SkM44FromSkcmsMatrix3x3(toXYZ_3x3); |
| } |
| |
| // static |
| bool ColorSpace::GetTransferFunction(TransferID transfer, |
| skcms_TransferFunction* fn) { |
| // Default to F(x) = pow(x, 1) |
| fn->a = 1; |
| fn->b = 0; |
| fn->c = 0; |
| fn->d = 0; |
| fn->e = 0; |
| fn->f = 0; |
| fn->g = 1; |
| |
| switch (transfer) { |
| case ColorSpace::TransferID::LINEAR: |
| case ColorSpace::TransferID::LINEAR_HDR: |
| *fn = SkNamedTransferFn::kLinear; |
| return true; |
| case ColorSpace::TransferID::GAMMA18: |
| fn->g = 1.801f; |
| return true; |
| case ColorSpace::TransferID::GAMMA22: |
| *fn = SkNamedTransferFn::kRec470SystemM; |
| return true; |
| case ColorSpace::TransferID::GAMMA24: |
| fn->g = 2.4f; |
| return true; |
| case ColorSpace::TransferID::GAMMA28: |
| *fn = SkNamedTransferFn::kRec470SystemBG; |
| return true; |
| case ColorSpace::TransferID::SMPTE240M: |
| *fn = SkNamedTransferFn::kSMPTE_ST_240; |
| return true; |
| case ColorSpace::TransferID::BT709: |
| case ColorSpace::TransferID::SMPTE170M: |
| case ColorSpace::TransferID::BT2020_10: |
| case ColorSpace::TransferID::BT2020_12: |
| // With respect to rendering BT709 |
| // * SMPTE 1886 suggests that we should be using gamma 2.4. |
| // * Most displays actually use a gamma of 2.2, and most media playing |
| // software uses the sRGB transfer function. |
| // * User studies shows that users don't really care. |
| // * Apple's CoreVideo uses gamma=1.961. |
| // Bearing all of that in mind, use the same transfer function as sRGB, |
| // which will allow more optimization, and will more closely match other |
| // media players. |
| case ColorSpace::TransferID::SRGB: |
| case ColorSpace::TransferID::SRGB_HDR: |
| *fn = SkNamedTransferFn::kSRGB; |
| return true; |
| case ColorSpace::TransferID::BT709_APPLE: |
| *fn = SkNamedTransferFnExt::kRec709Apple; |
| return true; |
| case ColorSpace::TransferID::SMPTEST428_1: |
| *fn = SkNamedTransferFn::kSMPTE_ST_428_1; |
| return true; |
| case ColorSpace::TransferID::IEC61966_2_4: |
| // This could potentially be represented the same as SRGB, but it handles |
| // negative values differently. |
| break; |
| case ColorSpace::TransferID::HLG: |
| case ColorSpace::TransferID::BT1361_ECG: |
| case ColorSpace::TransferID::LOG: |
| case ColorSpace::TransferID::LOG_SQRT: |
| case ColorSpace::TransferID::PQ: |
| case ColorSpace::TransferID::CUSTOM: |
| case ColorSpace::TransferID::CUSTOM_HDR: |
| case ColorSpace::TransferID::SCRGB_LINEAR_80_NITS: |
| case ColorSpace::TransferID::INVALID: |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool ColorSpace::GetTransferFunction( |
| skcms_TransferFunction* fn, |
| std::optional<float> sdr_white_level) const { |
| switch (transfer_) { |
| case TransferID::CUSTOM: |
| case TransferID::CUSTOM_HDR: |
| fn->a = transfer_params_[0]; |
| fn->b = transfer_params_[1]; |
| fn->c = transfer_params_[2]; |
| fn->d = transfer_params_[3]; |
| fn->e = transfer_params_[4]; |
| fn->f = transfer_params_[5]; |
| fn->g = transfer_params_[6]; |
| return true; |
| case TransferID::SCRGB_LINEAR_80_NITS: |
| if (sdr_white_level) { |
| fn->a = 80.f / *sdr_white_level; |
| fn->b = 0; |
| fn->c = 0; |
| fn->d = 0; |
| fn->e = 0; |
| fn->f = 0; |
| fn->g = 1; |
| return true; |
| } else { |
| // Using SCRGB_LINEAR_80_NITS without specifying an SDR white level is |
| // guaranteed to produce incorrect results. |
| return false; |
| } |
| default: |
| return GetTransferFunction(transfer_, fn); |
| } |
| } |
| |
| bool ColorSpace::GetInverseTransferFunction( |
| skcms_TransferFunction* fn, |
| std::optional<float> sdr_white_level) const { |
| if (!GetTransferFunction(fn, sdr_white_level)) |
| return false; |
| *fn = SkTransferFnInverse(*fn); |
| return true; |
| } |
| |
| SkM44 ColorSpace::GetTransferMatrix(int bit_depth) const { |
| DCHECK_GE(bit_depth, 8); |
| // If chroma samples are real numbers in the range of −0.5 to 0.5, an offset |
| // of 0.5 is added to get real numbers in the range of 0 to 1. When |
| // represented as an unsigned |bit_depth|-bit integer, this 0.5 offset is |
| // approximated by 1 << (bit_depth - 1). chroma_0_5 is this approximate value |
| // converted to a real number in the range of 0 to 1. |
| // |
| // TODO(wtc): For now chroma_0_5 is only used for YCgCo. It should also be |
| // used for YUV. |
| const float chroma_0_5 = |
| static_cast<float>(1 << (bit_depth - 1)) / ((1 << bit_depth) - 1); |
| float Kr = 0; |
| float Kb = 0; |
| switch (matrix_) { |
| case ColorSpace::MatrixID::RGB: |
| case ColorSpace::MatrixID::INVALID: |
| return SkM44(); |
| |
| case ColorSpace::MatrixID::BT709: |
| Kr = 0.2126f; |
| Kb = 0.0722f; |
| break; |
| |
| case ColorSpace::MatrixID::FCC: |
| Kr = 0.30f; |
| Kb = 0.11f; |
| break; |
| |
| case ColorSpace::MatrixID::BT470BG: |
| case ColorSpace::MatrixID::SMPTE170M: |
| Kr = 0.299f; |
| Kb = 0.114f; |
| break; |
| |
| case ColorSpace::MatrixID::SMPTE240M: |
| Kr = 0.212f; |
| Kb = 0.087f; |
| break; |
| |
| case ColorSpace::MatrixID::YCOCG: { |
| float data[16] = {0.25f, 0.5f, 0.25f, 0.0f, // Y |
| -0.25f, 0.5f, -0.25f, chroma_0_5, // Cg |
| 0.5f, 0.0f, -0.5f, chroma_0_5, // Co |
| 0.0f, 0.0f, 0.0f, 1.0f}; |
| return SkM44::RowMajor(data); |
| } |
| |
| case ColorSpace::MatrixID::BT2020_NCL: |
| Kr = 0.2627f; |
| Kb = 0.0593f; |
| break; |
| |
| case ColorSpace::MatrixID::YDZDX: { |
| // clang-format off |
| float data[16] = { |
| 0.0f, 1.0f, 0.0f, 0.0f, // Y |
| 0.0f, -0.5f, 0.986566f / 2.0f, 0.5f, // DX or DZ |
| 0.5f, -0.991902f / 2.0f, 0.0f, 0.5f, // DZ or DX |
| 0.0f, 0.0f, 0.0f, 1.0f, |
| }; |
| // clang-format on |
| return SkM44::RowMajor(data); |
| } |
| case ColorSpace::MatrixID::GBR: { |
| float data[16] = {0.0f, 1.0f, 0.0f, 0.0f, // G |
| 0.0f, 0.0f, 1.0f, 0.0f, // B |
| 1.0f, 0.0f, 0.0f, 0.0f, // R |
| 0.0f, 0.0f, 0.0f, 1.0f}; |
| return SkM44::RowMajor(data); |
| } |
| } |
| float Kg = 1.0f - Kr - Kb; |
| float u_m = 0.5f / (1.0f - Kb); |
| float v_m = 0.5f / (1.0f - Kr); |
| // clang-format off |
| float data[16] = { |
| Kr, Kg, Kb, 0.0f, // Y |
| u_m * -Kr, u_m * -Kg, u_m * (1.0f - Kb), 0.5f, // U |
| v_m * (1.0f - Kr), v_m * -Kg, v_m * -Kb, 0.5f, // V |
| 0.0f, 0.0f, 0.0f, 1.0f, |
| }; |
| // clang-format on |
| return SkM44::RowMajor(data); |
| } |
| |
| SkM44 ColorSpace::GetRangeAdjustMatrix(int bit_depth) const { |
| DCHECK_GE(bit_depth, 8); |
| switch (range_) { |
| case RangeID::FULL: |
| case RangeID::INVALID: |
| return SkM44(); |
| |
| case RangeID::DERIVED: |
| case RangeID::LIMITED: |
| break; |
| } |
| |
| // See ITU-T H.273 (2016), Section 8.3. The following is derived from |
| // Equations 20-31. |
| const int shift = bit_depth - 8; |
| const float a_y = 219 << shift; |
| const float c = (1 << bit_depth) - 1; |
| const float scale_y = c / a_y; |
| switch (matrix_) { |
| case MatrixID::RGB: |
| case MatrixID::GBR: |
| case MatrixID::INVALID: |
| case MatrixID::YCOCG: |
| return SkM44::Scale(scale_y, scale_y, scale_y) |
| .postTranslate(-16.0f / 219.0f, -16.0f / 219.0f, -16.0f / 219.0f); |
| |
| case MatrixID::BT709: |
| case MatrixID::FCC: |
| case MatrixID::BT470BG: |
| case MatrixID::SMPTE170M: |
| case MatrixID::SMPTE240M: |
| case MatrixID::BT2020_NCL: |
| case MatrixID::YDZDX: { |
| const float a_uv = 224 << shift; |
| const float scale_uv = c / a_uv; |
| const float translate_uv = (a_uv - c) / (2.0f * a_uv); |
| return SkM44::Scale(scale_y, scale_uv, scale_uv) |
| .postTranslate(-16.0f / 219.0f, translate_uv, translate_uv); |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| bool ColorSpace::ToSkYUVColorSpace(int bit_depth, SkYUVColorSpace* out) const { |
| // There should be no usages of RGB matrix for YUV conversion. |
| if (matrix_ == gfx::ColorSpace::MatrixID::RGB) { |
| [[maybe_unused]] static bool call_once = [&]() { |
| SCOPED_CRASH_KEY_STRING256("ToSkYUVColorSpace", "ColorSpace", ToString()); |
| DUMP_WILL_BE_CHECK(false) |
| << "ToSkYUVColorSpace called on RGB color space = " << ToString(); |
| return true; |
| }(); |
| } |
| |
| switch (matrix_) { |
| case MatrixID::BT709: |
| *out = range_ == RangeID::FULL ? kRec709_Full_SkYUVColorSpace |
| : kRec709_Limited_SkYUVColorSpace; |
| return true; |
| |
| case MatrixID::BT470BG: |
| case MatrixID::SMPTE170M: |
| *out = range_ == RangeID::FULL ? kJPEG_SkYUVColorSpace |
| : kRec601_Limited_SkYUVColorSpace; |
| return true; |
| |
| case MatrixID::BT2020_NCL: |
| if (bit_depth <= 8) { |
| *out = range_ == RangeID::FULL ? kBT2020_8bit_Full_SkYUVColorSpace |
| : kBT2020_8bit_Limited_SkYUVColorSpace; |
| } else if (bit_depth <= 10) { |
| *out = range_ == RangeID::FULL ? kBT2020_10bit_Full_SkYUVColorSpace |
| : kBT2020_10bit_Limited_SkYUVColorSpace; |
| } else if (bit_depth <= 12) { |
| *out = range_ == RangeID::FULL ? kBT2020_12bit_Full_SkYUVColorSpace |
| : kBT2020_12bit_Limited_SkYUVColorSpace; |
| } else { |
| *out = range_ == RangeID::FULL ? kBT2020_16bit_Full_SkYUVColorSpace |
| : kBT2020_16bit_Limited_SkYUVColorSpace; |
| } |
| return true; |
| |
| case MatrixID::FCC: |
| *out = range_ == RangeID::FULL ? kFCC_Full_SkYUVColorSpace |
| : kFCC_Limited_SkYUVColorSpace; |
| return true; |
| |
| case MatrixID::SMPTE240M: |
| *out = range_ == RangeID::FULL ? kSMPTE240_Full_SkYUVColorSpace |
| : kSMPTE240_Limited_SkYUVColorSpace; |
| return true; |
| |
| case MatrixID::YDZDX: |
| *out = range_ == RangeID::FULL ? kYDZDX_Full_SkYUVColorSpace |
| : kYDZDX_Limited_SkYUVColorSpace; |
| return true; |
| |
| case MatrixID::GBR: |
| *out = range_ == RangeID::FULL ? kGBR_Full_SkYUVColorSpace |
| : kGBR_Limited_SkYUVColorSpace; |
| return true; |
| |
| case MatrixID::YCOCG: |
| if (bit_depth <= 8) { |
| *out = range_ == RangeID::FULL ? kYCgCo_8bit_Full_SkYUVColorSpace |
| : kYCgCo_8bit_Limited_SkYUVColorSpace; |
| } else if (bit_depth <= 10) { |
| *out = range_ == RangeID::FULL ? kYCgCo_10bit_Full_SkYUVColorSpace |
| : kYCgCo_10bit_Limited_SkYUVColorSpace; |
| } else if (bit_depth <= 12) { |
| *out = range_ == RangeID::FULL ? kYCgCo_12bit_Full_SkYUVColorSpace |
| : kYCgCo_12bit_Limited_SkYUVColorSpace; |
| } else { |
| *out = range_ == RangeID::FULL ? kYCgCo_16bit_Full_SkYUVColorSpace |
| : kYCgCo_16bit_Limited_SkYUVColorSpace; |
| } |
| return true; |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const ColorSpace& color_space) { |
| return out << color_space.ToString(); |
| } |
| |
| } // namespace gfx |