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