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