You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
9.6 KiB
193 lines
9.6 KiB
/* |
|
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; |
|
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"
|
|
|