summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-12-03 16:03:17 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2019-05-02 20:50:46 +0000
commit78a7e54f8f5c4ca6ce1ee6b0ac82c42b21738ac5 (patch)
tree8b1e5eb2a6341bde972d240d6569f26137a13989 /src
parentfe57936d8c6e1bdafea5ba3260cc05c137a88ead (diff)
Custom color-space based on chromaticities
Change-Id: I7fa6efa8993aa2b79ea60b6a21bf57c4f67a120f Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/gui/painting/qcolormatrix_p.h45
-rw-r--r--src/gui/painting/qcolorspace.cpp155
-rw-r--r--src/gui/painting/qcolorspace.h3
-rw-r--r--src/gui/painting/qcolorspace_p.h26
4 files changed, 201 insertions, 28 deletions
diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h
index 3d1dca6222..66db95df7e 100644
--- a/src/gui/painting/qcolormatrix_p.h
+++ b/src/gui/painting/qcolormatrix_p.h
@@ -52,6 +52,7 @@
//
#include <QtGui/qtguiglobal.h>
+#include <QtCore/qpoint.h>
#include <cmath>
QT_BEGIN_NAMESPACE
@@ -61,7 +62,13 @@ class QColorVector
{
public:
QColorVector() = default;
- constexpr QColorVector(float x, float y, float z) : x(x), y(y), z(z), _unused(0.0f) { }
+ Q_DECL_CONSTEXPR QColorVector(float x, float y, float z) : x(x), y(y), z(z), _unused(0.0f) { }
+ explicit Q_DECL_CONSTEXPR QColorVector(const QPointF &chr) // from XY chromaticity
+ : x(chr.x() / chr.y())
+ , y(1.0f)
+ , z((1.0 - chr.x() - chr.y()) / chr.y())
+ , _unused(0.0f)
+ { }
float x; // X, x or red
float y; // Y, y or green
float z; // Z, Y or blue
@@ -69,11 +76,28 @@ public:
friend inline bool operator==(const QColorVector &v1, const QColorVector &v2);
friend inline bool operator!=(const QColorVector &v1, const QColorVector &v2);
+ bool isNull() const
+ {
+ return !x && !y && !z;
+ }
+
+ static Q_DECL_CONSTEXPR QColorVector null() { return QColorVector(0.0f, 0.0f, 0.0f); }
+ static bool isValidChromaticity(const QPointF &chr)
+ {
+ if (chr.x() < qreal(0.0) || chr.x() > qreal(1.0))
+ return false;
+ if (chr.y() <= qreal(0.0) || chr.y() > qreal(1.0))
+ return false;
+ if (chr.x() + chr.y() > qreal(1.0))
+ return false;
+ return true;
+ }
- static constexpr QColorVector null() { return QColorVector(0.0f, 0.0f, 0.0f); }
- // Common whitepoints on normalized XYZ form:
- static constexpr QColorVector D50() { return QColorVector(0.96421f, 1.0f, 0.82519f); }
- static constexpr QColorVector D65() { return QColorVector(0.95043f, 1.0f, 1.08890f); }
+ // Common whitepoints:
+ static Q_DECL_CONSTEXPR QPointF D50Chromaticity() { return QPointF(0.34567, 0.35850); }
+ static Q_DECL_CONSTEXPR QPointF D65Chromaticity() { return QPointF(0.31271, 0.32902); }
+ static Q_DECL_CONSTEXPR QColorVector D50() { return QColorVector(D50Chromaticity()); }
+ static Q_DECL_CONSTEXPR QColorVector D65() { return QColorVector(D65Chromaticity()); }
};
inline bool operator==(const QColorVector &v1, const QColorVector &v2)
@@ -102,6 +126,10 @@ public:
friend inline bool operator==(const QColorMatrix &m1, const QColorMatrix &m2);
friend inline bool operator!=(const QColorMatrix &m1, const QColorMatrix &m2);
+ bool isNull() const
+ {
+ return r.isNull() && g.isNull() && b.isNull();
+ }
bool isValid() const
{
// A color matrix must be invertible
@@ -167,6 +195,13 @@ public:
{
return { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } };
}
+ static QColorMatrix fromScale(QColorVector v)
+ {
+ return QColorMatrix { { v.x, 0.0f, 0.0f },
+ { 0.0f, v.y, 0.0f },
+ { 0.0f, 0.0f, v.z } };
+ }
+ // These are used to recognize matrices from ICC profiles:
static QColorMatrix toXyzFromSRgb()
{
return QColorMatrix { { 0.4360217452f, 0.2224751115f, 0.0139281144f },
diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp
index 24785f7b61..c98c31fe05 100644
--- a/src/gui/painting/qcolorspace.cpp
+++ b/src/gui/painting/qcolorspace.cpp
@@ -53,6 +53,102 @@
QT_BEGIN_NAMESPACE
+QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Gamut gamut)
+{
+ switch (gamut) {
+ case QColorSpace::Gamut::SRgb:
+ redPoint = QPointF(0.640, 0.330);
+ greenPoint = QPointF(0.300, 0.600);
+ bluePoint = QPointF(0.150, 0.060);
+ whitePoint = QColorVector::D65Chromaticity();
+ break;
+ case QColorSpace::Gamut::DciP3D65:
+ redPoint = QPointF(0.680, 0.320);
+ greenPoint = QPointF(0.265, 0.690);
+ bluePoint = QPointF(0.150, 0.060);
+ whitePoint = QColorVector::D65Chromaticity();
+ break;
+ case QColorSpace::Gamut::Bt2020:
+ redPoint = QPointF(0.708, 0.292);
+ greenPoint = QPointF(0.190, 0.797);
+ bluePoint = QPointF(0.131, 0.046);
+ whitePoint = QColorVector::D65Chromaticity();
+ break;
+ case QColorSpace::Gamut::AdobeRgb:
+ redPoint = QPointF(0.640, 0.330);
+ greenPoint = QPointF(0.210, 0.710);
+ bluePoint = QPointF(0.150, 0.060);
+ whitePoint = QColorVector::D65Chromaticity();
+ break;
+ case QColorSpace::Gamut::ProPhotoRgb:
+ redPoint = QPointF(0.7347, 0.2653);
+ greenPoint = QPointF(0.1596, 0.8404);
+ bluePoint = QPointF(0.0366, 0.0001);
+ whitePoint = QColorVector::D50Chromaticity();
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+}
+
+bool QColorSpacePrimaries::areValid() const
+{
+ if (!QColorVector::isValidChromaticity(redPoint))
+ return false;
+ if (!QColorVector::isValidChromaticity(greenPoint))
+ return false;
+ if (!QColorVector::isValidChromaticity(bluePoint))
+ return false;
+ if (!QColorVector::isValidChromaticity(whitePoint))
+ return false;
+ return true;
+}
+
+QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
+{
+ // This converts to XYZ in some undefined scale.
+ QColorMatrix toXyz = { QColorVector(redPoint),
+ QColorVector(greenPoint),
+ QColorVector(bluePoint) };
+
+ // Since the white point should be (1.0, 1.0, 1.0) in the
+ // input, we can figure out the scale by using the
+ // inverse conversion on the white point.
+ QColorVector wXyz(whitePoint);
+ QColorVector whiteScale = toXyz.inverted().map(wXyz);
+
+ // Now we have scaled conversion to XYZ relative to the given whitepoint
+ toXyz = toXyz * QColorMatrix::fromScale(whiteScale);
+
+ // But we want a conversion to XYZ relative to D50
+ QColorVector wXyzD50 = QColorVector::D50();
+
+ if (wXyz != wXyzD50) {
+ // Do chromatic adaptation to map our white point to XYZ D50.
+
+ // The Bradford method chromatic adaptation matrix:
+ QColorMatrix abrad = { { 0.8951f, -0.7502f, 0.0389f },
+ { 0.2664f, 1.7135f, -0.0685f },
+ { -0.1614f, 0.0367f, 1.0296f } };
+ QColorMatrix abradinv = { { 0.9869929f, 0.4323053f, -0.0085287f },
+ { -0.1470543f, 0.5183603f, 0.0400428f },
+ { 0.1599627f, 0.0492912f, 0.9684867f } };
+
+ QColorVector srcCone = abrad.map(wXyz);
+ QColorVector dstCone = abrad.map(wXyzD50);
+
+ QColorMatrix wToD50 = { { dstCone.x / srcCone.x, 0, 0 },
+ { 0, dstCone.y / srcCone.y, 0 },
+ { 0, 0, dstCone.z / srcCone.z } };
+
+
+ QColorMatrix chromaticAdaptation = abradinv * (wToD50 * abrad);
+ toXyz = chromaticAdaptation * toXyz;
+ }
+
+ return toXyz;
+}
+
QColorSpacePrivate::QColorSpacePrivate()
: id(QColorSpace::Unknown)
, gamut(QColorSpace::Gamut::Custom)
@@ -128,6 +224,21 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Gamut gamut, QColorSpace::Tr
initialize();
}
+QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
+ QColorSpace::TransferFunction fun,
+ float gamma)
+ : gamut(QColorSpace::Gamut::Custom)
+ , transferFunction(fun)
+ , gamma(gamma)
+{
+ Q_ASSERT(primaries.areValid());
+ toXyz = primaries.toXyzMatrix();
+ whitePoint = QColorVector(primaries.whitePoint);
+ if (!identifyColorSpace())
+ id = QColorSpace::Unknown;
+ setTransferFunction();
+}
+
bool QColorSpacePrivate::identifyColorSpace()
{
switch (gamut) {
@@ -195,33 +306,14 @@ void QColorSpacePrivate::initialize()
void QColorSpacePrivate::setToXyzMatrix()
{
- switch (gamut) {
- case QColorSpace::Gamut::SRgb:
- toXyz = QColorMatrix::toXyzFromSRgb();
- whitePoint = QColorVector::D65();
- return;
- case QColorSpace::Gamut::AdobeRgb:
- toXyz = QColorMatrix::toXyzFromAdobeRgb();
- whitePoint = QColorVector::D65();
- return;
- case QColorSpace::Gamut::DciP3D65:
- toXyz = QColorMatrix::toXyzFromDciP3D65();
- whitePoint = QColorVector::D65();
- return;
- case QColorSpace::Gamut::ProPhotoRgb:
- toXyz = QColorMatrix::toXyzFromProPhotoRgb();
- whitePoint = QColorVector::D50();
- return;
- case QColorSpace::Gamut::Bt2020:
- toXyz = QColorMatrix::toXyzFromBt2020();
- whitePoint = QColorVector::D65();
- return;
- case QColorSpace::Gamut::Custom:
+ if (gamut == QColorSpace::Gamut::Custom) {
toXyz = QColorMatrix::null();
whitePoint = QColorVector::D50();
return;
}
- Q_UNREACHABLE();
+ QColorSpacePrimaries primaries(gamut);
+ toXyz = primaries.toXyzMatrix();
+ whitePoint = QColorVector(primaries.whitePoint);
}
void QColorSpacePrivate::setTransferFunction()
@@ -386,6 +478,23 @@ QColorSpace::QColorSpace(QColorSpace::Gamut gamut, float gamma)
{
}
+/*!
+ Creates a custom colorspace with a gamut based on the chromaticities of the primary colors \a whitePoint,
+ \a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a fun and optionally \a gamma.
+ */
+QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
+ const QPointF &greenPoint, const QPointF &bluePoint,
+ QColorSpace::TransferFunction fun, float gamma)
+{
+ QColorSpacePrimaries primaries(whitePoint, redPoint, greenPoint, bluePoint);
+ if (!primaries.areValid()) {
+ qWarning() << "QColorSpace attempted constructed from invalid primaries:" << whitePoint << redPoint << greenPoint << bluePoint;
+ d_ptr = QColorSpace(QColorSpace::Undefined).d_ptr;
+ return;
+ }
+ d_ptr = new QColorSpacePrivate(primaries, fun, gamma);
+}
+
QColorSpace::~QColorSpace()
{
}
diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h
index 923546ec6f..56676826a9 100644
--- a/src/gui/painting/qcolorspace.h
+++ b/src/gui/painting/qcolorspace.h
@@ -85,6 +85,9 @@ public:
QColorSpace(ColorSpaceId colorSpaceId = Undefined);
QColorSpace(Gamut gamut, TransferFunction fun, float gamma = 0.0f);
QColorSpace(Gamut gamut, float gamma);
+ QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
+ const QPointF &greenPoint, const QPointF &bluePoint,
+ TransferFunction fun, float gamma = 0.0f);
~QColorSpace();
QColorSpace(QColorSpace &&colorSpace);
diff --git a/src/gui/painting/qcolorspace_p.h b/src/gui/painting/qcolorspace_p.h
index 91107a9a89..21260a281d 100644
--- a/src/gui/painting/qcolorspace_p.h
+++ b/src/gui/painting/qcolorspace_p.h
@@ -57,15 +57,41 @@
#include "qcolortrclut_p.h"
#include <QtCore/qshareddata.h>
+#include <QtCore/qpoint.h>
QT_BEGIN_NAMESPACE
+class Q_GUI_EXPORT QColorSpacePrimaries
+{
+public:
+ QColorSpacePrimaries() = default;
+ QColorSpacePrimaries(QColorSpace::Gamut gamut);
+ QColorSpacePrimaries(QPointF whitePoint,
+ QPointF redPoint,
+ QPointF greenPoint,
+ QPointF bluePoint)
+ : whitePoint(whitePoint)
+ , redPoint(redPoint)
+ , greenPoint(greenPoint)
+ , bluePoint(bluePoint)
+ { }
+
+ QColorMatrix toXyzMatrix() const;
+ bool areValid() const;
+
+ QPointF whitePoint;
+ QPointF redPoint;
+ QPointF greenPoint;
+ QPointF bluePoint;
+};
+
class QColorSpacePrivate : public QSharedData
{
public:
QColorSpacePrivate();
QColorSpacePrivate(QColorSpace::ColorSpaceId colorSpaceId);
QColorSpacePrivate(QColorSpace::Gamut gamut, QColorSpace::TransferFunction fun, float gamma);
+ QColorSpacePrivate(const QColorSpacePrimaries &primaries, QColorSpace::TransferFunction fun, float gamma);
QColorSpacePrivate(const QColorSpacePrivate &other) = default;
QColorSpacePrivate &operator=(const QColorSpacePrivate &other) = default;