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