Add colorspace transfer functions based on tables of inputs

This is the most basic way to represent custom transfer functions.

Change-Id: I529fb647ece82c03e85ef77b056d9daf13fe5a61
Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
bb10
Allan Sandfeld Jensen 2019-04-15 14:19:48 +02:00
parent 667e5b1210
commit 48346e8d2d
5 changed files with 173 additions and 23 deletions

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
@ -221,6 +221,29 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
setTransferFunction();
}
QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const QVector<uint16_t> &transferFunctionTable)
: primaries(primaries)
, transferFunction(QColorSpace::TransferFunction::Custom)
, gamma(0)
{
setTransferFunctionTable(transferFunctionTable);
identifyColorSpace();
initialize();
}
QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QVector<uint16_t> &transferFunctionTable)
: primaries(QColorSpace::Primaries::Custom)
, transferFunction(QColorSpace::TransferFunction::Custom)
, gamma(0)
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
whitePoint = QColorVector(primaries.whitePoint);
setTransferFunctionTable(transferFunctionTable);
identifyColorSpace();
initialize();
}
void QColorSpacePrivate::identifyColorSpace()
{
switch (primaries) {
@ -298,6 +321,32 @@ void QColorSpacePrivate::setToXyzMatrix()
whitePoint = QColorVector(colorSpacePrimaries.whitePoint);
}
void QColorSpacePrivate::setTransferFunctionTable(const QVector<uint16_t> &transferFunctionTable)
{
QColorTransferTable table(transferFunctionTable.size(), transferFunctionTable);
if (!table.isEmpty() && !table.checkValidity()) {
qWarning() << "Invalid transfer function table given to QColorSpace";
trc[0].m_type = QColorTrc::Type::Uninitialized;
return;
}
transferFunction = QColorSpace::TransferFunction::Custom;
QColorTransferFunction curve;
if (table.asColorTransferFunction(&curve)) {
// Table recognized as a specific curve
if (curve.isLinear()) {
transferFunction = QColorSpace::TransferFunction::Linear;
gamma = 1.0f;
} else if (curve.isSRgb()) {
transferFunction = QColorSpace::TransferFunction::SRgb;
}
trc[0].m_type = QColorTrc::Type::Function;
trc[0].m_fun = curve;
} else {
trc[0].m_type = QColorTrc::Type::Table;
trc[0].m_table = table;
}
}
void QColorSpacePrivate::setTransferFunction()
{
switch (transferFunction) {
@ -470,6 +519,20 @@ QColorSpace::QColorSpace(QColorSpace::Primaries primaries, float gamma)
{
}
/*!
Creates a custom color space with the primaries \a primaries, using a custom transfer function
described by \a transferFunctionTable.
The table should contain at least 2 values, and contain an monotonically increasing list
of values from 0 to 65535.
\since 6.1
*/
QColorSpace::QColorSpace(QColorSpace::Primaries gamut, const QVector<uint16_t> &transferFunctionTable)
: d_ptr(new QColorSpacePrivate(gamut, transferFunctionTable))
{
}
/*!
Creates a custom colorspace with a primaries based on the chromaticities of the primary colors \a whitePoint,
\a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a transferFunction and optionally \a gamma.
@ -486,6 +549,20 @@ QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
d_ptr = new QColorSpacePrivate(primaries, transferFunction, gamma);
}
/*!
Creates a custom color space with primaries based on the chromaticities of the primary colors \a whitePoint,
\a redPoint, \a greenPoint and \a bluePoint, and using the custom transfer function described by
\a transferFunctionTable.
\since 6.1
*/
QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
const QPointF &greenPoint, const QPointF &bluePoint,
const QVector<uint16_t> &transferFunctionTable)
: d_ptr(new QColorSpacePrivate({whitePoint, redPoint, greenPoint, bluePoint}, transferFunctionTable))
{
}
QColorSpace::~QColorSpace() = default;
QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QColorSpacePrivate)
@ -559,6 +636,27 @@ void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunc
d_ptr->setTransferFunction();
}
/*!
Sets the transfer function to \a transferFunctionTable.
\since 6.1
\sa transferFunction()
*/
void QColorSpace::setTransferFunction(const QVector<uint16_t> &transferFunctionTable)
{
if (!d_ptr) {
d_ptr = new QColorSpacePrivate(Primaries::Custom, transferFunctionTable);
d_ptr->ref.ref();
return;
}
detach();
d_ptr->description.clear();
d_ptr->setTransferFunctionTable(transferFunctionTable);
d_ptr->gamma = 0;
d_ptr->identifyColorSpace();
d_ptr->setTransferFunction();
}
/*!
Returns a copy of this color space, except using the transfer function
\a transferFunction and \a gamma.
@ -576,6 +674,22 @@ QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction tran
return out;
}
/*!
Returns a copy of this color space, except using the transfer function
described by \a transferFunctionTable.
\since 6.1
\sa transferFunction(), setTransferFunction()
*/
QColorSpace QColorSpace::withTransferFunction(const QVector<uint16_t> &transferFunctionTable) const
{
if (!isValid())
return *this;
QColorSpace out(*this);
out.setTransferFunction(transferFunctionTable);
return out;
}
/*!
Sets the primaries to those of the \a primariesId set.

View File

@ -86,9 +86,13 @@ public:
QColorSpace(NamedColorSpace namedColorSpace);
QColorSpace(Primaries primaries, TransferFunction transferFunction, float gamma = 0.0f);
QColorSpace(Primaries primaries, float gamma);
QColorSpace(Primaries primaries, const QVector<uint16_t> &transferFunctionTable);
QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
const QPointF &greenPoint, const QPointF &bluePoint,
TransferFunction transferFunction, float gamma = 0.0f);
QColorSpace(const QPointF &whitePoint, const QPointF &redPoint,
const QPointF &greenPoint, const QPointF &bluePoint,
const QVector<uint16_t> &transferFunctionTable);
~QColorSpace();
QColorSpace(const QColorSpace &colorSpace) noexcept;
@ -110,7 +114,9 @@ public:
float gamma() const noexcept;
void setTransferFunction(TransferFunction transferFunction, float gamma = 0.0f);
void setTransferFunction(const QVector<uint16_t> &transferFunctionTable);
QColorSpace withTransferFunction(TransferFunction transferFunction, float gamma = 0.0f) const;
QColorSpace withTransferFunction(const QVector<uint16_t> &transferFunctionTable) const;
void setPrimaries(Primaries primariesId);
void setPrimaries(const QPointF &whitePoint, const QPointF &redPoint,

View File

@ -92,7 +92,9 @@ public:
QColorSpacePrivate();
QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace);
QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma);
QColorSpacePrivate(QColorSpace::Primaries primaries, const QVector<uint16_t> &transferFunctionTable);
QColorSpacePrivate(const QColorSpacePrimaries &primaries, QColorSpace::TransferFunction transferFunction, float gamma);
QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QVector<uint16_t> &transferFunctionTable);
QColorSpacePrivate(const QColorSpacePrivate &other) = default;
static const QColorSpacePrivate *get(const QColorSpace &colorSpace)
@ -109,6 +111,7 @@ public:
void setToXyzMatrix();
void setTransferFunction();
void identifyColorSpace();
void setTransferFunctionTable(const QVector<uint16_t> &transferFunctionTable);
QColorTransform transformationToColorSpace(const QColorSpacePrivate *out) const;
static constexpr QColorSpace::NamedColorSpace Unknown = QColorSpace::NamedColorSpace(0);

View File

@ -55,6 +55,8 @@
#include "qcolortransferfunction_p.h"
#include <QList>
#include <algorithm>
#include <cmath>
QT_BEGIN_NAMESPACE
@ -114,11 +116,11 @@ public:
float apply(float x) const
{
x = std::min(std::max(x, 0.0f), 1.0f);
x = std::clamp(x, 0.0f, 1.0f);
x *= m_tableSize - 1;
uint32_t lo = static_cast<uint32_t>(std::floor(x));
uint32_t hi = std::min(lo + 1, m_tableSize - 1);
float frac = x - lo;
const uint32_t lo = static_cast<uint32_t>(std::floor(x));
const uint32_t hi = std::min(lo + 1, m_tableSize);
const float frac = x - lo;
if (!m_table16.isEmpty())
return (m_table16[lo] * (1.0f - frac) + m_table16[hi] * frac) * (1.0f/65535.0f);
if (!m_table8.isEmpty())
@ -135,34 +137,30 @@ public:
if (x >= 1.0f)
return 1.0f;
if (!m_table16.isEmpty()) {
float v = x * 65535.0f;
uint32_t i = std::floor(resultLargerThan * (m_tableSize - 1)) + 1;
for ( ; i < m_tableSize; ++i) {
if (m_table16[i] > v)
break;
}
const float v = x * 65535.0f;
uint32_t i = static_cast<uint32_t>(std::floor(resultLargerThan * (m_tableSize - 1))) + 1;
auto it = std::lower_bound(m_table16.cbegin() + i, m_table16.cend(), v);
i = it - m_table16.cbegin();
if (i >= m_tableSize - 1)
return 1.0f;
float y1 = m_table16[i - 1];
float y2 = m_table16[i];
const float y1 = m_table16[i - 1];
const float y2 = m_table16[i];
Q_ASSERT(x >= y1 && x < y2);
float fr = (v - y1) / (y2 - y1);
const float fr = (v - y1) / (y2 - y1);
return (i + fr) * (1.0f / (m_tableSize - 1));
}
if (!m_table8.isEmpty()) {
float v = x * 255.0f;
uint32_t i = std::floor(resultLargerThan * (m_tableSize - 1)) + 1;
for ( ; i < m_tableSize; ++i) {
if (m_table8[i] > v)
break;
}
const float v = x * 255.0f;
uint32_t i = static_cast<uint32_t>(std::floor(resultLargerThan * (m_tableSize - 1))) + 1;
auto it = std::lower_bound(m_table8.cbegin() + i, m_table8.cend(), v);
i = it - m_table8.cbegin();
if (i >= m_tableSize - 1)
return 1.0f;
float y1 = m_table8[i - 1];
float y2 = m_table8[i];
const float y1 = m_table8[i - 1];
const float y2 = m_table8[i];
Q_ASSERT(x >= y1 && x < y2);
float fr = (v - y1) / (y2 - y1);
const float fr = (v - y1) / (y2 - y1);
return (i + fr) * (1.0f / (m_tableSize - 1));
}
return x;

View File

@ -74,6 +74,8 @@ private slots:
void changeTransferFunction();
void changePrimaries();
void transferFunctionTable();
};
tst_QColorSpace::tst_QColorSpace()
@ -468,5 +470,32 @@ void tst_QColorSpace::changePrimaries()
QCOMPARE(cs, QColorSpace(QColorSpace::SRgbLinear));
}
void tst_QColorSpace::transferFunctionTable()
{
QVector<quint16> linearTable = { 0, 65535 };
// Check linearSRgb is recognized
QColorSpace linearSRgb(QColorSpace::Primaries::SRgb, linearTable);
QCOMPARE(linearSRgb.primaries(), QColorSpace::Primaries::SRgb);
QCOMPARE(linearSRgb.transferFunction(), QColorSpace::TransferFunction::Linear);
QCOMPARE(linearSRgb.gamma(), 1.0);
QCOMPARE(linearSRgb, QColorSpace::SRgbLinear);
// Check other linear is recognized
QColorSpace linearARgb(QColorSpace::Primaries::AdobeRgb, linearTable);
QCOMPARE(linearARgb.primaries(), QColorSpace::Primaries::AdobeRgb);
QCOMPARE(linearARgb.transferFunction(), QColorSpace::TransferFunction::Linear);
QCOMPARE(linearARgb.gamma(), 1.0);
// Check custom transfer function.
QVector<quint16> customTable = { 0, 10, 100, 10000, 65535 };
QColorSpace customSRgb(QColorSpace::Primaries::SRgb, customTable);
QCOMPARE(customSRgb.primaries(), QColorSpace::Primaries::SRgb);
QCOMPARE(customSRgb.transferFunction(), QColorSpace::TransferFunction::Custom);
customSRgb.setTransferFunction(linearTable);
QCOMPARE(customSRgb, QColorSpace::SRgbLinear);
}
QTEST_MAIN(tst_QColorSpace)
#include "tst_qcolorspace.moc"