diff options
Diffstat (limited to 'src/gui/painting/qicc.cpp')
-rw-r--r-- | src/gui/painting/qicc.cpp | 1896 |
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; } |