kwin/autotests/test_colorspaces.cpp
Xaver Hugl eaf7e9a3a2 core/colorspace: be more robust about edge cases
xyz -> xy and xy -> xyz conversions have divisions in them, so we need to
handle the edge cases of the divisor being zero. This can happen if an ICC
profile is invalid, or if the XYZ color space is used.
2024-08-23 10:45:46 +00:00

195 lines
9.4 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 testXYZ_XYconversions();
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::testXYZ_XYconversions()
{
// this test ensures that Colorimetry::xyzToXY and Colorimetry::xyToXYZ can handle weird inputs
// and don't cause crashes
QCOMPARE(Colorimetry::xyzToXY(QVector3D(0, 0, 0)), QVector2D(0, 0));
QCOMPARE_LE(Colorimetry::xyzToXY(QVector3D(100, 100, 100)).y(), 1);
QCOMPARE(Colorimetry::xyToXYZ(QVector2D(0, 0)), QVector3D(0, 1, 0));
QCOMPARE(Colorimetry::xyToXYZ(QVector2D(1, 0)), QVector3D(1, 1, 0));
}
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 TransferFunction tf(transferFunction);
const ColorDescription src(colorimetry, tf, 100, tf.minLuminance, tf.maxLuminance, tf.maxLuminance);
const TransferFunction tf2(transferFunction, tf.minLuminance * 1.1, tf.maxLuminance * 1.1);
const ColorDescription dst(colorimetry, tf2, 110, tf2.minLuminance, tf2.maxLuminance, tf2.maxLuminance);
constexpr std::array renderingIntents = {
RenderingIntent::Perceptual,
RenderingIntent::RelativeColorimetric,
RenderingIntent::AbsoluteColorimetric,
RenderingIntent::RelativeColorimetricWithBPC,
};
for (const RenderingIntent intent : renderingIntents) {
const auto pipeline = ColorPipeline::create(src, dst, 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;
QTest::addRow("sRGB -> rec.2020 relative colorimetric with bpc")
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::gamma22, 0.2, 80), 80, 0.2, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT2020, TransferFunction(TransferFunction::PerceptualQuantizer, 0.005, 10'000), 500, 0.005, std::nullopt, std::nullopt)
<< QVector3D(0, 0, 0)
<< QVector3D(0.51667, 0.51667, 0.51667)
<< QVector3D(0.67658, 0.67658, 0.67658)
<< RenderingIntent::RelativeColorimetricWithBPC;
QTest::addRow("scRGB -> scRGB relative colorimetric with bpc")
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear, 0, 80), TransferFunction::defaultReferenceLuminanceFor(TransferFunction::gamma22), 0, std::nullopt, std::nullopt)
<< ColorDescription(NamedColorimetry::BT709, TransferFunction(TransferFunction::linear, 8, 80), 80, 8, std::nullopt, std::nullopt)
<< QVector3D(0, 0, 0)
<< QVector3D(0.5, 0.5, 0.5)
<< QVector3D(1, 1, 1)
<< RenderingIntent::RelativeColorimetricWithBPC;
}
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"