Parse grayscale ICC profiles

Parse them into color profiles, they are a simple subset with just a
single TRC and a whitepoint.

Change-Id: I300537d488feb3e907a1acff928b2519ffa75088
Fixes: QTBUG-81830
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2020-02-05 11:50:32 +01:00
parent 882f340f62
commit eb2af9d923

View File

@ -87,6 +87,11 @@ constexpr quint32 IccTag(uchar a, uchar b, uchar c, uchar d)
return (a << 24) | (b << 16) | (c << 8) | d; return (a << 24) | (b << 16) | (c << 8) | d;
} }
enum class ColorSpaceType : quint32 {
Rgb = IccTag('R', 'G', 'B', ' '),
Gray = IccTag('G', 'R', 'A', 'Y'),
};
enum class ProfileClass : quint32 { enum class ProfileClass : quint32 {
Input = IccTag('s', 'c', 'r', 'n'), Input = IccTag('s', 'c', 'r', 'n'),
Display = IccTag('m', 'n', 't', 'r'), Display = IccTag('m', 'n', 't', 'r'),
@ -105,6 +110,7 @@ enum class Tag : quint32 {
rTRC = IccTag('r', 'T', 'R', 'C'), rTRC = IccTag('r', 'T', 'R', 'C'),
gTRC = IccTag('g', 'T', 'R', 'C'), gTRC = IccTag('g', 'T', 'R', 'C'),
bTRC = IccTag('b', 'T', 'R', 'C'), bTRC = IccTag('b', 'T', 'R', 'C'),
kTRC = IccTag('k', 'T', 'R', 'C'),
A2B0 = IccTag('A', '2', 'B', '0'), A2B0 = IccTag('A', '2', 'B', '0'),
A2B1 = IccTag('A', '2', 'B', '1'), A2B1 = IccTag('A', '2', 'B', '1'),
B2A0 = IccTag('B', '2', 'A', '0'), B2A0 = IccTag('B', '2', 'A', '0'),
@ -219,8 +225,10 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
} }
// Don't overflow 32bit integers: // Don't overflow 32bit integers:
if (header.tagCount >= INT32_MAX / sizeof(TagTableEntry)) if (header.tagCount >= INT32_MAX / sizeof(TagTableEntry)) {
qCWarning(lcIcc, "Failed tag count sanity");
return false; return false;
}
if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) { if (header.profileSize - sizeof(ICCProfileHeader) < header.tagCount * sizeof(TagTableEntry)) {
qCWarning(lcIcc, "Failed basic size sanity"); qCWarning(lcIcc, "Failed basic size sanity");
return false; return false;
@ -231,7 +239,8 @@ static bool isValidIccProfile(const ICCProfileHeader &header)
qCWarning(lcIcc, "Unsupported ICC profile class %x", quint32(header.profileClass)); qCWarning(lcIcc, "Unsupported ICC profile class %x", quint32(header.profileClass));
return false; return false;
} }
if (header.inputColorSpace != 0x52474220 /* 'RGB '*/) { if (header.inputColorSpace != uint(ColorSpaceType::Rgb)
&& header.inputColorSpace != uint(ColorSpaceType::Gray)) {
qCWarning(lcIcc, "Unsupported ICC input color space %x", quint32(header.inputColorSpace)); qCWarning(lcIcc, "Unsupported ICC input color space %x", quint32(header.inputColorSpace));
return false; return false;
} }
@ -610,10 +619,8 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
return false; return false;
} }
const ICCProfileHeader *header = (const ICCProfileHeader *)data.constData(); const ICCProfileHeader *header = (const ICCProfileHeader *)data.constData();
if (!isValidIccProfile(*header)) { if (!isValidIccProfile(*header))
qCWarning(lcIcc) << "fromIccProfile: failed general sanity check"; return false; // if failed we already printing a warning
return false;
}
if (qsizetype(header->profileSize) > data.size()) { if (qsizetype(header->profileSize) > data.size()) {
qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2"; qCWarning(lcIcc) << "fromIccProfile: failed size sanity 2";
return false; return false;
@ -658,39 +665,74 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
} }
// Check the profile is three-component matrix based (what we currently support): // Check the profile is three-component matrix based (what we currently support):
if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) || if (header->inputColorSpace == uint(ColorSpaceType::Rgb)) {
!tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) || if (!tagIndex.contains(Tag::rXYZ) || !tagIndex.contains(Tag::gXYZ) || !tagIndex.contains(Tag::bXYZ) ||
!tagIndex.contains(Tag::wtpt)) { !tagIndex.contains(Tag::rTRC) || !tagIndex.contains(Tag::gTRC) || !tagIndex.contains(Tag::bTRC) ||
qCWarning(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based"; !tagIndex.contains(Tag::wtpt)) {
return false; qCWarning(lcIcc) << "fromIccProfile: Unsupported ICC profile - not three component matrix based";
return false;
}
} else {
Q_ASSERT(header->inputColorSpace == uint(ColorSpaceType::Gray));
if (!tagIndex.contains(Tag::kTRC) || !tagIndex.contains(Tag::wtpt)) {
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - not valid gray scale based";
return false;
}
} }
QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::getWritable(*colorSpace); QColorSpacePrivate *colorspaceDPtr = QColorSpacePrivate::getWritable(*colorSpace);
// Parse XYZ tags if (header->inputColorSpace == uint(ColorSpaceType::Rgb)) {
if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r)) // Parse XYZ tags
return false; if (!parseXyzData(data, tagIndex[Tag::rXYZ], colorspaceDPtr->toXyz.r))
if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g)) return false;
return false; if (!parseXyzData(data, tagIndex[Tag::gXYZ], colorspaceDPtr->toXyz.g))
if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b)) return false;
return false; if (!parseXyzData(data, tagIndex[Tag::bXYZ], colorspaceDPtr->toXyz.b))
if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint)) return false;
return false; if (!parseXyzData(data, tagIndex[Tag::wtpt], colorspaceDPtr->whitePoint))
return false;
colorspaceDPtr->primaries = QColorSpace::Primaries::Custom; colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) { if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromSRgb()) {
qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected"; qCDebug(lcIcc) << "fromIccProfile: sRGB primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb; colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) { } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromAdobeRgb()) {
qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected"; qCDebug(lcIcc) << "fromIccProfile: Adobe RGB primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb; colorspaceDPtr->primaries = QColorSpace::Primaries::AdobeRgb;
} else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) { } else if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromDciP3D65()) {
qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected"; qCDebug(lcIcc) << "fromIccProfile: DCI-P3 D65 primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65; colorspaceDPtr->primaries = QColorSpace::Primaries::DciP3D65;
} }
if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) { if (colorspaceDPtr->toXyz == QColorMatrix::toXyzFromProPhotoRgb()) {
qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected"; qCDebug(lcIcc) << "fromIccProfile: ProPhoto RGB primaries detected";
colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb; colorspaceDPtr->primaries = QColorSpace::Primaries::ProPhotoRgb;
}
} else {
// We will use sRGB primaries and fit to match the given white-point if
// it doesn't match sRGB's.
QColorVector whitePoint;
if (!parseXyzData(data, tagIndex[Tag::wtpt], whitePoint))
return false;
if (!qFuzzyCompare(whitePoint.y, 1.0f) || (1.0f + whitePoint.z - whitePoint.x) == 0.0f) {
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - gray white-point not normalized";
return false;
}
if (whitePoint == QColorVector::D65()) {
colorspaceDPtr->primaries = QColorSpace::Primaries::SRgb;
} else {
colorspaceDPtr->primaries = QColorSpace::Primaries::Custom;
// Calculate chromaticity from xyz (assuming y == 1.0f).
float y = 1.0f / (1.0f + whitePoint.z - whitePoint.x);
float x = whitePoint.x * y;
QColorSpacePrimaries primaries(QColorSpace::Primaries::SRgb);
primaries.whitePoint = QPointF(x,y);
if (!primaries.areValid()) {
qCWarning(lcIcc) << "fromIccProfile: Invalid ICC profile - invalid white-point";
return false;
}
colorspaceDPtr->toXyz = primaries.toXyzMatrix();
}
} }
// Reset the matrix to our canonical values: // Reset the matrix to our canonical values:
if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom) if (colorspaceDPtr->primaries != QColorSpace::Primaries::Custom)
@ -700,7 +742,11 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
TagEntry rTrc; TagEntry rTrc;
TagEntry gTrc; TagEntry gTrc;
TagEntry bTrc; TagEntry bTrc;
if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) { if (header->inputColorSpace == uint(ColorSpaceType::Gray)) {
rTrc = tagIndex[Tag::kTRC];
gTrc = tagIndex[Tag::kTRC];
bTrc = tagIndex[Tag::kTRC];
} else if (tagIndex.contains(Tag::aarg) && tagIndex.contains(Tag::aagg) && tagIndex.contains(Tag::aabg)) {
// Apple extension for parametric version of TRCs in ICCv2: // Apple extension for parametric version of TRCs in ICCv2:
rTrc = tagIndex[Tag::aarg]; rTrc = tagIndex[Tag::aarg];
gTrc = tagIndex[Tag::aagg]; gTrc = tagIndex[Tag::aagg];