| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // 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 <map> |
| |
| #include "base/lazy_instance.h" |
| #include "base/synchronization/lock.h" |
| #include "third_party/skia/include/core/SkColorSpace.h" |
| #include "ui/gfx/icc_profile.h" |
| #include "ui/gfx/transform.h" |
| |
| namespace gfx { |
| |
| namespace { |
| |
| SkColorSpaceTransferFn InvertTransferFn(SkColorSpaceTransferFn fn) { |
| SkColorSpaceTransferFn fn_inv = {0}; |
| if (fn.fA > 0 && fn.fG > 0) { |
| double a_to_the_g = pow(fn.fA, fn.fG); |
| fn_inv.fA = 1.f / a_to_the_g; |
| fn_inv.fB = -fn.fE / a_to_the_g; |
| fn_inv.fG = 1.f / fn.fG; |
| } |
| fn_inv.fD = fn.fC * fn.fD + fn.fF; |
| fn_inv.fE = -fn.fB / fn.fA; |
| if (fn.fC != 0) { |
| fn_inv.fC = 1.f / fn.fC; |
| fn_inv.fF = -fn.fF / fn.fC; |
| } |
| return fn_inv; |
| } |
| }; |
| |
| ColorSpace::PrimaryID ColorSpace::PrimaryIDFromInt(int primary_id) { |
| if (primary_id < 0 || primary_id > static_cast<int>(PrimaryID::LAST)) |
| return PrimaryID::UNKNOWN; |
| if (primary_id > static_cast<int>(PrimaryID::LAST_STANDARD_VALUE) && |
| primary_id < 1000) |
| return PrimaryID::UNKNOWN; |
| return static_cast<PrimaryID>(primary_id); |
| } |
| |
| ColorSpace::TransferID ColorSpace::TransferIDFromInt(int transfer_id) { |
| if (transfer_id < 0 || transfer_id > static_cast<int>(TransferID::LAST)) |
| return TransferID::UNKNOWN; |
| if (transfer_id > static_cast<int>(TransferID::LAST_STANDARD_VALUE) && |
| transfer_id < 1000) |
| return TransferID::UNKNOWN; |
| return static_cast<TransferID>(transfer_id); |
| } |
| |
| ColorSpace::MatrixID ColorSpace::MatrixIDFromInt(int matrix_id) { |
| if (matrix_id < 0 || matrix_id > static_cast<int>(MatrixID::LAST)) |
| return MatrixID::UNKNOWN; |
| if (matrix_id > static_cast<int>(MatrixID::LAST_STANDARD_VALUE) && |
| matrix_id < 1000) |
| return MatrixID::UNKNOWN; |
| return static_cast<MatrixID>(matrix_id); |
| } |
| |
| ColorSpace::ColorSpace() {} |
| |
| ColorSpace::ColorSpace(PrimaryID primaries, |
| TransferID transfer) |
| : primaries_(primaries), |
| transfer_(transfer), |
| matrix_(MatrixID::RGB), |
| range_(RangeID::FULL) {} |
| |
| ColorSpace::ColorSpace(PrimaryID primaries, |
| TransferID transfer, |
| MatrixID matrix, |
| RangeID range) |
| : primaries_(primaries), |
| transfer_(transfer), |
| matrix_(matrix), |
| range_(range) {} |
| |
| ColorSpace::ColorSpace(int primaries, int transfer, int matrix, RangeID range) |
| : primaries_(PrimaryIDFromInt(primaries)), |
| transfer_(TransferIDFromInt(transfer)), |
| matrix_(MatrixIDFromInt(matrix)), |
| range_(range) {} |
| |
| ColorSpace::ColorSpace(const ColorSpace& other) |
| : primaries_(other.primaries_), |
| transfer_(other.transfer_), |
| matrix_(other.matrix_), |
| range_(other.range_), |
| icc_profile_id_(other.icc_profile_id_), |
| icc_profile_sk_color_space_(other.icc_profile_sk_color_space_) { |
| if (transfer_ == TransferID::CUSTOM) { |
| memcpy(custom_transfer_params_, other.custom_transfer_params_, |
| sizeof(custom_transfer_params_)); |
| } |
| if (primaries_ == PrimaryID::CUSTOM) { |
| memcpy(custom_primary_matrix_, other.custom_primary_matrix_, |
| sizeof(custom_primary_matrix_)); |
| } |
| } |
| |
| ColorSpace::~ColorSpace() = default; |
| |
| bool ColorSpace::IsValid() const { |
| return *this != gfx::ColorSpace(); |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateSRGB() { |
| return ColorSpace(PrimaryID::BT709, TransferID::IEC61966_2_1, MatrixID::RGB, |
| RangeID::FULL); |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateSCRGBLinear() { |
| return ColorSpace(PrimaryID::BT709, TransferID::LINEAR_HDR, MatrixID::RGB, |
| RangeID::FULL); |
| } |
| |
| // Static |
| ColorSpace ColorSpace::CreateXYZD50() { |
| return ColorSpace(PrimaryID::XYZ_D50, TransferID::LINEAR, MatrixID::RGB, |
| RangeID::FULL); |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateJpeg() { |
| // TODO(ccameron): Determine which primaries and transfer function were |
| // intended here. |
| return ColorSpace(PrimaryID::BT709, TransferID::IEC61966_2_1, |
| MatrixID::SMPTE170M, RangeID::FULL); |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateREC601() { |
| return ColorSpace(PrimaryID::SMPTE170M, TransferID::SMPTE170M, |
| MatrixID::SMPTE170M, RangeID::LIMITED); |
| } |
| |
| // static |
| ColorSpace ColorSpace::CreateREC709() { |
| return ColorSpace(PrimaryID::BT709, TransferID::BT709, MatrixID::BT709, |
| RangeID::LIMITED); |
| } |
| |
| 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 && |
| memcmp(custom_primary_matrix_, other.custom_primary_matrix_, |
| sizeof(custom_primary_matrix_))) |
| return false; |
| if (transfer_ == TransferID::CUSTOM && |
| memcmp(custom_transfer_params_, other.custom_transfer_params_, |
| sizeof(custom_transfer_params_))) |
| return false; |
| return true; |
| } |
| |
| bool ColorSpace::IsHDR() const { |
| return transfer_ == TransferID::SMPTEST2084 || |
| transfer_ == TransferID::ARIB_STD_B67 || |
| transfer_ == TransferID::LINEAR_HDR; |
| } |
| |
| bool ColorSpace::operator!=(const ColorSpace& other) const { |
| return !(*this == other); |
| } |
| |
| 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 = |
| 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 (transfer_ == TransferID::CUSTOM) { |
| int transfer_result = |
| memcmp(custom_transfer_params_, other.custom_transfer_params_, |
| sizeof(custom_transfer_params_)); |
| if (transfer_result < 0) |
| return true; |
| if (transfer_result > 0) |
| return false; |
| } |
| return false; |
| } |
| |
| sk_sp<SkColorSpace> ColorSpace::ToSkColorSpace() const { |
| // If we got a specific SkColorSpace from the ICCProfile that this color space |
| // was created from, use that. |
| if (icc_profile_sk_color_space_) |
| return icc_profile_sk_color_space_; |
| |
| // Unspecified color spaces correspond to the null SkColorSpace. |
| if (primaries_ == PrimaryID::UNSPECIFIED || |
| transfer_ == TransferID::UNSPECIFIED) { |
| return nullptr; |
| } |
| |
| // Handle only full-range RGB spaces. |
| if (matrix_ != MatrixID::RGB) { |
| DLOG(ERROR) << "Not creating non-RGB SkColorSpace"; |
| return nullptr; |
| } |
| if (range_ != RangeID::FULL) { |
| DLOG(ERROR) << "Not creating non-full-range SkColorSpace"; |
| return nullptr; |
| } |
| |
| // Use the named SRGB and linear-SRGB instead of the generic constructors. |
| if (primaries_ == PrimaryID::BT709) { |
| if (transfer_ == TransferID::IEC61966_2_1) |
| return SkColorSpace::MakeSRGB(); |
| if (transfer_ == TransferID::LINEAR || transfer_ == TransferID::LINEAR_HDR) |
| return SkColorSpace::MakeSRGBLinear(); |
| } |
| |
| SkMatrix44 to_xyz_d50; |
| GetPrimaryMatrix(&to_xyz_d50); |
| |
| // Use the named sRGB and linear transfer functions. |
| if (transfer_ == TransferID::IEC61966_2_1) { |
| return SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, |
| to_xyz_d50); |
| } |
| if (transfer_ == TransferID::LINEAR || transfer_ == TransferID::LINEAR_HDR) { |
| return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, |
| to_xyz_d50); |
| } |
| |
| // Use the parametric transfer function if no other option is available. |
| SkColorSpaceTransferFn fn; |
| if (!GetTransferFunction(&fn)) { |
| DLOG(ERROR) << "Failed to parameterize transfer function for SkColorSpace"; |
| return nullptr; |
| } |
| return SkColorSpace::MakeRGB(fn, to_xyz_d50); |
| } |
| |
| void ColorSpace::GetPrimaryMatrix(SkMatrix44* to_XYZD50) const { |
| SkColorSpacePrimaries primaries = {0}; |
| switch (primaries_) { |
| case ColorSpace::PrimaryID::CUSTOM: |
| to_XYZD50->set3x3RowMajorf(custom_primary_matrix_); |
| return; |
| |
| case ColorSpace::PrimaryID::RESERVED0: |
| case ColorSpace::PrimaryID::RESERVED: |
| case ColorSpace::PrimaryID::UNSPECIFIED: |
| case ColorSpace::PrimaryID::UNKNOWN: |
| 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.) |
| primaries.fRX = 0.640f; |
| primaries.fRY = 0.330f; |
| primaries.fGX = 0.300f; |
| primaries.fGY = 0.600f; |
| primaries.fBX = 0.150f; |
| primaries.fBY = 0.060f; |
| primaries.fWX = 0.3127f; |
| primaries.fWY = 0.3290f; |
| break; |
| |
| case ColorSpace::PrimaryID::BT470M: |
| primaries.fRX = 0.67f; |
| primaries.fRY = 0.33f; |
| primaries.fGX = 0.21f; |
| primaries.fGY = 0.71f; |
| primaries.fBX = 0.14f; |
| primaries.fBY = 0.08f; |
| primaries.fWX = 0.31f; |
| primaries.fWY = 0.316f; |
| break; |
| |
| case ColorSpace::PrimaryID::BT470BG: |
| primaries.fRX = 0.64f; |
| primaries.fRY = 0.33f; |
| primaries.fGX = 0.29f; |
| primaries.fGY = 0.60f; |
| primaries.fBX = 0.15f; |
| primaries.fBY = 0.06f; |
| primaries.fWX = 0.3127f; |
| primaries.fWY = 0.3290f; |
| break; |
| |
| case ColorSpace::PrimaryID::SMPTE170M: |
| case ColorSpace::PrimaryID::SMPTE240M: |
| primaries.fRX = 0.630f; |
| primaries.fRY = 0.340f; |
| primaries.fGX = 0.310f; |
| primaries.fGY = 0.595f; |
| primaries.fBX = 0.155f; |
| primaries.fBY = 0.070f; |
| primaries.fWX = 0.3127f; |
| primaries.fWY = 0.3290f; |
| break; |
| |
| case ColorSpace::PrimaryID::FILM: |
| primaries.fRX = 0.681f; |
| primaries.fRY = 0.319f; |
| primaries.fGX = 0.243f; |
| primaries.fGY = 0.692f; |
| primaries.fBX = 0.145f; |
| primaries.fBY = 0.049f; |
| primaries.fWX = 0.310f; |
| primaries.fWY = 0.136f; |
| break; |
| |
| case ColorSpace::PrimaryID::BT2020: |
| primaries.fRX = 0.708f; |
| primaries.fRY = 0.292f; |
| primaries.fGX = 0.170f; |
| primaries.fGY = 0.797f; |
| primaries.fBX = 0.131f; |
| primaries.fBY = 0.046f; |
| primaries.fWX = 0.3127f; |
| primaries.fWY = 0.3290f; |
| break; |
| |
| case ColorSpace::PrimaryID::SMPTEST428_1: |
| primaries.fRX = 1.0f; |
| primaries.fRY = 0.0f; |
| primaries.fGX = 0.0f; |
| primaries.fGY = 1.0f; |
| primaries.fBX = 0.0f; |
| primaries.fBY = 0.0f; |
| primaries.fWX = 1.0f / 3.0f; |
| primaries.fWY = 1.0f / 3.0f; |
| break; |
| |
| case ColorSpace::PrimaryID::SMPTEST431_2: |
| primaries.fRX = 0.680f; |
| primaries.fRY = 0.320f; |
| primaries.fGX = 0.265f; |
| primaries.fGY = 0.690f; |
| primaries.fBX = 0.150f; |
| primaries.fBY = 0.060f; |
| primaries.fWX = 0.314f; |
| primaries.fWY = 0.351f; |
| break; |
| |
| case ColorSpace::PrimaryID::SMPTEST432_1: |
| primaries.fRX = 0.680f; |
| primaries.fRY = 0.320f; |
| primaries.fGX = 0.265f; |
| primaries.fGY = 0.690f; |
| primaries.fBX = 0.150f; |
| primaries.fBY = 0.060f; |
| primaries.fWX = 0.3127f; |
| primaries.fWY = 0.3290f; |
| break; |
| |
| case ColorSpace::PrimaryID::XYZ_D50: |
| primaries.fRX = 1.0f; |
| primaries.fRY = 0.0f; |
| primaries.fGX = 0.0f; |
| primaries.fGY = 1.0f; |
| primaries.fBX = 0.0f; |
| primaries.fBY = 0.0f; |
| primaries.fWX = 0.34567f; |
| primaries.fWY = 0.35850f; |
| break; |
| |
| case ColorSpace::PrimaryID::ADOBE_RGB: |
| primaries.fRX = 0.6400f; |
| primaries.fRY = 0.3300f; |
| primaries.fGX = 0.2100f; |
| primaries.fGY = 0.7100f; |
| primaries.fBX = 0.1500f; |
| primaries.fBY = 0.0600f; |
| primaries.fWX = 0.3127f; |
| primaries.fWY = 0.3290f; |
| break; |
| } |
| primaries.toXYZD50(to_XYZD50); |
| } |
| |
| bool ColorSpace::GetTransferFunction(SkColorSpaceTransferFn* fn) const { |
| // Default to F(x) = pow(x, 1) |
| fn->fA = 1; |
| fn->fB = 0; |
| fn->fC = 1; |
| fn->fD = 0; |
| fn->fE = 0; |
| fn->fF = 0; |
| fn->fG = 1; |
| |
| switch (transfer_) { |
| case ColorSpace::TransferID::CUSTOM: |
| fn->fA = custom_transfer_params_[0]; |
| fn->fB = custom_transfer_params_[1]; |
| fn->fC = custom_transfer_params_[2]; |
| fn->fD = custom_transfer_params_[3]; |
| fn->fE = custom_transfer_params_[4]; |
| fn->fF = custom_transfer_params_[5]; |
| fn->fG = custom_transfer_params_[6]; |
| return true; |
| case ColorSpace::TransferID::LINEAR: |
| case ColorSpace::TransferID::LINEAR_HDR: |
| return true; |
| case ColorSpace::TransferID::GAMMA22: |
| fn->fG = 2.2f; |
| return true; |
| case ColorSpace::TransferID::GAMMA24: |
| fn->fG = 2.4f; |
| return true; |
| case ColorSpace::TransferID::GAMMA28: |
| fn->fG = 2.8f; |
| return true; |
| case ColorSpace::TransferID::RESERVED0: |
| case ColorSpace::TransferID::RESERVED: |
| case ColorSpace::TransferID::UNSPECIFIED: |
| case ColorSpace::TransferID::UNKNOWN: |
| // All unknown values default to BT709 |
| case ColorSpace::TransferID::BT709: |
| case ColorSpace::TransferID::SMPTE170M: |
| case ColorSpace::TransferID::BT2020_10: |
| case ColorSpace::TransferID::BT2020_12: |
| fn->fA = 0.909672431050f; |
| fn->fB = 0.090327568950f; |
| fn->fC = 0.222222222222f; |
| fn->fD = 0.081242862158f; |
| fn->fG = 2.222222222222f; |
| return true; |
| case ColorSpace::TransferID::SMPTE240M: |
| fn->fA = 0.899626676224f; |
| fn->fB = 0.100373323776f; |
| fn->fC = 0.250000000000f; |
| fn->fD = 0.091286342118f; |
| fn->fG = 2.222222222222f; |
| return true; |
| case ColorSpace::TransferID::IEC61966_2_1: |
| fn->fA = 0.947867345704f; |
| fn->fB = 0.052132654296f; |
| fn->fC = 0.077399380805f; |
| fn->fD = 0.040449937172f; |
| fn->fG = 2.400000000000f; |
| return true; |
| case ColorSpace::TransferID::SMPTEST428_1: |
| fn->fA = 0.225615407568f; |
| fn->fE = -1.091041666667f; |
| fn->fG = 2.600000000000f; |
| return true; |
| case ColorSpace::TransferID::IEC61966_2_4: |
| // This could potentially be represented the same as IEC61966_2_1, but |
| // it handles negative values differently. |
| break; |
| case ColorSpace::TransferID::ARIB_STD_B67: |
| case ColorSpace::TransferID::BT1361_ECG: |
| case ColorSpace::TransferID::LOG: |
| case ColorSpace::TransferID::LOG_SQRT: |
| case ColorSpace::TransferID::SMPTEST2084: |
| case ColorSpace::TransferID::SMPTEST2084_NON_HDR: |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool ColorSpace::GetInverseTransferFunction(SkColorSpaceTransferFn* fn) const { |
| if (!GetTransferFunction(fn)) |
| return false; |
| *fn = InvertTransferFn(*fn); |
| return true; |
| } |
| |
| void ColorSpace::GetTransferMatrix(SkMatrix44* matrix) const { |
| float Kr = 0; |
| float Kb = 0; |
| switch (matrix_) { |
| case ColorSpace::MatrixID::RGB: |
| matrix->setIdentity(); |
| return; |
| |
| case ColorSpace::MatrixID::BT709: |
| case ColorSpace::MatrixID::UNSPECIFIED: |
| case ColorSpace::MatrixID::RESERVED: |
| case ColorSpace::MatrixID::UNKNOWN: |
| 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.5f, // Y |
| -0.25f, 0.5f, -0.25f, 0.5f, // Cg |
| 0.5f, 0.0f, -0.5f, 0.0f, // Co |
| 0.0f, 0.0f, 0.0f, 1.0f |
| }; |
| matrix->setRowMajorf(data); |
| return; |
| } |
| |
| // BT2020_CL is a special case. |
| // Basically we return a matrix that transforms RGB values |
| // to RYB values. (We replace the green component with the |
| // the luminance.) Later steps will compute the Cb & Cr values. |
| case ColorSpace::MatrixID::BT2020_CL: { |
| Kr = 0.2627f; |
| Kb = 0.0593f; |
| float data[16] = { |
| 1.0f, 0.0f, 0.0f, 0.0f, // R |
| Kr, 1.0f - Kr - Kb, Kb, 0.0f, // Y |
| 0.0f, 0.0f, 1.0f, 0.0f, // B |
| 0.0f, 0.0f, 0.0f, 1.0f |
| }; |
| matrix->setRowMajorf(data); |
| return; |
| } |
| |
| case ColorSpace::MatrixID::BT2020_NCL: |
| Kr = 0.2627f; |
| Kb = 0.0593f; |
| break; |
| |
| case ColorSpace::MatrixID::YDZDX: { |
| 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, |
| }; |
| matrix->setRowMajorf(data); |
| return; |
| } |
| } |
| float Kg = 1.0f - Kr - Kb; |
| float u_m = 0.5f / (1.0f - Kb); |
| float v_m = 0.5f / (1.0f - Kr); |
| 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, |
| }; |
| matrix->setRowMajorf(data); |
| } |
| |
| void ColorSpace::GetRangeAdjustMatrix(SkMatrix44* matrix) const { |
| switch (range_) { |
| case RangeID::FULL: |
| case RangeID::UNSPECIFIED: |
| matrix->setIdentity(); |
| return; |
| |
| case RangeID::DERIVED: |
| case RangeID::LIMITED: |
| break; |
| } |
| switch (matrix_) { |
| case MatrixID::RGB: |
| case MatrixID::YCOCG: |
| matrix->setScale(255.0f/219.0f, 255.0f/219.0f, 255.0f/219.0f); |
| matrix->postTranslate(-16.0f/219.0f, -16.0f/219.0f, -16.0f/219.0f); |
| break; |
| |
| case MatrixID::BT709: |
| case MatrixID::UNSPECIFIED: |
| case MatrixID::RESERVED: |
| case MatrixID::FCC: |
| case MatrixID::BT470BG: |
| case MatrixID::SMPTE170M: |
| case MatrixID::SMPTE240M: |
| case MatrixID::BT2020_NCL: |
| case MatrixID::BT2020_CL: |
| case MatrixID::YDZDX: |
| case MatrixID::UNKNOWN: |
| matrix->setScale(255.0f/219.0f, 255.0f/224.0f, 255.0f/224.0f); |
| matrix->postTranslate(-16.0f/219.0f, -15.5f/224.0f, -15.5f/224.0f); |
| break; |
| } |
| } |
| |
| } // namespace gfx |