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>
35#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
36#include <qcontainerfwd.h>
39#include <qstringlist.h>
56RgbColorSpace::RgbColorSpace(
QObject *parent)
58 , d_pointer(new RgbColorSpacePrivate(this))
87 cmsHPROFILE srgb = cmsCreate_sRGBProfile();
88 const bool success = result->d_pointer->initialize(srgb);
89 cmsCloseProfile(srgb);
102 std::optional<QStringList>());
105 result->d_pointer->m_profileCreationDateTime =
QDateTime();
107 result->d_pointer->m_profileManufacturer = tr(
"LittleCMS");
108 result->d_pointer->m_profileModel =
QString();
110 result->d_pointer->m_profileName = tr(
"sRGB color space");
111 result->d_pointer->m_profileMaximumCielchD50Chroma = 132;
161 constexpr auto myContextID =
nullptr;
164 cmsIOHANDLER *myIOHandler =
165 IOHandlerFactory::createReadOnly(myContextID, fileName);
166 if (myIOHandler ==
nullptr) {
171 cmsHPROFILE myProfileHandle =
172 cmsOpenProfileFromIOhandlerTHR(myContextID, myIOHandler);
173 if (myProfileHandle ==
nullptr) {
185 newObject->d_pointer->m_profileAbsoluteFilePath =
186 myFileInfo.absoluteFilePath();
187 newObject->d_pointer->m_profileFileSize = myFileInfo.
size();
188 const bool success = newObject->d_pointer->initialize(myProfileHandle);
191 cmsCloseProfile(myProfileHandle);
228bool RgbColorSpacePrivate::initialize(cmsHPROFILE rgbProfileHandle)
230 constexpr auto renderingIntent = INTENT_ABSOLUTE_COLORIMETRIC;
232 m_profileClass = cmsGetDeviceClass(rgbProfileHandle);
233 m_profileColorModel = cmsGetColorSpace(rgbProfileHandle);
234 m_profileCopyright = getInformationFromProfile(rgbProfileHandle,
236 m_profileCreationDateTime =
237 getCreationDateTimeFromProfile(rgbProfileHandle);
238 const bool inputUsesCLUT = cmsIsCLUT(rgbProfileHandle,
241 const bool outputUsesCLUT = cmsIsCLUT(rgbProfileHandle,
243 LCMS_USED_AS_OUTPUT);
248 m_profileHasClut = inputUsesCLUT || outputUsesCLUT;
249 m_profileHasMatrixShaper = cmsIsMatrixShaper(rgbProfileHandle);
250 m_profileIccVersion = getIccVersionFromProfile(rgbProfileHandle);
251 m_profileManufacturer = getInformationFromProfile(rgbProfileHandle,
252 cmsInfoManufacturer);
253 m_profileModel = getInformationFromProfile(rgbProfileHandle,
255 m_profileName = getInformationFromProfile(rgbProfileHandle,
257 m_profilePcsColorModel = cmsGetPCS(rgbProfileHandle);
261 cmsHPROFILE cielabD50ProfileHandle = cmsCreateLab4Profile(
275 constexpr auto flags = cmsFLAGS_NOCACHE;
276 m_transformCielabD50ToRgbHandle = cmsCreateTransform(
278 cielabD50ProfileHandle,
284 m_transformCielabD50ToRgb16Handle = cmsCreateTransform(
286 cielabD50ProfileHandle,
292 m_transformRgbToCielabD50Handle = cmsCreateTransform(
296 cielabD50ProfileHandle,
301 cmsCloseProfile(cielabD50ProfileHandle);
306 if ((m_transformCielabD50ToRgbHandle ==
nullptr)
307 || (m_transformCielabD50ToRgb16Handle ==
nullptr)
308 || (m_transformRgbToCielabD50Handle ==
nullptr)
322 while (!q_pointer->isCielchD50InGamut(candidate)) {
323 candidate.l += gamutPrecisionCielab;
324 if (candidate.l >= 100) {
328 m_cielabD50BlackpointL = candidate.l;
330 while (!q_pointer->isCielchD50InGamut(candidate)) {
331 candidate.l -= gamutPrecisionCielab;
332 if (candidate.l <= m_cielabD50BlackpointL) {
336 m_cielabD50WhitepointL = candidate.l;
339 while (!q_pointer->isOklchInGamut(candidate)) {
340 candidate.l += gamutPrecisionOklab;
341 if (candidate.l >= 1) {
345 m_oklabBlackpointL = candidate.l;
347 while (!q_pointer->isOklchInGamut(candidate)) {
348 candidate.l -= gamutPrecisionOklab;
349 if (candidate.l <= m_oklabBlackpointL) {
353 m_oklabWhitepointL = candidate.l;
357 m_profileMaximumCielchD50Chroma = detectMaximumCielchD50Chroma();
358 m_profileMaximumOklchChroma = detectMaximumOklchChroma();
364RgbColorSpace::~RgbColorSpace() noexcept
366 RgbColorSpacePrivate::deleteTransform(
367 &d_pointer->m_transformCielabD50ToRgb16Handle);
368 RgbColorSpacePrivate::deleteTransform(
369 &d_pointer->m_transformCielabD50ToRgbHandle);
370 RgbColorSpacePrivate::deleteTransform(
371 &d_pointer->m_transformRgbToCielabD50Handle);
378RgbColorSpacePrivate::RgbColorSpacePrivate(RgbColorSpace *backLink)
379 : q_pointer(backLink)
397void RgbColorSpacePrivate::deleteTransform(cmsHTRANSFORM *transformHandle)
399 if ((*transformHandle) !=
nullptr) {
400 cmsDeleteTransform(*transformHandle);
401 (*transformHandle) =
nullptr;
407QString RgbColorSpace::profileAbsoluteFilePath()
const
409 return d_pointer->m_profileAbsoluteFilePath;
414cmsProfileClassSignature RgbColorSpace::profileClass()
const
416 return d_pointer->m_profileClass;
421cmsColorSpaceSignature RgbColorSpace::profileColorModel()
const
423 return d_pointer->m_profileColorModel;
428QString RgbColorSpace::profileCopyright()
const
430 return d_pointer->m_profileCopyright;
435QDateTime RgbColorSpace::profileCreationDateTime()
const
437 return d_pointer->m_profileCreationDateTime;
442qint64 RgbColorSpace::profileFileSize()
const
444 return d_pointer->m_profileFileSize;
449bool RgbColorSpace::profileHasClut()
const
451 return d_pointer->m_profileHasClut;
456bool RgbColorSpace::profileHasMatrixShaper()
const
458 return d_pointer->m_profileHasMatrixShaper;
465 return d_pointer->m_profileIccVersion;
470QString RgbColorSpace::profileManufacturer()
const
472 return d_pointer->m_profileManufacturer;
477double RgbColorSpace::profileMaximumCielchD50Chroma()
const
479 return d_pointer->m_profileMaximumCielchD50Chroma;
484double RgbColorSpace::profileMaximumOklchChroma()
const
486 return d_pointer->m_profileMaximumOklchChroma;
491QString RgbColorSpace::profileModel()
const
493 return d_pointer->m_profileModel;
498QString RgbColorSpace::profileName()
const
500 return d_pointer->m_profileName;
505cmsColorSpaceSignature RgbColorSpace::profilePcsColorModel()
const
507 return d_pointer->m_profilePcsColorModel;
520QString RgbColorSpacePrivate::getInformationFromProfile(cmsHPROFILE profileHandle, cmsInfoType infoType)
531 languageCode =
list.
at(0).toUtf8();
536 if (languageCode.
size() != 2) {
539 languageCode = QByteArrayLiteral(
"en");
581 const cmsUInt32Number resultLength = cmsGetProfileInfo(
598 const cmsUInt32Number bufferLength = resultLength + 1;
617 wchar_t *buffer =
new wchar_t[bufferLength];
619 for (cmsUInt32Number i = 0; i < bufferLength; ++i) {
639 *(buffer + (bufferLength - 1)) = 0;
707QVersionNumber RgbColorSpacePrivate::getIccVersionFromProfile(cmsHPROFILE profileHandle)
728 cmsGetProfileVersion(profileHandle),
740QDateTime RgbColorSpacePrivate::getCreationDateTimeFromProfile(cmsHPROFILE profileHandle)
743 const bool success = cmsGetHeaderCreationDateTime(profileHandle, &myDateTime);
748 const QDate myDate(myDateTime.tm_year + 1900,
749 myDateTime.tm_mon + 1,
755 const QTime myTime(myDateTime.tm_hour,
757 qBound(0, myDateTime.tm_sec, 59));
783 LchDouble referenceColor = cielchD50color;
786 normalizePolar360(referenceColor.c, referenceColor.h);
789 referenceColor.c = qMin<
decltype(referenceColor.c)>(
791 profileMaximumCielchD50Chroma());
792 referenceColor.l = qBound(d_pointer->m_cielabD50BlackpointL,
794 d_pointer->m_cielabD50WhitepointL);
797 if (isCielchD50InGamut(referenceColor)) {
798 return referenceColor;
805 LchDouble lowerChroma{referenceColor.l, 0, referenceColor.h};
806 if (!isCielchD50InGamut(lowerChroma)) {
811 referenceColor.l = d_pointer->m_cielabD50BlackpointL;
812 lowerChroma.l = d_pointer->m_cielabD50BlackpointL;
816 constexpr bool quickApproximate =
true;
817 if constexpr (quickApproximate) {
819 LchDouble upperChroma{referenceColor};
823 while (upperChroma.c - lowerChroma.c > gamutPrecisionCielab) {
826 temp.c = ((lowerChroma.c + upperChroma.c) / 2);
827 if (isCielchD50InGamut(temp)) {
837 temp = referenceColor;
839 if (isCielchD50InGamut(temp)) {
842 temp.c -= gamutPrecisionCielab;
867 LchDouble referenceColor = oklchColor;
870 normalizePolar360(referenceColor.c, referenceColor.h);
873 referenceColor.c = qMin<
decltype(referenceColor.c)>(
875 profileMaximumOklchChroma());
876 referenceColor.l = qBound(d_pointer->m_oklabBlackpointL,
878 d_pointer->m_oklabWhitepointL);
881 if (isOklchInGamut(referenceColor)) {
882 return referenceColor;
889 LchDouble lowerChroma{referenceColor.l, 0, referenceColor.h};
890 if (!isOklchInGamut(lowerChroma)) {
895 referenceColor.l = d_pointer->m_oklabBlackpointL;
896 lowerChroma.l = d_pointer->m_oklabBlackpointL;
900 constexpr bool quickApproximate =
true;
901 if constexpr (quickApproximate) {
903 LchDouble upperChroma{referenceColor};
907 while (upperChroma.c - lowerChroma.c > gamutPrecisionOklab) {
910 temp.c = ((lowerChroma.c + upperChroma.c) / 2);
911 if (isOklchInGamut(temp)) {
921 temp = referenceColor;
923 if (isOklchInGamut(temp)) {
926 temp.c -= gamutPrecisionOklab;
946cmsCIELab RgbColorSpace::toCielabD50(
const QRgba64 rgbColor)
const
948 constexpr qreal maximum =
949 std::numeric_limits<
decltype(rgbColor.
red())>::max();
950 const double my_rgb[]{rgbColor.
red() / maximum,
951 rgbColor.
green() / maximum,
952 rgbColor.
blue() / maximum};
954 cmsDoTransform(d_pointer->m_transformRgbToCielabD50Handle,
959 if (cielabD50.L < 0) {
978 constexpr qreal maximum =
979 std::numeric_limits<
decltype(rgbColor.
red())>::max();
980 const double my_rgb[]{rgbColor.
red() / maximum,
981 rgbColor.
green() / maximum,
982 rgbColor.
blue() / maximum};
984 cmsDoTransform(d_pointer->m_transformRgbToCielabD50Handle,
989 if (cielabD50.L < 0) {
994 cmsLab2LCh(&cielchD50,
997 return LchDouble{cielchD50.L, cielchD50.C, cielchD50.h};
1012QRgb RgbColorSpace::fromCielchD50ToQRgbBound(
const LchDouble &lch)
const
1014 const cmsCIELCh myCmsCieLch = toCmsLch(lch);
1019 cmsUInt16Number rgb_int[3];
1020 cmsDoTransform(d_pointer->m_transformCielabD50ToRgb16Handle,
1025 constexpr qreal channelMaximumQReal =
1026 std::numeric_limits<cmsUInt16Number>::max();
1027 constexpr quint8 rgbMaximum = 255;
1028 return qRgb(qRound(rgb_int[0] / channelMaximumQReal * rgbMaximum),
1029 qRound(rgb_int[1] / channelMaximumQReal * rgbMaximum),
1030 qRound(rgb_int[2] / channelMaximumQReal * rgbMaximum));
1037bool RgbColorSpace::isCielchD50InGamut(
const LchDouble &lch)
const
1039 if (!isInRange<
decltype(lch.l)>(0, lch.l, 100)) {
1042 if (!isInRange<
decltype(lch.l)>(
1043 (-1) * d_pointer->m_profileMaximumCielchD50Chroma,
1045 d_pointer->m_profileMaximumCielchD50Chroma
1050 const cmsCIELCh myCmsCieLch = toCmsLch(lch);
1051 cmsLCh2Lab(&lab, &myCmsCieLch);
1052 return qAlpha(fromCielabD50ToQRgbOrTransparent(lab)) != 0;
1059bool RgbColorSpace::isOklchInGamut(
const LchDouble &lch)
const
1061 if (!isInRange<
decltype(lch.l)>(0, lch.l, 1)) {
1064 if (!isInRange<
decltype(lch.l)>(
1065 (-1) * d_pointer->m_profileMaximumOklchChroma,
1067 d_pointer->m_profileMaximumOklchChroma
1071 const auto oklab = AbsoluteColor::fromPolarToCartesian(GenericColor(lch));
1072 const auto xyzD65 = AbsoluteColor::fromOklabToXyzD65(oklab);
1073 const auto xyzD50 = AbsoluteColor::fromXyzD65ToXyzD50(xyzD65);
1074 const auto cielabD50 = AbsoluteColor::fromXyzD50ToCielabD50(xyzD50);
1075 const auto cielabD50cms = cielabD50.reinterpretAsLabToCmscielab();
1076 const auto rgb = fromCielabD50ToQRgbOrTransparent(cielabD50cms);
1077 return (qAlpha(rgb) != 0);
1084bool RgbColorSpace::isCielabD50InGamut(
const cmsCIELab &lab)
const
1086 if (!isInRange<
decltype(lab.L)>(0, lab.L, 100)) {
1089 const auto chromaSquare = lab.a * lab.a + lab.b * lab.b;
1090 const auto maximumChromaSquare = qPow(d_pointer->m_profileMaximumCielchD50Chroma, 2);
1091 if (chromaSquare > maximumChromaSquare) {
1094 return qAlpha(fromCielabD50ToQRgbOrTransparent(lab)) != 0;
1111QRgb RgbColorSpace::fromCielabD50ToQRgbOrTransparent(
const cmsCIELab &lab)
const
1113 constexpr QRgb transparentValue = 0;
1114 static_assert(qAlpha(transparentValue) == 0,
1115 "The alpha value of a transparent QRgb must be 0.");
1120 d_pointer->m_transformCielabD50ToRgbHandle,
1127 const bool colorIsValid =
1128 isInRange<double>(0, rgb[0], 1)
1129 && isInRange<double>(0, rgb[1], 1)
1130 && isInRange<double>(0, rgb[2], 1);
1131 if (!colorIsValid) {
1132 return transparentValue;
1136 cmsCIELab roundtripCielabD50;
1139 d_pointer->m_transformRgbToCielabD50Handle,
1141 &roundtripCielabD50,
1144 const qreal actualDeviationSquare =
1145 qPow(lab.L - roundtripCielabD50.L, 2)
1146 + qPow(lab.a - roundtripCielabD50.a, 2)
1147 + qPow(lab.b - roundtripCielabD50.b, 2);
1148 constexpr auto cielabDeviationLimitSquare =
1149 RgbColorSpacePrivate::cielabDeviationLimit
1150 * RgbColorSpacePrivate::cielabDeviationLimit;
1151 const bool actualDeviationIsOkay =
1152 actualDeviationSquare <= cielabDeviationLimitSquare;
1155 if (!actualDeviationIsOkay) {
1156 return transparentValue;
1161 static_cast<QColorFloatType
>(rgb[1]),
1162 static_cast<QColorFloatType
>(rgb[2]));
1176 const cmsCIELCh myCmsCieLch = toCmsLch(lch);
1184 d_pointer->m_transformCielabD50ToRgbHandle,
1189 return GenericColor(rgb[0], rgb[1], rgb[2]);
1195double RgbColorSpacePrivate::detectMaximumCielchD50Chroma()
const
1199 static_assert(0. + chromaDetectionHuePrecision > 0.);
1200 static_assert(360. + chromaDetectionHuePrecision > 360.);
1206 const auto qColorHue =
static_cast<QColorFloatType
>(
hue / 360.);
1208 result = qMax(result, q_pointer->toCielchD50Double(color).c);
1209 hue += chromaDetectionHuePrecision;
1211 result = result * chromaDetectionIncrementFactor + cielabDeviationLimit;
1212 return std::min<double>(result, CielchD50Values::maximumChroma);
1218double RgbColorSpacePrivate::detectMaximumOklchChroma()
const
1222 static_assert(0. + chromaDetectionHuePrecision > 0.);
1223 static_assert(360. + chromaDetectionHuePrecision > 360.);
1225 double chromaSquare = 0;
1228 const auto qColorHue =
static_cast<QColorFloatType
>(
hue / 360.);
1230 const auto cielabD50Color = q_pointer->toCielabD50(rgbColor);
1231 const auto cielabD50 = GenericColor(cielabD50Color);
1232 const auto xyzD50 = AbsoluteColor::fromCielabD50ToXyzD50(cielabD50);
1233 const auto xyzD65 = AbsoluteColor::fromXyzD50ToXyzD65(xyzD50);
1234 const auto oklab = AbsoluteColor::fromXyzD65ToOklab(xyzD65);
1235 chromaSquare = qMax(
1237 oklab.second * oklab.second + oklab.third * oklab.third);
1238 hue += chromaDetectionHuePrecision;
1240 const auto result = qSqrt(chromaSquare) * chromaDetectionIncrementFactor
1241 + oklabDeviationLimit;
1242 return std::min<double>(result, OklchValues::maximumChroma);
1254 const cmsUInt32Number intentCount =
1255 cmsGetSupportedIntents(0,
nullptr,
nullptr);
1256 cmsUInt32Number *codeArray =
new cmsUInt32Number[intentCount];
1257 char **descriptionArray =
new char *[intentCount];
1258 cmsGetSupportedIntents(intentCount, codeArray, descriptionArray);
1259 for (cmsUInt32Number i = 0; i < intentCount; ++i) {
1263 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.
const char * constData() const const
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()
const_reference at(qsizetype i) const const
qsizetype count() const const
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 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…)