6#include "rgbcolorspace.h"
8#include "rgbcolorspace_p.h"
10#include "absolutecolor.h"
11#include "constpropagatingrawpointer.h"
12#include "constpropagatinguniquepointer.h"
13#include "genericcolor.h"
14#include "helperconstants.h"
15#include "helperconversion.h"
16#include "helpermath.h"
17#include "helperqttypes.h"
18#include "initializetranslation.h"
19#include "iohandlerfactory.h"
24#include <qbytearray.h>
26#include <qcoreapplication.h>
30#include <qnamespace.h>
32#include <qsharedpointer.h>
33#include <qstringliteral.h>
36#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
37#include <qcontainerfwd.h>
40#include <qstringlist.h>
57RgbColorSpace::RgbColorSpace(
QObject *parent)
59 , d_pointer(new RgbColorSpacePrivate(this))
88 cmsHPROFILE srgb = cmsCreate_sRGBProfile();
89 const bool success = result->d_pointer->initialize(srgb);
90 cmsCloseProfile(srgb);
103 std::optional<QStringList>());
106 result->d_pointer->m_profileCreationDateTime =
QDateTime();
108 result->d_pointer->m_profileManufacturer = tr(
"LittleCMS");
109 result->d_pointer->m_profileModel =
QString();
111 result->d_pointer->m_profileName = tr(
"sRGB color space");
112 result->d_pointer->m_profileMaximumCielchD50Chroma = 132;
162 constexpr auto myContextID =
nullptr;
165 cmsIOHANDLER *myIOHandler =
166 IOHandlerFactory::createReadOnly(myContextID, fileName);
167 if (myIOHandler ==
nullptr) {
172 cmsHPROFILE myProfileHandle =
173 cmsOpenProfileFromIOhandlerTHR(myContextID, myIOHandler);
174 if (myProfileHandle ==
nullptr) {
186 newObject->d_pointer->m_profileAbsoluteFilePath =
188 newObject->d_pointer->m_profileFileSize = myFileInfo.
size();
189 const bool success = newObject->d_pointer->initialize(myProfileHandle);
192 cmsCloseProfile(myProfileHandle);
229bool RgbColorSpacePrivate::initialize(cmsHPROFILE rgbProfileHandle)
231 constexpr auto renderingIntent = INTENT_ABSOLUTE_COLORIMETRIC;
233 m_profileClass = cmsGetDeviceClass(rgbProfileHandle);
234 m_profileColorModel = cmsGetColorSpace(rgbProfileHandle);
235 m_profileCopyright = profileInformation(rgbProfileHandle,
237 m_profileCreationDateTime =
238 profileCreationDateTime(rgbProfileHandle);
239 const bool inputUsesCLUT = cmsIsCLUT(rgbProfileHandle,
242 const bool outputUsesCLUT = cmsIsCLUT(rgbProfileHandle,
244 LCMS_USED_AS_OUTPUT);
249 m_profileHasClut = inputUsesCLUT || outputUsesCLUT;
250 m_profileHasMatrixShaper = cmsIsMatrixShaper(rgbProfileHandle);
251 m_profileIccVersion = profileIccVersion(rgbProfileHandle);
252 m_profileManufacturer = profileInformation(rgbProfileHandle,
253 cmsInfoManufacturer);
254 m_profileModel = profileInformation(rgbProfileHandle,
256 m_profileName = profileInformation(rgbProfileHandle,
258 m_profilePcsColorModel = cmsGetPCS(rgbProfileHandle);
259 m_profileTagSignatures = profileTagSignatures(rgbProfileHandle);
289 if (m_profileTagSignatures.contains(QStringLiteral(
"vcgt"))) {
295 cmsHPROFILE cielabD50ProfileHandle = cmsCreateLab4Profile(
309 constexpr auto flags = cmsFLAGS_NOCACHE;
310 m_transformCielabD50ToRgbHandle = cmsCreateTransform(
312 cielabD50ProfileHandle,
318 m_transformCielabD50ToRgb16Handle = cmsCreateTransform(
320 cielabD50ProfileHandle,
326 m_transformRgbToCielabD50Handle = cmsCreateTransform(
330 cielabD50ProfileHandle,
335 cmsCloseProfile(cielabD50ProfileHandle);
340 if ((m_transformCielabD50ToRgbHandle ==
nullptr)
341 || (m_transformCielabD50ToRgb16Handle ==
nullptr)
342 || (m_transformRgbToCielabD50Handle ==
nullptr)
356 while (!q_pointer->isCielchD50InGamut(candidate)) {
357 candidate.l += gamutPrecisionCielab;
358 if (candidate.l >= 100) {
362 m_cielabD50BlackpointL = candidate.l;
364 while (!q_pointer->isCielchD50InGamut(candidate)) {
365 candidate.l -= gamutPrecisionCielab;
366 if (candidate.l <= m_cielabD50BlackpointL) {
370 m_cielabD50WhitepointL = candidate.l;
373 while (!q_pointer->isOklchInGamut(candidate)) {
374 candidate.l += gamutPrecisionOklab;
375 if (candidate.l >= 1) {
379 m_oklabBlackpointL = candidate.l;
381 while (!q_pointer->isOklchInGamut(candidate)) {
382 candidate.l -= gamutPrecisionOklab;
383 if (candidate.l <= m_oklabBlackpointL) {
387 m_oklabWhitepointL = candidate.l;
391 m_profileMaximumCielchD50Chroma = detectMaximumCielchD50Chroma();
392 m_profileMaximumOklchChroma = detectMaximumOklchChroma();
398RgbColorSpace::~RgbColorSpace() noexcept
400 RgbColorSpacePrivate::deleteTransform(
401 &d_pointer->m_transformCielabD50ToRgb16Handle);
402 RgbColorSpacePrivate::deleteTransform(
403 &d_pointer->m_transformCielabD50ToRgbHandle);
404 RgbColorSpacePrivate::deleteTransform(
405 &d_pointer->m_transformRgbToCielabD50Handle);
412RgbColorSpacePrivate::RgbColorSpacePrivate(RgbColorSpace *backLink)
413 : q_pointer(backLink)
431void RgbColorSpacePrivate::deleteTransform(cmsHTRANSFORM *transformHandle)
433 if ((*transformHandle) !=
nullptr) {
434 cmsDeleteTransform(*transformHandle);
435 (*transformHandle) =
nullptr;
441QString RgbColorSpace::profileAbsoluteFilePath()
const
443 return d_pointer->m_profileAbsoluteFilePath;
448cmsProfileClassSignature RgbColorSpace::profileClass()
const
450 return d_pointer->m_profileClass;
455cmsColorSpaceSignature RgbColorSpace::profileColorModel()
const
457 return d_pointer->m_profileColorModel;
462QString RgbColorSpace::profileCopyright()
const
464 return d_pointer->m_profileCopyright;
469QDateTime RgbColorSpace::profileCreationDateTime()
const
471 return d_pointer->m_profileCreationDateTime;
476qint64 RgbColorSpace::profileFileSize()
const
478 return d_pointer->m_profileFileSize;
483bool RgbColorSpace::profileHasClut()
const
485 return d_pointer->m_profileHasClut;
490bool RgbColorSpace::profileHasMatrixShaper()
const
492 return d_pointer->m_profileHasMatrixShaper;
499 return d_pointer->m_profileIccVersion;
504QString RgbColorSpace::profileManufacturer()
const
506 return d_pointer->m_profileManufacturer;
511double RgbColorSpace::profileMaximumCielchD50Chroma()
const
513 return d_pointer->m_profileMaximumCielchD50Chroma;
518double RgbColorSpace::profileMaximumOklchChroma()
const
520 return d_pointer->m_profileMaximumOklchChroma;
525QString RgbColorSpace::profileModel()
const
527 return d_pointer->m_profileModel;
532QString RgbColorSpace::profileName()
const
534 return d_pointer->m_profileName;
539cmsColorSpaceSignature RgbColorSpace::profilePcsColorModel()
const
541 return d_pointer->m_profilePcsColorModel;
546QStringList RgbColorSpace::profileTagSignatures()
const
548 return d_pointer->m_profileTagSignatures;
561QString RgbColorSpacePrivate::profileInformation(cmsHPROFILE profileHandle, cmsInfoType infoType)
572 languageCode =
list.
at(0).toUtf8();
577 if (languageCode.
size() != 2) {
580 languageCode = QByteArrayLiteral(
"en");
622 const cmsUInt32Number resultLength = cmsGetProfileInfo(
639 const cmsUInt32Number bufferLength = resultLength + 1;
658 wchar_t *buffer =
new wchar_t[bufferLength];
660 for (cmsUInt32Number i = 0; i < bufferLength; ++i) {
680 *(buffer + (bufferLength - 1)) = 0;
748QVersionNumber RgbColorSpacePrivate::profileIccVersion(cmsHPROFILE profileHandle)
769 cmsGetProfileVersion(profileHandle),
781QDateTime RgbColorSpacePrivate::profileCreationDateTime(cmsHPROFILE profileHandle)
784 const bool success = cmsGetHeaderCreationDateTime(profileHandle, &myDateTime);
789 const QDate myDate(myDateTime.tm_year + 1900,
790 myDateTime.tm_mon + 1,
796 const QTime myTime(myDateTime.tm_hour,
798 qBound(0, myDateTime.tm_sec, 59));
815QStringList RgbColorSpacePrivate::profileTagSignatures(cmsHPROFILE profileHandle)
817 const cmsInt32Number count = cmsGetTagCount(profileHandle);
823 const cmsUInt32Number countUnsigned =
static_cast<cmsUInt32Number
>(count);
824 using underlyingType = std::underlying_type<cmsTagSignature>::type;
825 for (cmsUInt32Number i = 0; i < countUnsigned; ++i) {
826 const underlyingType value = cmsGetTagSignature(profileHandle, i);
830 byteArray.
append(
static_cast<char>((value >> 24) & 0xFF));
831 byteArray.
append(
static_cast<char>((value >> 16) & 0xFF));
832 byteArray.
append(
static_cast<char>((value >> 8) & 0xFF));
833 byteArray.
append(
static_cast<char>(value & 0xFF));
855 LchDouble referenceColor = cielchD50color;
858 normalizePolar360(referenceColor.c, referenceColor.h);
861 referenceColor.c = qMin<
decltype(referenceColor.c)>(
863 profileMaximumCielchD50Chroma());
864 referenceColor.l = qBound(d_pointer->m_cielabD50BlackpointL,
866 d_pointer->m_cielabD50WhitepointL);
869 if (isCielchD50InGamut(referenceColor)) {
870 return referenceColor;
877 LchDouble lowerChroma{referenceColor.l, 0, referenceColor.h};
878 if (!isCielchD50InGamut(lowerChroma)) {
883 referenceColor.l = d_pointer->m_cielabD50BlackpointL;
884 lowerChroma.l = d_pointer->m_cielabD50BlackpointL;
888 constexpr bool quickApproximate =
true;
889 if constexpr (quickApproximate) {
891 LchDouble upperChroma{referenceColor};
895 while (upperChroma.c - lowerChroma.c > gamutPrecisionCielab) {
898 temp.c = ((lowerChroma.c + upperChroma.c) / 2);
899 if (isCielchD50InGamut(temp)) {
909 temp = referenceColor;
911 if (isCielchD50InGamut(temp)) {
914 temp.c -= gamutPrecisionCielab;
939 LchDouble referenceColor = oklchColor;
942 normalizePolar360(referenceColor.c, referenceColor.h);
945 referenceColor.c = qMin<
decltype(referenceColor.c)>(
947 profileMaximumOklchChroma());
948 referenceColor.l = qBound(d_pointer->m_oklabBlackpointL,
950 d_pointer->m_oklabWhitepointL);
953 if (isOklchInGamut(referenceColor)) {
954 return referenceColor;
961 LchDouble lowerChroma{referenceColor.l, 0, referenceColor.h};
962 if (!isOklchInGamut(lowerChroma)) {
967 referenceColor.l = d_pointer->m_oklabBlackpointL;
968 lowerChroma.l = d_pointer->m_oklabBlackpointL;
972 constexpr bool quickApproximate =
true;
973 if constexpr (quickApproximate) {
975 LchDouble upperChroma{referenceColor};
979 while (upperChroma.c - lowerChroma.c > gamutPrecisionOklab) {
982 temp.c = ((lowerChroma.c + upperChroma.c) / 2);
983 if (isOklchInGamut(temp)) {
993 temp = referenceColor;
995 if (isOklchInGamut(temp)) {
998 temp.c -= gamutPrecisionOklab;
1018cmsCIELab RgbColorSpace::toCielabD50(
const QRgba64 rgbColor)
const
1020 constexpr qreal maximum =
1021 std::numeric_limits<
decltype(rgbColor.
red())>::max();
1022 const double my_rgb[]{rgbColor.
red() / maximum,
1023 rgbColor.
green() / maximum,
1024 rgbColor.
blue() / maximum};
1025 cmsCIELab cielabD50;
1026 cmsDoTransform(d_pointer->m_transformRgbToCielabD50Handle,
1031 if (cielabD50.L < 0) {
1050 constexpr qreal maximum =
1051 std::numeric_limits<
decltype(rgbColor.
red())>::max();
1052 const double my_rgb[]{rgbColor.
red() / maximum,
1053 rgbColor.
green() / maximum,
1054 rgbColor.
blue() / maximum};
1055 cmsCIELab cielabD50;
1056 cmsDoTransform(d_pointer->m_transformRgbToCielabD50Handle,
1061 if (cielabD50.L < 0) {
1065 cmsCIELCh cielchD50;
1066 cmsLab2LCh(&cielchD50,
1069 return LchDouble{cielchD50.L, cielchD50.C, cielchD50.h};
1084QRgb RgbColorSpace::fromCielchD50ToQRgbBound(
const LchDouble &lch)
const
1086 const cmsCIELCh myCmsCieLch = toCmsLch(lch);
1091 cmsUInt16Number rgb_int[3];
1092 cmsDoTransform(d_pointer->m_transformCielabD50ToRgb16Handle,
1097 constexpr qreal channelMaximumQReal =
1098 std::numeric_limits<cmsUInt16Number>::max();
1099 constexpr quint8 rgbMaximum = 255;
1100 return qRgb(qRound(rgb_int[0] / channelMaximumQReal * rgbMaximum),
1101 qRound(rgb_int[1] / channelMaximumQReal * rgbMaximum),
1102 qRound(rgb_int[2] / channelMaximumQReal * rgbMaximum));
1109bool RgbColorSpace::isCielchD50InGamut(
const LchDouble &lch)
const
1111 if (!isInRange<
decltype(lch.l)>(0, lch.l, 100)) {
1114 if (!isInRange<
decltype(lch.l)>(
1115 (-1) * d_pointer->m_profileMaximumCielchD50Chroma,
1117 d_pointer->m_profileMaximumCielchD50Chroma
1122 const cmsCIELCh myCmsCieLch = toCmsLch(lch);
1123 cmsLCh2Lab(&lab, &myCmsCieLch);
1124 return qAlpha(fromCielabD50ToQRgbOrTransparent(lab)) != 0;
1131bool RgbColorSpace::isOklchInGamut(
const LchDouble &lch)
const
1133 if (!isInRange<
decltype(lch.l)>(0, lch.l, 1)) {
1136 if (!isInRange<
decltype(lch.l)>(
1137 (-1) * d_pointer->m_profileMaximumOklchChroma,
1139 d_pointer->m_profileMaximumOklchChroma
1143 const auto oklab = AbsoluteColor::fromPolarToCartesian(GenericColor(lch));
1144 const auto xyzD65 = AbsoluteColor::fromOklabToXyzD65(oklab);
1145 const auto xyzD50 = AbsoluteColor::fromXyzD65ToXyzD50(xyzD65);
1146 const auto cielabD50 = AbsoluteColor::fromXyzD50ToCielabD50(xyzD50);
1147 const auto cielabD50cms = cielabD50.reinterpretAsLabToCmscielab();
1148 const auto rgb = fromCielabD50ToQRgbOrTransparent(cielabD50cms);
1149 return (qAlpha(rgb) != 0);
1156bool RgbColorSpace::isCielabD50InGamut(
const cmsCIELab &lab)
const
1158 if (!isInRange<
decltype(lab.L)>(0, lab.L, 100)) {
1161 const auto chromaSquare = lab.a * lab.a + lab.b * lab.b;
1162 const auto maximumChromaSquare = qPow(d_pointer->m_profileMaximumCielchD50Chroma, 2);
1163 if (chromaSquare > maximumChromaSquare) {
1166 return qAlpha(fromCielabD50ToQRgbOrTransparent(lab)) != 0;
1183QRgb RgbColorSpace::fromCielabD50ToQRgbOrTransparent(
const cmsCIELab &lab)
const
1185 constexpr QRgb transparentValue = 0;
1186 static_assert(qAlpha(transparentValue) == 0,
1187 "The alpha value of a transparent QRgb must be 0.");
1192 d_pointer->m_transformCielabD50ToRgbHandle,
1199 const bool colorIsValid =
1200 isInRange<double>(0, rgb[0], 1)
1201 && isInRange<double>(0, rgb[1], 1)
1202 && isInRange<double>(0, rgb[2], 1);
1203 if (!colorIsValid) {
1204 return transparentValue;
1208 cmsCIELab roundtripCielabD50;
1211 d_pointer->m_transformRgbToCielabD50Handle,
1213 &roundtripCielabD50,
1216 const qreal actualDeviationSquare =
1217 qPow(lab.L - roundtripCielabD50.L, 2)
1218 + qPow(lab.a - roundtripCielabD50.a, 2)
1219 + qPow(lab.b - roundtripCielabD50.b, 2);
1220 constexpr auto cielabDeviationLimitSquare =
1221 RgbColorSpacePrivate::cielabDeviationLimit
1222 * RgbColorSpacePrivate::cielabDeviationLimit;
1223 const bool actualDeviationIsOkay =
1224 actualDeviationSquare <= cielabDeviationLimitSquare;
1227 if (!actualDeviationIsOkay) {
1228 return transparentValue;
1233 static_cast<QColorFloatType
>(rgb[1]),
1234 static_cast<QColorFloatType
>(rgb[2]));
1248 const cmsCIELCh myCmsCieLch = toCmsLch(lch);
1256 d_pointer->m_transformCielabD50ToRgbHandle,
1261 return GenericColor(rgb[0], rgb[1], rgb[2]);
1267double RgbColorSpacePrivate::detectMaximumCielchD50Chroma()
const
1271 static_assert(0. + chromaDetectionHuePrecision > 0.);
1272 static_assert(360. + chromaDetectionHuePrecision > 360.);
1278 const auto qColorHue =
static_cast<QColorFloatType
>(
hue / 360.);
1280 result = qMax(result, q_pointer->toCielchD50Double(color).c);
1281 hue += chromaDetectionHuePrecision;
1283 result = result * chromaDetectionIncrementFactor + cielabDeviationLimit;
1284 return std::min<double>(result, CielchD50Values::maximumChroma);
1290double RgbColorSpacePrivate::detectMaximumOklchChroma()
const
1294 static_assert(0. + chromaDetectionHuePrecision > 0.);
1295 static_assert(360. + chromaDetectionHuePrecision > 360.);
1297 double chromaSquare = 0;
1300 const auto qColorHue =
static_cast<QColorFloatType
>(
hue / 360.);
1302 const auto cielabD50Color = q_pointer->toCielabD50(rgbColor);
1303 const auto cielabD50 = GenericColor(cielabD50Color);
1304 const auto xyzD50 = AbsoluteColor::fromCielabD50ToXyzD50(cielabD50);
1305 const auto xyzD65 = AbsoluteColor::fromXyzD50ToXyzD65(xyzD50);
1306 const auto oklab = AbsoluteColor::fromXyzD65ToOklab(xyzD65);
1307 chromaSquare = qMax(
1309 oklab.second * oklab.second + oklab.third * oklab.third);
1310 hue += chromaDetectionHuePrecision;
1312 const auto result = qSqrt(chromaSquare) * chromaDetectionIncrementFactor
1313 + oklabDeviationLimit;
1314 return std::min<double>(result, OklchValues::maximumChroma);
1326 const cmsUInt32Number intentCount =
1327 cmsGetSupportedIntents(0,
nullptr,
nullptr);
1328 cmsUInt32Number *codeArray =
new cmsUInt32Number[intentCount];
1329 char **descriptionArray =
new char *[intentCount];
1330 cmsGetSupportedIntents(intentCount, codeArray, descriptionArray);
1331 for (cmsUInt32Number i = 0; i < intentCount; ++i) {
1335 delete[] descriptionArray;
KGUIADDONS_EXPORT qreal hue(const QColor &)
KCOREADDONS_EXPORT QString versionString()
QStringView countryCode(QStringView coachNumber)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
The namespace of this library.
QByteArray & append(QByteArrayView data)
const char * constData() const const
void reserve(qsizetype size)
qsizetype size() const const
QColor fromHsvF(float h, float s, float v, float a)
QColor fromRgbF(float r, float g, float b, float a)
QRgba64 rgba64() const const
QCoreApplication * instance()
QString absoluteFilePath() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
void reserve(qsizetype size)
QString name() const const
iterator insert(const Key &key, const T &value)
quint16 blue() const const
quint16 green() const const
quint16 red() const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString fromWCharArray(const wchar_t *string, qsizetype size)
QString number(double n, char format, int precision)
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
const_pointer constData() const const
qsizetype size() const const
QVersionNumber fromString(QAnyStringView string, qsizetype *suffixIndex)
A LCH color (Oklch, CielchD50, CielchD65…)