| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qcolorspace.h" |
| #include "qcolorspace_p.h" |
| |
| #include "qcolortransform.h" |
| #include "qcolormatrix_p.h" |
| #include "qcolortransferfunction_p.h" |
| #include "qcolortransform_p.h" |
| #include "qicc_p.h" |
| |
| #include <qmath.h> |
| #include <qtransform.h> |
| |
| #include <qdebug.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| QBasicMutex QColorSpacePrivate::s_lutWriteLock; |
| |
| QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Primaries primaries) |
| { |
| switch (primaries) { |
| case QColorSpace::Primaries::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::Primaries::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::Primaries::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::Primaries::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() |
| { |
| } |
| |
| QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSpace) |
| : namedColorSpace(namedColorSpace) |
| { |
| switch (namedColorSpace) { |
| case QColorSpace::SRgb: |
| primaries = QColorSpace::Primaries::SRgb; |
| transferFunction = QColorSpace::TransferFunction::SRgb; |
| description = QStringLiteral("sRGB"); |
| break; |
| case QColorSpace::SRgbLinear: |
| primaries = QColorSpace::Primaries::SRgb; |
| transferFunction = QColorSpace::TransferFunction::Linear; |
| description = QStringLiteral("Linear sRGB"); |
| break; |
| case QColorSpace::AdobeRgb: |
| primaries = QColorSpace::Primaries::AdobeRgb; |
| transferFunction = QColorSpace::TransferFunction::Gamma; |
| gamma = 2.19921875f; // Not quite 2.2, see https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf |
| description = QStringLiteral("Adobe RGB"); |
| break; |
| case QColorSpace::DisplayP3: |
| primaries = QColorSpace::Primaries::DciP3D65; |
| transferFunction = QColorSpace::TransferFunction::SRgb; |
| description = QStringLiteral("Display P3"); |
| break; |
| case QColorSpace::ProPhotoRgb: |
| primaries = QColorSpace::Primaries::ProPhotoRgb; |
| transferFunction = QColorSpace::TransferFunction::ProPhotoRgb; |
| description = QStringLiteral("ProPhoto RGB"); |
| break; |
| default: |
| Q_UNREACHABLE(); |
| } |
| initialize(); |
| } |
| |
| QColorSpacePrivate::QColorSpacePrivate(QColorSpace::Primaries primaries, QColorSpace::TransferFunction fun, float gamma) |
| : primaries(primaries) |
| , transferFunction(fun) |
| , gamma(gamma) |
| { |
| identifyColorSpace(); |
| initialize(); |
| } |
| |
| QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, |
| QColorSpace::TransferFunction fun, |
| float gamma) |
| : primaries(QColorSpace::Primaries::Custom) |
| , transferFunction(fun) |
| , gamma(gamma) |
| { |
| Q_ASSERT(primaries.areValid()); |
| toXyz = primaries.toXyzMatrix(); |
| whitePoint = QColorVector(primaries.whitePoint); |
| identifyColorSpace(); |
| setTransferFunction(); |
| } |
| |
| void QColorSpacePrivate::identifyColorSpace() |
| { |
| switch (primaries) { |
| case QColorSpace::Primaries::SRgb: |
| if (transferFunction == QColorSpace::TransferFunction::SRgb) { |
| namedColorSpace = QColorSpace::SRgb; |
| if (description.isEmpty()) |
| description = QStringLiteral("sRGB"); |
| return; |
| } |
| if (transferFunction == QColorSpace::TransferFunction::Linear) { |
| namedColorSpace = QColorSpace::SRgbLinear; |
| if (description.isEmpty()) |
| description = QStringLiteral("Linear sRGB"); |
| return; |
| } |
| break; |
| case QColorSpace::Primaries::AdobeRgb: |
| if (transferFunction == QColorSpace::TransferFunction::Gamma) { |
| if (qAbs(gamma - 2.19921875f) < (1/1024.0f)) { |
| namedColorSpace = QColorSpace::AdobeRgb; |
| if (description.isEmpty()) |
| description = QStringLiteral("Adobe RGB"); |
| return; |
| } |
| } |
| break; |
| case QColorSpace::Primaries::DciP3D65: |
| if (transferFunction == QColorSpace::TransferFunction::SRgb) { |
| namedColorSpace = QColorSpace::DisplayP3; |
| if (description.isEmpty()) |
| description = QStringLiteral("Display P3"); |
| return; |
| } |
| break; |
| case QColorSpace::Primaries::ProPhotoRgb: |
| if (transferFunction == QColorSpace::TransferFunction::ProPhotoRgb) { |
| namedColorSpace = QColorSpace::ProPhotoRgb; |
| if (description.isEmpty()) |
| description = QStringLiteral("ProPhoto RGB"); |
| return; |
| } |
| if (transferFunction == QColorSpace::TransferFunction::Gamma) { |
| // ProPhoto RGB's curve is effectively gamma 1.8 for 8bit precision. |
| if (qAbs(gamma - 1.8f) < (1/1024.0f)) { |
| namedColorSpace = QColorSpace::ProPhotoRgb; |
| if (description.isEmpty()) |
| description = QStringLiteral("ProPhoto RGB"); |
| return; |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| namedColorSpace = Unknown; |
| } |
| |
| void QColorSpacePrivate::initialize() |
| { |
| setToXyzMatrix(); |
| setTransferFunction(); |
| } |
| |
| void QColorSpacePrivate::setToXyzMatrix() |
| { |
| if (primaries == QColorSpace::Primaries::Custom) { |
| toXyz = QColorMatrix(); |
| whitePoint = QColorVector::D50(); |
| return; |
| } |
| QColorSpacePrimaries colorSpacePrimaries(primaries); |
| toXyz = colorSpacePrimaries.toXyzMatrix(); |
| whitePoint = QColorVector(colorSpacePrimaries.whitePoint); |
| } |
| |
| void QColorSpacePrivate::setTransferFunction() |
| { |
| switch (transferFunction) { |
| case QColorSpace::TransferFunction::Linear: |
| trc[0].m_type = QColorTrc::Type::Function; |
| trc[0].m_fun = QColorTransferFunction(); |
| if (qFuzzyIsNull(gamma)) |
| gamma = 1.0f; |
| break; |
| case QColorSpace::TransferFunction::Gamma: |
| trc[0].m_type = QColorTrc::Type::Function; |
| trc[0].m_fun = QColorTransferFunction::fromGamma(gamma); |
| break; |
| case QColorSpace::TransferFunction::SRgb: |
| trc[0].m_type = QColorTrc::Type::Function; |
| trc[0].m_fun = QColorTransferFunction::fromSRgb(); |
| if (qFuzzyIsNull(gamma)) |
| gamma = 2.31f; |
| break; |
| case QColorSpace::TransferFunction::ProPhotoRgb: |
| trc[0].m_type = QColorTrc::Type::Function; |
| trc[0].m_fun = QColorTransferFunction::fromProPhotoRgb(); |
| if (qFuzzyIsNull(gamma)) |
| gamma = 1.8f; |
| break; |
| case QColorSpace::TransferFunction::Custom: |
| break; |
| default: |
| Q_UNREACHABLE(); |
| break; |
| } |
| trc[1] = trc[0]; |
| trc[2] = trc[0]; |
| } |
| |
| QColorTransform QColorSpacePrivate::transformationToColorSpace(const QColorSpacePrivate *out) const |
| { |
| Q_ASSERT(out); |
| 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; |
| return combined; |
| } |
| |
| /*! |
| \class QColorSpace |
| \brief The QColorSpace class provides a color space abstraction. |
| \since 5.14 |
| |
| \ingroup painting |
| \ingroup appearance |
| \inmodule QtGui |
| |
| Color values can be interpreted in different ways, and based on the interpretation |
| can live in different spaces. We call this \e {color spaces}. |
| |
| QColorSpace provides access to creating several predefined color spaces and |
| can generate QColorTransforms for converting colors from one color space to |
| another. |
| |
| QColorSpace can also represent color spaces defined by ICC profiles or embedded |
| in images, that do not otherwise fit the predefined color spaces. |
| |
| 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 |
| called the gamut, and a color space that can represent a wider range of colors is also |
| known as a wide-gamut color space. |
| |
| The transfer function or gamma curve determines how each component in the |
| color space is encoded. These are used because human perception does not operate |
| linearly, and the transfer functions try to ensure that colors will seem evenly |
| spaced to human eyes. |
| */ |
| |
| |
| /*! |
| \enum QColorSpace::NamedColorSpace |
| |
| Predefined color spaces. |
| |
| \value SRgb The sRGB color space, which Qt operates in by default. It is a close approximation |
| of how most classic monitors operate, and a mode most software and hardware support. |
| \l{http://www.color.org/chardata/rgb/srgb.xalter}{ICC registration of sRGB}. |
| \value SRgbLinear The sRGB color space with linear gamma. Useful for gamma-corrected blending. |
| \value AdobeRgb The Adobe RGB color space is a classic wide-gamut color space, using a gamma of 2.2. |
| \l{http://www.color.org/chardata/rgb/adobergb.xalter}{ICC registration of Adobe RGB (1998)} |
| \value DisplayP3 A color-space using the primaries of DCI-P3, but with the whitepoint and transfer |
| function of sRGB. Common in modern wide-gamut screens. |
| \l{http://www.color.org/chardata/rgb/DCIP3.xalter}{ICC registration of DCI-P3} |
| \value ProPhotoRgb The Pro Photo RGB color space, also known as ROMM RGB is a very wide gamut color space. |
| \l{http://www.color.org/chardata/rgb/rommrgb.xalter}{ICC registration of ROMM RGB} |
| */ |
| |
| /*! |
| \enum QColorSpace::Primaries |
| |
| Predefined sets of primary colors. |
| |
| \value Custom The primaries are undefined or does not match any predefined sets. |
| \value SRgb The sRGB primaries |
| \value AdobeRgb The Adobe RGB primaries |
| \value DciP3D65 The DCI-P3 primaries with the D65 whitepoint |
| \value ProPhotoRgb The ProPhoto RGB primaries with the D50 whitepoint |
| */ |
| |
| /*! |
| \enum QColorSpace::TransferFunction |
| |
| Predefined transfer functions or gamma curves. |
| |
| \value Custom The custom or null transfer function |
| \value Linear The linear transfer functions |
| \value Gamma A transfer function that is a real gamma curve based on the value of gamma() |
| \value SRgb The sRGB transfer function, composed of linear and gamma parts |
| \value ProPhotoRgb The ProPhoto RGB transfer function, composed of linear and gamma parts |
| */ |
| |
| /*! |
| Creates a new colorspace object that represents an undefined and invalid colorspace. |
| */ |
| QColorSpace::QColorSpace() |
| { |
| } |
| |
| /*! |
| Creates a new colorspace object that represents a \a namedColorSpace. |
| */ |
| QColorSpace::QColorSpace(NamedColorSpace namedColorSpace) |
| { |
| if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::ProPhotoRgb) { |
| 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(); |
| } |
| d_ptr = predefinedColorspacePrivates[namedColorSpace]; |
| d_ptr->ref.ref(); |
| Q_ASSERT(isValid()); |
| } |
| |
| /*! |
| Creates a custom color space with the primaries \a primaries, using the transfer function \a fun and |
| optionally \a gamma. |
| */ |
| QColorSpace::QColorSpace(QColorSpace::Primaries primaries, QColorSpace::TransferFunction fun, float gamma) |
| : d_ptr(new QColorSpacePrivate(primaries, fun, gamma)) |
| { |
| d_ptr->ref.ref(); |
| } |
| |
| /*! |
| Creates a custom color space with the primaries \a primaries, using a gamma transfer function of |
| \a gamma. |
| */ |
| QColorSpace::QColorSpace(QColorSpace::Primaries primaries, float gamma) |
| : d_ptr(new QColorSpacePrivate(primaries, TransferFunction::Gamma, gamma)) |
| { |
| d_ptr->ref.ref(); |
| } |
| |
| /*! |
| 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. |
| */ |
| 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 = nullptr; |
| return; |
| } |
| d_ptr = new QColorSpacePrivate(primaries, fun, gamma); |
| d_ptr->ref.ref(); |
| } |
| |
| QColorSpace::~QColorSpace() |
| { |
| if (d_ptr && !d_ptr->ref.deref()) |
| delete d_ptr; |
| } |
| |
| QColorSpace::QColorSpace(const QColorSpace &colorSpace) |
| : d_ptr(colorSpace.d_ptr) |
| { |
| if (d_ptr) |
| d_ptr->ref.ref(); |
| } |
| |
| QColorSpace &QColorSpace::operator=(const QColorSpace &colorSpace) |
| { |
| 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; |
| } |
| |
| /*! \fn void QColorSpace::swap(QColorSpace &other) |
| |
| Swaps color space \a other with this color space. This operation is very fast and |
| never fails. |
| */ |
| |
| /*! |
| Returns the predefined primaries of the color space |
| or \c primaries::Custom if it doesn't match any of them. |
| */ |
| QColorSpace::Primaries QColorSpace::primaries() const noexcept |
| { |
| if (Q_UNLIKELY(!d_ptr)) |
| return QColorSpace::Primaries::Custom; |
| return d_ptr->primaries; |
| } |
| |
| /*! |
| Returns the predefined transfer function of the color space |
| or \c TransferFunction::Custom if it doesn't match any of them. |
| |
| \sa gamma(), setTransferFunction(), withTransferFunction() |
| */ |
| QColorSpace::TransferFunction QColorSpace::transferFunction() const noexcept |
| { |
| if (Q_UNLIKELY(!d_ptr)) |
| return QColorSpace::TransferFunction::Custom; |
| return d_ptr->transferFunction; |
| } |
| |
| /*! |
| Returns the gamma value of color spaces with \c TransferFunction::Gamma, |
| an approximate gamma value for other predefined color spaces, or |
| 0.0 if no approximate gamma is known. |
| |
| \sa transferFunction() |
| */ |
| float QColorSpace::gamma() const noexcept |
| { |
| if (Q_UNLIKELY(!d_ptr)) |
| return 0.0f; |
| return d_ptr->gamma; |
| } |
| |
| /*! |
| Sets the transfer function to \a transferFunction and \a gamma. |
| |
| \sa transferFunction(), gamma(), withTransferFunction() |
| */ |
| void QColorSpace::setTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) |
| { |
| if (transferFunction == TransferFunction::Custom) |
| 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(); |
| d_ptr->transferFunction = transferFunction; |
| d_ptr->gamma = gamma; |
| d_ptr->identifyColorSpace(); |
| d_ptr->setTransferFunction(); |
| } |
| |
| /*! |
| Returns a copy of this color space, except using the transfer function |
| \a transferFunction and \a gamma. |
| |
| \sa transferFunction(), gamma(), setTransferFunction() |
| */ |
| QColorSpace QColorSpace::withTransferFunction(QColorSpace::TransferFunction transferFunction, float gamma) const |
| { |
| if (!isValid() || transferFunction == QColorSpace::TransferFunction::Custom) |
| return *this; |
| if (d_ptr->transferFunction == transferFunction && d_ptr->gamma == gamma) |
| return *this; |
| QColorSpace out(*this); |
| out.setTransferFunction(transferFunction, gamma); |
| return out; |
| } |
| |
| /*! |
| Sets the primaries to those of the \a primariesId set. |
| |
| \sa primaries() |
| */ |
| void QColorSpace::setPrimaries(QColorSpace::Primaries primariesId) |
| { |
| if (primariesId == Primaries::Custom) |
| 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(); |
| d_ptr->primaries = primariesId; |
| d_ptr->identifyColorSpace(); |
| d_ptr->setToXyzMatrix(); |
| } |
| |
| /*! |
| Set primaries to the chromaticities of \a whitePoint, \a redPoint, \a greenPoint |
| and \a bluePoint. |
| |
| \sa primaries() |
| */ |
| void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoint, |
| const QPointF &greenPoint, const QPointF &bluePoint) |
| { |
| QColorSpacePrimaries primaries(whitePoint, redPoint, greenPoint, bluePoint); |
| if (!primaries.areValid()) |
| 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) |
| return; |
| QColorSpacePrivate::getWritable(*this); // detach |
| d_ptr->description.clear(); |
| d_ptr->primaries = QColorSpace::Primaries::Custom; |
| d_ptr->toXyz = toXyz; |
| d_ptr->whitePoint = QColorVector(primaries.whitePoint); |
| d_ptr->identifyColorSpace(); |
| } |
| |
| /*! |
| Returns an ICC profile representing the color space. |
| |
| If the color space was generated from an ICC profile, that profile |
| is returned, otherwise one is generated. |
| |
| \note Even invalid color spaces may return the ICC profile if they |
| were generated from one, to allow applications to implement wider |
| support themselves. |
| |
| \sa fromIccProfile() |
| */ |
| QByteArray QColorSpace::iccProfile() const |
| { |
| if (Q_UNLIKELY(!d_ptr)) |
| return QByteArray(); |
| if (!d_ptr->iccProfile.isEmpty()) |
| return d_ptr->iccProfile; |
| if (!isValid()) |
| return QByteArray(); |
| return QIcc::toIccProfile(*this); |
| } |
| |
| /*! |
| 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. |
| |
| 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) |
| { |
| QColorSpace colorSpace; |
| if (QIcc::fromIccProfile(iccProfile, &colorSpace)) |
| return colorSpace; |
| QColorSpacePrivate *d = QColorSpacePrivate::getWritable(colorSpace); |
| d->iccProfile = iccProfile; |
| return colorSpace; |
| } |
| |
| /*! |
| Returns \c true if the color space is valid. |
| */ |
| 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(); |
| } |
| |
| /*! |
| \relates QColorSpace |
| 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) |
| { |
| if (colorSpace1.d_ptr == colorSpace2.d_ptr) |
| return true; |
| if (!colorSpace1.d_ptr || !colorSpace2.d_ptr) |
| return false; |
| |
| if (colorSpace1.d_ptr->namedColorSpace && colorSpace2.d_ptr->namedColorSpace) |
| return colorSpace1.d_ptr->namedColorSpace == colorSpace2.d_ptr->namedColorSpace; |
| |
| const bool valid1 = colorSpace1.isValid(); |
| const bool valid2 = colorSpace2.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; |
| } |
| |
| // 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()) |
| return false; |
| } else { |
| if (colorSpace1.d_ptr->toXyz != colorSpace2.d_ptr->toXyz) |
| return false; |
| } |
| |
| if (colorSpace1.transferFunction() != QColorSpace::TransferFunction::Custom && |
| colorSpace2.transferFunction() != QColorSpace::TransferFunction::Custom) { |
| if (colorSpace1.transferFunction() != colorSpace2.transferFunction()) |
| return false; |
| if (colorSpace1.transferFunction() == QColorSpace::TransferFunction::Gamma) |
| return (qAbs(colorSpace1.gamma() - colorSpace2.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]) |
| 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()) |
| return QColorTransform(); |
| |
| return d_ptr->transformationToColorSpace(colorspace.d_ptr); |
| } |
| |
| /*! |
| Returns the color space as a QVariant. |
| \since 5.15 |
| */ |
| QColorSpace::operator QVariant() const |
| { |
| return QVariant(QMetaType::QColorSpace, this); |
| } |
| |
| /***************************************************************************** |
| QColorSpace stream functions |
| *****************************************************************************/ |
| #if !defined(QT_NO_DATASTREAM) |
| /*! |
| \fn QDataStream &operator<<(QDataStream &stream, const QColorSpace &colorSpace) |
| \relates QColorSpace |
| |
| Writes the given \a colorSpace to the given \a stream as an ICC profile. |
| |
| \sa QColorSpace::iccProfile(), {Serializing Qt Data Types} |
| */ |
| |
| QDataStream &operator<<(QDataStream &s, const QColorSpace &image) |
| { |
| s << image.iccProfile(); |
| return s; |
| } |
| |
| /*! |
| \fn QDataStream &operator>>(QDataStream &stream, QColorSpace &colorSpace) |
| \relates QColorSpace |
| |
| Reads a color space from the given \a stream and stores it in the given |
| \a colorSpace. |
| |
| \sa QColorSpace::fromIccProfile(), {Serializing Qt Data Types} |
| */ |
| |
| QDataStream &operator>>(QDataStream &s, QColorSpace &colorSpace) |
| { |
| QByteArray iccProfile; |
| s >> iccProfile; |
| colorSpace = QColorSpace::fromIccProfile(iccProfile); |
| return s; |
| } |
| #endif // QT_NO_DATASTREAM |
| |
| #ifndef QT_NO_DEBUG_STREAM |
| QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace) |
| { |
| QDebugStateSaver saver(dbg); |
| dbg.nospace(); |
| dbg << "QColorSpace("; |
| if (colorSpace.d_ptr) { |
| if (colorSpace.d_ptr->namedColorSpace) |
| dbg << colorSpace.d_ptr->namedColorSpace << ", "; |
| dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction(); |
| dbg << ", gamma=" << colorSpace.gamma(); |
| } |
| dbg << ')'; |
| return dbg; |
| } |
| #endif |
| |
| QT_END_NAMESPACE |