Commit 4b427113 authored by Christopher Cameron's avatar Christopher Cameron Committed by Commit Bot

Make SkColorSpace to gfx::ColorSpace preserve SDR white level

Bug: 1076568
Change-Id: I795e2d1a992a3f25cf6255a8ed263c26a12f9794
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2289545
Commit-Queue: ccameron <ccameron@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#786894}
parent b6c1f1c5
......@@ -40,6 +40,35 @@ static bool FloatsEqualWithinTolerance(const float* a,
return true;
}
skcms_TransferFunction GetPQSkTransferFunction(float sdr_white_level) {
// Note that SkColorSpace doesn't have the notion of an unspecified SDR white
// level.
if (sdr_white_level == 0.f)
sdr_white_level = ColorSpace::kDefaultSDRWhiteLevel;
// The generic PQ transfer function produces normalized luminance values i.e.
// the range 0-1 represents 0-10000 nits for the reference display, but we
// want to map 1.0 to |sdr_white_level| nits so we need to scale accordingly.
const double w = 10000. / sdr_white_level;
// Distribute scaling factor W by scaling A and B with X ^ (1/F):
// ((A + Bx^C) / (D + Ex^C))^F * W = ((A + Bx^C) / (D + Ex^C) * W^(1/F))^F
// See https://crbug.com/1058580#c32 for discussion.
skcms_TransferFunction fn = SkNamedTransferFn::kPQ;
const double ws = pow(w, 1. / fn.f);
fn.a = ws * fn.a;
fn.b = ws * fn.b;
return fn;
}
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
......@@ -80,8 +109,9 @@ ColorSpace::ColorSpace(const SkColorSpace& sk_color_space)
SetCustomTransferFunction(fn);
} else if (transfer_eq(fn, SkNamedTransferFn::kHLG)) {
transfer_ = TransferID::ARIB_STD_B67;
} else if (transfer_eq(fn, SkNamedTransferFn::kPQ)) {
} else if (fn.g == SkNamedTransferFn::kPQ.g) {
transfer_ = TransferID::SMPTEST2084;
transfer_params_[0] = GetSDRWhiteLevelFromPQSkTransferFunction(fn);
} else {
// Construct an invalid result: Unable to extract necessary parameters
return;
......@@ -613,7 +643,7 @@ sk_sp<SkColorSpace> ColorSpace::ToSkColorSpace() const {
transfer_fn = SkNamedTransferFn::kHLG;
break;
case TransferID::SMPTEST2084:
GetPQTransferFunction(&transfer_fn);
transfer_fn = GetPQSkTransferFunction(transfer_params_[0]);
break;
default:
if (!GetTransferFunction(&transfer_fn)) {
......@@ -945,23 +975,6 @@ bool ColorSpace::GetTransferFunction(skcms_TransferFunction* fn) const {
}
}
void ColorSpace::GetPQTransferFunction(skcms_TransferFunction* fn) const {
DCHECK_EQ(transfer_, TransferID::SMPTEST2084);
const float sdr_white_level =
transfer_params_[0] == 0.0f ? kDefaultSDRWhiteLevel : transfer_params_[0];
// The generic PQ transfer function produces normalized luminance values i.e.
// the range 0-1 represents 0-10000 nits for the reference display, but we
// want to map 1.0 to |sdr_white_level| nits so we need to scale accordingly.
const float w = 10000.0f / sdr_white_level;
// Distribute scaling factor W by scaling A and B with X ^ (1/F):
// ((A + Bx^C) / (D + Ex^C))^F * W = ((A + Bx^C) / (D + Ex^C) * W^(1/F))^F
// See https://crbug.com/1058580#c32 for discussion.
*fn = SkNamedTransferFn::kPQ;
const float ws = powf(w, 1 / fn->f);
fn->a = ws * fn->a;
fn->b = ws * fn->b;
}
bool ColorSpace::GetInverseTransferFunction(skcms_TransferFunction* fn) const {
if (!GetTransferFunction(fn))
return false;
......
......@@ -362,8 +362,6 @@ class COLOR_SPACE_EXPORT ColorSpace {
static bool GetTransferFunction(TransferID, skcms_TransferFunction* fn);
static size_t TransferParamCount(TransferID);
void GetPQTransferFunction(skcms_TransferFunction* fn) const;
void SetCustomTransferFunction(const skcms_TransferFunction& fn);
void SetCustomPrimaries(const skcms_Matrix3x3& to_XYZD50);
......
......@@ -244,6 +244,34 @@ TEST(ColorSpace, ConversionToAndFromSkColorSpace) {
}
}
TEST(ColorSpace, PQToSkColorSpace) {
ColorSpace color_space;
ColorSpace roundtrip_color_space;
float roundtrip_sdr_white_level;
const float kEpsilon = 1.e-5f;
// We expect that when a white point is specified, the conversion from
// ColorSpace -> SkColorSpace -> ColorSpace be the identity. Because of
// rounding error, this will not quite be the case.
color_space = ColorSpace::CreateHDR10(50.f);
roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace());
EXPECT_TRUE(
roundtrip_color_space.GetPQSDRWhiteLevel(&roundtrip_sdr_white_level));
EXPECT_NEAR(50.f, roundtrip_sdr_white_level, kEpsilon);
EXPECT_EQ(ColorSpace::TransferID::SMPTEST2084,
roundtrip_color_space.GetTransferID());
// When no white level is specified, we should get an SkColorSpace that
// specifies the default white level. Of note is that in the roundtrip, the
// value of kDefaultSDRWhiteLevel gets baked in.
color_space = ColorSpace::CreateHDR10();
roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace());
EXPECT_TRUE(
roundtrip_color_space.GetPQSDRWhiteLevel(&roundtrip_sdr_white_level));
EXPECT_NEAR(ColorSpace::kDefaultSDRWhiteLevel, roundtrip_sdr_white_level,
kEpsilon);
}
TEST(ColorSpace, MixedInvalid) {
ColorSpace color_space;
color_space = color_space.GetWithMatrixAndRange(ColorSpace::MatrixID::INVALID,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment