Coverage Report

Created: 2026-09-28 08:21

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