blob: 2ec3fdc1aaabf76b046a2615763ff0e1b1ffcc78 [file] [log] [blame]
// 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