Handle chad elements in ICC profiles

Parse them, so we can change white-points correctly,
and write them as required by ICCv4

Change-Id: I6d28fb6a85585f80a9867df45864069efda956c5
Reviewed-by: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
bb10
Allan Sandfeld Jensen 2024-02-26 18:10:41 +01:00
parent 04e5b86f9e
commit 48d582e0b7
3 changed files with 87 additions and 18 deletions

View File

@ -94,9 +94,6 @@ QColorMatrix QColorSpacePrimaries::toXyzMatrix() const
// Now we have scaled conversion to XYZ relative to the given whitepoint
toXyz = toXyz * QColorMatrix::fromScale(whiteScale);
// But we want a conversion to XYZ relative to D50
toXyz = QColorMatrix::chromaticAdaptation(wXyz) * toXyz;
return toXyz;
}
@ -162,6 +159,9 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
chad = QColorMatrix::chromaticAdaptation(whitePoint);
toXyz = chad * toXyz;
identifyColorSpace();
setTransferFunction();
}
@ -175,7 +175,8 @@ QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint,
, gamma(gamma)
, whitePoint(whitePoint)
{
toXyz = QColorMatrix::chromaticAdaptation(this->whitePoint);
chad = QColorMatrix::chromaticAdaptation(this->whitePoint);
toXyz = chad;
setTransferFunction();
}
@ -186,7 +187,8 @@ QColorSpacePrivate::QColorSpacePrivate(const QPointF &whitePoint, const QList<ui
, gamma(0)
, whitePoint(whitePoint)
{
toXyz = QColorMatrix::chromaticAdaptation(this->whitePoint);
chad = QColorMatrix::chromaticAdaptation(this->whitePoint);
toXyz = chad;
setTransferFunctionTable(transferFunctionTable);
setTransferFunction();
}
@ -211,6 +213,8 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries, co
{
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
chad = QColorMatrix::chromaticAdaptation(whitePoint);
toXyz = chad * toXyz;
setTransferFunctionTable(transferFunctionTable);
identifyColorSpace();
initialize();
@ -228,11 +232,12 @@ QColorSpacePrivate::QColorSpacePrivate(const QColorSpacePrimaries &primaries,
Q_ASSERT(primaries.areValid());
toXyz = primaries.toXyzMatrix();
whitePoint = QColorVector(primaries.whitePoint);
chad = QColorMatrix::chromaticAdaptation(whitePoint);
toXyz = chad * toXyz;
setTransferFunctionTables(redTransferFunctionTable,
greenTransferFunctionTable,
blueTransferFunctionTable);
identifyColorSpace();
setToXyzMatrix();
}
void QColorSpacePrivate::identifyColorSpace()
@ -310,6 +315,8 @@ void QColorSpacePrivate::setToXyzMatrix()
QColorSpacePrimaries colorSpacePrimaries(primaries);
toXyz = colorSpacePrimaries.toXyzMatrix();
whitePoint = QColorVector(colorSpacePrimaries.whitePoint);
chad = QColorMatrix::chromaticAdaptation(whitePoint);
toXyz = chad * toXyz;
}
void QColorSpacePrivate::setTransferFunctionTable(const QList<uint16_t> &transferFunctionTable)
@ -930,7 +937,9 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin
return;
}
QColorMatrix toXyz = primaries.toXyzMatrix();
if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz)
QColorMatrix chad = QColorMatrix::chromaticAdaptation(QColorVector(whitePoint));
toXyz = chad * toXyz;
if (QColorVector(primaries.whitePoint) == d_ptr->whitePoint && toXyz == d_ptr->toXyz && chad == d_ptr->chad)
return;
detach();
if (d_ptr->transformModel == TransformModel::ElementListProcessing)
@ -940,6 +949,7 @@ void QColorSpace::setPrimaries(const QPointF &whitePoint, const QPointF &redPoin
d_ptr->primaries = QColorSpace::Primaries::Custom;
d_ptr->colorModel = QColorSpace::ColorModel::Rgb;
d_ptr->toXyz = toXyz;
d_ptr->chad = chad;
d_ptr->whitePoint = QColorVector(primaries.whitePoint);
d_ptr->identifyColorSpace();
}
@ -982,13 +992,13 @@ void QColorSpace::setWhitePoint(const QPointF &whitePoint)
if (d_ptr->transformModel == QColorSpace::TransformModel::ThreeComponentMatrix) {
if (d_ptr->colorModel == QColorSpace::ColorModel::Rgb) {
// Rescale toXyz to new whitepoint
QColorMatrix chad = QColorMatrix::chromaticAdaptation(d_ptr->whitePoint);
QColorMatrix rawToXyz = chad.inverted() * d_ptr->toXyz;
QColorMatrix rawToXyz = d_ptr->chad.inverted() * d_ptr->toXyz;
QColorVector whiteScale = rawToXyz.inverted().map(wXyz);
rawToXyz = rawToXyz * QColorMatrix::fromScale(whiteScale);
d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz) * rawToXyz;
d_ptr->chad = QColorMatrix::chromaticAdaptation(wXyz);
d_ptr->toXyz = d_ptr->chad * rawToXyz;
} else if (d_ptr->colorModel == QColorSpace::ColorModel::Gray) {
d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz);
d_ptr->chad = d_ptr->toXyz = QColorMatrix::chromaticAdaptation(wXyz);
}
}
d_ptr->whitePoint = wXyz;

