Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qicc.cpp
Go to the documentation of this file.
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
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 "qcolorclut_p.h"
16#include "qcolormatrix_p.h"
17#include "qcolorspace_p.h"
18#include "qcolortrc_p.h"
19
20#include <array>
21
23Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc", QtWarningMsg)
24
25namespace QIcc {
26
56
58{
59 return (a << 24) | (b << 16) | (c << 8) | d;
60}
61
62enum class ColorSpaceType : quint32 {
63 Rgb = IccTag('R', 'G', 'B', ' '),
64 Gray = IccTag('G', 'R', 'A', 'Y'),
65 Cmyk = IccTag('C', 'M', 'Y', 'K'),
66};
67
68enum class ProfileClass : quint32 {
69 Input = IccTag('s', 'c', 'n', 'r'),
70 Display = IccTag('m', 'n', 't', 'r'),
71 Output = IccTag('p', 'r', 't', 'r'),
72 ColorSpace = IccTag('s', 'p', 'a', 'c'),
73 // Not supported:
74 DeviceLink = IccTag('l', 'i', 'n', 'k'),
75 Abstract = IccTag('a', 'b', 's', 't'),
76 NamedColor = IccTag('n', 'm', 'c', 'l'),
77};
78
79enum class Tag : quint32 {
80 acsp = IccTag('a', 'c', 's', 'p'),
81 Lab_ = IccTag('L', 'a', 'b', ' '),
82 RGB_ = IccTag('R', 'G', 'B', ' '),
83 XYZ_ = IccTag('X', 'Y', 'Z', ' '),
84 rXYZ = IccTag('r', 'X', 'Y', 'Z'),
85 gXYZ = IccTag('g', 'X', 'Y', 'Z'),
86 bXYZ = IccTag('b', 'X', 'Y', 'Z'),
87 rTRC = IccTag('r', 'T', 'R', 'C'),
88 gTRC = IccTag('g', 'T', 'R', 'C'),
89 bTRC = IccTag('b', 'T', 'R', 'C'),
90 kTRC = IccTag('k', 'T', 'R', 'C'),
91 A2B0 = IccTag('A', '2', 'B', '0'),
92 A2B1 = IccTag('A', '2', 'B', '1'),
93 A2B2 = IccTag('A', '2', 'B', '2'),
94 B2A0 = IccTag('B', '2', 'A', '0'),
95 B2A1 = IccTag('B', '2', 'A', '1'),
96 B2A2 = IccTag('B', '2', 'A', '2'),
97 B2D0 = IccTag('B', '2', 'D', '0'),
98 B2D1 = IccTag('B', '2', 'D', '1'),
99 B2D2 = IccTag('B', '2', 'D', '2'),
100 B2D3 = IccTag('B', '2', 'D', '3'),
101 D2B0 = IccTag('D', '2', 'B', '0'),
102 D2B1 = IccTag('D', '2', 'B', '1'),
103 D2B2 = IccTag('D', '2', 'B', '2'),
104 D2B3 = IccTag('D', '2', 'B', '3'),
105 desc = IccTag('d', 'e', 's', 'c'),
106 text = IccTag('t', 'e', 'x', 't'),
107 cprt = IccTag('c', 'p', 'r', 't'),
108 curv = IccTag('c', 'u', 'r', 'v'),
109 para = IccTag('p', 'a', 'r', 'a'),
110 wtpt = IccTag('w', 't', 'p', 't'),
111 bkpt = IccTag('b', 'k', 'p', 't'),
112 mft1 = IccTag('m', 'f', 't', '1'),
113 mft2 = IccTag('m', 'f', 't', '2'),
114 mluc = IccTag('m', 'l', 'u', 'c'),
115 mpet = IccTag('m', 'p', 'e', 't'),
116 mAB_ = IccTag('m', 'A', 'B', ' '),
117 mBA_ = IccTag('m', 'B', 'A', ' '),
118 chad = IccTag('c', 'h', 'a', 'd'),
119 gamt = IccTag('g', 'a', 'm', 't'),
120 sf32 = IccTag('s', 'f', '3', '2'),
121
122 // Apple extensions for ICCv2:
123 aarg = IccTag('a', 'a', 'r', 'g'),
124 aagg = IccTag('a', 'a', 'g', 'g'),
125 aabg = IccTag('a', 'a', 'b', 'g'),
126};
127
128} // namespace QIcc
129
130inline size_t qHash(const QIcc::Tag &key, size_t seed = 0)
131{
132 return qHash(quint32(key), seed);
133}
134
135namespace QIcc {
136
143
148
154
157 // followed by curv values: quint16_be[]
158};
159
163 // followed by parameter values: quint32_be[1-7];
164};
165
168 // followed by ascii description: char[]
169 // .. we ignore the rest
170};
171
178
184
199 // followed by parameter values: quint8[inputChannels * 256];
200 // followed by parameter values: quint8[outputChannels * clutGridPoints^inputChannels];
201 // followed by parameter values: quint8[outputChannels * 256];
202};
203
220 // followed by parameter values: quint16_be[inputChannels * inputTableEntries];
221 // followed by parameter values: quint16_be[outputChannels * clutGridPoints^inputChannels];
222 // followed by parameter values: quint16_be[outputChannels * outputTableEntries];
223};
224
225// For both mAB and mBA
237
245
249
264
265static int toFixedS1516(float x)
266{
267 return int(x * 65536.0f + 0.5f);
268}
269
270static float fromFixedS1516(int x)
271{
272 return x * (1.0f / 65536.0f);
273}
274
276{
277 if (header.signature != uint(Tag::acsp)) {
278 qCWarning(lcIcc, "Failed ICC signature test");
279 return false;
280 }
281
282 // Don't overflow 32bit integers:
283 if (header.tagCount >= (INT32_MAX - sizeof(ICCProfileHeader)) / sizeof(TagTableEntry)) {
284 qCWarning(lcIcc, "Failed tag count sanity");
285 return false;
286 }
287 if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) {
288 qCWarning(lcIcc, "Failed basic size sanity");
289 return false;
290 }
291
292 if (header.profileClass != uint(ProfileClass::Input)
293 && header.profileClass != uint(ProfileClass::Display)
294 && header.profileClass != uint(ProfileClass::Output)
295 && header.profileClass != uint(ProfileClass::ColorSpace)) {
296 qCInfo(lcIcc, "Unsupported ICC profile class 0x%x", quint32(header.profileClass));
297 return false;
298 }
299 if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
300 && header.inputColorSpace != uint(ColorSpaceType::Gray)
301 && header.inputColorSpace != uint(ColorSpaceType::Cmyk)) {
302 qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
303 return false;
304 }
305 if (header.pcs != uint(Tag::XYZ_) && header.pcs != uint(Tag::Lab_)) {
306 qCInfo(lcIcc, "Invalid ICC profile connection space 0x%x", quint32(header.pcs));
307 return false;
308 }
309
310 QColorVector illuminant;
311 illuminant.x = fromFixedS1516(header.illuminantXyz[0]);
312 illuminant.y = fromFixedS1516(header.illuminantXyz[1]);
313 illuminant.z = fromFixedS1516(header.illuminantXyz[2]);
314 if (illuminant != QColorVector::D50()) {
315 qCWarning(lcIcc, "Invalid ICC illuminant");
316 return false;
317 }
318
319 return true;
320}
321
323{
324 if (trc.isIdentity()) {
325 stream << uint(Tag::curv) << uint(0);
326 stream << uint(0);
327 return 12;
328 }
329
330 if (trc.m_type == QColorTrc::Type::Function) {
331 const QColorTransferFunction &fun = trc.m_fun;
332 stream << uint(Tag::para) << uint(0);
333 if (fun.isGamma()) {
334 stream << ushort(0) << ushort(0);
335 stream << toFixedS1516(fun.m_g);
336 return 12 + 4;
337 }
338 bool type3 = qFuzzyIsNull(fun.m_e) && qFuzzyIsNull(fun.m_f);
339 stream << ushort(type3 ? 3 : 4) << ushort(0);
340 stream << toFixedS1516(fun.m_g);
341 stream << toFixedS1516(fun.m_a);
342 stream << toFixedS1516(fun.m_b);
343 stream << toFixedS1516(fun.m_c);
344 stream << toFixedS1516(fun.m_d);
345 if (type3)
346 return 12 + 5 * 4;
347 stream << toFixedS1516(fun.m_e);
348 stream << toFixedS1516(fun.m_f);
349 return 12 + 7 * 4;
350 }
351
352 Q_ASSERT(trc.m_type == QColorTrc::Type::Table);
353 stream << uint(Tag::curv) << uint(0);
355 if (!trc.m_table.m_table16.isEmpty()) {
356 for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
358 }
359 } else {
360 for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
361 stream << ushort(trc.m_table.m_table8[i] * 257U);
362 }
363 }
364 if (trc.m_table.m_tableSize & 1) {
365 stream << ushort(0);
366 return 12 + 2 * trc.m_table.m_tableSize + 2;
367 }
368 return 12 + 2 * trc.m_table.m_tableSize;
369}
370
372{
373 if (!space.isValid())
374 return QByteArray();
375
376 const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
377 // This should catch anything not three component matrix based as we can only get that from parsed ICC
378 if (!spaceDPtr->iccProfile.isEmpty())
379 return spaceDPtr->iccProfile;
380 Q_ASSERT(spaceDPtr->isThreeComponentMatrix());
381
382 int fixedLengthTagCount = 5;
383 bool writeChad = false;
384 if (!spaceDPtr->whitePoint.isNull() && spaceDPtr->whitePoint != QColorVector::D50()) {
385 writeChad = true;
386 fixedLengthTagCount++;
387 }
388
389 const int tagCount = fixedLengthTagCount + 4;
390 const uint profileDataOffset = 128 + 4 + 12 * tagCount;
391 const uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
392 uint currentOffset = 0;
393 uint rTrcOffset, gTrcOffset, bTrcOffset;
394 uint rTrcSize, gTrcSize, bTrcSize;
395 uint descOffset, descSize;
396
400
401 // Profile header:
402 stream << uint(0); // Size, we will update this later
403 stream << uint(0);
404 stream << uint(0x04400000); // Version 4.4
406 stream << uint(Tag::RGB_);
407 stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
408 stream << uint(0) << uint(0) << uint(0);
409 stream << uint(Tag::acsp);
410 stream << uint(0) << uint(0) << uint(0);
411 stream << uint(0) << uint(0) << uint(0);
412 stream << uint(1); // Rendering intent
413 stream << uint(0x0000f6d6); // D50 X
414 stream << uint(0x00010000); // D50 Y
415 stream << uint(0x0000d32d); // D50 Z
416 stream << IccTag('Q','t', QT_VERSION_MAJOR, QT_VERSION_MINOR);
417 stream << uint(0) << uint(0) << uint(0) << uint(0);
418 stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
419
420 // Tag table:
421 currentOffset = profileDataOffset;
422 stream << uint(tagCount);
423 stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
424 stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
425 stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
426 stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
427 stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(34);
428 currentOffset += 20 + 20 + 20 + 20 + 34 + 2;
429 if (writeChad) {
430 stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
431 currentOffset += 44;
432 }
433 // From here the offset and size will be updated later:
434 stream << uint(Tag::rTRC) << uint(0) << uint(0);
435 stream << uint(Tag::gTRC) << uint(0) << uint(0);
436 stream << uint(Tag::bTRC) << uint(0) << uint(0);
437 stream << uint(Tag::desc) << uint(0) << uint(0);
438
439 // Tag data:
440 stream << uint(Tag::XYZ_) << uint(0);
441 stream << toFixedS1516(spaceDPtr->toXyz.r.x);
442 stream << toFixedS1516(spaceDPtr->toXyz.r.y);
443 stream << toFixedS1516(spaceDPtr->toXyz.r.z);
444 stream << uint(Tag::XYZ_) << uint(0);
445 stream << toFixedS1516(spaceDPtr->toXyz.g.x);
446 stream << toFixedS1516(spaceDPtr->toXyz.g.y);
447 stream << toFixedS1516(spaceDPtr->toXyz.g.z);
448 stream << uint(Tag::XYZ_) << uint(0);
449 stream << toFixedS1516(spaceDPtr->toXyz.b.x);
450 stream << toFixedS1516(spaceDPtr->toXyz.b.y);
451 stream << toFixedS1516(spaceDPtr->toXyz.b.z);
452 stream << uint(Tag::XYZ_) << uint(0);
453 stream << toFixedS1516(spaceDPtr->whitePoint.x);
454 stream << toFixedS1516(spaceDPtr->whitePoint.y);
455 stream << toFixedS1516(spaceDPtr->whitePoint.z);
456 stream << uint(Tag::mluc) << uint(0);
457 stream << uint(1) << uint(12);
458 stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
459 stream << uint(6) << uint(28);
460 stream << ushort('N') << ushort('/') << ushort('A');
461 stream << ushort(0); // 4-byte alignment
462 if (writeChad) {
463 QColorMatrix chad = QColorMatrix::chromaticAdaptation(spaceDPtr->whitePoint);
464 stream << uint(Tag::sf32) << uint(0);
465 stream << toFixedS1516(chad.r.x);
466 stream << toFixedS1516(chad.g.x);
467 stream << toFixedS1516(chad.b.x);
468 stream << toFixedS1516(chad.r.y);
469 stream << toFixedS1516(chad.g.y);
470 stream << toFixedS1516(chad.b.y);
471 stream << toFixedS1516(chad.r.z);
472 stream << toFixedS1516(chad.g.z);
473 stream << toFixedS1516(chad.b.z);
474 }
475
476 // From now on the data is variable sized:
477 rTrcOffset = currentOffset;
478 rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
479 currentOffset += rTrcSize;
480 if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
481 gTrcOffset = rTrcOffset;
482 gTrcSize = rTrcSize;
483 } else {
484 gTrcOffset = currentOffset;
485 gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
486 currentOffset += gTrcSize;
487 }
488 if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
489 bTrcOffset = rTrcOffset;
490 bTrcSize = rTrcSize;
491 } else {
492 bTrcOffset = currentOffset;
493 bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
494 currentOffset += bTrcSize;
495 }
496
497 // Writing description
498 descOffset = currentOffset;
499 const QString description = space.description();
500 stream << uint(Tag::mluc) << uint(0);
501 stream << uint(1) << uint(12);
502 stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
503 stream << uint(description.size() * 2) << uint(28);
504 for (QChar ch : description)
505 stream << ushort(ch.unicode());
506 descSize = 28 + description.size() * 2;
507 if (description.size() & 1) {
508 stream << ushort(0);
509 currentOffset += 2;
510 }
511 currentOffset += descSize;
512
513 buffer.close();
514 QByteArray iccProfile = buffer.buffer();
515 // Now write final size
516 *(quint32_be *)iccProfile.data() = iccProfile.size();
517 // And the final indices and sizes of variable size tags:
518 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
519 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
520 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
521 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
522 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
523 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
524 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
525 *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
526
527#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
528 const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
529 Q_ASSERT(qsizetype(iccHeader->profileSize) == qsizetype(iccProfile.size()));
530 Q_ASSERT(isValidIccProfile(*iccHeader));
531#endif
532
533 return iccProfile;
534}
535
540
541static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
542{
543 if (tagEntry.size < sizeof(XYZTagData)) {
544 qCWarning(lcIcc) << "Undersized XYZ tag";
545 return false;
546 }
547 const XYZTagData xyz = qFromUnaligned<XYZTagData>(data.constData() + tagEntry.offset);
548 if (xyz.type != quint32(Tag::XYZ_)) {
549 qCWarning(lcIcc) << "Bad XYZ content type";
550 return false;
551 }
552 const float x = fromFixedS1516(xyz.fixedX);
553 const float y = fromFixedS1516(xyz.fixedY);
554 const float z = fromFixedS1516(xyz.fixedZ);
555
556 colorVector = QColorVector(x, y, z);
557 return true;
558}
559
561{
562 if (tagData.size() < 12)
563 return 0;
564 const GenericTagData trcData = qFromUnaligned<GenericTagData>(tagData.constData());
565 if (trcData.type == quint32(Tag::curv)) {
566 Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
567 const CurvTagData curv = qFromUnaligned<CurvTagData>(tagData.constData());
568 if (curv.valueCount > (1 << 16))
569 return 0;
570 if (tagData.size() < qsizetype(12 + 2 * curv.valueCount))
571 return 0;
572 const auto valueOffset = sizeof(CurvTagData);
573 if (curv.valueCount == 0) {
574 gamma.m_type = QColorTrc::Type::Function;
575 gamma.m_fun = QColorTransferFunction(); // Linear
576 } else if (curv.valueCount == 1) {
577 const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
578 gamma.m_type = QColorTrc::Type::Function;
579 gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f));
580 } else {
581 QList<quint16> tabl;
582 tabl.resize(curv.valueCount);
583 static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
584 "GenericTagData has padding. The following code is a subject to UB.");
585 qFromBigEndian<quint16>(tagData.constData() + valueOffset, curv.valueCount, tabl.data());
586 QColorTransferTable table(curv.valueCount, tabl, type);
588 if (!table.checkValidity()) {
589 qCWarning(lcIcc) << "Invalid curv table";
590 return 0;
591 } else if (!table.asColorTransferFunction(&curve)) {
592 gamma.m_type = QColorTrc::Type::Table;
593 gamma.m_table = table;
594 } else {
595 qCDebug(lcIcc) << "Detected curv table as function";
596 gamma.m_type = QColorTrc::Type::Function;
597 gamma.m_fun = curve;
598 }
599 }
600 return 12 + 2 * curv.valueCount;
601 }
602 if (trcData.type == quint32(Tag::para)) {
603 Q_STATIC_ASSERT(sizeof(ParaTagData) == 12);
604 const ParaTagData para = qFromUnaligned<ParaTagData>(tagData.constData());
605 const auto parametersOffset = sizeof(ParaTagData);
606 quint32 parameters[7];
607 switch (para.curveType) {
608 case 0: {
609 if (tagData.size() < 12 + 1 * 4)
610 return 0;
611 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
612 float g = fromFixedS1516(parameters[0]);
613 gamma.m_type = QColorTrc::Type::Function;
615 return 12 + 1 * 4;
616 }
617 case 1: {
618 if (tagData.size() < 12 + 3 * 4)
619 return 0;
620 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 3, parameters);
621 if (parameters[1] == 0)
622 return 0;
623 float g = fromFixedS1516(parameters[0]);
624 float a = fromFixedS1516(parameters[1]);
625 float b = fromFixedS1516(parameters[2]);
626 float d = -b / a;
627 gamma.m_type = QColorTrc::Type::Function;
628 gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
629 return 12 + 3 * 4;
630 }
631 case 2: {
632 if (tagData.size() < 12 + 4 * 4)
633 return 0;
634 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 4, parameters);
635 if (parameters[1] == 0)
636 return 0;
637 float g = fromFixedS1516(parameters[0]);
638 float a = fromFixedS1516(parameters[1]);
639 float b = fromFixedS1516(parameters[2]);
640 float c = fromFixedS1516(parameters[3]);
641 float d = -b / a;
642 gamma.m_type = QColorTrc::Type::Function;
643 gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
644 return 12 + 4 * 4;
645 }
646 case 3: {
647 if (tagData.size() < 12 + 5 * 4)
648 return 0;
649 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 5, parameters);
650 float g = fromFixedS1516(parameters[0]);
651 float a = fromFixedS1516(parameters[1]);
652 float b = fromFixedS1516(parameters[2]);
653 float c = fromFixedS1516(parameters[3]);
654 float d = fromFixedS1516(parameters[4]);
655 gamma.m_type = QColorTrc::Type::Function;
656 gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
657 return 12 + 5 * 4;
658 }
659 case 4: {
660 if (tagData.size() < 12 + 7 * 4)
661 return 0;
662 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 7, parameters);
663 float g = fromFixedS1516(parameters[0]);
664 float a = fromFixedS1516(parameters[1]);
665 float b = fromFixedS1516(parameters[2]);
666 float c = fromFixedS1516(parameters[3]);
667 float d = fromFixedS1516(parameters[4]);
668 float e = fromFixedS1516(parameters[5]);
669 float f = fromFixedS1516(parameters[6]);
670 gamma.m_type = QColorTrc::Type::Function;
671 gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
672 return 12 + 7 * 4;
673 }
674 default:
675 qCWarning(lcIcc) << "Unknown para type" << uint(para.curveType);
676 return 0;
677 }
678 return true;
679 }
680 qCWarning(lcIcc) << "Invalid TRC data type" << Qt::hex << trcData.type;
681 return 0;
682}
683
684template<typename T>
685static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels)
686{
687 if (outputChannels == 4) {
688 for (qsizetype index = 0; index < clut->table.size(); ++index) {
689 QColorVector v(tableData[index * 4 + 0] * f,
690 tableData[index * 4 + 1] * f,
691 tableData[index * 4 + 2] * f,
692 tableData[index * 4 + 3] * f);
693 clut->table[index] = v;
694 };
695 } else {
696 for (qsizetype index = 0; index < clut->table.size(); ++index) {
697 QColorVector v(tableData[index * 3 + 0] * f,
698 tableData[index * 3 + 1] * f,
699 tableData[index * 3 + 2] * f);
700 clut->table[index] = v;
701 };
702 }
703}
704
705// very simple version for small values (<=4) of exp.
706static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
707{
708 return (exp <= 1) ? x : x * intPow(x, exp - 1);
709}
710
711// Parses lut8 and lut16 type elements
712template<typename T>
713static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
714{
715 if (tagEntry.size < sizeof(T)) {
716 qCWarning(lcIcc) << "Undersized lut8/lut16 tag";
717 return false;
718 }
719 if (qsizetype(tagEntry.size) > data.size()) {
720 qCWarning(lcIcc) << "Truncated lut8/lut16 tag";
721 return false;
722 }
723 using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
724 const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
725 int inputTableEntries, outputTableEntries, precision;
726 if constexpr (std::is_same_v<T, Lut8TagData>) {
727 Q_ASSERT(lut.type == quint32(Tag::mft1));
728 if (!colorSpacePrivate->isPcsLab && isAb) {
729 qCWarning(lcIcc) << "Lut8 can not output XYZ values";
730 return false;
731 }
732 inputTableEntries = 256;
733 outputTableEntries = 256;
734 precision = 1;
735 } else {
736 Q_ASSERT(lut.type == quint32(Tag::mft2));
737 inputTableEntries = lut.inputTableEntries;
738 outputTableEntries = lut.outputTableEntries;
739 if (inputTableEntries < 2 || inputTableEntries > 4096)
740 return false;
741 if (outputTableEntries < 2 || outputTableEntries > 4096)
742 return false;
743 precision = 2;
744 }
745
746 bool inTableIsLinear = true, outTableIsLinear = true;
749 QColorCLUT clutElement;
750 QColorMatrix matrixElement;
751
752 matrixElement.r.x = fromFixedS1516(lut.e1);
753 matrixElement.g.x = fromFixedS1516(lut.e2);
754 matrixElement.b.x = fromFixedS1516(lut.e3);
755 matrixElement.r.y = fromFixedS1516(lut.e4);
756 matrixElement.g.y = fromFixedS1516(lut.e5);
757 matrixElement.b.y = fromFixedS1516(lut.e6);
758 matrixElement.r.z = fromFixedS1516(lut.e7);
759 matrixElement.g.z = fromFixedS1516(lut.e8);
760 matrixElement.b.z = fromFixedS1516(lut.e9);
761 if (!colorSpacePrivate->isPcsLab && !isAb && !matrixElement.isValid()) {
762 qCWarning(lcIcc) << "Invalid matrix values in lut8/lut16";
763 return false;
764 }
765
766 if (lut.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.inputChannels == 4)) {
767 qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
768 return false;
769 }
770
771 if (lut.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.outputChannels == 4)) {
772 qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
773 return false;
774 }
775
776 const qsizetype clutTableSize = intPow(lut.clutGridPoints, lut.inputChannels);
777 if (tagEntry.size < (sizeof(T) + precision * lut.inputChannels * inputTableEntries
778 + precision * lut.outputChannels * outputTableEntries
779 + precision * lut.outputChannels * clutTableSize)) {
780 qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
781 return false;
782 }
783 if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
784 qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
785 return false;
786 }
787
788 const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
789
790 for (int j = 0; j < lut.inputChannels; ++j) {
791 QList<S> input(inputTableEntries);
792 qFromBigEndian<S>(tableData, inputTableEntries, input.data());
794 if (!table.checkValidity()) {
795 qCWarning(lcIcc) << "Bad input table in lut8/lut16";
796 return false;
797 }
798 if (!table.isIdentity())
799 inTableIsLinear = false;
800 inTableElement.trc[j] = std::move(table);
801 tableData += inputTableEntries * precision;
802 }
803
804 clutElement.table.resize(clutTableSize);
805 clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
806 if (lut.inputChannels == 4)
807 clutElement.gridPointsW = lut.clutGridPoints;
808
809 if constexpr (std::is_same_v<T, Lut8TagData>) {
810 parseCLUT(tableData, 1.f / 255.f, &clutElement, lut.outputChannels);
811 } else {
812 float f = 1.0f / 65535.f;
813 if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
814 f = 1.0f / 65280.f;
815 QList<S> clutTable(clutTableSize * lut.outputChannels);
816 qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
817 parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels);
818 }
819 tableData += clutTableSize * lut.outputChannels * precision;
820
821 for (int j = 0; j < lut.outputChannels; ++j) {
822 QList<S> output(outputTableEntries);
823 qFromBigEndian<S>(tableData, outputTableEntries, output.data());
825 if (!table.checkValidity()) {
826 qCWarning(lcIcc) << "Bad output table in lut8/lut16";
827 return false;
828 }
829 if (!table.isIdentity())
830 outTableIsLinear = false;
831 outTableElement.trc[j] = std::move(table);
832 tableData += outputTableEntries * precision;
833 }
834
835 if (isAb) {
836 if (!inTableIsLinear)
837 colorSpacePrivate->mAB.append(inTableElement);
838 if (!clutElement.isEmpty())
839 colorSpacePrivate->mAB.append(clutElement);
840 if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
841 colorSpacePrivate->mAB.append(outTableElement);
842 } else {
843 // The matrix is only to be applied if the input color-space is XYZ
844 if (!colorSpacePrivate->isPcsLab && !matrixElement.isIdentity())
845 colorSpacePrivate->mBA.append(matrixElement);
846 if (!inTableIsLinear)
847 colorSpacePrivate->mBA.append(inTableElement);
848 if (!clutElement.isEmpty())
849 colorSpacePrivate->mBA.append(clutElement);
850 if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
851 colorSpacePrivate->mBA.append(outTableElement);
852 }
853 return true;
854}
855
856// Parses mAB and mBA type elements
857static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
858{
859 if (tagEntry.size < sizeof(mABTagData)) {
860 qCWarning(lcIcc) << "Undersized mAB/mBA tag";
861 return false;
862 }
863 if (qsizetype(tagEntry.size) > data.size()) {
864 qCWarning(lcIcc) << "Truncated mAB/mBA tag";
865 return false;
866 }
867 const mABTagData mab = qFromUnaligned<mABTagData>(data.constData() + tagEntry.offset);
868 if ((mab.type != quint32(Tag::mAB_) && isAb) || (mab.type != quint32(Tag::mBA_) && !isAb)){
869 qCWarning(lcIcc) << "Bad mAB/mBA content type";
870 return false;
871 }
872
873 if (mab.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.inputChannels == 4)) {
874 qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
875 return false;
876 }
877
878 if (mab.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.outputChannels == 4)) {
879 qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
880 return false;
881 }
882
883 // These combinations are legal: B, M + Matrix + B, A + Clut + B, A + Clut + M + Matrix + B
884 if (!mab.bCurvesOffset) {
885 qCWarning(lcIcc) << "Illegal mAB/mBA without B table";
886 return false;
887 }
888 if (((bool)mab.matrixOffset != (bool)mab.mCurvesOffset) ||
889 ((bool)mab.aCurvesOffset != (bool)mab.clutOffset)) {
890 qCWarning(lcIcc) << "Illegal mAB/mBA element combination";
891 return false;
892 }
893
894 if (mab.aCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
895 mab.bCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
896 mab.mCurvesOffset > (tagEntry.size - 3 * sizeof(GenericTagData)) ||
897 mab.matrixOffset > (tagEntry.size - 4 * 12) ||
898 mab.clutOffset > (tagEntry.size - 20)) {
899 qCWarning(lcIcc) << "Illegal mAB/mBA element offset";
900 return false;
901 }
902
905 QColorCLUT clutElement;
907 QColorMatrix matrixElement;
908 QColorVector offsetElement;
909
910 auto parseCurves = [&data, &tagEntry] (uint curvesOffset, QColorTrc *table, int channels) {
911 for (int i = 0; i < channels; ++i) {
912 if (qsizetype(tagEntry.offset + curvesOffset + 12) > data.size() || curvesOffset + 12 > tagEntry.size) {
913 qCWarning(lcIcc) << "Space missing for channel curves in mAB/mBA";
914 return false;
915 }
916 auto size = parseTRC(QByteArrayView(data).sliced(tagEntry.offset + curvesOffset, tagEntry.size - curvesOffset), table[i], QColorTransferTable::OneWay);
917 if (!size)
918 return false;
919 if (size & 2) size += 2; // possible padding
920 curvesOffset += size;
921 }
922 return true;
923 };
924
925 bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
926
927 // B Curves
928 if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? mab.outputChannels : mab.inputChannels)) {
929 qCWarning(lcIcc) << "Invalid B curves";
930 return false;
931 } else {
932 bCurvesAreLinear = bTableElement.trc[0].isIdentity() && bTableElement.trc[1].isIdentity() && bTableElement.trc[2].isIdentity();
933 }
934
935 // A Curves
936 if (mab.aCurvesOffset) {
937 if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? mab.inputChannels : mab.outputChannels)) {
938 qCWarning(lcIcc) << "Invalid A curves";
939 return false;
940 } else {
941 aCurvesAreLinear = aTableElement.trc[0].isIdentity() && aTableElement.trc[1].isIdentity() && aTableElement.trc[2].isIdentity();
942 }
943 }
944
945 // M Curves
946 if (mab.mCurvesOffset) {
947 if (!parseCurves(mab.mCurvesOffset, mTableElement.trc, 3)) {
948 qCWarning(lcIcc) << "Invalid M curves";
949 return false;
950 } else {
951 mCurvesAreLinear = mTableElement.trc[0].isIdentity() && mTableElement.trc[1].isIdentity() && mTableElement.trc[2].isIdentity();
952 }
953 }
954
955 // Matrix
956 if (mab.matrixOffset) {
957 const MatrixElement matrix = qFromUnaligned<MatrixElement>(data.constData() + tagEntry.offset + mab.matrixOffset);
958 matrixElement.r.x = fromFixedS1516(matrix.e0);
959 matrixElement.g.x = fromFixedS1516(matrix.e1);
960 matrixElement.b.x = fromFixedS1516(matrix.e2);
961 matrixElement.r.y = fromFixedS1516(matrix.e3);
962 matrixElement.g.y = fromFixedS1516(matrix.e4);
963 matrixElement.b.y = fromFixedS1516(matrix.e5);
964 matrixElement.r.z = fromFixedS1516(matrix.e6);
965 matrixElement.g.z = fromFixedS1516(matrix.e7);
966 matrixElement.b.z = fromFixedS1516(matrix.e8);
967 offsetElement.x = fromFixedS1516(matrix.e9);
968 offsetElement.y = fromFixedS1516(matrix.e10);
969 offsetElement.z = fromFixedS1516(matrix.e11);
970 if (!matrixElement.isValid() || !offsetElement.isValid()) {
971 qCWarning(lcIcc) << "Invalid matrix values in mAB/mBA element";
972 return false;
973 }
974 }
975
976 // CLUT
977 if (mab.clutOffset) {
978 clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab.clutOffset]);
979 clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab.clutOffset + 1]);
980 clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab.clutOffset + 2]);
981 clutElement.gridPointsW = std::max(uint8_t(data[tagEntry.offset + mab.clutOffset + 3]), uint8_t(1));
982 const uchar precision = data[tagEntry.offset + mab.clutOffset + 16];
983 if (precision > 2 || precision < 1) {
984 qCWarning(lcIcc) << "Invalid mAB/mBA element CLUT precision";
985 return false;
986 }
987 if (clutElement.gridPointsX < 2 || clutElement.gridPointsY < 2 || clutElement.gridPointsZ < 2) {
988 qCWarning(lcIcc) << "Empty CLUT";
989 return false;
990 }
991 const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
992 if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
993 qCWarning(lcIcc) << "CLUT oversized for tag";
994 return false;
995 }
996
997 clutElement.table.resize(clutTableSize);
998 if (precision == 2) {
999 QList<uint16_t> clutTable(clutTableSize * mab.outputChannels);
1000 qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
1001 parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, mab.outputChannels);
1002 } else {
1003 const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
1004 parseCLUT(clutTable, (1.f/255.f), &clutElement, mab.outputChannels);
1005 }
1006 } else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
1007 qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
1008 return false;
1009 }
1010
1011 if (isAb) {
1012 if (mab.aCurvesOffset) {
1013 if (!aCurvesAreLinear)
1014 colorSpacePrivate->mAB.append(std::move(aTableElement));
1015 if (!clutElement.isEmpty())
1016 colorSpacePrivate->mAB.append(std::move(clutElement));
1017 }
1018 if (mab.mCurvesOffset && mab.outputChannels == 3) {
1019 if (!mCurvesAreLinear)
1020 colorSpacePrivate->mAB.append(std::move(mTableElement));
1021 if (!matrixElement.isIdentity())
1022 colorSpacePrivate->mAB.append(std::move(matrixElement));
1023 if (!offsetElement.isNull())
1024 colorSpacePrivate->mAB.append(std::move(offsetElement));
1025 }
1026 if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
1027 colorSpacePrivate->mAB.append(std::move(bTableElement));
1028 } else {
1029 if (!bCurvesAreLinear)
1030 colorSpacePrivate->mBA.append(std::move(bTableElement));
1031 if (mab.mCurvesOffset && mab.inputChannels == 3) {
1032 if (!matrixElement.isIdentity())
1033 colorSpacePrivate->mBA.append(std::move(matrixElement));
1034 if (!offsetElement.isNull())
1035 colorSpacePrivate->mBA.append(std::move(offsetElement));
1036 if (!mCurvesAreLinear)
1037 colorSpacePrivate->mBA.append(std::move(mTableElement));
1038 }
1039 if (mab.aCurvesOffset) {
1040 if (!clutElement.isEmpty())
1041 colorSpacePrivate->mBA.append(std::move(clutElement));
1042 if (!aCurvesAreLinear)
1043 colorSpacePrivate->mBA.append(std::move(aTableElement));
1044 }
1045 if (colorSpacePrivate->mBA.isEmpty()) // Ensure non-empty to indicate valid empty transform
1046 colorSpacePrivate->mBA.append(std::move(bTableElement));
1047 }
1048
1049 return true;
1050}
1051
1052static bool parseA2B(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *privat, bool isAb)
1053{
1054 const GenericTagData a2bData = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
1055 if (a2bData.type == quint32(Tag::mft1))
1056 return parseLutData<Lut8TagData>(data, tagEntry, privat, isAb);
1057 else if (a2bData.type == quint32(Tag::mft2))
1058 return parseLutData<Lut16TagData>(data, tagEntry, privat, isAb);
1059 else if (a2bData.type == quint32(Tag::mAB_) || a2bData.type == quint32(Tag::mBA_))
1060 return parseMabData(data, tagEntry, privat, isAb);
1061
1062 qCWarning(lcIcc) << "fromIccProfile: Unknown A2B/B2A data type";
1063 return false;
1064}
1065
1066static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
1067{
1068 const GenericTagData tag = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
1069
1070 // Either 'desc' (ICCv2) or 'mluc' (ICCv4)
1071 if (tag.type == quint32(Tag::desc)) {
1072 if (tagEntry.size < sizeof(DescTagData))
1073 return false;
1074 Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
1075 const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
1076 const quint32 len = desc.asciiDescriptionLength;
1077 if (len < 1)
1078 return false;
1079 if (tagEntry.size - 12 < len)
1080 return false;
1081 const char *asciiDescription = data.constData() + tagEntry.offset + sizeof(DescTagData);
1082 if (asciiDescription[len - 1] != '\0')
1083 return false;
1084 descName = QString::fromLatin1(asciiDescription, len - 1);
1085 return true;
1086 }
1087 if (tag.type != quint32(Tag::mluc))
1088 return false;
1089
1090 if (tagEntry.size < sizeof(MlucTagData))
1091 return false;
1092 const MlucTagData mluc = qFromUnaligned<MlucTagData>(data.constData() + tagEntry.offset);
1093 if (mluc.recordCount < 1)
1094 return false;
1095 if (mluc.recordSize != 12)
1096 return false;
1097 // We just use the primary record regardless of language or country.
1098 const quint32 stringOffset = mluc.records[0].offset;
1099 const quint32 stringSize = mluc.records[0].size;
1100 if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
1101 return false;
1102 if ((stringSize | stringOffset) & 1)
1103 return false;
1104 quint32 stringLen = stringSize / 2;
1105 QVarLengthArray<char16_t> utf16hostendian(stringLen);
1106 qFromBigEndian<char16_t>(data.constData() + tagEntry.offset + stringOffset, stringLen,
1107 utf16hostendian.data());
1108 // The given length shouldn't include 0-termination, but might.
1109 if (stringLen > 1 && utf16hostendian[stringLen - 1] == 0)
1110 --stringLen;
1111 descName = QString::fromUtf16(utf16hostendian.data(), stringLen);
1112 return true;
1113}
1114
1115static bool parseRgbMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
1116{
1117 // Parse XYZ tags
1118 if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
1119 return false;
1120 if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
1121 return false;
1122 if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
1123 return false;
1124 if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
1125 return false;
1126 if (!colorspaceDPtr->toXyz.isValid() || !colorspaceDPtr->whitePoint.isValid() || colorspaceDPtr->whitePoint.isNull()) {
1127 qCWarning(lcIcc) << "Invalid XYZ values in RGB matrix";
1128 return false;
1129 }
1130
1131 colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
1132 if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
1133 qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
1134 colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
1135 } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
1136 qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
1137 colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
1138 } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
1139 qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
1140 colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
1141 }
1142 if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
1143 qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
1144 colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
1145 }
1146 return true;
1147}
1148
1149static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr)
1150{
1151 QColorVector whitePoint;
1152 if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
1153 return false;
1154 if (!whitePoint.isValid() || !qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z + whitePoint.x) == 0.0f) {
1155 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
1156 return false;
1157 }
1158 colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
1159 colorspaceDPtr->whitePoint = whitePoint;
1160 return true;
1161}
1162
1163static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
1164{
1165 if (tagEntry.size < sizeof(Sf32TagData) || qsizetype(tagEntry.size) > data.size())
1166 return false;
1167 const Sf32TagData chadtag = qFromUnaligned<Sf32TagData>(data.constData() + tagEntry.offset);
1168 if (chadtag.type != uint32_t(Tag::sf32)) {
1169 qCWarning(lcIcc, "fromIccProfile: bad chad data type");
1170 return false;
1171 }
1173 chad.r.x = fromFixedS1516(chadtag.value[0]);
1174 chad.g.x = fromFixedS1516(chadtag.value[1]);
1175 chad.b.x = fromFixedS1516(chadtag.value[2]);
1176 chad.r.y = fromFixedS1516(chadtag.value[3]);
1177 chad.g.y = fromFixedS1516(chadtag.value[4]);
1178 chad.b.y = fromFixedS1516(chadtag.value[5]);
1179 chad.r.z = fromFixedS1516(chadtag.value[6]);
1180 chad.g.z = fromFixedS1516(chadtag.value[7]);
1181 chad.b.z = fromFixedS1516(chadtag.value[8]);
1182
1183 if (!chad.isValid()) {
1184 qCWarning(lcIcc, "fromIccProfile: invalid chad matrix");
1185 return false;
1186 }
1187 colorspaceDPtr->chad = chad;
1188 return true;
1189}
1190
1191static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
1192{
1193 TagEntry rTrc;
1194 TagEntry gTrc;
1195 TagEntry bTrc;
1196 if (isColorSpaceTypeGray) {
1197 rTrc = tagIndex[Tag::kTRC];
1198 gTrc = tagIndex[Tag::kTRC];
1199 bTrc = tagIndex[Tag::kTRC];
1200 } else if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
1201 // Apple extension for parametric version of TRCs in ICCv2:
1202 rTrc = tagIndex[Tag::aarg];
1203 gTrc = tagIndex[Tag::aagg];
1204 bTrc = tagIndex[Tag::aabg];
1205 } else {
1206 rTrc = tagIndex[Tag::rTRC];
1207 gTrc = tagIndex[Tag::gTRC];
1208 bTrc = tagIndex[Tag::bTRC];
1209 }
1210
1211 QColorTrc rCurve;
1212 QColorTrc gCurve;
1213 QColorTrc bCurve;
1214 if (!parseTRC(QByteArrayView(data).sliced(rTrc.offset, rTrc.size), rCurve, QColorTransferTable::TwoWay)) {
1215 qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
1216 return false;
1217 }
1218 if (!parseTRC(QByteArrayView(data).sliced(gTrc.offset, gTrc.size), gCurve, QColorTransferTable::TwoWay)) {
1219 qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
1220 return false;
1221 }
1222 if (!parseTRC(QByteArrayView(data).sliced(bTrc.offset, bTrc.size), bCurve, QColorTransferTable::TwoWay)) {
1223 qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
1224 return false;
1225 }
1226 if (rCurve == gCurve && gCurve == bCurve) {
1227 if (rCurve.isIdentity()) {
1228 qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
1229 colorspaceDPtr->trc[0] = QColorTransferFunction();
1230 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
1231 colorspaceDPtr->gamma = 1.0f;
1232 } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isGamma()) {
1233 qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
1234 colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
1235 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
1236 colorspaceDPtr->gamma = rCurve.m_fun.m_g;
1237 } else if (rCurve.m_type == QColorTrc::Type::Function && rCurve.m_fun.isSRgb()) {
1238 qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
1239 colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
1240 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
1241 } else {
1242 colorspaceDPtr->trc[0] = rCurve;
1243 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1244 }
1245 colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
1246 colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
1247 } else {
1248 colorspaceDPtr->trc[0] = rCurve;
1249 colorspaceDPtr->trc[1] = gCurve;
1250 colorspaceDPtr->trc[2] = bCurve;
1251 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1252 }
1253 return true;
1254}
1255
1257{
1258 if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
1259 qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
1260 return false;
1261 }
1262 const ICCProfileHeader header = qFromUnaligned<ICCProfileHeader>(data.constData());
1264 return false; // if failed we already printing a warning
1265 if (qsizetype(header.profileSize) > data.size() || qsizetype(header.profileSize) < qsizetype(sizeof(ICCProfileHeader))) {
1266 qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
1267 return false;
1268 }
1269
1270 const qsizetype offsetToData = sizeof(ICCProfileHeader) + header.tagCount * sizeof(TagTableEntry);
1271 Q_ASSERT(offsetToData > 0);
1272 if (offsetToData > data.size()) {
1273 qCWarning(lcIcc) << "fromIccProfile: failed index size sanity";
1274 return false;
1275 }
1276
1277 QHash<Tag, TagEntry> tagIndex;
1278 for (uint i = 0; i < header.tagCount; ++i) {
1279 // Read tag index
1280 const qsizetype tableOffset = sizeof(ICCProfileHeader) + i * sizeof(TagTableEntry);
1281 const TagTableEntry tagTable = qFromUnaligned<TagTableEntry>(data.constData()
1282 + tableOffset);
1283
1284 // Sanity check tag sizes and offsets:
1285 if (qsizetype(tagTable.offset) < offsetToData) {
1286 qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
1287 return false;
1288 }
1289 // Checked separately from (+ size) to handle overflow.
1290 if (tagTable.offset > header.profileSize) {
1291 qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
1292 return false;
1293 }
1294 if (tagTable.size < 8) {
1295 qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
1296 return false;
1297 }
1298 if (tagTable.size > header.profileSize - tagTable.offset) {
1299 qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
1300 return false;
1301 }
1302 if (tagTable.offset & 0x03) {
1303 qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment";
1304 return false;
1305 }
1306// printf("'%4s' %d %d\n", (const char *)&tagTable.signature,
1307// quint32(tagTable.offset),
1308// quint32(tagTable.size));
1309 tagIndex.insert(Tag(quint32(tagTable.signature)), { tagTable.offset, tagTable.size });
1310 }
1311
1312 bool threeComponentMatrix = true;
1313
1314 if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
1315 // Check the profile is three-component matrix based:
1316 if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
1317 !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
1318 !tagIndex.contains(Tag::wtpt) || header.pcs == uint(Tag::Lab_)) {
1319 threeComponentMatrix = false;
1320 // Check if the profile is valid n-LUT based:
1321 if (!tagIndex.contains(Tag::A2B0)) {
1322 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
1323 return false;
1324 }
1325 }
1326 } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
1327 if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
1328 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
1329 return false;
1330 }
1331 } else if (header.inputColorSpace == uint(ColorSpaceType::Cmyk)) {
1332 threeComponentMatrix = false;
1333 if (!tagIndex.contains(Tag::A2B0)) {
1334 qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
1335 return false;
1336 }
1337 } else {
1338 Q_UNREACHABLE();
1339 }
1340
1341 colorSpace->detach();
1342 QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(*colorSpace);
1343
1344 if (threeComponentMatrix) {
1345 colorspaceDPtr->isPcsLab = false;
1346 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
1347
1348 if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
1349 if (!parseRgbMatrix(data, tagIndex, colorspaceDPtr))
1350 return false;
1351 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1352 } else if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
1353 if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
1354 return false;
1355 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
1356 } else {
1357 Q_UNREACHABLE();
1358 }
1359 if (auto it = tagIndex.constFind(Tag::chad); it != tagIndex.constEnd()) {
1360 if (!parseChad(data, it.value(), colorspaceDPtr))
1361 return false;
1362 } else {
1363 colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
1364 }
1365 if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
1366 colorspaceDPtr->toXyz = colorspaceDPtr->chad;
1367
1368 // Reset the matrix to our canonical values:
1369 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1370 colorspaceDPtr->setToXyzMatrix();
1371
1372 if (!parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
1373 return false;
1374 } else {
1375 colorspaceDPtr->isPcsLab = (header.pcs == uint(Tag::Lab_));
1376 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
1377 if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
1378 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
1379 else
1380 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1381
1382 // Only parse the default perceptual transform for now
1383 if (!parseA2B(data, tagIndex[Tag::A2B0], colorspaceDPtr, true))
1384 return false;
1385 if (auto it = tagIndex.constFind(Tag::B2A0); it != tagIndex.constEnd()) {
1386 if (!parseA2B(data, it.value(), colorspaceDPtr, false))
1387 return false;
1388 }
1389
1390 if (auto it = tagIndex.constFind(Tag::wtpt); it != tagIndex.constEnd()) {
1391 if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
1392 return false;
1393 }
1394 }
1395
1396 if (auto it = tagIndex.constFind(Tag::desc); it != tagIndex.constEnd()) {
1397 if (!parseDesc(data, it.value(), colorspaceDPtr->description))
1398 qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
1399 else
1400 qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
1401 }
1402
1403 colorspaceDPtr->identifyColorSpace();
1404 if (colorspaceDPtr->namedColorSpace)
1405 qCDebug(lcIcc) << "fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace);
1406
1407 colorspaceDPtr->iccProfile = data;
1408
1409 Q_ASSERT(colorspaceDPtr->isValid());
1410 return true;
1411}
1412
1413} // namespace QIcc
1414
\inmodule QtCore \reentrant
Definition qbuffer.h:16
bool open(OpenMode openMode) override
\reimp
Definition qbuffer.cpp:295
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
\inmodule QtCore
static QColorMatrix toXyzFromSRgb()
static QColorMatrix toXyzFromAdobeRgb()
QColorVector r
static QColorMatrix toXyzFromDciP3D65()
static QColorMatrix chromaticAdaptation(const QColorVector &whitePoint)
static QColorMatrix toXyzFromProPhotoRgb()
static const QColorSpacePrivate * get(const QColorSpace &colorSpace)
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:90
Type m_type
Definition qcolortrc_p.h:89
bool isIdentity() const
Definition qcolortrc_p.h:40
QColorTransferTable m_table
Definition qcolortrc_p.h:91
static constexpr QColorVector D50()
bool isValid() const noexcept
\inmodule QtCore\reentrant
Definition qdatastream.h:46
bool isEmpty() const noexcept
Definition qlist.h:401
const_iterator constEnd() const noexcept
Definition qset.h:143
const_iterator constFind(const T &value) const
Definition qset.h:161
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition qstring.cpp:6045
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
QString text
QSet< QString >::iterator it
Definition qicc.cpp:25
static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
Definition qicc.cpp:706
ColorSpaceType
Definition qicc.cpp:62
static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
Definition qicc.cpp:1163
static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
Definition qicc.cpp:857
static bool parseGrayMatrix(const QByteArray &data, const QHash< Tag, TagEntry > &tagIndex, QColorSpacePrivate *colorspaceDPtr)
Definition qicc.cpp:1149
static bool parseRgbMatrix(const QByteArray &data, const QHash< Tag, TagEntry > &tagIndex, QColorSpacePrivate *colorspaceDPtr)
Definition qicc.cpp:1115
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
Definition qicc.cpp:1256
constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
Definition qicc.cpp:57
static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar outputChannels)
Definition qicc.cpp:685
static bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
Definition qicc.cpp:541
QByteArray toIccProfile(const QColorSpace &space)
Definition qicc.cpp:371
Tag
Definition qicc.cpp:79
static int toFixedS1516(float x)
Definition qicc.cpp:265
static bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
Definition qicc.cpp:1066
static bool isValidIccProfile(const ICCProfileHeader &header)
Definition qicc.cpp:275
static bool parseTRCs(const QByteArray &data, const QHash< Tag, TagEntry > &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
Definition qicc.cpp:1191
static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
Definition qicc.cpp:713
static bool parseA2B(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *privat, bool isAb)
Definition qicc.cpp:1052
static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorTransferTable::Type type=QColorTransferTable::TwoWay)
Definition qicc.cpp:560
static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
Definition qicc.cpp:322
static float fromFixedS1516(int x)
Definition qicc.cpp:270
ProfileClass
Definition qicc.cpp:68
Combined button and popup list for selecting options.
QTextStream & hex(QTextStream &stream)
Calls QTextStream::setIntegerBase(16) on stream and returns stream.
#define Q_STATIC_ASSERT(Condition)
Definition qassert.h:108
AudioChannelLayoutTag tag
static QString header(const QString &name)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition qfloat16.h:333
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition qfloat16.h:349
quint32 Tag
size_t qHash(const QIcc::Tag &key, size_t seed=0)
Definition qicc.cpp:130
@ 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]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLfloat GLfloat f
GLenum GLuint buffer
GLenum type
GLboolean GLboolean g
GLint y
const GLubyte * c
GLuint GLenum matrix
GLenum GLsizei len
GLenum GLenum GLenum input
GLenum GLenum GLsizei void * table
GLenum GLint GLint * precision
static Q_CONSTINIT QBasicAtomicInteger< unsigned > seed
Definition qrandom.cpp:196
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
struct _XDisplay Display
unsigned int quint32
Definition qtypes.h:50
unsigned char uchar
Definition qtypes.h:32
unsigned short quint16
Definition qtypes.h:48
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
unsigned short ushort
Definition qtypes.h:33
unsigned char quint8
Definition qtypes.h:46
QT_BEGIN_NAMESPACE typedef uchar * output
QJSValue fun
[0]
quint32_be valueCount
Definition qicc.cpp:156
quint32_be asciiDescriptionLength
Definition qicc.cpp:167
quint32_be type
Definition qicc.cpp:145
quint32_be null
Definition qicc.cpp:146
quint32_be deviceManufacturer
Definition qicc.cpp:41
quint32_be tagCount
Definition qicc.cpp:54
quint32_be profileSize
Definition qicc.cpp:29
quint32_be preferredCmmType
Definition qicc.cpp:31
quint32_be renderingIntent
Definition qicc.cpp:45
quint32_be inputColorSpace
Definition qicc.cpp:35
quint32_be signature
Definition qicc.cpp:38
quint32_be flags
Definition qicc.cpp:40
quint32_be profileVersion
Definition qicc.cpp:33
quint32_be profileClass
Definition qicc.cpp:34
quint32_be creatorSignature
Definition qicc.cpp:48
quint32_be platformSignature
Definition qicc.cpp:39
quint32_be deviceModel
Definition qicc.cpp:42
quint8 inputChannels
Definition qicc.cpp:205
quint8 clutGridPoints
Definition qicc.cpp:207
quint16_be inputTableEntries
Definition qicc.cpp:218
quint16_be outputTableEntries
Definition qicc.cpp:219
qint32_be e1
Definition qicc.cpp:209
qint32_be e5
Definition qicc.cpp:213
qint32_be e7
Definition qicc.cpp:215
quint8 outputChannels
Definition qicc.cpp:206
qint32_be e2
Definition qicc.cpp:210
qint32_be e9
Definition qicc.cpp:217
qint32_be e4
Definition qicc.cpp:212
qint32_be e3
Definition qicc.cpp:211
qint32_be e6
Definition qicc.cpp:214
qint32_be e8
Definition qicc.cpp:216
qint32_be e4
Definition qicc.cpp:193
qint32_be e9
Definition qicc.cpp:198
qint32_be e3
Definition qicc.cpp:192
qint32_be e7
Definition qicc.cpp:196
qint32_be e5
Definition qicc.cpp:194
qint32_be e8
Definition qicc.cpp:197
quint8 inputChannels
Definition qicc.cpp:186
quint8 clutGridPoints
Definition qicc.cpp:188
qint32_be e6
Definition qicc.cpp:195
quint8 outputChannels
Definition qicc.cpp:187
qint32_be e1
Definition qicc.cpp:190
qint32_be e2
Definition qicc.cpp:191
qint32_be e11
Definition qicc.cpp:262
qint32_be e10
Definition qicc.cpp:261
quint32_be recordCount
Definition qicc.cpp:180
MlucTagRecord records[1]
Definition qicc.cpp:182
quint32_be recordSize
Definition qicc.cpp:181
quint32_be size
Definition qicc.cpp:175
quint32_be offset
Definition qicc.cpp:176
quint16_be countryCode
Definition qicc.cpp:174
quint16_be languageCode
Definition qicc.cpp:173
quint16_be null2
Definition qicc.cpp:162
quint16_be curveType
Definition qicc.cpp:161
quint32 offset
Definition qicc.cpp:537
quint32 size
Definition qicc.cpp:538
quint32_be offset
Definition qicc.cpp:140
quint32_be size
Definition qicc.cpp:141
quint32_be signature
Definition qicc.cpp:139
qint32_be fixedY
Definition qicc.cpp:151
qint32_be fixedX
Definition qicc.cpp:150
qint32_be fixedZ
Definition qicc.cpp:152
quint8 outputChannels
Definition qicc.cpp:228
quint8 inputChannels
Definition qicc.cpp:227
quint32_be aCurvesOffset
Definition qicc.cpp:234
quint8 padding[2]
Definition qicc.cpp:229
quint32_be matrixOffset
Definition qicc.cpp:231
quint32_be mCurvesOffset
Definition qicc.cpp:232
quint32_be clutOffset
Definition qicc.cpp:233
quint32_be bCurvesOffset
Definition qicc.cpp:230
quint16_be inputChannels
Definition qicc.cpp:239
quint16_be outputChannels
Definition qicc.cpp:240
quint32_be processingElements
Definition qicc.cpp:241