diff options
Diffstat (limited to 'src/gui/painting/qcolorspace.cpp')
-rw-r--r-- | src/gui/painting/qcolorspace.cpp | 961 |
1 files changed, 789 insertions, 172 deletions
diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index 43efdc1166..7a1d34a408 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -1,51 +1,17 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qcolorspace.h" #include "qcolorspace_p.h" #include "qcolortransform.h" +#include "qcolorclut_p.h" #include "qcolormatrix_p.h" #include "qcolortransferfunction_p.h" #include "qcolortransform_p.h" #include "qicc_p.h" +#include <qatomic.h> #include <qmath.h> #include <qtransform.h> @@ -53,7 +19,19 @@ QT_BEGIN_NAMESPACE -QBasicMutex QColorSpacePrivate::s_lutWriteLock; +Q_CONSTINIT QBasicMutex QColorSpacePrivate::s_lutWriteLock; + +Q_CONSTINIT static QAtomicPointer<QColorSpacePrivate> s_predefinedColorspacePrivates[QColorSpace::ProPhotoRgb] = {}; +static void cleanupPredefinedColorspaces() +{ + for (QAtomicPointer<QColorSpacePrivate> &ptr : s_predefinedColorspacePrivates) { + QColorSpacePrivate *prv = ptr.fetchAndStoreAcquire(nullptr); + if (prv && !prv->ref.deref()) + delete prv; + } +} + +Q_DESTRUCTOR_FUNCTION(cleanupPredefinedColorspaces) QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Primaries primaries) { @@ -103,45 +81,19 @@ bool QColorSpacePrimaries::areValid() const QColorMatrix QColorSpacePrimaries::toXyzMatrix() const { // This converts to XYZ in some undefined scale. - QColorMatrix toXyz = { QColorVector(redPoint), - QColorVector(greenPoint), - QColorVector(bluePoint) }; + QColorMatrix toXyz = { QColorVector::fromXYChromaticity(redPoint), + QColorVector::fromXYChromaticity(greenPoint), + QColorVector::fromXYChromaticity(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); + const auto wXyz = QColorVector::fromXYChromaticity(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; } @@ -151,6 +103,7 @@ QColorSpacePrivate::QColorSpacePrivate() QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace) : namedColorSpace(namedColorSpace) + , colorModel(QColorSpace::ColorModel::Rgb) { switch (namedColorSpace) { case QColorSpace::SRgb: @@ -185,9 +138,10 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp initialize(); } -QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction fun, float gamma) +QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma) : primaries(primaries) - , transferFunction(fun) + , transferFunction(transferFunction) + , colorModel(QColorSpace::ColorModel::Rgb) , gamma(gamma) { identifyColorSpace(); @@ -195,19 +149,97 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorS } QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, - QColorSpace::TransferFunction fun, + QColorSpace::TransferFunction transferFunction, float gamma) : primaries(QColorSpace::Primaries::Custom) - , transferFunction(fun) + , transferFunction(transferFunction) + , colorModel(QColorSpace::ColorModel::Rgb) , gamma(gamma) + , whitePoint(QColorVector::fromXYChromaticity(primaries.whitePoint)) { Q_ASSERT(primaries.areValid()); toXyz = primaries.toXyzMatrix(); - whitePoint = QColorVector(primaries.whitePoint); + chad = QColorMatrix::chromaticAdaptation(whitePoint); + toXyz = chad * toXyz; + identifyColorSpace(); setTransferFunction(); } +QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint, + QColorSpace::TransferFunction transferFunction, + float gamma) + : primaries(QColorSpace::Primaries::Custom) + , transferFunction(transferFunction) + , colorModel(QColorSpace::ColorModel::Gray) + , gamma(gamma) + , whitePoint(QColorVector::fromXYChromaticity(whitePoint)) +{ + chad = QColorMatrix::chromaticAdaptation(this->whitePoint); + toXyz = chad; + setTransferFunction(); +} + +QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable) + : primaries(QColorSpace::Primaries::Custom) + , transferFunction(QColorSpace::TransferFunction::Custom) + , colorModel(QColorSpace::ColorModel::Gray) + , gamma(0) + , whitePoint(QColorVector::fromXYChromaticity(whitePoint)) +{ + chad = QColorMatrix::chromaticAdaptation(this->whitePoint); + toXyz = chad; + setTransferFunctionTable(transferFunctionTable); + setTransferFunction(); +} + +QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, const QList<uint16_t> &transferFunctionTable) + : primaries(primaries) + , transferFunction(QColorSpace::TransferFunction::Custom) + , colorModel(QColorSpace::ColorModel::Rgb) + , gamma(0) +{ + setTransferFunctionTable(transferFunctionTable); + identifyColorSpace(); + initialize(); +} + +QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, const QList<uint16_t> &transferFunctionTable) + : primaries(QColorSpace::Primaries::Custom) + , transferFunction(QColorSpace::TransferFunction::Custom) + , colorModel(QColorSpace::ColorModel::Rgb) + , gamma(0) + , whitePoint(QColorVector::fromXYChromaticity(primaries.whitePoint)) +{ + Q_ASSERT(primaries.areValid()); + toXyz = primaries.toXyzMatrix(); + chad = QColorMatrix::chromaticAdaptation(whitePoint); + toXyz = chad * toXyz; + setTransferFunctionTable(transferFunctionTable); + identifyColorSpace(); + initialize(); +} + +QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, + const QList<uint16_t> &redTransferFunctionTable, + const QList<uint16_t> &greenTransferFunctionTable, + const QList<uint16_t> &blueTransferFunctionTable) + : primaries(QColorSpace::Primaries::Custom) + , transferFunction(QColorSpace::TransferFunction::Custom) + , colorModel(QColorSpace::ColorModel::Rgb) + , gamma(0) +{ + Q_ASSERT(primaries.areValid()); + toXyz = primaries.toXyzMatrix(); + whitePoint = QColorVector::fromXYChromaticity(primaries.whitePoint); + chad = QColorMatrix::chromaticAdaptation(whitePoint); + toXyz = chad * toXyz; + setTransferFunctionTables(redTransferFunctionTable, + greenTransferFunctionTable, + blueTransferFunctionTable); + identifyColorSpace(); +} + void QColorSpacePrivate::identifyColorSpace() { switch (primaries) { @@ -282,7 +314,76 @@ void QColorSpacePrivate::setToXyzMatrix() } QColorSpacePrimaries colorSpacePrimaries(primaries); toXyz = colorSpacePrimaries.toXyzMatrix(); - whitePoint = QColorVector(colorSpacePrimaries.whitePoint); + whitePoint = QColorVector::fromXYChromaticity(colorSpacePrimaries.whitePoint); + chad = QColorMatrix::chromaticAdaptation(whitePoint); + toXyz = chad * toXyz; +} + +void QColorSpacePrivate::setTransferFunctionTable(const QList<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.isIdentity()) { + 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::setTransferFunctionTables(const QList<uint16_t> &redTransferFunctionTable, + const QList<uint16_t> &greenTransferFunctionTable, + const QList<uint16_t> &blueTransferFunctionTable) +{ + QColorTransferTable redTable(redTransferFunctionTable.size(), redTransferFunctionTable); + QColorTransferTable greenTable(greenTransferFunctionTable.size(), greenTransferFunctionTable); + QColorTransferTable blueTable(blueTransferFunctionTable.size(), blueTransferFunctionTable); + if (!redTable.isEmpty() && !greenTable.isEmpty() && !blueTable.isEmpty() && + !redTable.checkValidity() && !greenTable.checkValidity() && !blueTable.checkValidity()) { + qWarning() << "Invalid transfer function table given to QColorSpace"; + trc[0].m_type = QColorTrc::Type::Uninitialized; + trc[1].m_type = QColorTrc::Type::Uninitialized; + trc[2].m_type = QColorTrc::Type::Uninitialized; + return; + } + transferFunction = QColorSpace::TransferFunction::Custom; + QColorTransferFunction curve; + if (redTable.asColorTransferFunction(&curve)) { + trc[0].m_type = QColorTrc::Type::Function; + trc[0].m_fun = curve; + } else { + trc[0].m_type = QColorTrc::Type::Table; + trc[0].m_table = redTable; + } + if (greenTable.asColorTransferFunction(&curve)) { + trc[1].m_type = QColorTrc::Type::Function; + trc[1].m_fun = curve; + } else { + trc[1].m_type = QColorTrc::Type::Table; + trc[1].m_table = greenTable; + } + if (blueTable.asColorTransferFunction(&curve)) { + trc[2].m_type = QColorTrc::Type::Function; + trc[2].m_fun = curve; + } else { + trc[2].m_type = QColorTrc::Type::Table; + trc[2].m_table = blueTable; + } + lut.generated.storeRelease(0); } void QColorSpacePrivate::setTransferFunction() @@ -318,6 +419,7 @@ void QColorSpacePrivate::setTransferFunction() } trc[1] = trc[0]; trc[2] = trc[0]; + lut.generated.storeRelease(0); } QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpacePrivate *out) const @@ -326,13 +428,52 @@ QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpace QColorTransform combined; auto ptr = new QColorTransformPrivate; combined.d = ptr; - combined.d->ref.ref(); ptr->colorSpaceIn = this; ptr->colorSpaceOut = out; - ptr->colorMatrix = out->toXyz.inverted() * toXyz; + if (isThreeComponentMatrix()) + ptr->colorMatrix = toXyz; + else + ptr->colorMatrix = QColorMatrix::identity(); + if (out->isThreeComponentMatrix()) + ptr->colorMatrix = out->toXyz.inverted() * ptr->colorMatrix; + if (ptr->isIdentity()) + return QColorTransform(); return combined; } +QColorTransform QColorSpacePrivate::transformationToXYZ() const +{ + QColorTransform transform; + auto ptr = new QColorTransformPrivate; + transform.d = ptr; + ptr->colorSpaceIn = this; + ptr->colorSpaceOut = this; + // Convert to XYZ relative to our white point, not the regular D50 white point. + if (isThreeComponentMatrix()) + ptr->colorMatrix = QColorMatrix::chromaticAdaptation(whitePoint).inverted() * toXyz; + else + ptr->colorMatrix = QColorMatrix::identity(); + return transform; +} + +bool QColorSpacePrivate::isThreeComponentMatrix() const +{ + return transformModel == QColorSpace::TransformModel::ThreeComponentMatrix; +} + +void QColorSpacePrivate::clearElementListProcessingForEdit() +{ + Q_ASSERT(transformModel == QColorSpace::TransformModel::ElementListProcessing); + Q_ASSERT(primaries == QColorSpace::Primaries::Custom); + Q_ASSERT(transferFunction == QColorSpace::TransferFunction::Custom); + + transformModel = QColorSpace::TransformModel::ThreeComponentMatrix; + colorModel = QColorSpace::ColorModel::Rgb; + isPcsLab = false; + mAB.clear(); + mBA.clear(); +} + /*! \class QColorSpace \brief The QColorSpace class provides a color space abstraction. @@ -355,9 +496,10 @@ QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpace A color space can generally speaking be conceived as a combination of set of primary colors and a transfer function. The primaries defines the axes of the color space, and the transfer function how values are mapped on the axes. - The primaries are defined by three primary colors that represent exactly how red, green, - and blue look in this particular color space, and a white color that represents where - and how bright pure white is. The range of colors expressable by the primary colors is + The primaries are for ColorModel::Rgb color spaces defined by three primary colors that + represent exactly how red, green, and blue look in this particular color space, and a white + color that represents where and how bright pure white is. For grayscale color spaces, only + a single white primary is needed. The range of colors expressible by the primary colors is called the gamut, and a color space that can represent a wider range of colors is also known as a wide-gamut color space. @@ -411,11 +553,39 @@ QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpace */ /*! + \enum QColorSpace::TransformModel + \since 6.8 + + Defines the processing model used for color space transforms. + + \value ThreeComponentMatrix The transform consist of a matrix calculated from primaries and set of transfer functions + for each color channel. This is very fast and used by all predefined color spaces. Any color space on this form is + reversible and always both valid sources and targets. + \value ElementListProcessing The transforms are one or two lists of processing elements that can do many things, + each list only process either to the connection color space or from it. This is very flexible, but rather + slow, and can only be set by reading ICC profiles (See \l fromIccProfile()). Since the two lists are + separate a color space on this form can be a valid source, but not necessarily also a valid target. When changing + either primaries or transfer function on a color space on this type it will reset to an empty ThreeComponentMatrix form. +*/ + +/*! + \enum QColorSpace::ColorModel + \since 6.8 + + Defines the color model used by the color space data. + + \value Undefined No color model + \value Rgb An RGB color model with red, green, and blue colors. Can apply to RGB and grayscale data. + \value Gray A gray scale color model. Can only apply to grayscale data. + \value Cmyk Can only represent color data defined with cyan, magenta, yellow, and black colors. + In effect only QImage::Format_CMYK32. Note Cmyk color spaces will be TransformModel::ElementListProcessing. +*/ + +/*! + \fn QColorSpace::QColorSpace() + Creates a new colorspace object that represents an undefined and invalid colorspace. */ -QColorSpace::QColorSpace() -{ -} /*! Creates a new colorspace object that represents a \a namedColorSpace. @@ -426,24 +596,28 @@ QColorSpace::QColorSpace(NamedColorSpace namedColorSpace) qWarning() << "QColorSpace attempted constructed from invalid QColorSpace::NamedColorSpace: " << int(namedColorSpace); return; } - static QColorSpacePrivate *predefinedColorspacePrivates[QColorSpace::ProPhotoRgb + 1]; - if (!predefinedColorspacePrivates[namedColorSpace]) { - predefinedColorspacePrivates[namedColorSpace] = new QColorSpacePrivate(namedColorSpace); - predefinedColorspacePrivates[namedColorSpace]->ref.ref(); + // The defined namespaces start at 1: + auto &atomicRef = s_predefinedColorspacePrivates[static_cast<int>(namedColorSpace) - 1]; + QColorSpacePrivate *cspriv = atomicRef.loadAcquire(); + if (!cspriv) { + auto *tmp = new QColorSpacePrivate(namedColorSpace); + tmp->ref.ref(); + if (atomicRef.testAndSetOrdered(nullptr, tmp, cspriv)) + cspriv = tmp; + else + delete tmp; } - d_ptr = predefinedColorspacePrivates[namedColorSpace]; - d_ptr->ref.ref(); + d_ptr = cspriv; Q_ASSERT(isValid()); } /*! - Creates a custom color space with the primaries \a primaries, using the transfer function \a fun and + Creates a custom color space with the primaries \a primaries, using the transfer function \a transferFunction and optionally \a gamma. */ -QColorSpace::QColorSpace(QColorSpace::Primaries primaries, QColorSpace::TransferFunction fun, float gamma) - : d_ptr(new QColorSpacePrivate(primaries, fun, gamma)) +QColorSpace::QColorSpace(QColorSpace::Primaries primaries, QColorSpace::TransferFunction transferFunction, float gamma) + : d_ptr(new QColorSpacePrivate(primaries, transferFunction, gamma)) { - d_ptr->ref.ref(); } /*! @@ -453,51 +627,99 @@ QColorSpace::QColorSpace(QColorSpace::Primaries primaries, QColorSpace::Transfer QColorSpace::QColorSpace(QColorSpace::Primaries primaries, float gamma) : d_ptr(new QColorSpacePrivate(primaries, TransferFunction::Gamma, gamma)) { - d_ptr->ref.ref(); +} + +/*! + Creates a custom color space with the primaries \a gamut, 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 QList<uint16_t> &transferFunctionTable) + : d_ptr(new QColorSpacePrivate(gamut, transferFunctionTable)) +{ +} + +/*! + Creates a custom grayscale color space with the white point \a whitePoint, using the transfer function \a transferFunction and + optionally \a gamma. + + \since 6.8 +*/ +QColorSpace::QColorSpace(const QPointF &whitePoint, TransferFunction transferFunction, float gamma) + : d_ptr(new QColorSpacePrivate(whitePoint, transferFunction, gamma)) +{ +} + +/*! + Creates a custom grayscale color space with white point \a whitePoint, and using the custom transfer function described by + \a transferFunctionTable. + + \since 6.8 +*/ +QColorSpace::QColorSpace(const QPointF &whitePoint, const QList<uint16_t> &transferFunctionTable) + : d_ptr(new QColorSpacePrivate(whitePoint, 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 fun and optionally \a gamma. + \a redPoint, \a greenPoint and \a bluePoint, and using the transfer function \a transferFunction and optionally \a gamma. */ QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint, const QPointF &greenPoint, const QPointF &bluePoint, - QColorSpace::TransferFunction fun, float gamma) + QColorSpace::TransferFunction transferFunction, float gamma) { QColorSpacePrimaries primaries(whitePoint, redPoint, greenPoint, bluePoint); if (!primaries.areValid()) { qWarning() << "QColorSpace attempted constructed from invalid primaries:" << whitePoint << redPoint << greenPoint << bluePoint; - d_ptr = nullptr; return; } - d_ptr = new QColorSpacePrivate(primaries, fun, gamma); - d_ptr->ref.ref(); + d_ptr = new QColorSpacePrivate(primaries, transferFunction, gamma); } -QColorSpace::~QColorSpace() -{ - if (d_ptr && !d_ptr->ref.deref()) - delete d_ptr; -} +/*! + 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. -QColorSpace::QColorSpace(const QColorSpace &colorSpace) - : d_ptr(colorSpace.d_ptr) + \since 6.1 + */ +QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint, + const QPointF &greenPoint, const QPointF &bluePoint, + const QList<uint16_t> &transferFunctionTable) + : d_ptr(new QColorSpacePrivate({whitePoint, redPoint, greenPoint, bluePoint}, transferFunctionTable)) { - if (d_ptr) - d_ptr->ref.ref(); } -QColorSpace &QColorSpace::operator=(const QColorSpace &colorSpace) +/*! + 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 functions described by + \a redTransferFunctionTable, \a greenTransferFunctionTable, and \a blueTransferFunctionTable. + + \since 6.1 + */ +QColorSpace::QColorSpace(const QPointF &whitePoint, const QPointF &redPoint, + const QPointF &greenPoint, const QPointF &bluePoint, + const QList<uint16_t> &redTransferFunctionTable, + const QList<uint16_t> &greenTransferFunctionTable, + const QList<uint16_t> &blueTransferFunctionTable) + : d_ptr(new QColorSpacePrivate({whitePoint, redPoint, greenPoint, bluePoint}, + redTransferFunctionTable, + greenTransferFunctionTable, + blueTransferFunctionTable)) { - QColorSpacePrivate *oldD = d_ptr; - d_ptr = colorSpace.d_ptr; - if (d_ptr) - d_ptr->ref.ref(); - if (oldD && !oldD->ref.deref()) - delete oldD; - return *this; } +QColorSpace::~QColorSpace() = default; + +QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QColorSpacePrivate) + +QColorSpace::QColorSpace(const QColorSpace &colorSpace) noexcept = default; + /*! \fn void QColorSpace::swap(QColorSpace &other) Swaps color space \a other with this color space. This operation is very fast and @@ -553,13 +775,15 @@ void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunc return; if (!d_ptr) { d_ptr = new QColorSpacePrivate(Primaries::Custom, transferFunction, gamma); - d_ptr->ref.ref(); return; } if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma) return; - QColorSpacePrivate::getWritable(*this); // detach - d_ptr->description.clear(); + detach(); + if (d_ptr->transformModel == TransformModel::ElementListProcessing) + d_ptr->clearElementListProcessingForEdit(); + d_ptr->iccProfile = {}; + d_ptr->description = QString(); d_ptr->transferFunction = transferFunction; d_ptr->gamma = gamma; d_ptr->identifyColorSpace(); @@ -567,6 +791,61 @@ void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunc } /*! + Sets the transfer function to \a transferFunctionTable. + + \since 6.1 + \sa withTransferFunction() +*/ +void QColorSpace::setTransferFunction(const QList<uint16_t> &transferFunctionTable) +{ + if (!d_ptr) { + d_ptr = new QColorSpacePrivate(Primaries::Custom, transferFunctionTable); + d_ptr->ref.ref(); + return; + } + detach(); + if (d_ptr->transformModel == TransformModel::ElementListProcessing) + d_ptr->clearElementListProcessingForEdit(); + d_ptr->iccProfile = {}; + d_ptr->description = QString(); + d_ptr->setTransferFunctionTable(transferFunctionTable); + d_ptr->gamma = 0; + d_ptr->identifyColorSpace(); + d_ptr->setTransferFunction(); +} + +/*! + Sets the transfer functions to \a redTransferFunctionTable, + \a greenTransferFunctionTable and \a blueTransferFunctionTable. + + \since 6.1 + \sa withTransferFunctions() +*/ +void QColorSpace::setTransferFunctions(const QList<uint16_t> &redTransferFunctionTable, + const QList<uint16_t> &greenTransferFunctionTable, + const QList<uint16_t> &blueTransferFunctionTable) +{ + if (!d_ptr) { + d_ptr = new QColorSpacePrivate(); + d_ptr->setTransferFunctionTables(redTransferFunctionTable, + greenTransferFunctionTable, + blueTransferFunctionTable); + d_ptr->ref.ref(); + return; + } + detach(); + if (d_ptr->transformModel == TransformModel::ElementListProcessing) + d_ptr->clearElementListProcessingForEdit(); + d_ptr->iccProfile = {}; + d_ptr->description = QString(); + d_ptr->setTransferFunctionTables(redTransferFunctionTable, + greenTransferFunctionTable, + blueTransferFunctionTable); + d_ptr->gamma = 0; + d_ptr->identifyColorSpace(); +} + +/*! Returns a copy of this color space, except using the transfer function \a transferFunction and \a gamma. @@ -574,7 +853,7 @@ void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunc */ QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) const { - if (!isValid() || transferFunction == QColorSpace::TransferFunction::Custom) + if (!isValid() || transferFunction == TransferFunction::Custom) return *this; if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma) return *this; @@ -584,6 +863,41 @@ QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction tran } /*! + 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 QList<uint16_t> &transferFunctionTable) const +{ + if (!isValid()) + return *this; + QColorSpace out(*this); + out.setTransferFunction(transferFunctionTable); + return out; +} + +/*! + Returns a copy of this color space, except using the transfer functions + described by \a redTransferFunctionTable, \a greenTransferFunctionTable and + \a blueTransferFunctionTable. + + \since 6.1 + \sa setTransferFunctions() +*/ +QColorSpace QColorSpace::withTransferFunctions(const QList<uint16_t> &redTransferFunctionTable, + const QList<uint16_t> &greenTransferFunctionTable, + const QList<uint16_t> &blueTransferFunctionTable) const +{ + if (!isValid()) + return *this; + QColorSpace out(*this); + out.setTransferFunctions(redTransferFunctionTable, greenTransferFunctionTable, blueTransferFunctionTable); + return out; +} + +/*! Sets the primaries to those of the \a primariesId set. \sa primaries() @@ -594,14 +908,17 @@ void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId) return; if (!d_ptr) { d_ptr = new QColorSpacePrivate(primariesId, TransferFunction::Custom, 0.0f); - d_ptr->ref.ref(); return; } if (d_ptr->primaries == primariesId) return; - QColorSpacePrivate::getWritable(*this); // detach - d_ptr->description.clear(); + detach(); + if (d_ptr->transformModel == TransformModel::ElementListProcessing) + d_ptr->clearElementListProcessingForEdit(); + d_ptr->iccProfile = {}; + d_ptr->description = QString(); d_ptr->primaries = primariesId; + d_ptr->colorModel = QColorSpace::ColorModel::Rgb; d_ptr->identifyColorSpace(); d_ptr->setToXyzMatrix(); } @@ -620,21 +937,114 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin return; if (!d_ptr) { d_ptr = new QColorSpacePrivate(primaries, TransferFunction::Custom, 0.0f); - d_ptr->ref.ref(); return; } QColorMatrix toXyz = primaries.toXyzMatrix(); - if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz) + QColorMatrix chad = QColorMatrix::chromaticAdaptation(QColorVector::fromXYChromaticity(whitePoint)); + toXyz = chad * toXyz; + if (QColorVector::fromXYChromaticity(primaries.whitePoint) == d_ptr->whitePoint + && toXyz == d_ptr->toXyz && chad == d_ptr->chad) return; - QColorSpacePrivate::getWritable(*this); // detach - d_ptr->description.clear(); + detach(); + if (d_ptr->transformModel == TransformModel::ElementListProcessing) + d_ptr->clearElementListProcessingForEdit(); + d_ptr->iccProfile = {}; + d_ptr->description = QString(); d_ptr->primaries = QColorSpace::Primaries::Custom; + d_ptr->colorModel = QColorSpace::ColorModel::Rgb; d_ptr->toXyz = toXyz; - d_ptr->whitePoint = QColorVector(primaries.whitePoint); + d_ptr->chad = chad; + d_ptr->whitePoint = QColorVector::fromXYChromaticity(primaries.whitePoint); + d_ptr->identifyColorSpace(); +} + +/*! + Returns the white point used for this color space. Returns a null QPointF if not defined. + + \since 6.8 +*/ +QPointF QColorSpace::whitePoint() const +{ + if (Q_UNLIKELY(!d_ptr)) + return QPointF(); + return d_ptr->whitePoint.toChromaticity(); +} + +/*! + Sets the white point to used for this color space to \a whitePoint. + + \since 6.8 +*/ +void QColorSpace::setWhitePoint(const QPointF &whitePoint) +{ + if (Q_UNLIKELY(!d_ptr)) { + d_ptr = new QColorSpacePrivate(whitePoint, TransferFunction::Custom, 0.0f); + return; + } + if (QColorVector::fromXYChromaticity(whitePoint) == d_ptr->whitePoint) + return; + detach(); + if (d_ptr->transformModel == TransformModel::ElementListProcessing) + d_ptr->clearElementListProcessingForEdit(); + d_ptr->iccProfile = {}; + d_ptr->description = QString(); + d_ptr->primaries = QColorSpace::Primaries::Custom; + // An RGB color model stays RGB, a gray stays gray, but an undefined one can now be considered gray + if (d_ptr->colorModel == QColorSpace::ColorModel::Undefined) + d_ptr->colorModel = QColorSpace::ColorModel::Gray; + QColorVector wXyz(QColorVector::fromXYChromaticity(whitePoint)); + if (d_ptr->transformModel == QColorSpace::TransformModel::ThreeComponentMatrix) { + if (d_ptr->colorModel == QColorSpace::ColorModel::Rgb) { + // Rescale toXyz to new whitepoint + QColorMatrix rawToXyz = d_ptr->chad.inverted() * d_ptr->toXyz; + QColorVector whiteScale = rawToXyz.inverted().map(wXyz); + rawToXyz = rawToXyz * QColorMatrix::fromScale(whiteScale); + d_ptr->chad = QColorMatrix::chromaticAdaptation(wXyz); + d_ptr->toXyz = d_ptr->chad * rawToXyz; + } else if (d_ptr->colorModel == QColorSpace::ColorModel::Gray) { + d_ptr->chad = d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz); + } + } + d_ptr->whitePoint = wXyz; d_ptr->identifyColorSpace(); } /*! + Returns the transfrom processing model used for this color space. + + \since 6.8 +*/ +QColorSpace::TransformModel QColorSpace::transformModel() const noexcept +{ + if (Q_UNLIKELY(!d_ptr)) + return QColorSpace::TransformModel::ThreeComponentMatrix; + return d_ptr->transformModel; +} + +/*! + Returns the color model this color space can represent + + \since 6.8 +*/ +QColorSpace::ColorModel QColorSpace::colorModel() const noexcept +{ + if (Q_UNLIKELY(!d_ptr)) + return QColorSpace::ColorModel::Undefined; + return d_ptr->colorModel; +} + +/*! + \internal +*/ +void QColorSpace::detach() +{ + if (d_ptr) + d_ptr.detach(); + else + d_ptr = new QColorSpacePrivate; +} + +/*! Returns an ICC profile representing the color space. If the color space was generated from an ICC profile, that profile @@ -661,14 +1071,11 @@ QByteArray QColorSpace::iccProfile() const Creates a QColorSpace from ICC profile \a iccProfile. \note Not all ICC profiles are supported. QColorSpace only supports - RGB-XYZ ICC profiles that are three-component matrix-based. + RGB or Gray ICC profiles. If the ICC profile is not supported an invalid QColorSpace is returned where you can still read the original ICC profile using iccProfile(). - \note If the QByteArray data is created from external sources it should be - at least 4 byte aligned. - \sa iccProfile() */ QColorSpace QColorSpace::fromIccProfile(const QByteArray &iccProfile) @@ -676,90 +1083,232 @@ QColorSpace QColorSpace::fromIccProfile(const QByteArray &iccProfile) QColorSpace colorSpace; if (QIcc::fromIccProfile(iccProfile, &colorSpace)) return colorSpace; - QColorSpacePrivate *d = QColorSpacePrivate::getWritable(colorSpace); - d->iccProfile = iccProfile; + colorSpace.detach(); + colorSpace.d_ptr->iccProfile = iccProfile; return colorSpace; } /*! - Returns \c true if the color space is valid. + Returns \c true if the color space is valid. For a color space with \c TransformModel::ThreeComponentMatrix + that means both primaries and transfer functions set, and implies isValidTarget(). + For a color space with \c TransformModel::ElementListProcessing it means it has a valid source transform, to + check if it also a valid target color space use isValidTarget(). + + \sa isValidTarget() */ bool QColorSpace::isValid() const noexcept { - return d_ptr - && d_ptr->toXyz.isValid() - && d_ptr->trc[0].isValid() && d_ptr->trc[1].isValid() && d_ptr->trc[2].isValid(); + if (!d_ptr) + return false; + return d_ptr->isValid(); } /*! - \relates QColorSpace + \since 6.8 + + Returns \c true if the color space is a valid target color space. +*/ +bool QColorSpace::isValidTarget() const noexcept +{ + if (!d_ptr) + return false; + if (!d_ptr->isThreeComponentMatrix()) + return !d_ptr->mBA.isEmpty(); + return d_ptr->isValid(); +} + +/*! + \internal +*/ +bool QColorSpacePrivate::isValid() const noexcept +{ + if (!isThreeComponentMatrix()) + return !mAB.isEmpty(); + if (!toXyz.isValid()) + return false; + if (colorModel == QColorSpace::ColorModel::Gray) { + if (!trc[0].isValid()) + return false; + } else { + if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid()) + return false; + } + return true; +} + +/*! + \fn bool QColorSpace::operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2) + Returns \c true if colorspace \a colorSpace1 is equal to colorspace \a colorSpace2; otherwise returns \c false */ -bool operator==(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2) + +/*! + \fn bool QColorSpace::operator!=(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2) + + Returns \c true if colorspace \a colorSpace1 is not equal to colorspace \a colorSpace2; + otherwise returns \c false +*/ + +static bool compareElement(const QColorSpacePrivate::TransferElement &element, + const QColorSpacePrivate::TransferElement &other) { - if (colorSpace1.d_ptr == colorSpace2.d_ptr) + return element.trc[0] == other.trc[0] + && element.trc[1] == other.trc[1] + && element.trc[2] == other.trc[2] + && element.trc[3] == other.trc[3]; +} + +static bool compareElement(const QColorMatrix &element, + const QColorMatrix &other) +{ + return element == other; +} + +static bool compareElement(const QColorVector &element, + const QColorVector &other) +{ + return element == other; +} + +static bool compareElement(const QColorCLUT &element, + const QColorCLUT &other) +{ + if (element.gridPointsX != other.gridPointsX) + return false; + if (element.gridPointsY != other.gridPointsY) + return false; + if (element.gridPointsZ != other.gridPointsZ) + return false; + if (element.gridPointsW != other.gridPointsW) + return false; + if (element.table.size() != other.table.size()) + return false; + for (qsizetype i = 0; i < element.table.size(); ++i) { + if (element.table[i] != other.table[i]) + return false; + } + return true; +} + +template<typename T> +static bool compareElements(const T &element, const QColorSpacePrivate::Element &other) +{ + return compareElement(element, std::get<T>(other)); +} + +/*! + \internal +*/ +bool QColorSpace::equals(const QColorSpace &other) const +{ + if (d_ptr == other.d_ptr) return true; - if (!colorSpace1.d_ptr || !colorSpace2.d_ptr) + if (!d_ptr) + return false; + return d_ptr->equals(other.d_ptr.constData()); +} + +/*! + \internal +*/ +bool QColorSpacePrivate::equals(const QColorSpacePrivate *other) const +{ + if (!other) return false; - if (colorSpace1.d_ptr->namedColorSpace && colorSpace2.d_ptr->namedColorSpace) - return colorSpace1.d_ptr->namedColorSpace == colorSpace2.d_ptr->namedColorSpace; + if (namedColorSpace && other->namedColorSpace) + return namedColorSpace == other->namedColorSpace; - const bool valid1 = colorSpace1.isValid(); - const bool valid2 = colorSpace2.isValid(); + const bool valid1 = isValid(); + const bool valid2 = other->isValid(); if (valid1 != valid2) return false; if (!valid1 && !valid2) { - if (!colorSpace1.d_ptr->iccProfile.isEmpty() || !colorSpace2.d_ptr->iccProfile.isEmpty()) - return colorSpace1.d_ptr->iccProfile == colorSpace2.d_ptr->iccProfile; + if (!iccProfile.isEmpty() || !other->iccProfile.isEmpty()) + return iccProfile == other->iccProfile; + return false; } // At this point one or both color spaces are unknown, and must be compared in detail instead - if (colorSpace1.primaries() != QColorSpace::Primaries::Custom && colorSpace2.primaries() != QColorSpace::Primaries::Custom) { - if (colorSpace1.primaries() != colorSpace2.primaries()) + if (transformModel != other->transformModel) + return false; + + if (!isThreeComponentMatrix()) { + if (isPcsLab != other->isPcsLab) + return false; + if (colorModel != other->colorModel) + return false; + if (mAB.count() != other->mAB.count()) + return false; + if (mBA.count() != other->mBA.count()) + return false; + + // Compare element types + for (qsizetype i = 0; i < mAB.count(); ++i) { + if (mAB[i].index() != other->mAB[i].index()) + return false; + } + for (qsizetype i = 0; i < mBA.count(); ++i) { + if (mBA[i].index() != other->mBA[i].index()) + return false; + } + + // Compare element contents + for (qsizetype i = 0; i < mAB.count(); ++i) { + if (!std::visit([&](auto &&elm) { return compareElements(elm, other->mAB[i]); }, mAB[i])) + return false; + } + for (qsizetype i = 0; i < mBA.count(); ++i) { + if (!std::visit([&](auto &&elm) { return compareElements(elm, other->mBA[i]); }, mBA[i])) + return false; + } + + return true; + } + + if (primaries != QColorSpace::Primaries::Custom && other->primaries != QColorSpace::Primaries::Custom) { + if (primaries != other->primaries) return false; } else { - if (colorSpace1.d_ptr->toXyz != colorSpace2.d_ptr->toXyz) + if (toXyz != other->toXyz) return false; } - if (colorSpace1.transferFunction() != QColorSpace::TransferFunction::Custom && - colorSpace2.transferFunction() != QColorSpace::TransferFunction::Custom) { - if (colorSpace1.transferFunction() != colorSpace2.transferFunction()) + if (transferFunction != QColorSpace::TransferFunction::Custom && other->transferFunction != QColorSpace::TransferFunction::Custom) { + if (transferFunction != other->transferFunction) return false; - if (colorSpace1.transferFunction() == QColorSpace::TransferFunction::Gamma) - return (qAbs(colorSpace1.gamma() - colorSpace2.gamma()) <= (1.0f / 512.0f)); + if (transferFunction == QColorSpace::TransferFunction::Gamma) + return (qAbs(gamma - other->gamma) <= (1.0f / 512.0f)); return true; } - if (colorSpace1.d_ptr->trc[0] != colorSpace2.d_ptr->trc[0] || - colorSpace1.d_ptr->trc[1] != colorSpace2.d_ptr->trc[1] || - colorSpace1.d_ptr->trc[2] != colorSpace2.d_ptr->trc[2]) + if (trc[0] != other->trc[0] || + trc[1] != other->trc[1] || + trc[2] != other->trc[2]) return false; return true; } /*! - \fn bool operator!=(const QColorSpace &colorSpace1, const QColorSpace &colorSpace2) - \relates QColorSpace - - Returns \c true if colorspace \a colorSpace1 is not equal to colorspace \a colorSpace2; - otherwise returns \c false -*/ - -/*! Generates and returns a color space transformation from this color space to \a colorspace. */ QColorTransform QColorSpace::transformationToColorSpace(const QColorSpace &colorspace) const { - if (!isValid() || !colorspace.isValid()) + if (!isValid()) return QColorTransform(); - return d_ptr->transformationToColorSpace(colorspace.d_ptr); + if (*this == colorspace) + return QColorTransform(); + if (!colorspace.isValidTarget()) { + qWarning() << "QColorSpace::transformationToColorSpace: colorspace not a valid target"; + return QColorTransform(); + } + + return d_ptr->transformationToColorSpace(colorspace.d_ptr.get()); } /*! @@ -768,7 +1317,37 @@ QColorTransform QColorSpace::transformationToColorSpace(const QColorSpace &color */ QColorSpace::operator QVariant() const { - return QVariant(QMetaType::QColorSpace, this); + return QVariant::fromValue(*this); +} + +/*! + Returns the name or short description. If a description hasn't been given + in setDescription(), the original name of the profile is returned if the + profile is unmodified, a guessed name is returned if the profile has been + recognized as a known color space, otherwise an empty string is returned. + + \since 6.2 +*/ +QString QColorSpace::description() const noexcept +{ + if (d_ptr) + return d_ptr->userDescription.isEmpty() ? d_ptr->description : d_ptr->userDescription; + return QString(); +} + +/*! + Sets the name or short description of the color space to \a description. + + If set to empty description() will return original or guessed descriptions + instead. + + \since 6.2 +*/ +void QColorSpace::setDescription(const QString &description) +{ + detach(); + d_ptr->iccProfile = {}; + d_ptr->userDescription = description; } /***************************************************************************** @@ -810,6 +1389,28 @@ QDataStream &operator>>(QDataStream &s, QColorSpace &colorSpace) #endif // QT_NO_DATASTREAM #ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug dbg, const QColorSpacePrivate::TransferElement &) +{ + return dbg << ":Transfer"; +} +QDebug operator<<(QDebug dbg, const QColorMatrix &) +{ + return dbg << ":Matrix"; +} +QDebug operator<<(QDebug dbg, const QColorVector &) +{ + return dbg << ":Offset"; +} +QDebug operator<<(QDebug dbg, const QColorCLUT &) +{ + return dbg << ":CLUT"; +} +QDebug operator<<(QDebug dbg, const QList<QColorSpacePrivate::Element> &elements) +{ + for (auto &&element : elements) + std::visit([&](auto &&elm) { dbg << elm; }, element); + return dbg; +} QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace) { QDebugStateSaver saver(dbg); @@ -818,8 +1419,22 @@ QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace) if (colorSpace.d_ptr) { if (colorSpace.d_ptr->namedColorSpace) dbg << colorSpace.d_ptr->namedColorSpace << ", "; - dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction(); - dbg << ", gamma=" << colorSpace.gamma(); + if (!colorSpace.isValid()) { + dbg << "Invalid"; + if (!colorSpace.d_ptr->iccProfile.isEmpty()) + dbg << " with profile data"; + } else if (colorSpace.d_ptr->isThreeComponentMatrix()) { + dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction(); + dbg << ", gamma=" << colorSpace.gamma(); + } else { + if (colorSpace.d_ptr->isPcsLab) + dbg << "PCSLab, "; + else + dbg << "PCSXYZ, "; + dbg << "A2B" << colorSpace.d_ptr->mAB; + if (!colorSpace.d_ptr->mBA.isEmpty()) + dbg << ", B2A" << colorSpace.d_ptr->mBA; + } } dbg << ')'; return dbg; @@ -827,3 +1442,5 @@ QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace) #endif QT_END_NAMESPACE + +#include "moc_qcolorspace.cpp" |