View File

@ -110,6 +110,7 @@ public:
// Three component matrix data:
QColorTrc trc[3];
QColorMatrix toXyz;
QColorMatrix chad;
// Element list processing data:
struct TransferElement {

View File

@ -244,7 +244,7 @@ struct mpetTagData : GenericTagData {
};
struct Sf32TagData : GenericTagData {
quint32_be value[1];
quint32_be value[9];
};
struct MatrixElement {
@ -378,9 +378,16 @@ QByteArray toIccProfile(const QColorSpace &space)
return spaceDPtr->iccProfile;
Q_ASSERT(spaceDPtr->isThreeComponentMatrix());
constexpr int tagCount = 9;
constexpr uint profileDataOffset = 128 + 4 + 12 * tagCount;
constexpr uint variableTagTableOffsets = 128 + 4 + 12 * 5;
int fixedLengthTagCount = 5;
bool writeChad = false;
if (!spaceDPtr->whitePoint.isNull() && spaceDPtr->whitePoint != QColorVector::D50()) {
writeChad = true;
fixedLengthTagCount++;
}
const int tagCount = fixedLengthTagCount + 4;
const uint profileDataOffset = 128 + 4 + 12 * tagCount;
const uint variableTagTableOffsets = 128 + 4 + 12 * fixedLengthTagCount;
uint currentOffset = 0;
uint rTrcOffset, gTrcOffset, bTrcOffset;
uint rTrcSize, gTrcSize, bTrcSize;
@ -410,19 +417,23 @@ QByteArray toIccProfile(const QColorSpace &space)
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(12);
currentOffset += 92;
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);
// TODO: consider adding 'chad' tag (required in ICC >=4 when we have non-D50 whitepoint)
currentOffset = profileDataOffset;
// Tag data:
stream << uint(Tag::XYZ_) << uint(0);
@ -443,7 +454,19 @@ QByteArray toIccProfile(const QColorSpace &space)
stream << toFixedS1516(spaceDPtr->whitePoint.z);
stream << uint(Tag::text) << uint(0);
stream << uint(IccTag('N', '/', 'A', '\0'));
currentOffset += 92;
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);
}
// From now on the data is variable sized:
rTrcOffset = currentOffset;
@ -1117,6 +1140,35 @@ static bool parseGrayMatrix(const QByteArray &data, const QHash<Tag, TagEntry> &
}
return true;
}
static bool parseChad(const QByteArray &data, const TagEntry &tagEntry, QColorSpacePrivate *colorspaceDPtr)
{
if (tagEntry.size < sizeof(Sf32TagData) || qsizetype(tagEntry.size) > data.size())
return false;
const Sf32TagData chadtag = qFromUnaligned<Sf32TagData>(data.constData() + tagEntry.offset);
if (chadtag.type != uint32_t(Tag::sf32)) {
qCWarning(lcIcc, "fromIccProfile: bad chad data type");
return false;
}
QColorMatrix chad;
chad.r.x = fromFixedS1516(chadtag.value[0]);
chad.g.x = fromFixedS1516(chadtag.value[1]);
chad.b.x = fromFixedS1516(chadtag.value[2]);
chad.r.y = fromFixedS1516(chadtag.value[3]);
chad.g.y = fromFixedS1516(chadtag.value[4]);
chad.b.y = fromFixedS1516(chadtag.value[5]);
chad.r.z = fromFixedS1516(chadtag.value[6]);
chad.g.z = fromFixedS1516(chadtag.value[7]);
chad.b.z = fromFixedS1516(chadtag.value[8]);
if (!chad.isValid()) {
qCWarning(lcIcc, "fromIccProfile: invalid chad matrix");
return false;
}
colorspaceDPtr->chad = chad;
return true;
}
static bool parseTRCs(const QByteArray &data, const QHash<Tag, TagEntry> &tagIndex, QColorSpacePrivate *colorspaceDPtr, bool isColorSpaceTypeGray)
{
TagEntry rTrc;
@ -1279,6 +1331,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
} else {
Q_UNREACHABLE();
}
if (tagIndex.contains(Tag::chad)) {
if (!parseChad(data, tagIndex[Tag::chad], colorspaceDPtr))
return false;
} else {
colorspaceDPtr->chad = QColorMatrix::chromaticAdaptation(colorspaceDPtr->whitePoint);
}
// Reset the matrix to our canonical values:
if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)