blob: a20f011a9a11fcfb9491ae9086446a4f64f8dcb7 [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 <map>
#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"
#include "ui/gfx/transform.h"
namespace gfx {
// static
ColorSpace ColorSpace::CreateVideo(int video_primary,
int video_transfer,
int video_matrix,
RangeID range_id) {
// TODO(hubbe): Use more context to decide how to handle UNSPECIFIED values.
ColorSpace result;
switch (video_primary) {
default:
case 0: // RESERVED0
case 1: // BT709
case 2: // UNSPECIFIED
case 3: // RESERVED
result.primaries_ = PrimaryID::BT709;
break;
case 4: // BT470M
result.primaries_ = PrimaryID::BT470M;
break;
case 5: // BT470BG
result.primaries_ = PrimaryID::BT470BG;
break;
case 6: // SMPTE170M
result.primaries_ = PrimaryID::SMPTE170M;
break;
case 7: // SMPTE240M
result.primaries_ = PrimaryID::SMPTE240M;
break;
case 8: // FILM
result.primaries_ = PrimaryID::FILM;
break;
case 9: // BT2020
result.primaries_ = PrimaryID::BT2020;
break;
case 10: // SMPTEST428_1
result.primaries_ = PrimaryID::SMPTEST428_1;
break;
case 11: // SMPTEST431_2
result.primaries_ = PrimaryID::SMPTEST431_2;
break;
case 12: // SMPTEST432_1
result.primaries_ = PrimaryID::SMPTEST432_1;
break;
}
switch (video_transfer) {
default:
case 0: // RESERVED0
case 1: // BT709
case 2: // UNSPECIFIED
case 3: // RESERVED
result.transfer_ = TransferID::BT709;
break;
case 4: // GAMMA22
result.transfer_ = TransferID::GAMMA22;
break;
case 5: // GAMMA28
result.transfer_ = TransferID::GAMMA28;
break;
case 6: // SMPTE170M
result.transfer_ = TransferID::SMPTE170M;
break;
case 7: // SMPTE240M
result.transfer_ = TransferID::SMPTE240M;
break;
case 8: // LINEAR
result.transfer_ = TransferID::LINEAR;
break;
case 9: // LOG
result.transfer_ = TransferID::LOG;
break;
case 10: // LOG_SQRT
result.transfer_ = TransferID::LOG_SQRT;
break;
case 11: // IEC61966_2_4
result.transfer_ = TransferID::IEC61966_2_4;
break;
case 12: // BT1361_ECG
result.transfer_ = TransferID::BT1361_ECG;
break;
case 13: // IEC61966_2_1
result.transfer_ = TransferID::IEC61966_2_1;
break;
case 14: // BT2020_10
result.transfer_ = TransferID::BT2020_10;
break;
case 15: // BT2020_12
result.transfer_ = TransferID::BT2020_12;
break;
case 16: // SMPTEST2084
result.transfer_ = TransferID::SMPTEST2084;
break;
case 17: // SMPTEST428_1
result.transfer_ = TransferID::SMPTEST428_1;
break;
case 18: // ARIB_STD_B67
result.transfer_ = TransferID::ARIB_STD_B67;
break;
}
switch (video_matrix) {
case 0: // RGB
result.matrix_ = MatrixID::RGB;
break;
default:
case 1: // BT709
case 2: // UNSPECIFIED
case 3: // RESERVED
result.matrix_ = MatrixID::BT709;
break;
case 4: // FCC
result.matrix_ = MatrixID::FCC;
break;
case 5: // BT470BG
result.matrix_ = MatrixID::BT470BG;
break;
case 6: // SMPTE170M
result.matrix_ = MatrixID::SMPTE170M;
break;
case 7: // SMPTE240M
result.matrix_ = MatrixID::SMPTE240M;
break;
case 8: // YCOCG
result.matrix_ = MatrixID::YCOCG;
break;
case 9: // BT2020_NCL
result.matrix_ = MatrixID::BT2020_NCL;
break;
case 10: // BT2020_CL
result.matrix_ = MatrixID::BT2020_CL;
break;
case 11: // YDZDX
result.matrix_ = MatrixID::YDZDX;
break;
}
result.range_ = range_id;
return result;
}
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(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 primaries_ != PrimaryID::INVALID && transfer_ != TransferID::INVALID &&
matrix_ != MatrixID::INVALID && range_ != RangeID::INVALID;
}
// static
ColorSpace ColorSpace::CreateSRGB() {
return ColorSpace(PrimaryID::BT709, TransferID::IEC61966_2_1, MatrixID::RGB,
RangeID::FULL);
}
// static
ColorSpace ColorSpace::CreateCustom(const SkMatrix44& to_XYZD50,
const SkColorSpaceTransferFn& fn) {
ColorSpace result(ColorSpace::PrimaryID::CUSTOM,
ColorSpace::TransferID::CUSTOM, ColorSpace::MatrixID::RGB,
ColorSpace::RangeID::FULL);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
result.custom_primary_matrix_[3 * row + col] = to_XYZD50.get(row, col);
}
}
result.custom_transfer_params_[0] = fn.fA;
result.custom_transfer_params_[1] = fn.fB;
result.custom_transfer_params_[2] = fn.fC;
result.custom_transfer_params_[3] = fn.fD;
result.custom_transfer_params_[4] = fn.fE;
result.custom_transfer_params_[5] = fn.fF;
result.custom_transfer_params_[6] = fn.fG;
// TODO(ccameron): Use enums for near matches to know color spaces.
return result;
}
// 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 (!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;
}
// 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);
}
bool ColorSpace::GetICCProfile(ICCProfile* icc_profile) const {
if (!IsValid())
return false;
// If this was created from an ICC profile, retrieve that exact profile.
ICCProfile result;
if (ICCProfile::FromId(icc_profile_id_, false, icc_profile))
return true;
// Otherwise, construct an ICC profile based on the best approximated
// primaries and matrix.
SkMatrix44 to_XYZD50_matrix;
GetPrimaryMatrix(&to_XYZD50_matrix);
SkColorSpaceTransferFn fn;
if (!GetTransferFunction(&fn)) {
DLOG(ERROR) << "Failed to get ColorSpace transfer function for ICCProfile.";
return false;
}
sk_sp<SkData> data = SkICC::WriteToICC(fn, to_XYZD50_matrix);
if (!data) {
DLOG(ERROR) << "Failed to create SkICC.";
return false;
}
*icc_profile = ICCProfile::FromData(data->data(), data->size());
DCHECK(icc_profile->IsValid());
return true;
}
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::INVALID:
to_XYZD50->setIdentity();
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::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::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:
case ColorSpace::TransferID::INVALID:
break;
}
return false;
}
bool ColorSpace::GetInverseTransferFunction(SkColorSpaceTransferFn* fn) const {
if (!GetTransferFunction(fn))
return false;
*fn = SkTransferFnInverse(*fn);
return true;
}
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 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::INVALID:
matrix->setIdentity();
return;
case RangeID::DERIVED:
case RangeID::LIMITED:
break;
}
switch (matrix_) {
case MatrixID::RGB:
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;
}
}
} // namespace gfx