/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 |