kwin/autotests/test_colorspaces.cpp
Xaver Hugl 6f79597f13 core/colorspace: match default luminances with the Wayland protocol
It wasn't feasible to do it before, because it made SDR black far too bright, but
with black point compensation the result looks as users expect it
2024-08-15 01:23:00 +00:00

179 lines
8.5 KiB
C++

/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <QTest>
#include "core/colorpipeline.h"
#include "core/colorspace.h"
using namespace KWin;
class TestColorspaces : public QObject
{
Q_OBJECT
public:
TestColorspaces() = default;
private Q_SLOTS:
void roundtripConversion_data();
void roundtripConversion();
void nonNormalizedPrimaries();
void testIdentityTransformation_data();
void testIdentityTransformation();
void testColorPipeline_data();
void testColorPipeline();
};
static bool compareVectors(const QVector3D &one, const QVector3D &two, float maxDifference)
{
const bool ret = std::abs(one.x() - two.x()) <= maxDifference
&& std::abs(one.y() - two.y()) <= maxDifference
&& std::abs(one.z() - two.z()) <= maxDifference;
if (!ret) {
qWarning() << one << "!=" << two << "within" << maxDifference;
}
return ret;
}
static const double s_resolution10bit = std::pow(1.0 / 2.0, 10);
void TestColorspaces::roundtripConversion_data()
{
QTest::addColumn<NamedColorimetry>("srcColorimetry");
QTest::addColumn<TransferFunction::Type>("srcTransferFunction");
QTest::addColumn<NamedColorimetry>("dstColorimetry");
QTest::addColumn<TransferFunction::Type>("dstTransferFunction");
QTest::addColumn<double>("requiredAccuracy");
QTest::addRow("BT709 (sRGB) <-> BT2020 (linear)") << NamedColorimetry::BT709 << TransferFunction::sRGB << NamedColorimetry::BT2020 << TransferFunction::linear << s_resolution10bit;
QTest::addRow("BT709 (gamma 2.2) <-> BT2020 (linear)") << NamedColorimetry::BT709 << TransferFunction::gamma22 << NamedColorimetry::BT2020 << TransferFunction::linear << s_resolution10bit;
QTest::addRow("BT709 (linear) <-> BT2020 (linear)") << NamedColorimetry::BT709 << TransferFunction::linear << NamedColorimetry::BT2020 << TransferFunction::linear << s_resolution10bit;
QTest::addRow("BT709 (PQ) <-> BT2020 (linear)") << NamedColorimetry::BT709 << TransferFunction::PerceptualQuantizer << NamedColorimetry::BT2020 << TransferFunction::linear << 3 * s_resolution10bit;
}
void TestColorspaces::roundtripConversion()
{
QFETCH(NamedColorimetry, srcColorimetry);
QFETCH(TransferFunction::Type, srcTransferFunction);
QFETCH(NamedColorimetry, dstColorimetry);
QFETCH(TransferFunction::Type, dstTransferFunction);
QFETCH(double, requiredAccuracy);
const auto src = ColorDescription(srcColorimetry, TransferFunction(srcTransferFunction), 100, 0, 100, 100);
const auto dst = ColorDescription(dstColorimetry, TransferFunction(dstTransferFunction), 100, 0, 100, 100);
const QVector3D red(1, 0, 0);
const QVector3D green(0, 1, 0);
const QVector3D blue(0, 0, 1);
const QVector3D white(1, 1, 1);
constexpr std::array renderingIntents = {
RenderingIntent::RelativeColorimetric,
RenderingIntent::AbsoluteColorimetric,
};
for (const RenderingIntent intent : renderingIntents) {
QVERIFY(compareVectors(dst.mapTo(src.mapTo(red, dst, intent), src, intent), red, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(green, dst, intent), src, intent), green, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(blue, dst, intent), src, intent), blue, requiredAccuracy));
QVERIFY(compareVectors(dst.mapTo(src.mapTo(white, dst, intent), src, intent), white, requiredAccuracy));
}
}
void TestColorspaces::nonNormalizedPrimaries()
{
// this test ensures that non-normalized primaries don't mess up the transformations between color spaces
const auto &from = ColorDescription::sRGB;
const ColorDescription to(Colorimetry(Colorimetry::xyToXYZ(from.containerColorimetry().red()) * 2, Colorimetry::xyToXYZ(from.containerColorimetry().green()) * 2, Colorimetry::xyToXYZ(from.containerColorimetry().blue()) * 2, Colorimetry::xyToXYZ(from.containerColorimetry().white()) * 2), from.transferFunction(), from.referenceLuminance(), from.minLuminance(), from.maxAverageLuminance(), from.maxHdrLuminance());
const auto convertedWhite = from.toOther(to, RenderingIntent::RelativeColorimetric) * QVector3D(1, 1, 1);
QCOMPARE_LE(std::abs(1 - convertedWhite.x()), s_resolution10bit);
QCOMPARE_LE(std::abs(1 - convertedWhite.y()), s_resolution10bit);
QCOMPARE_LE(std::abs(1 - convertedWhite.z()), s_resolution10bit);
}
void TestColorspaces::testIdentityTransformation_data()
{
QTest::addColumn<NamedColorimetry>("colorimetry");
QTest::addColumn<TransferFunction::Type>("transferFunction");
QTest::addRow("BT709 (sRGB)") << NamedColorimetry::BT709 << TransferFunction::sRGB;
QTest::addRow("BT709 (gamma22)") << NamedColorimetry::BT709 << TransferFunction::gamma22;
QTest::addRow("BT709 (PQ)") << NamedColorimetry::BT709 << TransferFunction::PerceptualQuantizer;
QTest::addRow("BT709 (linear)") << NamedColorimetry::BT709 << TransferFunction::linear;
QTest::addRow("BT2020 (sRGB)") << NamedColorimetry::BT2020 << TransferFunction::sRGB;
QTest::addRow("BT2020 (gamma22)") << NamedColorimetry::BT2020 << TransferFunction::gamma22;
QTest::addRow("BT2020 (PQ)") << NamedColorimetry::BT2020 << TransferFunction::PerceptualQuantizer;
QTest::addRow("BT2020 (linear)") << NamedColorimetry::BT2020 << TransferFunction::linear;
}
void TestColorspaces::testIdentityTransformation()
{
QFETCH(NamedColorimetry, colorimetry);
QFETCH(TransferFunction::Type, transferFunction);
const ColorDescription color(colorimetry, TransferFunction(transferFunction), 100, 0, 100, 100);
constexpr std::array renderingIntents = {
RenderingIntent::Perceptual,
RenderingIntent::RelativeColorimetric,
RenderingIntent::AbsoluteColorimetric,
};
for (const RenderingIntent intent : renderingIntents) {
const auto pipeline = ColorPipeline::create(color, color, intent);
if (!pipeline.isIdentity()) {
qWarning() << pipeline;
}
QVERIFY(pipeline.isIdentity());
}
}
void TestColorspaces::testColorPipeline_data()
{
QTest::addColumn<ColorDescription>("srcColor");
QTest::addColumn<ColorDescription>("dstColor");
QTest::addColumn<QVector3D>("dstBlack");
QTest::addColumn<QVector3D>("dstGray");
QTest::addColumn<QVector3D>("dstWhite");
QTest::addColumn<RenderingIntent>("intent");
QTest::addRow("sRGB -> rec.2020 relative colorimetric")
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::PerceptualQuantizer), 500, 0, std::nullopt, std::nullopt)
<< QVector3D(0.06729, 0.06729, 0.06729)
<< QVector3D(0.51667, 0.51667, 0.51667)
<< QVector3D(0.67658, 0.67658, 0.67658)
<< RenderingIntent::RelativeColorimetric;
QTest::addRow("sRGB -> scRGB relative colorimetric")
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear, 0, 80), 80, 0, std::nullopt, std::nullopt)
<< QVector3D(0.00025, 0.00025, 0.00025)
<< QVector3D(0.217833, 0.217833, 0.217833)
<< QVector3D(1, 1, 1)
<< RenderingIntent::RelativeColorimetric;
}
void TestColorspaces::testColorPipeline()
{
QFETCH(ColorDescription, srcColor);
QFETCH(ColorDescription, dstColor);
QFETCH(QVector3D, dstBlack);
QFETCH(QVector3D, dstGray);
QFETCH(QVector3D, dstWhite);
QFETCH(RenderingIntent, intent);
const auto pipeline = ColorPipeline::create(srcColor, dstColor, intent);
QVERIFY(compareVectors(pipeline.evaluate(QVector3D(0, 0, 0)), dstBlack, s_resolution10bit));
QVERIFY(compareVectors(pipeline.evaluate(QVector3D(0.5, 0.5, 0.5)), dstGray, s_resolution10bit));
QVERIFY(compareVectors(pipeline.evaluate(QVector3D(1, 1, 1)), dstWhite, s_resolution10bit));
const auto inversePipeline = ColorPipeline::create(dstColor, srcColor, intent);
QVERIFY(compareVectors(inversePipeline.evaluate(dstBlack), QVector3D(0, 0, 0), s_resolution10bit));
QVERIFY(compareVectors(inversePipeline.evaluate(dstGray), QVector3D(0.5, 0.5, 0.5), s_resolution10bit));
QVERIFY(compareVectors(inversePipeline.evaluate(dstWhite), QVector3D(1, 1, 1), s_resolution10bit));
}
QTEST_MAIN(TestColorspaces)
#include "test_colorspaces.moc"