blob: 6b27741a3586738442b33f573cd7b47b77f9c1ca [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 <algorithm>
#include <cmath>
#include <tuple>
#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);
}
}
}
TEST(ColorSpace, RasterAndBlend) {
ColorSpace display_color_space;
// A linear transfer function being used for HDR should be blended using an
// sRGB-like transfer function.
display_color_space = ColorSpace::CreateSCRGBLinear();
EXPECT_EQ(ColorSpace::CreateExtendedSRGB(),
display_color_space.GetBlendingColorSpace());
EXPECT_EQ(ColorSpace::CreateDisplayP3D65(),
display_color_space.GetRasterColorSpace());
// If not used for HDR, a linear transfer function should be left unchanged.
display_color_space = ColorSpace::CreateXYZD50();
EXPECT_EQ(display_color_space, display_color_space.GetBlendingColorSpace());
EXPECT_EQ(display_color_space, display_color_space.GetRasterColorSpace());
}
TEST(ColorSpace, ConversionToAndFromSkColorSpace) {
const size_t kNumTests = 5;
skcms_Matrix3x3 primary_matrix = {{
{0.205276f, 0.625671f, 0.060867f},
{0.149185f, 0.063217f, 0.744553f},
{0.609741f, 0.311111f, 0.019470f},
}};
skcms_TransferFunction transfer_fn = {2.1f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
SkColorSpaceTransferFn sk_transfer_fn;
memcpy(&sk_transfer_fn, &transfer_fn, sizeof(sk_transfer_fn));
SkMatrix44 primary_matrix_4x4;
primary_matrix_4x4.set3x3RowMajorf(&primary_matrix.vals[0][0]);
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),
ColorSpace::CreateCustom(primary_matrix_4x4, sk_transfer_fn),
};
sk_sp<SkColorSpace> sk_color_spaces[kNumTests] = {
SkColorSpace::MakeSRGB(),
SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB),
SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, SkNamedGamut::kDCIP3),
SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020),
SkColorSpace::MakeRGB(transfer_fn, primary_matrix),
};
// Test that converting from ColorSpace to SkColorSpace is producing an
// equivalent representation, and that the SkColorSpace ref-pointers are being
// globally cached.
sk_sp<SkColorSpace> got_sk_color_spaces[kNumTests];
for (size_t i = 0; i < kNumTests; ++i)
got_sk_color_spaces[i] = color_spaces[i].ToSkColorSpace();
for (size_t i = 0; i < kNumTests; ++i) {
EXPECT_TRUE(SkColorSpace::Equals(got_sk_color_spaces[i].get(),
sk_color_spaces[i].get()))
<< " on iteration i = " << i;
// ToSkColorSpace should return the same thing every time.
EXPECT_EQ(got_sk_color_spaces[i].get(),
color_spaces[i].ToSkColorSpace().get())
<< " on iteration i = " << i;
// But there is no cache within Skia, except for sRGB.
// This test may start failing if this behavior changes.
if (i != 0) {
EXPECT_NE(got_sk_color_spaces[i].get(), sk_color_spaces[i].get())
<< " on iteration i = " << i;
}
}
// Invariant test: Test that converting a SkColorSpace to a ColorSpace is
// producing an equivalent representation; and then converting the converted
// ColorSpace back to SkColorSpace is also producing an equivalent
// representation.
for (size_t i = 0; i < kNumTests; ++i) {
const ColorSpace from_sk_color_space(*sk_color_spaces[i]);
EXPECT_EQ(color_spaces[i], from_sk_color_space);
EXPECT_TRUE(SkColorSpace::Equals(
sk_color_spaces[i].get(), from_sk_color_space.ToSkColorSpace().get()));
}
}
} // namespace
} // namespace gfx