Parse color space name from ICC profile

Adds parsing of ICCv2 and ICCv4 description tags.

Change-Id: I8647e1529d4127950624f16c475d8dba610149b6
Reviewed-by: Eirik Aavitsland <eirik.aavitsland@qt.io>
This commit is contained in:
Allan Sandfeld Jensen 2019-07-19 14:46:20 +02:00
parent a2c1109152
commit dcc4d54fc9
4 changed files with 117 additions and 19 deletions

View File

@ -247,12 +247,14 @@ bool QColorSpacePrivate::identifyColorSpace()
case QColorSpace::Gamut::SRgb:
if (transferFunction == QColorSpace::TransferFunction::SRgb) {
id = QColorSpace::SRgb;
description = QStringLiteral("sRGB");
if (description.isEmpty())
description = QStringLiteral("sRGB");
return true;
}
if (transferFunction == QColorSpace::TransferFunction::Linear) {
id = QColorSpace::SRgbLinear;
description = QStringLiteral("Linear sRGB");
if (description.isEmpty())
description = QStringLiteral("Linear sRGB");
return true;
}
break;
@ -260,7 +262,8 @@ bool QColorSpacePrivate::identifyColorSpace()
if (transferFunction == QColorSpace::TransferFunction::Gamma) {
if (qAbs(gamma - 2.19921875f) < (1/1024.0f)) {
id = QColorSpace::AdobeRgb;
description = QStringLiteral("Adobe RGB");
if (description.isEmpty())
description = QStringLiteral("Adobe RGB");
return true;
}
}
@ -268,21 +271,24 @@ bool QColorSpacePrivate::identifyColorSpace()
case QColorSpace::Gamut::DciP3D65:
if (transferFunction == QColorSpace::TransferFunction::SRgb) {
id = QColorSpace::DisplayP3;
description = QStringLiteral("Display P3");
if (description.isEmpty())
description = QStringLiteral("Display P3");
return true;
}
break;
case QColorSpace::Gamut::ProPhotoRgb:
if (transferFunction == QColorSpace::TransferFunction::ProPhotoRgb) {
id = QColorSpace::ProPhotoRgb;
description = QStringLiteral("ProPhoto RGB");
if (description.isEmpty())
description = QStringLiteral("ProPhoto RGB");
return true;
}
if (transferFunction == QColorSpace::TransferFunction::Gamma) {
// ProPhoto RGB's curve is effectively gamma 1.8 for 8bit precision.
if (qAbs(gamma - 1.8f) < (1/1024.0f)) {
id = QColorSpace::ProPhotoRgb;
description = QStringLiteral("ProPhoto RGB");
if (description.isEmpty())
description = QStringLiteral("ProPhoto RGB");
return true;
}
}
@ -290,7 +296,8 @@ bool QColorSpacePrivate::identifyColorSpace()
case QColorSpace::Gamut::Bt2020:
if (transferFunction == QColorSpace::TransferFunction::Bt2020) {
id = QColorSpace::Bt2020;
description = QStringLiteral("BT.2020");
if (description.isEmpty())
description = QStringLiteral("BT.2020");
return true;
}
break;

View File

