462static int writeMab(QDataStream &stream,
const QList<QColorSpacePrivate::Element> &abList,
bool isAb,
bool pcsLab,
bool isCmyk)
466 for (
auto &&element : abList)
467 std::visit([&](
auto &&elm) { visitElement(combo, elm, number++, abList); }, element);
475 if (combo
.clut && (combo
.clut->gridPointsX != combo
.clut->gridPointsY ||
476 combo
.clut->gridPointsX != combo
.clut->gridPointsZ ||
477 (combo
.clut->gridPointsW > 1 && combo
.clut->gridPointsX != combo
.clut->gridPointsW)))
495 const int inChannels = (isCmyk && isAb) ? 4 : 3;
496 const int outChannels = (isCmyk && !isAb) ? 4 : 3;
497 stream << uchar(inChannels) << uchar(outChannels);
498 qsizetype gridPointsLut16 = 0;
499 if (lut16 && combo.clut)
500 gridPointsLut16 = combo.clut->gridPointsX;
502 stream << uchar(gridPointsLut16) << uchar(0);
504 stream << quint16(0);
507 stream << toFixedS1516(combo
.inMatrix->r.x);
508 stream << toFixedS1516(combo
.inMatrix->g.x);
509 stream << toFixedS1516(combo
.inMatrix->b.x);
510 stream << toFixedS1516(combo
.inMatrix->r.y);
511 stream << toFixedS1516(combo
.inMatrix->g.y);
512 stream << toFixedS1516(combo
.inMatrix->b.y);
513 stream << toFixedS1516(combo
.inMatrix->r.z);
514 stream << toFixedS1516(combo
.inMatrix->g.z);
515 stream << toFixedS1516(combo
.inMatrix->b.z);
527 int inputEntries = 0, outputEntries = 0;
529 inputEntries = combo
.inTable->trc[0].table().m_tableSize;
533 outputEntries = combo
.outTable->trc[0].table().m_tableSize;
536 stream << quint16(inputEntries);
537 stream << quint16(outputEntries);
540 for (
int j = 0; j < channels; ++j) {
541 if (!table->trc[j].table().m_table16.isEmpty()) {
542 for (
int i = 0; i < entries; ++i)
543 stream << table->trc[j].table().m_table16[i];
545 for (
int i = 0; i < entries; ++i)
546 stream << quint16(table->trc[j].table().m_table8[i] * 257);
550 for (
int j = 0; j < channels; ++j)
551 stream << quint16(0) << quint16(65535);
555 writeTable(combo
.inTable, inputEntries, inChannels);
558 if (isAb && pcsLab) {
559 for (
const QColorVector &v : combo.clut->table) {
560 stream << quint16(v.x * 65280.0f + 0.5f);
561 stream << quint16(v.y * 65280.0f + 0.5f);
562 stream << quint16(v.z * 65280.0f + 0.5f);
565 if (outChannels == 4) {
566 for (
const QColorVector &v : combo.clut->table) {
567 stream << quint16(v.x * 65535.0f + 0.5f);
568 stream << quint16(v.y * 65535.0f + 0.5f);
569 stream << quint16(v.z * 65535.0f + 0.5f);
570 stream << quint16(v.w * 65535.0f + 0.5f);
573 for (
const QColorVector &v : combo.clut->table) {
574 stream << quint16(v.x * 65535.0f + 0.5f);
575 stream << quint16(v.y * 65535.0f + 0.5f);
576 stream << quint16(v.z * 65535.0f + 0.5f);
582 writeTable(combo
.outTable, outputEntries, outChannels);
584 qsizetype offset =
sizeof(Lut16TagData) + 2 * inChannels * inputEntries
585 + 2 * outChannels * outputEntries
586 + 2 * outChannels * intPow(gridPointsLut16, inChannels);
588 stream << quint16(0);
603 buffer2.open(QIODevice::WriteOnly);
604 QDataStream stream2(&buffer2);
605 quint32 bOffset = offset;
606 quint32 matrixOffset = 0;
608 quint32 clutOffset = 0;
611 auto alignTag = [&]() {
613 stream2 << quint16(0);
622 aChannels = inChannels;
624 Q_ASSERT(outChannels == 3);
627 aChannels = outChannels;
629 Q_ASSERT(inChannels == 3);
632 offset += writeColorTrc(stream2, bCurve->trc[0]);
634 offset += writeColorTrc(stream2, bCurve->trc[1]);
636 offset += writeColorTrc(stream2, bCurve->trc[2]);
639 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
640 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
641 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
645 matrixOffset = offset;
647 stream2 << toFixedS1516(combo
.midMatrix->r.x);
648 stream2 << toFixedS1516(combo
.midMatrix->g.x);
649 stream2 << toFixedS1516(combo
.midMatrix->b.x);
650 stream2 << toFixedS1516(combo
.midMatrix->r.y);
651 stream2 << toFixedS1516(combo
.midMatrix->g.y);
652 stream2 << toFixedS1516(combo
.midMatrix->b.y);
653 stream2 << toFixedS1516(combo
.midMatrix->r.z);
654 stream2 << toFixedS1516(combo
.midMatrix->g.z);
655 stream2 << toFixedS1516(combo
.midMatrix->b.z);
679 offset += writeColorTrc(stream2, combo
.midTable->trc[0]);
681 offset += writeColorTrc(stream2, combo
.midTable->trc[1]);
683 offset += writeColorTrc(stream2, combo
.midTable->trc[2]);
686 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
687 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
688 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
692 if (combo
.clut || aCurve) {
695 stream2 << uchar(combo
.clut->gridPointsX);
696 stream2 << uchar(combo
.clut->gridPointsY);
697 stream2 << uchar(combo
.clut->gridPointsZ);
699 stream2 << uchar(combo
.clut->gridPointsW);
702 for (
int i = 0; i < 12; ++i)
704 stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
706 if (outChannels == 4) {
707 for (
const QColorVector &v : combo.clut->table) {
708 stream2 << quint16(v.x * 65535.0f + 0.5f);
709 stream2 << quint16(v.y * 65535.0f + 0.5f);
710 stream2 << quint16(v.z * 65535.0f + 0.5f);
711 stream2 << quint16(v.w * 65535.0f + 0.5f);
714 for (
const QColorVector &v : combo.clut->table) {
715 stream2 << quint16(v.x * 65535.0f + 0.5f);
716 stream2 << quint16(v.y * 65535.0f + 0.5f);
717 stream2 << quint16(v.z * 65535.0f + 0.5f);
720 offset += 2 * outChannels * combo
.clut->table.size();
723 for (
int i = 0; i < 16; ++i)
725 stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
730 offset += writeColorTrc(stream2, aCurve->trc[0]);
732 offset += writeColorTrc(stream2, aCurve->trc[1]);
734 offset += writeColorTrc(stream2, aCurve->trc[2]);
736 if (aChannels == 4) {
737 offset += writeColorTrc(stream2, aCurve->trc[3]);
741 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
742 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
743 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
745 stream2 << uint(
Tag::curv) << uint(0) << uint(0);
746 offset += 12 * aChannels;
751 stream << quint32(bOffset);
752 stream << quint32(matrixOffset);
753 stream << quint32(mOffset);
754 stream << quint32(clutOffset);
755 stream << quint32(aOffset);
756 stream.writeRawData(tagData.data(), tagData.size());
758 return int(
sizeof(
mABTagData) + tagData.size());
764 if (!space.isValid())
768 if (!spaceDPtr->iccProfile.isEmpty())
769 return spaceDPtr->iccProfile;
771 int fixedLengthTagCount = 5;
773 fixedLengthTagCount = 2;
774 else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
775 fixedLengthTagCount = 2;
776 bool writeChad =
false;
777 bool writeB2a =
true;
778 bool writeCicp =
false;
781 fixedLengthTagCount++;
783 int varLengthTagCount = 4;
785 varLengthTagCount = 3;
786 else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
787 varLengthTagCount = 2;
789 if (!space.isValidTarget()) {
794 switch (spaceDPtr->transferFunction) {
795 case QColorSpace::TransferFunction::St2084:
796 case QColorSpace::TransferFunction::Hlg:
798 fixedLengthTagCount++;
804 const int tagCount = fixedLengthTagCount + varLengthTagCount;
805 const uint profileDataOffset = 128 + 4 + 12 * tagCount;
806 uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
808 uint currentOffset = 0;
825 buffer.open(QIODevice::WriteOnly);
826 QDataStream stream(&buffer);
831 stream << uint(0x04400000);
833 switch (spaceDPtr->colorModel) {
834 case QColorSpace::ColorModel::Rgb:
837 case QColorSpace::ColorModel::Gray:
840 case QColorSpace::ColorModel::Cmyk:
843 case QColorSpace::ColorModel::Undefined:
847 stream << uint(0) << uint(0) << uint(0);
849 stream << uint(0) << uint(0) << uint(0);
850 stream << uint(0) << uint(0) << uint(0);
852 stream << uint(0x0000f6d6);
853 stream << uint(0x00010000);
854 stream << uint(0x0000d32d);
855 stream << IccTag(
'Q',
't', QT_VERSION_MAJOR, QT_VERSION_MINOR);
856 stream << uint(0) << uint(0) << uint(0) << uint(0);
857 stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
859 currentOffset = profileDataOffset;
862 stream << uint(tagCount);
863 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
864 stream << uint(
Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
865 stream << uint(
Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
866 stream << uint(
Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
867 currentOffset += 20 + 20 + 20;
869 stream << uint(
Tag::wtpt) << uint(currentOffset + 00) << uint(20);
870 stream << uint(
Tag::cprt) << uint(currentOffset + 20) << uint(34);
871 currentOffset += 20 + 34 + 2;
873 stream << uint(
Tag::chad) << uint(currentOffset) << uint(44);
877 stream << uint(
Tag::cicp) << uint(currentOffset) << uint(12);
881 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
882 stream << uint(
Tag::rTRC) << uint(0) << uint(0);
883 stream << uint(
Tag::gTRC) << uint(0) << uint(0);
884 stream << uint(
Tag::bTRC) << uint(0) << uint(0);
886 stream << uint(
Tag::kTRC) << uint(0) << uint(0);
888 stream << uint(
Tag::desc) << uint(0) << uint(0);
891 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
893 stream << toFixedS1516(spaceDPtr->toXyz.r.x);
894 stream << toFixedS1516(spaceDPtr->toXyz.r.y);
895 stream << toFixedS1516(spaceDPtr->toXyz.r.z);
897 stream << toFixedS1516(spaceDPtr->toXyz.g.x);
898 stream << toFixedS1516(spaceDPtr->toXyz.g.y);
899 stream << toFixedS1516(spaceDPtr->toXyz.g.z);
901 stream << toFixedS1516(spaceDPtr->toXyz.b.x);
902 stream << toFixedS1516(spaceDPtr->toXyz.b.y);
903 stream << toFixedS1516(spaceDPtr->toXyz.b.z);
906 stream << toFixedS1516(spaceDPtr->whitePoint.x);
907 stream << toFixedS1516(spaceDPtr->whitePoint.y);
908 stream << toFixedS1516(spaceDPtr->whitePoint.z);
910 stream << uint(1) << uint(12);
911 stream << uchar(
'e') << uchar(
'n') << uchar(
'U') << uchar(
'S');
912 stream << uint(6) << uint(28);
913 stream << ushort(
'N') << ushort(
'/') << ushort(
'A');
918 stream << toFixedS1516(chad.r.x);
919 stream << toFixedS1516(chad.g.x);
920 stream << toFixedS1516(chad.b.x);
921 stream << toFixedS1516(chad.r.y);
922 stream << toFixedS1516(chad.g.y);
923 stream << toFixedS1516(chad.b.y);
924 stream << toFixedS1516(chad.r.z);
925 stream << toFixedS1516(chad.g.z);
926 stream << toFixedS1516(chad.b.z);
931 if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::St2084)
933 else if (spaceDPtr->transferFunction == QColorSpace::TransferFunction::Hlg)
942 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
943 rTrcOffset = currentOffset;
944 rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
945 currentOffset += rTrcSize;
946 if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
947 gTrcOffset = rTrcOffset;
950 gTrcOffset = currentOffset;
951 gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
952 currentOffset += gTrcSize;
954 if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
955 bTrcOffset = rTrcOffset;
958 bTrcOffset = currentOffset;
959 bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
960 currentOffset += bTrcSize;
963 Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
964 kTrcOffset = currentOffset;
965 kTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
966 currentOffset += kTrcSize;
970 stream << uint(tagCount);
971 stream << uint(
Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
972 stream << uint(
Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
973 currentOffset += 20 + 34 + 2;
975 stream << uint(
Tag::A2B0) << uint(0) << uint(0);
977 stream << uint(
Tag::B2A0) << uint(0) << uint(0);
978 stream << uint(
Tag::desc) << uint(0) << uint(0);
982 stream << toFixedS1516(spaceDPtr->whitePoint.x);
983 stream << toFixedS1516(spaceDPtr->whitePoint.y);
984 stream << toFixedS1516(spaceDPtr->whitePoint.z);
986 stream << uint(1) << uint(12);
987 stream << uchar(
'e') << uchar(
'n') << uchar(
'U') << uchar(
'S');
988 stream << uint(6) << uint(28);
989 stream << ushort(
'N') << ushort(
'/') << ushort(
'A');
993 mA2bOffset = currentOffset;
994 mA2bSize = writeMab(stream, spaceDPtr->mAB,
true, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
995 currentOffset += mA2bSize;
997 mB2aOffset = currentOffset;
998 mB2aSize = writeMab(stream, spaceDPtr->mBA,
false, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
999 currentOffset += mB2aSize;
1004 descOffset = currentOffset;
1005 const QString description = space.description();
1007 stream << uint(1) << uint(12);
1008 stream << uchar(
'e') << uchar(
'n') << uchar(
'U') << uchar(
'S');
1009 stream << uint(description.size() * 2) << uint(28);
1010 for (QChar ch : description)
1011 stream << ushort(ch.unicode());
1012 descSize = 28 + description.size() * 2;
1013 if (description.size() & 1) {
1014 stream << ushort(0);
1017 currentOffset += descSize;
1022 *(
quint32_be *)iccProfile.data() = iccProfile.size();
1025 if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
1026 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
1027 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
1028 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
1029 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
1030 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
1031 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
1032 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
1033 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
1035 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
1036 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
1037 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
1038 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
1041 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
1042 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
1043 variableTagTableOffsets += 12;
1045 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
1046 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
1047 variableTagTableOffsets += 12;
1049 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
1050 *(
quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
1053#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
1055 Q_ASSERT(qsizetype(iccHeader
->profileSize) == qsizetype(iccProfile.size()));
1088 if (tagData.size() < 12)
1095 qCWarning(lcIcc) <<
"Invalid count in curv table";
1098 if (tagData.size() < qsizetype(12 + 2 * curv
.valueCount)) {
1099 qCWarning(lcIcc) <<
"Truncated curv table";
1104 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1107 const quint16 v = qFromBigEndian<quint16>(tagData.constData() + valueOffset);
1108 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1111 QList<quint16> tabl;
1114 "GenericTagData has padding. The following code is a subject to UB.");
1115 qFromBigEndian<quint16>(tagData.constData() + valueOffset, curv.valueCount, tabl.data());
1116 QColorTransferTable table(curv.valueCount, tabl, type);
1118 if (!table.checkValidity()) {
1119 qCWarning(lcIcc) <<
"Invalid curv table";
1121 }
else if (!table.asColorTransferFunction(&curve)) {
1122 gamma.m_type = QColorTrc::Type::Table;
1123 gamma.m_table = table;
1125 qCDebug(lcIcc) <<
"Detected curv table as function";
1126 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1127 gamma.m_fun = curve;
1135 const auto parametersOffset =
sizeof(
ParaTagData);
1136 quint32 parameters[7];
1139 if (tagData.size() < 12 + 1 * 4)
1141 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 1, parameters);
1142 float g = fromFixedS1516(parameters[0]);
1143 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1148 if (tagData.size() < 12 + 3 * 4)
1150 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 3, parameters);
1151 if (parameters[1] == 0)
1153 float g = fromFixedS1516(parameters[0]);
1154 float a = fromFixedS1516(parameters[1]);
1155 float b = fromFixedS1516(parameters[2]);
1157 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1162 if (tagData.size() < 12 + 4 * 4)
1164 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 4, parameters);
1165 if (parameters[1] == 0)
1167 float g = fromFixedS1516(parameters[0]);
1168 float a = fromFixedS1516(parameters[1]);
1169 float b = fromFixedS1516(parameters[2]);
1170 float c = fromFixedS1516(parameters[3]);
1172 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1177 if (tagData.size() < 12 + 5 * 4)
1179 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 5, parameters);
1180 float g = fromFixedS1516(parameters[0]);
1181 float a = fromFixedS1516(parameters[1]);
1182 float b = fromFixedS1516(parameters[2]);
1183 float c = fromFixedS1516(parameters[3]);
1184 float d = fromFixedS1516(parameters[4]);
1185 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1190 if (tagData.size() < 12 + 7 * 4)
1192 qFromBigEndian<quint32>(tagData.constData() + parametersOffset, 7, parameters);
1193 float g = fromFixedS1516(parameters[0]);
1194 float a = fromFixedS1516(parameters[1]);
1195 float b = fromFixedS1516(parameters[2]);
1196 float c = fromFixedS1516(parameters[3]);
1197 float d = fromFixedS1516(parameters[4]);
1198 float e = fromFixedS1516(parameters[5]);
1199 float f = fromFixedS1516(parameters[6]);
1200 gamma.m_type = QColorTrc::Type::ParameterizedFunction;
1205 qCWarning(lcIcc) <<
"Unknown para type" << uint(para.curveType);
1210 qCWarning(lcIcc) <<
"Invalid TRC data type" << Qt::hex << trcData.type;
1239 if (tagEntry.size <
sizeof(T)) {
1240 qCWarning(lcIcc) <<
"Undersized lut8/lut16 tag";
1243 if (qsizetype(tagEntry.size) > data.size()) {
1244 qCWarning(lcIcc) <<
"Truncated lut8/lut16 tag";
1247 using S = std::conditional_t<std::is_same_v<T, Lut8TagData>, uint8_t, uint16_t>;
1248 const T lut = qFromUnaligned<T>(data.constData() + tagEntry.offset);
1249 int inputTableEntries, outputTableEntries, precision;
1251 Q_ASSERT(lut.type == quint32(
Tag::mft1));
1253 qCWarning(lcIcc) <<
"Lut8 can not output XYZ values";
1256 inputTableEntries = 256;
1257 outputTableEntries = 256;
1260 Q_ASSERT(lut.type == quint32(
Tag::mft2));
1261 inputTableEntries = lut.inputTableEntries;
1262 outputTableEntries = lut.outputTableEntries;
1263 if (inputTableEntries < 2 || inputTableEntries > 4096)
1265 if (outputTableEntries < 2 || outputTableEntries > 4096)
1270 bool inTableIsLinear =
true, outTableIsLinear =
true;
1276 matrixElement.r.x = fromFixedS1516(lut.e1);
1277 matrixElement.g.x = fromFixedS1516(lut.e2);
1278 matrixElement.b.x = fromFixedS1516(lut.e3);
1279 matrixElement.r.y = fromFixedS1516(lut.e4);
1280 matrixElement.g.y = fromFixedS1516(lut.e5);
1281 matrixElement.b.y = fromFixedS1516(lut.e6);
1282 matrixElement.r.z = fromFixedS1516(lut.e7);
1283 matrixElement.g.z = fromFixedS1516(lut.e8);
1284 matrixElement.b.z = fromFixedS1516(lut.e9);
1286 qCWarning(lcIcc) <<
"Invalid matrix values in lut8/lut16";
1290 const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1291 const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1293 if (lut.inputChannels != inputChannels) {
1294 qCWarning(lcIcc) <<
"Unsupported lut8/lut16 input channel count" << lut.inputChannels;
1298 if (lut.outputChannels != outputChannels) {
1299 qCWarning(lcIcc) <<
"Unsupported lut8/lut16 output channel count" << lut.outputChannels;
1303 const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
1304 if (tagEntry.size < (
sizeof(T) + precision * inputChannels * inputTableEntries
1305 + precision * outputChannels * outputTableEntries
1306 + precision * outputChannels * clutTableSize)) {
1307 qCWarning(lcIcc) <<
"Undersized lut8/lut16 tag, no room for tables";
1310 if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && clutTableSize == 0) {
1311 qCWarning(lcIcc) <<
"Cmyk conversion must have a CLUT";
1315 const uint8_t *tableData =
reinterpret_cast<
const uint8_t *>(data.constData() + tagEntry.offset +
sizeof(T));
1317 for (
int j = 0; j < inputChannels; ++j) {
1318 QList<S> input(inputTableEntries);
1319 qFromBigEndian<S>(tableData, inputTableEntries, input.data());
1320 QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
1321 if (!table.checkValidity()) {
1322 qCWarning(lcIcc) <<
"Bad input table in lut8/lut16";
1325 if (!table.isIdentity())
1326 inTableIsLinear =
false;
1327 inTableElement.trc[j] =
std::move(table);
1328 tableData += inputTableEntries * precision;
1331 clutElement.table.resize(clutTableSize);
1332 clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
1333 if (inputChannels == 4)
1334 clutElement.gridPointsW = lut.clutGridPoints;
1337 parseCLUT(tableData, 1.f / 255.f, &clutElement, outputChannels);
1339 float f = 1.0f / 65535.f;
1342 QList<S> clutTable(clutTableSize * outputChannels);
1343 qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
1344 parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
1346 tableData += clutTableSize * outputChannels * precision;
1348 for (
int j = 0; j < outputChannels; ++j) {
1349 QList<S> output(outputTableEntries);
1350 qFromBigEndian<S>(tableData, outputTableEntries, output.data());
1351 QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
1352 if (!table.checkValidity()) {
1353 qCWarning(lcIcc) <<
"Bad output table in lut8/lut16";
1356 if (!table.isIdentity())
1357 outTableIsLinear =
false;
1358 outTableElement.trc[j] =
std::move(table);
1359 tableData += outputTableEntries * precision;
1363 if (!inTableIsLinear)
1364 colorSpacePrivate->mAB.append(inTableElement);
1366 colorSpacePrivate->mAB.append(clutElement);
1367 if (!outTableIsLinear || colorSpacePrivate->mAB.isEmpty())
1368 colorSpacePrivate->mAB.append(outTableElement);
1372 colorSpacePrivate->mBA.append(matrixElement);
1373 if (!inTableIsLinear)
1374 colorSpacePrivate->mBA.append(inTableElement);
1376 colorSpacePrivate->mBA.append(clutElement);
1377 if (!outTableIsLinear || colorSpacePrivate->mBA.isEmpty())
1378 colorSpacePrivate->mBA.append(outTableElement);
1387 qCWarning(lcIcc) <<
"Undersized mAB/mBA tag";
1390 if (qsizetype(tagEntry.size) > data.size()) {
1391 qCWarning(lcIcc) <<
"Truncated mAB/mBA tag";
1396 qCWarning(lcIcc) <<
"Bad mAB/mBA content type";
1400 const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1401 const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
1403 if (mab.inputChannels != inputChannels) {
1404 qCWarning(lcIcc) <<
"Unsupported mAB/mBA input channel count" << mab.inputChannels;
1408 if (mab.outputChannels != outputChannels) {
1409 qCWarning(lcIcc) <<
"Unsupported mAB/mBA output channel count" << mab.outputChannels;
1415 qCWarning(lcIcc) <<
"Illegal mAB/mBA without B table";
1420 qCWarning(lcIcc) <<
"Illegal mAB/mBA element combination";
1429 qCWarning(lcIcc) <<
"Illegal mAB/mBA element offset";
1438 QColorVector offsetElement;
1440 auto parseCurves = [&data, &tagEntry] (uint curvesOffset, QColorTrc *table,
int channels) {
1441 for (
int i = 0; i < channels; ++i) {
1442 if (qsizetype(tagEntry.offset + curvesOffset + 12) > data.size() || curvesOffset + 12 > tagEntry.size) {
1443 qCWarning(lcIcc) <<
"Space missing for channel curves in mAB/mBA";
1446 auto size = parseTRC(QByteArrayView(data).sliced(tagEntry.offset + curvesOffset, tagEntry.size - curvesOffset), table[i], QColorTransferTable::OneWay);
1449 if (size & 2) size += 2;
1450 curvesOffset += size;
1455 bool bCurvesAreLinear =
true, aCurvesAreLinear =
true, mCurvesAreLinear =
true;
1458 if (!parseCurves(mab
.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
1462 bCurvesAreLinear = bTableElement.trc[0].isIdentity() && bTableElement.trc[1].isIdentity() && bTableElement.trc[2].isIdentity();
1467 if (!parseCurves(mab
.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
1471 aCurvesAreLinear = aTableElement.trc[0].isIdentity() && aTableElement.trc[1].isIdentity() && aTableElement.trc[2].isIdentity();
1481 mCurvesAreLinear = mTableElement.trc[0].isIdentity() && mTableElement.trc[1].isIdentity() && mTableElement.trc[2].isIdentity();
1500 if (!matrixElement
.isValid() || !offsetElement.isValid()) {
1501 qCWarning(lcIcc) <<
"Invalid matrix values in mAB/mBA element";
1508 clutElement.gridPointsX = uint8_t(data[tagEntry.offset + mab
.clutOffset]);
1509 clutElement.gridPointsY = uint8_t(data[tagEntry.offset + mab
.clutOffset + 1]);
1510 clutElement.gridPointsZ = uint8_t(data[tagEntry.offset + mab
.clutOffset + 2]);
1511 clutElement.gridPointsW =
std::max(uint8_t(data[tagEntry.offset + mab
.clutOffset + 3]), uint8_t(1));
1512 const uchar precision = data[tagEntry.offset + mab
.clutOffset + 16];
1513 if (precision > 2 || precision < 1) {
1514 qCWarning(lcIcc) <<
"Invalid mAB/mBA element CLUT precision";
1517 if (clutElement.gridPointsX < 2 || clutElement.gridPointsY < 2 || clutElement.gridPointsZ < 2) {
1521 const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
1522 if ((mab
.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
1523 qCWarning(lcIcc) <<
"CLUT oversized for tag";
1527 clutElement.table.resize(clutTableSize);
1528 if (precision == 2) {
1529 QList<uint16_t> clutTable(clutTableSize * outputChannels);
1530 qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
1531 parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, outputChannels);
1533 const uint8_t *clutTable =
reinterpret_cast<
const uint8_t *>(data.constData() + tagEntry.offset + mab
.clutOffset + 20);
1534 parseCLUT(clutTable, (1.f/255.f), &clutElement, outputChannels);
1536 }
else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
1537 qCWarning(lcIcc) <<
"Cmyk conversion must have a CLUT";
1543 if (!aCurvesAreLinear)
1544 colorSpacePrivate->mAB.append(
std::move(aTableElement));
1546 colorSpacePrivate->mAB.append(
std::move(clutElement));
1549 if (!mCurvesAreLinear)
1550 colorSpacePrivate->mAB.append(
std::move(mTableElement));
1552 colorSpacePrivate->mAB.append(
std::move(matrixElement));
1553 if (!offsetElement.isNull())
1554 colorSpacePrivate->mAB.append(
std::move(offsetElement));
1556 if (!bCurvesAreLinear|| colorSpacePrivate->mAB.isEmpty())
1557 colorSpacePrivate->mAB.append(
std::move(bTableElement));
1559 if (!bCurvesAreLinear)
1560 colorSpacePrivate->mBA.append(
std::move(bTableElement));
1563 colorSpacePrivate->mBA.append(
std::move(matrixElement));
1564 if (!offsetElement.isNull())
1565 colorSpacePrivate->mBA.append(
std::move(offsetElement));
1566 if (!mCurvesAreLinear)
1567 colorSpacePrivate->mBA.append(
std::move(mTableElement));
1571 colorSpacePrivate->mBA.append(
std::move(clutElement));
1572 if (!aCurvesAreLinear)
1573 colorSpacePrivate->mBA.append(
std::move(aTableElement));
1575 if (colorSpacePrivate->mBA.isEmpty())
1576 colorSpacePrivate->mBA.append(
std::move(bTableElement));
1728 if (isColorSpaceTypeGray) {
1729 rTrc = tagIndex[Tag::kTRC];
1730 gTrc = tagIndex[Tag::kTRC];
1731 bTrc = tagIndex[Tag::kTRC];
1734 rTrc = tagIndex[Tag::aarg];
1735 gTrc = tagIndex[Tag::aagg];
1736 bTrc = tagIndex[Tag::aabg];
1738 rTrc = tagIndex[Tag::rTRC];
1739 gTrc = tagIndex[Tag::gTRC];
1740 bTrc = tagIndex[Tag::bTRC];
1746 if (!parseTRC(QByteArrayView(data).sliced(rTrc.offset, rTrc.size), rCurve, QColorTransferTable::TwoWay)) {
1747 qCWarning(lcIcc) <<
"fromIccProfile: Invalid rTRC";
1750 if (!parseTRC(QByteArrayView(data).sliced(gTrc.offset, gTrc.size), gCurve, QColorTransferTable::TwoWay)) {
1751 qCWarning(lcIcc) <<
"fromIccProfile: Invalid gTRC";
1754 if (!parseTRC(QByteArrayView(data).sliced(bTrc.offset, bTrc.size), bCurve, QColorTransferTable::TwoWay)) {
1755 qCWarning(lcIcc) <<
"fromIccProfile: Invalid bTRC";
1758 if (rCurve == gCurve && gCurve == bCurve) {
1759 if (rCurve.isIdentity()) {
1760 qCDebug(lcIcc) <<
"fromIccProfile: Linear gamma detected";
1762 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Linear;
1764 }
else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isGamma()) {
1765 qCDebug(lcIcc) <<
"fromIccProfile: Simple gamma detected";
1767 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Gamma;
1768 colorspaceDPtr
->gamma = rCurve.m_fun.m_g;
1769 }
else if (rCurve.m_type == QColorTrc::Type::ParameterizedFunction && rCurve.m_fun.isSRgb()) {
1770 qCDebug(lcIcc) <<
"fromIccProfile: sRGB gamma detected";
1772 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::SRgb;
1774 colorspaceDPtr->trc[0] = rCurve;
1775 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1777 colorspaceDPtr->trc[1] = colorspaceDPtr->trc[0];
1778 colorspaceDPtr->trc[2] = colorspaceDPtr->trc[0];
1780 colorspaceDPtr->trc[0] = rCurve;
1781 colorspaceDPtr->trc[1] = gCurve;
1782 colorspaceDPtr->trc[2] = bCurve;
1783 colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
1845bool fromIccProfile(
const QByteArray &data, QColorSpace *colorSpace)
1848 qCWarning(lcIcc) <<
"fromIccProfile: failed size sanity 1";
1854 if (qsizetype(header.profileSize) > data.size() || qsizetype(header.profileSize) < qsizetype(
sizeof(ICCProfileHeader))) {
1855 qCWarning(lcIcc) <<
"fromIccProfile: failed size sanity 2";
1860 Q_ASSERT(offsetToData > 0);
1861 if (offsetToData > data.size()) {
1862 qCWarning(lcIcc) <<
"fromIccProfile: failed index size sanity";
1867 for (uint i = 0; i < header
.tagCount; ++i) {
1874 if (qsizetype(tagTable.offset) < offsetToData) {
1875 qCWarning(lcIcc) <<
"fromIccProfile: failed tag offset sanity 1";
1880 qCWarning(lcIcc) <<
"fromIccProfile: failed tag offset sanity 2";
1883 if (tagTable
.size < 8) {
1884 qCWarning(lcIcc) <<
"fromIccProfile: failed minimal tag size sanity";
1888 qCWarning(lcIcc) <<
"fromIccProfile: failed tag offset + size sanity";
1892 qCWarning(lcIcc) <<
"fromIccProfile: invalid tag offset alignment";
1901 bool threeComponentMatrix =
true;
1908 threeComponentMatrix =
false;
1911 qCWarning(lcIcc) <<
"fromIccProfile: Invalid ICC profile - neither valid three component nor n-LUT";
1917 qCWarning(lcIcc) <<
"fromIccProfile: Invalid ICC profile - not valid gray scale based";
1921 threeComponentMatrix =
false;
1923 qCWarning(lcIcc) <<
"fromIccProfile: Invalid ICC profile - CMYK, not n-LUT";
1930 colorSpace->detach();
1936 if (parseCicp(data, tagIndex[
Tag::cicp], colorspaceDPtr))
1937 threeComponentMatrix =
true;
1938 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1940 if (colorspaceDPtr->transferFunction != QColorSpace::TransferFunction::Custom)
1944 if (threeComponentMatrix) {
1945 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ThreeComponentMatrix;
1948 if (colorspaceDPtr->primaries == QColorSpace::Primaries::Custom && !parseRgbMatrix(data, tagIndex, colorspaceDPtr))
1950 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1952 if (!parseGrayMatrix(data, tagIndex, colorspaceDPtr))
1954 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Gray;
1958 if (
auto it = tagIndex.constFind(
Tag::chad); it != tagIndex.constEnd()) {
1959 if (!parseChad(data, it.value(), colorspaceDPtr))
1964 if (colorspaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
1965 colorspaceDPtr->toXyz = colorspaceDPtr->chad;
1968 if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
1971 if (colorspaceDPtr->transferFunction == QColorSpace::TransferFunction::Custom &&
1972 !parseTRCs(data, tagIndex, colorspaceDPtr, header.inputColorSpace == uint(ColorSpaceType::Gray)))
1975 colorspaceDPtr->transformModel = QColorSpace::TransformModel::ElementListProcessing;
1976 if (header.inputColorSpace == uint(ColorSpaceType::Cmyk))
1977 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Cmyk;
1979 colorspaceDPtr->colorModel = QColorSpace::ColorModel::Rgb;
1982 if (!parseA2B(data, tagIndex[
Tag::A2B0], colorspaceDPtr,
true))
1984 if (
auto it = tagIndex.constFind(
Tag::B2A0); it != tagIndex.constEnd()) {
1985 if (!parseA2B(data, it.value(), colorspaceDPtr,
false))
1989 if (
auto it = tagIndex.constFind(
Tag::wtpt); it != tagIndex.constEnd()) {
1990 if (!parseXyzData(data, it.value(), colorspaceDPtr->whitePoint))
1993 if (
auto it = tagIndex.constFind(
Tag::chad); it != tagIndex.constEnd()) {
1994 if (!parseChad(data, it.value(), colorspaceDPtr))
1996 }
else if (!colorspaceDPtr->whitePoint.isNull()) {
2001 if (
auto it = tagIndex.constFind(
Tag::desc); it != tagIndex.constEnd()) {
2002 if (!parseDesc(data, it.value(), colorspaceDPtr->description))
2003 qCWarning(lcIcc) <<
"fromIccProfile: Failed to parse description";
2005 qCDebug(lcIcc) <<
"fromIccProfile: Description" << colorspaceDPtr->description;
2009 if (colorspaceDPtr->namedColorSpace)
2010 qCDebug(lcIcc) <<
"fromIccProfile: Named colorspace detected: " << QColorSpace::NamedColorSpace(colorspaceDPtr->namedColorSpace);
2012 colorspaceDPtr->iccProfile = data;