Complete color space toICC write

Also write gray, CMYK and mAB RGB color space profiles.

Fixes: QTBUG-125302
Change-Id: Id3b3b64537b9c08f1d40b8243c228ad111d08289
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2024-02-28 16:31:39 +01:00
parent 57595bd90a
commit 53ad7b2d9b
5 changed files with 631 additions and 124 deletions

View File

@ -1423,13 +1423,16 @@ QDebug operator<<(QDebug dbg, const QColorSpace &colorSpace)
if (colorSpace.d_ptr) {
if (colorSpace.d_ptr->namedColorSpace)
dbg << colorSpace.d_ptr->namedColorSpace << ", ";
else
dbg << colorSpace.colorModel() << ", ";
if (!colorSpace.isValid()) {
dbg << "Invalid";
if (!colorSpace.d_ptr->iccProfile.isEmpty())
dbg << " with profile data";
} else if (colorSpace.d_ptr->isThreeComponentMatrix()) {
dbg << colorSpace.primaries() << ", " << colorSpace.transferFunction();
dbg << ", gamma=" << colorSpace.gamma();
if (colorSpace.transferFunction() == QColorSpace::TransferFunction::Gamma)
dbg << "=" << colorSpace.gamma();
} else {
if (colorSpace.d_ptr->isPcsLab)
dbg << "PCSLab, ";

View File

@ -86,6 +86,8 @@ public:
friend inline bool operator!=(const QColorTrc &o1, const QColorTrc &o2);
friend inline bool operator==(const QColorTrc &o1, const QColorTrc &o2);
const QColorTransferTable &table() const { return m_table; }
Type m_type;
QColorTransferFunction m_fun;
QColorTransferTable m_table;

View File

@ -264,7 +264,11 @@ struct MatrixElement {
static int toFixedS1516(float x)
{
return int(x * 65536.0f + 0.5f);
if (x < float(SHRT_MIN))
return INT_MIN;
if (x > float(SHRT_MAX))
return INT_MAX;
return qRound(x * 65536.0f);
}
static float fromFixedS1516(int x)
@ -368,31 +372,431 @@ static int writeColorTrc(QDataStream &stream, const QColorTrc &trc)
return 12 + 2 * trc.m_table.m_tableSize;
}
// very simple version for small values (<=4) of exp.
static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
{
return (exp <= 1) ? x : x * intPow(x, exp - 1);
}
struct ElementCombo
{
const QColorMatrix *inMatrix = nullptr;
const QColorSpacePrivate::TransferElement *inTable = nullptr;
const QColorCLUT *clut = nullptr;
const QColorSpacePrivate::TransferElement *midTable = nullptr;
const QColorMatrix *midMatrix = nullptr;
const QColorVector *midOffset = nullptr;
const QColorSpacePrivate::TransferElement *outTable = nullptr;
};
static void visitElement(ElementCombo &combo, const QColorSpacePrivate::TransferElement &element, int number,
const QList<QColorSpacePrivate::Element> &list)
{
if (number == 0)
combo.inTable = &element;
else if (number == list.size() - 1)
combo.outTable = &element;
else if (number == 1 && combo.inMatrix)
combo.inTable = &element;
else
combo.midTable = &element;
}
static void visitElement(ElementCombo &combo, const QColorMatrix &element, int number,
const QList<QColorSpacePrivate::Element> &)
{
if (number == 0)
combo.inMatrix = &element;
else
combo.midMatrix = &element;
}
static void visitElement(ElementCombo &combo, const QColorVector &element, int,
const QList<QColorSpacePrivate::Element> &)
{
combo.midOffset = &element;
}
static void visitElement(ElementCombo &combo, const QColorCLUT &element, int,
const QList<QColorSpacePrivate::Element> &)
{
combo.clut = &element;
}
static bool isTableTrc(const QColorSpacePrivate::TransferElement *transfer)
{
int i = 0;
while (i < 4 && transfer->trc[i].isValid()) {
if (transfer->trc[i].m_type != QColorTrc::Type::Table)
return false;
i++;
}
return i > 0;
}
static bool isTableTrcSingleSize(const QColorSpacePrivate::TransferElement *transfer)
{
Q_ASSERT(transfer->trc[0].m_type == QColorTrc::Type::Table);
int i = 1;
const uint32_t size = transfer->trc[0].table().m_tableSize;
while (i < 4 && transfer->trc[i].isValid()) {
if (transfer->trc[i].table().m_tableSize != size)
return false;
i++;
}
return true;
}
static int writeMab(QDataStream &stream, const QList<QColorSpacePrivate::Element> &abList, bool isAb, bool pcsLab, bool isCmyk)
{
int number = 0;
ElementCombo combo;
for (auto &&element : abList)
std::visit([&](auto &&elm) { visitElement(combo, elm, number++, abList); }, element);
Q_ASSERT(!(combo.inMatrix && combo.midMatrix));
// qWarning() << Q_FUNC_INFO << bool(combo.inMatrix) << bool(combo.inTable) << bool(combo.clut) << bool(combo.midTable) << bool(combo.midMatrix) << bool(combo.midOffset) << bool(combo.outTable);
bool lut16 = true;
if (combo.midMatrix || combo.midTable || combo.midOffset)
lut16 = false;
if (combo.clut && (combo.clut->gridPointsX != combo.clut->gridPointsY ||
combo.clut->gridPointsX != combo.clut->gridPointsZ ||
(combo.clut->gridPointsW > 1 && combo.clut->gridPointsX != combo.clut->gridPointsW)))
lut16 = false;
if (lut16 && combo.inTable)
lut16 = isTableTrc(combo.inTable) && isTableTrcSingleSize(combo.inTable);
if (lut16 && combo.outTable)
lut16 = isTableTrc(combo.outTable) && isTableTrcSingleSize(combo.outTable);
if (!lut16) {
if (combo.inMatrix)
qSwap(combo.inMatrix, combo.midMatrix);
if (isAb)
stream << uint(Tag::mAB_) << uint(0);
else
stream << uint(Tag::mBA_) << uint(0);
} else {
stream << uint(Tag::mft2) << uint(0);
}
const int inChannels = (isCmyk && isAb) ? 4 : 3;
const int outChannels = (isCmyk && !isAb) ? 4 : 3;
stream << uchar(inChannels) << uchar(outChannels);
qsizetype gridPointsLut16 = 0;
if (lut16 && combo.clut)
gridPointsLut16 = combo.clut->gridPointsX;
if (lut16)
stream << uchar(gridPointsLut16) << uchar(0);
else
stream << quint16(0);
if (lut16) {
if (combo.inMatrix) {
stream << toFixedS1516(combo.inMatrix->r.x);
stream << toFixedS1516(combo.inMatrix->g.x);
stream << toFixedS1516(combo.inMatrix->b.x);
stream << toFixedS1516(combo.inMatrix->r.y);
stream << toFixedS1516(combo.inMatrix->g.y);
stream << toFixedS1516(combo.inMatrix->b.y);
stream << toFixedS1516(combo.inMatrix->r.z);
stream << toFixedS1516(combo.inMatrix->g.z);
stream << toFixedS1516(combo.inMatrix->b.z);
} else {
stream << toFixedS1516(1.0f);
stream << toFixedS1516(0.0f);
stream << toFixedS1516(0.0f);
stream << toFixedS1516(0.0f);
stream << toFixedS1516(1.0f);
stream << toFixedS1516(0.0f);
stream << toFixedS1516(0.0f);
stream << toFixedS1516(0.0f);
stream << toFixedS1516(1.0f);
}
int inputEntries = 0, outputEntries = 0;
if (combo.inTable)
inputEntries = combo.inTable->trc[0].table().m_tableSize;
else
inputEntries = 2;
if (combo.outTable)
outputEntries = combo.outTable->trc[0].table().m_tableSize;
else
outputEntries = 2;
stream << quint16(inputEntries);
stream << quint16(outputEntries);
auto writeTable = [&](const QColorSpacePrivate::TransferElement *table, int entries, int channels) {
if (table) {
for (int j = 0; j < channels; ++j) {
if (!table->trc[j].table().m_table16.isEmpty()) {
for (int i = 0; i < entries; ++i)
stream << table->trc[j].table().m_table16[i];
} else {
for (int i = 0; i < entries; ++i)
stream << quint16(table->trc[j].table().m_table8[i] * 257);
}
}
} else {
for (int j = 0; j < channels; ++j)
stream << quint16(0) << quint16(65535);
}
};
writeTable(combo.inTable, inputEntries, inChannels);
if (combo.clut) {
if (isAb && pcsLab) {
for (const QColorVector &v : combo.clut->table) {
stream << quint16(v.x * 65280.0f + 0.5f);
stream << quint16(v.y * 65280.0f + 0.5f);
stream << quint16(v.z * 65280.0f + 0.5f);
}
} else {
if (outChannels == 4) {
for (const QColorVector &v : combo.clut->table) {
stream << quint16(v.x * 65535.0f + 0.5f);
stream << quint16(v.y * 65535.0f + 0.5f);
stream << quint16(v.z * 65535.0f + 0.5f);
stream << quint16(v.w * 65535.0f + 0.5f);
}
} else {
for (const QColorVector &v : combo.clut->table) {
stream << quint16(v.x * 65535.0f + 0.5f);
stream << quint16(v.y * 65535.0f + 0.5f);
stream << quint16(v.z * 65535.0f + 0.5f);
}
}
}
}
writeTable(combo.outTable, outputEntries, outChannels);
qsizetype offset = sizeof(Lut16TagData) + 2 * inChannels * inputEntries
+ 2 * outChannels * outputEntries
+ 2 * outChannels * intPow(gridPointsLut16, inChannels);
if (offset & 0x2) {
stream << quint16(0);
offset += 2;
}
return offset;
} else {
// mAB/mBA tag:
if (isAb) {
if (!combo.clut && combo.inTable && combo.midMatrix && !combo.midTable)
std::swap(combo.inTable, combo.midTable);
} else {
if (!combo.clut && combo.outTable && combo.midMatrix && !combo.midTable)
std::swap(combo.outTable, combo.midTable);
}
quint32 offset = sizeof(mABTagData);
QBuffer buffer2;
buffer2.open(QIODevice::WriteOnly);
QDataStream stream2(&buffer2);
quint32 bOffset = offset;
quint32 matrixOffset = 0;
quint32 mOffset = 0;
quint32 clutOffset = 0;
quint32 aOffset = 0;
// Tags must start on 4 byte offsets, but sampled curves might have sizes 2-byte aligned
auto alignTag = [&]() {
if (offset & 0x2) {
stream2 << quint16(0);
offset += 2;
}
};
const QColorSpacePrivate::TransferElement *aCurve, *bCurve;
int aChannels;
if (isAb) {
aCurve = combo.inTable;
aChannels = inChannels;
bCurve = combo.outTable;
Q_ASSERT(outChannels == 3);
} else {
aCurve = combo.outTable;
aChannels = outChannels;
bCurve = combo.inTable;
Q_ASSERT(inChannels == 3);
}
if (bCurve) {
offset += writeColorTrc(stream2, bCurve->trc[0]);
alignTag();
offset += writeColorTrc(stream2, bCurve->trc[1]);
alignTag();
offset += writeColorTrc(stream2, bCurve->trc[2]);
alignTag();
} else {
stream2 << uint(Tag::curv) << uint(0) << uint(0);
stream2 << uint(Tag::curv) << uint(0) << uint(0);
stream2 << uint(Tag::curv) << uint(0) << uint(0);
offset += 12 * 3;
}
if (combo.midMatrix || combo.midOffset || combo.midTable) {
matrixOffset = offset;
if (combo.midMatrix) {
stream2 << toFixedS1516(combo.midMatrix->r.x);
stream2 << toFixedS1516(combo.midMatrix->g.x);
stream2 << toFixedS1516(combo.midMatrix->b.x);
stream2 << toFixedS1516(combo.midMatrix->r.y);
stream2 << toFixedS1516(combo.midMatrix->g.y);
stream2 << toFixedS1516(combo.midMatrix->b.y);
stream2 << toFixedS1516(combo.midMatrix->r.z);
stream2 << toFixedS1516(combo.midMatrix->g.z);
stream2 << toFixedS1516(combo.midMatrix->b.z);
} else {
stream2 << toFixedS1516(1.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(1.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(1.0f);
}
if (combo.midOffset) {
stream2 << toFixedS1516(combo.midOffset->x);
stream2 << toFixedS1516(combo.midOffset->y);
stream2 << toFixedS1516(combo.midOffset->z);
} else {
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(0.0f);
stream2 << toFixedS1516(0.0f);
}
offset += 12 * 4;
mOffset = offset;
if (combo.midTable) {
offset += writeColorTrc(stream2, combo.midTable->trc[0]);
alignTag();
offset += writeColorTrc(stream2, combo.midTable->trc[1]);
alignTag();
offset += writeColorTrc(stream2, combo.midTable->trc[2]);
alignTag();
} else {
stream2 << uint(Tag::curv) << uint(0) << uint(0);
stream2 << uint(Tag::curv) << uint(0) << uint(0);
stream2 << uint(Tag::curv) << uint(0) << uint(0);
offset += 12 * 3;
}
}
if (combo.clut || aCurve) {
clutOffset = offset;
if (combo.clut) {
stream2 << uchar(combo.clut->gridPointsX);
stream2 << uchar(combo.clut->gridPointsY);
stream2 << uchar(combo.clut->gridPointsZ);
if (inChannels == 4)
stream2 << uchar(combo.clut->gridPointsW);
else
stream2 << uchar(0);
for (int i = 0; i < 12; ++i)
stream2 << uchar(0);
stream2 << uchar(2) << uchar(0) << uchar(0) << uchar(0);
offset += 20;
if (outChannels == 4) {
for (const QColorVector &v : combo.clut->table) {
stream2 << quint16(v.x * 65535.0f + 0.5f);
stream2 << quint16(v.y * 65535.0f + 0.5f);
stream2 << quint16(v.z * 65535.0f + 0.5f);
stream2 << quint16(v.w * 65535.0f + 0.5f);
}
} else {
for (const QColorVector &v : combo.clut->table) {
stream2 << quint16(v.x * 65535.0f + 0.5f);
stream2 << quint16(v.y * 65535.0f + 0.5f);
stream2 << quint16(v.z * 65535.0f + 0.5f);
}
}
offset += 2 * outChannels * combo.clut->table.size();
alignTag();
} else {
for (int i = 0; i < 16; ++i)
stream2 << uchar(0);
stream2 << uchar(1) << uchar(0) << uchar(0) << uchar(0);
offset += 20;
}
aOffset = offset;
if (aCurve) {
offset += writeColorTrc(stream2, aCurve->trc[0]);
alignTag();
offset += writeColorTrc(stream2, aCurve->trc[1]);
alignTag();
offset += writeColorTrc(stream2, aCurve->trc[2]);
alignTag();
if (aChannels == 4) {
offset += writeColorTrc(stream2, aCurve->trc[3]);
alignTag();
}
} else {
stream2 << uint(Tag::curv) << uint(0) << uint(0);
stream2 << uint(Tag::curv) << uint(0) << uint(0);
stream2 << uint(Tag::curv) << uint(0) << uint(0);
if (aChannels == 4)
stream2 << uint(Tag::curv) << uint(0) << uint(0);
offset += 12 * aChannels;
}
}
buffer2.close();
QByteArray tagData = buffer2.buffer();
stream << quint32(bOffset);
stream << quint32(matrixOffset);
stream << quint32(mOffset);
stream << quint32(clutOffset);
stream << quint32(aOffset);
stream.writeRawData(tagData.data(), tagData.size());
return int(sizeof(mABTagData) + tagData.size());
}
}
QByteArray toIccProfile(const QColorSpace &space)
{
if (!space.isValid())
return QByteArray();
const QColorSpacePrivate *spaceDPtr = QColorSpacePrivate::get(space);
// This should catch anything not three component matrix based as we can only get that from parsed ICC
if (!spaceDPtr->iccProfile.isEmpty())
return spaceDPtr->iccProfile;
Q_ASSERT(spaceDPtr->isThreeComponentMatrix());
int fixedLengthTagCount = 5;
if (!spaceDPtr->isThreeComponentMatrix())
fixedLengthTagCount = 2;
else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
fixedLengthTagCount = 2;
bool writeChad = false;
if (!spaceDPtr->whitePoint.isNull() && spaceDPtr->whitePoint != QColorVector::D50()) {
bool writeB2a = true;
if (spaceDPtr->isThreeComponentMatrix() && !spaceDPtr->chad.isIdentity()) {
writeChad = true;
fixedLengthTagCount++;
}
int varLengthTagCount = 4;
if (!spaceDPtr->isThreeComponentMatrix())
varLengthTagCount = 3;
else if (spaceDPtr->colorModel == QColorSpace::ColorModel::Gray)
varLengthTagCount = 2;
const int tagCount = fixedLengthTagCount + 4;
if (!space.isValidTarget()) {
writeB2a = false;
Q_ASSERT(!spaceDPtr->isThreeComponentMatrix());
varLengthTagCount--;
}
const int tagCount = fixedLengthTagCount + varLengthTagCount;
const uint profileDataOffset = 128 + 4 + 12 * tagCount;
const uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
uint currentOffset = 0;
uint rTrcOffset, gTrcOffset, bTrcOffset;
uint rTrcSize, gTrcSize, bTrcSize;
uint descOffset, descSize;
uint rTrcOffset = 0;
uint gTrcOffset = 0;
uint bTrcOffset = 0;
uint kTrcOffset = 0;
uint rTrcSize = 0;
uint gTrcSize = 0;
uint bTrcSize = 0;
uint kTrcSize = 0;
uint descOffset = 0;
uint descSize = 0;
uint mA2bOffset = 0;
uint mB2aOffset = 0;
uint mA2bSize = 0;
uint mB2aSize = 0;
QBuffer buffer;
buffer.open(QIODevice::WriteOnly);
@ -403,13 +807,25 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0);
stream << uint(0x04400000); // Version 4.4
stream << uint(ProfileClass::Display);
stream << uint(Tag::RGB_);
switch (spaceDPtr->colorModel) {
case QColorSpace::ColorModel::Rgb:
stream << uint(ColorSpaceType::Rgb);
break;
case QColorSpace::ColorModel::Gray:
stream << uint(ColorSpaceType::Gray);
break;
case QColorSpace::ColorModel::Cmyk:
stream << uint(ColorSpaceType::Cmyk);
break;
case QColorSpace::ColorModel::Undefined:
Q_UNREACHABLE();
}
stream << (spaceDPtr->isPcsLab ? uint(Tag::Lab_) : uint(Tag::XYZ_));
stream << uint(0) << uint(0) << uint(0);
stream << uint(Tag::acsp);
stream << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0);
stream << uint(1); // Rendering intent
stream << uint(0); // Rendering intent
stream << uint(0x0000f6d6); // D50 X
stream << uint(0x00010000); // D50 Y
stream << uint(0x0000d32d); // D50 Z
@ -417,81 +833,132 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << uint(0) << uint(0) << uint(0) << uint(0);
stream << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0) << uint(0);
// Tag table:
currentOffset = profileDataOffset;
stream << uint(tagCount);
stream << uint(Tag::rXYZ) << uint(profileDataOffset + 00) << uint(20);
stream << uint(Tag::gXYZ) << uint(profileDataOffset + 20) << uint(20);
stream << uint(Tag::bXYZ) << uint(profileDataOffset + 40) << uint(20);
stream << uint(Tag::wtpt) << uint(profileDataOffset + 60) << uint(20);
stream << uint(Tag::cprt) << uint(profileDataOffset + 80) << uint(34);
currentOffset += 20 + 20 + 20 + 20 + 34 + 2;
if (writeChad) {
stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
currentOffset += 44;
}
// From here the offset and size will be updated later:
stream << uint(Tag::rTRC) << uint(0) << uint(0);
stream << uint(Tag::gTRC) << uint(0) << uint(0);
stream << uint(Tag::bTRC) << uint(0) << uint(0);
stream << uint(Tag::desc) << uint(0) << uint(0);
if (spaceDPtr->isThreeComponentMatrix()) {
// Tag table:
stream << uint(tagCount);
if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
stream << uint(Tag::rXYZ) << uint(currentOffset + 00) << uint(20);
stream << uint(Tag::gXYZ) << uint(currentOffset + 20) << uint(20);
stream << uint(Tag::bXYZ) << uint(currentOffset + 40) << uint(20);
currentOffset += 20 + 20 + 20;
}
stream << uint(Tag::wtpt) << uint(currentOffset + 00) << uint(20);
stream << uint(Tag::cprt) << uint(currentOffset + 20) << uint(34);
currentOffset += 20 + 34 + 2;
if (writeChad) {
stream << uint(Tag::chad) << uint(currentOffset) << uint(44);
currentOffset += 44;
}
// From here the offset and size will be updated later:
if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
stream << uint(Tag::rTRC) << uint(0) << uint(0);
stream << uint(Tag::gTRC) << uint(0) << uint(0);
stream << uint(Tag::bTRC) << uint(0) << uint(0);
} else {
stream << uint(Tag::kTRC) << uint(0) << uint(0);
}
stream << uint(Tag::desc) << uint(0) << uint(0);
// Tag data:
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.r.x);
stream << toFixedS1516(spaceDPtr->toXyz.r.y);
stream << toFixedS1516(spaceDPtr->toXyz.r.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.g.x);
stream << toFixedS1516(spaceDPtr->toXyz.g.y);
stream << toFixedS1516(spaceDPtr->toXyz.g.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.b.x);
stream << toFixedS1516(spaceDPtr->toXyz.b.y);
stream << toFixedS1516(spaceDPtr->toXyz.b.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->whitePoint.x);
stream << toFixedS1516(spaceDPtr->whitePoint.y);
stream << toFixedS1516(spaceDPtr->whitePoint.z);
stream << uint(Tag::mluc) << uint(0);
stream << uint(1) << uint(12);
stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
stream << uint(6) << uint(28);
stream << ushort('N') << ushort('/') << ushort('A');
stream << ushort(0); // 4-byte alignment
if (writeChad) {
QColorMatrix chad = QColorMatrix::chromaticAdaptation(spaceDPtr->whitePoint);
stream << uint(Tag::sf32) << uint(0);
stream << toFixedS1516(chad.r.x);
stream << toFixedS1516(chad.g.x);
stream << toFixedS1516(chad.b.x);
stream << toFixedS1516(chad.r.y);
stream << toFixedS1516(chad.g.y);
stream << toFixedS1516(chad.b.y);
stream << toFixedS1516(chad.r.z);
stream << toFixedS1516(chad.g.z);
stream << toFixedS1516(chad.b.z);
}
// Tag data:
if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.r.x);
stream << toFixedS1516(spaceDPtr->toXyz.r.y);
stream << toFixedS1516(spaceDPtr->toXyz.r.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.g.x);
stream << toFixedS1516(spaceDPtr->toXyz.g.y);
stream << toFixedS1516(spaceDPtr->toXyz.g.z);
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->toXyz.b.x);
stream << toFixedS1516(spaceDPtr->toXyz.b.y);
stream << toFixedS1516(spaceDPtr->toXyz.b.z);
}
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->whitePoint.x);
stream << toFixedS1516(spaceDPtr->whitePoint.y);
stream << toFixedS1516(spaceDPtr->whitePoint.z);
stream << uint(Tag::mluc) << uint(0);
stream << uint(1) << uint(12);
stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
stream << uint(6) << uint(28);
stream << ushort('N') << ushort('/') << ushort('A');
stream << ushort(0); // 4-byte alignment
if (writeChad) {
const QColorMatrix &chad = spaceDPtr->chad;
stream << uint(Tag::sf32) << uint(0);
stream << toFixedS1516(chad.r.x);
stream << toFixedS1516(chad.g.x);
stream << toFixedS1516(chad.b.x);
stream << toFixedS1516(chad.r.y);
stream << toFixedS1516(chad.g.y);
stream << toFixedS1516(chad.b.y);
stream << toFixedS1516(chad.r.z);
stream << toFixedS1516(chad.g.z);
stream << toFixedS1516(chad.b.z);
}
// From now on the data is variable sized:
rTrcOffset = currentOffset;
rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
currentOffset += rTrcSize;
if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
gTrcOffset = rTrcOffset;
gTrcSize = rTrcSize;
// From now on the data is variable sized:
if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
rTrcOffset = currentOffset;
rTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
currentOffset += rTrcSize;
if (spaceDPtr->trc[0] == spaceDPtr->trc[1]) {
gTrcOffset = rTrcOffset;
gTrcSize = rTrcSize;
} else {
gTrcOffset = currentOffset;
gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
currentOffset += gTrcSize;
}
if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
bTrcOffset = rTrcOffset;
bTrcSize = rTrcSize;
} else {
bTrcOffset = currentOffset;
bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
currentOffset += bTrcSize;
}
} else {
Q_ASSERT(spaceDPtr->colorModel == QColorSpace::ColorModel::Gray);
kTrcOffset = currentOffset;
kTrcSize = writeColorTrc(stream, spaceDPtr->trc[0]);
currentOffset += kTrcSize;
}
} else {
gTrcOffset = currentOffset;
gTrcSize = writeColorTrc(stream, spaceDPtr->trc[1]);
currentOffset += gTrcSize;
}
if (spaceDPtr->trc[0] == spaceDPtr->trc[2]) {
bTrcOffset = rTrcOffset;
bTrcSize = rTrcSize;
} else {
bTrcOffset = currentOffset;
bTrcSize = writeColorTrc(stream, spaceDPtr->trc[2]);
currentOffset += bTrcSize;
// Tag table:
stream << uint(tagCount);
stream << uint(Tag::wtpt) << uint(profileDataOffset + 00) << uint(20);
stream << uint(Tag::cprt) << uint(profileDataOffset + 20) << uint(34);
currentOffset += 20 + 34 + 2;
// From here the offset and size will be updated later:
stream << uint(Tag::A2B0) << uint(0) << uint(0);
if (writeB2a)
stream << uint(Tag::B2A0) << uint(0) << uint(0);
stream << uint(Tag::desc) << uint(0) << uint(0);
// Fixed tag data
stream << uint(Tag::XYZ_) << uint(0);
stream << toFixedS1516(spaceDPtr->whitePoint.x);
stream << toFixedS1516(spaceDPtr->whitePoint.y);
stream << toFixedS1516(spaceDPtr->whitePoint.z);
stream << uint(Tag::mluc) << uint(0);
stream << uint(1) << uint(12);
stream << uchar('e') << uchar('n') << uchar('U') << uchar('S');
stream << uint(6) << uint(28);
stream << ushort('N') << ushort('/') << ushort('A');
stream << ushort(0); // 4-byte alignment
// From now on the data is variable sized:
mA2bOffset = currentOffset;
mA2bSize = writeMab(stream, spaceDPtr->mAB, true, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
currentOffset += mA2bSize;
if (writeB2a) {
mB2aOffset = currentOffset;
mB2aSize = writeMab(stream, spaceDPtr->mBA, false, spaceDPtr->isPcsLab, spaceDPtr->colorModel == QColorSpace::ColorModel::Cmyk);
currentOffset += mB2aSize;
}
}
// Writing description
@ -515,14 +982,34 @@ QByteArray toIccProfile(const QColorSpace &space)
// Now write final size
*(quint32_be *)iccProfile.data() = iccProfile.size();
// And the final indices and sizes of variable size tags:
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
if (spaceDPtr->isThreeComponentMatrix()) {
if (spaceDPtr->colorModel == QColorSpace::ColorModel::Rgb) {
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = rTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = rTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = gTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = gTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 4) = bTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 2 * 12 + 8) = bTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 4) = descOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 3 * 12 + 8) = descSize;
} else {
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = kTrcOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = kTrcSize;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 4) = descOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 12 + 8) = descSize;
}
} else {
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mA2bOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mA2bSize;
variableTagTableOffsets += 12;
if (writeB2a) {
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = mB2aOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = mB2aSize;
variableTagTableOffsets += 12;
}
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 4) = descOffset;
*(quint32_be *)(iccProfile.data() + variableTagTableOffsets + 8) = descSize;
}
#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS)
const ICCProfileHeader *iccHeader = (const ICCProfileHeader *)iccProfile.constData();
@ -565,10 +1052,14 @@ static quint32 parseTRC(const QByteArrayView &tagData, QColorTrc &gamma, QColorT
if (trcData.type == quint32(Tag::curv)) {
Q_STATIC_ASSERT(sizeof(CurvTagData) == 12);
const CurvTagData curv = qFromUnaligned<CurvTagData>(tagData.constData());
if (curv.valueCount > (1 << 16))
if (curv.valueCount > (1 << 16)) {
qCWarning(lcIcc) << "Invalid count in curv table";
return 0;
if (tagData.size() < qsizetype(12 + 2 * curv.valueCount))
}
if (tagData.size() < qsizetype(12 + 2 * curv.valueCount)) {
qCWarning(lcIcc) << "Truncated curv table";
return 0;
}
const auto valueOffset = sizeof(CurvTagData);
if (curv.valueCount == 0) {
gamma.m_type = QColorTrc::Type::Function;
@ -702,12 +1193,6 @@ static void parseCLUT(const T *tableData, const float f, QColorCLUT *clut, uchar
}
}
// very simple version for small values (<=4) of exp.
static constexpr qsizetype intPow(qsizetype x, qsizetype exp)
{
return (exp <= 1) ? x : x * intPow(x, exp - 1);
}
// Parses lut8 and lut16 type elements
template<typename T>
static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorSpacePrivate, bool isAb)
@ -763,20 +1248,23 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
if (lut.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.inputChannels == 4)) {
const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
if (lut.inputChannels != inputChannels) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 input channel count" << lut.inputChannels;
return false;
}
if (lut.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && lut.outputChannels == 4)) {
if (lut.outputChannels != outputChannels) {
qCWarning(lcIcc) << "Unsupported lut8/lut16 output channel count" << lut.outputChannels;
return false;
}
const qsizetype clutTableSize = intPow(lut.clutGridPoints, lut.inputChannels);
if (tagEntry.size < (sizeof(T) + precision * lut.inputChannels * inputTableEntries
+ precision * lut.outputChannels * outputTableEntries
+ precision * lut.outputChannels * clutTableSize)) {
const qsizetype clutTableSize = intPow(lut.clutGridPoints, inputChannels);
if (tagEntry.size < (sizeof(T) + precision * inputChannels * inputTableEntries
+ precision * outputChannels * outputTableEntries
+ precision * outputChannels * clutTableSize)) {
qCWarning(lcIcc) << "Undersized lut8/lut16 tag, no room for tables";
return false;
}
@ -787,7 +1275,7 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
const uint8_t *tableData = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + sizeof(T));
for (int j = 0; j < lut.inputChannels; ++j) {
for (int j = 0; j < inputChannels; ++j) {
QList<S> input(inputTableEntries);
qFromBigEndian<S>(tableData, inputTableEntries, input.data());
QColorTransferTable table(inputTableEntries, input, QColorTransferTable::OneWay);
@ -803,22 +1291,22 @@ static bool parseLutData(const QByteArray &data, const TagEntry &tagEntry, QColo
clutElement.table.resize(clutTableSize);
clutElement.gridPointsX = clutElement.gridPointsY = clutElement.gridPointsZ = lut.clutGridPoints;
if (lut.inputChannels == 4)
if (inputChannels == 4)
clutElement.gridPointsW = lut.clutGridPoints;
if constexpr (std::is_same_v<T, Lut8TagData>) {
parseCLUT(tableData, 1.f / 255.f, &clutElement, lut.outputChannels);
parseCLUT(tableData, 1.f / 255.f, &clutElement, outputChannels);
} else {
float f = 1.0f / 65535.f;
if (colorSpacePrivate->isPcsLab && isAb) // Legacy lut16 conversion to Lab
f = 1.0f / 65280.f;
QList<S> clutTable(clutTableSize * lut.outputChannels);
QList<S> clutTable(clutTableSize * outputChannels);
qFromBigEndian<S>(tableData, clutTable.size(), clutTable.data());
parseCLUT(clutTable.constData(), f, &clutElement, lut.outputChannels);
parseCLUT(clutTable.constData(), f, &clutElement, outputChannels);
}
tableData += clutTableSize * lut.outputChannels * precision;
tableData += clutTableSize * outputChannels * precision;
for (int j = 0; j < lut.outputChannels; ++j) {
for (int j = 0; j < outputChannels; ++j) {
QList<S> output(outputTableEntries);
qFromBigEndian<S>(tableData, outputTableEntries, output.data());
QColorTransferTable table(outputTableEntries, output, QColorTransferTable::OneWay);
@ -870,12 +1358,15 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
if (mab.inputChannels != 3 && !(isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.inputChannels == 4)) {
const int inputChannels = (isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
const int outputChannels = (!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) ? 4 : 3;
if (mab.inputChannels != inputChannels) {
qCWarning(lcIcc) << "Unsupported mAB/mBA input channel count" << mab.inputChannels;
return false;
}
if (mab.outputChannels != 3 && !(!isAb && colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk && mab.outputChannels == 4)) {
if (mab.outputChannels != outputChannels) {
qCWarning(lcIcc) << "Unsupported mAB/mBA output channel count" << mab.outputChannels;
return false;
}
@ -925,7 +1416,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
bool bCurvesAreLinear = true, aCurvesAreLinear = true, mCurvesAreLinear = true;
// B Curves
if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? mab.outputChannels : mab.inputChannels)) {
if (!parseCurves(mab.bCurvesOffset, bTableElement.trc, isAb ? outputChannels : inputChannels)) {
qCWarning(lcIcc) << "Invalid B curves";
return false;
} else {
@ -934,7 +1425,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
// A Curves
if (mab.aCurvesOffset) {
if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? mab.inputChannels : mab.outputChannels)) {
if (!parseCurves(mab.aCurvesOffset, aTableElement.trc, isAb ? inputChannels : outputChannels)) {
qCWarning(lcIcc) << "Invalid A curves";
return false;
} else {
@ -989,19 +1480,19 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
return false;
}
const qsizetype clutTableSize = clutElement.gridPointsX * clutElement.gridPointsY * clutElement.gridPointsZ * clutElement.gridPointsW;
if ((mab.clutOffset + 20 + clutTableSize * mab.outputChannels * precision) > tagEntry.size) {
if ((mab.clutOffset + 20 + clutTableSize * outputChannels * precision) > tagEntry.size) {
qCWarning(lcIcc) << "CLUT oversized for tag";
return false;
}
clutElement.table.resize(clutTableSize);
if (precision == 2) {
QList<uint16_t> clutTable(clutTableSize * mab.outputChannels);
QList<uint16_t> clutTable(clutTableSize * outputChannels);
qFromBigEndian<uint16_t>(data.constData() + tagEntry.offset + mab.clutOffset + 20, clutTable.size(), clutTable.data());
parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, mab.outputChannels);
parseCLUT(clutTable.constData(), (1.f/65535.f), &clutElement, outputChannels);
} else {
const uint8_t *clutTable = reinterpret_cast<const uint8_t *>(data.constData() + tagEntry.offset + mab.clutOffset + 20);
parseCLUT(clutTable, (1.f/255.f), &clutElement, mab.outputChannels);
parseCLUT(clutTable, (1.f/255.f), &clutElement, outputChannels);
}
} else if (colorSpacePrivate->colorModel == QColorSpace::ColorModel::Cmyk) {
qCWarning(lcIcc) << "Cmyk conversion must have a CLUT";
@ -1015,7 +1506,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
if (!clutElement.isEmpty())
colorSpacePrivate->mAB.append(std::move(clutElement));
}
if (mab.mCurvesOffset && mab.outputChannels == 3) {
if (mab.mCurvesOffset && outputChannels == 3) {
if (!mCurvesAreLinear)
colorSpacePrivate->mAB.append(std::move(mTableElement));
if (!matrixElement.isIdentity())
@ -1028,7 +1519,7 @@ static bool parseMabData(const QByteArray &data, const TagEntry &tagEntry, QColo
} else {
if (!bCurvesAreLinear)
colorSpacePrivate->mBA.append(std::move(bTableElement));
if (mab.mCurvesOffset && mab.inputChannels == 3) {
if (mab.mCurvesOffset && inputChannels == 3) {
if (!matrixElement.isIdentity())
colorSpacePrivate->mBA.append(std::move(matrixElement));
if (!offsetElement.isNull())

View File

@ -254,6 +254,15 @@ void tst_QColorSpace::fromIccProfile()
QCOMPARE(iccProfile, iccProfile2);
QColorSpace fileColorSpace2 = QColorSpace::fromIccProfile(iccProfile2);
QCOMPARE(fileColorSpace2, fileColorSpace);
// Change description to force generation of new icc profile data.
fileColorSpace2.setDescription("Hello my QTest description");
iccProfile2 = fileColorSpace2.iccProfile();
QCOMPARE_NE(iccProfile, iccProfile2);
fileColorSpace2 = QColorSpace::fromIccProfile(iccProfile2);
QVERIFY(fileColorSpace2.isValid());
// Note, we do not currently compare description in color space equality
QCOMPARE(fileColorSpace2, fileColorSpace);
}
void tst_QColorSpace::imageConversion_data()

View File

@ -41,8 +41,10 @@ extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
trans1.isIdentity();
QColorSpace cs2 = cs;
cs2.setDescription("Hello");
bool b = (cs == cs2);
Q_UNUSED(b);
Q_ASSERT(cs == cs2);
QByteArray profileData = cs2.iccProfile();
QColorSpace cs3 = QColorSpace::fromIccProfile(profileData);
Q_ASSERT(cs == cs3);
QColor color(0xfaf8fa00);
color = trans1.map(color);
QImage img(16, 2, toFormat(cs.colorModel()));