461static int writeMab(QDataStream &stream,
const QList<QColorSpacePrivate::Element> &abList,
bool isAb,
bool pcsLab,
bool isCmyk)
465 for (
auto &&element : abList)
466 std::visit([&](
auto &&elm) { visitElement(combo, elm, number++, abList); }, element);
494 const int inChannels = (isCmyk && isAb) ? 4 : 3;
495 const int outChannels = (isCmyk && !isAb) ? 4 : 3;
496 stream << uchar(inChannels) << uchar(outChannels);
497 qsizetype gridPointsLut16 = 0;
498 if (lut16 && combo.clut)
499 gridPointsLut16 = combo.clut->gridPointsX;
501 stream << uchar(gridPointsLut16) << uchar(0);
503 stream << quint16(0);
506 stream << toFixedS1516(combo
.inMatrix->r.x);
507 stream << toFixedS1516(combo
.inMatrix->g.x);
508 stream << toFixedS1516(combo
.inMatrix->b.x);
509 stream << toFixedS1516(combo
.inMatrix->r.y);
510 stream << toFixedS1516(combo
.inMatrix->g.y);
511 stream << toFixedS1516(combo
.inMatrix->b.y);
512 stream << toFixedS1516(combo
.inMatrix->r.z);
513 stream << toFixedS1516(combo
.inMatrix->g.z);
514 stream << toFixedS1516(combo
.inMatrix->b.z);
526 int inputEntries = 0, outputEntries = 0;
528 inputEntries = combo
.inTable->trc[0].table().m_tableSize;
532 outputEntries = combo
.outTable->trc[0].table().m_tableSize;
535 stream << quint16(inputEntries);
536 stream << quint16(outputEntries);
539 for (
int j = 0; j < channels; ++j) {
540 if (!table->trc[j].table().m_table16.isEmpty()) {
541 for (
int i = 0; i < entries; ++i)
542 stream << table->trc[j].table().m_table16[i];
544 for (
int i = 0; i < entries; ++i)
545 stream << quint16(table->trc[j].table().m_table8[i] * 257);
549 for (
int j = 0; j < channels; ++j)
550 stream << quint16(0) << quint16(65535);
554 writeTable(combo
.inTable, inputEntries, inChannels);
557 if (isAb && pcsLab) {
558 for (
const QColorVector &v : combo.clut->table) {
559 stream << quint16(v.x * 65280.0f + 0.5f);
560 stream << quint16(v.y * 65280.0f + 0.5f);
561 stream << quint16(v.z * 65280.0f + 0.5f);
564 if (outChannels == 4) {
565 for (
const QColorVector &v : combo.clut->table) {
566 stream << quint16(v.x * 65535.0f + 0.5f);
567 stream << quint16(v.y * 65535.0f + 0.5f);
568 stream << quint16(v.z * 65535.0f + 0.5f);
569 stream << quint16(v.w * 65535.0f + 0.5f);
572 for (
const QColorVector &v : combo.clut->table) {
573 stream << quint16(v.x * 65535.0f + 0.5f);
574 stream << quint16(v.y * 65535.0f + 0.5f);
575 stream << quint16(v.z * 65535.0f + 0.5f);
581 writeTable(combo
.outTable, outputEntries, outChannels);
583 qsizetype offset =
sizeof(Lut16TagData) + 2 * inChannels * inputEntries
584 + 2 * outChannels * outputEntries
585 + 2 * outChannels * intPow(gridPointsLut16, inChannels);
587 stream << quint16(0);
602 buffer2.open(QIODevice::WriteOnly);
603 QDataStream stream2(&buffer2);
604 quint32 bOffset = offset;
605 quint32 matrixOffset = 0;
607 quint32 clutOffset = 0;
610 auto alignTag = [&]() {
612 stream2 << quint16(0);
621 aChannels = inChannels;
623 Q_ASSERT(outChannels == 3);
626 aChannels = outChannels;
628 Q_ASSERT(inChannels == 3);
631 offset += writeColorTrc(stream2, bCurve->trc[0]);
633 offset += writeColorTrc(stream2, bCurve->trc[1]);
635 offset += writeColorTrc(stream2, bCurve->trc[2]);
638 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
639 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
640 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
644 matrixOffset = offset;
646 stream2 << toFixedS1516(combo
.midMatrix->r.x);
647 stream2 << toFixedS1516(combo
.midMatrix->g.x);
648 stream2 << toFixedS1516(combo
.midMatrix->b.x);
649 stream2 << toFixedS1516(combo
.midMatrix->r.y);
650 stream2 << toFixedS1516(combo
.midMatrix->g.y);
651 stream2 << toFixedS1516(combo
.midMatrix->b.y);
652 stream2 << toFixedS1516(combo
.midMatrix->r.z);
653 stream2 << toFixedS1516(combo
.midMatrix->g.z);
654 stream2 << toFixedS1516(combo
.midMatrix->b.z);
678 offset += writeColorTrc(stream2, combo
.midTable->trc[0]);
680 offset += writeColorTrc(stream2, combo
.midTable->trc[1]);
682 offset += writeColorTrc(stream2, combo
.midTable->trc[2]);
685 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
686 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
687 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
691 if (combo
.clut || aCurve) {
701 for (
int i = 0; i < 12; ++i)
703 stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
705 if (outChannels == 4) {
706 for (
const QColorVector &v : combo.clut->table) {
707 stream2 << quint16(v.x * 65535.0f + 0.5f);
708 stream2 << quint16(v.y * 65535.0f + 0.5f);
709 stream2 << quint16(v.z * 65535.0f + 0.5f);
710 stream2 << quint16(v.w * 65535.0f + 0.5f);
713 for (
const QColorVector &v : combo.clut->table) {
714 stream2 << quint16(v.x * 65535.0f + 0.5f);
715 stream2 << quint16(v.y * 65535.0f + 0.5f);
716 stream2 << quint16(v.z * 65535.0f + 0.5f);
719 offset += 2 * outChannels * combo
.clut->table.size();
722 for (
int i = 0; i < 16; ++i)
724 stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
729 offset += writeColorTrc(stream2, aCurve->trc[0]);
731 offset += writeColorTrc(stream2, aCurve->trc[1]);
733 offset += writeColorTrc(stream2, aCurve->trc[2]);
735 if (aChannels == 4) {
736 offset += writeColorTrc(stream2, aCurve->trc[3]);
740 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
741 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
742 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
744 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
745 offset += 12 * aChannels;
750 stream << quint32(bOffset);
751 stream << quint32(matrixOffset);
752 stream << quint32(mOffset);
753 stream << quint32(clutOffset);
754 stream << quint32(aOffset);
755 stream.writeRawData(tagData.data(), tagData.size());
757 return int(
sizeof(
mABTagData) + tagData.size());
763 if (!space.isValid())
767 if (!spaceDPtr->iccProfile.isEmpty())
768 return spaceDPtr->iccProfile;
770 int fixedLengthTagCount = 5;
772 fixedLengthTagCount = 2;
773 else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
774 fixedLengthTagCount = 2;
775 bool writeChad =
false;
776 bool writeB2a =
true;
777 bool writeCicp =
false;
780 fixedLengthTagCount++;
782 int varLengthTagCount = 4;
784 varLengthTagCount = 3;
785 else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
786 varLengthTagCount = 2;
788 if (!space.isValidTarget()) {
793 switch (spaceDPtr->transferFunction) {
794 case QColorSpace::TransferFunction::St2084:
795 case QColorSpace::TransferFunction::Hlg:
797 fixedLengthTagCount++;
803 const int tagCount = fixedLengthTagCount + varLengthTagCount;
804 const uint profileDataOffset = 128 + 4 + 12 * tagCount;
805 uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
807 uint currentOffset = 0;
824 buffer.open(QIODevice::WriteOnly);
825 QDataStream stream(&buffer);
830 stream << uint(0x04400000);
832 switch (spaceDPtr->colorModel) {
833 case QColorSpace::ColorModel::Rgb:
836 case QColorSpace::ColorModel::Gray:
839 case QColorSpace::ColorModel::Cmyk:
842 case QColorSpace::ColorModel::Undefined:
846 stream << uint(0) << uint(0) << uint(0);
848 stream << uint(0) << uint(0) << uint(0);
849 stream << uint(0) << uint(0) << uint(0);
851 stream << uint(0x0000f6d6);
852 stream << uint(0x00010000);
853 stream << uint(0x0000d32d);
854 stream << IccTag(
'Q',
't', QT_VERSION_MAJOR, QT_VERSION_MINOR);
855 stream << uint(0) << uint(0) << uint(0) << uint(0);
856 stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
858 currentOffset = profileDataOffset;
861 stream << uint(tagCount);
862 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
863 stream << uint(
Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
864 stream << uint(
Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
865 stream << uint(
Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
866 currentOffset += 20 + 20 + 20;
868 stream << uint(
Tag::wtpt) << uint(currentOffset + 00) << uint(20);
869 stream << uint(
Tag::cprt) << uint(currentOffset + 20) << uint(34);
870 currentOffset += 20 + 34 + 2;
872 stream << uint(
Tag::chad) << uint(currentOffset) << uint(44);
876 stream << uint(
Tag::cicp) << uint(currentOffset) << uint(12);
880 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
881 stream << uint(
Tag::rTRC) << uint(0) << uint(0);
882 stream << uint(
Tag::gTRC) << uint(0) << uint(0);
883 stream << uint(
Tag::bTRC) << uint(0) << uint(0);
885 stream << uint(
Tag::kTRC) << uint(0) << uint(0);
887 stream << uint(
Tag::desc) << uint(0) << uint(0);
890 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
892 stream << toFixedS1516(spaceDPtr->toXyz.r.x);
893 stream << toFixedS1516(spaceDPtr->toXyz.r.y);
894 stream << toFixedS1516(spaceDPtr->toXyz.r.z);
896 stream << toFixedS1516(spaceDPtr->toXyz.g.x);
897 stream << toFixedS1516(spaceDPtr->toXyz.g.y);
898 stream << toFixedS1516(spaceDPtr->toXyz.g.z);
900 stream << toFixedS1516(spaceDPtr->toXyz.b.x);
901 stream << toFixedS1516(spaceDPtr->toXyz.b.y);
902 stream << toFixedS1516(spaceDPtr->toXyz.b.z);
905 stream << toFixedS1516(spaceDPtr->whitePoint.x);
906 stream << toFixedS1516(spaceDPtr->whitePoint.y);
907 stream << toFixedS1516(spaceDPtr->whitePoint.z);
909 stream << uint(1) << uint(12);
910 stream << uchar(
'e') << uchar(
'n') << uchar(
'U') << uchar(
'S');
911 stream << uint(6) << uint(28);
912 stream << ushort(
'N') << ushort(
'/') << ushort(
'A');
917 stream << toFixedS1516(chad.r.x);
918 stream << toFixedS1516(chad.g.x);
919 stream << toFixedS1516(chad.b.x);
920 stream << toFixedS1516(chad.r.y);
921 stream << toFixedS1516(chad.g.y);
922 stream << toFixedS1516(chad.b.y);
923 stream << toFixedS1516(chad.r.z);
924 stream << toFixedS1516(chad.g.z);
925 stream << toFixedS1516(chad.b.z);
930 if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::St2084)
932 else if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::Hlg)
941 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
942 rTrcOffset = currentOffset;
943 rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
944 currentOffset += rTrcSize;
945 if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
946 gTrcOffset = rTrcOffset;
949 gTrcOffset = currentOffset;
950 gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
951 currentOffset += gTrcSize;
953 if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
954 bTrcOffset = rTrcOffset;
957 bTrcOffset = currentOffset;
958 bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
959 currentOffset += bTrcSize;
962 Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
963 kTrcOffset = currentOffset;
964 kTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
965 currentOffset += kTrcSize;
969 stream << uint(tagCount);
970 stream << uint(
Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
971 stream << uint(
Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
972 currentOffset += 20 + 34 + 2;
974 stream << uint(
Tag::A2B0) << uint(0) << uint(0);
976 stream << uint(
Tag::B2A0) << uint(0) << uint(0);
977 stream << uint(
Tag::desc) << uint(0) << uint(0);
981 stream << toFixedS1516(spaceDPtr->whitePoint.x);
982 stream << toFixedS1516(spaceDPtr->whitePoint.y);
983 stream << toFixedS1516(spaceDPtr->whitePoint.z);
985 stream << uint(1) << uint(12);
986 stream << uchar(
'e') << uchar(
'n') << uchar(
'U') << uchar(
'S');
987 stream << uint(6) << uint(28);
988 stream << ushort(
'N') << ushort(
'/') << ushort(
'A');
992 mA2bOffset = currentOffset;
993 mA2bSize = writeMab(stream, spaceDPtr->mAB,
true, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
994 currentOffset += mA2bSize;
996 mB2aOffset = currentOffset;
997 mB2aSize = writeMab(stream, spaceDPtr->mBA,
false, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
998 currentOffset += mB2aSize;
1003 descOffset = currentOffset;
1004 const QString description = space.description();
1006 stream << uint(1) << uint(12);
1007 stream << uchar(
'e') << uchar(
'n') << uchar(
'U') << uchar(
'S');
1008 stream << uint(description.size() * 2) << uint(28);
1009 for (QChar ch : description)
1010 stream << ushort(ch.unicode());
1011 descSize = 28 + description.size() * 2;
1012 if (description.size() & 1) {
1013 stream << ushort(0);
1016 currentOffset += descSize;
1021 *(
quint32_be *)iccProfile.data() = iccProfile.size();
1024 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
1025 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
1026 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
1027 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
1028 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
1029 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
1030 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
1031 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
1032 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
1034 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
1035 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
1036 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
1037 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
1040 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
1041 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
1042 variableTagTableOffsets += 12;
1044 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
1045 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
1046 variableTagTableOffsets += 12;
1048 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
1049 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
1052#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
1054 Q_ASSERT(qsizetype(iccHeader
->profileSize) == qsizetype(iccProfile.size()));
1087 if (tagData.size() < 12)
1094 qCWarning(lcIcc) <<
"Invalid count in curv table";
1097 if (tagData.size() < qsizetype(12 + 2 * curv
.valueCount)) {
1098 qCWarning(lcIcc) <<
"Truncated curv table";
1103 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1106 const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
1107 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1110 QList<quint16> tabl;
1113 "GenericTagData has padding. The following code is a subject to UB.");
1114 qFromBigEndian<quint16>(tagData.constData() + valueOffset, curv.valueCount, tabl.data());
1115 QColorTransferTable table(curv.valueCount, tabl, type);
1117 if (!table.checkValidity()) {
1118 qCWarning(lcIcc) <<
"Invalid curv table";
1120 }
else if (!table.asColorTransferFunction(&curve)) {
1121 gamma.m_type = QColorTrc::Type::Table;
1122 gamma.m_table = table;
1124 qCDebug(lcIcc) <<
"Detected curv table as function";
1125 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1126 gamma.m_fun = curve;
1134 const auto parametersOffset =
sizeof(
ParaTagData);
1135 quint32 parameters[7];
1138 if (tagData.size() < 12 + 1 * 4)
1140 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
1141 float g = fromFixedS1516(parameters[0]);
1142 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1147 if (tagData.size() < 12 + 3 * 4)
1149 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 3, parameters);
1150 if (parameters[1] == 0)
1152 float g = fromFixedS1516(parameters[0]);
1153 float a = fromFixedS1516(parameters[1]);
1154 float b = fromFixedS1516(parameters[2]);
1156 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1161 if (tagData.size() < 12 + 4 * 4)
1163 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 4, parameters);
1164 if (parameters[1] == 0)
1166 float g = fromFixedS1516(parameters[0]);
1167 float a = fromFixedS1516(parameters[1]);
1168 float b = fromFixedS1516(parameters[2]);
1169 float c = fromFixedS1516(parameters[3]);
1171 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1176 if (tagData.size() < 12 + 5 * 4)
1178 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 5, parameters);
1179 float g = fromFixedS1516(parameters[0]);
1180 float a = fromFixedS1516(parameters[1]);
1181 float b = fromFixedS1516(parameters[2]);
1182 float c = fromFixedS1516(parameters[3]);
1183 float d = fromFixedS1516(parameters[4]);
1184 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1189 if (tagData.size() < 12 + 7 * 4)
1191 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 7, parameters);
1192 float g = fromFixedS1516(parameters[0]);
1193 float a = fromFixedS1516(parameters[1]);
1194 float b = fromFixedS1516(parameters[2]);
1195 float c = fromFixedS1516(parameters[3]);
1196 float d = fromFixedS1516(parameters[4]);
1197 float e = fromFixedS1516(parameters[5]);
1198 float f = fromFixedS1516(parameters[6]);
1199 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1204 qCWarning(lcIcc) <<
"Unknown para type" << uint(para.curveType);
1209 qCWarning(lcIcc) <<
"Invalid TRC data type" << Qt::hex << trcData.type;
1238 if (tagEntry.size <
sizeof(T)) {
1239 qCWarning(lcIcc) <<
"Undersized lut8/lut16 tag";
1242 if (qsizetype(tagEntry.size) > data.size()) {
1243 qCWarning(lcIcc) <<
"Truncated lut8/lut16 tag";
1246 using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
1247 const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
1248 int inputTableEntries, outputTableEntries, precision;
1250 Q_ASSERT(lut.type == quint32(
Tag::mft1));
1252 qCWarning(lcIcc) <<
"Lut8 can not output XYZ values";
1255 inputTableEntries = 256;
1256 outputTableEntries = 256;
1259 Q_ASSERT(lut.type == quint32(
Tag::mft2));
1260 inputTableEntries = lut.inputTableEntries;
1261 outputTableEntries = lut.outputTableEntries;
1262 if (inputTableEntries < 2 || inputTableEntries > 4096)
1264 if (outputTableEntries < 2 || outputTableEntries > 4096)
1269 bool inTableIsLinear =
true, outTableIsLinear =
true;
1275 matrixElement.r.x = fromFixedS1516(lut.e1);
1276 matrixElement.g.x = fromFixedS1516(lut.e2);
1277 matrixElement.b.x = fromFixedS1516(lut.e3);
1278 matrixElement.r.y = fromFixedS1516(lut.e4);
1279 matrixElement.g.y = fromFixedS1516(lut.e5);
1280 matrixElement.b.y = fromFixedS1516(lut.e6);
1281 matrixElement.r.z = fromFixedS1516(lut.e7);
1282 matrixElement.g.z = fromFixedS1516(lut.e8);
1283 matrixElement.b.z = fromFixedS1516(lut.e9);
1285 qCWarning(lcIcc) <<
"Invalid matrix values in lut8/lut16";
1289 const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1290 const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1292 if (lut.inputChannels != inputChannels) {
1293 qCWarning(lcIcc) <<
"Unsupported lut8/lut16 input channel count" << lut.inputChannels;
1297 if (lut.outputChannels != outputChannels) {
1298 qCWarning(lcIcc) <<
"Unsupported lut8/lut16 output channel count" << lut.outputChannels;
1302 const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
1303 if (tagEntry.size < (
sizeof(T) + precision * inputChannels * inputTableEntries
1304 + precision * outputChannels * outputTableEntries
1305 + precision * outputChannels * clutTableSize)) {
1306 qCWarning(lcIcc) <<
"Undersized lut8/lut16 tag, no room for tables";
1309 if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
1310 qCWarning(lcIcc) <<
"Cmyk conversion must have a CLUT";
1314 const uint8_t *tableData =
reinterpret_cast<
const uint8_t *>(data.constData() + tagEntry.offset +
sizeof(T));
1316 for (
int j = 0; j < inputChannels; ++j) {
1317 QList<S> input(inputTableEntries);
1318 qFromBigEndian<S>(tableData, inputTableEntries, input.data());
1319 QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
1320 if (!table.checkValidity()) {
1321 qCWarning(lcIcc) <<
"Bad input table in lut8/lut16";
1324 if (!table.isIdentity())
1325 inTableIsLinear =
false;
1326 inTableElement.trc[j] =
std::move(table);
1327 tableData += inputTableEntries * precision;
1330 clutElement.table.resize(clutTableSize);
1332 if (inputChannels == 4)
1336 parseCLUT(tableData, 1.f / 255.f, &clutElement, outputChannels);
1338 float f = 1.0f / 65535.f;
1341 QList<S> clutTable(clutTableSize * outputChannels);
1342 qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
1343 parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
1345 tableData += clutTableSize * outputChannels * precision;
1347 for (
int j = 0; j < outputChannels; ++j) {
1348 QList<S> output(outputTableEntries);
1349 qFromBigEndian<S>(tableData, outputTableEntries, output.data());
1350 QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
1351 if (!table.checkValidity()) {
1352 qCWarning(lcIcc) <<
"Bad output table in lut8/lut16";
1355 if (!table.isIdentity())
1356 outTableIsLinear =
false;
1357 outTableElement.trc[j] =
std::move(table);
1358 tableData += outputTableEntries * precision;
1362 if (!inTableIsLinear)
1363 colorSpacePrivate->mAB.append(inTableElement);
1365 colorSpacePrivate->mAB.append(clutElement);
1366 if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
1367 colorSpacePrivate->mAB.append(outTableElement);
1371 colorSpacePrivate->mBA.append(matrixElement);
1372 if (!inTableIsLinear)
1373 colorSpacePrivate->mBA.append(inTableElement);
1375 colorSpacePrivate->mBA.append(clutElement);
1376 if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
1377 colorSpacePrivate->mBA.append(outTableElement);
1386 qCWarning(lcIcc) <<
"Undersized mAB/mBA tag";
1389 if (qsizetype(tagEntry.size) > data.size()) {
1390 qCWarning(lcIcc) <<
"Truncated mAB/mBA tag";
1395 qCWarning(lcIcc) <<
"Bad mAB/mBA content type";
1399 const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1400 const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1402 if (mab.inputChannels != inputChannels) {
1403 qCWarning(lcIcc) <<
"Unsupported mAB/mBA input channel count" << mab.inputChannels;
1407 if (mab.outputChannels != outputChannels) {
1408 qCWarning(lcIcc) <<
"Unsupported mAB/mBA output channel count" << mab.outputChannels;
1414 qCWarning(lcIcc) <<
"Illegal mAB/mBA without B table";
1419 qCWarning(lcIcc) <<
"Illegal mAB/mBA element combination";
1428 qCWarning(lcIcc) <<
"Illegal mAB/mBA element offset";
1437 QColorVector offsetElement;
1439 auto parseCurves = [&data, &tagEntry] (uint curvesOffset, QColorTrc *table,
int channels) {
1440 for (
int i = 0; i < channels; ++i) {
1441 if (qsizetype(tagEntry.offset + curvesOffset + 12) > data.size() || curvesOffset + 12 > tagEntry.size) {
1442 qCWarning(lcIcc) <<
"Space missing for channel curves in mAB/mBA";
1445 auto size = parseTRC(QByteArrayView(data).sliced(tagEntry.offset + curvesOffset, tagEntry.size - curvesOffset), table[i], QColorTransferTable::OneWay);
1448 if (size & 2) size += 2;
1449 curvesOffset += size;
1454 bool bCurvesAreLinear =
true, aCurvesAreLinear =
true, mCurvesAreLinear =
true;
1457 if (!parseCurves(mab
.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
1461 bCurvesAreLinear = bTableElement.trc[0].isIdentity() && bTableElement.trc[1].isIdentity() && bTableElement.trc[2].isIdentity();
1466 if (!parseCurves(mab
.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
1470 aCurvesAreLinear = aTableElement.trc[0].isIdentity() && aTableElement.trc[1].isIdentity() && aTableElement.trc[2].isIdentity();
1480 mCurvesAreLinear = mTableElement.trc[0].isIdentity() && mTableElement.trc[1].isIdentity() && mTableElement.trc[2].isIdentity();
1499 if (!matrixElement
.isValid() || !offsetElement.isValid()) {
1500 qCWarning(lcIcc) <<
"Invalid matrix values in mAB/mBA element";
1511 const uchar precision = data[tagEntry.offset + mab
.clutOffset + 16];
1512 if (precision > 2 || precision < 1) {
1513 qCWarning(lcIcc) <<
"Invalid mAB/mBA element CLUT precision";
1521 if ((mab
.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
1522 qCWarning(lcIcc) <<
"CLUT oversized for tag";
1526 clutElement.table.resize(clutTableSize);
1527 if (precision == 2) {
1528 QList<uint16_t> clutTable(clutTableSize * outputChannels);
1529 qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
1530 parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, outputChannels);
1532 const uint8_t *clutTable =
reinterpret_cast<
const uint8_t *>(data.constData() + tagEntry.offset + mab
.clutOffset + 20);
1533 parseCLUT(clutTable, (1.f/255.f), &clutElement, outputChannels);
1535 }
else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
1536 qCWarning(lcIcc) <<
"Cmyk conversion must have a CLUT";
1542 if (!aCurvesAreLinear)
1543 colorSpacePrivate->mAB.append(
std::move(aTableElement));
1545 colorSpacePrivate->mAB.append(
std::move(clutElement));
1548 if (!mCurvesAreLinear)
1549 colorSpacePrivate->mAB.append(
std::move(mTableElement));
1551 colorSpacePrivate->mAB.append(
std::move(matrixElement));
1552 if (!offsetElement.isNull())
1553 colorSpacePrivate->mAB.append(
std::move(offsetElement));
1555 if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
1556 colorSpacePrivate->mAB.append(
std::move(bTableElement));
1558 if (!bCurvesAreLinear)
1559 colorSpacePrivate->mBA.append(
std::move(bTableElement));
1562 colorSpacePrivate->mBA.append(
std::move(matrixElement));
1563 if (!offsetElement.isNull())
1564 colorSpacePrivate->mBA.append(
std::move(offsetElement));
1565 if (!mCurvesAreLinear)
1566 colorSpacePrivate->mBA.append(
std::move(mTableElement));
1570 colorSpacePrivate->mBA.append(
std::move(clutElement));
1571 if (!aCurvesAreLinear)
1572 colorSpacePrivate->mBA.append(
std::move(aTableElement));
1574 if (colorSpacePrivate->mBA.isEmpty())
1575 colorSpacePrivate->mBA.append(
std::move(bTableElement));
1727 if (isColorSpaceTypeGray) {
1728 rTrc = tagIndex[Tag::kTRC];
1729 gTrc = tagIndex[Tag::kTRC];
1730 bTrc = tagIndex[Tag::kTRC];
1733 rTrc = tagIndex[Tag::aarg];
1734 gTrc = tagIndex[Tag::aagg];
1735 bTrc = tagIndex[Tag::aabg];
1737 rTrc = tagIndex[Tag::rTRC];
1738 gTrc = tagIndex[Tag::gTRC];
1739 bTrc = tagIndex[Tag::bTRC];
1745 if (!parseTRC(QByteArrayView(data).sliced(rTrc.offset, rTrc.size), rCurve, QColorTransferTable::TwoWay)) {
1746 qCWarning(lcIcc) <<
"fromIccProfile: Invalid rTRC";
1749 if (!parseTRC(QByteArrayView(data).sliced(gTrc.offset, gTrc.size), gCurve, QColorTransferTable::TwoWay)) {
1750 qCWarning(lcIcc) <<
"fromIccProfile: Invalid gTRC";
1753 if (!parseTRC(QByteArrayView(data).sliced(bTrc.offset, bTrc.size), bCurve, QColorTransferTable::TwoWay)) {
1754 qCWarning(lcIcc) <<
"fromIccProfile: Invalid bTRC";
1757 if (rCurve == gCurve && gCurve == bCurve) {
1758 if (rCurve.isIdentity()) {
1759 qCDebug(lcIcc) <<
"fromIccProfile: Linear gamma detected";
1761 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
1763 }
else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isGamma()) {
1764 qCDebug(lcIcc) <<
"fromIccProfile: Simple gamma detected";
1766 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
1767 colorspaceDPtr
->gamma = rCurve.m_fun.m_g;
1768 }
else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isSRgb()) {
1769 qCDebug(lcIcc) <<
"fromIccProfile: sRGB gamma detected";
1771 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
1773 colorspaceDPtr->trc[0] = rCurve;
1774 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1776 colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
1777 colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
1779 colorspaceDPtr->trc[0] = rCurve;
1780 colorspaceDPtr->trc[1] = gCurve;
1781 colorspaceDPtr->trc[2] = bCurve;
1782 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1844bool fromIccProfile(
const QByteArray &data, QColorSpace *colorSpace)
1847 qCWarning(lcIcc) <<
"fromIccProfile: failed size sanity 1";
1853 if (qsizetype(header.profileSize) > data.size() || qsizetype(header.profileSize) < qsizetype(
sizeof(ICCProfileHeader))) {
1854 qCWarning(lcIcc) <<
"fromIccProfile: failed size sanity 2";
1859 Q_ASSERT(offsetToData > 0);
1860 if (offsetToData > data.size()) {
1861 qCWarning(lcIcc) <<
"fromIccProfile: failed index size sanity";
1866 for (uint i = 0; i < header
.tagCount; ++i) {
1873 if (qsizetype(tagTable.offset) < offsetToData) {
1874 qCWarning(lcIcc) <<
"fromIccProfile: failed tag offset sanity 1";
1879 qCWarning(lcIcc) <<
"fromIccProfile: failed tag offset sanity 2";
1882 if (tagTable
.size < 8) {
1883 qCWarning(lcIcc) <<
"fromIccProfile: failed minimal tag size sanity";
1887 qCWarning(lcIcc) <<
"fromIccProfile: failed tag offset + size sanity";
1891 qCWarning(lcIcc) <<
"fromIccProfile: invalid tag offset alignment";
1900 bool threeComponentMatrix =
true;
1907 threeComponentMatrix =
false;
1910 qCWarning(lcIcc) <<
"fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
1916 qCWarning(lcIcc) <<
"fromIccProfile: Invalid ICC profile - not valid gray scale based";
1920 threeComponentMatrix =
false;
1922 qCWarning(lcIcc) <<
"fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
1929 colorSpace->detach();
1935 if (parseCicp(data, tagIndex[
Tag::cicp], colorspaceDPtr))
1936 threeComponentMatrix =
true;
1937 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1939 if (colorspaceDPtr->transferFunction != QColorSpace::TransferFunction::Custom)
1943 if (threeComponentMatrix) {
1944 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
1947 if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr))
1949 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1951 if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
1953 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
1957 if (
auto it = tagIndex.constFind(
Tag::chad); it != tagIndex.constEnd()) {
1958 if (!parseChad(data, it.value(), colorspaceDPtr))
1963 if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
1964 colorspaceDPtr->toXyz = colorspaceDPtr->chad;
1967 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1970 if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom &&
1971 !parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
1974 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
1975 if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
1976 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
1978 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1981 if (!parseA2B(data, tagIndex[
Tag::A2B0], colorspaceDPtr,
true))
1983 if (
auto it = tagIndex.constFind(
Tag::B2A0); it != tagIndex.constEnd()) {
1984 if (!parseA2B(data, it.value(), colorspaceDPtr,
false))
1988 if (
auto it = tagIndex.constFind(
Tag::wtpt); it != tagIndex.constEnd()) {
1989 if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
1992 if (
auto it = tagIndex.constFind(
Tag::chad); it != tagIndex.constEnd()) {
1993 if (!parseChad(data, it.value(), colorspaceDPtr))
1995 }
else if (!colorspaceDPtr->whitePoint.isNull()) {
2000 if (
auto it = tagIndex.constFind(
Tag::desc); it != tagIndex.constEnd()) {
2001 if (!parseDesc(data, it.value(), colorspaceDPtr->description))
2002 qCWarning(lcIcc) <<
"fromIccProfile: Failed to parse description";
2004 qCDebug(lcIcc) <<
"fromIccProfile: Description" << colorspaceDPtr->description;
2008 if (colorspaceDPtr->namedColorSpace)
2009 qCDebug(lcIcc) <<
"fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace);
2011 colorspaceDPtr->iccProfile = data;