@ -42,8 +42,9 @@
#include <qbuffer.h>
#include <qbytearray.h>
#include <qdatastream.h>
#include <qloggingcategory.h>
#include <qendian.h>
#include <qloggingcategory.h>
#include <qstring.h>
#include "qcolorspace_p.h"
#include "qcolortrc_p.h"
@ -117,6 +118,7 @@ enum class Tag : quint32 {
bkpt = IccTag('b', 'k', 'p', 't'),
mft1 = IccTag('m', 'f', 't', '1'),
mft2 = IccTag('m', 'f', 't', '2'),
mluc = IccTag('m', 'l', 'u', 'c'),
mAB_ = IccTag('m', 'A', 'B', ' '),
mBA_ = IccTag('m', 'B', 'A', ' '),
chad = IccTag('c', 'h', 'a', 'd'),
@ -164,6 +166,25 @@ struct ParaTagData : GenericTagData {
quint32_be parameter[1];
};
struct DescTagData : GenericTagData {
quint32_be asciiDescriptionLength;
char asciiDescription[1];
// .. we ignore the rest
};
struct MlucTagRecord {
quint16_be languageCode;
quint16_be countryCode;
quint32_be size;
quint32_be offset;
};
struct MlucTagData : GenericTagData {
quint32_be recordCount;
quint32_be recordSize; // = sizeof(MlucTagRecord)
MlucTagRecord records[1];
};
// For both mAB and mBA
struct mABTagData : GenericTagData {
quint8 inputChannels;
@ -535,6 +556,53 @@ bool parseTRC(const QByteArray &data, const TagEntry &tagEntry, QColorTrc &gamma
return false;
}
bool parseDesc(const QByteArray &data, const TagEntry &tagEntry, QString &descName)
{
const GenericTagData *tag = (const GenericTagData *)(data.constData() + tagEntry.offset);
// Either 'desc' (ICCv2) or 'mluc' (ICCv4)
if (tag->type == quint32(Tag::desc)) {
if (tagEntry.size < sizeof(DescTagData))
return false;
const DescTagData *desc = (const DescTagData *)(data.constData() + tagEntry.offset);
const quint32 len = desc->asciiDescriptionLength;
if (len < 1)
return false;
if (tagEntry.size - 12 < len)
return false;
if (desc->asciiDescription[len - 1] != '\0')
return false;
descName = QString::fromLatin1(desc->asciiDescription, len - 1);
return true;
}
if (tag->type != quint32(Tag::mluc))
return false;
if (tagEntry.size < sizeof(MlucTagData))
return false;
const MlucTagData *mluc = (const MlucTagData *)(data.constData() + tagEntry.offset);
if (mluc->recordCount < 1)
return false;
if (mluc->recordSize < 12)
return false;
// We just use the primary record regardless of language or country.
const quint32 stringOffset = mluc->records[0].offset;
const quint32 stringSize = mluc->records[0].size;
if (tagEntry.size < stringOffset || tagEntry.size - stringOffset < stringSize )
return false;
if ((stringSize | stringOffset) & 1)
return false;
quint32 stringLen = stringSize / 2;
const ushort *unicodeString = (const ushort *)(data.constData() + tagEntry.offset + stringOffset);
// The given length shouldn't include 0-termination, but might.
if (stringLen > 1 && unicodeString[stringLen - 1] == 0)
--stringLen;
QVarLengthArray<quint16> utf16hostendian(stringLen);
qFromBigEndian<ushort>(unicodeString, stringLen, utf16hostendian.data());
descName = QString::fromUtf16(utf16hostendian.data(), stringLen);
return true;
}
bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
{
if (data.size() < qsizetype(sizeof(ICCProfileHeader))) {
@ -690,7 +758,12 @@ bool fromIccProfile(const QByteArray &data, QColorSpace *colorSpace)
colorspaceDPtr->transferFunction = QColorSpace::TransferFunction::Custom;
}
// FIXME: try to parse the description..
if (tagIndex.contains(Tag::desc)) {
if (!parseDesc(data, tagIndex[Tag::desc], colorspaceDPtr->description))
qCWarning(lcIcc) << "fromIccProfile: Failed to parse description";
else
qCDebug(lcIcc) << "fromIccProfile: Description" << colorspaceDPtr->description;
}
if (!colorspaceDPtr->identifyColorSpace())
colorspaceDPtr->id = QColorSpace::Unknown;

View File

@ -53,6 +53,7 @@ private slots:
void toIccProfile_data();
void toIccProfile();
void fromIccProfile_data();
void fromIccProfile();
void imageConversion_data();
@ -142,21 +143,38 @@ void tst_QColorSpace::toIccProfile()
QCOMPARE(iccProfile2, iccProfile);
}
void tst_QColorSpace::fromIccProfile_data()
{
QTest::addColumn<QString>("testProfile");
QTest::addColumn<QColorSpace::ColorSpaceId>("colorSpaceId");
QTest::addColumn<QColorSpace::TransferFunction>("transferFunction");
QTest::addColumn<QString>("description");
QString prefix = QFINDTESTDATA("resources/");
// Read the official sRGB ICCv2 profile:
QTest::newRow("sRGB2014 (ICCv2)") << prefix + "sRGB2014.icc" << QColorSpace::SRgb
<< QColorSpace::TransferFunction::SRgb << QString("sRGB2014");
// My monitor's profile:
QTest::newRow("HP ZR30w (ICCv4)") << prefix + "HP_ZR30w.icc" << QColorSpace::Unknown
<< QColorSpace::TransferFunction::Gamma << QString("HP Z30i");
}
void tst_QColorSpace::fromIccProfile()
{
// Read the official sRGB ICCv2 profile:
QString prefix = QFINDTESTDATA("resources/");
QFile file(prefix + "sRGB2014.icc");
QFETCH(QString, testProfile);
QFETCH(QColorSpace::ColorSpaceId, colorSpaceId);
QFETCH(QColorSpace::TransferFunction, transferFunction);
QFETCH(QString, description);
QFile file(testProfile);
file.open(QIODevice::ReadOnly);
QByteArray iccProfile = file.readAll();
QColorSpace stdSRgb = QColorSpace::fromIccProfile(iccProfile);
QVERIFY(stdSRgb.isValid());
QColorSpace fileColorSpace = QColorSpace::fromIccProfile(iccProfile);
QVERIFY(fileColorSpace.isValid());
QCOMPARE(stdSRgb.gamut(), QColorSpace::Gamut::SRgb);
QCOMPARE(stdSRgb.transferFunction(), QColorSpace::TransferFunction::SRgb);
QCOMPARE(stdSRgb.colorSpaceId(), QColorSpace::SRgb);
QCOMPARE(stdSRgb, QColorSpace(QColorSpace::SRgb));
QCOMPARE(fileColorSpace.colorSpaceId(), colorSpaceId);
QCOMPARE(fileColorSpace.transferFunction(), transferFunction);
QCOMPARE(QColorSpacePrivate::get(fileColorSpace)->description, description);
}
void tst_QColorSpace::imageConversion_data()