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