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.
This commit is contained in:
parent
99f2cd2f51
commit
eaf7e9a3a2
4 changed files with 35 additions and 41 deletions
|
@ -21,7 +21,7 @@ public:
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void roundtripConversion_data();
|
void roundtripConversion_data();
|
||||||
void roundtripConversion();
|
void roundtripConversion();
|
||||||
void nonNormalizedPrimaries();
|
void testXYZ_XYconversions();
|
||||||
void testIdentityTransformation_data();
|
void testIdentityTransformation_data();
|
||||||
void testIdentityTransformation();
|
void testIdentityTransformation();
|
||||||
void testColorPipeline_data();
|
void testColorPipeline_data();
|
||||||
|
@ -82,16 +82,14 @@ void TestColorspaces::roundtripConversion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestColorspaces::nonNormalizedPrimaries()
|
void TestColorspaces::testXYZ_XYconversions()
|
||||||
{
|
{
|
||||||
// this test ensures that non-normalized primaries don't mess up the transformations between color spaces
|
// this test ensures that Colorimetry::xyzToXY and Colorimetry::xyToXYZ can handle weird inputs
|
||||||
const auto &from = ColorDescription::sRGB;
|
// and don't cause crashes
|
||||||
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());
|
QCOMPARE(Colorimetry::xyzToXY(QVector3D(0, 0, 0)), QVector2D(0, 0));
|
||||||
|
QCOMPARE_LE(Colorimetry::xyzToXY(QVector3D(100, 100, 100)).y(), 1);
|
||||||
const auto convertedWhite = from.toOther(to, RenderingIntent::RelativeColorimetric) * QVector3D(1, 1, 1);
|
QCOMPARE(Colorimetry::xyToXYZ(QVector2D(0, 0)), QVector3D(0, 1, 0));
|
||||||
QCOMPARE_LE(std::abs(1 - convertedWhite.x()), s_resolution10bit);
|
QCOMPARE(Colorimetry::xyToXYZ(QVector2D(1, 0)), QVector3D(1, 1, 0));
|
||||||
QCOMPARE_LE(std::abs(1 - convertedWhite.y()), s_resolution10bit);
|
|
||||||
QCOMPARE_LE(std::abs(1 - convertedWhite.z()), s_resolution10bit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestColorspaces::testIdentityTransformation_data()
|
void TestColorspaces::testIdentityTransformation_data()
|
||||||
|
|
|
@ -28,12 +28,20 @@ static QMatrix4x4 matrixFromColumns(const QVector3D &first, const QVector3D &sec
|
||||||
|
|
||||||
QVector3D Colorimetry::xyToXYZ(QVector2D xy)
|
QVector3D Colorimetry::xyToXYZ(QVector2D xy)
|
||||||
{
|
{
|
||||||
|
if (xy.y() == 0) {
|
||||||
|
// special case for XYZ Colorimetry
|
||||||
|
// where xy.y == 0 is valid
|
||||||
|
return QVector3D(xy.x(), 1, 0);
|
||||||
|
}
|
||||||
return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y());
|
return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector2D Colorimetry::xyzToXY(QVector3D xyz)
|
QVector2D Colorimetry::xyzToXY(QVector3D xyz)
|
||||||
{
|
{
|
||||||
xyz /= xyz.y();
|
if (xyz.y() == 0) {
|
||||||
|
// this is nonsense, but at least doesn't crash
|
||||||
|
return QVector2D(0, 0);
|
||||||
|
}
|
||||||
return QVector2D(xyz.x() / (xyz.x() + xyz.y() + xyz.z()), xyz.y() / (xyz.x() + xyz.y() + xyz.z()));
|
return QVector2D(xyz.x() / (xyz.x() + xyz.y() + xyz.z()), xyz.y() / (xyz.x() + xyz.y() + xyz.z()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,11 +84,6 @@ QMatrix4x4 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QV
|
||||||
return inverseBradford * adaptation * bradford;
|
return inverseBradford * adaptation * bradford;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QVector3D normalizeToY1(const QVector3D &vect)
|
|
||||||
{
|
|
||||||
return vect.y() == 0 ? vect : QVector3D(vect.x() / vect.y(), 1, vect.z() / vect.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
QMatrix4x4 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
|
QMatrix4x4 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
|
||||||
{
|
{
|
||||||
const auto component_scale = (matrixFromColumns(red, green, blue)).inverted() * white;
|
const auto component_scale = (matrixFromColumns(red, green, blue)).inverted() * white;
|
||||||
|
@ -107,16 +110,6 @@ Colorimetry::Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Colorimetry::Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
|
|
||||||
: m_red(xyzToXY(red))
|
|
||||||
, m_green(xyzToXY(green))
|
|
||||||
, m_blue(xyzToXY(blue))
|
|
||||||
, m_white(xyzToXY(white))
|
|
||||||
, m_toXYZ(calculateToXYZMatrix(normalizeToY1(red), normalizeToY1(green), normalizeToY1(blue), normalizeToY1(white)))
|
|
||||||
, m_fromXYZ(m_toXYZ.inverted())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const QMatrix4x4 &Colorimetry::toXYZ() const
|
const QMatrix4x4 &Colorimetry::toXYZ() const
|
||||||
{
|
{
|
||||||
return m_toXYZ;
|
return m_toXYZ;
|
||||||
|
|
|
@ -68,7 +68,6 @@ public:
|
||||||
static QMatrix4x4 calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white);
|
static QMatrix4x4 calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white);
|
||||||
|
|
||||||
explicit Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white);
|
explicit Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white);
|
||||||
explicit Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns a matrix that transforms from the linear RGB representation of colors in this colorimetry to the XYZ representation
|
* @returns a matrix that transforms from the linear RGB representation of colors in this colorimetry to the XYZ representation
|
||||||
|
|
|
@ -263,11 +263,15 @@ std::unique_ptr<IccProfile> IccProfile::load(const QString &path)
|
||||||
qCWarning(KWIN_CORE, "profile is missing the wtpt tag");
|
qCWarning(KWIN_CORE, "profile is missing the wtpt tag");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
if (whitepoint->Y == 0) {
|
||||||
|
qCWarning(KWIN_CORE, "profile has a zero luminance whitepoint");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
QVector3D red;
|
QVector2D red;
|
||||||
QVector3D green;
|
QVector2D green;
|
||||||
QVector3D blue;
|
QVector2D blue;
|
||||||
QVector3D white(whitepoint->X, whitepoint->Y, whitepoint->Z);
|
QVector2D white = Colorimetry::xyzToXY(QVector3D(whitepoint->X, whitepoint->Y, whitepoint->Z));
|
||||||
std::optional<QMatrix4x4> chromaticAdaptationMatrix;
|
std::optional<QMatrix4x4> chromaticAdaptationMatrix;
|
||||||
if (cmsIsTag(handle, cmsSigChromaticAdaptationTag)) {
|
if (cmsIsTag(handle, cmsSigChromaticAdaptationTag)) {
|
||||||
// the chromatic adaptation tag is a 3x3 matrix that converts from the actual whitepoint to D50
|
// the chromatic adaptation tag is a 3x3 matrix that converts from the actual whitepoint to D50
|
||||||
|
@ -284,12 +288,12 @@ std::unique_ptr<IccProfile> IccProfile::load(const QString &path)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const QVector3D D50(0.9642, 1.0, 0.8249);
|
const QVector3D D50(0.9642, 1.0, 0.8249);
|
||||||
white = *chromaticAdaptationMatrix * D50;
|
white = Colorimetry::xyzToXY(*chromaticAdaptationMatrix * D50);
|
||||||
}
|
}
|
||||||
if (cmsCIExyYTRIPLE *chrmTag = static_cast<cmsCIExyYTRIPLE *>(cmsReadTag(handle, cmsSigChromaticityTag))) {
|
if (cmsCIExyYTRIPLE *chrmTag = static_cast<cmsCIExyYTRIPLE *>(cmsReadTag(handle, cmsSigChromaticityTag))) {
|
||||||
red = Colorimetry::xyToXYZ(QVector2D(chrmTag->Red.x, chrmTag->Red.y)) * chrmTag->Red.Y;
|
red = QVector2D(chrmTag->Red.x, chrmTag->Red.y);
|
||||||
green = Colorimetry::xyToXYZ(QVector2D(chrmTag->Green.x, chrmTag->Green.y)) * chrmTag->Green.Y;
|
green = QVector2D(chrmTag->Green.x, chrmTag->Green.y);
|
||||||
blue = Colorimetry::xyToXYZ(QVector2D(chrmTag->Blue.x, chrmTag->Blue.y)) * chrmTag->Blue.Y;
|
blue = QVector2D(chrmTag->Blue.x, chrmTag->Blue.y);
|
||||||
} else {
|
} else {
|
||||||
const cmsCIEXYZ *r = static_cast<cmsCIEXYZ *>(cmsReadTag(handle, cmsSigRedColorantTag));
|
const cmsCIEXYZ *r = static_cast<cmsCIEXYZ *>(cmsReadTag(handle, cmsSigRedColorantTag));
|
||||||
const cmsCIEXYZ *g = static_cast<cmsCIEXYZ *>(cmsReadTag(handle, cmsSigGreenColorantTag));
|
const cmsCIEXYZ *g = static_cast<cmsCIEXYZ *>(cmsReadTag(handle, cmsSigGreenColorantTag));
|
||||||
|
@ -299,9 +303,9 @@ std::unique_ptr<IccProfile> IccProfile::load(const QString &path)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (chromaticAdaptationMatrix) {
|
if (chromaticAdaptationMatrix) {
|
||||||
red = *chromaticAdaptationMatrix * QVector3D(r->X, r->Y, r->Z);
|
red = Colorimetry::xyzToXY(*chromaticAdaptationMatrix * QVector3D(r->X, r->Y, r->Z));
|
||||||
green = *chromaticAdaptationMatrix * QVector3D(g->X, g->Y, g->Z);
|
green = Colorimetry::xyzToXY(*chromaticAdaptationMatrix * QVector3D(g->X, g->Y, g->Z));
|
||||||
blue = *chromaticAdaptationMatrix * QVector3D(b->X, b->Y, b->Z);
|
blue = Colorimetry::xyzToXY(*chromaticAdaptationMatrix * QVector3D(b->X, b->Y, b->Z));
|
||||||
} else {
|
} else {
|
||||||
// if the chromatic adaptation tag isn't available, fall back to using the media whitepoint instead
|
// if the chromatic adaptation tag isn't available, fall back to using the media whitepoint instead
|
||||||
cmsCIEXYZ adaptedR{};
|
cmsCIEXYZ adaptedR{};
|
||||||
|
@ -313,9 +317,9 @@ std::unique_ptr<IccProfile> IccProfile::load(const QString &path)
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
red = QVector3D(adaptedR.X, adaptedR.Y, adaptedR.Z);
|
red = Colorimetry::xyzToXY(QVector3D(adaptedR.X, adaptedR.Y, adaptedR.Z));
|
||||||
green = QVector3D(adaptedG.X, adaptedG.Y, adaptedG.Z);
|
green = Colorimetry::xyzToXY(QVector3D(adaptedG.X, adaptedG.Y, adaptedG.Z));
|
||||||
blue = QVector3D(adaptedB.X, adaptedB.Y, adaptedB.Z);
|
blue = Colorimetry::xyzToXY(QVector3D(adaptedB.X, adaptedB.Y, adaptedB.Z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue