Skip to content

Commit

Permalink
Add DisplayP3, Rec2020 and AdobeRGB to XYZD50 color conversion
Browse files Browse the repository at this point in the history
This CL is a part of a series of CL of color conversions needed for
CSS Color 5 implementation of color-mix.

This CL adds DisplayP3, Rec2020 and AdobeRGB conversions to XYZD50
space.
This CL also adds the tests.

Follow up CLs will use these methods to create color-mix conversions.

Bug: 1092638, 1358565
Change-Id: I35641936771cec58b822fff28b8852527ff76058
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3931783
Commit-Queue: ccameron chromium <ccameron@chromium.org>
Auto-Submit: Juanmi Huertas <juanmihd@chromium.org>
Reviewed-by: ccameron chromium <ccameron@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1054798}
  • Loading branch information
Juanmihd authored and Chromium LUCI CQ committed Oct 4, 2022
1 parent 4d34327 commit b6fe1f4
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 33 deletions.
84 changes: 78 additions & 6 deletions ui/gfx/color_conversions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,40 @@ skcms_Matrix3x3* getXYDZ50toXYZD65matrix() {
return &adapt_d50_to_d65;
}

skcms_Matrix3x3* getXYZD50tosSRGBLinearMatrix() {
skcms_Matrix3x3* getXYZD50toSRGBLinearMatrix() {
static skcms_Matrix3x3 xyzd50_to_srgb_linear;
skcms_Matrix3x3_invert(&SkNamedGamut::kSRGB, &xyzd50_to_srgb_linear);
return &xyzd50_to_srgb_linear;
}

skcms_Matrix3x3* getkXYZD65tosRGBMatrix() {
static skcms_Matrix3x3 adapt_XYZD65_to_srgb = skcms_Matrix3x3_concat(
getXYZD50toSRGBLinearMatrix(), getXYDZ65toXYZD50matrix());
return &adapt_XYZD65_to_srgb;
}

skcms_Matrix3x3* getProPhotoRGBtoXYZD50Matrix() {
static skcms_Matrix3x3 lin_proPhoto_to_XYZ_D50;
SkNamedPrimariesExt::kProPhotoRGB.toXYZD50(&lin_proPhoto_to_XYZ_D50);
return &lin_proPhoto_to_XYZ_D50;
}

skcms_Matrix3x3* getkXYZD65tosRGBMatrix() {
static skcms_Matrix3x3 adapt_XYZD65_to_srgb = skcms_Matrix3x3_concat(
getXYZD50tosSRGBLinearMatrix(), getXYDZ65toXYZD50matrix());
return &adapt_XYZD65_to_srgb;
skcms_Matrix3x3* getXYZD50toDisplayP3Matrix() {
static skcms_Matrix3x3 xyzd50_to_DisplayP3;
skcms_Matrix3x3_invert(&SkNamedGamut::kDisplayP3, &xyzd50_to_DisplayP3);
return &xyzd50_to_DisplayP3;
}

skcms_Matrix3x3* getXYZD50toAdobeRGBMatrix() {
static skcms_Matrix3x3 xyzd50_to_kAdobeRGB;
skcms_Matrix3x3_invert(&SkNamedGamut::kAdobeRGB, &xyzd50_to_kAdobeRGB);
return &xyzd50_to_kAdobeRGB;
}

skcms_Matrix3x3* getXYZD50toRec2020Matrix() {
static skcms_Matrix3x3 xyzd50_to_Rec2020;
skcms_Matrix3x3_invert(&SkNamedGamut::kRec2020, &xyzd50_to_Rec2020);
return &xyzd50_to_Rec2020;
}

float LabInverseTransferFunction(float t) {
Expand Down Expand Up @@ -111,6 +129,21 @@ std::tuple<float, float, float> ApplyTransferFnAdobeRGB(float r,
skcms_TransferFunction_eval(&SkNamedTransferFn::k2Dot2, b));
}

skcms_TransferFunction* getAdobeRGBInverseTrfn() {
static skcms_TransferFunction AdobeRGB_inverse;
skcms_TransferFunction_invert(&SkNamedTransferFn::k2Dot2, &AdobeRGB_inverse);
return &AdobeRGB_inverse;
}

std::tuple<float, float, float> ApplyInverseTransferFnAdobeRGB(float r,
float g,
float b) {
return std::make_tuple(
skcms_TransferFunction_eval(getAdobeRGBInverseTrfn(), r),
skcms_TransferFunction_eval(getAdobeRGBInverseTrfn(), g),
skcms_TransferFunction_eval(getAdobeRGBInverseTrfn(), b));
}

std::tuple<float, float, float> ApplyTransferFnRec2020(float r,
float g,
float b) {
Expand All @@ -119,6 +152,21 @@ std::tuple<float, float, float> ApplyTransferFnRec2020(float r,
skcms_TransferFunction_eval(&SkNamedTransferFn::kRec2020, g),
skcms_TransferFunction_eval(&SkNamedTransferFn::kRec2020, b));
}

skcms_TransferFunction* getRec2020nverseTrfn() {
static skcms_TransferFunction Rec2020_inverse;
skcms_TransferFunction_invert(&SkNamedTransferFn::kRec2020, &Rec2020_inverse);
return &Rec2020_inverse;
}

std::tuple<float, float, float> ApplyInverseTransferFnRec2020(float r,
float g,
float b) {
return std::make_tuple(
skcms_TransferFunction_eval(getRec2020nverseTrfn(), r),
skcms_TransferFunction_eval(getRec2020nverseTrfn(), g),
skcms_TransferFunction_eval(getRec2020nverseTrfn(), b));
}
} // namespace

std::tuple<float, float, float> LchToLab(float l,
Expand Down Expand Up @@ -202,7 +250,7 @@ std::tuple<float, float, float> XYZD65tosRGBLinear(float x, float y, float z) {
std::tuple<float, float, float> XYZD50tosRGBLinear(float x, float y, float z) {
skcms_Vector3 xyz_input{{x, y, z}};
skcms_Vector3 rgb_result =
skcms_Matrix3x3_apply(getXYZD50tosSRGBLinearMatrix(), &xyz_input);
skcms_Matrix3x3_apply(getXYZD50toSRGBLinearMatrix(), &xyz_input);
return std::make_tuple(rgb_result.vals[0], rgb_result.vals[1],
rgb_result.vals[2]);
}
Expand All @@ -225,6 +273,14 @@ std::tuple<float, float, float> DisplayP3ToXYZD50(float r, float g, float b) {
xyz_output.vals[2]);
}

std::tuple<float, float, float> XYZD50ToDisplayP3(float x, float y, float z) {
skcms_Vector3 xyz_input{{x, y, z}};
skcms_Vector3 rgb_output =
skcms_Matrix3x3_apply(getXYZD50toDisplayP3Matrix(), &xyz_input);
return ApplyInverseTransferFnsRGB(rgb_output.vals[0], rgb_output.vals[1],
rgb_output.vals[2]);
}

std::tuple<float, float, float> AdobeRGBToXYZD50(float r, float g, float b) {
auto [r_, g_, b_] = ApplyTransferFnAdobeRGB(r, g, b);
skcms_Vector3 rgb_input{{r_, g_, b_}};
Expand All @@ -234,6 +290,14 @@ std::tuple<float, float, float> AdobeRGBToXYZD50(float r, float g, float b) {
xyz_output.vals[2]);
}

std::tuple<float, float, float> XYZD50ToAdobeRGB(float x, float y, float z) {
skcms_Vector3 xyz_input{{x, y, z}};
skcms_Vector3 rgb_output =
skcms_Matrix3x3_apply(getXYZD50toAdobeRGBMatrix(), &xyz_input);
return ApplyInverseTransferFnAdobeRGB(rgb_output.vals[0], rgb_output.vals[1],
rgb_output.vals[2]);
}

std::tuple<float, float, float> Rec2020ToXYZD50(float r, float g, float b) {
auto [r_, g_, b_] = ApplyTransferFnRec2020(r, g, b);
skcms_Vector3 rgb_input{{r_, g_, b_}};
Expand All @@ -243,6 +307,14 @@ std::tuple<float, float, float> Rec2020ToXYZD50(float r, float g, float b) {
xyz_output.vals[2]);
}

std::tuple<float, float, float> XYZD50ToRec2020(float x, float y, float z) {
skcms_Vector3 xyz_input{{x, y, z}};
skcms_Vector3 rgb_output =
skcms_Matrix3x3_apply(getXYZD50toRec2020Matrix(), &xyz_input);
return ApplyInverseTransferFnRec2020(rgb_output.vals[0], rgb_output.vals[1],
rgb_output.vals[2]);
}

SkColor4f SRGBLinearToSkColor4f(float r, float g, float b, float alpha) {
auto [srgb_r, srgb_g, srgb_b] = ApplyInverseTransferFnsRGB(r, g, b);
return SkColor4f{srgb_r, srgb_g, srgb_b, alpha};
Expand Down
15 changes: 15 additions & 0 deletions ui/gfx/color_conversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ GFX_EXPORT std::tuple<float, float, float> DisplayP3ToXYZD50(float r,
float g,
float b);

// Method exposed for testing purposes.
GFX_EXPORT std::tuple<float, float, float> XYZD50ToDisplayP3(float x,
float y,
float z);

// Method exposed for testing purposes.
GFX_EXPORT std::tuple<float, float, float> ProPhotoToXYZD50(float r,
float g,
Expand All @@ -38,11 +43,21 @@ GFX_EXPORT std::tuple<float, float, float> AdobeRGBToXYZD50(float r,
float g,
float b);

// Method exposed for testing purposes.
GFX_EXPORT std::tuple<float, float, float> XYZD50ToAdobeRGB(float x,
float y,
float z);

// Method exposed for testing purposes.
GFX_EXPORT std::tuple<float, float, float> Rec2020ToXYZD50(float r,
float g,
float b);

// Method exposed for testing purposes.
GFX_EXPORT std::tuple<float, float, float> XYZD50ToRec2020(float x,
float y,
float z);

// Method exposed for testing purposes.
GFX_EXPORT std::tuple<float, float, float> XYZD50toD65(float x,
float y,
Expand Down
173 changes: 146 additions & 27 deletions ui/gfx/color_conversions_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,47 @@ TEST(ColorConversions, DisplayP3ToXYZD50) {
}
}

TEST(ColorConversions, XYZD50ToDisplayP3) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=purple&precision=4
ColorTest colors_tests[] = {
{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}, // black
{{0.9642956660812443f, 1.0000000361162846f, 0.8251045485672053f},
{0.9999999999999999f, 0.9999999999999997f,
0.9999999999999999f}}, // white
{{0.3851514688337912f, 0.7168870538238823f, 0.09708128566574631f},
{0.45840159019103005f, 0.9852645833250543f,
0.29829470783345835f}}, // lime
{{0.1763053229982614f, 0.10171766135467991f, 0.024020600356509242f},
{0.5957181607237907f, 0.2055939145569215f,
0.18695695018247227f}}, // brown
{{0.1250143560558979f, 0.0611129099463755f, 0.15715146562446167f},
{0.4584004101072638f, 0.07977226603250179f,
0.4847907338567859f}}, // purple
{{0.7245316165924385f, 0.6365774485679174f, 0.4915583325045292f},
{0.962148711796773f, 0.7628803605364196f,
0.7971503318758075f}}}; // pink

for (auto& color_pair : colors_tests) {
auto [input_x, input_y, input_z] = color_pair.input;
auto [expected_r, expected_g, expected_b] = color_pair.expected;
auto [output_r, output_g, output_b] =
XYZD50ToDisplayP3(input_x, input_y, input_z);
EXPECT_NEAR(output_r, expected_r, 0.001f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
EXPECT_NEAR(output_g, expected_g, 0.001f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
EXPECT_NEAR(output_b, expected_b, 0.001f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
}
}

TEST(ColorConversions, DisplayP3ToSkColor4f) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=purple&precision=4
Expand Down Expand Up @@ -616,6 +657,42 @@ TEST(ColorConversions, ProPhotoToXYZD50) {
}
}

TEST(ColorConversions, ProPhotoToSkColor4f) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=pink&precision=4
ColorTest colors_tests[] = {
{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}, // black
{{0.9999999886663737f, 1.0000000327777285f, 0.9999999636791804f},
{1.0f, 1.0f, 1.0f}}, // white
{{0.5402807890930262f, 0.9275948938161531f, 0.30456598218387576f},
{0.0f, 1.0f, 0.0f}}, // lime
{{0.4202512875251534f, 0.20537448341387265f, 0.14018716364460992f},
{0.6470588235294118f, 0.16470588235294117f,
0.16470588235294117f}}, // brown
{{0.3415199027593793f, 0.13530888280806527f, 0.3980101298732242f},
{0.5019607843137255f, 0.0f, 0.5019607843137255f}}, // purple
{{0.8755612852965058f, 0.7357597566543541f, 0.7499575746802042f},
{1.0f, 0.7529411764705882f, 0.796078431372549f}}}; // pink

for (auto& color_pair : colors_tests) {
auto [input_r, input_g, input_b] = color_pair.input;
auto [expected_r, expected_g, expected_b] = color_pair.expected;
SkColor4f color = ProPhotoToSkColor4f(input_r, input_g, input_b, 1.0f);
EXPECT_NEAR(color.fR, expected_r, 0.01f)
<< input_r << ' ' << input_g << ' ' << input_b << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << color.fR
<< ' ' << color.fG << ' ' << color.fB;
EXPECT_NEAR(color.fG, expected_g, 0.01f)
<< input_r << ' ' << input_g << ' ' << input_b << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << color.fR
<< ' ' << color.fG << ' ' << color.fB;
EXPECT_NEAR(color.fB, expected_b, 0.01f)
<< input_r << ' ' << input_g << ' ' << input_b << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << color.fR
<< ' ' << color.fG << ' ' << color.fB;
}
}

TEST(ColorConversions, AdobeRGBToXYZD50) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=purple&precision=4
Expand Down Expand Up @@ -657,39 +734,42 @@ TEST(ColorConversions, AdobeRGBToXYZD50) {
}
}

TEST(ColorConversions, ProPhotoToSkColor4f) {
TEST(ColorConversions, XYZD50ToAdobeRGB) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=pink&precision=4
// https://colorjs.io/apps/convert/?color=purple&precision=4
ColorTest colors_tests[] = {
{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}, // black
{{0.9999999886663737f, 1.0000000327777285f, 0.9999999636791804f},
{1.0f, 1.0f, 1.0f}}, // white
{{0.5402807890930262f, 0.9275948938161531f, 0.30456598218387576f},
{0.0f, 1.0f, 0.0f}}, // lime
{{0.4202512875251534f, 0.20537448341387265f, 0.14018716364460992f},
{0.6470588235294118f, 0.16470588235294117f,
0.16470588235294117f}}, // brown
{{0.3415199027593793f, 0.13530888280806527f, 0.3980101298732242f},
{0.5019607843137255f, 0.0f, 0.5019607843137255f}}, // purple
{{0.8755612852965058f, 0.7357597566543541f, 0.7499575746802042f},
{1.0f, 0.7529411764705882f, 0.796078431372549f}}}; // pink
{{0.9642956660812443f, 1.0000000361162846f, 0.8251045485672053f},
{1.0000000000000002f, 0.9999999999999999f, 1.f}}, // white
{{0.3851514688337912f, 0.7168870538238823f, 0.09708128566574631f},
{0.564972265988564f, 0.9999999999999999f,
0.23442379872902916f}}, // lime
{{0.1763053229982614f, 0.10171766135467991f, 0.024020600356509242f},
{0.5565979160264471f, 0.18045907254050694f,
0.18045907254050705f}}, // brown
{{0.1250143560558979f, 0.0611129099463755f, 0.15715146562446167f},
{0.4275929819700999f, 0.0f, 0.4885886519419426f}}, // purple
{{0.7245316165924385f, 0.6365774485679174f, 0.4915583325045292f},
{0.9363244100721754f, 0.7473920857106169f,
0.7893042668092753f}}}; // pink

for (auto& color_pair : colors_tests) {
auto [input_r, input_g, input_b] = color_pair.input;
auto [input_x, input_y, input_z] = color_pair.input;
auto [expected_r, expected_g, expected_b] = color_pair.expected;
SkColor4f color = ProPhotoToSkColor4f(input_r, input_g, input_b, 1.0f);
EXPECT_NEAR(color.fR, expected_r, 0.01f)
<< input_r << ' ' << input_g << ' ' << input_b << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << color.fR
<< ' ' << color.fG << ' ' << color.fB;
EXPECT_NEAR(color.fG, expected_g, 0.01f)
<< input_r << ' ' << input_g << ' ' << input_b << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << color.fR
<< ' ' << color.fG << ' ' << color.fB;
EXPECT_NEAR(color.fB, expected_b, 0.01f)
<< input_r << ' ' << input_g << ' ' << input_b << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << color.fR
<< ' ' << color.fG << ' ' << color.fB;
auto [output_r, output_g, output_b] =
XYZD50ToAdobeRGB(input_x, input_y, input_z);
EXPECT_NEAR(output_r, expected_r, 0.01f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
EXPECT_NEAR(output_g, expected_g, 0.01f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
EXPECT_NEAR(output_b, expected_b, 0.01f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
}
}

Expand Down Expand Up @@ -770,6 +850,45 @@ TEST(ColorConversions, Rec2020ToXYZD50) {
}
}

TEST(ColorConversions, XYZD50ToRec2020) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=purple&precision=4
ColorTest colors_tests[] = {
{{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}}, // black
{{0.9642956660812443f, 1.0000000361162846f, 0.8251045485672053f},
{1.0000000000000002f, 1.f, 1.f}}, // white
{{0.3851514688337912f, 0.7168870538238823f, 0.09708128566574631f},
{0.5675424725933591f, 0.959278677099374f, 0.2689692617052188f}}, // lime
{{0.1763053229982614f, 0.10171766135467991f, 0.024020600356509242f},
{0.4841434514625542f, 0.17985588424119636f,
0.12395667053434403f}}, // brown
{{0.1250143560558979f, 0.0611129099463755f, 0.15715146562446167f},
{0.36142160262090384f, 0.0781562275109019f,
0.429742223818931f}}, // purple
{{0.7245316165924385f, 0.6365774485679174f, 0.4915583325045292f},
{0.9098509851821579f, 0.747938726996672f,
0.7726929727190115f}}}; // pink

for (auto& color_pair : colors_tests) {
auto [input_x, input_y, input_z] = color_pair.input;
auto [expected_r, expected_g, expected_b] = color_pair.expected;
auto [output_r, output_g, output_b] =
XYZD50ToRec2020(input_x, input_y, input_z);
EXPECT_NEAR(output_r, expected_r, 0.001f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
EXPECT_NEAR(output_g, expected_g, 0.001f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
EXPECT_NEAR(output_b, expected_b, 0.001f)
<< input_x << ' ' << input_y << ' ' << input_z << " to " << expected_r
<< ' ' << expected_g << ' ' << expected_b << " produced " << output_r
<< ' ' << output_g << ' ' << output_b;
}
}

TEST(ColorConversions, Rec2020ToSkColor4f) {
// Color conversions obtained from
// https://colorjs.io/apps/convert/?color=purple&precision=4
Expand Down

0 comments on commit b6fe1f4

Please sign in to comment.