QtBase  v6.3.1
qicc.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qicc_p.h"
41 
42 #include <qbuffer.h>
43 #include <qbytearray.h>
44 #include <qdatastream.h>
45 #include <qendian.h>
46 #include <qloggingcategory.h>
47 #include <qstring.h>
48 
49 #include "qcolorspace_p.h"
50 #include "qcolortrc_p.h"
51 
52 #include <array>
53 
55 Q_LOGGING_CATEGORY(lcIcc, "qt.gui.icc", QtWarningMsg)
56 
58 {
60 
62 
67  quint32_be datetime[3];
73  quint32_be deviceAttributes[2];
74 
76  qint32_be illuminantXyz[3];
77 
79  quint32_be profileId[4];
80 
81  quint32_be reserved[7];
82 
83 // Technically after the header, but easier to include here:
85 };
86 
88 {
89  return (a << 24) | (b << 16) | (c << 8) | d;
90 }
91 
92 enum class ColorSpaceType : quint32 {
93  Rgb = IccTag('R', 'G', 'B', ' '),
94  Gray = IccTag('G', 'R', 'A', 'Y'),
95 };
96 
97 enum class ProfileClass : quint32 {
98  Input = IccTag('s', 'c', 'r', 'n'),
99  Display = IccTag('m', 'n', 't', 'r'),
100  // Not supported:
101  Output = IccTag('p', 'r', 't', 'r'),
102  ColorSpace = IccTag('s', 'p', 'a', 'c'),
103 };
104 
105 enum class Tag : quint32 {
106  acsp = IccTag('a', 'c', 's', 'p'),
107  RGB_ = IccTag('R', 'G', 'B', ' '),
108  XYZ_ = IccTag('X', 'Y', 'Z', ' '),
109  rXYZ = IccTag('r', 'X', 'Y', 'Z'),
110  gXYZ = IccTag('g', 'X', 'Y', 'Z'),
111  bXYZ = IccTag('b', 'X', 'Y', 'Z'),
112  rTRC = IccTag('r', 'T', 'R', 'C'),
113  gTRC = IccTag('g', 'T', 'R', 'C'),
114  bTRC = IccTag('b', 'T', 'R', 'C'),
115  kTRC = IccTag('k', 'T', 'R', 'C'),
116  A2B0 = IccTag('A', '2', 'B', '0'),
117  A2B1 = IccTag('A', '2', 'B', '1'),
118  B2A0 = IccTag('B', '2', 'A', '0'),
119  B2A1 = IccTag('B', '2', 'A', '1'),
120  desc = IccTag('d', 'e', 's', 'c'),
121  text = IccTag('t', 'e', 'x', 't'),
122  cprt = IccTag('c', 'p', 'r', 't'),
123  curv = IccTag('c', 'u', 'r', 'v'),
124  para = IccTag('p', 'a', 'r', 'a'),
125  wtpt = IccTag('w', 't', 'p', 't'),
126  bkpt = IccTag('b', 'k', 'p', 't'),
127  mft1 = IccTag('m', 'f', 't', '1'),
128  mft2 = IccTag('m', 'f', 't', '2'),
129  mluc = IccTag('m', 'l', 'u', 'c'),
130  mAB_ = IccTag('m', 'A', 'B', ' '),
131  mBA_ = IccTag('m', 'B', 'A', ' '),
132  chad = IccTag('c', 'h', 'a', 'd'),
133  sf32 = IccTag('s', 'f', '3', '2'),
134 
135  // Apple extensions for ICCv2:
136  aarg = IccTag('a', 'a', 'r', 'g'),
137  aagg = IccTag('a', 'a', 'g', 'g'),
138  aabg = IccTag('a', 'a', 'b', 'g'),
139 };
140 
141 inline size_t qHash(const Tag &key, size_t seed = 0)
142 {
143  return qHash(quint32(key), seed);
144 }
145 
146 namespace QIcc {
147 
149 {
153 };
154 
158 };
159 
164 };
165 
168  // followed by curv values: quint16_be[]
169 };
170 
174  // followed by parameter values: quint32_be[1-7];
175 };
176 
179  // followed by ascii description: char[]
180  // .. we ignore the rest
181 };
182 
188 };
189 
192  quint32_be recordSize; // = sizeof(MlucTagRecord)
194 };
195 
196 // For both mAB and mBA
206 };
207 
210 };
211 
212 static int toFixedS1516(float x)
213 {
214  return int(x * 65536.0f + 0.5f);
215 }
216 
217 static float fromFixedS1516(int x)
218 {
219  return x * (1.0f / 65536.0f);
220 }
221 
222 static bool isValidIccProfile(const ICCProfileHeader &header)
223 {
224  if (header.signature != uint(Tag::acsp)) {
225  qCWarning(lcIcc, "Failed ICC signature test");
226  return false;
227  }
228 
229  // Don't overflow 32bit integers:
230  if (header.tagCount >= (INT32_MAX - sizeof(ICCProfileHeader)) / sizeof(TagTableEntry)) {
231  qCWarning(lcIcc, "Failed tag count sanity");
232  return false;
233  }
234  if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) {
235  qCWarning(lcIcc, "Failed basic size sanity");
236  return false;
237  }
238 
239  if (header.profileClass != uint(ProfileClass::Input)
240  && header.profileClass != uint(ProfileClass::Display)
241  && (header.profileClass != uint(ProfileClass::Output)
242  || header.inputColorSpace != uint(ColorSpaceType::Gray))) {
243  qCInfo(lcIcc, "Unsupported ICC profile class 0x%x", quint32(header.profileClass));
244  return false;
245  }
246  if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
247  && header.inputColorSpace != uint(ColorSpaceType::Gray)) {
248  qCInfo(lcIcc, "Unsupported ICC input color space 0x%x", quint32(header.inputColorSpace));
249  return false;
250  }
251  if (header.pcs != 0x58595a20 /* 'XYZ '*/) {
252  // ### support PCSLAB
253  qCInfo(lcIcc, "Unsupported ICC profile connection space 0x%x", quint32(header.pcs));
254  return false;
255  }
256 
257  QColorVector illuminant;
258  illuminant.x = fromFixedS1516(header.illuminantXyz[0]);
259  illuminant.y = fromFixedS1516(header.illuminantXyz[1]);
260  illuminant.z = fromFixedS1516(header.illuminantXyz[2]);
261  if (illuminant != QColorVector::D50()) {
262  qCWarning(lcIcc, "Invalid ICC illuminant");
263  return false;
264  }
265 
266  return true;
267 }
268 
269 static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
270 {
271  if (trc.isLinear()) {
272  stream << uint(Tag::curv) << uint(0);
273  stream << uint(0);
274  return 12;
275  }
276 
277  if (trc.m_type == QColorTrc::Type::Function) {
278  const QColorTransferFunction &fun = trc.m_fun;
279  stream << uint(Tag::para) << uint(0);
280  if (fun.isGamma()) {
281  stream << ushort(0) << ushort(0);
282  stream << toFixedS1516(fun.m_g);
283  return 12 + 4;
284  }
285  bool type3 = qFuzzyIsNull(fun.m_e) && qFuzzyIsNull(fun.m_f);
286  stream << ushort(type3 ? 3 : 4) << ushort(0);
287  stream << toFixedS1516(fun.m_g);
288  stream << toFixedS1516(fun.m_a);
289  stream << toFixedS1516(fun.m_b);
290  stream << toFixedS1516(fun.m_c);
291  stream << toFixedS1516(fun.m_d);
292  if (type3)
293  return 12 + 5 * 4;
294  stream << toFixedS1516(fun.m_e);
295  stream << toFixedS1516(fun.m_f);
296  return 12 + 7 * 4;
297  }
298 
300  stream << uint(Tag::curv) << uint(0);
301  stream << uint(trc.m_table.m_tableSize);
302  if (!trc.m_table.m_table16.isEmpty()) {
303  for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
304  stream << ushort(trc.m_table.m_table16[i]);
305  }
306  } else {
307  for (uint i = 0; i < trc.m_table.m_tableSize; ++i) {
308  stream << ushort(trc.m_table.m_table8[i] * 257U);
309  }
310  }
311  return 12 + 2 * trc.m_table.m_tableSize;
312 }
313 
315 {
316  if (!space.isValid())
317  return QByteArray();
318 
319  const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
320 
321  constexpr int tagCount = 9;
322  constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
323  constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
324  uint currentOffset = 0;
325  uint rTrcOffset, gTrcOffset, bTrcOffset;
326  uint rTrcSize, gTrcSize, bTrcSize;
327  uint descOffset, descSize;
328 
329  QBuffer buffer;
332 
333  // Profile header:
334  stream << uint(0); // Size, we will update this later
335  stream << uint(0);
336  stream << uint(0x02400000); // Version 2.4 (note we use 'para' from version 4)
338  stream << uint(Tag::RGB_);
339  stream << uint(Tag::XYZ_);
340  stream << uint(0) << uint(0) << uint(0);
341  stream << uint(Tag::acsp);
342  stream << uint(0) << uint(0) << uint(0);
343  stream << uint(0) << uint(0) << uint(0);
344  stream << uint(1); // Rendering intent
345  stream << uint(0x0000f6d6); // D50 X
346  stream << uint(0x00010000); // D50 Y
347  stream << uint(0x0000d32d); // D50 Z
348  stream << IccTag('Q','t', QT_VERSION_MAJOR, QT_VERSION_MINOR);
349  stream << uint(0) << uint(0) << uint(0) << uint(0);
350  stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
351 
352  // Tag table:
353  stream << uint(tagCount);
354  stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
355  stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
356  stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
357  stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
358  stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(12);
359  // From here the offset and size will be updated later:
360  stream << uint(Tag::rTRC) << uint(0) << uint(0);
361  stream << uint(Tag::gTRC) << uint(0) << uint(0);
362  stream << uint(Tag::bTRC) << uint(0) << uint(0);
363  stream << uint(Tag::desc) << uint(0) << uint(0);
364  // TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
365  currentOffset = profileDataOffset;
366 
367  // Tag data:
368  stream << uint(Tag::XYZ_) << uint(0);
369  stream << toFixedS1516(spaceDPtr->toXyz.r.x);
370  stream << toFixedS1516(spaceDPtr->toXyz.r.y);
371  stream << toFixedS1516(spaceDPtr->toXyz.r.z);
372  stream << uint(Tag::XYZ_) << uint(0);
373  stream << toFixedS1516(spaceDPtr->toXyz.g.x);
374  stream << toFixedS1516(spaceDPtr->toXyz.g.y);
375  stream << toFixedS1516(spaceDPtr->toXyz.g.z);
376  stream << uint(Tag::XYZ_) << uint(0);
377  stream << toFixedS1516(spaceDPtr->toXyz.b.x);
378  stream << toFixedS1516(spaceDPtr->toXyz.b.y);
379  stream << toFixedS1516(spaceDPtr->toXyz.b.z);
380  stream << uint(Tag::XYZ_) << uint(0);
381  stream << toFixedS1516(spaceDPtr->whitePoint.x);
382  stream << toFixedS1516(spaceDPtr->whitePoint.y);
383  stream << toFixedS1516(spaceDPtr->whitePoint.z);
384  stream << uint(Tag::text) << uint(0);
385  stream << uint(IccTag('N', '/', 'A', '\0'));
386  currentOffset += 92;
387 
388  // From now on the data is variable sized:
389  rTrcOffset = currentOffset;
390  rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
391  currentOffset += rTrcSize;
392  if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
393  gTrcOffset = rTrcOffset;
394  gTrcSize = rTrcSize;
395  } else {
396  gTrcOffset = currentOffset;
397  gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
398  currentOffset += gTrcSize;
399  }
400  if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
401  bTrcOffset = rTrcOffset;
402  bTrcSize = rTrcSize;
403  } else {
404  bTrcOffset = currentOffset;
405  bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
406  currentOffset += bTrcSize;
407  }
408 
409  descOffset = currentOffset;
410  QByteArray description = space.description().toUtf8();
411  stream << uint(Tag::desc) << uint(0);
412  stream << uint(description.size() + 1);
413  stream.writeRawData(description.constData(), description.size() + 1);
414  stream << uint(0) << uint(0);
415  stream << ushort(0) << uchar(0);
416  QByteArray macdesc(67, '\0');
417  stream.writeRawData(macdesc.constData(), 67);
418  descSize = 90 + description.size() + 1;
419  currentOffset += descSize;
420 
421  buffer.close();
422  QByteArray iccProfile = buffer.buffer();
423  // Now write final size
424  *(quint32_be *)iccProfile.data() = iccProfile.size();
425  // And the final indices and sizes of variable size tags:
426  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
427  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
428  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
429  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
430  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
431  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
432  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
433  *(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
434 
435 #if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
436  const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
437  Q_ASSERT(qsizetype(iccHeader->profileSize) == qsizetype(iccProfile.size()));
438  Q_ASSERT(isValidIccProfile(*iccHeader));
439 #endif
440 
441  return iccProfile;
442 }
443 
444 struct TagEntry {
447 };
448 
449 bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
450 {
451  if (tagEntry.size < sizeof(XYZTagData)) {
452  qCWarning(lcIcc) << "Undersized XYZ tag";
453  return false;
454  }
455  const XYZTagData xyz = qFromUnaligned<XYZTagData>(data.constData() + tagEntry.offset);
456  if (xyz.type != quint32(Tag::XYZ_)) {
457  qCWarning(lcIcc) << "Bad XYZ content type";
458  return false;
459  }
460  const float x = fromFixedS1516(xyz.fixedX);
461  const float y = fromFixedS1516(xyz.fixedY);
462  const float z = fromFixedS1516(xyz.fixedZ);
463 
464  colorVector = QColorVector(x, y, z);
465  return true;
466 }
467 
468 bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma)
469 {
470  const GenericTagData trcData = qFromUnaligned<GenericTagData>(data.constData()
471  + tagEntry.offset);
472  if (trcData.type == quint32(Tag::curv)) {
473  Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
474  const CurvTagData curv = qFromUnaligned<CurvTagData>(data.constData() + tagEntry.offset);
475  if (curv.valueCount > (1 << 16))
476  return false;
477  if (tagEntry.size - 12 < 2 * curv.valueCount)
478  return false;
479  const auto valueOffset = tagEntry.offset + sizeof(CurvTagData);
480  if (curv.valueCount == 0) {
482  gamma.m_fun = QColorTransferFunction(); // Linear
483  } else if (curv.valueCount == 1) {
484  const quint16 v = qFromBigEndian<quint16>(data.constData() + valueOffset);
486  gamma.m_fun = QColorTransferFunction::fromGamma(v * (1.0f / 256.0f));
487  } else {
488  QList<quint16> tabl;
489  tabl.resize(curv.valueCount);
490  static_assert(sizeof(GenericTagData) == 2 * sizeof(quint32_be),
491  "GenericTagData has padding. The following code is a subject to UB.");
492  qFromBigEndian<quint16>(data.constData() + valueOffset, curv.valueCount, tabl.data());
493  QColorTransferTable table = QColorTransferTable(curv.valueCount, std::move(tabl));
495  if (!table.checkValidity()) {
496  qCWarning(lcIcc) << "Invalid curv table";
497  return false;
498  } else if (!table.asColorTransferFunction(&curve)) {
500  gamma.m_table = table;
501  } else {
502  qCDebug(lcIcc) << "Detected curv table as function";
504  gamma.m_fun = curve;
505  }
506  }
507  return true;
508  }
509  if (trcData.type == quint32(Tag::para)) {
510  Q_STATIC_ASSERT(sizeof(ParaTagData) == 12);
511  const ParaTagData para = qFromUnaligned<ParaTagData>(data.constData() + tagEntry.offset);
512  const auto parametersOffset = tagEntry.offset + sizeof(ParaTagData);
513  quint32 parameters[7];
514  switch (para.curveType) {
515  case 0: {
516  if (tagEntry.size < sizeof(ParaTagData) + 1 * 4)
517  return false;
518  qFromBigEndian<quint32>(data.constData() + parametersOffset, 1, parameters);
519  float g = fromFixedS1516(parameters[0]);
522  break;
523  }
524  case 1: {
525  if (tagEntry.size < sizeof(ParaTagData) + 3 * 4)
526  return false;
527  qFromBigEndian<quint32>(data.constData() + parametersOffset, 3, parameters);
528  if (parameters[1] == 0)
529  return false;
530  float g = fromFixedS1516(parameters[0]);
531  float a = fromFixedS1516(parameters[1]);
532  float b = fromFixedS1516(parameters[2]);
533  float d = -b / a;
535  gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, 0.0f, 0.0f, g);
536  break;
537  }
538  case 2: {
539  if (tagEntry.size < sizeof(ParaTagData) + 4 * 4)
540  return false;
541  qFromBigEndian<quint32>(data.constData() + parametersOffset, 4, parameters);
542  if (parameters[1] == 0)
543  return false;
544  float g = fromFixedS1516(parameters[0]);
545  float a = fromFixedS1516(parameters[1]);
546  float b = fromFixedS1516(parameters[2]);
547  float c = fromFixedS1516(parameters[3]);
548  float d = -b / a;
550  gamma.m_fun = QColorTransferFunction(a, b, 0.0f, d, c, c, g);
551  break;
552  }
553  case 3: {
554  if (tagEntry.size < sizeof(ParaTagData) + 5 * 4)
555  return false;
556  qFromBigEndian<quint32>(data.constData() + parametersOffset, 5, parameters);
557  float g = fromFixedS1516(parameters[0]);
558  float a = fromFixedS1516(parameters[1]);
559  float b = fromFixedS1516(parameters[2]);
560  float c = fromFixedS1516(parameters[3]);
561  float d = fromFixedS1516(parameters[4]);
563  gamma.m_fun = QColorTransferFunction(a, b, c, d, 0.0f, 0.0f, g);
564  break;
565  }
566  case 4: {
567  if (tagEntry.size < sizeof(ParaTagData) + 7 * 4)
568  return false;
569  qFromBigEndian<quint32>(data.constData() + parametersOffset, 7, parameters);
570  float g = fromFixedS1516(parameters[0]);
571  float a = fromFixedS1516(parameters[1]);
572  float b = fromFixedS1516(parameters[2]);
573  float c = fromFixedS1516(parameters[3]);
574  float d = fromFixedS1516(parameters[4]);
575  float e = fromFixedS1516(parameters[5]);
576  float f = fromFixedS1516(parameters[6]);
578  gamma.m_fun = QColorTransferFunction(a, b, c, d, e, f, g);
579  break;
580  }
581  default:
582  qCWarning(lcIcc) << "Unknown para type" << uint(para.curveType);
583  return false;
584  }
585  return true;
586  }
587  qCWarning(lcIcc) << "Invalid TRC data type";
588  return false;
589 }
590 
591 bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
592 {
593  const GenericTagData tag = qFromUnaligned<GenericTagData>(data.constData() + tagEntry.offset);
594 
595  // Either 'desc' (ICCv2) or 'mluc' (ICCv4)
596  if (tag.type == quint32(Tag::desc)) {
597  Q_STATIC_ASSERT(sizeof(DescTagData) == 12);
598  const DescTagData desc = qFromUnaligned<DescTagData>(data.constData() + tagEntry.offset);
599  const quint32 len = desc.asciiDescriptionLength;
600  if (len < 1)
601  return false;
602  if (tagEntry.size - 12 < len)
603  return false;
604  const char *asciiDescription = data.constData() + tagEntry.offset + sizeof(DescTagData);
605  if (asciiDescription[len - 1] != '\0')
606  return false;
607  descName = QString::fromLatin1(asciiDescription, len - 1);
608  return true;
609  }
610  if (tag.type != quint32(Tag::mluc))
611  return false;
612 
613  if (tagEntry.size < sizeof(MlucTagData))
614  return false;
615  const MlucTagData mluc = qFromUnaligned<MlucTagData>(data.constData() + tagEntry.offset);
616  if (mluc.recordCount < 1)
617  return false;
618  if (mluc.recordSize < 12)
619  return false;
620  // We just use the primary record regardless of language or country.
621  const quint32 stringOffset = mluc.records[0].offset;
622  const quint32 stringSize = mluc.records[0].size;
623  if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
624  return false;
625  if ((stringSize | stringOffset) & 1)
626  return false;
627  quint32 stringLen = stringSize / 2;
628  QVarLengthArray<char16_t> utf16hostendian(stringLen);
629  qFromBigEndian<char16_t>(data.constData() + tagEntry.offset + stringOffset, stringLen,
630  utf16hostendian.data());
631  // The given length shouldn't include 0-termination, but might.
632  if (stringLen > 1 && utf16hostendian[stringLen - 1] == 0)
633  --stringLen;
634  descName = QString::fromUtf16(utf16hostendian.data(), stringLen);
635  return true;
636 }
637 
638 bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
639 {
640  if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
641  qCWarning(lcIcc) << "fromIccProfile: failed size sanity 1";
642  return false;
643  }
644  const ICCProfileHeader header = qFromUnaligned<ICCProfileHeader>(data.constData());
645  if (!isValidIccProfile(header))
646  return false; // if failed we already printing a warning
647  if (qsizetype(header.profileSize) > data.size() || qsizetype(header.profileSize) < qsizetype(sizeof(ICCProfileHeader))) {
648  qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
649  return false;
650  }
651 
652  const qsizetype offsetToData = sizeof(ICCProfileHeader) + header.tagCount * sizeof(TagTableEntry);
653  Q_ASSERT(offsetToData > 0);
654  if (offsetToData > data.size()) {
655  qCWarning(lcIcc) << "fromIccProfile: failed index size sanity";
656  return false;
657  }
658 
659  QHash<Tag, TagEntry> tagIndex;
660  for (uint i = 0; i < header.tagCount; ++i) {
661  // Read tag index
662  const qsizetype tableOffset = sizeof(ICCProfileHeader) + i * sizeof(TagTableEntry);
663  const TagTableEntry tagTable = qFromUnaligned<TagTableEntry>(data.constData()
664  + tableOffset);
665 
666  // Sanity check tag sizes and offsets:
667  if (qsizetype(tagTable.offset) < offsetToData) {
668  qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 1";
669  return false;
670  }
671  // Checked separately from (+ size) to handle overflow.
672  if (tagTable.offset > header.profileSize) {
673  qCWarning(lcIcc) << "fromIccProfile: failed tag offset sanity 2";
674  return false;
675  }
676  if (tagTable.size < 12) {
677  qCWarning(lcIcc) << "fromIccProfile: failed minimal tag size sanity";
678  return false;
679  }
680  if (tagTable.size > header.profileSize - tagTable.offset) {
681  qCWarning(lcIcc) << "fromIccProfile: failed tag offset + size sanity";
682  return false;
683  }
684  if (tagTable.offset & 0x03) {
685  qCWarning(lcIcc) << "fromIccProfile: invalid tag offset alignment";
686  return false;
687  }
688 // printf("'%4s' %d %d\n", (const char *)&tagTable.signature,
689 // quint32(tagTable.offset),
690 // quint32(tagTable.size));
691  tagIndex.insert(Tag(quint32(tagTable.signature)), { tagTable.offset, tagTable.size });
692  }
693 
694  // Check the profile is three-component matrix based (what we currently support):
695  if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
696  if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
697  !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
698  !tagIndex.contains(Tag::wtpt)) {
699  qCInfo(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based";
700  return false;
701  }
702  } else {
703  Q_ASSERT(header.inputColorSpace == uint(ColorSpaceType::Gray));
704  if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
705  qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
706  return false;
707  }
708  }
709 
710  colorSpace->detach();
711  QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::get(*colorSpace);
712 
713  if (header.inputColorSpace == uint(ColorSpaceType::Rgb)) {
714  // Parse XYZ tags
715  if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
716  return false;
717  if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
718  return false;
719  if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
720  return false;
721  if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
722  return false;
723 
724  colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
725  if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
726  qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
727  colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
728  } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
729  qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
731  } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
732  qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
734  }
735  if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
736  qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
738  }
739  } else {
740  // We will use sRGB primaries and fit to match the given white-point if
741  // it doesn't match sRGB's.
742  QColorVector whitePoint;
743  if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
744  return false;
745  if (!qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z + whitePoint.x) == 0.0f) {
746  qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
747  return false;
748  }
749  if (whitePoint == QColorVector::D65()) {
750  colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
751  } else {
752  colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
753  // Calculate chromaticity from xyz (assuming y == 1.0f).
754  float y = 1.0f / (1.0f + whitePoint.z + whitePoint.x);
755  float x = whitePoint.x * y;
757  primaries.whitePoint = QPointF(x,y);
758  if (!primaries.areValid()) {
759  qCWarning(lcIcc, "fromIccProfile: Invalid ICC profile - invalid white-point(%f, %f)", x, y);
760  return false;
761  }
762  colorspaceDPtr->toXyz = primaries.toXyzMatrix();
763  }
764  }
765  // Reset the matrix to our canonical values:
766  if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
767  colorspaceDPtr->setToXyzMatrix();
768 
769  // Parse TRC tags
770  TagEntry rTrc;
771  TagEntry gTrc;
772  TagEntry bTrc;
773  if (header.inputColorSpace == uint(ColorSpaceType::Gray)) {
774  rTrc = tagIndex[Tag::kTRC];
775  gTrc = tagIndex[Tag::kTRC];
776  bTrc = tagIndex[Tag::kTRC];
777  } else if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
778  // Apple extension for parametric version of TRCs in ICCv2:
779  rTrc = tagIndex[Tag::aarg];
780  gTrc = tagIndex[Tag::aagg];
781  bTrc = tagIndex[Tag::aabg];
782  } else {
783  rTrc = tagIndex[Tag::rTRC];
784  gTrc = tagIndex[Tag::gTRC];
785  bTrc = tagIndex[Tag::bTRC];
786  }
787 
788  QColorTrc rCurve;
789  QColorTrc gCurve;
790  QColorTrc bCurve;
791  if (!parseTRC(data, rTrc, rCurve)) {
792  qCWarning(lcIcc) << "fromIccProfile: Invalid rTRC";
793  return false;
794  }
795  if (!parseTRC(data, gTrc, gCurve)) {
796  qCWarning(lcIcc) << "fromIccProfile: Invalid gTRC";
797  return false;
798  }
799  if (!parseTRC(data, bTrc, bCurve)) {
800  qCWarning(lcIcc) << "fromIccProfile: Invalid bTRC";
801  return false;
802  }
803  if (rCurve == gCurve && gCurve == bCurve && rCurve.m_type == QColorTrc::Type::Function) {
804  if (rCurve.m_fun.isLinear()) {
805  qCDebug(lcIcc) << "fromIccProfile: Linear gamma detected";
806  colorspaceDPtr->trc[0] = QColorTransferFunction();
808  colorspaceDPtr->gamma = 1.0f;
809  } else if (rCurve.m_fun.isGamma()) {
810  qCDebug(lcIcc) << "fromIccProfile: Simple gamma detected";
811  colorspaceDPtr->trc[0] = QColorTransferFunction::fromGamma(rCurve.m_fun.m_g);
813  colorspaceDPtr->gamma = rCurve.m_fun.m_g;
814  } else if (rCurve.m_fun.isSRgb()) {
815  qCDebug(lcIcc) << "fromIccProfile: sRGB gamma detected";
816  colorspaceDPtr->trc[0] = QColorTransferFunction::fromSRgb();
818  } else {
819  colorspaceDPtr->trc[0] = rCurve;
821  }
822 
823  colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
824  colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
825  } else {
826  colorspaceDPtr->trc[0] = rCurve;
827  colorspaceDPtr->trc[1] = gCurve;
828  colorspaceDPtr->trc[2] = bCurve;
830  }
831 
832  if (tagIndex.contains(Tag::desc)) {
833  if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description))
834  qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
835  else
836  qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
837  }
838 
839  colorspaceDPtr->identifyColorSpace();
840  if (colorspaceDPtr->namedColorSpace)
841  qCDebug(lcIcc) << "fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace);
842 
843  colorspaceDPtr->iccProfile = data;
844 
845  return true;
846 }
847 
848 } // namespace QIcc
849 
small capitals from c petite p scientific i
[1]
Definition: afcover.h:80
The QBuffer class provides a QIODevice interface for a QByteArray.
Definition: qbuffer.h:52
The QByteArray class provides an array of bytes.
Definition: qbytearray.h:85
char * data()
Definition: qbytearray.h:516
qsizetype size() const noexcept
Definition: qbytearray.h:470
const char * constData() const noexcept
Definition: qbytearray.h:144
static QColorMatrix toXyzFromSRgb()
static QColorMatrix toXyzFromAdobeRgb()
QColorVector g
QColorVector b
QColorVector r
static QColorMatrix toXyzFromDciP3D65()
static QColorMatrix toXyzFromProPhotoRgb()
The QColorSpace class provides a color space abstraction.
Definition: qcolorspace.h:57
bool isValid() const noexcept
QString description() const noexcept
QColorMatrix toXyzMatrix() const
bool areValid() const
QColorVector whitePoint
QColorSpace::NamedColorSpace namedColorSpace
QColorTrc trc[3]
static const QColorSpacePrivate * get(const QColorSpace &colorSpace)
QByteArray iccProfile
QColorSpace::Primaries primaries
QColorSpace::TransferFunction transferFunction
QColorMatrix toXyz
static QColorTransferFunction fromGamma(float gamma)
static QColorTransferFunction fromSRgb()
QList< uint16_t > m_table16
QColorTransferFunction m_fun
Definition: qcolortrc_p.h:127
Type m_type
Definition: qcolortrc_p.h:126
bool isLinear() const
Definition: qcolortrc_p.h:78
QColorTransferTable m_table
Definition: qcolortrc_p.h:128
static constexpr QColorVector D50()
static constexpr QColorVector D65()
The QDataStream class provides serialization of binary data to a QIODevice.
Definition: qdatastream.h:66
template< typename Enum > size_t qHash(QFlags< Enum > flags, size_t seed=0) noexcept
The QHash class is a template class that provides a hash-table-based dictionary.
Definition: qhash.h:773
bool contains(const Key &key) const noexcept
Definition: qhash.h:944
iterator insert(const Key &key, const T &value)
Definition: qhash.h:1228
bool isEmpty() const noexcept
Definition: qlist.h:418
pointer data()
Definition: qlist.h:442
void resize(qsizetype size)
Definition: qlist.h:420
The QPointF class defines a point in the plane using floating point precision.
Definition: qpoint.h:242
The QString class provides a Unicode character string.
Definition: qstring.h:388
static QString fromLatin1(QByteArrayView ba)
Definition: qstring.cpp:5488
static QString fromUtf16(const char16_t *, qsizetype size=-1)
Definition: qstring.cpp:5658
QByteArray toUtf8() const &
Definition: qstring.h:749
T * data() noexcept
double e
Definition: qicc.cpp:146
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
Definition: qicc.cpp:638
QByteArray toIccProfile(const QColorSpace &space)
Definition: qicc.cpp:314
bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma)
Definition: qicc.cpp:468
bool parseXyzData(const QByteArray &data, const TagEntry &tagEntry, QColorVector &colorVector)
Definition: qicc.cpp:449
bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
Definition: qicc.cpp:591
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
EGLStreamKHR stream
EGLOutputLayerEXT EGLint EGLAttrib value
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
Definition: qfloat16.h:233
bool qFuzzyIsNull(qfloat16 f) noexcept
Definition: qfloat16.h:249
unsigned int quint32
Definition: qglobal.h:288
#define Q_STATIC_ASSERT(Condition)
Definition: qglobal.h:190
QT_BEGIN_INCLUDE_NAMESPACE typedef unsigned char uchar
Definition: qglobal.h:332
unsigned short quint16
Definition: qglobal.h:286
ptrdiff_t qsizetype
Definition: qglobal.h:308
unsigned int uint
Definition: qglobal.h:334
unsigned short ushort
Definition: qglobal.h:333
unsigned char quint8
Definition: qglobal.h:284
ColorSpaceType
Definition: qicc.cpp:92
Tag
Definition: qicc.cpp:105
@ bkpt
@ bTRC
@ aagg
@ text
@ cprt
@ desc
@ bXYZ
@ B2A0
@ wtpt
@ acsp
@ mBA_
@ mluc
@ A2B1
@ aarg
@ B2A1
@ kTRC
@ mAB_
@ gXYZ
@ RGB_
@ gTRC
@ A2B0
@ mft2
@ rXYZ
@ XYZ_
@ para
@ mft1
@ rTRC
@ sf32
@ chad
@ curv
@ aabg
ProfileClass
Definition: qicc.cpp:97
constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
Definition: qicc.cpp:87
@ QtWarningMsg
Definition: qlogging.h:62
#define Q_LOGGING_CATEGORY(name,...)
#define qCInfo(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLboolean GLboolean GLboolean b
GLsizei const GLfloat * v
[13]
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat z
GLint GLint GLint GLint GLint x
[0]
GLuint64 key
GLboolean GLboolean GLboolean GLboolean a
[7]
GLfloat GLfloat f
GLenum GLuint buffer
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLboolean GLboolean g
GLint y
const GLubyte * c
Definition: qopenglext.h:12701
GLenum GLsizei len
Definition: qopenglext.h:3292
GLenum GLenum GLsizei void * table
Definition: qopenglext.h:2745
#define Q_ASSERT(cond)
Definition: qrandom.cpp:84
@ desc
@ mluc
@ para
@ curv
struct _XDisplay Display
Definition: qtx11extras_p.h:58
QHttpRequestHeader header("GET", QUrl::toPercentEncoding("/index.html"))
[1]
quint32_be signature
Definition: qicc.cpp:68
quint32_be tagCount
Definition: qicc.cpp:84
quint32_be inputColorSpace
Definition: qicc.cpp:65
quint32_be deviceManufacturer
Definition: qicc.cpp:71
quint32_be pcs
Definition: qicc.cpp:66
quint32_be profileSize
Definition: qicc.cpp:59
quint32_be flags
Definition: qicc.cpp:70
quint32_be profileClass
Definition: qicc.cpp:64
quint32_be renderingIntent
Definition: qicc.cpp:75
quint32_be creatorSignature
Definition: qicc.cpp:78
quint32_be preferredCmmType
Definition: qicc.cpp:61
quint32_be profileVersion
Definition: qicc.cpp:63
quint32_be deviceModel
Definition: qicc.cpp:72
quint32_be platformSignature
Definition: qicc.cpp:69
quint32_be valueCount
Definition: qicc.cpp:167
quint32_be asciiDescriptionLength
Definition: qicc.cpp:178
quint32_be type
Definition: qicc.cpp:156
quint32_be recordCount
Definition: qicc.cpp:191
MlucTagRecord records[1]
Definition: qicc.cpp:193
quint32_be recordSize
Definition: qicc.cpp:192
quint32_be size
Definition: qicc.cpp:186
quint32_be offset
Definition: qicc.cpp:187
quint16_be countryCode
Definition: qicc.cpp:185
quint16_be languageCode
Definition: qicc.cpp:184
quint16_be null2
Definition: qicc.cpp:173
quint16_be curveType
Definition: qicc.cpp:172
Definition: qicc.cpp:444
quint32 offset
Definition: qicc.cpp:445
quint32 size
Definition: qicc.cpp:446
Definition: qicc.cpp:149
quint32_be offset
Definition: qicc.cpp:151
quint32_be size
Definition: qicc.cpp:152
quint32_be signature
Definition: qicc.cpp:150
qint32_be fixedY
Definition: qicc.cpp:162
qint32_be fixedX
Definition: qicc.cpp:161
qint32_be fixedZ
Definition: qicc.cpp:163
quint8 outputChannels
Definition: qicc.cpp:199
quint8 inputChannels
Definition: qicc.cpp:198
quint32_be aCurvesOffset
Definition: qicc.cpp:205
quint8 padding[2]
Definition: qicc.cpp:200
quint32_be matrixOffset
Definition: qicc.cpp:202
quint32_be mCurvesOffset
Definition: qicc.cpp:203
quint32_be clutOffset
Definition: qicc.cpp:204
quint32_be bCurvesOffset
Definition: qicc.cpp:201
Definition: data.cpp:95
int(* fun)()
XmlOutput::xml_output tag(const QString &name)
Definition: xmloutput.h:154