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