From dcc4d54fc9340fcbed3ffddbe1cf99e6ca70d4f9 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Fri, 19 Jul 2019 14:46:20 +0200 Subject: [PATCH] Parse color space name from ICC profile Adds parsing of ICCv2 and ICCv4 description tags. Change-Id: I8647e1529d4127950624f16c475d8dba610149b6 Reviewed-by: Eirik Aavitsland --- src/gui/painting/qcolorspace.cpp | 21 +++-- src/gui/painting/qicc.cpp | 77 +++++++++++++++++- .../qcolorspace/resources/HP_ZR30w.icc | Bin 0 -> 1856 bytes .../painting/qcolorspace/tst_qcolorspace.cpp | 38 ++++++--- 4 files changed, 117 insertions(+), 19 deletions(-) create mode 100644 tests/auto/gui/painting/qcolorspace/resources/HP_ZR30w.icc diff --git a/src/gui/painting/qcolorspace.cpp b/src/gui/painting/qcolorspace.cpp index d058505e68..4a1ed373f5 100644 --- a/src/gui/painting/qcolorspace.cpp +++ b/src/gui/painting/qcolorspace.cpp @@ -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; diff --git a/src/gui/painting/qicc.cpp b/src/gui/painting/qicc.cpp index 53055b426c..1941981d30 100644 --- a/src/gui/painting/qicc.cpp +++ b/src/gui/painting/qicc.cpp @@ -42,8 +42,9 @@ #include #include #include -#include #include +#include +#include #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 utf16hostendian(stringLen); + qFromBigEndian(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; diff --git a/tests/auto/gui/painting/qcolorspace/resources/HP_ZR30w.icc b/tests/auto/gui/painting/qcolorspace/resources/HP_ZR30w.icc new file mode 100644 index 0000000000000000000000000000000000000000..b3f860714c5cd7e71ea3830af395d0b19282c6f0 GIT binary patch literal 1856 zcmb7^--{bn6vw}ttgc{NYAsTvNRSq>Y?fq`?k3>F)@|(;e}#3cbrsghOp*$Md_L6P3yf+V#2pqsDCT`>bgl zy>aRBYiBQ~ziTaj`O(@NAM83U9@rPuy1q0vi96|cq8|1+?28wA9kYMMKIpG{0rnrp ziCCbN%2>P_+eNb*vAtk+w6r)6=S}SWjS;&#V*9I62&V<-NDv0-r{2My48peAUt>QL z)_NZHpVIh3*y^)R_tP)>X%A zsfHg%>qKkLx3P@Y)P{9|cSb%|N1HI3YM|32X6!DO#eA>{r%hx^va=*7PjW`xdSTjy z#vcAcdf?r`;MS|s#M9E9YlFexpAQCiE=%d3q^;Y@{P&)cPE|RBx0BeBtJ2T_?GnPoOBf2yR}Q*?x)5X#ph9@Vpi3j zt5HQBwOQjGiPH_5K6lGKkMKr+ECW6U&Va|jx4?s-@yvf8wDHft2f?qvC&BCBA{(UI zHl8{OJ_OqLg08!RbKoU>^irRI`#@^v_&*HeH^BqM{{qo>MM6JK*Aw6z$o%vYh;Eu) zIDcd4UmW_D$HuRKhrn-!zU49hx1s+J$a=@gY5Z}^V}8l`^LkOwX=Qjr1NGg_>T|D} z)N@l=?pIvfCb0%ymb!YxLU;SN=U3skUDY?yxZm05(TmWIZ*-m;onu`m_|_^qqnC7= z6$Wt5!|=fsFwh2bJaPqKmM`;OH^Ih7zuc_m(Jj&nw7h2YJnbYom*^|3Wu1LQE?bQZ zmhTkfXXq>JJnwi@f**0OwM+6=W#v_^Tc-YmQvoHCjT duS(VWtZiE@PqtZiKBH<0E7r=PQGl0M{$DnICFTGC literal 0 HcmV?d00001 diff --git a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp index b82a56dbf8..291a25fede 100644 --- a/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp +++ b/tests/auto/gui/painting/qcolorspace/tst_qcolorspace.cpp @@ -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("testProfile"); + QTest::addColumn("colorSpaceId"); + QTest::addColumn("transferFunction"); + QTest::addColumn("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()