1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qicc_p.h"
41 
42 #include <qbuffer.h>
43 #include <qbytearray.h>
44 #include <qdatastream.h>
45 #include <qendian.h>
46 #include <qloggingcategory.h>
47 #include <qstring.h>
48 
49 #include "qcolorspace_p.h"
50 #include "qcolortrc_p.h"
51 
52 #include <array>
53 
54 QT_BEGIN_NAMESPACE
55 Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc")
56 
57 struct ICCProfileHeader
58 {
59     quint32_be profileSize;
60 
61     quint32_be preferredCmmType;
62 
63     quint32_be profileVersion;
64     quint32_be profileClass;
65     quint32_be inputColorSpace;
66     quint32_be pcs;
67     quint32_be datetime[3];
68     quint32_be signature;
69     quint32_be platformSignature;
70     quint32_be flags;
71     quint32_be deviceManufacturer;
72     quint32_be deviceModel;
73     quint32_be deviceAttributes[2];
74 
75     quint32_be renderingIntent;
76     qint32_be  illuminantXyz[3];
77 
78     quint32_be creatorSignature;
79     quint32_be profileId[4];
80 
81     quint32_be reserved[7];
82 
83 // Technically after the header, but easier to include here:
84     quint32_be tagCount;
85 };
86 
IccTag(uchar a,uchar b,uchar c,uchar d)87 constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
88 {
89     return (a << 24) | (b << 16) | (c << 8) | d;
90 }
91 
92 enum class ColorSpaceType : quint32 {
93     Rgb       = IccTag('R', 'G', 'B', ' '),
94     Gray      = IccTag('G', 'R', 'A', 'Y'),
95 };
96 
97 enum class ProfileClass : quint32 {
98     Input       = IccTag('s', 'c', 'r', 'n'),
99     Display     = IccTag('m', 'n', 't', 'r'),
100     // Not supported:
101     Output      = IccTag('p', 'r', 't', 'r'),
102     ColorSpace  = IccTag('s', 'p', 'a', 'c'),
103 };
104 
105 enum class Tag : quint32 {
106     acsp = IccTag('a', 'c', 's', 'p'),
107     RGB_ = IccTag('R', 'G', 'B', ' '),
108     XYZ_ = IccTag('X', 'Y', 'Z', ' '),
109     rXYZ = IccTag('r', 'X', 'Y', 'Z'),
110     gXYZ = IccTag('g', 'X', 'Y', 'Z'),
111     bXYZ = IccTag('b', 'X', 'Y', 'Z'),
112     rTRC = IccTag('r', 'T', 'R', 'C'),
113     gTRC = IccTag('g', 'T', 'R', 'C'),
114     bTRC = IccTag('b', 'T', 'R', 'C'),
115     kTRC = IccTag('k', 'T', 'R', 'C'),
116     A2B0 = IccTag('A', '2', 'B', '0'),
117     A2B1 = IccTag('A', '2', 'B', '1'),
118     B2A0 = IccTag('B', '2', 'A', '0'),
119     B2A1 = IccTag('B', '2', 'A', '1'),
120     desc = IccTag('d', 'e', 's', 'c'),
121     text = IccTag('t', 'e', 'x', 't'),
122     cprt = IccTag('c', 'p', 'r', 't'),
123     curv = IccTag('c', 'u', 'r', 'v'),
124     para = IccTag('p', 'a', 'r', 'a'),
125     wtpt = IccTag('w', 't', 'p', 't'),
126     bkpt = IccTag('b', 'k', 'p', 't'),
127     mft1 = IccTag('m', 'f', 't', '1'),
128     mft2 = IccTag('m', 'f', 't', '2'),
129     mluc = IccTag('m', 'l', 'u', 'c'),
130     mAB_ = IccTag('m', 'A', 'B', ' '),
131     mBA_ = IccTag('m', 'B', 'A', ' '),
132     chad = IccTag('c', 'h', 'a', 'd'),
133     sf32 = IccTag('s', 'f', '3', '2'),
134 
135     // Apple extensions for ICCv2:
136     aarg = IccTag('a', 'a', 'r', 'g'),
137     aagg = IccTag('a', 'a', 'g', 'g'),
138     aabg = IccTag('a', 'a', 'b', 'g'),
139 };
140 
qHash(const Tag & key,uint seed=0)141 inline uint qHash(const Tag &key, uint seed = 0)
142 {
143     return qHash(quint32(key), seed);
144 }
145 
146 namespace QIcc {
147 
148 struct TagTableEntry
149 {
150     quint32_be signature;
151     quint32_be offset;
152     quint32_be size;
153 };
154 
155 struct GenericTagData {
156     quint32_be type;
157     quint32_be null;
158 };
159 
160 struct XYZTagData : GenericTagData {
161     qint32_be fixedX;
162     qint32_be fixedY;
163     qint32_be fixedZ;
164 };
165 
166 struct CurvTagData : GenericTagData {
167     quint32_be valueCount;
168     quint16_be value[1];
169 };
170 
171 struct ParaTagData : GenericTagData {
172     quint16_be curveType;
173     quint16_be null2;
174     quint32_be parameter[1];
175 };
176 
177 struct DescTagData : GenericTagData {
178     quint32_be asciiDescriptionLength;
179     char asciiDescription[1];
180     // .. we ignore the rest
181 };
182 
183 struct MlucTagRecord {
184     quint16_be languageCode;
185     quint16_be countryCode;
186     quint32_be size;
187     quint32_be offset;
188 };
189 
190 struct MlucTagData : GenericTagData {
191     quint32_be recordCount;
192     quint32_be recordSize; // = sizeof(MlucTagRecord)
193     MlucTagRecord records[1];
194 };
195 
196 // For both mAB and mBA
197 struct mABTagData : GenericTagData {
198     quint8 inputChannels;
199     quint8 outputChannels;
200     quint8 padding[2];
201     quint32_be bCurvesOffset;
202     quint32_be matrixOffset;
203     quint32_be mCurvesOffset;
204     quint32_be clutOffset;
205     quint32_be aCurvesOffset;
206 };
207 
208 struct Sf32TagData : GenericTagData {
209     quint32_be value[1];
210 };
211 
toFixedS1516(float x)212 static int toFixedS1516(float x)
213 {
214     return int(x * 65536.0f + 0.5f);
215 }
216 
fromFixedS1516(int x)217 static float fromFixedS1516(int x)
218 {
219     return x * (1.0f / 65536.0f);
220 }
221 
isValidIccProfile(const ICCProfileHeader & header)222 static bool isValidIccProfile(const ICCProfileHeader &header)
223 {
224     if (header.signature != uint(Tag::acsp)) {
225         qCWarning(lcIcc, "Failed ICC signature test");
226         return false;
227     }
228 
229     // Don't overflow 32bit integers:
230     if (header.tagCount >= (INT32_MAX - sizeof(ICCProfileHeader)) / sizeof(TagTableEntry)) {
231         qCWarning(lcIcc, "Failed tag count sanity");
232         return false;
233     }
234     if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) {
235         qCWarning(lcIcc, "Failed basic size sanity");
236         return false;
237     }
238 
239     if (header.profileClass != uint(ProfileClass::Input)
240         && header.profileClass != uint(ProfileClass::Display)) {
241         qCWarning(lcIcc, "Unsupported ICC profile class %x", quint32(header.profileClass));
242         return false;
243     }
244     if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
245         && header.inputColorSpace != uint(ColorSpaceType::Gray)) {
246         qCWarning(lcIcc, "Unsupported ICC input color space %x", quint32(header.inputColorSpace));
247         return false;
248     }
249     if (header.pcs != 0x58595a20 /* 'XYZ '*/) {
250         // ### support PCSLAB
251         qCWarning(lcIcc, "Unsupported ICC profile connection space %x", quint32(header.pcs));
252         return false;
253     }
254 
255     QColorVector illuminant;
256     illuminant.x = fromFixedS1516(header.illuminantXyz[0]);
257     illuminant.y = fromFixedS1516(header.illuminantXyz[1]);
258     illuminant.z = fromFixedS1516(header.illuminantXyz[2]);
259     if (illuminant != QColorVector::D50()) {
260         qCWarning(lcIcc, "Invalid ICC illuminant");
261         return false;
262     }
263 
264     return true;
265 }
266 
writeColorTrc(QDataStream & stream,const QColorTrc & trc)267 static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
268 {
269     if (trc.isLinear()) {
270         stream << uint(Tag::curv) << uint(0);
271         stream << uint(0);
272         return 12;
273     }
274 
275     if (trc.m_type == QColorTrc::Type::Function) {
276         const QColorTransferFunction &fun = trc.m_fun;
277         stream << uint(Tag::para) << uint(0);
278         if (fun.isGamma()) {
279             stream << ushort(0) << ushort(0);
280             stream << toFixedS1516(fun.m_g);
281             return 12 + 4;
282         }
283         bool type3 = qFuzzyIsNull(fun.m_e) && qFuzzyIsNull(fun.m_f);
284         stream << ushort(type3 ? 3 : 4) << ushort(0);
285         stream << toFixedS1516(fun.m_g);
286         stream << toFixedS1516(fun.m_a);
287         stream << toFixedS1516(fun.m_b);
288         stream << toFixedS1516(fun.m_c);
289         stream << toFixedS1516(fun.m_d);
290         if (type3)
291             return 12 + 5 * 4;
292         stream << toFixedS1516(fun.m_e);
293         stream << toFixedS1516(fun.m_f);
294         return 12 + 7 * 4;
295     }
296 
297     Q_ASSERT(trc.m_type == QColorTrc::Type::Table);
298     stream << uint(Tag::curv) << uint(0);
299     stream << uint(trc.m_table.m_tableSize);
300     if (!trc.m_table.m_table16.isEmpty()) {
301         for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
302             stream << ushort(trc.m_table.m_table16[i]);
303         }
304     } else {
305         for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
306             stream << ushort(trc.m_table.m_table8[i] * 257U);
307         }
308     }
309     return 12 + 2 * trc.m_table.m_tableSize;
310 }
311 
toIccProfile(const QColorSpace & space)312 QByteArray toIccProfile(const QColorSpace &space)
313 {
314     if (!space.isValid())
315         return QByteArray();
316 
317     const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
318 
319     constexpr int tagCount = 9;
320     constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
321     constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
322     uint currentOffset = 0;
323     uint rTrcOffset, gTrcOffset, bTrcOffset;
324     uint rTrcSize, gTrcSize, bTrcSize;
325     uint descOffset, descSize;
326 
327     QBuffer buffer;
328     buffer.open(QIODevice::WriteOnly);
329     QDataStream stream(&buffer);
330 
331     // Profile header:
332     stream << uint(0); // Size, we will update this later
333     stream << uint(0);
334     stream << uint(0x02400000); // Version 2.4 (note we use 'para' from version 4)
335     stream << uint(ProfileClass::Display);
336     stream << uint(Tag::RGB_);
337     stream << uint(Tag::XYZ_);
338     stream << uint(0) << uint(0) << uint(0);
339     stream << uint(Tag::acsp);
340     stream << uint(0) << uint(0) << uint(0);
341     stream << uint(0) << uint(0) << uint(0);
342     stream << uint(1); // Rendering intent
343     stream << uint(0x0000f6d6); // D50 X
344     stream << uint(0x00010000); // D50 Y
345     stream << uint(0x0000d32d); // D50 Z
346     stream << IccTag('Q','t', QT_VERSION_MAJOR, QT_VERSION_MINOR);
347     stream << uint(0) << uint(0) << uint(0) << uint(0);
348     stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
349 
350     // Tag table:
351     stream << uint(tagCount);
352     stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
353     stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
354     stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
355     stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
356     stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(12);
357     // From here the offset and size will be updated later:
358     stream << uint(Tag::rTRC) << uint(0) << uint(0);
359     stream << uint(Tag::gTRC) << uint(0) << uint(0);
360     stream << uint(Tag::bTRC) << uint(0) << uint(0);
361     stream << uint(Tag::desc) << uint(0) << uint(0);
362     // TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
363     currentOffset = profileDataOffset;
364 
365     // Tag data:
366     stream << uint(Tag::XYZ_) << uint(0);
367     stream << toFixedS1516(spaceDPtr->toXyz.r.x);
368     stream << toFixedS1516(spaceDPtr->toXyz.r.y);
369     stream << toFixedS1516(spaceDPtr->toXyz.r.z);
370     stream << uint(Tag::XYZ_) << uint(0);
371     stream << toFixedS1516(spaceDPtr->toXyz.g.x);
372     stream << toFixedS1516(spaceDPtr->toXyz.g.y);
373     stream << toFixedS1516(spaceDPtr->toXyz.g.z);
374     stream << uint(Tag::XYZ_) << uint(0);
375     stream << toFixedS1516(spaceDPtr->toXyz.b.x);
376     stream << toFixedS1516(spaceDPtr->toXyz.b.y);
377     stream << toFixedS1516(spaceDPtr->toXyz.b.z);
378     stream << uint(Tag::XYZ_) << uint(0);
379     stream << toFixedS1516(spaceDPtr->whitePoint.x);
380     stream << toFixedS1516(spaceDPtr->whitePoint.y);
381     stream << toFixedS1516(spaceDPtr->whitePoint.z);
382     stream << uint(Tag::text) << uint(0);
383     stream << uint(IccTag('N', '/', 'A', '\0'));
384     currentOffset += 92;
385 
386     // From now on the data is variable sized:
387     rTrcOffset = currentOffset;
388     rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
389     currentOffset += rTrcSize;
390     if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
391         gTrcOffset = rTrcOffset;
392         gTrcSize = rTrcSize;
393     } else {
394         gTrcOffset = currentOffset;
395         gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
396         currentOffset += gTrcSize;
397     }
398     if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
399         bTrcOffset = rTrcOffset;
400         bTrcSize = rTrcSize;
401     } else {
402         bTrcOffset = currentOffset;
403         bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
404         currentOffset += bTrcSize;
405     }
406 
407     descOffset = currentOffset;
408     QByteArray description = spaceDPtr->description.toUtf8();
409     stream << uint(Tag::desc) << uint(0);
410     stream << uint(description.size() + 1);
411     stream.writeRawData(description.constData(), description.size() + 1);
412     stream << uint(0) << uint(0);
413     stream << ushort(0) << uchar(0);
414     QByteArray macdesc(67, '\0');
415     stream.writeRawData(macdesc.constData(), 67);
416     descSize = 90 + description.size() + 1;
417     currentOffset += descSize;
418 
419     buffer.close();
420     QByteArray iccProfile = buffer.buffer();
421     // Now write final size
422     *(quint32_be *)iccProfile.data() = iccProfile.size();
423     // And the final indices and sizes of variable size tags:
424     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
425     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
426     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
427     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
428     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
429     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
430     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
431     *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
432 
433 #if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
434     const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
435     Q_ASSERT(qsizetype(iccHeader->profileSize) == qsizetype(iccProfile.size()));
436     Q_ASSERT(isValidIccProfile(*iccHeader));
437 #endif
438 
439     return iccProfile;
440 }
441 
442 struct TagEntry {
443     quint32 offset;
444     quint32 size;
445 };
446 
parseXyzData(const QByteArray & data,const TagEntry & tagEntry,QColorVector & colorVector)447 bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
448 {
449     if (tagEntry.size < sizeof(XYZTagData)) {
450         qCWarning(lcIcc) << "Undersized XYZ tag";
451         return false;
452     }
453     const XYZTagData xyz = qFromUnaligned<XYZTagData>(data.constData() + tagEntry.offset);
454     if (xyz.type != quint32(Tag::XYZ_)) {
455         qCWarning(lcIcc) << "Bad XYZ content type";
456         return false;
457     }
458     const float x = fromFixedS1516(xyz.fixedX);
459     const float y = fromFixedS1516(xyz.fixedY);
460     const float z = fromFixedS1516(xyz.fixedZ);
461 
462     colorVector = QColorVector(x, y, z);
463     return true;
464 }
465 
parseTRC(const QByteArray & data,const TagEntry & tagEntry,QColorTrc & gamma)466 bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma)
467 {
468     const GenericTagData trcData = qFromUnaligned<GenericTagData>(data.constData()
469                                                                   + tagEntry.offset);
470     if (trcData.type == quint32(Tag::curv)) {
471         const CurvTagData curv = qFromUnaligned<CurvTagData>(data.constData() + tagEntry.offset);
472         if (curv.valueCount > (1 << 16))
473             return false;
474         if (tagEntry.size - 12 < 2 * curv.valueCount)
475             return false;
476         if (curv.valueCount == 0) {
477             gamma.m_type = QColorTrc::Type::Function;
478             gamma.m_fun = QColorTransferFunction(); // Linear
479         } else if (curv.valueCount == 1) {
480             float g = curv.value[0] * (1.0f / 256.0f);
481             gamma.m_type = QColorTrc::Type::Function;
482             gamma.m_fun = QColorTransferFunction::fromGamma(g);
483         } else {
484             QVector<quint16> tabl;
485             tabl.resize(curv.valueCount);
486             static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
487                           "GenericTagData has padding. The following code is a subject to UB.");
488             const auto offset = tagEntry.offset + sizeof(GenericTagData) + sizeof(quint32_be);
489             qFromBigEndian<quint16>(data.constData() + offset, curv.valueCount, tabl.data());
490             QColorTransferTable table = QColorTransferTable(curv.valueCount, std::move(tabl));
491             QColorTransferFunction curve;
492             if (!table.checkValidity()) {
493                 qCWarning(lcIcc) << "Invalid curv table";
494                 return false;
495             } else if (!table.asColorTransferFunction(&curve)) {
496                 gamma.m_type = QColorTrc::Type::Table;
497                 gamma.m_table = table;
498             } else {
499                 qCDebug(lcIcc) << "Detected curv table as function";
500                 gamma.m_type = QColorTrc::Type::Function;
501                 gamma.m_fun = curve;
502             }
503         }
504         return true;
505     }
506     if (trcData.type == quint32(Tag::para)) {
507         if (tagEntry.size < sizeof(ParaTagData))
508             return false;
509         static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
510                       "GenericTagData has padding. The following code is a subject to UB.");
511         const ParaTagData para = qFromUnaligned<ParaTagData>(data.constData() + tagEntry.offset);
512         // re-read first parameter for consistency:
513         const auto parametersOffset = tagEntry.offset + sizeof(GenericTagData)
514                                       + 2 * sizeof(quint16_be);
515         switch (para.curveType) {
516         case 0: {
517             float g = fromFixedS1516(para.parameter[0]);
518             gamma.m_type = QColorTrc::Type::Function;
519             gamma.m_fun = QColorTransferFunction::fromGamma(g);
520             break;
521         }
522         case 1: {
523             if (tagEntry.size < sizeof(ParaTagData) + 2 * 4)
524                 return false;
525             std::array<quint32_be, 3> parameters =
526                 qFromUnaligned<decltype(parameters)>(data.constData() + parametersOffset);
527             float g = fromFixedS1516(parameters[0]);
528             float a = fromFixedS1516(parameters[1]);
529             float b = fromFixedS1516(parameters[2]);
530             float d = -b / a;
531             gamma.m_type = QColorTrc::Type::Function;
532             gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
533             break;
534         }
535         case 2: {
536             if (tagEntry.size < sizeof(ParaTagData) + 3 * 4)
537                 return false;
538             std::array<quint32_be, 4> parameters =
539                 qFromUnaligned<decltype(parameters)>(data.constData() + parametersOffset);
540             float g = fromFixedS1516(parameters[0]);
541             float a = fromFixedS1516(parameters[1]);
542             float b = fromFixedS1516(parameters[2]);
543             float c = fromFixedS1516(parameters[3]);
544             float d = -b / a;
545             gamma.m_type = QColorTrc::Type::Function;
546             gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
547             break;
548         }
549         case 3: {
550             if (tagEntry.size < sizeof(ParaTagData) + 4 * 4)
551                 return false;
552             std::array<quint32_be, 5> parameters =
553                 qFromUnaligned<decltype(parameters)>(data.constData() + parametersOffset);
554             float g = fromFixedS1516(parameters[0]);
555             float a = fromFixedS1516(parameters[1]);
556             float b = fromFixedS1516(parameters[2]);
557             float c = fromFixedS1516(parameters[3]);
558             float d = fromFixedS1516(parameters[4]);
559             gamma.m_type = QColorTrc::Type::Function;
560             gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
561             break;
562         }
563         case 4: {
564             if (tagEntry.size < sizeof(ParaTagData) + 6 * 4)
565                 return false;
566             std::array<quint32_be, 7> parameters =
567                 qFromUnaligned<decltype(parameters)>(data.constData() + parametersOffset);
568             float g = fromFixedS1516(parameters[0]);
569             float a = fromFixedS1516(parameters[1]);
570             float b = fromFixedS1516(parameters[2]);
571             float c = fromFixedS1516(parameters[3]);
572             float d = fromFixedS1516(parameters[4]);
573             float e = fromFixedS1516(parameters[5]);
574             float f = fromFixedS1516(parameters[6]);
575             gamma.m_type = QColorTrc::Type::Function;
576             gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
577             break;
578         }
579         default:
580             qCWarning(lcIcc)  << "Unknown para type" << uint(para.curveType);
581             return false;
582         }
583         return true;
584     }
585     qCWarning(lcIcc) << "Invalid TRC data type";
586     return false;
587 }
588 
parseDesc(const QByteArray & data,const TagEntry & tagEntry,QString & descName)589 bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
590 {
591     const GenericTagData tag = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
592 
593     // Either 'desc' (ICCv2) or 'mluc' (ICCv4)
594     if (tag.type == quint32(Tag::desc)) {
595         if (tagEntry.size < sizeof(DescTagData))
596             return false;
597         const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
598         const quint32 len = desc.asciiDescriptionLength;
599         if (len < 1)
600             return false;
601         if (tagEntry.size - 12 < len)
602             return false;
603         static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
604                       "GenericTagData has padding. The following code is a subject to UB.");
605         const char *asciiDescription = data.constData() + tagEntry.offset + sizeof(GenericTagData)
606                                        + sizeof(quint32_be);
607         if (asciiDescription[len - 1] != '\0')
608             return false;
609         descName = QString::fromLatin1(asciiDescription, len - 1);
610         return true;
611     }
612     if (tag.type != quint32(Tag::mluc))
613         return false;
614 
615     if (tagEntry.size < sizeof(MlucTagData))
616         return false;
617     const MlucTagData mluc = qFromUnaligned<MlucTagData>(data.constData() + tagEntry.offset);
618     if (mluc.recordCount < 1)
619         return false;
620     if (mluc.recordSize < 12)
621         return false;
622     // We just use the primary record regardless of language or country.
623     const quint32 stringOffset = mluc.records[0].offset;
624     const quint32 stringSize = mluc.records[0].size;
625     if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
626         return false;
627     if ((stringSize | stringOffset) & 1)
628         return false;
629     quint32 stringLen = stringSize / 2;
630     QVarLengthArray<ushort> utf16hostendian(stringLen);
631     qFromBigEndian<ushort>(data.constData() + tagEntry.offset + stringOffset, stringLen,
632                              utf16hostendian.data());
633     // The given length shouldn't include 0-termination, but might.
634     if (stringLen > 1 && utf16hostendian[stringLen - 1] == 0)
635         --stringLen;
636     descName = QString::fromUtf16(utf16hostendian.data(), stringLen);
637     return true;
638 }
639 
fromIccProfile(const QByteArray & data,QColorSpace * colorSpace)640 bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
641 {
642     if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
643         qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
644         return false;
645     }
646     const ICCProfileHeader header = qFromUnaligned<ICCProfileHeader>(data.constData());
647     if (!isValidIccProfile(header))
648         return false; // if failed we already printing a warning
649     if (qsizetype(header.profileSize) > data.size()) {
650         qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
651         return false;
652     }
653 
654     const qsizetype offsetToData = sizeof(ICCProfileHeader) + header.tagCount * sizeof(TagTableEntry);
655     Q_ASSERT(offsetToData > 0);
656     if (offsetToData > data.size()) {
657         qCWarning(lcIcc) << "fromIccProfile: failed index size sanity";
658         return false;
659     }
660 
661     QHash<Tag, TagEntry> tagIndex;
662     for (uint i = 0; i < header.tagCount; ++i) {
663         // Read tag index
664         const qsizetype tableOffset = sizeof(ICCProfileHeader) + i * sizeof(TagTableEntry);
665         const TagTableEntry tagTable = qFromUnaligned<TagTableEntry>(data.constData()
666                                                                      + tableOffset);
667 
668         // Sanity check tag sizes and offsets:
669         if (qsizetype(tagTable.offset) < offsetToData) {
670             qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
671             return false;
672         }
673         // Checked separately from (+ size) to handle overflow.
674         if (tagTable.offset > header.profileSize) {
675             qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
676             return false;
677         }
678         if (tagTable.size < 12) {
679             qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
680             return false;
681         }
682         if (tagTable.size > header.profileSize - tagTable.offset) {
683             qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
684             return false;
685         }
686         if (tagTable.offset & 0x03) {
687             qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment";
688             return false;
689         }
690 //        printf("'%4s' %d %d\n", (const char *)&tagTable.signature,
691 //                                quint32(tagTable.offset),
692 //                                quint32(tagTable.size));
693         tagIndex.insert(Tag(quint32(tagTable.signature)), { tagTable.offset, tagTable.size });
694     }
695 
696     // Check the profile is three-component matrix based (what we currently support):
697     if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
698         if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
699             !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
700             !tagIndex.contains(Tag::wtpt)) {
701             qCWarning(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based";
702             return false;
703         }
704     } else {
705         Q_ASSERT(header.inputColorSpace == uint(ColorSpaceType::Gray));
706         if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
707             qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
708             return false;
709         }
710     }
711 
712     QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::getWritable(*colorSpace);
713 
714     if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
715         // Parse XYZ tags
716         if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
717             return false;
718         if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
719             return false;
720         if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
721             return false;
722         if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
723             return false;
724 
725         colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
726         if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
727             qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
728             colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
729         } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
730             qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
731             colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
732         } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
733             qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
734             colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
735         }
736         if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
737             qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
738             colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
739         }
740     } else {
741         // We will use sRGB primaries and fit to match the given white-point if
742         // it doesn't match sRGB's.
743         QColorVector whitePoint;
744         if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
745             return false;
746         if (!qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z - whitePoint.x) == 0.0f) {
747             qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
748             return false;
749         }
750         if (whitePoint == QColorVector::D65()) {
751             colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
752         } else {
753             colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
754             // Calculate chromaticity from xyz (assuming y == 1.0f).
755             float y = 1.0f / (1.0f + whitePoint.z - whitePoint.x);
756             float x = whitePoint.x * y;
757             QColorSpacePrimaries primaries(QColorSpace::Primaries::SRgb);
758             primaries.whitePoint = QPointF(x,y);
759             if (!primaries.areValid()) {
760                 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - invalid white-point";
761                 return false;
762             }
763             colorspaceDPtr->toXyz = primaries.toXyzMatrix();
764         }
765     }
766     // Reset the matrix to our canonical values:
767     if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
768         colorspaceDPtr->setToXyzMatrix();
769 
770     // Parse TRC tags
771     TagEntry rTrc;
772     TagEntry gTrc;
773     TagEntry bTrc;
774     if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
775         rTrc = tagIndex[Tag::kTRC];
776         gTrc = tagIndex[Tag::kTRC];
777         bTrc = tagIndex[Tag::kTRC];
778     } else if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
779         // Apple extension for parametric version of TRCs in ICCv2:
780         rTrc = tagIndex[Tag::aarg];
781         gTrc = tagIndex[Tag::aagg];
782         bTrc = tagIndex[Tag::aabg];
783     } else {
784         rTrc = tagIndex[Tag::rTRC];
785         gTrc = tagIndex[Tag::gTRC];
786         bTrc = tagIndex[Tag::bTRC];
787     }
788 
789     QColorTrc rCurve;
790     QColorTrc gCurve;
791     QColorTrc bCurve;
792     if (!parseTRC(data, rTrc, rCurve)) {
793         qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
794         return false;
795     }
796     if (!parseTRC(data, gTrc, gCurve)) {
797         qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
798         return false;
799     }
800     if (!parseTRC(data, bTrc, bCurve)) {
801         qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
802         return false;
803     }
804     if (rCurve == gCurve && gCurve == bCurve && rCurve.m_type == QColorTrc::Type::Function) {
805         if (rCurve.m_fun.isLinear()) {
806             qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
807             colorspaceDPtr->trc[0] = QColorTransferFunction();
808             colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
809             colorspaceDPtr->gamma = 1.0f;
810         } else if (rCurve.m_fun.isGamma()) {
811             qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
812             colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
813             colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
814             colorspaceDPtr->gamma = rCurve.m_fun.m_g;
815         } else if (rCurve.m_fun.isSRgb()) {
816             qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
817             colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
818             colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
819         } else {
820             colorspaceDPtr->trc[0] = rCurve;
821             colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
822         }
823 
824         colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
825         colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
826     } else {
827         colorspaceDPtr->trc[0] = rCurve;
828         colorspaceDPtr->trc[1] = gCurve;
829         colorspaceDPtr->trc[2] = bCurve;
830         colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
831     }
832 
833     if (tagIndex.contains(Tag::desc)) {
834         if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description))
835             qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
836         else
837             qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
838     }
839 
840     colorspaceDPtr->identifyColorSpace();
841     if (colorspaceDPtr->namedColorSpace)
842         qCDebug(lcIcc) << "fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace);
843 
844     colorspaceDPtr->iccProfile = data;
845 
846     return true;
847 }
848 
849 } // namespace QIcc
850 
851 QT_END_NAMESPACE
852