From ce33e476670c82bdf50344f4ab9bdbb17e6feaea Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Tue, 9 Apr 2024 16:06:05 +0200 Subject: [PATCH] Add Bt2020 and Bt2100 color spaces [ChangeLog][QtGui][QColorSpace] Bt.2020 and Bt.2100 (aka HDR10) formats have been added. Task-number: QTBUG-114722 Change-Id: I4ffce460202837e1077e34f48a9286981ee444bb Reviewed-by: Laszlo Agocs --- src/gui/CMakeLists.txt | 1 + src/gui/kernel/qguiapplication.cpp | 4 +- src/gui/painting/qcolormatrix_p.h | 6 + src/gui/painting/qcolorspace.cpp | 92 +++++++++-- src/gui/painting/qcolorspace.h | 13 +- src/gui/painting/qcolortransferfunction_p.h | 5 + src/gui/painting/qcolortransfergeneric_p.h | 109 +++++++++++++ src/gui/painting/qcolortransform.cpp | 18 +-- src/gui/painting/qcolortrc_p.h | 101 +++++++----- src/gui/painting/qcolortrclut.cpp | 69 ++++++--- src/gui/painting/qcolortrclut_p.h | 11 +- src/gui/painting/qicc.cpp | 145 ++++++++++++++++-- .../resources/Rec. ITU-R BT.2100 PQ.icc | Bin 0 -> 13300 bytes .../painting/qcolorspace/tst_qcolorspace.cpp | 14 ++ 14 files changed, 478 insertions(+), 110 deletions(-) create mode 100644 src/gui/painting/qcolortransfergeneric_p.h create mode 100644 tests/auto/gui/painting/qcolorspace/resources/Rec. ITU-R BT.2100 PQ.icc diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index aed66563a79..ac1b7de9924 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -171,6 +171,7 @@ qt_internal_add_module(Gui painting/qcolormatrix_p.h painting/qcolorspace.cpp painting/qcolorspace.h painting/qcolorspace_p.h painting/qcolortransferfunction_p.h + painting/qcolortransfergeneric_p.h painting/qcolortransfertable_p.h painting/qcolortransform.cpp painting/qcolortransform.h painting/qcolortransform_p.h painting/qcolortrc_p.h diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 5228ac94779..2595c005e3d 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -4325,7 +4325,7 @@ const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA8Text() { #ifdef Q_OS_WIN if (!m_a8ColorProfile) - m_a8ColorProfile = QColorTrcLut::fromGamma(2.31); // This is a hard-coded thing for Windows text rendering + m_a8ColorProfile = QColorTrcLut::fromGamma(2.31f); // This is a hard-coded thing for Windows text rendering return m_a8ColorProfile.get(); #else return colorProfileForA32Text(); @@ -4335,7 +4335,7 @@ const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA8Text() const QColorTrcLut *QGuiApplicationPrivate::colorProfileForA32Text() { if (!m_a32ColorProfile) - m_a32ColorProfile = QColorTrcLut::fromGamma(fontSmoothingGamma); + m_a32ColorProfile = QColorTrcLut::fromGamma(float(fontSmoothingGamma)); return m_a32ColorProfile.get(); } diff --git a/src/gui/painting/qcolormatrix_p.h b/src/gui/painting/qcolormatrix_p.h index 6a9b9f4f9af..0c85222a9a5 100644 --- a/src/gui/painting/qcolormatrix_p.h +++ b/src/gui/painting/qcolormatrix_p.h @@ -336,6 +336,12 @@ public: { 0.1351922452f, 0.7118769884f, 0.0000000000f }, { 0.0313525312f, 0.0000856627f, 0.8251883388f } }; } + static QColorMatrix toXyzFromBt2020() + { + return QColorMatrix { { 0.673447f, 0.279037f, -0.00192261f }, + { 0.165665f, 0.675339f, 0.0299835f }, + { 0.125092f, 0.0456238f, 0.797134f } }; + } friend inline bool comparesEqual(const QColorMatrix &lhs, const QColorMatrix &rhs); Q_DECLARE_EQUALITY_COMPARABLE(QColorMatrix); }; diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index 99c88ee0bea..fce2ae4c85f 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -21,7 +21,7 @@ QT_BEGIN_NAMESPACE Q_CONSTINIT QBasicMutex QColorSpacePrivate::s_lutWriteLock; -Q_CONSTINIT static QAtomicPointer s_predefinedColorspacePrivates[QColorSpace::ProPhotoRgb] = {}; +Q_CONSTINIT static QAtomicPointer s_predefinedColorspacePrivates[QColorSpace::Bt2100Hlg] = {}; static void cleanupPredefinedColorspaces() { for (QAtomicPointer &ptr : s_predefinedColorspacePrivates) { @@ -60,6 +60,12 @@ QColorSpacePrimaries::QColorSpacePrimaries(QColorSpace::Primaries primaries) bluePoint = QPointF(0.0366, 0.0001); whitePoint = QColorVector::D50Chromaticity(); break; + case QColorSpace::Primaries::Bt2020: + redPoint = QPointF(0.708, 0.292); + greenPoint = QPointF(0.170, 0.797); + bluePoint = QPointF(0.131, 0.046); + whitePoint = QColorVector::D65Chromaticity(); + break; default: Q_UNREACHABLE(); } @@ -132,6 +138,21 @@ QColorSpacePrivate::QColorSpacePrivate(QColorSpace::NamedColorSpace namedColorSp transferFunction = QColorSpace::TransferFunction::ProPhotoRgb; description = QStringLiteral("ProPhoto RGB"); break; + case QColorSpace::Bt2020: + primaries = QColorSpace::Primaries::Bt2020; + transferFunction = QColorSpace::TransferFunction::Bt2020; + description = QStringLiteral("BT.2020"); + break; + case QColorSpace::Bt2100Pq: + primaries = QColorSpace::Primaries::Bt2020; + transferFunction = QColorSpace::TransferFunction::St2084; + description = QStringLiteral("BT.2100(PQ)"); + break; + case QColorSpace::Bt2100Hlg: + primaries = QColorSpace::Primaries::Bt2020; + transferFunction = QColorSpace::TransferFunction::Hlg; + description = QStringLiteral("BT.2100(HLG)"); + break; default: Q_UNREACHABLE(); } @@ -292,6 +313,26 @@ void QColorSpacePrivate::identifyColorSpace() } } break; + case QColorSpace::Primaries::Bt2020: + if (transferFunction == QColorSpace::TransferFunction::Bt2020) { + namedColorSpace = QColorSpace::Bt2020; + if (description.isEmpty()) + description = QStringLiteral("BT.2020"); + return; + } + if (transferFunction == QColorSpace::TransferFunction::St2084) { + namedColorSpace = QColorSpace::Bt2100Pq; + if (description.isEmpty()) + description = QStringLiteral("BT.2100(PQ)"); + return; + } + if (transferFunction == QColorSpace::TransferFunction::Hlg) { + namedColorSpace = QColorSpace::Bt2100Hlg; + if (description.isEmpty()) + description = QStringLiteral("BT.2100(HLG)"); + return; + } + break; default: break; } @@ -337,7 +378,7 @@ void QColorSpacePrivate::setTransferFunctionTable(const QList &transfe } else if (curve.isSRgb()) { transferFunction = QColorSpace::TransferFunction::SRgb; } - trc[0].m_type = QColorTrc::Type::Function; + trc[0].m_type = QColorTrc::Type::ParameterizedFunction; trc[0].m_fun = curve; } else { trc[0].m_type = QColorTrc::Type::Table; @@ -363,21 +404,21 @@ void QColorSpacePrivate::setTransferFunctionTables(const QList &redTra transferFunction = QColorSpace::TransferFunction::Custom; QColorTransferFunction curve; if (redTable.asColorTransferFunction(&curve)) { - trc[0].m_type = QColorTrc::Type::Function; + trc[0].m_type = QColorTrc::Type::ParameterizedFunction; 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_type = QColorTrc::Type::ParameterizedFunction; 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_type = QColorTrc::Type::ParameterizedFunction; trc[2].m_fun = curve; } else { trc[2].m_type = QColorTrc::Type::Table; @@ -390,27 +431,34 @@ void QColorSpacePrivate::setTransferFunction() { switch (transferFunction) { case QColorSpace::TransferFunction::Linear: - trc[0].m_type = QColorTrc::Type::Function; - trc[0].m_fun = QColorTransferFunction(); + trc[0] = 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); + trc[0] = QColorTransferFunction::fromGamma(gamma); break; case QColorSpace::TransferFunction::SRgb: - trc[0].m_type = QColorTrc::Type::Function; - trc[0].m_fun = QColorTransferFunction::fromSRgb(); + trc[0] = 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(); + trc[0] = QColorTransferFunction::fromProPhotoRgb(); if (qFuzzyIsNull(gamma)) gamma = 1.8f; break; + case QColorSpace::TransferFunction::Bt2020: + trc[0] = QColorTransferFunction::fromBt2020(); + if (qFuzzyIsNull(gamma)) + gamma = 2.1f; + break; + case QColorSpace::TransferFunction::St2084: + trc[0] = QColorTransferGenericFunction::pq(); + break; + case QColorSpace::TransferFunction::Hlg: + trc[0] = QColorTransferGenericFunction::hlg(); + break; case QColorSpace::TransferFunction::Custom: break; default: @@ -530,6 +578,13 @@ void QColorSpacePrivate::clearElementListProcessingForEdit() \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} + \value [since 6.8] Bt2020 BT.2020, also known as Rec.2020 is a basic colorspace of HDR TVs. + \l{http://www.color.org/chardata/rgb/BT2020.xalter}{ICC registration of BT.2020} + \value [since 6.8] Bt2100Pq BT.2100(PQ), also known as Rec.2100 or HDR10 is an HDR encoding with the same + primaries as Bt2020 but using the Perceptual Quantizer transfer function. + \l{http://www.color.org/chardata/rgb/BT2100.xalter}{ICC registration of BT.2100} + \value [since 6.8] Bt2100Hlg BT.2100 (HLG) is an HDR encoding with the same + primaries as Bt2020 but using the Hybrid Log-Gamma transfer function. */ /*! @@ -542,6 +597,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit() \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 + \value [since 6.8] Bt2020 The BT.2020 primaries with a D65 whitepoint */ /*! @@ -554,6 +610,10 @@ void QColorSpacePrivate::clearElementListProcessingForEdit() \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 + \value [since 6.8] Bt2020 The BT.2020 transfer function, composited of linear and gamma parts + \value [since 6.8] St2084 The SMPTE ST 2084 transfer function, also known Perceptual Quantizer(PQ). + \value [since 6.8] Hlg The Hybrid log-gamma transfer function. + */ /*! @@ -596,7 +656,7 @@ void QColorSpacePrivate::clearElementListProcessingForEdit() */ QColorSpace::QColorSpace(NamedColorSpace namedColorSpace) { - if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::ProPhotoRgb) { + if (namedColorSpace < QColorSpace::SRgb || namedColorSpace > QColorSpace::Bt2100Hlg) { qWarning() << "QColorSpace attempted constructed from invalid QColorSpace::NamedColorSpace: " << int(namedColorSpace); return; } @@ -1133,9 +1193,11 @@ bool QColorSpacePrivate::isValid() const noexcept if (colorModel == QColorSpace::ColorModel::Gray) { if (!trc[0].isValid()) return false; - } else { + } else if (colorModel == QColorSpace::ColorModel::Rgb){ if (!trc[0].isValid() || !trc[1].isValid() || !trc[2].isValid()) return false; + } else { + return false; } return true; } diff --git a/src/gui/painting/qcolorspace.h b/src/gui/painting/qcolorspace.h index 488cbd6a530..ecf66145616 100644 --- a/src/gui/painting/qcolorspace.h +++ b/src/gui/painting/qcolorspace.h @@ -26,7 +26,10 @@ public: SRgbLinear, AdobeRgb, DisplayP3, - ProPhotoRgb + ProPhotoRgb, + Bt2020, + Bt2100Pq, + Bt2100Hlg, }; Q_ENUM(NamedColorSpace) enum class Primaries { @@ -34,7 +37,8 @@ public: SRgb, AdobeRgb, DciP3D65, - ProPhotoRgb + ProPhotoRgb, + Bt2020, }; Q_ENUM(Primaries) enum class TransferFunction { @@ -42,7 +46,10 @@ public: Linear, Gamma, SRgb, - ProPhotoRgb + ProPhotoRgb, + Bt2020, + St2084, + Hlg, }; Q_ENUM(TransferFunction) enum class TransformModel : uint8_t { diff --git a/src/gui/painting/qcolortransferfunction_p.h b/src/gui/painting/qcolortransferfunction_p.h index 484cc691148..b9a09b4646a 100644 --- a/src/gui/painting/qcolortransferfunction_p.h +++ b/src/gui/painting/qcolortransferfunction_p.h @@ -114,6 +114,11 @@ public: return QColorTransferFunction(1.0f, 0.0f, 1.0f / 16.0f, 16.0f / 512.0f, 0.0f, 0.0f, 1.8f, Hints(Hint::Calculated)); } + static QColorTransferFunction fromBt2020() + { + return QColorTransferFunction(1.0f / 1.0993f, 0.0993f / 1.0993f, 1.0f / 4.5f, 0.08145f, 0.0f, 0.0f, 2.2f, + Hints(Hint::Calculated)); + } bool matches(const QColorTransferFunction &o) const { return paramCompare(m_a, o.m_a) && paramCompare(m_b, o.m_b) diff --git a/src/gui/painting/qcolortransfergeneric_p.h b/src/gui/painting/qcolortransfergeneric_p.h new file mode 100644 index 00000000000..b4e6e8dec70 --- /dev/null +++ b/src/gui/painting/qcolortransfergeneric_p.h @@ -0,0 +1,109 @@ +// Copyright (C) 2024 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 + +#ifndef QCOLORTRANSFERGENERIC_P_H +#define QCOLORTRANSFERGENERIC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +#include + +QT_BEGIN_NAMESPACE + +// Defines the a generic transfer function for our HDR functions +class QColorTransferGenericFunction +{ +public: + using ConverterPtr = float (*)(float); + constexpr QColorTransferGenericFunction(ConverterPtr toLinear = nullptr, ConverterPtr fromLinear = nullptr) noexcept + : m_toLinear(toLinear), m_fromLinear(fromLinear) + {} + + static QColorTransferGenericFunction hlg() + { + return QColorTransferGenericFunction(hlgToLinear, hlgFromLinear); + } + static QColorTransferGenericFunction pq() + { + return QColorTransferGenericFunction(pqToLinear, pqFromLinear); + } + + float apply(float x) const + { + return m_toLinear(x); + } + + float applyInverse(float x) const + { + return m_fromLinear(x); + } + + bool operator==(const QColorTransferGenericFunction &o) const noexcept + { + return m_toLinear == o.m_toLinear && m_fromLinear == o.m_fromLinear; + } + bool operator!=(const QColorTransferGenericFunction &o) const noexcept + { + return m_toLinear != o.m_toLinear || m_fromLinear != o.m_fromLinear; + } + +private: + ConverterPtr m_toLinear = nullptr; + ConverterPtr m_fromLinear = nullptr; + + // HLG from linear [0-12] -> [0-1] + static float hlgFromLinear(float x) + { + if (x > 1.f) + return m_hlg_a * std::log(x - m_hlg_b) + m_hlg_c; + return std::sqrt(x * 0.25f); + } + + // HLG to linear [0-1] -> [0-12] + static float hlgToLinear(float x) + { + if (x < 0.5f) + return (x * x) * 4.f; + return std::exp((x - m_hlg_c) / m_hlg_a) + m_hlg_b; + } + + constexpr static float m_hlg_a = 0.17883277f; + constexpr static float m_hlg_b = 1.f - (4.f * m_hlg_a); + constexpr static float m_hlg_c = 0.55991073f; // 0.5 - a * ln(4 * a) + + // PQ from linear [0-64] -> [0-1] + static float pqFromLinear(float x) + { + x = std::pow(x * (1.f / m_pq_f), (1.f / m_pq_m1)); + return std::pow((m_pq_c1 - x) / (m_pq_c3 * x - m_pq_c2), (1.f / m_pq_m2)); + } + + // PQ from linear [0-1] -> [0-64] + static float pqToLinear(float x) + { + x = std::pow(x, m_pq_m1); + return std::pow((m_pq_c1 + m_pq_c2 * x) / (1.f + m_pq_c3 * x), m_pq_m2) * m_pq_f; + } + + constexpr static float m_pq_c1 = 107.f / 128.f; // c3 - c2 + 1 + constexpr static float m_pq_c2 = 2413.f / 128.f; + constexpr static float m_pq_c3 = 2392.f / 128.f; + constexpr static float m_pq_m1 = 1305.f / 8192.f; + constexpr static float m_pq_m2 = 2523.f / 32.f; + constexpr static float m_pq_f = 64.f; // This might need to be set based on scene metadata +}; + +QT_END_NAMESPACE + +#endif // QCOLORTRANSFERGENERIC_P_H diff --git a/src/gui/painting/qcolortransform.cpp b/src/gui/painting/qcolortransform.cpp index 7692e853fee..cffcc4485de 100644 --- a/src/gui/painting/qcolortransform.cpp +++ b/src/gui/painting/qcolortransform.cpp @@ -22,16 +22,6 @@ QT_BEGIN_NAMESPACE -std::shared_ptr lutFromTrc(const QColorTrc &trc) -{ - if (trc.m_type == QColorTrc::Type::Table) - return QColorTrcLut::fromTransferTable(trc.m_table); - if (trc.m_type == QColorTrc::Type::Function) - return QColorTrcLut::fromTransferFunction(trc.m_fun); - qWarning() << "TRC uninitialized"; - return nullptr; -} - void QColorTransformPrivate::updateLutsIn() const { if (colorSpaceIn->lut.generated.loadAcquire()) @@ -46,12 +36,12 @@ void QColorTransformPrivate::updateLutsIn() const } if (colorSpaceIn->trc[0] == colorSpaceIn->trc[1] && colorSpaceIn->trc[0] == colorSpaceIn->trc[2]) { - colorSpaceIn->lut[0] = lutFromTrc(colorSpaceIn->trc[0]); + colorSpaceIn->lut[0] = QColorTrcLut::fromTrc(colorSpaceIn->trc[0]); colorSpaceIn->lut[1] = colorSpaceIn->lut[0]; colorSpaceIn->lut[2] = colorSpaceIn->lut[0]; } else { for (int i = 0; i < 3; ++i) - colorSpaceIn->lut[i] = lutFromTrc(colorSpaceIn->trc[i]); + colorSpaceIn->lut[i] = QColorTrcLut::fromTrc(colorSpaceIn->trc[i]); } colorSpaceIn->lut.generated.storeRelease(1); @@ -70,12 +60,12 @@ void QColorTransformPrivate::updateLutsOut() const } if (colorSpaceOut->trc[0] == colorSpaceOut->trc[1] && colorSpaceOut->trc[0] == colorSpaceOut->trc[2]) { - colorSpaceOut->lut[0] = lutFromTrc(colorSpaceOut->trc[0]); + colorSpaceOut->lut[0] = QColorTrcLut::fromTrc(colorSpaceOut->trc[0]); colorSpaceOut->lut[1] = colorSpaceOut->lut[0]; colorSpaceOut->lut[2] = colorSpaceOut->lut[0]; } else { for (int i = 0; i < 3; ++i) - colorSpaceOut->lut[i] = lutFromTrc(colorSpaceOut->trc[i]); + colorSpaceOut->lut[i] = QColorTrcLut::fromTrc(colorSpaceOut->trc[i]); } colorSpaceOut->lut.generated.storeRelease(1); diff --git a/src/gui/painting/qcolortrc_p.h b/src/gui/painting/qcolortrc_p.h index c7a1d1bc882..dbca91c7f64 100644 --- a/src/gui/painting/qcolortrc_p.h +++ b/src/gui/painting/qcolortrc_p.h @@ -17,6 +17,7 @@ #include #include "qcolortransferfunction_p.h" +#include "qcolortransfergeneric_p.h" #include "qcolortransfertable_p.h" QT_BEGIN_NAMESPACE @@ -26,20 +27,23 @@ class Q_GUI_EXPORT QColorTrc { public: QColorTrc() noexcept : m_type(Type::Uninitialized) { } - QColorTrc(const QColorTransferFunction &fun) : m_type(Type::Function), m_fun(fun) { } + QColorTrc(const QColorTransferFunction &fun) : m_type(Type::ParameterizedFunction), m_fun(fun) { } QColorTrc(const QColorTransferTable &table) : m_type(Type::Table), m_table(table) { } - QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::Function), m_fun(std::move(fun)) { } + QColorTrc(const QColorTransferGenericFunction &hdr) : m_type(Type::GenericFunction), m_hdr(hdr) { } + QColorTrc(QColorTransferFunction &&fun) noexcept : m_type(Type::ParameterizedFunction), m_fun(std::move(fun)) { } QColorTrc(QColorTransferTable &&table) noexcept : m_type(Type::Table), m_table(std::move(table)) { } + QColorTrc(QColorTransferGenericFunction &&hdr) noexcept : m_type(Type::GenericFunction), m_hdr(std::move(hdr)) { } enum class Type { Uninitialized, - Function, - Table + ParameterizedFunction, + GenericFunction, + Table, }; bool isIdentity() const { - return (m_type == Type::Function && m_fun.isIdentity()) + return (m_type == Type::ParameterizedFunction && m_fun.isIdentity()) || (m_type == Type::Table && m_table.isIdentity()); } bool isValid() const @@ -48,64 +52,87 @@ public: } float apply(float x) const { - if (m_type == Type::Table) - return m_table.apply(x); - if (m_type == Type::Function) - return m_fun.apply(x); + switch (m_type) { + case Type::ParameterizedFunction: + return fun().apply(x); + case Type::GenericFunction: + return hdr().apply(x); + case Type::Table: + return table().apply(x); + default: + break; + } return x; } float applyExtended(float x) const { - if (x >= 0.0f && x <= 1.0f) - return apply(x); - if (m_type == Type::Function) - return std::copysign(m_fun.apply(std::abs(x)), x); - if (m_type == Type::Table) - return x < 0.0f ? 0.0f : 1.0f; + switch (m_type) { + case Type::ParameterizedFunction: + return std::copysign(fun().apply(std::abs(x)), x); + case Type::GenericFunction: + return hdr().apply(x); + case Type::Table: + return table().apply(x); + default: + break; + } return x; } float applyInverse(float x) const { - if (m_type == Type::Table) - return m_table.applyInverse(x); - if (m_type == Type::Function) - return m_fun.inverted().apply(x); + switch (m_type) { + case Type::ParameterizedFunction: + return fun().inverted().apply(x); + case Type::GenericFunction: + return hdr().applyInverse(x); + case Type::Table: + return table().applyInverse(x); + default: + break; + } return x; } float applyInverseExtended(float x) const { - if (x >= 0.0f && x <= 1.0f) - return applyInverse(x); - if (m_type == Type::Function) + switch (m_type) { + case Type::ParameterizedFunction: return std::copysign(applyInverse(std::abs(x)), x); - if (m_type == Type::Table) - return x < 0.0f ? 0.0f : 1.0f; + case Type::GenericFunction: + return hdr().applyInverse(x); + case Type::Table: + return table().applyInverse(x); + default: + break; + } return x; } - friend inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2); - friend inline bool operator==(const QColorTrc &o1, const QColorTrc &o2); - const QColorTransferTable &table() const { return m_table; } + const QColorTransferFunction &fun() const{ return m_fun; } + const QColorTransferGenericFunction &hdr() const { return m_hdr; } + Type type() const noexcept { return m_type; } Type m_type; + + friend inline bool comparesEqual(const QColorTrc &lhs, const QColorTrc &rhs); + Q_DECLARE_EQUALITY_COMPARABLE(QColorTrc); + QColorTransferFunction m_fun; QColorTransferTable m_table; + QColorTransferGenericFunction m_hdr; }; -inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2) +inline bool comparesEqual(const QColorTrc &o1, const QColorTrc &o2) { if (o1.m_type != o2.m_type) - return true; - if (o1.m_type == QColorTrc::Type::Function) - return o1.m_fun != o2.m_fun; + return false; + if (o1.m_type == QColorTrc::Type::ParameterizedFunction) + return o1.m_fun == o2.m_fun; if (o1.m_type == QColorTrc::Type::Table) - return o1.m_table != o2.m_table; - return false; -} -inline bool operator==(const QColorTrc &o1, const QColorTrc &o2) -{ - return !(o1 != o2); + return o1.m_table == o2.m_table; + if (o1.m_type == QColorTrc::Type::GenericFunction) + return o1.m_hdr == o2.m_hdr; + return true; } QT_END_NAMESPACE diff --git a/src/gui/painting/qcolortrclut.cpp b/src/gui/painting/qcolortrclut.cpp index 8a7673bc009..1357aa41a69 100644 --- a/src/gui/painting/qcolortrclut.cpp +++ b/src/gui/painting/qcolortrclut.cpp @@ -3,7 +3,9 @@ #include "qcolortrclut_p.h" #include "qcolortransferfunction_p.h" +#include "qcolortransfergeneric_p.h" #include "qcolortransfertable_p.h" +#include "qcolortrc_p.h" #include QT_BEGIN_NAMESPACE @@ -13,51 +15,49 @@ std::shared_ptr QColorTrcLut::create() return std::make_shared(); } -std::shared_ptr QColorTrcLut::fromGamma(qreal gamma, Direction dir) +std::shared_ptr QColorTrcLut::fromGamma(float gamma, Direction dir) { auto cp = create(); cp->setFromGamma(gamma, dir); return cp; } -std::shared_ptr QColorTrcLut::fromTransferFunction(const QColorTransferFunction &fun, Direction dir) +std::shared_ptr QColorTrcLut::fromTrc(const QColorTrc &trc, Direction dir) { + if (!trc.isValid()) + return nullptr; auto cp = create(); - cp->setFromTransferFunction(fun, dir); + cp->setFromTrc(trc, dir); return cp; } -std::shared_ptr QColorTrcLut::fromTransferTable(const QColorTransferTable &table, Direction dir) -{ - auto cp = create(); - cp->setFromTransferTable(table, dir); - return cp; -} - -void QColorTrcLut::setFromGamma(qreal gamma, Direction dir) +void QColorTrcLut::setFromGamma(float gamma, Direction dir) { + constexpr float iRes = 1.f / float(Resolution); if (dir & ToLinear) { if (!m_toLinear) m_toLinear.reset(new ushort[Resolution + 1]); for (int i = 0; i <= Resolution; ++i) - m_toLinear[i] = ushort(qRound(qPow(i / qreal(Resolution), gamma) * (255 * 256))); + m_toLinear[i] = ushort(qRound(qBound(0.f, qPow(i * iRes, gamma), 1.f) * (255 * 256))); } if (dir & FromLinear) { + const float iGamma = 1.f / gamma; if (!m_fromLinear) m_fromLinear.reset(new ushort[Resolution + 1]); for (int i = 0; i <= Resolution; ++i) - m_fromLinear[i] = ushort(qRound(qPow(i / qreal(Resolution), qreal(1) / gamma) * (255 * 256))); + m_fromLinear[i] = ushort(qRound(qBound(0.f, qPow(i * iRes, iGamma), 1.f) * (255 * 256))); } } void QColorTrcLut::setFromTransferFunction(const QColorTransferFunction &fun, Direction dir) { + constexpr float iRes = 1.f / float(Resolution); if (dir & ToLinear) { if (!m_toLinear) m_toLinear.reset(new ushort[Resolution + 1]); for (int i = 0; i <= Resolution; ++i) - m_toLinear[i] = ushort(qRound(fun.apply(i / qreal(Resolution)) * (255 * 256))); + m_toLinear[i] = ushort(qRound(qBound(0.f, fun.apply(i * iRes), 1.f) * (255 * 256))); } if (dir & FromLinear) { @@ -65,17 +65,36 @@ void QColorTrcLut::setFromTransferFunction(const QColorTransferFunction &fun, Di m_fromLinear.reset(new ushort[Resolution + 1]); QColorTransferFunction inv = fun.inverted(); for (int i = 0; i <= Resolution; ++i) - m_fromLinear[i] = ushort(qRound(inv.apply(i / qreal(Resolution)) * (255 * 256))); + m_fromLinear[i] = ushort(qRound(qBound(0.f, inv.apply(i * iRes), 1.f) * (255 * 256))); + } +} + +void QColorTrcLut::setFromTransferGenericFunction(const QColorTransferGenericFunction &fun, Direction dir) +{ + constexpr float iRes = 1.f / float(Resolution); + if (dir & ToLinear) { + if (!m_toLinear) + m_toLinear.reset(new ushort[Resolution + 1]); + for (int i = 0; i <= Resolution; ++i) + m_toLinear[i] = ushort(qRound(qBound(0.f, fun.apply(i * iRes), 1.f) * (255 * 256))); + } + + if (dir & FromLinear) { + if (!m_fromLinear) + m_fromLinear.reset(new ushort[Resolution + 1]); + for (int i = 0; i <= Resolution; ++i) + m_fromLinear[i] = ushort(qRound(qBound(0.f, fun.applyInverse(i * iRes), 1.f) * (255 * 256))); } } void QColorTrcLut::setFromTransferTable(const QColorTransferTable &table, Direction dir) { + constexpr float iRes = 1.f / float(Resolution); if (dir & ToLinear) { if (!m_toLinear) m_toLinear.reset(new ushort[Resolution + 1]); for (int i = 0; i <= Resolution; ++i) - m_toLinear[i] = ushort(qBound(0, qRound(table.apply(i / qreal(Resolution)) * (255 * 256)), 65280)); + m_toLinear[i] = ushort(qRound(table.apply(i * iRes) * (255 * 256))); } if (dir & FromLinear) { @@ -83,10 +102,24 @@ void QColorTrcLut::setFromTransferTable(const QColorTransferTable &table, Direct m_fromLinear.reset(new ushort[Resolution + 1]); float minInverse = 0.0f; for (int i = 0; i <= Resolution; ++i) { - minInverse = table.applyInverse(i / qreal(Resolution), minInverse); - m_fromLinear[i] = ushort(qBound(0, qRound(minInverse * (255 * 256)), 65280)); + minInverse = table.applyInverse(i * iRes, minInverse); + m_fromLinear[i] = ushort(qRound(minInverse * (255 * 256))); } } } +void QColorTrcLut::setFromTrc(const QColorTrc &trc, Direction dir) +{ + switch (trc.m_type) { + case QColorTrc::Type::ParameterizedFunction: + return setFromTransferFunction(trc.fun(), dir); + case QColorTrc::Type::Table: + return setFromTransferTable(trc.table(), dir); + case QColorTrc::Type::GenericFunction: + return setFromTransferGenericFunction(trc.hdr(), dir); + case QColorTrc::Type::Uninitialized: + break; + } +} + QT_END_NAMESPACE diff --git a/src/gui/painting/qcolortrclut_p.h b/src/gui/painting/qcolortrclut_p.h index 3ebab428090..15f7348836c 100644 --- a/src/gui/painting/qcolortrclut_p.h +++ b/src/gui/painting/qcolortrclut_p.h @@ -30,8 +30,10 @@ QT_BEGIN_NAMESPACE +class QColorTransferGenericFunction; class QColorTransferFunction; class QColorTransferTable; +class QColorTrc; class Q_GUI_EXPORT QColorTrcLut { @@ -46,12 +48,13 @@ public: BiLinear = ToLinear | FromLinear }; - static std::shared_ptr fromGamma(qreal gamma, Direction dir = BiLinear); - static std::shared_ptr fromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear); - static std::shared_ptr fromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear); - void setFromGamma(qreal gamma, Direction dir = BiLinear); + static std::shared_ptr fromGamma(float gamma, Direction dir = BiLinear); + static std::shared_ptr fromTrc(const QColorTrc &trc, Direction dir = BiLinear); + void setFromGamma(float gamma, Direction dir = BiLinear); void setFromTransferFunction(const QColorTransferFunction &transFn, Direction dir = BiLinear); void setFromTransferTable(const QColorTransferTable &transTable, Direction dir = BiLinear); + void setFromTransferGenericFunction(const QColorTransferGenericFunction &transfn, Direction dir); + void setFromTrc(const QColorTrc &trc, Direction dir); // The following methods all convert opaque or unpremultiplied colors: diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp index 957fd833f2c..574911788da 100644 --- a/src/gui/painting/qicc.cpp +++ b/src/gui/painting/qicc.cpp @@ -116,6 +116,7 @@ enum class Tag : quint32 { mAB_ = IccTag('m', 'A', 'B', ' '), mBA_ = IccTag('m', 'B', 'A', ' '), chad = IccTag('c', 'h', 'a', 'd'), + cicp = IccTag('c', 'i', 'c', 'p'), gamt = IccTag('g', 'a', 'm', 't'), sf32 = IccTag('s', 'f', '3', '2'), @@ -247,6 +248,13 @@ struct Sf32TagData : GenericTagData { quint32_be value[9]; }; +struct CicpTagData : GenericTagData { + quint8 colorPrimaries; + quint8 transferCharacteristics; + quint8 matrixCoefficients; + quint8 videoFullRangeFlag; +}; + struct MatrixElement { qint32_be e0; qint32_be e1; @@ -331,7 +339,7 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc) return 12; } - if (trc.m_type == QColorTrc::Type::Function) { + if (trc.m_type == QColorTrc::Type::ParameterizedFunction) { const QColorTransferFunction &fun = trc.m_fun; stream << uint(Tag::para) << uint(0); if (fun.isGamma()) { @@ -352,6 +360,14 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc) stream << toFixedS1516(fun.m_f); return 12 + 7 * 4; } + if (trc.m_type != QColorTrc::Type::Table) { + stream << uint(Tag::curv) << uint(0); + stream << uint(16); + for (uint i = 0; i < 16; ++i) { + stream << ushort(qBound(0, qRound(trc.apply(i / 15.f) * 65535.f), 65535)); + } + return 12 + 16 * 2; + } Q_ASSERT(trc.m_type == QColorTrc::Type::Table); stream << uint(Tag::curv) << uint(0); @@ -763,6 +779,7 @@ QByteArray toIccProfile(const QColorSpace &space) fixedLengthTagCount = 2; bool writeChad = false; bool writeB2a = true; + bool writeCicp = false; if (spaceDPtr->isThreeComponentMatrix() && !spaceDPtr->chad.isIdentity()) { writeChad = true; fixedLengthTagCount++; @@ -778,10 +795,19 @@ QByteArray toIccProfile(const QColorSpace &space) Q_ASSERT(!spaceDPtr->isThreeComponentMatrix()); varLengthTagCount--; } + switch (spaceDPtr->transferFunction) { + case QColorSpace::TransferFunction::St2084: + case QColorSpace::TransferFunction::Hlg: + writeCicp = true; + fixedLengthTagCount++; + default: + break; + } const int tagCount = fixedLengthTagCount + varLengthTagCount; const uint profileDataOffset = 128 + 4 + 12 * tagCount; uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount; + uint currentOffset = 0; uint rTrcOffset = 0; uint gTrcOffset = 0; @@ -850,6 +876,10 @@ QByteArray toIccProfile(const QColorSpace &space) stream << uint(Tag::chad) << uint(currentOffset) << uint(44); currentOffset += 44; } + if (writeCicp) { + stream << uint(Tag::cicp) << uint(currentOffset) << uint(12); + currentOffset += 12; + } // From here the offset and size will be updated later: if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) { stream << uint(Tag::rTRC) << uint(0) << uint(0); @@ -898,6 +928,18 @@ QByteArray toIccProfile(const QColorSpace &space) stream << toFixedS1516(chad.g.z); stream << toFixedS1516(chad.b.z); } + if (writeCicp) { + stream << uint(Tag::cicp) << uint(0); + stream << uchar(2); // Unspecified primaries + if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::St2084) + stream << uchar(16); + else if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::Hlg) + stream << uchar(18); + else + Q_UNREACHABLE(); + stream << uchar(0); // Only for YCbCr, otherwise 0 + stream << uchar(1); // Video full range + } // From now on the data is variable sized: if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) { @@ -1062,11 +1104,11 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT } const auto valueOffset = sizeof(CurvTagData); if (curv.valueCount == 0) { - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction(); // Linear } else if (curv.valueCount == 1) { const quint16 v = qFromBigEndian(tagData.constData() + valueOffset); - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f)); } else { QList tabl; @@ -1084,7 +1126,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT gamma.m_table = table; } else { qCDebug(lcIcc) << "Detected curv table as function"; - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = curve; } } @@ -1101,7 +1143,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT return 0; qFromBigEndian(tagData.constData() + parametersOffset, 1, parameters); float g = fromFixedS1516(parameters[0]); - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction::fromGamma(g); return 12 + 1 * 4; } @@ -1115,7 +1157,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT float a = fromFixedS1516(parameters[1]); float b = fromFixedS1516(parameters[2]); float d = -b / a; - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g); return 12 + 3 * 4; } @@ -1130,7 +1172,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT float b = fromFixedS1516(parameters[2]); float c = fromFixedS1516(parameters[3]); float d = -b / a; - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g); return 12 + 4 * 4; } @@ -1143,7 +1185,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT float b = fromFixedS1516(parameters[2]); float c = fromFixedS1516(parameters[3]); float d = fromFixedS1516(parameters[4]); - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g); return 12 + 5 * 4; } @@ -1158,7 +1200,7 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT float d = fromFixedS1516(parameters[4]); float e = fromFixedS1516(parameters[5]); float f = fromFixedS1516(parameters[6]); - gamma.m_type = QColorTrc::Type::Function; + gamma.m_type = QColorTrc::Type::ParameterizedFunction; gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g); return 12 + 7 * 4; } @@ -1629,10 +1671,12 @@ static bool parseRgbMatrix(const QByteArray &data, const QHash &t } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) { qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected"; colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65; - } - if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) { + } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) { qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected"; colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb; + } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromBt2020()) { + qCDebug(lcIcc) << "fromIccProfile: BT.2020 primaries detected"; + colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020; } return true; } @@ -1720,12 +1764,12 @@ static bool parseTRCs(const QByteArray &data, const QHash &tagInd colorspaceDPtr->trc[0] = QColorTransferFunction(); colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear; colorspaceDPtr->gamma = 1.0f; - } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isGamma()) { + } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isGamma()) { qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected"; colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g); colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma; colorspaceDPtr->gamma = rCurve.m_fun.m_g; - } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isSRgb()) { + } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isSRgb()) { qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected"; colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb(); colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb; @@ -1744,6 +1788,63 @@ static bool parseTRCs(const QByteArray &data, const QHash &tagInd return true; } +static bool parseCicp(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr) +{ + if (colorspaceDPtr->isPcsLab) + return false; + if (tagEntry.size < sizeof(CicpTagData) || qsizetype(tagEntry.size) > data.size()) + return false; + const CicpTagData cicp = qFromUnaligned(data.constData() + tagEntry.offset); + if (cicp.type != uint32_t(Tag::cicp)) { + qCWarning(lcIcc, "fromIccProfile: bad cicp data type"); + return false; + } + switch (cicp.transferCharacteristics) { + case 4: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma; + colorspaceDPtr->gamma = 2.2f; + break; + case 5: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma; + colorspaceDPtr->gamma = 2.8f; + break; + case 8: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear; + break; + case 13: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb; + break; + case 1: + case 6: + case 14: + case 15: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Bt2020; + break; + case 16: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::St2084; + break; + case 18: + colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Hlg; + break; + default: + return false; + } + switch (cicp.colorPrimaries) { + case 1: + colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb; + break; + case 9: + colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020; + break; + case 12: + colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65; + break; + default: + return false; + } + return true; +} + bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) { if (data.size() < qsizetype(sizeof(ICCProfileHeader))) { @@ -1832,12 +1933,22 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) colorSpace->detach(); QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(*colorSpace); + colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_)); + if (tagIndex.contains(Tag::cicp)) { + // Let cicp override nLut profiles if we fully recognize it. + if (parseCicp(data, tagIndex[Tag::cicp], colorspaceDPtr)) + threeComponentMatrix = true; + if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom) + colorspaceDPtr->setToXyzMatrix(); + if (colorspaceDPtr->transferFunction != QColorSpace::TransferFunction::Custom) + colorspaceDPtr->setTransferFunction(); + } + if (threeComponentMatrix) { - colorspaceDPtr->isPcsLab = false; colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix; if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) { - if (!parseRgbMatrix(data, tagIndex, colorspaceDPtr)) + if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr)) return false; colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb; } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) { @@ -1860,10 +1971,10 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace) if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom) colorspaceDPtr->setToXyzMatrix(); - if (!parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray))) + if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom && + !parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray))) return false; } else { - colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_)); colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing; if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk; diff --git a/tests/auto/gui/painting/qcolorspace/resources/Rec. ITU-R BT.2100 PQ.icc b/tests/auto/gui/painting/qcolorspace/resources/Rec. ITU-R BT.2100 PQ.icc new file mode 100644 index 0000000000000000000000000000000000000000..e0ceaa12f8063934eeaba8c231fbb439d865c23d GIT binary patch literal 13300 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