summaryrefslogtreecommitdiffstats
path: root/src/gui/painting/qicc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/painting/qicc.cpp')
-rw-r--r--src/gui/painting/qicc.cpp1896
1 files changed, 1546 insertions, 350 deletions
diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp
index 32d67ea76a..d62d4627cb 100644
--- a/src/gui/painting/qicc.cpp
+++ b/src/gui/painting/qicc.cpp
@@ -1,56 +1,28 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 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) 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
#include "qicc_p.h"
#include <qbuffer.h>
#include <qbytearray.h>
+#include <qvarlengtharray.h>
+#include <qhash.h>
#include <qdatastream.h>
#include <qendian.h>
#include <qloggingcategory.h>
#include <qstring.h>
+#include "qcolorclut_p.h"
+#include "qcolormatrix_p.h"
#include "qcolorspace_p.h"
#include "qcolortrc_p.h"
+#include <array>
+
QT_BEGIN_NAMESPACE
-Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc")
+Q_STATIC_LOGGING_CATEGORY(lcIcc, "qt.gui.icc", QtWarningMsg)
+
+namespace QIcc {
struct ICCProfileHeader
{
@@ -90,18 +62,23 @@ constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
enum class ColorSpaceType : quint32 {
Rgb = IccTag('R', 'G', 'B', ' '),
Gray = IccTag('G', 'R', 'A', 'Y'),
+ Cmyk = IccTag('C', 'M', 'Y', 'K'),
};
enum class ProfileClass : quint32 {
- Input = IccTag('s', 'c', 'r', 'n'),
+ Input = IccTag('s', 'c', 'n', 'r'),
Display = IccTag('m', 'n', 't', 'r'),
- // Not supported:
Output = IccTag('p', 'r', 't', 'r'),
ColorSpace = IccTag('s', 'p', 'a', 'c'),
+ // Not supported:
+ DeviceLink = IccTag('l', 'i', 'n', 'k'),
+ Abstract = IccTag('a', 'b', 's', 't'),
+ NamedColor = IccTag('n', 'm', 'c', 'l'),
};
enum class Tag : quint32 {
acsp = IccTag('a', 'c', 's', 'p'),
+ Lab_ = IccTag('L', 'a', 'b', ' '),
RGB_ = IccTag('R', 'G', 'B', ' '),
XYZ_ = IccTag('X', 'Y', 'Z', ' '),
rXYZ = IccTag('r', 'X', 'Y', 'Z'),
@@ -113,8 +90,18 @@ enum class Tag : quint32 {
kTRC = IccTag('k', 'T', 'R', 'C'),
A2B0 = IccTag('A', '2', 'B', '0'),
A2B1 = IccTag('A', '2', 'B', '1'),
+ A2B2 = IccTag('A', '2', 'B', '2'),
B2A0 = IccTag('B', '2', 'A', '0'),
B2A1 = IccTag('B', '2', 'A', '1'),
+ B2A2 = IccTag('B', '2', 'A', '2'),
+ B2D0 = IccTag('B', '2', 'D', '0'),
+ B2D1 = IccTag('B', '2', 'D', '1'),
+ B2D2 = IccTag('B', '2', 'D', '2'),
+ B2D3 = IccTag('B', '2', 'D', '3'),
+ D2B0 = IccTag('D', '2', 'B', '0'),
+ D2B1 = IccTag('D', '2', 'B', '1'),
+ D2B2 = IccTag('D', '2', 'B', '2'),
+ D2B3 = IccTag('D', '2', 'B', '3'),
desc = IccTag('d', 'e', 's', 'c'),
text = IccTag('t', 'e', 'x', 't'),
cprt = IccTag('c', 'p', 'r', 't'),
@@ -125,9 +112,12 @@ enum class Tag : quint32 {
mft1 = IccTag('m', 'f', 't', '1'),
mft2 = IccTag('m', 'f', 't', '2'),
mluc = IccTag('m', 'l', 'u', 'c'),
+ mpet = IccTag('m', 'p', 'e', 't'),
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'),
// Apple extensions for ICCv2:
@@ -136,7 +126,9 @@ enum class Tag : quint32 {
aabg = IccTag('a', 'a', 'b', 'g'),
};
-inline size_t qHash(const Tag &key, size_t seed = 0)
+} // namespace QIcc
+
+inline size_t qHash(const QIcc::Tag &key, size_t seed = 0)
{
return qHash(quint32(key), seed);
}
@@ -163,18 +155,18 @@ struct XYZTagData : GenericTagData {
struct CurvTagData : GenericTagData {
quint32_be valueCount;
- quint16_be value[1];
+ // followed by curv values: quint16_be[]
};
struct ParaTagData : GenericTagData {
quint16_be curveType;
quint16_be null2;
- quint32_be parameter[1];
+ // followed by parameter values: quint32_be[1-7];
};
struct DescTagData : GenericTagData {
quint32_be asciiDescriptionLength;
- char asciiDescription[1];
+ // followed by ascii description: char[]
// .. we ignore the rest
};
@@ -191,6 +183,46 @@ struct MlucTagData : GenericTagData {
MlucTagRecord records[1];
};
+struct Lut8TagData : GenericTagData {
+ quint8 inputChannels;
+ quint8 outputChannels;
+ quint8 clutGridPoints;
+ quint8 padding;
+ qint32_be e1;
+ qint32_be e2;
+ qint32_be e3;
+ qint32_be e4;
+ qint32_be e5;
+ qint32_be e6;
+ qint32_be e7;
+ qint32_be e8;
+ qint32_be e9;
+ // followed by parameter values: quint8[inputChannels * 256];
+ // followed by parameter values: quint8[outputChannels * clutGridPoints^inputChannels];
+ // followed by parameter values: quint8[outputChannels * 256];
+};
+
+struct Lut16TagData : GenericTagData {
+ quint8 inputChannels;
+ quint8 outputChannels;
+ quint8 clutGridPoints;
+ quint8 padding;
+ qint32_be e1;
+ qint32_be e2;
+ qint32_be e3;
+ qint32_be e4;
+ qint32_be e5;
+ qint32_be e6;
+ qint32_be e7;
+ qint32_be e8;
+ qint32_be e9;
+ quint16_be inputTableEntries;
+ quint16_be outputTableEntries;
+ // followed by parameter values: quint16_be[inputChannels * inputTableEntries];
+ // followed by parameter values: quint16_be[outputChannels * clutGridPoints^inputChannels];
+ // followed by parameter values: quint16_be[outputChannels * outputTableEntries];
+};
+
// For both mAB and mBA
struct mABTagData : GenericTagData {
quint8 inputChannels;
@@ -201,15 +233,50 @@ struct mABTagData : GenericTagData {
quint32_be mCurvesOffset;
quint32_be clutOffset;
quint32_be aCurvesOffset;
+ // followed by embedded data for the offsets above
+};
+
+struct mpetTagData : GenericTagData {
+ quint16_be inputChannels;
+ quint16_be outputChannels;
+ quint32_be processingElements;
+ // element offset table
+ // element data
};
struct Sf32TagData : GenericTagData {
- quint32_be value[1];
+ quint32_be value[9];
+};
+
+struct CicpTagData : GenericTagData {
+ quint8 colorPrimaries;
+ quint8 transferCharacteristics;
+ quint8 matrixCoefficients;
+ quint8 videoFullRangeFlag;
+};
+
+struct MatrixElement {
+ qint32_be e0;
+ qint32_be e1;
+ qint32_be e2;
+ qint32_be e3;
+ qint32_be e4;
+ qint32_be e5;
+ qint32_be e6;
+ qint32_be e7;
+ qint32_be e8;
+ qint32_be e9;
+ qint32_be e10;
+ qint32_be e11;
};
static int toFixedS1516(float x)
{
- return int(x * 65536.0f + 0.5f);
+ if (x < float(SHRT_MIN))
+ return INT_MIN;
+ if (x > float(SHRT_MAX))
+ return INT_MAX;
+ return qRound(x * 65536.0f);
}
static float fromFixedS1516(int x)
@@ -235,18 +302,20 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
}
if (header.profileClass != uint(ProfileClass::Input)
- && header.profileClass != uint(ProfileClass::Display)) {
- qCWarning(lcIcc, "Unsupported ICC profile class %x", quint32(header.profileClass));
+ && header.profileClass != uint(ProfileClass::Display)
+ && header.profileClass != uint(ProfileClass::Output)
+ && header.profileClass != uint(ProfileClass::ColorSpace)) {
+ qCInfo(lcIcc, "Unsupported ICC profile class 0x%x", quint32(header.profileClass));
return false;
}
if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
- && header.inputColorSpace != uint(ColorSpaceType::Gray)) {
- qCWarning(lcIcc, "Unsupported ICC input color space %x", quint32(header.inputColorSpace));
+ && header.inputColorSpace != uint(ColorSpaceType::Gray)
+ && header.inputColorSpace != uint(ColorSpaceType::Cmyk)) {
+ qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
return false;
}
- if (header.pcs != 0x58595a20 /* 'XYZ '*/) {
- // ### support PCSLAB
- qCWarning(lcIcc, "Unsupported ICC profile connection space %x", quint32(header.pcs));
+ if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) {
+ qCInfo(lcIcc, "Invalid ICC profile connection space 0x%x", quint32(header.pcs));
return false;
}
@@ -264,13 +333,13 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
{
- if (trc.isLinear()) {
+ if (trc.isIdentity()) {
stream << uint(Tag::curv) << uint(0);
stream << uint(0);
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()) {
@@ -291,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);
@@ -304,23 +381,449 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
stream << ushort(trc.m_table.m_table8[i] * 257U);
}
}
+ if (trc.m_table.m_tableSize & 1) {
+ stream << ushort(0);
+ return 12 + 2 * trc.m_table.m_tableSize + 2;
+ }
return 12 + 2 * trc.m_table.m_tableSize;
}
+// very simple version for small values (<=4) of exp.
+static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
+{
+ return (exp <= 1) ? x : x * intPow(x, exp - 1);
+}
+
+struct ElementCombo
+{
+ const QColorMatrix *inMatrix = nullptr;
+ const QColorSpacePrivate::TransferElement *inTable = nullptr;
+ const QColorCLUT *clut = nullptr;
+ const QColorSpacePrivate::TransferElement *midTable = nullptr;
+ const QColorMatrix *midMatrix = nullptr;
+ const QColorVector *midOffset = nullptr;
+ const QColorSpacePrivate::TransferElement *outTable = nullptr;
+};
+
+static void visitElement(ElementCombo &combo, const QColorSpacePrivate::TransferElement &element, int number,
+ const QList<QColorSpacePrivate::Element> &list)
+{
+ if (number == 0)
+ combo.inTable = &element;
+ else if (number == list.size() - 1)
+ combo.outTable = &element;
+ else if (number == 1 && combo.inMatrix)
+ combo.inTable = &element;
+ else
+ combo.midTable = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorMatrix &element, int number,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ if (number == 0)
+ combo.inMatrix = &element;
+ else
+ combo.midMatrix = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorVector &element, int,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ combo.midOffset = &element;
+}
+
+static void visitElement(ElementCombo &combo, const QColorCLUT &element, int,
+ const QList<QColorSpacePrivate::Element> &)
+{
+ combo.clut = &element;
+}
+
+static bool isTableTrc(const QColorSpacePrivate::TransferElement *transfer)
+{
+ int i = 0;
+ while (i < 4 && transfer->trc[i].isValid()) {
+ if (transfer->trc[i].m_type != QColorTrc::Type::Table)
+ return false;
+ i++;
+ }
+ return i > 0;
+}
+
+static bool isTableTrcSingleSize(const QColorSpacePrivate::TransferElement *transfer)
+{
+ Q_ASSERT(transfer->trc[0].m_type == QColorTrc::Type::Table);
+ int i = 1;
+ const uint32_t size = transfer->trc[0].table().m_tableSize;
+ while (i < 4 && transfer->trc[i].isValid()) {
+ if (transfer->trc[i].table().m_tableSize != size)
+ return false;
+ i++;
+ }
+ return true;
+}
+
+static int writeMab(QDataStream &stream, const QList<QColorSpacePrivate::Element> &abList, bool isAb, bool pcsLab, bool isCmyk)
+{
+ int number = 0;
+ ElementCombo combo;
+ for (auto &&element : abList)
+ std::visit([&](auto &&elm) { visitElement(combo, elm, number++, abList); }, element);
+
+ Q_ASSERT(!(combo.inMatrix && combo.midMatrix));
+
+ // qWarning() << Q_FUNC_INFO << bool(combo.inMatrix) << bool(combo.inTable) << bool(combo.clut) << bool(combo.midTable) << bool(combo.midMatrix) << bool(combo.midOffset) << bool(combo.outTable);
+ bool lut16 = true;
+ if (combo.midMatrix || combo.midTable || combo.midOffset)
+ lut16 = false;
+ if (combo.clut && (combo.clut->gridPointsX != combo.clut->gridPointsY ||
+ combo.clut->gridPointsX != combo.clut->gridPointsZ ||
+ (combo.clut->gridPointsW > 1 && combo.clut->gridPointsX != combo.clut->gridPointsW)))
+ lut16 = false;
+ if (lut16 && combo.inTable)
+ lut16 = isTableTrc(combo.inTable) && isTableTrcSingleSize(combo.inTable);
+ if (lut16 && combo.outTable)
+ lut16 = isTableTrc(combo.outTable) && isTableTrcSingleSize(combo.outTable);
+
+ if (!lut16) {
+ if (combo.inMatrix)
+ qSwap(combo.inMatrix, combo.midMatrix);
+ if (isAb)
+ stream << uint(Tag::mAB_) << uint(0);
+ else
+ stream << uint(Tag::mBA_) << uint(0);
+ } else {
+ stream << uint(Tag::mft2) << uint(0);
+ }
+
+ const int inChannels = (isCmyk && isAb) ? 4 : 3;
+ const int outChannels = (isCmyk && !isAb) ? 4 : 3;
+ stream << uchar(inChannels) << uchar(outChannels);
+ qsizetype gridPointsLut16 = 0;
+ if (lut16 && combo.clut)
+ gridPointsLut16 = combo.clut->gridPointsX;
+ if (lut16)
+ stream << uchar(gridPointsLut16) << uchar(0);
+ else
+ stream << quint16(0);
+ if (lut16) {
+ if (combo.inMatrix) {
+ stream << toFixedS1516(combo.inMatrix->r.x);
+ stream << toFixedS1516(combo.inMatrix->g.x);
+ stream << toFixedS1516(combo.inMatrix->b.x);
+ stream << toFixedS1516(combo.inMatrix->r.y);
+ stream << toFixedS1516(combo.inMatrix->g.y);
+ stream << toFixedS1516(combo.inMatrix->b.y);
+ stream << toFixedS1516(combo.inMatrix->r.z);
+ stream << toFixedS1516(combo.inMatrix->g.z);
+ stream << toFixedS1516(combo.inMatrix->b.z);
+ } else {
+ stream << toFixedS1516(1.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(1.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(0.0f);
+ stream << toFixedS1516(1.0f);
+ }
+ int inputEntries = 0, outputEntries = 0;
+ if (combo.inTable)
+ inputEntries = combo.inTable->trc[0].table().m_tableSize;
+ else
+ inputEntries = 2;
+ if (combo.outTable)
+ outputEntries = combo.outTable->trc[0].table().m_tableSize;
+ else
+ outputEntries = 2;
+ stream << quint16(inputEntries);
+ stream << quint16(outputEntries);
+ auto writeTable = [&](const QColorSpacePrivate::TransferElement *table, int entries, int channels) {
+ if (table) {
+ for (int j = 0; j < channels; ++j) {
+ if (!table->trc[j].table().m_table16.isEmpty()) {
+ for (int i = 0; i < entries; ++i)
+ stream << table->trc[j].table().m_table16[i];
+ } else {
+ for (int i = 0; i < entries; ++i)
+ stream << quint16(table->trc[j].table().m_table8[i] * 257);
+ }
+ }
+ } else {
+ for (int j = 0; j < channels; ++j)
+ stream << quint16(0) << quint16(65535);
+ }
+ };
+
+ writeTable(combo.inTable, inputEntries, inChannels);
+
+ if (combo.clut) {
+ if (isAb && pcsLab) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65280.0f + 0.5f);
+ stream << quint16(v.y * 65280.0f + 0.5f);
+ stream << quint16(v.z * 65280.0f + 0.5f);
+ }
+ } else {
+ if (outChannels == 4) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65535.0f + 0.5f);
+ stream << quint16(v.y * 65535.0f + 0.5f);
+ stream << quint16(v.z * 65535.0f + 0.5f);
+ stream << quint16(v.w * 65535.0f + 0.5f);
+ }
+ } else {
+ for (const QColorVector &v : combo.clut->table) {
+ stream << quint16(v.x * 65535.0f + 0.5f);
+ stream << quint16(v.y * 65535.0f + 0.5f);
+ stream << quint16(v.z * 65535.0f + 0.5f);
+ }
+ }
+ }
+ }
+
+ writeTable(combo.outTable, outputEntries, outChannels);
+
+ qsizetype offset = sizeof(Lut16TagData) + 2 * inChannels * inputEntries
+ + 2 * outChannels * outputEntries
+ + 2 * outChannels * intPow(gridPointsLut16, inChannels);
+ if (offset & 0x2) {
+ stream << quint16(0);
+ offset += 2;
+ }
+ return offset;
+ } else {
+ // mAB/mBA tag:
+ if (isAb) {
+ if (!combo.clut && combo.inTable && combo.midMatrix && !combo.midTable)
+ std::swap(combo.inTable, combo.midTable);
+ } else {
+ if (!combo.clut && combo.outTable && combo.midMatrix && !combo.midTable)
+ std::swap(combo.outTable, combo.midTable);
+ }
+ quint32 offset = sizeof(mABTagData);
+ QBuffer buffer2;
+ buffer2.open(QIODevice::WriteOnly);
+ QDataStream stream2(&buffer2);
+ quint32 bOffset = offset;
+ quint32 matrixOffset = 0;
+ quint32 mOffset = 0;
+ quint32 clutOffset = 0;
+ quint32 aOffset = 0;
+ // Tags must start on 4 byte offsets, but sampled curves might have sizes 2-byte aligned
+ auto alignTag = [&]() {
+ if (offset & 0x2) {
+ stream2 << quint16(0);
+ offset += 2;
+ }
+ };
+
+ const QColorSpacePrivate::TransferElement *aCurve, *bCurve;
+ int aChannels;
+ if (isAb) {
+ aCurve = combo.inTable;
+ aChannels = inChannels;
+ bCurve = combo.outTable;
+ Q_ASSERT(outChannels == 3);
+ } else {
+ aCurve = combo.outTable;
+ aChannels = outChannels;
+ bCurve = combo.inTable;
+ Q_ASSERT(inChannels == 3);
+ }
+ if (bCurve) {
+ offset += writeColorTrc(stream2, bCurve->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, bCurve->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, bCurve->trc[2]);
+ alignTag();
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * 3;
+ }
+ if (combo.midMatrix || combo.midOffset || combo.midTable) {
+ matrixOffset = offset;
+ if (combo.midMatrix) {
+ stream2 << toFixedS1516(combo.midMatrix->r.x);
+ stream2 << toFixedS1516(combo.midMatrix->g.x);
+ stream2 << toFixedS1516(combo.midMatrix->b.x);
+ stream2 << toFixedS1516(combo.midMatrix->r.y);
+ stream2 << toFixedS1516(combo.midMatrix->g.y);
+ stream2 << toFixedS1516(combo.midMatrix->b.y);
+ stream2 << toFixedS1516(combo.midMatrix->r.z);
+ stream2 << toFixedS1516(combo.midMatrix->g.z);
+ stream2 << toFixedS1516(combo.midMatrix->b.z);
+ } else {
+ stream2 << toFixedS1516(1.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(1.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(1.0f);
+ }
+ if (combo.midOffset) {
+ stream2 << toFixedS1516(combo.midOffset->x);
+ stream2 << toFixedS1516(combo.midOffset->y);
+ stream2 << toFixedS1516(combo.midOffset->z);
+ } else {
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ stream2 << toFixedS1516(0.0f);
+ }
+ offset += 12 * 4;
+ mOffset = offset;
+ if (combo.midTable) {
+ offset += writeColorTrc(stream2, combo.midTable->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, combo.midTable->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, combo.midTable->trc[2]);
+ alignTag();
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * 3;
+ }
+ }
+ if (combo.clut || aCurve) {
+ clutOffset = offset;
+ if (combo.clut) {
+ stream2 << uchar(combo.clut->gridPointsX);
+ stream2 << uchar(combo.clut->gridPointsY);
+ stream2 << uchar(combo.clut->gridPointsZ);
+ if (inChannels == 4)
+ stream2 << uchar(combo.clut->gridPointsW);
+ else
+ stream2 << uchar(0);
+ for (int i = 0; i < 12; ++i)
+ stream2 << uchar(0);
+ stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
+ offset += 20;
+ if (outChannels == 4) {
+ for (const QColorVector &v : combo.clut->table) {
+ stream2 << quint16(v.x * 65535.0f + 0.5f);
+ stream2 << quint16(v.y * 65535.0f + 0.5f);
+ stream2 << quint16(v.z * 65535.0f + 0.5f);
+ stream2 << quint16(v.w * 65535.0f + 0.5f);
+ }
+ } else {
+ for (const QColorVector &v : combo.clut->table) {
+ stream2 << quint16(v.x * 65535.0f + 0.5f);
+ stream2 << quint16(v.y * 65535.0f + 0.5f);
+ stream2 << quint16(v.z * 65535.0f + 0.5f);
+ }
+ }
+ offset += 2 * outChannels * combo.clut->table.size();
+ alignTag();
+ } else {
+ for (int i = 0; i < 16; ++i)
+ stream2 << uchar(0);
+ stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
+ offset += 20;
+ }
+ aOffset = offset;
+ if (aCurve) {
+ offset += writeColorTrc(stream2, aCurve->trc[0]);
+ alignTag();
+ offset += writeColorTrc(stream2, aCurve->trc[1]);
+ alignTag();
+ offset += writeColorTrc(stream2, aCurve->trc[2]);
+ alignTag();
+ if (aChannels == 4) {
+ offset += writeColorTrc(stream2, aCurve->trc[3]);
+ alignTag();
+ }
+ } else {
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ if (aChannels == 4)
+ stream2 << uint(Tag::curv) << uint(0) << uint(0);
+ offset += 12 * aChannels;
+ }
+ }
+ buffer2.close();
+ QByteArray tagData = buffer2.buffer();
+ stream << quint32(bOffset);
+ stream << quint32(matrixOffset);
+ stream << quint32(mOffset);
+ stream << quint32(clutOffset);
+ stream << quint32(aOffset);
+ stream.writeRawData(tagData.data(), tagData.size());
+
+ return int(sizeof(mABTagData) + tagData.size());
+ }
+}
+
QByteArray toIccProfile(const QColorSpace &space)
{
if (!space.isValid())
return QByteArray();
const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
+ if (!spaceDPtr->iccProfile.isEmpty())
+ return spaceDPtr->iccProfile;
+
+ int fixedLengthTagCount = 5;
+ if (!spaceDPtr->isThreeComponentMatrix())
+ fixedLengthTagCount = 2;
+ else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ fixedLengthTagCount = 2;
+ bool writeChad = false;
+ bool writeB2a = true;
+ bool writeCicp = false;
+ if (spaceDPtr->isThreeComponentMatrix() && !spaceDPtr->chad.isIdentity()) {
+ writeChad = true;
+ fixedLengthTagCount++;
+ }
+ int varLengthTagCount = 4;
+ if (!spaceDPtr->isThreeComponentMatrix())
+ varLengthTagCount = 3;
+ else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ varLengthTagCount = 2;
+
+ if (!space.isValidTarget()) {
+ writeB2a = false;
+ Q_ASSERT(!spaceDPtr->isThreeComponentMatrix());
+ varLengthTagCount--;
+ }
+ switch (spaceDPtr->transferFunction) {
+ case QColorSpace::TransferFunction::St2084:
+ case QColorSpace::TransferFunction::Hlg:
+ writeCicp = true;
+ fixedLengthTagCount++;
+ break;
+ default:
+ break;
+ }
+
+ const int tagCount = fixedLengthTagCount + varLengthTagCount;
+ const uint profileDataOffset = 128 + 4 + 12 * tagCount;
+ uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
- constexpr int tagCount = 9;
- constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
- constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
uint currentOffset = 0;
- uint rTrcOffset, gTrcOffset, bTrcOffset;
- uint rTrcSize, gTrcSize, bTrcSize;
- uint descOffset, descSize;
+ uint rTrcOffset = 0;
+ uint gTrcOffset = 0;
+ uint bTrcOffset = 0;
+ uint kTrcOffset = 0;
+ uint rTrcSize = 0;
+ uint gTrcSize = 0;
+ uint bTrcSize = 0;
+ uint kTrcSize = 0;
+ uint descOffset = 0;
+ uint descSize = 0;
+ uint mA2bOffset = 0;
+ uint mB2aOffset = 0;
+ uint mA2bSize = 0;
+ uint mB2aSize = 0;
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
@@ -329,15 +832,27 @@ QByteArray toIccProfile(const QColorSpace &space)
// Profile header:
stream << uint(0); // Size, we will update this later
stream << uint(0);
- stream << uint(0x02400000); // Version 2.4 (note we use 'para' from version 4)
+ stream << uint(0x04400000); // Version 4.4
stream << uint(ProfileClass::Display);
- stream << uint(Tag::RGB_);
- stream << uint(Tag::XYZ_);
+ switch (spaceDPtr->colorModel) {
+ case QColorSpace::ColorModel::Rgb:
+ stream << uint(ColorSpaceType::Rgb);
+ break;
+ case QColorSpace::ColorModel::Gray:
+ stream << uint(ColorSpaceType::Gray);
+ break;
+ case QColorSpace::ColorModel::Cmyk:
+ stream << uint(ColorSpaceType::Cmyk);
+ break;
+ case QColorSpace::ColorModel::Undefined:
+ Q_UNREACHABLE();
+ }
+ stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
stream << uint(0) << uint(0) << uint(0);
stream << uint(Tag::acsp);
stream << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0);
- stream << uint(1); // Rendering intent
+ stream << uint(0); // Rendering intent
stream << uint(0x0000f6d6); // D50 X
stream << uint(0x00010000); // D50 Y
stream << uint(0x0000d32d); // D50 Z
@@ -345,73 +860,164 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0) << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
- // Tag table:
- stream << uint(tagCount);
- stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
- stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
- stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
- stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
- stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(12);
- // From here the offset and size will be updated later:
- stream << uint(Tag::rTRC) << uint(0) << uint(0);
- stream << uint(Tag::gTRC) << uint(0) << uint(0);
- stream << uint(Tag::bTRC) << uint(0) << uint(0);
- stream << uint(Tag::desc) << uint(0) << uint(0);
- // TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
currentOffset = profileDataOffset;
+ if (spaceDPtr->isThreeComponentMatrix()) {
+ // Tag table:
+ stream << uint(tagCount);
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
+ stream << uint(Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
+ stream << uint(Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
+ currentOffset += 20 + 20 + 20;
+ }
+ stream << uint(Tag::wtpt) << uint(currentOffset + 00) << uint(20);
+ stream << uint(Tag::cprt) << uint(currentOffset + 20) << uint(34);
+ currentOffset += 20 + 34 + 2;
+ if (writeChad) {
+ 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);
+ stream << uint(Tag::gTRC) << uint(0) << uint(0);
+ stream << uint(Tag::bTRC) << uint(0) << uint(0);
+ } else {
+ stream << uint(Tag::kTRC) << uint(0) << uint(0);
+ }
+ stream << uint(Tag::desc) << uint(0) << uint(0);
+
+ // Tag data:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.r.z);
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.g.z);
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.x);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.y);
+ stream << toFixedS1516(spaceDPtr->toXyz.b.z);
+ }
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->whitePoint.x);
+ stream << toFixedS1516(spaceDPtr->whitePoint.y);
+ stream << toFixedS1516(spaceDPtr->whitePoint.z);
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+ if (writeChad) {
+ const QColorMatrix &chad = spaceDPtr->chad;
+ stream << uint(Tag::sf32) << uint(0);
+ stream << toFixedS1516(chad.r.x);
+ stream << toFixedS1516(chad.g.x);
+ stream << toFixedS1516(chad.b.x);
+ stream << toFixedS1516(chad.r.y);
+ stream << toFixedS1516(chad.g.y);
+ stream << toFixedS1516(chad.b.y);
+ stream << toFixedS1516(chad.r.z);
+ 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
+ }
- // Tag data:
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.r.x);
- stream << toFixedS1516(spaceDPtr->toXyz.r.y);
- stream << toFixedS1516(spaceDPtr->toXyz.r.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.g.x);
- stream << toFixedS1516(spaceDPtr->toXyz.g.y);
- stream << toFixedS1516(spaceDPtr->toXyz.g.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->toXyz.b.x);
- stream << toFixedS1516(spaceDPtr->toXyz.b.y);
- stream << toFixedS1516(spaceDPtr->toXyz.b.z);
- stream << uint(Tag::XYZ_) << uint(0);
- stream << toFixedS1516(spaceDPtr->whitePoint.x);
- stream << toFixedS1516(spaceDPtr->whitePoint.y);
- stream << toFixedS1516(spaceDPtr->whitePoint.z);
- stream << uint(Tag::text) << uint(0);
- stream << uint(IccTag('N', '/', 'A', '\0'));
- currentOffset += 92;
-
- // From now on the data is variable sized:
- rTrcOffset = currentOffset;
- rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
- currentOffset += rTrcSize;
- if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
- gTrcOffset = rTrcOffset;
- gTrcSize = rTrcSize;
- } else {
- gTrcOffset = currentOffset;
- gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
- currentOffset += gTrcSize;
- }
- if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
- bTrcOffset = rTrcOffset;
- bTrcSize = rTrcSize;
+ // From now on the data is variable sized:
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ rTrcOffset = currentOffset;
+ rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
+ currentOffset += rTrcSize;
+ if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
+ gTrcOffset = rTrcOffset;
+ gTrcSize = rTrcSize;
+ } else {
+ gTrcOffset = currentOffset;
+ gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
+ currentOffset += gTrcSize;
+ }
+ if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
+ bTrcOffset = rTrcOffset;
+ bTrcSize = rTrcSize;
+ } else {
+ bTrcOffset = currentOffset;
+ bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
+ currentOffset += bTrcSize;
+ }
+ } else {
+ Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
+ kTrcOffset = currentOffset;
+ kTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
+ currentOffset += kTrcSize;
+ }
} else {
- bTrcOffset = currentOffset;
- bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
- currentOffset += bTrcSize;
+ // Tag table:
+ stream << uint(tagCount);
+ stream << uint(Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
+ stream << uint(Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
+ currentOffset += 20 + 34 + 2;
+ // From here the offset and size will be updated later:
+ stream << uint(Tag::A2B0) << uint(0) << uint(0);
+ if (writeB2a)
+ stream << uint(Tag::B2A0) << uint(0) << uint(0);
+ stream << uint(Tag::desc) << uint(0) << uint(0);
+
+ // Fixed tag data
+ stream << uint(Tag::XYZ_) << uint(0);
+ stream << toFixedS1516(spaceDPtr->whitePoint.x);
+ stream << toFixedS1516(spaceDPtr->whitePoint.y);
+ stream << toFixedS1516(spaceDPtr->whitePoint.z);
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(6) << uint(28);
+ stream << ushort('N') << ushort('/') << ushort('A');
+ stream << ushort(0); // 4-byte alignment
+
+ // From now on the data is variable sized:
+ mA2bOffset = currentOffset;
+ mA2bSize = writeMab(stream, spaceDPtr->mAB, true, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
+ currentOffset += mA2bSize;
+ if (writeB2a) {
+ mB2aOffset = currentOffset;
+ mB2aSize = writeMab(stream, spaceDPtr->mBA, false, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
+ currentOffset += mB2aSize;
+ }
}
+ // Writing description
descOffset = currentOffset;
- QByteArray description = spaceDPtr->description.toUtf8();
- stream << uint(Tag::desc) << uint(0);
- stream << uint(description.size() + 1);
- stream.writeRawData(description.constData(), description.size() + 1);
- stream << uint(0) << uint(0);
- stream << ushort(0) << uchar(0);
- QByteArray macdesc(67, '\0');
- stream.writeRawData(macdesc.constData(), 67);
- descSize = 90 + description.size() + 1;
+ const QString description = space.description();
+ stream << uint(Tag::mluc) << uint(0);
+ stream << uint(1) << uint(12);
+ stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
+ stream << uint(description.size() * 2) << uint(28);
+ for (QChar ch : description)
+ stream << ushort(ch.unicode());
+ descSize = 28 + description.size() * 2;
+ if (description.size() & 1) {
+ stream << ushort(0);
+ currentOffset += 2;
+ }
currentOffset += descSize;
buffer.close();
@@ -419,14 +1025,34 @@ QByteArray toIccProfile(const QColorSpace &space)
// Now write final size
*(quint32_be *)iccProfile.data() = iccProfile.size();
// And the final indices and sizes of variable size tags:
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
- *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
+ if (spaceDPtr->isThreeComponentMatrix()) {
+ if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
+ } else {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
+ }
+ } else {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
+ variableTagTableOffsets += 12;
+ if (writeB2a) {
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
+ variableTagTableOffsets += 12;
+ }
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
+ *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
+ }
#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
@@ -442,313 +1068,668 @@ struct TagEntry {
quint32 size;
};
-bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
+static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
{
if (tagEntry.size < sizeof(XYZTagData)) {
qCWarning(lcIcc) << "Undersized XYZ tag";
return false;
}
- const XYZTagData *xyz = reinterpret_cast<const XYZTagData *>(data.constData() + tagEntry.offset);
- if (xyz->type != quint32(Tag::XYZ_)) {
+ const XYZTagData xyz = qFromUnaligned<XYZTagData>(data.constData() + tagEntry.offset);
+ if (xyz.type != quint32(Tag::XYZ_)) {
qCWarning(lcIcc) << "Bad XYZ content type";
return false;
}
- const float x = fromFixedS1516(xyz->fixedX);
- const float y = fromFixedS1516(xyz->fixedY);
- const float z = fromFixedS1516(xyz->fixedZ);
+ const float x = fromFixedS1516(xyz.fixedX);
+ const float y = fromFixedS1516(xyz.fixedY);
+ const float z = fromFixedS1516(xyz.fixedZ);
colorVector = QColorVector(x, y, z);
return true;
}
-bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma)
+static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorTransferTable::Type type = QColorTransferTable::TwoWay)
{
- const GenericTagData *trcData = reinterpret_cast<const GenericTagData *>(data.constData() + tagEntry.offset);
- if (trcData->type == quint32(Tag::curv)) {
- const CurvTagData *curv = static_cast<const CurvTagData *>(trcData);
- if (curv->valueCount > (1 << 16))
- return false;
- if (tagEntry.size - 12 < 2 * curv->valueCount)
- return false;
- if (curv->valueCount == 0) {
- gamma.m_type = QColorTrc::Type::Function;
+ if (tagData.size() < 12)
+ return 0;
+ const GenericTagData trcData = qFromUnaligned<GenericTagData>(tagData.constData());
+ if (trcData.type == quint32(Tag::curv)) {
+ Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
+ const CurvTagData curv = qFromUnaligned<CurvTagData>(tagData.constData());
+ if (curv.valueCount > (1 << 16)) {
+ qCWarning(lcIcc) << "Invalid count in curv table";
+ return 0;
+ }
+ if (tagData.size() < qsizetype(12 + 2 * curv.valueCount)) {
+ qCWarning(lcIcc) << "Truncated curv table";
+ return 0;
+ }
+ const auto valueOffset = sizeof(CurvTagData);
+ if (curv.valueCount == 0) {
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(); // Linear
- } else if (curv->valueCount == 1) {
- float g = curv->value[0] * (1.0f / 256.0f);
- gamma.m_type = QColorTrc::Type::Function;
- gamma.m_fun = QColorTransferFunction::fromGamma(g);
+ } else if (curv.valueCount == 1) {
+ const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
+ gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f));
} else {
- QVector<quint16> tabl;
- tabl.resize(curv->valueCount);
- for (uint i = 0; i < curv->valueCount; ++i)
- tabl[i] = curv->value[i];
- QColorTransferTable table = QColorTransferTable(curv->valueCount, std::move(tabl));
+ QList<quint16> tabl;
+ tabl.resize(curv.valueCount);
+ static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
+ "GenericTagData has padding. The following code is a subject to UB.");
+ qFromBigEndian<quint16>(tagData.constData() + valueOffset, curv.valueCount, tabl.data());
+ QColorTransferTable table(curv.valueCount, tabl, type);
QColorTransferFunction curve;
- if (!table.asColorTransferFunction(&curve)) {
+ if (!table.checkValidity()) {
+ qCWarning(lcIcc) << "Invalid curv table";
+ return 0;
+ } else if (!table.asColorTransferFunction(&curve)) {
gamma.m_type = QColorTrc::Type::Table;
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;
}
}
- return true;
+ return 12 + 2 * curv.valueCount;
}
- if (trcData->type == quint32(Tag::para)) {
- if (tagEntry.size < sizeof(ParaTagData))
- return false;
- const ParaTagData *para = static_cast<const ParaTagData *>(trcData);
- switch (para->curveType) {
+ if (trcData.type == quint32(Tag::para)) {
+ Q_STATIC_ASSERT(sizeof(ParaTagData) == 12);
+ const ParaTagData para = qFromUnaligned<ParaTagData>(tagData.constData());
+ const auto parametersOffset = sizeof(ParaTagData);
+ quint32 parameters[7];
+ switch (para.curveType) {
case 0: {
- float g = fromFixedS1516(para->parameter[0]);
- gamma.m_type = QColorTrc::Type::Function;
+ if (tagData.size() < 12 + 1 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
+ float g = fromFixedS1516(parameters[0]);
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction::fromGamma(g);
- break;
+ return 12 + 1 * 4;
}
case 1: {
- if (tagEntry.size < sizeof(ParaTagData) + 2 * 4)
- return false;
- float g = fromFixedS1516(para->parameter[0]);
- float a = fromFixedS1516(para->parameter[1]);
- float b = fromFixedS1516(para->parameter[2]);
+ if (tagData.size() < 12 + 3 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 3, parameters);
+ if (parameters[1] == 0)
+ return 0;
+ float g = fromFixedS1516(parameters[0]);
+ 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);
- break;
+ return 12 + 3 * 4;
}
case 2: {
- if (tagEntry.size < sizeof(ParaTagData) + 3 * 4)
- return false;
- float g = fromFixedS1516(para->parameter[0]);
- float a = fromFixedS1516(para->parameter[1]);
- float b = fromFixedS1516(para->parameter[2]);
- float c = fromFixedS1516(para->parameter[3]);
+ if (tagData.size() < 12 + 4 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 4, parameters);
+ if (parameters[1] == 0)
+ return 0;
+ float g = fromFixedS1516(parameters[0]);
+ float a = fromFixedS1516(parameters[1]);
+ 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);
- break;
+ return 12 + 4 * 4;
}
case 3: {
- if (tagEntry.size < sizeof(ParaTagData) + 4 * 4)
- return false;
- float g = fromFixedS1516(para->parameter[0]);
- float a = fromFixedS1516(para->parameter[1]);
- float b = fromFixedS1516(para->parameter[2]);
- float c = fromFixedS1516(para->parameter[3]);
- float d = fromFixedS1516(para->parameter[4]);
- gamma.m_type = QColorTrc::Type::Function;
+ if (tagData.size() < 12 + 5 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 5, parameters);
+ float g = fromFixedS1516(parameters[0]);
+ float a = fromFixedS1516(parameters[1]);
+ float b = fromFixedS1516(parameters[2]);
+ float c = fromFixedS1516(parameters[3]);
+ float d = fromFixedS1516(parameters[4]);
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
- break;
+ return 12 + 5 * 4;
}
case 4: {
- if (tagEntry.size < sizeof(ParaTagData) + 6 * 4)
- return false;
- float g = fromFixedS1516(para->parameter[0]);
- float a = fromFixedS1516(para->parameter[1]);
- float b = fromFixedS1516(para->parameter[2]);
- float c = fromFixedS1516(para->parameter[3]);
- float d = fromFixedS1516(para->parameter[4]);
- float e = fromFixedS1516(para->parameter[5]);
- float f = fromFixedS1516(para->parameter[6]);
- gamma.m_type = QColorTrc::Type::Function;
+ if (tagData.size() < 12 + 7 * 4)
+ return 0;
+ qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 7, parameters);
+ float g = fromFixedS1516(parameters[0]);
+ float a = fromFixedS1516(parameters[1]);
+ float b = fromFixedS1516(parameters[2]);
+ float c = fromFixedS1516(parameters[3]);
+ float d = fromFixedS1516(parameters[4]);
+ float e = fromFixedS1516(parameters[5]);
+ float f = fromFixedS1516(parameters[6]);
+ gamma.m_type = QColorTrc::Type::ParameterizedFunction;
gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
- break;
+ return 12 + 7 * 4;
}
default:
- qCWarning(lcIcc) << "Unknown para type" << uint(para->curveType);
- return false;
+ qCWarning(lcIcc) << "Unknown para type" << uint(para.curveType);
+ return 0;
}
return true;
}
- qCWarning(lcIcc) << "Invalid TRC data type";
- return false;
+ qCWarning(lcIcc) << "Invalid TRC data type" << Qt::hex << trcData.type;
+ return 0;
}
-bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
+template<typename T>
+static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels)
{
- const GenericTagData *tag = (const GenericTagData *)(data.constData() + tagEntry.offset);
+ if (outputChannels == 4) {
+ for (qsizetype index = 0; index < clut->table.size(); ++index) {
+ QColorVector v(tableData[index * 4 + 0] * f,
+ tableData[index * 4 + 1] * f,
+ tableData[index * 4 + 2] * f,
+ tableData[index * 4 + 3] * f);
+ clut->table[index] = v;
+ };
+ } else {
+ for (qsizetype index = 0; index < clut->table.size(); ++index) {
+ QColorVector v(tableData[index * 3 + 0] * f,
+ tableData[index * 3 + 1] * f,
+ tableData[index * 3 + 2] * f);
+ clut->table[index] = v;
+ };
+ }
+}
- // Either 'desc' (ICCv2) or 'mluc' (ICCv4)
- if (tag->type == quint32(Tag::desc)) {
- if (tagEntry.size < sizeof(DescTagData))
- return false;
- const DescTagData *desc = (const DescTagData *)(data.constData() + tagEntry.offset);
- const quint32 len = desc->asciiDescriptionLength;
- if (len < 1)
+// Parses lut8 and lut16 type elements
+template<typename T>
+static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
+{
+ if (tagEntry.size < sizeof(T)) {
+ qCWarning(lcIcc) << "Undersized lut8/lut16 tag";
+ return false;
+ }
+ if (qsizetype(tagEntry.size) > data.size()) {
+ qCWarning(lcIcc) << "Truncated lut8/lut16 tag";
+ return false;
+ }
+ using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
+ const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
+ int inputTableEntries, outputTableEntries, precision;
+ if constexpr (std::is_same_v<T, Lut8TagData>) {
+ Q_ASSERT(lut.type == quint32(Tag::mft1));
+ if (!colorSpacePrivate->isPcsLab && isAb) {
+ qCWarning(lcIcc) << "Lut8 can not output XYZ values";
return false;
- if (tagEntry.size - 12 < len)
+ }
+ inputTableEntries = 256;
+ outputTableEntries = 256;
+ precision = 1;
+ } else {
+ Q_ASSERT(lut.type == quint32(Tag::mft2));
+ inputTableEntries = lut.inputTableEntries;
+ outputTableEntries = lut.outputTableEntries;
+ if (inputTableEntries < 2 || inputTableEntries > 4096)
return false;
- if (desc->asciiDescription[len - 1] != '\0')
+ if (outputTableEntries < 2 || outputTableEntries > 4096)
return false;
- descName = QString::fromLatin1(desc->asciiDescription, len - 1);
- return true;
+ precision = 2;
}
- if (tag->type != quint32(Tag::mluc))
- return false;
- if (tagEntry.size < sizeof(MlucTagData))
+ bool inTableIsLinear = true, outTableIsLinear = true;
+ QColorSpacePrivate::TransferElement inTableElement;
+ QColorSpacePrivate::TransferElement outTableElement;
+ QColorCLUT clutElement;
+ QColorMatrix matrixElement;
+
+ matrixElement.r.x = fromFixedS1516(lut.e1);
+ matrixElement.g.x = fromFixedS1516(lut.e2);
+ matrixElement.b.x = fromFixedS1516(lut.e3);
+ matrixElement.r.y = fromFixedS1516(lut.e4);
+ matrixElement.g.y = fromFixedS1516(lut.e5);
+ matrixElement.b.y = fromFixedS1516(lut.e6);
+ matrixElement.r.z = fromFixedS1516(lut.e7);
+ matrixElement.g.z = fromFixedS1516(lut.e8);
+ matrixElement.b.z = fromFixedS1516(lut.e9);
+ if (!colorSpacePrivate->isPcsLab && !isAb && !matrixElement.isValid()) {
+ qCWarning(lcIcc) << "Invalid matrix values in lut8/lut16";
return false;
- const MlucTagData *mluc = (const MlucTagData *)(data.constData() + tagEntry.offset);
- if (mluc->recordCount < 1)
+ }
+
+ const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+ const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+
+ if (lut.inputChannels != inputChannels) {
+ qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
return false;
- if (mluc->recordSize < 12)
+ }
+
+ if (lut.outputChannels != outputChannels) {
+ qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
return false;
- // We just use the primary record regardless of language or country.
- const quint32 stringOffset = mluc->records[0].offset;
- const quint32 stringSize = mluc->records[0].size;
- if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
+ }
+
+ const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
+ if (tagEntry.size < (sizeof(T) + precision * inputChannels * inputTableEntries
+ + precision * outputChannels * outputTableEntries
+ + precision * outputChannels * clutTableSize)) {
+ qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
return false;
- if ((stringSize | stringOffset) & 1)
+ }
+ if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
+ qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
return false;
- quint32 stringLen = stringSize / 2;
- const ushort *unicodeString = (const ushort *)(data.constData() + tagEntry.offset + stringOffset);
- // The given length shouldn't include 0-termination, but might.
- if (stringLen > 1 && unicodeString[stringLen - 1] == 0)
- --stringLen;
- QVarLengthArray<quint16> utf16hostendian(stringLen);
- qFromBigEndian<ushort>(unicodeString, stringLen, utf16hostendian.data());
- descName = QString::fromUtf16(utf16hostendian.data(), stringLen);
+ }
+
+ const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
+
+ for (int j = 0; j < inputChannels; ++j) {
+ QList<S> input(inputTableEntries);
+ qFromBigEndian<S>(tableData, inputTableEntries, input.data());
+ QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
+ if (!table.checkValidity()) {
+ qCWarning(lcIcc) << "Bad input table in lut8/lut16";
+ return false;
+ }
+ if (!table.isIdentity())
+ inTableIsLinear = false;
+ inTableElement.trc[j] = std::move(table);
+ tableData += inputTableEntries * precision;
+ }
+
+ clutElement.table.resize(clutTableSize);
+ clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
+ if (inputChannels == 4)
+ clutElement.gridPointsW = lut.clutGridPoints;
+
+ if constexpr (std::is_same_v<T, Lut8TagData>) {
+ parseCLUT(tableData, 1.f / 255.f, &clutElement, outputChannels);
+ } else {
+ float f = 1.0f / 65535.f;
+ if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
+ f = 1.0f / 65280.f;
+ QList<S> clutTable(clutTableSize * outputChannels);
+ qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
+ parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
+ }
+ tableData += clutTableSize * outputChannels * precision;
+
+ for (int j = 0; j < outputChannels; ++j) {
+ QList<S> output(outputTableEntries);
+ qFromBigEndian<S>(tableData, outputTableEntries, output.data());
+ QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
+ if (!table.checkValidity()) {
+ qCWarning(lcIcc) << "Bad output table in lut8/lut16";
+ return false;
+ }
+ if (!table.isIdentity())
+ outTableIsLinear = false;
+ outTableElement.trc[j] = std::move(table);
+ tableData += outputTableEntries * precision;
+ }
+
+ if (isAb) {
+ if (!inTableIsLinear)
+ colorSpacePrivate->mAB.append(inTableElement);
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mAB.append(clutElement);
+ if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
+ colorSpacePrivate->mAB.append(outTableElement);
+ } else {
+ // The matrix is only to be applied if the input color-space is XYZ
+ if (!colorSpacePrivate->isPcsLab && !matrixElement.isIdentity())
+ colorSpacePrivate->mBA.append(matrixElement);
+ if (!inTableIsLinear)
+ colorSpacePrivate->mBA.append(inTableElement);
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mBA.append(clutElement);
+ if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
+ colorSpacePrivate->mBA.append(outTableElement);
+ }
return true;
}
-bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
+// Parses mAB and mBA type elements
+static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
{
- Q_ASSERT((reinterpret_cast<qintptr>(data.constData()) & 0x3) == 0);
- if (reinterpret_cast<qintptr>(data.constData()) & 0x3) {
- qCWarning(lcIcc) << "fromIccProfile: Unaligned profile data";
+ if (tagEntry.size < sizeof(mABTagData)) {
+ qCWarning(lcIcc) << "Undersized mAB/mBA tag";
return false;
}
- if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
- qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
+ if (qsizetype(tagEntry.size) > data.size()) {
+ qCWarning(lcIcc) << "Truncated mAB/mBA tag";
return false;
}
- const ICCProfileHeader *header = (const ICCProfileHeader *)data.constData();
- if (!isValidIccProfile(*header))
- return false; // if failed we already printing a warning
- if (qsizetype(header->profileSize) > data.size()) {
- qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
+ const mABTagData mab = qFromUnaligned<mABTagData>(data.constData() + tagEntry.offset);
+ if ((mab.type != quint32(Tag::mAB_) && isAb) || (mab.type != quint32(Tag::mBA_) && !isAb)){
+ qCWarning(lcIcc) << "Bad mAB/mBA content type";
return false;
}
- // Read tag index
- const TagTableEntry *tagTable = (const TagTableEntry *)(data.constData() + sizeof(ICCProfileHeader));
- const qsizetype offsetToData = sizeof(ICCProfileHeader) + header->tagCount * sizeof(TagTableEntry);
- Q_ASSERT(offsetToData > 0);
- if (offsetToData > data.size()) {
- qCWarning(lcIcc) << "fromIccProfile: failed index size sanity";
+ const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+ const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
+
+ if (mab.inputChannels != inputChannels) {
+ qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
return false;
}
- QHash<Tag, TagEntry> tagIndex;
- for (uint i = 0; i < header->tagCount; ++i) {
- // Sanity check tag sizes and offsets:
- if (qsizetype(tagTable[i].offset) < offsetToData) {
- qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
+ if (mab.outputChannels != outputChannels) {
+ qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
+ return false;
+ }
+
+ // These combinations are legal: B, M + Matrix + B, A + Clut + B, A + Clut + M + Matrix + B
+ if (!mab.bCurvesOffset) {
+ qCWarning(lcIcc) << "Illegal mAB/mBA without B table";
+ return false;
+ }
+ if (((bool)mab.matrixOffset != (bool)mab.mCurvesOffset) ||
+ ((bool)mab.aCurvesOffset != (bool)mab.clutOffset)) {
+ qCWarning(lcIcc) << "Illegal mAB/mBA element combination";
+ return false;
+ }
+
+ if (mab.aCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
+ mab.bCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
+ mab.mCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
+ mab.matrixOffset > (tagEntry.size - 4 * 12) ||
+ mab.clutOffset > (tagEntry.size - 20)) {
+ qCWarning(lcIcc) << "Illegal mAB/mBA element offset";
+ return false;
+ }
+
+ QColorSpacePrivate::TransferElement bTableElement;
+ QColorSpacePrivate::TransferElement aTableElement;
+ QColorCLUT clutElement;
+ QColorSpacePrivate::TransferElement mTableElement;
+ QColorMatrix matrixElement;
+ QColorVector offsetElement;
+
+ auto parseCurves = [&data, &tagEntry] (uint curvesOffset, QColorTrc *table, int channels) {
+ for (int i = 0; i < channels; ++i) {
+ if (qsizetype(tagEntry.offset + curvesOffset + 12) > data.size() || curvesOffset + 12 > tagEntry.size) {
+ qCWarning(lcIcc) << "Space missing for channel curves in mAB/mBA";
+ return false;
+ }
+ auto size = parseTRC(QByteArrayView(data).sliced(tagEntry.offset + curvesOffset, tagEntry.size - curvesOffset), table[i], QColorTransferTable::OneWay);
+ if (!size)
+ return false;
+ if (size & 2) size += 2; // possible padding
+ curvesOffset += size;
+ }
+ return true;
+ };
+
+ bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
+
+ // B Curves
+ if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
+ qCWarning(lcIcc) << "Invalid B curves";
+ return false;
+ } else {
+ bCurvesAreLinear = bTableElement.trc[0].isIdentity() && bTableElement.trc[1].isIdentity() && bTableElement.trc[2].isIdentity();
+ }
+
+ // A Curves
+ if (mab.aCurvesOffset) {
+ if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
+ qCWarning(lcIcc) << "Invalid A curves";
return false;
+ } else {
+ aCurvesAreLinear = aTableElement.trc[0].isIdentity() && aTableElement.trc[1].isIdentity() && aTableElement.trc[2].isIdentity();
}
- // Checked separately from (+ size) to handle overflow.
- if (tagTable[i].offset > header->profileSize) {
- qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
+ }
+
+ // M Curves
+ if (mab.mCurvesOffset) {
+ if (!parseCurves(mab.mCurvesOffset, mTableElement.trc, 3)) {
+ qCWarning(lcIcc) << "Invalid M curves";
return false;
+ } else {
+ mCurvesAreLinear = mTableElement.trc[0].isIdentity() && mTableElement.trc[1].isIdentity() && mTableElement.trc[2].isIdentity();
}
- if (tagTable[i].size < 12) {
- qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
+ }
+
+ // Matrix
+ if (mab.matrixOffset) {
+ const MatrixElement matrix = qFromUnaligned<MatrixElement>(data.constData() + tagEntry.offset + mab.matrixOffset);
+ matrixElement.r.x = fromFixedS1516(matrix.e0);
+ matrixElement.g.x = fromFixedS1516(matrix.e1);
+ matrixElement.b.x = fromFixedS1516(matrix.e2);
+ matrixElement.r.y = fromFixedS1516(matrix.e3);
+ matrixElement.g.y = fromFixedS1516(matrix.e4);
+ matrixElement.b.y = fromFixedS1516(matrix.e5);
+ matrixElement.r.z = fromFixedS1516(matrix.e6);
+ matrixElement.g.z = fromFixedS1516(matrix.e7);
+ matrixElement.b.z = fromFixedS1516(matrix.e8);
+ offsetElement.x = fromFixedS1516(matrix.e9);
+ offsetElement.y = fromFixedS1516(matrix.e10);
+ offsetElement.z = fromFixedS1516(matrix.e11);
+ if (!matrixElement.isValid() || !offsetElement.isValid()) {
+ qCWarning(lcIcc) << "Invalid matrix values in mAB/mBA element";
return false;
}
- if (tagTable[i].size > header->profileSize - tagTable[i].offset) {
- qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
+ }
+
+ // CLUT
+ if (mab.clutOffset) {
+ clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]);
+ clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]);
+ clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab.clutOffset + 2]);
+ clutElement.gridPointsW = std::max(uint8_t(data[tagEntry.offset + mab.clutOffset + 3]), uint8_t(1));
+ const uchar precision = data[tagEntry.offset + mab.clutOffset + 16];
+ if (precision > 2 || precision < 1) {
+ qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision";
return false;
}
- if (tagTable[i].offset & 0x03) {
- qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment";
+ if (clutElement.gridPointsX < 2 || clutElement.gridPointsY < 2 || clutElement.gridPointsZ < 2) {
+ qCWarning(lcIcc) << "Empty CLUT";
return false;
}
-// printf("'%4s' %d %d\n", (const char *)&tagTable[i].signature,
-// quint32(tagTable[i].offset),
-// quint32(tagTable[i].size));
- tagIndex.insert(Tag(quint32(tagTable[i].signature)), { tagTable[i].offset, tagTable[i].size });
+ const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
+ if ((mab.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
+ qCWarning(lcIcc) << "CLUT oversized for tag";
+ return false;
+ }
+
+ clutElement.table.resize(clutTableSize);
+ if (precision == 2) {
+ QList<uint16_t> clutTable(clutTableSize * outputChannels);
+ qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
+ parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, outputChannels);
+ } else {
+ const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
+ parseCLUT(clutTable, (1.f/255.f), &clutElement, outputChannels);
+ }
+ } else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
+ qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
+ return false;
}
- // Check the profile is three-component matrix based (what we currently support):
- if (header->inputColorSpace == uint(ColorSpaceType::Rgb)) {
- if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
- !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
- !tagIndex.contains(Tag::wtpt)) {
- qCWarning(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based";
- return false;
+ if (isAb) {
+ if (mab.aCurvesOffset) {
+ if (!aCurvesAreLinear)
+ colorSpacePrivate->mAB.append(std::move(aTableElement));
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mAB.append(std::move(clutElement));
+ }
+ if (mab.mCurvesOffset && outputChannels == 3) {
+ if (!mCurvesAreLinear)
+ colorSpacePrivate->mAB.append(std::move(mTableElement));
+ if (!matrixElement.isIdentity())
+ colorSpacePrivate->mAB.append(std::move(matrixElement));
+ if (!offsetElement.isNull())
+ colorSpacePrivate->mAB.append(std::move(offsetElement));
}
+ if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
+ colorSpacePrivate->mAB.append(std::move(bTableElement));
} else {
- Q_ASSERT(header->inputColorSpace == uint(ColorSpaceType::Gray));
- if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
- return false;
+ if (!bCurvesAreLinear)
+ colorSpacePrivate->mBA.append(std::move(bTableElement));
+ if (mab.mCurvesOffset && inputChannels == 3) {
+ if (!matrixElement.isIdentity())
+ colorSpacePrivate->mBA.append(std::move(matrixElement));
+ if (!offsetElement.isNull())
+ colorSpacePrivate->mBA.append(std::move(offsetElement));
+ if (!mCurvesAreLinear)
+ colorSpacePrivate->mBA.append(std::move(mTableElement));
}
+ if (mab.aCurvesOffset) {
+ if (!clutElement.isEmpty())
+ colorSpacePrivate->mBA.append(std::move(clutElement));
+ if (!aCurvesAreLinear)
+ colorSpacePrivate->mBA.append(std::move(aTableElement));
+ }
+ if (colorSpacePrivate->mBA.isEmpty()) // Ensure non-empty to indicate valid empty transform
+ colorSpacePrivate->mBA.append(std::move(bTableElement));
}
- QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::getWritable(*colorSpace);
+ return true;
+}
+
+static bool parseA2B(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *privat, bool isAb)
+{
+ const GenericTagData a2bData = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
+ if (a2bData.type == quint32(Tag::mft1))
+ return parseLutData<Lut8TagData>(data, tagEntry, privat, isAb);
+ else if (a2bData.type == quint32(Tag::mft2))
+ return parseLutData<Lut16TagData>(data, tagEntry, privat, isAb);
+ else if (a2bData.type == quint32(Tag::mAB_) || a2bData.type == quint32(Tag::mBA_))
+ return parseMabData(data, tagEntry, privat, isAb);
+
+ qCWarning(lcIcc) << "fromIccProfile: Unknown A2B/B2A data type";
+ return false;
+}
+
+static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
+{
+ const GenericTagData tag = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
- if (header->inputColorSpace == uint(ColorSpaceType::Rgb)) {
- // Parse XYZ tags
- if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
+ // Either 'desc' (ICCv2) or 'mluc' (ICCv4)
+ if (tag.type == quint32(Tag::desc)) {
+ if (tagEntry.size < sizeof(DescTagData))
return false;
- if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
+ Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
+ const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
+ const quint32 len = desc.asciiDescriptionLength;
+ if (len < 1)
return false;
- if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
+ if (tagEntry.size - 12 < len)
return false;
- if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
+ const char *asciiDescription = data.constData() + tagEntry.offset + sizeof(DescTagData);
+ if (asciiDescription[len - 1] != '\0')
return false;
+ descName = QString::fromLatin1(asciiDescription, len - 1);
+ return true;
+ }
+ if (tag.type != quint32(Tag::mluc))
+ return false;
- colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
- if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
- } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
- } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
- qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
- }
- if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
- qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
- colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
- }
- } else {
- // We will use sRGB primaries and fit to match the given white-point if
- // it doesn't match sRGB's.
- QColorVector whitePoint;
- if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
- return false;
- if (!qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z - whitePoint.x) == 0.0f) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
- return false;
- }
- if (whitePoint == QColorVector::D65()) {
- colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
- } else {
- colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
- // Calculate chromaticity from xyz (assuming y == 1.0f).
- float y = 1.0f / (1.0f + whitePoint.z - whitePoint.x);
- float x = whitePoint.x * y;
- QColorSpacePrimaries primaries(QColorSpace::Primaries::SRgb);
- primaries.whitePoint = QPointF(x,y);
- if (!primaries.areValid()) {
- qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - invalid white-point";
- return false;
- }
- colorspaceDPtr->toXyz = primaries.toXyzMatrix();
- }
+ if (tagEntry.size < sizeof(MlucTagData))
+ return false;
+ const MlucTagData mluc = qFromUnaligned<MlucTagData>(data.constData() + tagEntry.offset);
+ if (mluc.recordCount < 1)
+ return false;
+ if (mluc.recordSize != 12)
+ return false;
+ // We just use the primary record regardless of language or country.
+ const quint32 stringOffset = mluc.records[0].offset;
+ const quint32 stringSize = mluc.records[0].size;
+ if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
+ return false;
+ if ((stringSize | stringOffset) & 1)
+ return false;
+ quint32 stringLen = stringSize / 2;
+ QVarLengthArray<char16_t> utf16hostendian(stringLen);
+ qFromBigEndian<char16_t>(data.constData() + tagEntry.offset + stringOffset, stringLen,
+ utf16hostendian.data());
+ // The given length shouldn't include 0-termination, but might.
+ if (stringLen > 1 && utf16hostendian[stringLen - 1] == 0)
+ --stringLen;
+ descName = QString::fromUtf16(utf16hostendian.data(), stringLen);
+ return true;
+}
+
+static bool parseRgbMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
+{
+ // Parse XYZ tags
+ if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
+ return false;
+ if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
+ return false;
+ if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
+ return false;
+ if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
+ return false;
+ if (!colorspaceDPtr->toXyz.isValid() || !colorspaceDPtr->whitePoint.isValid() || colorspaceDPtr->whitePoint.isNull()) {
+ qCWarning(lcIcc) << "Invalid XYZ values in RGB matrix";
+ return false;
+ }
+
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
+ if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
+ qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
+ qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
+ } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
+ qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
+ colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
+ } 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;
+}
+
+static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
+{
+ QColorVector whitePoint;
+ if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
+ return false;
+ if (!whitePoint.isValid() || !qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z + whitePoint.x) == 0.0f) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
+ return false;
+ }
+ colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
+ colorspaceDPtr->whitePoint = whitePoint;
+ return true;
+}
+
+static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
+{
+ if (tagEntry.size < sizeof(Sf32TagData) || qsizetype(tagEntry.size) > data.size())
+ return false;
+ const Sf32TagData chadtag = qFromUnaligned<Sf32TagData>(data.constData() + tagEntry.offset);
+ if (chadtag.type != uint32_t(Tag::sf32)) {
+ qCWarning(lcIcc, "fromIccProfile: bad chad data type");
+ return false;
}
- // Reset the matrix to our canonical values:
- if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
- colorspaceDPtr->setToXyzMatrix();
+ QColorMatrix chad;
+ chad.r.x = fromFixedS1516(chadtag.value[0]);
+ chad.g.x = fromFixedS1516(chadtag.value[1]);
+ chad.b.x = fromFixedS1516(chadtag.value[2]);
+ chad.r.y = fromFixedS1516(chadtag.value[3]);
+ chad.g.y = fromFixedS1516(chadtag.value[4]);
+ chad.b.y = fromFixedS1516(chadtag.value[5]);
+ chad.r.z = fromFixedS1516(chadtag.value[6]);
+ chad.g.z = fromFixedS1516(chadtag.value[7]);
+ chad.b.z = fromFixedS1516(chadtag.value[8]);
+
+ if (!chad.isValid()) {
+ qCWarning(lcIcc, "fromIccProfile: invalid chad matrix");
+ return false;
+ }
+ colorspaceDPtr->chad = chad;
+ return true;
+}
- // Parse TRC tags
+static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
+{
TagEntry rTrc;
TagEntry gTrc;
TagEntry bTrc;
- if (header->inputColorSpace == uint(ColorSpaceType::Gray)) {
+ if (isColorSpaceTypeGray) {
rTrc = tagIndex[Tag::kTRC];
gTrc = tagIndex[Tag::kTRC];
bTrc = tagIndex[Tag::kTRC];
@@ -766,30 +1747,30 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
QColorTrc rCurve;
QColorTrc gCurve;
QColorTrc bCurve;
- if (!parseTRC(data, rTrc, rCurve)) {
+ if (!parseTRC(QByteArrayView(data).sliced(rTrc.offset, rTrc.size), rCurve, QColorTransferTable::TwoWay)) {
qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
return false;
}
- if (!parseTRC(data, gTrc, gCurve)) {
+ if (!parseTRC(QByteArrayView(data).sliced(gTrc.offset, gTrc.size), gCurve, QColorTransferTable::TwoWay)) {
qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
return false;
}
- if (!parseTRC(data, bTrc, bCurve)) {
+ if (!parseTRC(QByteArrayView(data).sliced(bTrc.offset, bTrc.size), bCurve, QColorTransferTable::TwoWay)) {
qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
return false;
}
- if (rCurve == gCurve && gCurve == bCurve && rCurve.m_type == QColorTrc::Type::Function) {
- if (rCurve.m_fun.isLinear()) {
+ if (rCurve == gCurve && gCurve == bCurve) {
+ if (rCurve.isIdentity()) {
qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
colorspaceDPtr->trc[0] = QColorTransferFunction();
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
colorspaceDPtr->gamma = 1.0f;
- } else if (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_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;
@@ -797,7 +1778,6 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->trc[0] = rCurve;
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
}
-
colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
} else {
@@ -806,9 +1786,224 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->trc[2] = bCurve;
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
}
+ 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<CicpTagData>(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))) {
+ qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
+ return false;
+ }
+ const ICCProfileHeader header = qFromUnaligned<ICCProfileHeader>(data.constData());
+ if (!isValidIccProfile(header))
+ return false; // if failed we already printing a warning
+ if (qsizetype(header.profileSize) > data.size() || qsizetype(header.profileSize) < qsizetype(sizeof(ICCProfileHeader))) {
+ qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
+ return false;
+ }
+
+ const qsizetype offsetToData = sizeof(ICCProfileHeader) + header.tagCount * sizeof(TagTableEntry);
+ Q_ASSERT(offsetToData > 0);
+ if (offsetToData > data.size()) {
+ qCWarning(lcIcc) << "fromIccProfile: failed index size sanity";
+ return false;
+ }
+
+ QHash<Tag, TagEntry> tagIndex;
+ for (uint i = 0; i < header.tagCount; ++i) {
+ // Read tag index
+ const qsizetype tableOffset = sizeof(ICCProfileHeader) + i * sizeof(TagTableEntry);
+ const TagTableEntry tagTable = qFromUnaligned<TagTableEntry>(data.constData()
+ + tableOffset);
+
+ // Sanity check tag sizes and offsets:
+ if (qsizetype(tagTable.offset) < offsetToData) {
+ qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
+ return false;
+ }
+ // Checked separately from (+ size) to handle overflow.
+ if (tagTable.offset > header.profileSize) {
+ qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
+ return false;
+ }
+ if (tagTable.size < 8) {
+ qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
+ return false;
+ }
+ if (tagTable.size > header.profileSize - tagTable.offset) {
+ qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
+ return false;
+ }
+ if (tagTable.offset & 0x03) {
+ qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment";
+ return false;
+ }
+// printf("'%4s' %d %d\n", (const char *)&tagTable.signature,
+// quint32(tagTable.offset),
+// quint32(tagTable.size));
+ tagIndex.insert(Tag(quint32(tagTable.signature)), { tagTable.offset, tagTable.size });
+ }
+
+ bool threeComponentMatrix = true;
+
+ if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
+ // Check the profile is three-component matrix based:
+ if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
+ !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
+ !tagIndex.contains(Tag::wtpt) || header.pcs == uint(Tag::Lab_)) {
+ threeComponentMatrix = false;
+ // Check if the profile is valid n-LUT based:
+ if (!tagIndex.contains(Tag::A2B0)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
+ return false;
+ }
+ }
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
+ if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
+ return false;
+ }
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) {
+ threeComponentMatrix = false;
+ if (!tagIndex.contains(Tag::A2B0)) {
+ qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
+ return false;
+ }
+ } else {
+ Q_UNREACHABLE();
+ }
+
+ 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->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
+
+ if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
+ if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr))
+ return false;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
+ } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
+ if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
+ return false;
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
+ } else {
+ Q_UNREACHABLE();
+ }
+ if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
+ if (!parseChad(data, it.value(), colorspaceDPtr))
+ return false;
+ } else {
+ colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
+ }
+ if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
+ colorspaceDPtr->toXyz = colorspaceDPtr->chad;
+
+ // Reset the matrix to our canonical values:
+ if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
+ colorspaceDPtr->setToXyzMatrix();
+
+ if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom &&
+ !parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
+ return false;
+ } else {
+ colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
+ if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
+ else
+ colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
+
+ // Only parse the default perceptual transform for now
+ if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
+ return false;
+ if (auto it = tagIndex.constFind(Tag::B2A0); it != tagIndex.constEnd()) {
+ if (!parseA2B(data, it.value(), colorspaceDPtr, false))
+ return false;
+ }
+
+ if (auto it = tagIndex.constFind(Tag::wtpt); it != tagIndex.constEnd()) {
+ if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
+ return false;
+ }
+ if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
+ if (!parseChad(data, it.value(), colorspaceDPtr))
+ return false;
+ } else if (!colorspaceDPtr->whitePoint.isNull()) {
+ colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
+ }
+ }
- if (tagIndex.contains(Tag::desc)) {
- if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description))
+ if (auto it = tagIndex.constFind(Tag::desc); it != tagIndex.constEnd()) {
+ if (!parseDesc(data, it.value(), colorspaceDPtr->description))
qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
else
qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
@@ -820,6 +2015,7 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->iccProfile = data;
+ Q_ASSERT(colorspaceDPtr->isValid());
return true;
}