| // Copyright 2017 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 <cmath> |
| |
| #include "base/logging.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/color_space.h" |
| #include "ui/gfx/skia_color_space_util.h" |
| |
| namespace gfx { |
| namespace { |
| |
| const float kEpsilon = 1.0e-3f; |
| |
| // Returns the L-infty difference of u and v. |
| float Diff(const SkVector4& u, const SkVector4& v) { |
| float result = 0; |
| for (size_t i = 0; i < 4; ++i) |
| result = std::max(result, std::abs(u.fData[i] - v.fData[i])); |
| return result; |
| } |
| |
| TEST(ColorSpace, RGBToYUV) { |
| const size_t kNumTestRGBs = 3; |
| SkVector4 test_rgbs[kNumTestRGBs] = { |
| SkVector4(1.f, 0.f, 0.f, 1.f), |
| SkVector4(0.f, 1.f, 0.f, 1.f), |
| SkVector4(0.f, 0.f, 1.f, 1.f), |
| }; |
| |
| const size_t kNumColorSpaces = 4; |
| gfx::ColorSpace color_spaces[kNumColorSpaces] = { |
| gfx::ColorSpace::CreateREC601(), gfx::ColorSpace::CreateREC709(), |
| gfx::ColorSpace::CreateJpeg(), gfx::ColorSpace::CreateXYZD50(), |
| }; |
| |
| SkVector4 expected_yuvs[kNumColorSpaces][kNumTestRGBs] = { |
| // REC601 |
| { |
| SkVector4(0.3195f, 0.3518f, 0.9392f, 1.0000f), |
| SkVector4(0.5669f, 0.2090f, 0.1322f, 1.0000f), |
| SkVector4(0.1607f, 0.9392f, 0.4286f, 1.0000f), |
| }, |
| // REC709 |
| { |
| SkVector4(0.2453f, 0.3994f, 0.9392f, 1.0000f), |
| SkVector4(0.6770f, 0.1614f, 0.1011f, 1.0000f), |
| SkVector4(0.1248f, 0.9392f, 0.4597f, 1.0000f), |
| }, |
| // Jpeg |
| { |
| SkVector4(0.2990f, 0.3313f, 1.0000f, 1.0000f), |
| SkVector4(0.5870f, 0.1687f, 0.0813f, 1.0000f), |
| SkVector4(0.1140f, 1.0000f, 0.4187f, 1.0000f), |
| }, |
| // XYZD50 |
| { |
| SkVector4(1.0000f, 0.0000f, 0.0000f, 1.0000f), |
| SkVector4(0.0000f, 1.0000f, 0.0000f, 1.0000f), |
| SkVector4(0.0000f, 0.0000f, 1.0000f, 1.0000f), |
| }, |
| }; |
| |
| for (size_t i = 0; i < kNumColorSpaces; ++i) { |
| SkMatrix44 transfer; |
| color_spaces[i].GetTransferMatrix(&transfer); |
| |
| SkMatrix44 range_adjust; |
| color_spaces[i].GetRangeAdjustMatrix(&range_adjust); |
| |
| SkMatrix44 range_adjust_inv; |
| range_adjust.invert(&range_adjust_inv); |
| |
| for (size_t j = 0; j < kNumTestRGBs; ++j) { |
| SkVector4 yuv = range_adjust_inv * transfer * test_rgbs[j]; |
| EXPECT_LT(Diff(yuv, expected_yuvs[i][j]), kEpsilon); |
| } |
| } |
| } |
| |
| typedef std::tr1::tuple<ColorSpace::TransferID, size_t> TableTestData; |
| |
| class ColorSpaceTableTest : public testing::TestWithParam<TableTestData> {}; |
| |
| TEST_P(ColorSpaceTableTest, ApproximateTransferFn) { |
| ColorSpace::TransferID transfer_id = std::tr1::get<0>(GetParam()); |
| const size_t table_size = std::tr1::get<1>(GetParam()); |
| |
| gfx::ColorSpace color_space(ColorSpace::PrimaryID::BT709, transfer_id); |
| SkColorSpaceTransferFn tr_fn; |
| SkColorSpaceTransferFn tr_fn_inv; |
| bool result = color_space.GetTransferFunction(&tr_fn); |
| CHECK(result); |
| color_space.GetInverseTransferFunction(&tr_fn_inv); |
| |
| std::vector<float> x; |
| std::vector<float> t; |
| for (float v = 0; v <= 1.f; v += 1.f / table_size) { |
| x.push_back(v); |
| t.push_back(SkTransferFnEval(tr_fn, v)); |
| } |
| |
| SkColorSpaceTransferFn fn_approx; |
| bool converged = |
| SkApproximateTransferFn(x.data(), t.data(), x.size(), &fn_approx); |
| EXPECT_TRUE(converged); |
| |
| for (size_t i = 0; i < x.size(); ++i) { |
| float fn_approx_of_x = SkTransferFnEval(fn_approx, x[i]); |
| EXPECT_NEAR(t[i], fn_approx_of_x, 3.f / 256.f); |
| if (std::abs(t[i] - fn_approx_of_x) > 3.f / 256.f) |
| break; |
| } |
| } |
| |
| ColorSpace::TransferID all_transfers[] = { |
| ColorSpace::TransferID::GAMMA22, ColorSpace::TransferID::GAMMA24, |
| ColorSpace::TransferID::GAMMA28, ColorSpace::TransferID::BT709, |
| ColorSpace::TransferID::SMPTE240M, ColorSpace::TransferID::IEC61966_2_1, |
| ColorSpace::TransferID::LINEAR}; |
| |
| size_t all_table_sizes[] = {512, 256, 128, 64, 16, 11, 8, 7, 6, 5, 4}; |
| |
| INSTANTIATE_TEST_CASE_P(A, |
| ColorSpaceTableTest, |
| testing::Combine(testing::ValuesIn(all_transfers), |
| testing::ValuesIn(all_table_sizes))); |
| |
| TEST(ColorSpace, ApproximateTransferFnBadMatch) { |
| const float kStep = 1.f / 512.f; |
| ColorSpace::TransferID transfer_ids[3] = { |
| ColorSpace::TransferID::IEC61966_2_1, ColorSpace::TransferID::GAMMA22, |
| ColorSpace::TransferID::BT709, |
| }; |
| gfx::ColorSpace color_spaces[3]; |
| |
| // The first iteration will have a perfect match. The second will be very |
| // close. The third will converge, but with an error of ~7/256. |
| for (size_t transfers_to_use = 1; transfers_to_use <= 3; ++transfers_to_use) { |
| std::vector<float> x; |
| std::vector<float> t; |
| for (size_t c = 0; c < transfers_to_use; ++c) { |
| color_spaces[c] = |
| gfx::ColorSpace(ColorSpace::PrimaryID::BT709, transfer_ids[c]); |
| SkColorSpaceTransferFn tr_fn; |
| bool result = color_spaces[c].GetTransferFunction(&tr_fn); |
| CHECK(result); |
| |
| for (float v = 0; v <= 1.f; v += kStep) { |
| x.push_back(v); |
| t.push_back(SkTransferFnEval(tr_fn, v)); |
| } |
| } |
| |
| SkColorSpaceTransferFn fn_approx; |
| bool converged = |
| SkApproximateTransferFn(x.data(), t.data(), x.size(), &fn_approx); |
| EXPECT_TRUE(converged); |
| |
| float expected_error = 1.5f / 256.f; |
| if (transfers_to_use == 3) |
| expected_error = 8.f / 256.f; |
| |
| for (size_t i = 0; i < x.size(); ++i) { |
| float fn_approx_of_x = SkTransferFnEval(fn_approx, x[i]); |
| EXPECT_NEAR(t[i], fn_approx_of_x, expected_error); |
| if (std::abs(t[i] - fn_approx_of_x) > expected_error) |
| break; |
| } |
| } |
| } |
| |
| TEST(ColorSpace, ToSkColorSpace) { |
| const size_t kNumTests = 4; |
| ColorSpace color_spaces[kNumTests] = { |
| ColorSpace(ColorSpace::PrimaryID::BT709, |
| ColorSpace::TransferID::IEC61966_2_1), |
| ColorSpace(ColorSpace::PrimaryID::ADOBE_RGB, |
| ColorSpace::TransferID::IEC61966_2_1), |
| ColorSpace(ColorSpace::PrimaryID::SMPTEST432_1, |
| ColorSpace::TransferID::LINEAR), |
| ColorSpace(ColorSpace::PrimaryID::BT2020, |
| ColorSpace::TransferID::IEC61966_2_1), |
| }; |
| sk_sp<SkColorSpace> sk_color_spaces[kNumTests] = { |
| SkColorSpace::MakeSRGB(), |
| SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, |
| SkColorSpace::kAdobeRGB_Gamut), |
| SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, |
| SkColorSpace::kDCIP3_D65_Gamut), |
| SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, |
| SkColorSpace::kRec2020_Gamut), |
| }; |
| for (size_t i = 0; i < kNumTests; ++i) { |
| EXPECT_TRUE(SkColorSpace::Equals(color_spaces[i].ToSkColorSpace().get(), |
| sk_color_spaces[i].get())) |
| << " on iteration i = " << i; |
| } |
| } |
| |
| } // namespace |
| } // namespace gfx |