blob: 4e040de23c44a940fdba62058a2161d715e83669 [file] [log] [blame]
// 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 <iomanip>
#include <limits>
#include <map>
#include <sstream>
#include "base/atomic_sequence_num.h"
#include "base/lazy_instance.h"
#include "base/synchronization/lock.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkData.h"
#include "third_party/skia/include/core/SkICC.h"
#include "ui/gfx/icc_profile.h"
#include "ui/gfx/skia_color_space_util.h"
namespace gfx {
namespace {
base::AtomicSequenceNumber g_color_space_id;
static bool IsAlmostZero(float value) {
return std::abs(value) < std::numeric_limits<float>::epsilon();
}
static bool FloatsEqualWithinTolerance(const float* a,
const float* b,
int n,
float tol) {
for (int i = 0; i < n; ++i) {
if (std::abs(a[i] - b[i]) > tol) {
return false;
}
}
return true;
}
} // namespace
// static
int ColorSpace::kInvalidId = -1;
ColorSpace::ColorSpace(PrimaryID primaries,
TransferID transfer)
: primaries_(primaries),
transfer_(transfer),
matrix_(MatrixID::RGB),
range_(RangeID::FULL) {}
ColorSpace::ColorSpace(PrimaryID primaries,
const skcms_TransferFunction& fn,
MatrixID matrix,
RangeID range)
: primaries_(primaries), matrix_(matrix), range_(range) {
SetCustomTransferFunction(fn);
}
ColorSpace::ColorSpace(const SkColorSpace& sk_color_space)
: ColorSpace(PrimaryID::INVALID,
TransferID::INVALID,
MatrixID::RGB,
RangeID::FULL) {
skcms_TransferFunction fn;
skcms_Matrix3x3 to_XYZD50;
if (!sk_color_space.isNumericalTransferFn(&fn) ||
!sk_color_space.toXYZD50(&to_XYZD50)) {
// Construct an invalid result: Unable to extract necessary parameters
return;
}
SetCustomTransferFunction(fn);
SetCustomPrimaries(to_XYZD50);
}
bool ColorSpace::IsValid() const {
return primaries_ != PrimaryID::INVALID && transfer_ != TransferID::INVALID &&
matrix_ != MatrixID::INVALID && range_ != RangeID::INVALID;
}
// 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);
result.SetCustomPrimaries(to_XYZD50);
result.SetCustomTransferFunction(fn);
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::SMPTEST432_1,
PrimaryID::XYZ_D50,
PrimaryID::ADOBE_RGB,
PrimaryID::APPLE_GENERIC_RGB,
PrimaryID::WIDE_GAMUT_COLOR_SPIN,
};
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;
}
}
memcpy(custom_primary_matrix_, &to_XYZD50, 9 * sizeof(float));
primaries_ = PrimaryID::CUSTOM;
}
void ColorSpace::SetCustomTransferFunction(const skcms_TransferFunction& fn) {
// 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 IEC61966_2_1).
const TransferID kIDsToCheck[] = {
TransferID::IEC61966_2_1, TransferID::LINEAR,
TransferID::GAMMA18, TransferID::GAMMA22,
TransferID::GAMMA24, TransferID::GAMMA28,
TransferID::SMPTE240M, TransferID::BT709_APPLE,
TransferID::SMPTEST428_1,
};
for (TransferID id : kIDsToCheck) {
skcms_TransferFunction id_fn;
GetTransferFunction(id, &id_fn);
if (FloatsEqualWithinTolerance(&fn.g, &id_fn.g, 7, 0.001f)) {
transfer_ = id;
return;
}
}
custom_transfer_params_[0] = fn.a;
custom_transfer_params_[1] = fn.b;
custom_transfer_params_[2] = fn.c;
custom_transfer_params_[3] = fn.d;
custom_transfer_params_[4] = fn.e;
custom_transfer_params_[5] = fn.f;
custom_transfer_params_[6] = fn.g;
transfer_ = TransferID::CUSTOM;
}
// static
int ColorSpace::GetNextId() {
return g_color_space_id.GetNext();
}
bool ColorSpace::operator==(const ColorSpace& other) const {
if (primaries_ != other.primaries_ || transfer_ != other.transfer_ ||
matrix_ != other.matrix_ || range_ != other.range_ ||
icc_profile_id_ != other.icc_profile_id_)
return false;
if (primaries_ == PrimaryID::CUSTOM) {
if (memcmp(custom_primary_matrix_, other.custom_primary_matrix_,
sizeof(custom_primary_matrix_))) {
return false;
}
}
if (transfer_ == TransferID::CUSTOM) {
if (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 ||
transfer_ == TransferID::IEC61966_2_1_HDR;
}
bool ColorSpace::FullRangeEncodedValues() const {
return transfer_ == TransferID::LINEAR_HDR ||
transfer_ == TransferID::IEC61966_2_1_HDR ||
transfer_ == TransferID::BT1361_ECG ||
transfer_ == TransferID::IEC61966_2_4;
}
bool ColorSpace::IsParametricAccurate() const {
return icc_profile_id_ == 0;
}
ColorSpace ColorSpace::GetParametricApproximation() const {
ColorSpace result = *this;
result.icc_profile_id_ = 0;
return result;
}
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 (icc_profile_id_ < other.icc_profile_id_)
return true;
if (icc_profile_id_ > other.icc_profile_id_)
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;
}
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 ^= params[4];
result ^= params[8];
}
if (transfer_ == TransferID::CUSTOM) {
const uint32_t* params =
reinterpret_cast<const uint32_t*>(custom_transfer_params_);
result ^= params[3];
result ^= 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, SMPTEST432_1)
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)
case PrimaryID::CUSTOM:
// |custom_primary_matrix_| is in row-major order.
const float sum_R = custom_primary_matrix_[0] +
custom_primary_matrix_[3] + custom_primary_matrix_[6];
const float sum_G = custom_primary_matrix_[1] +
custom_primary_matrix_[4] + custom_primary_matrix_[7];
const float sum_B = custom_primary_matrix_[2] +
custom_primary_matrix_[5] + custom_primary_matrix_[8];
if (IsAlmostZero(sum_R) || IsAlmostZero(sum_G) || IsAlmostZero(sum_B))
break;
ss << "{primaries_d50_referred: [[" << (custom_primary_matrix_[0] / sum_R)
<< ", " << (custom_primary_matrix_[3] / sum_R) << "], "
<< " [" << (custom_primary_matrix_[1] / sum_G) << ", "
<< (custom_primary_matrix_[4] / sum_G) << "], "
<< " [" << (custom_primary_matrix_[2] / sum_B) << ", "
<< (custom_primary_matrix_[5] / sum_B) << "]]";
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, IEC61966_2_1)
PRINT_ENUM_CASE(TransferID, BT2020_10)
PRINT_ENUM_CASE(TransferID, BT2020_12)
PRINT_ENUM_CASE(TransferID, SMPTEST2084)
PRINT_ENUM_CASE(TransferID, SMPTEST428_1)
PRINT_ENUM_CASE(TransferID, ARIB_STD_B67)
PRINT_ENUM_CASE(TransferID, SMPTEST2084_NON_HDR)
PRINT_ENUM_CASE(TransferID, IEC61966_2_1_HDR)
PRINT_ENUM_CASE(TransferID, LINEAR_HDR)
case TransferID::CUSTOM: {
skcms_TransferFunction fn;
GetTransferFunction(&fn);
ss << fn.c << "*x + " << fn.f << " if x < " << fn.d << " else (";
ss << fn.a << "*x + " << fn.b << ")**" << fn.g << " + " << fn.e;
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, BT2020_CL)
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)
}
if (icc_profile_id_) {
ss << ", icc_profile_id:" << icc_profile_id_;
}
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;
}
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) {
to_XYZD50.vals[row][col] *= factor;
}
}
result.SetCustomPrimaries(to_XYZD50);
return result;
}
ColorSpace ColorSpace::GetRasterColorSpace() const {
// Rasterization can only be done into parametric color spaces.
if (icc_profile_id_)
return GetParametricApproximation();
// Rasterization doesn't support more than 8 bit unorm values. If the output
// space has an extended range, use Display P3 for the rasterization space,
// to get a somewhat wider color gamut.
if (HasExtendedSkTransferFn())
return CreateDisplayP3D65();
return *this;
}
ColorSpace ColorSpace::GetBlendingColorSpace() const {
// HDR output on windows requires output have a linear transfer function.
// Linear blending breaks the web, so use extended-sRGB for blending.
if (transfer_ == TransferID::LINEAR_HDR)
return CreateExtendedSRGB();
return *this;
}
sk_sp<SkColorSpace> ColorSpace::ToSkColorSpace() const {
// Unspecified color spaces correspond to the null SkColorSpace.
if (!IsValid())
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;
}
// If we got a specific SkColorSpace from the ICCProfile that this color space
// was created from, use that.
if (icc_profile_id_) {
sk_sp<SkColorSpace> result =
ICCProfile::GetSkColorSpaceFromId(icc_profile_id_);
if (result)
return result;
// This will fall through to creating a parametric approximation. The
// result will be that we will use an inaccurate transfer function.
DLOG(ERROR) << "Unable to find ICCProfile for SkColorSpace.";
}
// 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();
}
skcms_TransferFunction transfer_fn = SkNamedTransferFn::kSRGB;
switch (transfer_) {
case TransferID::IEC61966_2_1:
break;
case TransferID::LINEAR:
case TransferID::LINEAR_HDR:
transfer_fn = SkNamedTransferFn::kLinear;
break;
default:
if (!GetTransferFunction(&transfer_fn)) {
DLOG(ERROR) << "Failed to 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::SMPTEST432_1:
gamut = SkNamedGamut::kDCIP3;
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;
}
// static
void ColorSpace::GetPrimaryMatrix(PrimaryID primary_id,
skcms_Matrix3x3* to_XYZD50) {
SkColorSpacePrimaries primaries = {0};
switch (primary_id) {
case ColorSpace::PrimaryID::CUSTOM:
case ColorSpace::PrimaryID::INVALID:
*to_XYZD50 = SkNamedGamut::kXYZ; // Identity
return;
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::APPLE_GENERIC_RGB:
primaries.fRX = 0.63002f;
primaries.fRY = 0.34000f;
primaries.fGX = 0.29505f;
primaries.fGY = 0.60498f;
primaries.fBX = 0.15501f;
primaries.fBY = 0.07701f;
primaries.fWX = 0.3127f;
primaries.fWY = 0.3290f;
break;
case ColorSpace::PrimaryID::WIDE_GAMUT_COLOR_SPIN:
primaries.fRX = 0.01f;
primaries.fRY = 0.98f;
primaries.fGX = 0.01f;
primaries.fGY = 0.01f;
primaries.fBX = 0.98f;
primaries.fBY = 0.01f;
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);
}
void ColorSpace::GetPrimaryMatrix(skcms_Matrix3x3* to_XYZD50) const {
if (primaries_ == PrimaryID::CUSTOM) {
memcpy(to_XYZD50, custom_primary_matrix_, 9 * sizeof(float));
} else {
GetPrimaryMatrix(primaries_, to_XYZD50);
}
}
void ColorSpace::GetPrimaryMatrix(SkMatrix44* to_XYZD50) const {
skcms_Matrix3x3 toXYZ_3x3;
GetPrimaryMatrix(&toXYZ_3x3);
to_XYZD50->set3x3RowMajorf(&toXYZ_3x3.vals[0][0]);
}
// 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:
return true;
case ColorSpace::TransferID::GAMMA18:
fn->g = 1.801f;
return true;
case ColorSpace::TransferID::GAMMA22:
fn->g = 2.2f;
return true;
case ColorSpace::TransferID::GAMMA24:
fn->g = 2.4f;
return true;
case ColorSpace::TransferID::GAMMA28:
fn->g = 2.8f;
return true;
case ColorSpace::TransferID::SMPTE240M:
fn->a = 0.899626676224f;
fn->b = 0.100373323776f;
fn->c = 0.250000000000f;
fn->d = 0.091286342118f;
fn->g = 2.222222222222f;
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 funciton as sRGB,
// which will allow more optimization, and will more closely match other
// media players.
case ColorSpace::TransferID::IEC61966_2_1:
case ColorSpace::TransferID::IEC61966_2_1_HDR:
fn->a = 0.947867345704f;
fn->b = 0.052132654296f;
fn->c = 0.077399380805f;
fn->d = 0.040449937172f;
fn->g = 2.400000000000f;
return true;
case ColorSpace::TransferID::BT709_APPLE:
fn->g = 1.961000000000f;
return true;
case ColorSpace::TransferID::SMPTEST428_1:
fn->a = 0.225615407568f;
fn->e = -1.091041666667f;
fn->g = 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:
case ColorSpace::TransferID::CUSTOM:
case ColorSpace::TransferID::INVALID:
break;
}
return false;
}
bool ColorSpace::GetTransferFunction(skcms_TransferFunction* fn) const {
if (transfer_ == TransferID::CUSTOM) {
fn->a = custom_transfer_params_[0];
fn->b = custom_transfer_params_[1];
fn->c = custom_transfer_params_[2];
fn->d = custom_transfer_params_[3];
fn->e = custom_transfer_params_[4];
fn->f = custom_transfer_params_[5];
fn->g = custom_transfer_params_[6];
return true;
} else {
return GetTransferFunction(transfer_, fn);
}
}
bool ColorSpace::GetInverseTransferFunction(skcms_TransferFunction* fn) const {
if (!GetTransferFunction(fn))
return false;
*fn = SkTransferFnInverse(*fn);
return true;
}
bool ColorSpace::HasExtendedSkTransferFn() const {
return transfer_ == TransferID::LINEAR_HDR ||
transfer_ == TransferID::IEC61966_2_1_HDR;
}
void ColorSpace::GetTransferMatrix(SkMatrix44* matrix) const {
float Kr = 0;
float Kb = 0;
switch (matrix_) {
case ColorSpace::MatrixID::RGB:
case ColorSpace::MatrixID::INVALID:
matrix->setIdentity();
return;
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.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 RYB values
// to YUV values. (Note that the green component have been replaced
// with the luminance.)
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;
}
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};
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::INVALID:
matrix->setIdentity();
return;
case RangeID::DERIVED:
case RangeID::LIMITED:
break;
}
switch (matrix_) {
case MatrixID::RGB:
case MatrixID::GBR:
case MatrixID::INVALID:
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::FCC:
case MatrixID::BT470BG:
case MatrixID::SMPTE170M:
case MatrixID::SMPTE240M:
case MatrixID::BT2020_NCL:
case MatrixID::BT2020_CL:
case MatrixID::YDZDX:
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;
}
}
bool ColorSpace::ToSkYUVColorSpace(SkYUVColorSpace* out) const {
if (range_ == RangeID::FULL) {
*out = kJPEG_SkYUVColorSpace;
return true;
}
switch (matrix_) {
case MatrixID::BT709:
*out = kRec709_SkYUVColorSpace;
return true;
case MatrixID::BT470BG:
case MatrixID::SMPTE170M:
case MatrixID::SMPTE240M:
*out = kRec601_SkYUVColorSpace;
return true;
default:
break;
}
return false;
}
std::ostream& operator<<(std::ostream& out, const ColorSpace& color_space) {
return out << color_space.ToString();
}
} // namespace gfx