| /**************************************************************************** |
| ** |
| ** 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$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qicc_p.h" |
| |
| #include <qbuffer.h> |
| #include <qbytearray.h> |
| #include <qdatastream.h> |
| #include <qendian.h> |
| #include <qloggingcategory.h> |
| #include <qstring.h> |
| |
| #include "qcolorspace_p.h" |
| #include "qcolortrc_p.h" |
| |
| QT_BEGIN_NAMESPACE |
| Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc") |
| |
| struct ICCProfileHeader |
| { |
| quint32_be profileSize; |
| |
| quint32_be preferredCmmType; |
| |
| quint32_be profileVersion; |
| quint32_be profileClass; |
| quint32_be inputColorSpace; |
| quint32_be pcs; |
| quint32_be datetime[3]; |
| quint32_be signature; |
| quint32_be platformSignature; |
| quint32_be flags; |
| quint32_be deviceManufacturer; |
| quint32_be deviceModel; |
| quint32_be deviceAttributes[2]; |
| |
| quint32_be renderingIntent; |
| qint32_be illuminantXyz[3]; |
| |
| quint32_be creatorSignature; |
| quint32_be profileId[4]; |
| |
| quint32_be reserved[7]; |
| |
| // Technically after the header, but easier to include here: |
| quint32_be tagCount; |
| }; |
| |
| constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d) |
| { |
| return (a << 24) | (b << 16) | (c << 8) | d; |
| } |
| |
| enum class ProfileClass : quint32 { |
| Input = IccTag('s', 'c', 'r', 'n'), |
| Display = IccTag('m', 'n', 't', 'r'), |
| // Not supported: |
| Output = IccTag('p', 'r', 't', 'r'), |
| ColorSpace = IccTag('s', 'p', 'a', 'c'), |
| }; |
| |
| enum class Tag : quint32 { |
| acsp = IccTag('a', 'c', 's', 'p'), |
| RGB_ = IccTag('R', 'G', 'B', ' '), |
| XYZ_ = IccTag('X', 'Y', 'Z', ' '), |
| rXYZ = IccTag('r', 'X', 'Y', 'Z'), |
| gXYZ = IccTag('g', 'X', 'Y', 'Z'), |
| bXYZ = IccTag('b', 'X', 'Y', 'Z'), |
| rTRC = IccTag('r', 'T', 'R', 'C'), |
| gTRC = IccTag('g', 'T', 'R', 'C'), |
| bTRC = IccTag('b', 'T', 'R', 'C'), |
| A2B0 = IccTag('A', '2', 'B', '0'), |
| A2B1 = IccTag('A', '2', 'B', '1'), |
| B2A0 = IccTag('B', '2', 'A', '0'), |
| B2A1 = IccTag('B', '2', 'A', '1'), |
| desc = IccTag('d', 'e', 's', 'c'), |
| text = IccTag('t', 'e', 'x', 't'), |
| cprt = IccTag('c', 'p', 'r', 't'), |
| curv = IccTag('c', 'u', 'r', 'v'), |
| para = IccTag('p', 'a', 'r', 'a'), |
| wtpt = IccTag('w', 't', 'p', 't'), |
| bkpt = IccTag('b', 'k', 'p', 't'), |
| mft1 = IccTag('m', 'f', 't', '1'), |
| mft2 = IccTag('m', 'f', 't', '2'), |
| mluc = IccTag('m', 'l', 'u', 'c'), |
| mAB_ = IccTag('m', 'A', 'B', ' '), |
| mBA_ = IccTag('m', 'B', 'A', ' '), |
| chad = IccTag('c', 'h', 'a', 'd'), |
| sf32 = IccTag('s', 'f', '3', '2'), |
| |
| // Apple extensions for ICCv2: |
| aarg = IccTag('a', 'a', 'r', 'g'), |
| aagg = IccTag('a', 'a', 'g', 'g'), |
| aabg = IccTag('a', 'a', 'b', 'g'), |
| }; |
| |
| inline uint qHash(const Tag &key, uint seed = 0) |
| { |
| return qHash(quint32(key), seed); |
| } |
| |
| namespace QIcc { |
| |
| struct TagTableEntry |
| { |
| quint32_be signature; |
| quint32_be offset; |
| quint32_be size; |
| }; |
| |
| struct GenericTagData { |
| quint32_be type; |
| quint32_be null; |
| }; |
| |
| struct XYZTagData : GenericTagData { |
| qint32_be fixedX; |
| qint32_be fixedY; |
| qint32_be fixedZ; |
| }; |
| |
| struct CurvTagData : GenericTagData { |
| quint32_be valueCount; |
| quint16_be value[1]; |
| }; |
| |
| struct ParaTagData : GenericTagData { |
| quint16_be curveType; |
| quint16_be null2; |
| quint32_be parameter[1]; |
| }; |
| |
| struct DescTagData : GenericTagData { |
| quint32_be asciiDescriptionLength; |
| char asciiDescription[1]; |
| // .. we ignore the rest |
| }; |
| |
| struct MlucTagRecord { |
| quint16_be languageCode; |
| quint16_be countryCode; |
| quint32_be size; |
| quint32_be offset; |
| }; |
| |
| struct MlucTagData : GenericTagData { |
| quint32_be recordCount; |
| quint32_be recordSize; // = sizeof(MlucTagRecord) |
| MlucTagRecord records[1]; |
| }; |
| |
| // For both mAB and mBA |
| struct mABTagData : GenericTagData { |
| quint8 inputChannels; |
| quint8 outputChannels; |
| quint8 padding[2]; |
| quint32_be bCurvesOffset; |
| quint32_be matrixOffset; |
| quint32_be mCurvesOffset; |
| quint32_be clutOffset; |
| quint32_be aCurvesOffset; |
| }; |
| |
| struct Sf32TagData : GenericTagData { |
| quint32_be value[1]; |
| }; |
| |
| static int toFixedS1516(float x) |
| { |
| return int(x * 65536.0f + 0.5f); |
| } |
| |
| static float fromFixedS1516(int x) |
| { |
| return x * (1.0f / 65536.0f); |
| } |
| |
| static bool isValidIccProfile(const ICCProfileHeader &header) |
| { |
| if (header.signature != uint(Tag::acsp)) { |
| qCWarning(lcIcc, "Failed ICC signature test"); |
| return false; |
| } |
| |
| // Don't overflow 32bit integers: |
| if (header.tagCount >= INT32_MAX / sizeof(TagTableEntry)) |
| return false; |
| if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) { |
| qCWarning(lcIcc, "Failed basic size sanity"); |
| return false; |
| } |
| |
| if (header.profileClass != uint(ProfileClass::Input) |
| && header.profileClass != uint(ProfileClass::Display)) { |
| qCWarning(lcIcc, "Unsupported ICC profile class %x", quint32(header.profileClass)); |
| return false; |
| } |
| if (header.inputColorSpace != 0x52474220 /* 'RGB '*/) { |
| qCWarning(lcIcc, "Unsupported ICC input color space %x", quint32(header.inputColorSpace)); |
| return false; |
| } |
| if (header.pcs != 0x58595a20 /* 'XYZ '*/) { |
| // ### support PCSLAB |
| qCWarning(lcIcc, "Unsupported ICC profile connection space %x", quint32(header.pcs)); |
| return false; |
| } |
| |
| QColorVector illuminant; |
| illuminant.x = fromFixedS1516(header.illuminantXyz[0]); |
| illuminant.y = fromFixedS1516(header.illuminantXyz[1]); |
| illuminant.z = fromFixedS1516(header.illuminantXyz[2]); |
| if (illuminant != QColorVector::D50()) { |
| qCWarning(lcIcc, "Invalid ICC illuminant"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int writeColorTrc(QDataStream &stream, const QColorTrc &trc) |
| { |
| if (trc.isLinear()) { |
| stream << uint(Tag::curv) << uint(0); |
| stream << uint(0); |
| return 12; |
| } |
| |
| if (trc.m_type == QColorTrc::Type::Function) { |
| const QColorTransferFunction &fun = trc.m_fun; |
| stream << uint(Tag::para) << uint(0); |
| if (fun.isGamma()) { |
| stream << ushort(0) << ushort(0); |
| stream << toFixedS1516(fun.m_g); |
| return 12 + 4; |
| } |
| bool type3 = qFuzzyIsNull(fun.m_e) && qFuzzyIsNull(fun.m_f); |
| stream << ushort(type3 ? 3 : 4) << ushort(0); |
| stream << toFixedS1516(fun.m_g); |
| stream << toFixedS1516(fun.m_a); |
| stream << toFixedS1516(fun.m_b); |
| stream << toFixedS1516(fun.m_c); |
| stream << toFixedS1516(fun.m_d); |
| if (type3) |
| return 12 + 5 * 4; |
| stream << toFixedS1516(fun.m_e); |
| stream << toFixedS1516(fun.m_f); |
| return 12 + 7 * 4; |
| } |
| |
| Q_ASSERT(trc.m_type == QColorTrc::Type::Table); |
| stream << uint(Tag::curv) << uint(0); |
| stream << uint(trc.m_table.m_tableSize); |
| if (!trc.m_table.m_table16.isEmpty()) { |
| for (uint i = 0; i < trc.m_table.m_tableSize; ++i) { |
| stream << ushort(trc.m_table.m_table16[i]); |
| } |
| } else { |
| for (uint i = 0; i < trc.m_table.m_tableSize; ++i) { |
| stream << ushort(trc.m_table.m_table8[i] * 257U); |
| } |
| } |
| return 12 + 2 * trc.m_table.m_tableSize; |
| } |
| |
| QByteArray toIccProfile(const QColorSpace &space) |
| { |
| if (!space.isValid()) |
| return QByteArray(); |
| |
| const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space); |
| |
| 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; |
| |
| QBuffer buffer; |
| buffer.open(QIODevice::WriteOnly); |
| QDataStream stream(&buffer); |
| |
| // 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(ProfileClass::Display); |
| stream << uint(Tag::RGB_); |
| stream << 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(0x0000f6d6); // D50 X |
| stream << uint(0x00010000); // D50 Y |
| stream << uint(0x0000d32d); // D50 Z |
| stream << IccTag('Q','t', QT_VERSION_MAJOR, QT_VERSION_MINOR); |
| 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; |
| |
| // 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; |
| } else { |
| bTrcOffset = currentOffset; |
| bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]); |
| currentOffset += bTrcSize; |
| } |
| |
| 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; |
| currentOffset += descSize; |
| |
| buffer.close(); |
| QByteArray iccProfile = buffer.buffer(); |
| // 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 !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) |
| const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData(); |
| Q_ASSERT(qsizetype(iccHeader->profileSize) == qsizetype(iccProfile.size())); |
| Q_ASSERT(isValidIccProfile(*iccHeader)); |
| #endif |
| |
| return iccProfile; |
| } |
| |
| struct TagEntry { |
| quint32 offset; |
| quint32 size; |
| }; |
| |
| 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_)) { |
| 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); |
| |
| colorVector = QColorVector(x, y, z); |
| return true; |
| } |
| |
| bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma) |
| { |
| 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; |
| 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 { |
| 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)); |
| QColorTransferFunction curve; |
| 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_fun = curve; |
| } |
| } |
| return true; |
| } |
| if (trcData->type == quint32(Tag::para)) { |
| if (tagEntry.size < sizeof(ParaTagData)) |
| return false; |
| const ParaTagData *para = static_cast<const ParaTagData *>(trcData); |
| switch (para->curveType) { |
| case 0: { |
| float g = fromFixedS1516(para->parameter[0]); |
| gamma.m_type = QColorTrc::Type::Function; |
| gamma.m_fun = QColorTransferFunction::fromGamma(g); |
| break; |
| } |
| 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]); |
| float d = -b / a; |
| gamma.m_type = QColorTrc::Type::Function; |
| gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g); |
| break; |
| } |
| 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]); |
| float d = -b / a; |
| gamma.m_type = QColorTrc::Type::Function; |
| gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g); |
| break; |
| } |
| 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; |
| gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g); |
| break; |
| } |
| 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; |
| gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g); |
| break; |
| } |
| default: |
| qCWarning(lcIcc) << "Unknown para type" << uint(para->curveType); |
| return false; |
| } |
| return true; |
| } |
| qCWarning(lcIcc) << "Invalid TRC data type"; |
| return false; |
| } |
| |
| bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName) |
| { |
| const GenericTagData *tag = (const GenericTagData *)(data.constData() + tagEntry.offset); |
| |
| // 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) |
| return false; |
| if (tagEntry.size - 12 < len) |
| return false; |
| if (desc->asciiDescription[len - 1] != '\0') |
| return false; |
| descName = QString::fromLatin1(desc->asciiDescription, len - 1); |
| return true; |
| } |
| if (tag->type != quint32(Tag::mluc)) |
| return false; |
| |
| if (tagEntry.size < sizeof(MlucTagData)) |
| return false; |
| const MlucTagData *mluc = (const 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; |
| 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); |
| 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 = (const ICCProfileHeader *)data.constData(); |
| if (!isValidIccProfile(*header)) { |
| qCWarning(lcIcc) << "fromIccProfile: failed general sanity check"; |
| return false; |
| } |
| if (qsizetype(header->profileSize) > data.size()) { |
| qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2"; |
| return false; |
| } |
| |
| // Read tag index |
| const TagTableEntry *tagTable = (const TagTableEntry *)(data.constData() + sizeof(ICCProfileHeader)); |
| const qsizetype offsetToData = sizeof(ICCProfileHeader) + header->tagCount * sizeof(TagTableEntry); |
| 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) { |
| // Sanity check tag sizes and offsets: |
| if (qsizetype(tagTable[i].offset) < offsetToData) { |
| qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1"; |
| return false; |
| } |
| // Checked separately from (+ size) to handle overflow. |
| if (tagTable[i].offset > header->profileSize) { |
| qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2"; |
| return false; |
| } |
| if (tagTable[i].size < 12) { |
| qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity"; |
| return false; |
| } |
| if (tagTable[i].size > header->profileSize - tagTable[i].offset) { |
| qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity"; |
| return false; |
| } |
| if (tagTable[i].offset & 0x03) { |
| qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment"; |
| 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 }); |
| } |
| |
| // Check the profile is three-component matrix based (what we currently support): |
| 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; |
| } |
| |
| QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::getWritable(*colorSpace); |
| |
| // 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; |
| |
| 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; |
| } |
| // Reset the matrix to our canonical values: |
| if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom) |
| colorspaceDPtr->setToXyzMatrix(); |
| |
| // Parse TRC tags |
| TagEntry rTrc; |
| TagEntry gTrc; |
| TagEntry bTrc; |
| if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) { |
| // Apple extension for parametric version of TRCs in ICCv2: |
| rTrc = tagIndex[Tag::aarg]; |
| gTrc = tagIndex[Tag::aagg]; |
| bTrc = tagIndex[Tag::aabg]; |
| } else { |
| rTrc = tagIndex[Tag::rTRC]; |
| gTrc = tagIndex[Tag::gTRC]; |
| bTrc = tagIndex[Tag::bTRC]; |
| } |
| |
| QColorTrc rCurve; |
| QColorTrc gCurve; |
| QColorTrc bCurve; |
| if (!parseTRC(data, rTrc, rCurve)) { |
| qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC"; |
| return false; |
| } |
| if (!parseTRC(data, gTrc, gCurve)) { |
| qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC"; |
| return false; |
| } |
| if (!parseTRC(data, bTrc, bCurve)) { |
| qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC"; |
| return false; |
| } |
| if (rCurve == gCurve && gCurve == bCurve && rCurve.m_type == QColorTrc::Type::Function) { |
| if (rCurve.m_fun.isLinear()) { |
| 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()) { |
| 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()) { |
| qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected"; |
| colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb(); |
| colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb; |
| } else { |
| colorspaceDPtr->trc[0] = rCurve; |
| colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom; |
| } |
| |
| colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0]; |
| colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0]; |
| } else { |
| colorspaceDPtr->trc[0] = rCurve; |
| colorspaceDPtr->trc[1] = gCurve; |
| colorspaceDPtr->trc[2] = bCurve; |
| colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom; |
| } |
| |
| if (tagIndex.contains(Tag::desc)) { |
| if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description)) |
| qCWarning(lcIcc) << "fromIccProfile: Failed to parse description"; |
| else |
| qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description; |
| } |
| |
| colorspaceDPtr->identifyColorSpace(); |
| if (colorspaceDPtr->namedColorSpace) |
| qCDebug(lcIcc) << "fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace); |
| |
| colorspaceDPtr->iccProfile = data; |
| |
| return true; |
| } |
| |
| } // namespace QIcc |
| |
| QT_END_NAMESPACE |