1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include "qicc_p.h"
6
7#include <qbuffer.h>
8#include <qbytearray.h>
9#include <qvarlengtharray.h>
10#include <qhash.h>
11#include <qdatastream.h>
12#include <qendian.h>
13#include <qloggingcategory.h>
14#include <qstring.h>
15
16#include "qcolorclut_p.h"
17#include "qcolormatrix_p.h"
18#include "qcolorspace_p.h"
19#include "qcolortrc_p.h"
20
21#include <array>
22
23QT_BEGIN_NAMESPACE
24Q_STATIC_LOGGING_CATEGORY(lcIcc, "qt.gui.icc", QtWarningMsg)
25
26namespace QIcc {
27
28struct ICCProfileHeader
29{
30 quint32_be profileSize;
31
32 quint32_be preferredCmmType;
33
34 quint32_be profileVersion;
35 quint32_be profileClass;
36 quint32_be inputColorSpace;
37 quint32_be pcs;
38 quint32_be datetime[3];
39 quint32_be signature;
40 quint32_be platformSignature;
41 quint32_be flags;
42 quint32_be deviceManufacturer;
43 quint32_be deviceModel;
44 quint32_be deviceAttributes[2];
45
46 quint32_be renderingIntent;
47 qint32_be illuminantXyz[3];
48
49 quint32_be creatorSignature;
50 quint32_be profileId[4];
51
52 quint32_be reserved[7];
53
54// Technically after the header, but easier to include here:
55 quint32_be tagCount;
56};
57
58constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
59{
60 return (a << 24) | (b << 16) | (c << 8) | d;
61}
62
63enum class ColorSpaceType : quint32 {
64 Rgb = IccTag(a: 'R', b: 'G', c: 'B', d: ' '),
65 Gray = IccTag(a: 'G', b: 'R', c: 'A', d: 'Y'),
66 Cmyk = IccTag(a: 'C', b: 'M', c: 'Y', d: 'K'),
67};
68
69enum class ProfileClass : quint32 {
70 Input = IccTag(a: 's', b: 'c', c: 'n', d: 'r'),
71 Display = IccTag(a: 'm', b: 'n', c: 't', d: 'r'),
72 Output = IccTag(a: 'p', b: 'r', c: 't', d: 'r'),
73 ColorSpace = IccTag(a: 's', b: 'p', c: 'a', d: 'c'),
74 // Not supported:
75 DeviceLink = IccTag(a: 'l', b: 'i', c: 'n', d: 'k'),
76 Abstract = IccTag(a: 'a', b: 'b', c: 's', d: 't'),
77 NamedColor = IccTag(a: 'n', b: 'm', c: 'c', d: 'l'),
78};
79
80enum class Tag : quint32 {
81 acsp = IccTag(a: 'a', b: 'c', c: 's', d: 'p'),
82 Lab_ = IccTag(a: 'L', b: 'a', c: 'b', d: ' '),
83 RGB_ = IccTag(a: 'R', b: 'G', c: 'B', d: ' '),
84 XYZ_ = IccTag(a: 'X', b: 'Y', c: 'Z', d: ' '),
85 rXYZ = IccTag(a: 'r', b: 'X', c: 'Y', d: 'Z'),
86 gXYZ = IccTag(a: 'g', b: 'X', c: 'Y', d: 'Z'),
87 bXYZ = IccTag(a: 'b', b: 'X', c: 'Y', d: 'Z'),
88 rTRC = IccTag(a: 'r', b: 'T', c: 'R', d: 'C'),
89 gTRC = IccTag(a: 'g', b: 'T', c: 'R', d: 'C'),
90 bTRC = IccTag(a: 'b', b: 'T', c: 'R', d: 'C'),
91 kTRC = IccTag(a: 'k', b: 'T', c: 'R', d: 'C'),
92 A2B0 = IccTag(a: 'A', b: '2', c: 'B', d: '0'),
93 A2B1 = IccTag(a: 'A', b: '2', c: 'B', d: '1'),
94 A2B2 = IccTag(a: 'A', b: '2', c: 'B', d: '2'),
95 B2A0 = IccTag(a: 'B', b: '2', c: 'A', d: '0'),
96 B2A1 = IccTag(a: 'B', b: '2', c: 'A', d: '1'),
97 B2A2 = IccTag(a: 'B', b: '2', c: 'A', d: '2'),
98 B2D0 = IccTag(a: 'B', b: '2', c: 'D', d: '0'),
99 B2D1 = IccTag(a: 'B', b: '2', c: 'D', d: '1'),
100 B2D2 = IccTag(a: 'B', b: '2', c: 'D', d: '2'),
101 B2D3 = IccTag(a: 'B', b: '2', c: 'D', d: '3'),
102 D2B0 = IccTag(a: 'D', b: '2', c: 'B', d: '0'),
103 D2B1 = IccTag(a: 'D', b: '2', c: 'B', d: '1'),
104 D2B2 = IccTag(a: 'D', b: '2', c: 'B', d: '2'),
105 D2B3 = IccTag(a: 'D', b: '2', c: 'B', d: '3'),
106 desc = IccTag(a: 'd', b: 'e', c: 's', d: 'c'),
107 text = IccTag(a: 't', b: 'e', c: 'x', d: 't'),
108 cprt = IccTag(a: 'c', b: 'p', c: 'r', d: 't'),
109 curv = IccTag(a: 'c', b: 'u', c: 'r', d: 'v'),
110 para = IccTag(a: 'p', b: 'a', c: 'r', d: 'a'),
111 wtpt = IccTag(a: 'w', b: 't', c: 'p', d: 't'),
112 bkpt = IccTag(a: 'b', b: 'k', c: 'p', d: 't'),
113 mft1 = IccTag(a: 'm', b: 'f', c: 't', d: '1'),
114 mft2 = IccTag(a: 'm', b: 'f', c: 't', d: '2'),
115 mluc = IccTag(a: 'm', b: 'l', c: 'u', d: 'c'),
116 mpet = IccTag(a: 'm', b: 'p', c: 'e', d: 't'),
117 mAB_ = IccTag(a: 'm', b: 'A', c: 'B', d: ' '),
118 mBA_ = IccTag(a: 'm', b: 'B', c: 'A', d: ' '),
119 chad = IccTag(a: 'c', b: 'h', c: 'a', d: 'd'),
120 cicp = IccTag(a: 'c', b: 'i', c: 'c', d: 'p'),
121 gamt = IccTag(a: 'g', b: 'a', c: 'm', d: 't'),
122 sf32 = IccTag(a: 's', b: 'f', c: '3', d: '2'),
123
124 // Apple extensions for ICCv2:
125 aarg = IccTag(a: 'a', b: 'a', c: 'r', d: 'g'),
126 aagg = IccTag(a: 'a', b: 'a', c: 'g', d: 'g'),
127 aabg = IccTag(a: 'a', b: 'a', c: 'b', d: 'g'),
128};
129
130} // namespace QIcc
131
132namespace QIcc {
133
134struct TagTableEntry
135{
136 quint32_be signature;
137 quint32_be offset;
138 quint32_be size;
139};
140
141struct GenericTagData {
142 quint32_be type;
143 quint32_be null;
144};
145
146struct XYZTagData : GenericTagData {
147 qint32_be fixedX;
148 qint32_be fixedY;
149 qint32_be fixedZ;
150};
151
152struct CurvTagData : GenericTagData {
153 quint32_be valueCount;
154 // followed by curv values: quint16_be[]
155};
156
157struct ParaTagData : GenericTagData {
158 quint16_be curveType;
159 quint16_be null2;
160 // followed by parameter values: quint32_be[1-7];
161};
162
163struct DescTagData : GenericTagData {
164 quint32_be asciiDescriptionLength;
165 // followed by ascii description: char[]
166 // .. we ignore the rest
167};
168
169struct MlucTagRecord {
170 quint16_be languageCode;
171 quint16_be countryCode;
172 quint32_be size;
173 quint32_be offset;
174};
175
176struct MlucTagData : GenericTagData {
177 quint32_be recordCount;
178 quint32_be recordSize; // = sizeof(MlucTagRecord)
179 MlucTagRecord records[1];
180};
181
182struct Lut8TagData : GenericTagData {
183 quint8 inputChannels;
184 quint8 outputChannels;
185 quint8 clutGridPoints;
186 quint8 padding;
187 qint32_be e1;
188 qint32_be e2;
189 qint32_be e3;
190 qint32_be e4;
191 qint32_be e5;
192 qint32_be e6;
193 qint32_be e7;
194 qint32_be e8;
195 qint32_be e9;
196 // followed by parameter values: quint8[inputChannels * 256];
197 // followed by parameter values: quint8[outputChannels * clutGridPoints^inputChannels];
198 // followed by parameter values: quint8[outputChannels * 256];
199};
200
201struct Lut16TagData : GenericTagData {
202 quint8 inputChannels;
203 quint8 outputChannels;
204 quint8 clutGridPoints;
205 quint8 padding;
206 qint32_be e1;
207 qint32_be e2;
208 qint32_be e3;
209 qint32_be e4;
210 qint32_be e5;
211 qint32_be e6;
212 qint32_be e7;
213 qint32_be e8;
214 qint32_be e9;
215 quint16_be inputTableEntries;
216 quint16_be outputTableEntries;
217 // followed by parameter values: quint16_be[inputChannels * inputTableEntries];
218 // followed by parameter values: quint16_be[outputChannels * clutGridPoints^inputChannels];
219 // followed by parameter values: quint16_be[outputChannels * outputTableEntries];
220};
221
222// For both mAB and mBA
223struct mABTagData : GenericTagData {
224 quint8 inputChannels;
225 quint8 outputChannels;
226 quint8 padding[2];
227 quint32_be bCurvesOffset;
228 quint32_be matrixOffset;
229 quint32_be mCurvesOffset;
230 quint32_be clutOffset;
231 quint32_be aCurvesOffset;
232 // followed by embedded data for the offsets above
233};
234
235struct mpetTagData : GenericTagData {
236 quint16_be inputChannels;
237 quint16_be outputChannels;
238 quint32_be processingElements;
239 // element offset table
240 // element data
241};
242
243struct Sf32TagData : GenericTagData {
244 quint32_be value[9];
245};
246
247struct CicpTagData : GenericTagData {
248 quint8 colorPrimaries;
249 quint8 transferCharacteristics;
250 quint8 matrixCoefficients;
251 quint8 videoFullRangeFlag;
252};
253
254struct MatrixElement {
255 qint32_be e0;
256 qint32_be e1;
257 qint32_be e2;
258 qint32_be e3;
259 qint32_be e4;
260 qint32_be e5;
261 qint32_be e6;
262 qint32_be e7;
263 qint32_be e8;
264 qint32_be e9;
265 qint32_be e10;
266 qint32_be e11;
267};
268
269static int toFixedS1516(float x)
270{
271 if (x < float(SHRT_MIN))
272 return INT_MIN;
273 if (x > float(SHRT_MAX))
274 return INT_MAX;
275 return qRound(f: x * 65536.0f);
276}
277
278static float fromFixedS1516(int x)
279{
280 return x * (1.0f / 65536.0f);
281}
282
283static bool isValidIccProfile(const ICCProfileHeader &header)
284{
285 if (header.signature != uint(Tag::acsp)) {
286 qCWarning(lcIcc, "Failed ICC signature test");
287 return false;
288 }
289
290 // Don't overflow 32bit integers:
291 if (header.tagCount >= (INT32_MAX - sizeof(ICCProfileHeader)) / sizeof(TagTableEntry)) {
292 qCWarning(lcIcc, "Failed tag count sanity");
293 return false;
294 }
295 if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) {
296 qCWarning(lcIcc, "Failed basic size sanity");
297 return false;
298 }
299
300 if (header.profileClass != uint(ProfileClass::Input)
301 && header.profileClass != uint(ProfileClass::Display)
302 && header.profileClass != uint(ProfileClass::Output)
303 && header.profileClass != uint(ProfileClass::ColorSpace)) {
304 qCInfo(lcIcc, "Unsupported ICC profile class 0x%x", quint32(header.profileClass));
305 return false;
306 }
307 if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
308 && header.inputColorSpace != uint(ColorSpaceType::Gray)
309 && header.inputColorSpace != uint(ColorSpaceType::Cmyk)) {
310 qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
311 return false;
312 }
313 if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) {
314 qCInfo(lcIcc, "Invalid ICC profile connection space 0x%x", quint32(header.pcs));
315 return false;
316 }
317
318 QColorVector illuminant;
319 illuminant.x = fromFixedS1516(x: header.illuminantXyz[0]);
320 illuminant.y = fromFixedS1516(x: header.illuminantXyz[1]);
321 illuminant.z = fromFixedS1516(x: header.illuminantXyz[2]);
322 if (illuminant != QColorVector::D50()) {
323 qCWarning(lcIcc, "Invalid ICC illuminant");
324 return false;
325 }
326
327 return true;
328}
329
330static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
331{
332 if (trc.isIdentity()) {
333 stream << uint(Tag::curv) << uint(0);
334 stream << uint(0);
335 return 12;
336 }
337
338 if (trc.m_type == QColorTrc::Type::ParameterizedFunction) {
339 const QColorTransferFunction &fun = trc.m_fun;
340 stream << uint(Tag::para) << uint(0);
341 if (fun.isGamma()) {
342 stream << ushort(0) << ushort(0);
343 stream << toFixedS1516(x: fun.m_g);
344 return 12 + 4;
345 }
346 bool type3 = qFuzzyIsNull(f: fun.m_e) && qFuzzyIsNull(f: fun.m_f);
347 stream << ushort(type3 ? 3 : 4) << ushort(0);
348 stream << toFixedS1516(x: fun.m_g);
349 stream << toFixedS1516(x: fun.m_a);
350 stream << toFixedS1516(x: fun.m_b);
351 stream << toFixedS1516(x: fun.m_c);
352 stream << toFixedS1516(x: fun.m_d);
353 if (type3)
354 return 12 + 5 * 4;
355 stream << toFixedS1516(x: fun.m_e);
356 stream << toFixedS1516(x: fun.m_f);
357 return 12 + 7 * 4;
358 }
359 if (trc.m_type != QColorTrc::Type::Table) {
360 stream << uint(Tag::curv) << uint(0);
361 stream << uint(16);
362 for (uint i = 0; i < 16; ++i) {
363 stream << ushort(qBound(min: 0, val: qRound(f: trc.apply(x: i / 15.f) * 65535.f), max: 65535));
364 }
365 return 12 + 16 * 2;
366 }
367
368 Q_ASSERT(trc.m_type == QColorTrc::Type::Table);
369 stream << uint(Tag::curv) << uint(0);
370 stream << uint(trc.m_table.m_tableSize);
371 if (!trc.m_table.m_table16.isEmpty()) {
372 for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
373 stream << ushort(trc.m_table.m_table16[i]);
374 }
375 } else {
376 for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
377 stream << ushort(trc.m_table.m_table8[i] * 257U);
378 }
379 }
380 if (trc.m_table.m_tableSize & 1) {
381 stream << ushort(0);
382 return 12 + 2 * trc.m_table.m_tableSize + 2;
383 }
384 return 12 + 2 * trc.m_table.m_tableSize;
385}
386
387// very simple version for small values (<=4) of exp.
388static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
389{
390 return (exp <= 1) ? x : x * intPow(x, exp: exp - 1);
391}
392
393struct ElementCombo
394{
395 const QColorMatrix *inMatrix = nullptr;
396 const QColorSpacePrivate::TransferElement *inTable = nullptr;
397 const QColorCLUT *clut = nullptr;
398 const QColorSpacePrivate::TransferElement *midTable = nullptr;
399 const QColorMatrix *midMatrix = nullptr;
400 const QColorVector *midOffset = nullptr;
401 const QColorSpacePrivate::TransferElement *outTable = nullptr;
402};
403
404static void visitElement(ElementCombo &combo, const QColorSpacePrivate::TransferElement &element, int number,
405 const QList<QColorSpacePrivate::Element> &list)
406{
407 if (number == 0)
408 combo.inTable = &element;
409 else if (number == list.size() - 1)
410 combo.outTable = &element;
411 else if (number == 1 && combo.inMatrix)
412 combo.inTable = &element;
413 else
414 combo.midTable = &element;
415}
416
417static void visitElement(ElementCombo &combo, const QColorMatrix &element, int number,
418 const QList<QColorSpacePrivate::Element> &)
419{
420 if (number == 0)
421 combo.inMatrix = &element;
422 else
423 combo.midMatrix = &element;
424}
425
426static void visitElement(ElementCombo &combo, const QColorVector &element, int,
427 const QList<QColorSpacePrivate::Element> &)
428{
429 combo.midOffset = &element;
430}
431
432static void visitElement(ElementCombo &combo, const QColorCLUT &element, int,
433 const QList<QColorSpacePrivate::Element> &)
434{
435 combo.clut = &element;
436}
437
438static bool isTableTrc(const QColorSpacePrivate::TransferElement *transfer)
439{
440 int i = 0;
441 while (i < 4 && transfer->trc[i].isValid()) {
442 if (transfer->trc[i].m_type != QColorTrc::Type::Table)
443 return false;
444 i++;
445 }
446 return i > 0;
447}
448
449static bool isTableTrcSingleSize(const QColorSpacePrivate::TransferElement *transfer)
450{
451 Q_ASSERT(transfer->trc[0].m_type == QColorTrc::Type::Table);
452 int i = 1;
453 const uint32_t size = transfer->trc[0].table().m_tableSize;
454 while (i < 4 && transfer->trc[i].isValid()) {
455 if (transfer->trc[i].table().m_tableSize != size)
456 return false;
457 i++;
458 }
459 return true;
460}
461
462static int writeMab(QDataStream &stream, const QList<QColorSpacePrivate::Element> &abList, bool isAb, bool pcsLab, bool isCmyk)
463{
464 int number = 0;
465 ElementCombo combo;
466 for (auto &&element : abList)
467 std::visit(visitor: [&](auto &&elm) { visitElement(combo, elm, number++, abList); }, variants: element);
468
469 Q_ASSERT(!(combo.inMatrix && combo.midMatrix));
470
471 // qWarning() << Q_FUNC_INFO << bool(combo.inMatrix) << bool(combo.inTable) << bool(combo.clut) << bool(combo.midTable) << bool(combo.midMatrix) << bool(combo.midOffset) << bool(combo.outTable);
472 bool lut16 = true;
473 if (combo.midMatrix || combo.midTable || combo.midOffset)
474 lut16 = false;
475 if (combo.clut && (combo.clut->gridPointsX != combo.clut->gridPointsY ||
476 combo.clut->gridPointsX != combo.clut->gridPointsZ ||
477 (combo.clut->gridPointsW > 1 && combo.clut->gridPointsX != combo.clut->gridPointsW)))
478 lut16 = false;
479 if (lut16 && combo.inTable)
480 lut16 = isTableTrc(transfer: combo.inTable) && isTableTrcSingleSize(transfer: combo.inTable);
481 if (lut16 && combo.outTable)
482 lut16 = isTableTrc(transfer: combo.outTable) && isTableTrcSingleSize(transfer: combo.outTable);
483
484 if (!lut16) {
485 if (combo.inMatrix)
486 qSwap(value1&: combo.inMatrix, value2&: combo.midMatrix);
487 if (isAb)
488 stream << uint(Tag::mAB_) << uint(0);
489 else
490 stream << uint(Tag::mBA_) << uint(0);
491 } else {
492 stream << uint(Tag::mft2) << uint(0);
493 }
494
495 const int inChannels = (isCmyk && isAb) ? 4 : 3;
496 const int outChannels = (isCmyk && !isAb) ? 4 : 3;
497 stream << uchar(inChannels) << uchar(outChannels);
498 qsizetype gridPointsLut16 = 0;
499 if (lut16 && combo.clut)
500 gridPointsLut16 = combo.clut->gridPointsX;
501 if (lut16)
502 stream << uchar(gridPointsLut16) << uchar(0);
503 else
504 stream << quint16(0);
505 if (lut16) {
506 if (combo.inMatrix) {
507 stream << toFixedS1516(x: combo.inMatrix->r.x);
508 stream << toFixedS1516(x: combo.inMatrix->g.x);
509 stream << toFixedS1516(x: combo.inMatrix->b.x);
510 stream << toFixedS1516(x: combo.inMatrix->r.y);
511 stream << toFixedS1516(x: combo.inMatrix->g.y);
512 stream << toFixedS1516(x: combo.inMatrix->b.y);
513 stream << toFixedS1516(x: combo.inMatrix->r.z);
514 stream << toFixedS1516(x: combo.inMatrix->g.z);
515 stream << toFixedS1516(x: combo.inMatrix->b.z);
516 } else {
517 stream << toFixedS1516(x: 1.0f);
518 stream << toFixedS1516(x: 0.0f);
519 stream << toFixedS1516(x: 0.0f);
520 stream << toFixedS1516(x: 0.0f);
521 stream << toFixedS1516(x: 1.0f);
522 stream << toFixedS1516(x: 0.0f);
523 stream << toFixedS1516(x: 0.0f);
524 stream << toFixedS1516(x: 0.0f);
525 stream << toFixedS1516(x: 1.0f);
526 }
527 int inputEntries = 0, outputEntries = 0;
528 if (combo.inTable)
529 inputEntries = combo.inTable->trc[0].table().m_tableSize;
530 else
531 inputEntries = 2;
532 if (combo.outTable)
533 outputEntries = combo.outTable->trc[0].table().m_tableSize;
534 else
535 outputEntries = 2;
536 stream << quint16(inputEntries);
537 stream << quint16(outputEntries);
538 auto writeTable = [&](const QColorSpacePrivate::TransferElement *table, int entries, int channels) {
539 if (table) {
540 for (int j = 0; j < channels; ++j) {
541 if (!table->trc[j].table().m_table16.isEmpty()) {
542 for (int i = 0; i < entries; ++i)
543 stream << table->trc[j].table().m_table16[i];
544 } else {
545 for (int i = 0; i < entries; ++i)
546 stream << quint16(table->trc[j].table().m_table8[i] * 257);
547 }
548 }
549 } else {
550 for (int j = 0; j < channels; ++j)
551 stream << quint16(0) << quint16(65535);
552 }
553 };
554
555 writeTable(combo.inTable, inputEntries, inChannels);
556
557 if (combo.clut) {
558 if (isAb && pcsLab) {
559 for (const QColorVector &v : combo.clut->table) {
560 stream << quint16(v.x * 65280.0f + 0.5f);
561 stream << quint16(v.y * 65280.0f + 0.5f);
562 stream << quint16(v.z * 65280.0f + 0.5f);
563 }
564 } else {
565 if (outChannels == 4) {
566 for (const QColorVector &v : combo.clut->table) {
567 stream << quint16(v.x * 65535.0f + 0.5f);
568 stream << quint16(v.y * 65535.0f + 0.5f);
569 stream << quint16(v.z * 65535.0f + 0.5f);
570 stream << quint16(v.w * 65535.0f + 0.5f);
571 }
572 } else {
573 for (const QColorVector &v : combo.clut->table) {
574 stream << quint16(v.x * 65535.0f + 0.5f);
575 stream << quint16(v.y * 65535.0f + 0.5f);
576 stream << quint16(v.z * 65535.0f + 0.5f);
577 }
578 }
579 }
580 }
581
582 writeTable(combo.outTable, outputEntries, outChannels);
583
584 qsizetype offset = sizeof(Lut16TagData) + 2 * inChannels * inputEntries
585 + 2 * outChannels * outputEntries
586 + 2 * outChannels * intPow(x: gridPointsLut16, exp: inChannels);
587 if (offset & 0x2) {
588 stream << quint16(0);
589 offset += 2;
590 }
591 return offset;
592 } else {
593 // mAB/mBA tag:
594 if (isAb) {
595 if (!combo.clut && combo.inTable && combo.midMatrix && !combo.midTable)
596 std::swap(a&: combo.inTable, b&: combo.midTable);
597 } else {
598 if (!combo.clut && combo.outTable && combo.midMatrix && !combo.midTable)
599 std::swap(a&: combo.outTable, b&: combo.midTable);
600 }
601 quint32 offset = sizeof(mABTagData);
602 QBuffer buffer2;
603 buffer2.open(openMode: QIODevice::WriteOnly);
604 QDataStream stream2(&buffer2);
605 quint32 bOffset = offset;
606 quint32 matrixOffset = 0;
607 quint32 mOffset = 0;
608 quint32 clutOffset = 0;
609 quint32 aOffset = 0;
610 // Tags must start on 4 byte offsets, but sampled curves might have sizes 2-byte aligned
611 auto alignTag = [&]() {
612 if (offset & 0x2) {
613 stream2 << quint16(0);
614 offset += 2;
615 }
616 };
617
618 const QColorSpacePrivate::TransferElement *aCurve, *bCurve;
619 int aChannels;
620 if (isAb) {
621 aCurve = combo.inTable;
622 aChannels = inChannels;
623 bCurve = combo.outTable;
624 Q_ASSERT(outChannels == 3);
625 } else {
626 aCurve = combo.outTable;
627 aChannels = outChannels;
628 bCurve = combo.inTable;
629 Q_ASSERT(inChannels == 3);
630 }
631 if (bCurve) {
632 offset += writeColorTrc(stream&: stream2, trc: bCurve->trc[0]);
633 alignTag();
634 offset += writeColorTrc(stream&: stream2, trc: bCurve->trc[1]);
635 alignTag();
636 offset += writeColorTrc(stream&: stream2, trc: bCurve->trc[2]);
637 alignTag();
638 } else {
639 stream2 << uint(Tag::curv) << uint(0) << uint(0);
640 stream2 << uint(Tag::curv) << uint(0) << uint(0);
641 stream2 << uint(Tag::curv) << uint(0) << uint(0);
642 offset += 12 * 3;
643 }
644 if (combo.midMatrix || combo.midOffset || combo.midTable) {
645 matrixOffset = offset;
646 if (combo.midMatrix) {
647 stream2 << toFixedS1516(x: combo.midMatrix->r.x);
648 stream2 << toFixedS1516(x: combo.midMatrix->g.x);
649 stream2 << toFixedS1516(x: combo.midMatrix->b.x);
650 stream2 << toFixedS1516(x: combo.midMatrix->r.y);
651 stream2 << toFixedS1516(x: combo.midMatrix->g.y);
652 stream2 << toFixedS1516(x: combo.midMatrix->b.y);
653 stream2 << toFixedS1516(x: combo.midMatrix->r.z);
654 stream2 << toFixedS1516(x: combo.midMatrix->g.z);
655 stream2 << toFixedS1516(x: combo.midMatrix->b.z);
656 } else {
657 stream2 << toFixedS1516(x: 1.0f);
658 stream2 << toFixedS1516(x: 0.0f);
659 stream2 << toFixedS1516(x: 0.0f);
660 stream2 << toFixedS1516(x: 0.0f);
661 stream2 << toFixedS1516(x: 1.0f);
662 stream2 << toFixedS1516(x: 0.0f);
663 stream2 << toFixedS1516(x: 0.0f);
664 stream2 << toFixedS1516(x: 0.0f);
665 stream2 << toFixedS1516(x: 1.0f);
666 }
667 if (combo.midOffset) {
668 stream2 << toFixedS1516(x: combo.midOffset->x);
669 stream2 << toFixedS1516(x: combo.midOffset->y);
670 stream2 << toFixedS1516(x: combo.midOffset->z);
671 } else {
672 stream2 << toFixedS1516(x: 0.0f);
673 stream2 << toFixedS1516(x: 0.0f);
674 stream2 << toFixedS1516(x: 0.0f);
675 }
676 offset += 12 * 4;
677 mOffset = offset;
678 if (combo.midTable) {
679 offset += writeColorTrc(stream&: stream2, trc: combo.midTable->trc[0]);
680 alignTag();
681 offset += writeColorTrc(stream&: stream2, trc: combo.midTable->trc[1]);
682 alignTag();
683 offset += writeColorTrc(stream&: stream2, trc: combo.midTable->trc[2]);
684 alignTag();
685 } else {
686 stream2 << uint(Tag::curv) << uint(0) << uint(0);
687 stream2 << uint(Tag::curv) << uint(0) << uint(0);
688 stream2 << uint(Tag::curv) << uint(0) << uint(0);
689 offset += 12 * 3;
690 }
691 }
692 if (combo.clut || aCurve) {
693 clutOffset = offset;
694 if (combo.clut) {
695 stream2 << uchar(combo.clut->gridPointsX);
696 stream2 << uchar(combo.clut->gridPointsY);
697 stream2 << uchar(combo.clut->gridPointsZ);
698 if (inChannels == 4)
699 stream2 << uchar(combo.clut->gridPointsW);
700 else
701 stream2 << uchar(0);
702 for (int i = 0; i < 12; ++i)
703 stream2 << uchar(0);
704 stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
705 offset += 20;
706 if (outChannels == 4) {
707 for (const QColorVector &v : combo.clut->table) {
708 stream2 << quint16(v.x * 65535.0f + 0.5f);
709 stream2 << quint16(v.y * 65535.0f + 0.5f);
710 stream2 << quint16(v.z * 65535.0f + 0.5f);
711 stream2 << quint16(v.w * 65535.0f + 0.5f);
712 }
713 } else {
714 for (const QColorVector &v : combo.clut->table) {
715 stream2 << quint16(v.x * 65535.0f + 0.5f);
716 stream2 << quint16(v.y * 65535.0f + 0.5f);
717 stream2 << quint16(v.z * 65535.0f + 0.5f);
718 }
719 }
720 offset += 2 * outChannels * combo.clut->table.size();
721 alignTag();
722 } else {
723 for (int i = 0; i < 16; ++i)
724 stream2 << uchar(0);
725 stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
726 offset += 20;
727 }
728 aOffset = offset;
729 if (aCurve) {
730 offset += writeColorTrc(stream&: stream2, trc: aCurve->trc[0]);
731 alignTag();
732 offset += writeColorTrc(stream&: stream2, trc: aCurve->trc[1]);
733 alignTag();
734 offset += writeColorTrc(stream&: stream2, trc: aCurve->trc[2]);
735 alignTag();
736 if (aChannels == 4) {
737 offset += writeColorTrc(stream&: stream2, trc: aCurve->trc[3]);
738 alignTag();
739 }
740 } else {
741 stream2 << uint(Tag::curv) << uint(0) << uint(0);
742 stream2 << uint(Tag::curv) << uint(0) << uint(0);
743 stream2 << uint(Tag::curv) << uint(0) << uint(0);
744 if (aChannels == 4)
745 stream2 << uint(Tag::curv) << uint(0) << uint(0);
746 offset += 12 * aChannels;
747 }
748 }
749 buffer2.close();
750 QByteArray tagData = buffer2.buffer();
751 stream << quint32(bOffset);
752 stream << quint32(matrixOffset);
753 stream << quint32(mOffset);
754 stream << quint32(clutOffset);
755 stream << quint32(aOffset);
756 stream.writeRawData(tagData.data(), len: tagData.size());
757
758 return int(sizeof(mABTagData) + tagData.size());
759 }
760}
761
762QByteArray toIccProfile(const QColorSpace &space)
763{
764 if (!space.isValid())
765 return QByteArray();
766
767 const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(colorSpace: space);
768 if (!spaceDPtr->iccProfile.isEmpty())
769 return spaceDPtr->iccProfile;
770
771 int fixedLengthTagCount = 5;
772 if (!spaceDPtr->isThreeComponentMatrix())
773 fixedLengthTagCount = 2;
774 else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
775 fixedLengthTagCount = 2;
776 bool writeChad = false;
777 bool writeB2a = true;
778 bool writeCicp = false;
779 if (spaceDPtr->isThreeComponentMatrix() && !spaceDPtr->chad.isIdentity()) {
780 writeChad = true;
781 fixedLengthTagCount++;
782 }
783 int varLengthTagCount = 4;
784 if (!spaceDPtr->isThreeComponentMatrix())
785 varLengthTagCount = 3;
786 else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
787 varLengthTagCount = 2;
788
789 if (!space.isValidTarget()) {
790 writeB2a = false;
791 Q_ASSERT(!spaceDPtr->isThreeComponentMatrix());
792 varLengthTagCount--;
793 }
794 switch (spaceDPtr->transferFunction) {
795 case QColorSpace::TransferFunction::St2084:
796 case QColorSpace::TransferFunction::Hlg:
797 writeCicp = true;
798 fixedLengthTagCount++;
799 break;
800 default:
801 break;
802 }
803
804 const int tagCount = fixedLengthTagCount + varLengthTagCount;
805 const uint profileDataOffset = 128 + 4 + 12 * tagCount;
806 uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
807
808 uint currentOffset = 0;
809 uint rTrcOffset = 0;
810 uint gTrcOffset = 0;
811 uint bTrcOffset = 0;
812 uint kTrcOffset = 0;
813 uint rTrcSize = 0;
814 uint gTrcSize = 0;
815 uint bTrcSize = 0;
816 uint kTrcSize = 0;
817 uint descOffset = 0;
818 uint descSize = 0;
819 uint mA2bOffset = 0;
820 uint mB2aOffset = 0;
821 uint mA2bSize = 0;
822 uint mB2aSize = 0;
823
824 QBuffer buffer;
825 buffer.open(openMode: QIODevice::WriteOnly);
826 QDataStream stream(&buffer);
827
828 // Profile header:
829 stream << uint(0); // Size, we will update this later
830 stream << uint(0);
831 stream << uint(0x04400000); // Version 4.4
832 stream << uint(ProfileClass::Display);
833 switch (spaceDPtr->colorModel) {
834 case QColorSpace::ColorModel::Rgb:
835 stream << uint(ColorSpaceType::Rgb);
836 break;
837 case QColorSpace::ColorModel::Gray:
838 stream << uint(ColorSpaceType::Gray);
839 break;
840 case QColorSpace::ColorModel::Cmyk:
841 stream << uint(ColorSpaceType::Cmyk);
842 break;
843 case QColorSpace::ColorModel::Undefined:
844 Q_UNREACHABLE();
845 }
846 stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
847 stream << uint(0) << uint(0) << uint(0);
848 stream << uint(Tag::acsp);
849 stream << uint(0) << uint(0) << uint(0);
850 stream << uint(0) << uint(0) << uint(0);
851 stream << uint(0); // Rendering intent
852 stream << uint(0x0000f6d6); // D50 X
853 stream << uint(0x00010000); // D50 Y
854 stream << uint(0x0000d32d); // D50 Z
855 stream << IccTag(a: 'Q',b: 't', QT_VERSION_MAJOR, QT_VERSION_MINOR);
856 stream << uint(0) << uint(0) << uint(0) << uint(0);
857 stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
858
859 currentOffset = profileDataOffset;
860 if (spaceDPtr->isThreeComponentMatrix()) {
861 // Tag table:
862 stream << uint(tagCount);
863 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
864 stream << uint(Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
865 stream << uint(Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
866 stream << uint(Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
867 currentOffset += 20 + 20 + 20;
868 }
869 stream << uint(Tag::wtpt) << uint(currentOffset + 00) << uint(20);
870 stream << uint(Tag::cprt) << uint(currentOffset + 20) << uint(34);
871 currentOffset += 20 + 34 + 2;
872 if (writeChad) {
873 stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
874 currentOffset += 44;
875 }
876 if (writeCicp) {
877 stream << uint(Tag::cicp) << uint(currentOffset) << uint(12);
878 currentOffset += 12;
879 }
880 // From here the offset and size will be updated later:
881 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
882 stream << uint(Tag::rTRC) << uint(0) << uint(0);
883 stream << uint(Tag::gTRC) << uint(0) << uint(0);
884 stream << uint(Tag::bTRC) << uint(0) << uint(0);
885 } else {
886 stream << uint(Tag::kTRC) << uint(0) << uint(0);
887 }
888 stream << uint(Tag::desc) << uint(0) << uint(0);
889
890 // Tag data:
891 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
892 stream << uint(Tag::XYZ_) << uint(0);
893 stream << toFixedS1516(x: spaceDPtr->toXyz.r.x);
894 stream << toFixedS1516(x: spaceDPtr->toXyz.r.y);
895 stream << toFixedS1516(x: spaceDPtr->toXyz.r.z);
896 stream << uint(Tag::XYZ_) << uint(0);
897 stream << toFixedS1516(x: spaceDPtr->toXyz.g.x);
898 stream << toFixedS1516(x: spaceDPtr->toXyz.g.y);
899 stream << toFixedS1516(x: spaceDPtr->toXyz.g.z);
900 stream << uint(Tag::XYZ_) << uint(0);
901 stream << toFixedS1516(x: spaceDPtr->toXyz.b.x);
902 stream << toFixedS1516(x: spaceDPtr->toXyz.b.y);
903 stream << toFixedS1516(x: spaceDPtr->toXyz.b.z);
904 }
905 stream << uint(Tag::XYZ_) << uint(0);
906 stream << toFixedS1516(x: spaceDPtr->whitePoint.x);
907 stream << toFixedS1516(x: spaceDPtr->whitePoint.y);
908 stream << toFixedS1516(x: spaceDPtr->whitePoint.z);
909 stream << uint(Tag::mluc) << uint(0);
910 stream << uint(1) << uint(12);
911 stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
912 stream << uint(6) << uint(28);
913 stream << ushort('N') << ushort('/') << ushort('A');
914 stream << ushort(0); // 4-byte alignment
915 if (writeChad) {
916 const QColorMatrix &chad = spaceDPtr->chad;
917 stream << uint(Tag::sf32) << uint(0);
918 stream << toFixedS1516(x: chad.r.x);
919 stream << toFixedS1516(x: chad.g.x);
920 stream << toFixedS1516(x: chad.b.x);
921 stream << toFixedS1516(x: chad.r.y);
922 stream << toFixedS1516(x: chad.g.y);
923 stream << toFixedS1516(x: chad.b.y);
924 stream << toFixedS1516(x: chad.r.z);
925 stream << toFixedS1516(x: chad.g.z);
926 stream << toFixedS1516(x: chad.b.z);
927 }
928 if (writeCicp) {
929 stream << uint(Tag::cicp) << uint(0);
930 stream << uchar(2); // Unspecified primaries
931 if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::St2084)
932 stream << uchar(16);
933 else if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::Hlg)
934 stream << uchar(18);
935 else
936 Q_UNREACHABLE();
937 stream << uchar(0); // Only for YCbCr, otherwise 0
938 stream << uchar(1); // Video full range
939 }
940
941 // From now on the data is variable sized:
942 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
943 rTrcOffset = currentOffset;
944 rTrcSize = writeColorTrc(stream, trc: spaceDPtr->trc[0]);
945 currentOffset += rTrcSize;
946 if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
947 gTrcOffset = rTrcOffset;
948 gTrcSize = rTrcSize;
949 } else {
950 gTrcOffset = currentOffset;
951 gTrcSize = writeColorTrc(stream, trc: spaceDPtr->trc[1]);
952 currentOffset += gTrcSize;
953 }
954 if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
955 bTrcOffset = rTrcOffset;
956 bTrcSize = rTrcSize;
957 } else {
958 bTrcOffset = currentOffset;
959 bTrcSize = writeColorTrc(stream, trc: spaceDPtr->trc[2]);
960 currentOffset += bTrcSize;
961 }
962 } else {
963 Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
964 kTrcOffset = currentOffset;
965 kTrcSize = writeColorTrc(stream, trc: spaceDPtr->trc[0]);
966 currentOffset += kTrcSize;
967 }
968 } else {
969 // Tag table:
970 stream << uint(tagCount);
971 stream << uint(Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
972 stream << uint(Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
973 currentOffset += 20 + 34 + 2;
974 // From here the offset and size will be updated later:
975 stream << uint(Tag::A2B0) << uint(0) << uint(0);
976 if (writeB2a)
977 stream << uint(Tag::B2A0) << uint(0) << uint(0);
978 stream << uint(Tag::desc) << uint(0) << uint(0);
979
980 // Fixed tag data
981 stream << uint(Tag::XYZ_) << uint(0);
982 stream << toFixedS1516(x: spaceDPtr->whitePoint.x);
983 stream << toFixedS1516(x: spaceDPtr->whitePoint.y);
984 stream << toFixedS1516(x: spaceDPtr->whitePoint.z);
985 stream << uint(Tag::mluc) << uint(0);
986 stream << uint(1) << uint(12);
987 stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
988 stream << uint(6) << uint(28);
989 stream << ushort('N') << ushort('/') << ushort('A');
990 stream << ushort(0); // 4-byte alignment
991
992 // From now on the data is variable sized:
993 mA2bOffset = currentOffset;
994 mA2bSize = writeMab(stream, abList: spaceDPtr->mAB, isAb: true, pcsLab: spaceDPtr->isPcsLab, isCmyk: spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
995 currentOffset += mA2bSize;
996 if (writeB2a) {
997 mB2aOffset = currentOffset;
998 mB2aSize = writeMab(stream, abList: spaceDPtr->mBA, isAb: false, pcsLab: spaceDPtr->isPcsLab, isCmyk: spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
999 currentOffset += mB2aSize;
1000 }
1001 }
1002
1003 // Writing description
1004 descOffset = currentOffset;
1005 const QString description = space.description();
1006 stream << uint(Tag::mluc) << uint(0);
1007 stream << uint(1) << uint(12);
1008 stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
1009 stream << uint(description.size() * 2) << uint(28);
1010 for (QChar ch : description)
1011 stream << ushort(ch.unicode());
1012 descSize = 28 + description.size() * 2;
1013 if (description.size() & 1) {
1014 stream << ushort(0);
1015 currentOffset += 2;
1016 }
1017 currentOffset += descSize;
1018
1019 buffer.close();
1020 QByteArray iccProfile = buffer.buffer();
1021 // Now write final size
1022 *(quint32_be *)iccProfile.data() = iccProfile.size();
1023 // And the final indices and sizes of variable size tags:
1024 if (spaceDPtr->isThreeComponentMatrix()) {
1025 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
1026 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
1027 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
1028 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
1029 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
1030 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
1031 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
1032 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
1033 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
1034 } else {
1035 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
1036 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
1037 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
1038 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
1039 }
1040 } else {
1041 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
1042 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
1043 variableTagTableOffsets += 12;
1044 if (writeB2a) {
1045 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
1046 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
1047 variableTagTableOffsets += 12;
1048 }
1049 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
1050 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
1051 }
1052
1053#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
1054 const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
1055 Q_ASSERT(qsizetype(iccHeader->profileSize) == qsizetype(iccProfile.size()));
1056 Q_ASSERT(isValidIccProfile(*iccHeader));
1057#endif
1058
1059 return iccProfile;
1060}
1061
1062struct TagEntry {
1063 quint32 offset;
1064 quint32 size;
1065};
1066
1067static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
1068{
1069 if (tagEntry.size < sizeof(XYZTagData)) {
1070 qCWarning(lcIcc) << "Undersized XYZ tag";
1071 return false;
1072 }
1073 const XYZTagData xyz = qFromUnaligned<XYZTagData>(src: data.constData() + tagEntry.offset);
1074 if (xyz.type != quint32(Tag::XYZ_)) {
1075 qCWarning(lcIcc) << "Bad XYZ content type";
1076 return false;
1077 }
1078 const float x = fromFixedS1516(x: xyz.fixedX);
1079 const float y = fromFixedS1516(x: xyz.fixedY);
1080 const float z = fromFixedS1516(x: xyz.fixedZ);
1081
1082 colorVector = QColorVector(x, y, z);
1083 return true;
1084}
1085
1086static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorTransferTable::Type type = QColorTransferTable::TwoWay)
1087{
1088 if (tagData.size() < 12)
1089 return 0;
1090 const GenericTagData trcData = qFromUnaligned<GenericTagData>(src: tagData.constData());
1091 if (trcData.type == quint32(Tag::curv)) {
1092 Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
1093 const CurvTagData curv = qFromUnaligned<CurvTagData>(src: tagData.constData());
1094 if (curv.valueCount > (1 << 16)) {
1095 qCWarning(lcIcc) << "Invalid count in curv table";
1096 return 0;
1097 }
1098 if (tagData.size() < qsizetype(12 + 2 * curv.valueCount)) {
1099 qCWarning(lcIcc) << "Truncated curv table";
1100 return 0;
1101 }
1102 const auto valueOffset = sizeof(CurvTagData);
1103 if (curv.valueCount == 0) {
1104 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1105 gamma.m_fun = QColorTransferFunction(); // Linear
1106 } else if (curv.valueCount == 1) {
1107 const quint16 v = qFromBigEndian<quint16>(src: tagData.constData() + valueOffset);
1108 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1109 gamma.m_fun = QColorTransferFunction::fromGamma(gamma: v * (1.0f / 256.0f));
1110 } else {
1111 QList<quint16> tabl;
1112 tabl.resize(size: curv.valueCount);
1113 static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
1114 "GenericTagData has padding. The following code is a subject to UB.");
1115 qFromBigEndian<quint16>(source: tagData.constData() + valueOffset, count: curv.valueCount, dest: tabl.data());
1116 QColorTransferTable table(curv.valueCount, tabl, type);
1117 QColorTransferFunction curve;
1118 if (!table.checkValidity()) {
1119 qCWarning(lcIcc) << "Invalid curv table";
1120 return 0;
1121 } else if (!table.asColorTransferFunction(transferFn: &curve)) {
1122 gamma.m_type = QColorTrc::Type::Table;
1123 gamma.m_table = table;
1124 } else {
1125 qCDebug(lcIcc) << "Detected curv table as function";
1126 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1127 gamma.m_fun = curve;
1128 }
1129 }
1130 return 12 + 2 * curv.valueCount;
1131 }
1132 if (trcData.type == quint32(Tag::para)) {
1133 Q_STATIC_ASSERT(sizeof(ParaTagData) == 12);
1134 const ParaTagData para = qFromUnaligned<ParaTagData>(src: tagData.constData());
1135 const auto parametersOffset = sizeof(ParaTagData);
1136 quint32 parameters[7];
1137 switch (para.curveType) {
1138 case 0: {
1139 if (tagData.size() < 12 + 1 * 4)
1140 return 0;
1141 qFromBigEndian<quint32>(source: tagData.constData() + parametersOffset, count: 1, dest: parameters);
1142 float g = fromFixedS1516(x: parameters[0]);
1143 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1144 gamma.m_fun = QColorTransferFunction::fromGamma(gamma: g);
1145 return 12 + 1 * 4;
1146 }
1147 case 1: {
1148 if (tagData.size() < 12 + 3 * 4)
1149 return 0;
1150 qFromBigEndian<quint32>(source: tagData.constData() + parametersOffset, count: 3, dest: parameters);
1151 if (parameters[1] == 0)
1152 return 0;
1153 float g = fromFixedS1516(x: parameters[0]);
1154 float a = fromFixedS1516(x: parameters[1]);
1155 float b = fromFixedS1516(x: parameters[2]);
1156 float d = -b / a;
1157 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1158 gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
1159 return 12 + 3 * 4;
1160 }
1161 case 2: {
1162 if (tagData.size() < 12 + 4 * 4)
1163 return 0;
1164 qFromBigEndian<quint32>(source: tagData.constData() + parametersOffset, count: 4, dest: parameters);
1165 if (parameters[1] == 0)
1166 return 0;
1167 float g = fromFixedS1516(x: parameters[0]);
1168 float a = fromFixedS1516(x: parameters[1]);
1169 float b = fromFixedS1516(x: parameters[2]);
1170 float c = fromFixedS1516(x: parameters[3]);
1171 float d = -b / a;
1172 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1173 gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
1174 return 12 + 4 * 4;
1175 }
1176 case 3: {
1177 if (tagData.size() < 12 + 5 * 4)
1178 return 0;
1179 qFromBigEndian<quint32>(source: tagData.constData() + parametersOffset, count: 5, dest: parameters);
1180 float g = fromFixedS1516(x: parameters[0]);
1181 float a = fromFixedS1516(x: parameters[1]);
1182 float b = fromFixedS1516(x: parameters[2]);
1183 float c = fromFixedS1516(x: parameters[3]);
1184 float d = fromFixedS1516(x: parameters[4]);
1185 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1186 gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
1187 return 12 + 5 * 4;
1188 }
1189 case 4: {
1190 if (tagData.size() < 12 + 7 * 4)
1191 return 0;
1192 qFromBigEndian<quint32>(source: tagData.constData() + parametersOffset, count: 7, dest: parameters);
1193 float g = fromFixedS1516(x: parameters[0]);
1194 float a = fromFixedS1516(x: parameters[1]);
1195 float b = fromFixedS1516(x: parameters[2]);
1196 float c = fromFixedS1516(x: parameters[3]);
1197 float d = fromFixedS1516(x: parameters[4]);
1198 float e = fromFixedS1516(x: parameters[5]);
1199 float f = fromFixedS1516(x: parameters[6]);
1200 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1201 gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
1202 return 12 + 7 * 4;
1203 }
1204 default:
1205 qCWarning(lcIcc) << "Unknown para type" << uint(para.curveType);
1206 return 0;
1207 }
1208 return true;
1209 }
1210 qCWarning(lcIcc) << "Invalid TRC data type" << Qt::hex << trcData.type;
1211 return 0;
1212}
1213
1214template<typename T>
1215static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels)
1216{
1217 if (outputChannels == 4) {
1218 for (qsizetype index = 0; index < clut->table.size(); ++index) {
1219 QColorVector v(tableData[index * 4 + 0] * f,
1220 tableData[index * 4 + 1] * f,
1221 tableData[index * 4 + 2] * f,
1222 tableData[index * 4 + 3] * f);
1223 clut->table[index] = v;
1224 };
1225 } else {
1226 for (qsizetype index = 0; index < clut->table.size(); ++index) {
1227 QColorVector v(tableData[index * 3 + 0] * f,
1228 tableData[index * 3 + 1] * f,
1229 tableData[index * 3 + 2] * f);
1230 clut->table[index] = v;
1231 };
1232 }
1233}
1234
1235// Parses lut8 and lut16 type elements
1236template<typename T>
1237static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
1238{
1239 if (tagEntry.size < sizeof(T)) {
1240 qCWarning(lcIcc) << "Undersized lut8/lut16 tag";
1241 return false;
1242 }
1243 if (qsizetype(tagEntry.size) > data.size()) {
1244 qCWarning(lcIcc) << "Truncated lut8/lut16 tag";
1245 return false;
1246 }
1247 using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
1248 const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
1249 int inputTableEntries, outputTableEntries, precision;
1250 if constexpr (std::is_same_v<T, Lut8TagData>) {
1251 Q_ASSERT(lut.type == quint32(Tag::mft1));
1252 if (!colorSpacePrivate->isPcsLab && isAb) {
1253 qCWarning(lcIcc) << "Lut8 can not output XYZ values";
1254 return false;
1255 }
1256 inputTableEntries = 256;
1257 outputTableEntries = 256;
1258 precision = 1;
1259 } else {
1260 Q_ASSERT(lut.type == quint32(Tag::mft2));
1261 inputTableEntries = lut.inputTableEntries;
1262 outputTableEntries = lut.outputTableEntries;
1263 if (inputTableEntries < 2 || inputTableEntries > 4096)
1264 return false;
1265 if (outputTableEntries < 2 || outputTableEntries > 4096)
1266 return false;
1267 precision = 2;
1268 }
1269
1270 bool inTableIsLinear = true, outTableIsLinear = true;
1271 QColorSpacePrivate::TransferElement inTableElement;
1272 QColorSpacePrivate::TransferElement outTableElement;
1273 QColorCLUT clutElement;
1274 QColorMatrix matrixElement;
1275
1276 matrixElement.r.x = fromFixedS1516(lut.e1);
1277 matrixElement.g.x = fromFixedS1516(lut.e2);
1278 matrixElement.b.x = fromFixedS1516(lut.e3);
1279 matrixElement.r.y = fromFixedS1516(lut.e4);
1280 matrixElement.g.y = fromFixedS1516(lut.e5);
1281 matrixElement.b.y = fromFixedS1516(lut.e6);
1282 matrixElement.r.z = fromFixedS1516(lut.e7);
1283 matrixElement.g.z = fromFixedS1516(lut.e8);
1284 matrixElement.b.z = fromFixedS1516(lut.e9);
1285 if (!colorSpacePrivate->isPcsLab && !isAb && !matrixElement.isValid()) {
1286 qCWarning(lcIcc) << "Invalid matrix values in lut8/lut16";
1287 return false;
1288 }
1289
1290 const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1291 const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1292
1293 if (lut.inputChannels != inputChannels) {
1294 qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
1295 return false;
1296 }
1297
1298 if (lut.outputChannels != outputChannels) {
1299 qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
1300 return false;
1301 }
1302
1303 const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
1304 if (tagEntry.size < (sizeof(T) + precision * inputChannels * inputTableEntries
1305 + precision * outputChannels * outputTableEntries
1306 + precision * outputChannels * clutTableSize)) {
1307 qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
1308 return false;
1309 }
1310 if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
1311 qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
1312 return false;
1313 }
1314
1315 const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
1316
1317 for (int j = 0; j < inputChannels; ++j) {
1318 QList<S> input(inputTableEntries);
1319 qFromBigEndian<S>(tableData, inputTableEntries, input.data());
1320 QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
1321 if (!table.checkValidity()) {
1322 qCWarning(lcIcc) << "Bad input table in lut8/lut16";
1323 return false;
1324 }
1325 if (!table.isIdentity())
1326 inTableIsLinear = false;
1327 inTableElement.trc[j] = std::move(table);
1328 tableData += inputTableEntries * precision;
1329 }
1330
1331 clutElement.table.resize(size: clutTableSize);
1332 clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
1333 if (inputChannels == 4)
1334 clutElement.gridPointsW = lut.clutGridPoints;
1335
1336 if constexpr (std::is_same_v<T, Lut8TagData>) {
1337 parseCLUT(tableData, f: 1.f / 255.f, clut: &clutElement, outputChannels);
1338 } else {
1339 float f = 1.0f / 65535.f;
1340 if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
1341 f = 1.0f / 65280.f;
1342 QList<S> clutTable(clutTableSize * outputChannels);
1343 qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
1344 parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
1345 }
1346 tableData += clutTableSize * outputChannels * precision;
1347
1348 for (int j = 0; j < outputChannels; ++j) {
1349 QList<S> output(outputTableEntries);
1350 qFromBigEndian<S>(tableData, outputTableEntries, output.data());
1351 QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
1352 if (!table.checkValidity()) {
1353 qCWarning(lcIcc) << "Bad output table in lut8/lut16";
1354 return false;
1355 }
1356 if (!table.isIdentity())
1357 outTableIsLinear = false;
1358 outTableElement.trc[j] = std::move(table);
1359 tableData += outputTableEntries * precision;
1360 }
1361
1362 if (isAb) {
1363 if (!inTableIsLinear)
1364 colorSpacePrivate->mAB.append(t: inTableElement);
1365 if (!clutElement.isEmpty())
1366 colorSpacePrivate->mAB.append(t: clutElement);
1367 if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
1368 colorSpacePrivate->mAB.append(t: outTableElement);
1369 } else {
1370 // The matrix is only to be applied if the input color-space is XYZ
1371 if (!colorSpacePrivate->isPcsLab && !matrixElement.isIdentity())
1372 colorSpacePrivate->mBA.append(t: matrixElement);
1373 if (!inTableIsLinear)
1374 colorSpacePrivate->mBA.append(t: inTableElement);
1375 if (!clutElement.isEmpty())
1376 colorSpacePrivate->mBA.append(t: clutElement);
1377 if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
1378 colorSpacePrivate->mBA.append(t: outTableElement);
1379 }
1380 return true;
1381}
1382
1383// Parses mAB and mBA type elements
1384static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
1385{
1386 if (tagEntry.size < sizeof(mABTagData)) {
1387 qCWarning(lcIcc) << "Undersized mAB/mBA tag";
1388 return false;
1389 }
1390 if (qsizetype(tagEntry.size) > data.size()) {
1391 qCWarning(lcIcc) << "Truncated mAB/mBA tag";
1392 return false;
1393 }
1394 const mABTagData mab = qFromUnaligned<mABTagData>(src: data.constData() + tagEntry.offset);
1395 if ((mab.type != quint32(Tag::mAB_) && isAb) || (mab.type != quint32(Tag::mBA_) && !isAb)){
1396 qCWarning(lcIcc) << "Bad mAB/mBA content type";
1397 return false;
1398 }
1399
1400 const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1401 const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1402
1403 if (mab.inputChannels != inputChannels) {
1404 qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
1405 return false;
1406 }
1407
1408 if (mab.outputChannels != outputChannels) {
1409 qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
1410 return false;
1411 }
1412
1413 // These combinations are legal: B, M + Matrix + B, A + Clut + B, A + Clut + M + Matrix + B
1414 if (!mab.bCurvesOffset) {
1415 qCWarning(lcIcc) << "Illegal mAB/mBA without B table";
1416 return false;
1417 }
1418 if (((bool)mab.matrixOffset != (bool)mab.mCurvesOffset) ||
1419 ((bool)mab.aCurvesOffset != (bool)mab.clutOffset)) {
1420 qCWarning(lcIcc) << "Illegal mAB/mBA element combination";
1421 return false;
1422 }
1423
1424 if (mab.aCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
1425 mab.bCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
1426 mab.mCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
1427 mab.matrixOffset > (tagEntry.size - 4 * 12) ||
1428 mab.clutOffset > (tagEntry.size - 20)) {
1429 qCWarning(lcIcc) << "Illegal mAB/mBA element offset";
1430 return false;
1431 }
1432
1433 QColorSpacePrivate::TransferElement bTableElement;
1434 QColorSpacePrivate::TransferElement aTableElement;
1435 QColorCLUT clutElement;
1436 QColorSpacePrivate::TransferElement mTableElement;
1437 QColorMatrix matrixElement;
1438 QColorVector offsetElement;
1439
1440 auto parseCurves = [&data, &tagEntry] (uint curvesOffset, QColorTrc *table, int channels) {
1441 for (int i = 0; i < channels; ++i) {
1442 if (qsizetype(tagEntry.offset + curvesOffset + 12) > data.size() || curvesOffset + 12 > tagEntry.size) {
1443 qCWarning(lcIcc) << "Space missing for channel curves in mAB/mBA";
1444 return false;
1445 }
1446 auto size = parseTRC(tagData: QByteArrayView(data).sliced(pos: tagEntry.offset + curvesOffset, n: tagEntry.size - curvesOffset), gamma&: table[i], type: QColorTransferTable::OneWay);
1447 if (!size)
1448 return false;
1449 if (size & 2) size += 2; // possible padding
1450 curvesOffset += size;
1451 }
1452 return true;
1453 };
1454
1455 bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
1456
1457 // B Curves
1458 if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
1459 qCWarning(lcIcc) << "Invalid B curves";
1460 return false;
1461 } else {
1462 bCurvesAreLinear = bTableElement.trc[0].isIdentity() && bTableElement.trc[1].isIdentity() && bTableElement.trc[2].isIdentity();
1463 }
1464
1465 // A Curves
1466 if (mab.aCurvesOffset) {
1467 if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
1468 qCWarning(lcIcc) << "Invalid A curves";
1469 return false;
1470 } else {
1471 aCurvesAreLinear = aTableElement.trc[0].isIdentity() && aTableElement.trc[1].isIdentity() && aTableElement.trc[2].isIdentity();
1472 }
1473 }
1474
1475 // M Curves
1476 if (mab.mCurvesOffset) {
1477 if (!parseCurves(mab.mCurvesOffset, mTableElement.trc, 3)) {
1478 qCWarning(lcIcc) << "Invalid M curves";
1479 return false;
1480 } else {
1481 mCurvesAreLinear = mTableElement.trc[0].isIdentity() && mTableElement.trc[1].isIdentity() && mTableElement.trc[2].isIdentity();
1482 }
1483 }
1484
1485 // Matrix
1486 if (mab.matrixOffset) {
1487 const MatrixElement matrix = qFromUnaligned<MatrixElement>(src: data.constData() + tagEntry.offset + mab.matrixOffset);
1488 matrixElement.r.x = fromFixedS1516(x: matrix.e0);
1489 matrixElement.g.x = fromFixedS1516(x: matrix.e1);
1490 matrixElement.b.x = fromFixedS1516(x: matrix.e2);
1491 matrixElement.r.y = fromFixedS1516(x: matrix.e3);
1492 matrixElement.g.y = fromFixedS1516(x: matrix.e4);
1493 matrixElement.b.y = fromFixedS1516(x: matrix.e5);
1494 matrixElement.r.z = fromFixedS1516(x: matrix.e6);
1495 matrixElement.g.z = fromFixedS1516(x: matrix.e7);
1496 matrixElement.b.z = fromFixedS1516(x: matrix.e8);
1497 offsetElement.x = fromFixedS1516(x: matrix.e9);
1498 offsetElement.y = fromFixedS1516(x: matrix.e10);
1499 offsetElement.z = fromFixedS1516(x: matrix.e11);
1500 if (!matrixElement.isValid() || !offsetElement.isValid()) {
1501 qCWarning(lcIcc) << "Invalid matrix values in mAB/mBA element";
1502 return false;
1503 }
1504 }
1505
1506 // CLUT
1507 if (mab.clutOffset) {
1508 clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]);
1509 clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]);
1510 clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab.clutOffset + 2]);
1511 clutElement.gridPointsW = std::max(a: uint8_t(data[tagEntry.offset + mab.clutOffset + 3]), b: uint8_t(1));
1512 const uchar precision = data[tagEntry.offset + mab.clutOffset + 16];
1513 if (precision > 2 || precision < 1) {
1514 qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision";
1515 return false;
1516 }
1517 if (clutElement.gridPointsX < 2 || clutElement.gridPointsY < 2 || clutElement.gridPointsZ < 2) {
1518 qCWarning(lcIcc) << "Empty CLUT";
1519 return false;
1520 }
1521 const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
1522 if ((mab.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
1523 qCWarning(lcIcc) << "CLUT oversized for tag";
1524 return false;
1525 }
1526
1527 clutElement.table.resize(size: clutTableSize);
1528 if (precision == 2) {
1529 QList<uint16_t> clutTable(clutTableSize * outputChannels);
1530 qFromBigEndian<uint16_t>(source: data.constData() + tagEntry.offset + mab.clutOffset + 20, count: clutTable.size(), dest: clutTable.data());
1531 parseCLUT(tableData: clutTable.constData(), f: (1.f/65535.f), clut: &clutElement, outputChannels);
1532 } else {
1533 const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
1534 parseCLUT(tableData: clutTable, f: (1.f/255.f), clut: &clutElement, outputChannels);
1535 }
1536 } else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
1537 qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
1538 return false;
1539 }
1540
1541 if (isAb) {
1542 if (mab.aCurvesOffset) {
1543 if (!aCurvesAreLinear)
1544 colorSpacePrivate->mAB.append(t: std::move(aTableElement));
1545 if (!clutElement.isEmpty())
1546 colorSpacePrivate->mAB.append(t: std::move(clutElement));
1547 }
1548 if (mab.mCurvesOffset && outputChannels == 3) {
1549 if (!mCurvesAreLinear)
1550 colorSpacePrivate->mAB.append(t: std::move(mTableElement));
1551 if (!matrixElement.isIdentity())
1552 colorSpacePrivate->mAB.append(t: std::move(matrixElement));
1553 if (!offsetElement.isNull())
1554 colorSpacePrivate->mAB.append(t: std::move(offsetElement));
1555 }
1556 if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
1557 colorSpacePrivate->mAB.append(t: std::move(bTableElement));
1558 } else {
1559 if (!bCurvesAreLinear)
1560 colorSpacePrivate->mBA.append(t: std::move(bTableElement));
1561 if (mab.mCurvesOffset && inputChannels == 3) {
1562 if (!matrixElement.isIdentity())
1563 colorSpacePrivate->mBA.append(t: std::move(matrixElement));
1564 if (!offsetElement.isNull())
1565 colorSpacePrivate->mBA.append(t: std::move(offsetElement));
1566 if (!mCurvesAreLinear)
1567 colorSpacePrivate->mBA.append(t: std::move(mTableElement));
1568 }
1569 if (mab.aCurvesOffset) {
1570 if (!clutElement.isEmpty())
1571 colorSpacePrivate->mBA.append(t: std::move(clutElement));
1572 if (!aCurvesAreLinear)
1573 colorSpacePrivate->mBA.append(t: std::move(aTableElement));
1574 }
1575 if (colorSpacePrivate->mBA.isEmpty()) // Ensure non-empty to indicate valid empty transform
1576 colorSpacePrivate->mBA.append(t: std::move(bTableElement));
1577 }
1578
1579 return true;
1580}
1581
1582static bool parseA2B(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *privat, bool isAb)
1583{
1584 const GenericTagData a2bData = qFromUnaligned<GenericTagData>(src: data.constData() + tagEntry.offset);
1585 if (a2bData.type == quint32(Tag::mft1))
1586 return parseLutData<Lut8TagData>(data, tagEntry, colorSpacePrivate: privat, isAb);
1587 else if (a2bData.type == quint32(Tag::mft2))
1588 return parseLutData<Lut16TagData>(data, tagEntry, colorSpacePrivate: privat, isAb);
1589 else if (a2bData.type == quint32(Tag::mAB_) || a2bData.type == quint32(Tag::mBA_))
1590 return parseMabData(data, tagEntry, colorSpacePrivate: privat, isAb);
1591
1592 qCWarning(lcIcc) << "fromIccProfile: Unknown A2B/B2A data type";
1593 return false;
1594}
1595
1596static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
1597{
1598 const GenericTagData tag = qFromUnaligned<GenericTagData>(src: data.constData() + tagEntry.offset);
1599
1600 // Either 'desc' (ICCv2) or 'mluc' (ICCv4)
1601 if (tag.type == quint32(Tag::desc)) {
1602 if (tagEntry.size < sizeof(DescTagData))
1603 return false;
1604 Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
1605 const DescTagData desc = qFromUnaligned<DescTagData>(src: data.constData() + tagEntry.offset);
1606 const quint32 len = desc.asciiDescriptionLength;
1607 if (len < 1)
1608 return false;
1609 if (tagEntry.size - 12 < len)
1610 return false;
1611 const char *asciiDescription = data.constData() + tagEntry.offset + sizeof(DescTagData);
1612 if (asciiDescription[len - 1] != '\0')
1613 return false;
1614 descName = QString::fromLatin1(str: asciiDescription, size: len - 1);
1615 return true;
1616 }
1617 if (tag.type != quint32(Tag::mluc))
1618 return false;
1619
1620 if (tagEntry.size < sizeof(MlucTagData))
1621 return false;
1622 const MlucTagData mluc = qFromUnaligned<MlucTagData>(src: data.constData() + tagEntry.offset);
1623 if (mluc.recordCount < 1)
1624 return false;
1625 if (mluc.recordSize != 12)
1626 return false;
1627 // We just use the primary record regardless of language or country.
1628 const quint32 stringOffset = mluc.records[0].offset;
1629 const quint32 stringSize = mluc.records[0].size;
1630 if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
1631 return false;
1632 if ((stringSize | stringOffset) & 1)
1633 return false;
1634 quint32 stringLen = stringSize / 2;
1635 QVarLengthArray<char16_t> utf16hostendian(stringLen);
1636 qFromBigEndian<char16_t>(source: data.constData() + tagEntry.offset + stringOffset, count: stringLen,
1637 dest: utf16hostendian.data());
1638 // The given length shouldn't include 0-termination, but might.
1639 if (stringLen > 1 && utf16hostendian[stringLen - 1] == 0)
1640 --stringLen;
1641 descName = QString::fromUtf16(utf16hostendian.data(), size: stringLen);
1642 return true;
1643}
1644
1645static bool parseRgbMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
1646{
1647 // Parse XYZ tags
1648 if (!parseXyzData(data, tagEntry: tagIndex[Tag::rXYZ], colorVector&: colorspaceDPtr->toXyz.r))
1649 return false;
1650 if (!parseXyzData(data, tagEntry: tagIndex[Tag::gXYZ], colorVector&: colorspaceDPtr->toXyz.g))
1651 return false;
1652 if (!parseXyzData(data, tagEntry: tagIndex[Tag::bXYZ], colorVector&: colorspaceDPtr->toXyz.b))
1653 return false;
1654 if (!parseXyzData(data, tagEntry: tagIndex[Tag::wtpt], colorVector&: colorspaceDPtr->whitePoint))
1655 return false;
1656 if (!colorspaceDPtr->toXyz.isValid() || !colorspaceDPtr->whitePoint.isValid() || colorspaceDPtr->whitePoint.isNull()) {
1657 qCWarning(lcIcc) << "Invalid XYZ values in RGB matrix";
1658 return false;
1659 }
1660
1661 colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
1662 if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
1663 qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
1664 colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
1665 } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
1666 qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
1667 colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
1668 } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
1669 qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
1670 colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
1671 } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
1672 qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
1673 colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
1674 } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromBt2020()) {
1675 qCDebug(lcIcc) << "fromIccProfile: BT.2020 primaries detected";
1676 colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020;
1677 }
1678 return true;
1679}
1680
1681static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
1682{
1683 QColorVector whitePoint;
1684 if (!parseXyzData(data, tagEntry: tagIndex[Tag::wtpt], colorVector&: whitePoint))
1685 return false;
1686 if (!whitePoint.isValid() || !qFuzzyCompare(p1: whitePoint.y, p2: 1.0f) || (1.0f + whitePoint.z + whitePoint.x) == 0.0f) {
1687 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
1688 return false;
1689 }
1690 colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
1691 colorspaceDPtr->whitePoint = whitePoint;
1692 return true;
1693}
1694
1695static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
1696{
1697 if (tagEntry.size < sizeof(Sf32TagData) || qsizetype(tagEntry.size) > data.size())
1698 return false;
1699 const Sf32TagData chadtag = qFromUnaligned<Sf32TagData>(src: data.constData() + tagEntry.offset);
1700 if (chadtag.type != uint32_t(Tag::sf32)) {
1701 qCWarning(lcIcc, "fromIccProfile: bad chad data type");
1702 return false;
1703 }
1704 QColorMatrix chad;
1705 chad.r.x = fromFixedS1516(x: chadtag.value[0]);
1706 chad.g.x = fromFixedS1516(x: chadtag.value[1]);
1707 chad.b.x = fromFixedS1516(x: chadtag.value[2]);
1708 chad.r.y = fromFixedS1516(x: chadtag.value[3]);
1709 chad.g.y = fromFixedS1516(x: chadtag.value[4]);
1710 chad.b.y = fromFixedS1516(x: chadtag.value[5]);
1711 chad.r.z = fromFixedS1516(x: chadtag.value[6]);
1712 chad.g.z = fromFixedS1516(x: chadtag.value[7]);
1713 chad.b.z = fromFixedS1516(x: chadtag.value[8]);
1714
1715 if (!chad.isValid()) {
1716 qCWarning(lcIcc, "fromIccProfile: invalid chad matrix");
1717 return false;
1718 }
1719 colorspaceDPtr->chad = chad;
1720 return true;
1721}
1722
1723static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
1724{
1725 TagEntry rTrc;
1726 TagEntry gTrc;
1727 TagEntry bTrc;
1728 if (isColorSpaceTypeGray) {
1729 rTrc = tagIndex[Tag::kTRC];
1730 gTrc = tagIndex[Tag::kTRC];
1731 bTrc = tagIndex[Tag::kTRC];
1732 } else if (tagIndex.contains(key: Tag::aarg) && tagIndex.contains(key: Tag::aagg) && tagIndex.contains(key: Tag::aabg)) {
1733 // Apple extension for parametric version of TRCs in ICCv2:
1734 rTrc = tagIndex[Tag::aarg];
1735 gTrc = tagIndex[Tag::aagg];
1736 bTrc = tagIndex[Tag::aabg];
1737 } else {
1738 rTrc = tagIndex[Tag::rTRC];
1739 gTrc = tagIndex[Tag::gTRC];
1740 bTrc = tagIndex[Tag::bTRC];
1741 }
1742
1743 QColorTrc rCurve;
1744 QColorTrc gCurve;
1745 QColorTrc bCurve;
1746 if (!parseTRC(tagData: QByteArrayView(data).sliced(pos: rTrc.offset, n: rTrc.size), gamma&: rCurve, type: QColorTransferTable::TwoWay)) {
1747 qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
1748 return false;
1749 }
1750 if (!parseTRC(tagData: QByteArrayView(data).sliced(pos: gTrc.offset, n: gTrc.size), gamma&: gCurve, type: QColorTransferTable::TwoWay)) {
1751 qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
1752 return false;
1753 }
1754 if (!parseTRC(tagData: QByteArrayView(data).sliced(pos: bTrc.offset, n: bTrc.size), gamma&: bCurve, type: QColorTransferTable::TwoWay)) {
1755 qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
1756 return false;
1757 }
1758 if (rCurve == gCurve && gCurve == bCurve) {
1759 if (rCurve.isIdentity()) {
1760 qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
1761 colorspaceDPtr->trc[0] = QColorTransferFunction();
1762 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
1763 colorspaceDPtr->gamma = 1.0f;
1764 } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isGamma()) {
1765 qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
1766 colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(gamma: rCurve.m_fun.m_g);
1767 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
1768 colorspaceDPtr->gamma = rCurve.m_fun.m_g;
1769 } else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isSRgb()) {
1770 qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
1771 colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
1772 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
1773 } else {
1774 colorspaceDPtr->trc[0] = rCurve;
1775 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1776 }
1777 colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
1778 colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
1779 } else {
1780 colorspaceDPtr->trc[0] = rCurve;
1781 colorspaceDPtr->trc[1] = gCurve;
1782 colorspaceDPtr->trc[2] = bCurve;
1783 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1784 }
1785 return true;
1786}
1787
1788static bool parseCicp(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
1789{
1790 if (colorspaceDPtr->isPcsLab)
1791 return false;
1792 if (tagEntry.size < sizeof(CicpTagData) || qsizetype(tagEntry.size) > data.size())
1793 return false;
1794 const CicpTagData cicp = qFromUnaligned<CicpTagData>(src: data.constData() + tagEntry.offset);
1795 if (cicp.type != uint32_t(Tag::cicp)) {
1796 qCWarning(lcIcc, "fromIccProfile: bad cicp data type");
1797 return false;
1798 }
1799 switch (cicp.transferCharacteristics) {
1800 case 4:
1801 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
1802 colorspaceDPtr->gamma = 2.2f;
1803 break;
1804 case 5:
1805 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
1806 colorspaceDPtr->gamma = 2.8f;
1807 break;
1808 case 8:
1809 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
1810 break;
1811 case 13:
1812 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
1813 break;
1814 case 1:
1815 case 6:
1816 case 14:
1817 case 15:
1818 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Bt2020;
1819 break;
1820 case 16:
1821 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::St2084;
1822 break;
1823 case 18:
1824 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Hlg;
1825 break;
1826 default:
1827 return false;
1828 }
1829 switch (cicp.colorPrimaries) {
1830 case 1:
1831 colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
1832 break;
1833 case 9:
1834 colorspaceDPtr->primaries = QColorSpace::Primaries::Bt2020;
1835 break;
1836 case 12:
1837 colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
1838 break;
1839 default:
1840 return false;
1841 }
1842 return true;
1843}
1844
1845bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
1846{
1847 if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
1848 qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
1849 return false;
1850 }
1851 const ICCProfileHeader header = qFromUnaligned<ICCProfileHeader>(src: data.constData());
1852 if (!isValidIccProfile(header))
1853 return false; // if failed we already printing a warning
1854 if (qsizetype(header.profileSize) > data.size() || qsizetype(header.profileSize) < qsizetype(sizeof(ICCProfileHeader))) {
1855 qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
1856 return false;
1857 }
1858
1859 const qsizetype offsetToData = sizeof(ICCProfileHeader) + header.tagCount * sizeof(TagTableEntry);
1860 Q_ASSERT(offsetToData > 0);
1861 if (offsetToData > data.size()) {
1862 qCWarning(lcIcc) << "fromIccProfile: failed index size sanity";
1863 return false;
1864 }
1865
1866 QHash<Tag, TagEntry> tagIndex;
1867 for (uint i = 0; i < header.tagCount; ++i) {
1868 // Read tag index
1869 const qsizetype tableOffset = sizeof(ICCProfileHeader) + i * sizeof(TagTableEntry);
1870 const TagTableEntry tagTable = qFromUnaligned<TagTableEntry>(src: data.constData()
1871 + tableOffset);
1872
1873 // Sanity check tag sizes and offsets:
1874 if (qsizetype(tagTable.offset) < offsetToData) {
1875 qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
1876 return false;
1877 }
1878 // Checked separately from (+ size) to handle overflow.
1879 if (tagTable.offset > header.profileSize) {
1880 qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
1881 return false;
1882 }
1883 if (tagTable.size < 8) {
1884 qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
1885 return false;
1886 }
1887 if (tagTable.size > header.profileSize - tagTable.offset) {
1888 qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
1889 return false;
1890 }
1891 if (tagTable.offset & 0x03) {
1892 qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment";
1893 return false;
1894 }
1895// printf("'%4s' %d %d\n", (const char *)&tagTable.signature,
1896// quint32(tagTable.offset),
1897// quint32(tagTable.size));
1898 tagIndex.insert(key: Tag(quint32(tagTable.signature)), value: { .offset: tagTable.offset, .size: tagTable.size });
1899 }
1900
1901 bool threeComponentMatrix = true;
1902
1903 if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
1904 // Check the profile is three-component matrix based:
1905 if (!tagIndex.contains(key: Tag::rXYZ) || !tagIndex.contains(key: Tag::gXYZ) || !tagIndex.contains(key: Tag::bXYZ) ||
1906 !tagIndex.contains(key: Tag::rTRC) || !tagIndex.contains(key: Tag::gTRC) || !tagIndex.contains(key: Tag::bTRC) ||
1907 !tagIndex.contains(key: Tag::wtpt) || header.pcs == uint(Tag::Lab_)) {
1908 threeComponentMatrix = false;
1909 // Check if the profile is valid n-LUT based:
1910 if (!tagIndex.contains(key: Tag::A2B0)) {
1911 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
1912 return false;
1913 }
1914 }
1915 } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
1916 if (!tagIndex.contains(key: Tag::kTRC) || !tagIndex.contains(key: Tag::wtpt)) {
1917 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
1918 return false;
1919 }
1920 } else if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) {
1921 threeComponentMatrix = false;
1922 if (!tagIndex.contains(key: Tag::A2B0)) {
1923 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
1924 return false;
1925 }
1926 } else {
1927 Q_UNREACHABLE();
1928 }
1929
1930 colorSpace->detach();
1931 QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(colorSpace&: *colorSpace);
1932
1933 colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
1934 if (tagIndex.contains(key: Tag::cicp) && header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
1935 // Let cicp override nLut profiles if we fully recognize it.
1936 if (parseCicp(data, tagEntry: tagIndex[Tag::cicp], colorspaceDPtr))
1937 threeComponentMatrix = true;
1938 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1939 colorspaceDPtr->setToXyzMatrix();
1940 if (colorspaceDPtr->transferFunction != QColorSpace::TransferFunction::Custom)
1941 colorspaceDPtr->setTransferFunction();
1942 }
1943
1944 if (threeComponentMatrix) {
1945 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
1946
1947 if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
1948 if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr))
1949 return false;
1950 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1951 } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
1952 if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
1953 return false;
1954 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
1955 } else {
1956 Q_UNREACHABLE();
1957 }
1958 if (auto it = tagIndex.constFind(key: Tag::chad); it != tagIndex.constEnd()) {
1959 if (!parseChad(data, tagEntry: it.value(), colorspaceDPtr))
1960 return false;
1961 } else {
1962 colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(whitePoint: colorspaceDPtr->whitePoint);
1963 }
1964 if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
1965 colorspaceDPtr->toXyz = colorspaceDPtr->chad;
1966
1967 // Reset the matrix to our canonical values:
1968 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1969 colorspaceDPtr->setToXyzMatrix();
1970
1971 if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom &&
1972 !parseTRCs(data, tagIndex, colorspaceDPtr, isColorSpaceTypeGray: header.inputColorSpace == uint(ColorSpaceType::Gray)))
1973 return false;
1974 } else {
1975 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
1976 if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
1977 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
1978 else
1979 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1980
1981 // Only parse the default perceptual transform for now
1982 if (!parseA2B(data, tagEntry: tagIndex[Tag::A2B0], privat: colorspaceDPtr, isAb: true))
1983 return false;
1984 if (auto it = tagIndex.constFind(key: Tag::B2A0); it != tagIndex.constEnd()) {
1985 if (!parseA2B(data, tagEntry: it.value(), privat: colorspaceDPtr, isAb: false))
1986 return false;
1987 }
1988
1989 if (auto it = tagIndex.constFind(key: Tag::wtpt); it != tagIndex.constEnd()) {
1990 if (!parseXyzData(data, tagEntry: it.value(), colorVector&: colorspaceDPtr->whitePoint))
1991 return false;
1992 }
1993 if (auto it = tagIndex.constFind(key: Tag::chad); it != tagIndex.constEnd()) {
1994 if (!parseChad(data, tagEntry: it.value(), colorspaceDPtr))
1995 return false;
1996 } else if (!colorspaceDPtr->whitePoint.isNull()) {
1997 colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(whitePoint: colorspaceDPtr->whitePoint);
1998 }
1999 }
2000
2001 if (auto it = tagIndex.constFind(key: Tag::desc); it != tagIndex.constEnd()) {
2002 if (!parseDesc(data, tagEntry: it.value(), descName&: colorspaceDPtr->description))
2003 qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
2004 else
2005 qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
2006 }
2007
2008 colorspaceDPtr->identifyColorSpace();
2009 if (colorspaceDPtr->namedColorSpace)
2010 qCDebug(lcIcc) << "fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace);
2011
2012 colorspaceDPtr->iccProfile = data;
2013
2014 Q_ASSERT(colorspaceDPtr->isValid());
2015 return true;
2016}
2017
2018} // namespace QIcc
2019
2020QT_END_NAMESPACE
2021

source code of qtbase/src/gui/painting/qicc.cpp