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

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