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 "helpermath.h"
16#include "helperqttypes.h"
17#include "initializetranslation.h"
18#include "iohandlerfactory.h"
22#include <qbytearray.h>
24#include <qcoreapplication.h>
28#include <qnamespace.h>
30#include <qsharedpointer.h>
31#include <qstringliteral.h>
34#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
35#include <qcontainerfwd.h>
38#include <qstringlist.h>
55RgbColorSpace::RgbColorSpace(QObject *parent)
57 , d_pointer(new RgbColorSpacePrivate(this))
86 cmsHPROFILE srgb = cmsCreate_sRGBProfile();
87 const bool success = result->d_pointer->initialize(srgb);
88 cmsCloseProfile(srgb);
101 std::optional<QStringList>());
104 result->d_pointer->m_profileCreationDateTime =
QDateTime();
106 result->d_pointer->m_profileManufacturer = tr(
"LittleCMS");
107 result->d_pointer->m_profileModel =
QString();
109 result->d_pointer->m_profileName = tr(
"sRGB color space");
110 result->d_pointer->m_profileMaximumCielchD50Chroma = 132;
160 constexpr auto myContextID =
nullptr;
163 cmsIOHANDLER *myIOHandler =
164 IOHandlerFactory::createReadOnly(myContextID, fileName);
165 if (myIOHandler ==
nullptr) {
170 cmsHPROFILE myProfileHandle =
171 cmsOpenProfileFromIOhandlerTHR(myContextID, myIOHandler);
172 if (myProfileHandle ==
nullptr) {
184 newObject->d_pointer->m_profileAbsoluteFilePath =
186 newObject->d_pointer->m_profileFileSize = myFileInfo.
size();
187 const bool success = newObject->d_pointer->initialize(myProfileHandle);
190 cmsCloseProfile(myProfileHandle);
227bool RgbColorSpacePrivate::initialize(cmsHPROFILE rgbProfileHandle)
229 constexpr auto renderingIntent = INTENT_ABSOLUTE_COLORIMETRIC;
231 m_profileClass = cmsGetDeviceClass(rgbProfileHandle);
232 m_profileColorModel = cmsGetColorSpace(rgbProfileHandle);
237 m_profileCopyright = profileInformation(rgbProfileHandle,
240 m_profileCreationDateTime =
241 profileCreationDateTime(rgbProfileHandle);
242 const bool inputUsesCLUT = cmsIsCLUT(rgbProfileHandle,
245 const bool outputUsesCLUT = cmsIsCLUT(rgbProfileHandle,
247 LCMS_USED_AS_OUTPUT);
252 m_profileHasClut = inputUsesCLUT || outputUsesCLUT;
253 m_profileHasMatrixShaper = cmsIsMatrixShaper(rgbProfileHandle);
254 m_profileIccVersion = profileIccVersion(rgbProfileHandle);
255 m_profileManufacturer = profileInformation(rgbProfileHandle,
258 m_profileModel = profileInformation(rgbProfileHandle,
261 m_profileName = profileInformation(rgbProfileHandle,
264 m_profilePcsColorModel = cmsGetPCS(rgbProfileHandle);
265 m_profileTagSignatures = profileTagSignatures(rgbProfileHandle);
295 if (m_profileTagSignatures.contains(QStringLiteral(
"vcgt"))) {
298 m_profileTagWhitepoint = profileReadCmsciexyzTag(rgbProfileHandle,
299 cmsSigMediaWhitePointTag);
300 m_profileTagBlackpoint = profileReadCmsciexyzTag(rgbProfileHandle,
301 cmsSigMediaBlackPointTag);
302 m_profileTagRedPrimary = profileReadCmsciexyzTag(rgbProfileHandle,
303 cmsSigRedColorantTag);
304 m_profileTagGreenPrimary = profileReadCmsciexyzTag(rgbProfileHandle,
305 cmsSigGreenColorantTag);
306 m_profileTagBluePrimary = profileReadCmsciexyzTag(rgbProfileHandle,
307 cmsSigBlueColorantTag);
311 cmsHPROFILE cielabD50ProfileHandle = cmsCreateLab4Profile(
325 constexpr auto flags = cmsFLAGS_NOCACHE;
326 m_transformCielabD50ToRgbHandle = cmsCreateTransform(
328 cielabD50ProfileHandle,
334 m_transformCielabD50ToRgb16Handle = cmsCreateTransform(
336 cielabD50ProfileHandle,
342 m_transformRgbToCielabD50Handle = cmsCreateTransform(
346 cielabD50ProfileHandle,
351 cmsCloseProfile(cielabD50ProfileHandle);
356 if ((m_transformCielabD50ToRgbHandle ==
nullptr)
357 || (m_transformCielabD50ToRgb16Handle ==
nullptr)
358 || (m_transformRgbToCielabD50Handle ==
nullptr)
368 GenericColor candidate;
369 candidate.second = 0;
372 while (!q_pointer->isCielchD50InGamut(candidate)) {
373 candidate.first += gamutPrecisionCielab;
374 if (candidate.first >= 100) {
378 m_cielabD50BlackpointL = candidate.first;
379 candidate.first = 100;
380 while (!q_pointer->isCielchD50InGamut(candidate)) {
381 candidate.first -= gamutPrecisionCielab;
382 if (candidate.first <= m_cielabD50BlackpointL) {
386 m_cielabD50WhitepointL = candidate.first;
389 while (!q_pointer->isOklchInGamut(candidate)) {
390 candidate.first += gamutPrecisionOklab;
391 if (candidate.first >= 1) {
395 m_oklabBlackpointL = candidate.first;
397 while (!q_pointer->isOklchInGamut(candidate)) {
398 candidate.first -= gamutPrecisionOklab;
399 if (candidate.first <= m_oklabBlackpointL) {
403 m_oklabWhitepointL = candidate.first;
407 m_profileMaximumCielchD50Chroma = detectMaximumCielchD50Chroma();
408 m_profileMaximumOklchChroma = detectMaximumOklchChroma();
414RgbColorSpace::~RgbColorSpace() noexcept
416 RgbColorSpacePrivate::deleteTransform(
417 &d_pointer->m_transformCielabD50ToRgb16Handle);
418 RgbColorSpacePrivate::deleteTransform(
419 &d_pointer->m_transformCielabD50ToRgbHandle);
420 RgbColorSpacePrivate::deleteTransform(
421 &d_pointer->m_transformRgbToCielabD50Handle);
428RgbColorSpacePrivate::RgbColorSpacePrivate(RgbColorSpace *backLink)
429 : q_pointer(backLink)
447void RgbColorSpacePrivate::deleteTransform(cmsHTRANSFORM *transformHandle)
449 if ((*transformHandle) !=
nullptr) {
450 cmsDeleteTransform(*transformHandle);
451 (*transformHandle) =
nullptr;
457QString RgbColorSpace::profileAbsoluteFilePath()
const
459 return d_pointer->m_profileAbsoluteFilePath;
464cmsProfileClassSignature RgbColorSpace::profileClass()
const
466 return d_pointer->m_profileClass;
471cmsColorSpaceSignature RgbColorSpace::profileColorModel()
const
473 return d_pointer->m_profileColorModel;
478QString RgbColorSpace::profileCopyright()
const
480 return d_pointer->m_profileCopyright;
485QDateTime RgbColorSpace::profileCreationDateTime()
const
487 return d_pointer->m_profileCreationDateTime;
492qint64 RgbColorSpace::profileFileSize()
const
494 return d_pointer->m_profileFileSize;
499bool RgbColorSpace::profileHasClut()
const
501 return d_pointer->m_profileHasClut;
506bool RgbColorSpace::profileHasMatrixShaper()
const
508 return d_pointer->m_profileHasMatrixShaper;
515 return d_pointer->m_profileIccVersion;
520QString RgbColorSpace::profileManufacturer()
const
522 return d_pointer->m_profileManufacturer;
527double RgbColorSpace::profileMaximumCielchD50Chroma()
const
529 return d_pointer->m_profileMaximumCielchD50Chroma;
534double RgbColorSpace::profileMaximumOklchChroma()
const
536 return d_pointer->m_profileMaximumOklchChroma;
541QString RgbColorSpace::profileModel()
const
543 return d_pointer->m_profileModel;
548QString RgbColorSpace::profileName()
const
550 return d_pointer->m_profileName;
555cmsColorSpaceSignature RgbColorSpace::profilePcsColorModel()
const
557 return d_pointer->m_profilePcsColorModel;
562std::optional<cmsCIEXYZ> RgbColorSpace::profileTagBlackpoint()
const
564 return d_pointer->m_profileTagBlackpoint;
569std::optional<cmsCIEXYZ> RgbColorSpace::profileTagBluePrimary()
const
571 return d_pointer->m_profileTagBluePrimary;
576std::optional<cmsCIEXYZ> RgbColorSpace::profileTagGreenPrimary()
const
578 return d_pointer->m_profileTagGreenPrimary;
583std::optional<cmsCIEXYZ> RgbColorSpace::profileTagRedPrimary()
const
585 return d_pointer->m_profileTagRedPrimary;
590QStringList RgbColorSpace::profileTagSignatures()
const
592 return d_pointer->m_profileTagSignatures;
597std::optional<cmsCIEXYZ> RgbColorSpace::profileTagWhitepoint()
const
599 return d_pointer->m_profileTagWhitepoint;
617QString RgbColorSpacePrivate::profileInformation(cmsHPROFILE profileHandle, cmsInfoType infoType,
const QString &languageTerritory)
628 languageCode =
list.
at(0).toUtf8();
633 if (languageCode.
size() != 2) {
636 languageCode = QByteArrayLiteral(
"en");
678 const cmsUInt32Number resultLength = cmsGetProfileInfo(
695 const cmsUInt32Number bufferLength = resultLength + 1;
714 wchar_t *buffer =
new wchar_t[bufferLength];
716 for (cmsUInt32Number i = 0; i < bufferLength; ++i) {
736 *(buffer + (bufferLength - 1)) = 0;
804QVersionNumber RgbColorSpacePrivate::profileIccVersion(cmsHPROFILE profileHandle)
825 cmsGetProfileVersion(profileHandle),
837QDateTime RgbColorSpacePrivate::profileCreationDateTime(cmsHPROFILE profileHandle)
840 const bool success = cmsGetHeaderCreationDateTime(profileHandle, &myDateTime);
845 const QDate myDate(myDateTime.tm_year + 1900,
846 myDateTime.tm_mon + 1,
852 const QTime myTime(myDateTime.tm_hour,
854 qBound(0, myDateTime.tm_sec, 59));
871QStringList RgbColorSpacePrivate::profileTagSignatures(cmsHPROFILE profileHandle)
873 const cmsInt32Number count = cmsGetTagCount(profileHandle);
879 const cmsUInt32Number countUnsigned =
static_cast<cmsUInt32Number
>(count);
880 using underlyingType = std::underlying_type<cmsTagSignature>::type;
881 for (cmsUInt32Number i = 0; i < countUnsigned; ++i) {
882 const underlyingType value = cmsGetTagSignature(profileHandle, i);
887 sizeof(underlyingType) == 4,
888 "cmsTagSignature must have 4 bytes for this code to work.");
889 byteArray.
append(
static_cast<char>((value >> 24) & 0xFF));
890 byteArray.
append(
static_cast<char>((value >> 16) & 0xFF));
891 byteArray.
append(
static_cast<char>((value >> 8) & 0xFF));
892 byteArray.
append(
static_cast<char>(value & 0xFF));
911std::optional<cmsCIEXYZ> RgbColorSpacePrivate::profileReadCmsciexyzTag(cmsHPROFILE profileHandle, cmsTagSignature signature)
913 if (!cmsIsTag(profileHandle, signature)) {
917 void *voidPointer = cmsReadTag(profileHandle, signature);
919 if (voidPointer ==
nullptr) {
923 const cmsCIEXYZ result = *
static_cast<cmsCIEXYZ *
>(voidPointer);
941PerceptualColor::GenericColor RgbColorSpace::reduceCielchD50ChromaToFitIntoGamut(
const PerceptualColor::GenericColor &cielchD50color)
const
943 GenericColor referenceColor = cielchD50color;
946 normalizePolar360(referenceColor.second, referenceColor.third);
949 referenceColor.second = qMin<
decltype(referenceColor.second)>(
950 referenceColor.second,
951 profileMaximumCielchD50Chroma());
952 referenceColor.first = qBound(d_pointer->m_cielabD50BlackpointL,
953 referenceColor.first,
954 d_pointer->m_cielabD50WhitepointL);
957 if (isCielchD50InGamut(referenceColor)) {
958 return referenceColor;
965 GenericColor lowerChroma{referenceColor.first, 0, referenceColor.third};
966 if (!isCielchD50InGamut(lowerChroma)) {
971 referenceColor.first = d_pointer->m_cielabD50BlackpointL;
972 lowerChroma.first = d_pointer->m_cielabD50BlackpointL;
976 constexpr bool quickApproximate =
true;
977 if constexpr (quickApproximate) {
979 GenericColor upperChroma{referenceColor};
983 while (upperChroma.second - lowerChroma.second > gamutPrecisionCielab) {
986 temp.second = ((lowerChroma.second + upperChroma.second) / 2);
987 if (isCielchD50InGamut(temp)) {
997 temp = referenceColor;
998 while (temp.second > 0) {
999 if (isCielchD50InGamut(temp)) {
1002 temp.second -= gamutPrecisionCielab;
1005 if (temp.second < 0) {
1025PerceptualColor::GenericColor RgbColorSpace::reduceOklchChromaToFitIntoGamut(
const PerceptualColor::GenericColor &oklchColor)
const
1027 GenericColor referenceColor = oklchColor;
1030 normalizePolar360(referenceColor.second, referenceColor.third);
1033 referenceColor.second = qMin<
decltype(referenceColor.second)>(
1034 referenceColor.second,
1035 profileMaximumOklchChroma());
1036 referenceColor.first = qBound(d_pointer->m_oklabBlackpointL,
1037 referenceColor.first,
1038 d_pointer->m_oklabWhitepointL);
1041 if (isOklchInGamut(referenceColor)) {
1042 return referenceColor;
1049 GenericColor lowerChroma{referenceColor.first, 0, referenceColor.third};
1050 if (!isOklchInGamut(lowerChroma)) {
1055 referenceColor.first = d_pointer->m_oklabBlackpointL;
1056 lowerChroma.first = d_pointer->m_oklabBlackpointL;
1060 constexpr bool quickApproximate =
true;
1061 if constexpr (quickApproximate) {
1063 GenericColor upperChroma{referenceColor};
1067 while (upperChroma.second - lowerChroma.second > gamutPrecisionOklab) {
1070 temp.second = ((lowerChroma.second + upperChroma.second) / 2);
1071 if (isOklchInGamut(temp)) {
1081 temp = referenceColor;
1082 while (temp.second > 0) {
1083 if (isOklchInGamut(temp)) {
1086 temp.second -= gamutPrecisionOklab;
1089 if (temp.second < 0) {
1106cmsCIELab RgbColorSpace::toCielabD50(
const QRgba64 rgbColor)
const
1108 constexpr qreal maximum =
1109 std::numeric_limits<
decltype(rgbColor.
red())>::max();
1110 const double my_rgb[]{rgbColor.
red() / maximum,
1111 rgbColor.
green() / maximum,
1112 rgbColor.
blue() / maximum};
1113 cmsCIELab cielabD50;
1114 cmsDoTransform(d_pointer->m_transformRgbToCielabD50Handle,
1119 if (cielabD50.L < 0) {
1137PerceptualColor::GenericColor RgbColorSpace::toCielchD50(
const QRgba64 rgbColor)
const
1139 constexpr qreal maximum =
1140 std::numeric_limits<
decltype(rgbColor.
red())>::max();
1141 const double my_rgb[]{rgbColor.
red() / maximum,
1142 rgbColor.
green() / maximum,
1143 rgbColor.
blue() / maximum};
1144 cmsCIELab cielabD50;
1145 cmsDoTransform(d_pointer->m_transformRgbToCielabD50Handle,
1150 if (cielabD50.L < 0) {
1154 cmsCIELCh cielchD50;
1155 cmsLab2LCh(&cielchD50,
1158 return GenericColor{cielchD50.L, cielchD50.C, cielchD50.h};
1173cmsCIELab RgbColorSpace::fromLchToCmsCIELab(
const GenericColor &lch)
1175 const cmsCIELCh myCmsCieLch = lch.reinterpretAsLchToCmscielch();
1195QRgb RgbColorSpace::fromCielchD50ToQRgbBound(
const GenericColor &cielchD50)
const
1197 const auto cielabD50 = fromLchToCmsCIELab(cielchD50);
1198 cmsUInt16Number rgb_int[3];
1199 cmsDoTransform(d_pointer->m_transformCielabD50ToRgb16Handle,
1204 constexpr qreal channelMaximumQReal =
1205 std::numeric_limits<cmsUInt16Number>::max();
1206 constexpr quint8 rgbMaximum = 255;
1207 return qRgb(qRound(rgb_int[0] / channelMaximumQReal * rgbMaximum),
1208 qRound(rgb_int[1] / channelMaximumQReal * rgbMaximum),
1209 qRound(rgb_int[2] / channelMaximumQReal * rgbMaximum));
1216bool RgbColorSpace::isCielchD50InGamut(
const GenericColor &lch)
const
1218 if (!isInRange<
decltype(lch.first)>(0, lch.first, 100)) {
1221 if (!isInRange<
decltype(lch.first)>(
1222 (-1) * d_pointer->m_profileMaximumCielchD50Chroma,
1224 d_pointer->m_profileMaximumCielchD50Chroma
1228 const auto cielabD50 = fromLchToCmsCIELab(lch);
1229 return qAlpha(fromCielabD50ToQRgbOrTransparent(cielabD50)) != 0;
1236bool RgbColorSpace::isOklchInGamut(
const GenericColor &lch)
const
1238 if (!isInRange<
decltype(lch.first)>(0, lch.first, 1)) {
1241 if (!isInRange<
decltype(lch.first)>(
1242 (-1) * d_pointer->m_profileMaximumOklchChroma,
1244 d_pointer->m_profileMaximumOklchChroma
1248 const auto oklab = AbsoluteColor::fromPolarToCartesian(GenericColor(lch));
1249 const auto xyzD65 = AbsoluteColor::fromOklabToXyzD65(oklab);
1250 const auto xyzD50 = AbsoluteColor::fromXyzD65ToXyzD50(xyzD65);
1251 const auto cielabD50 = AbsoluteColor::fromXyzD50ToCielabD50(xyzD50);
1252 const auto cielabD50cms = cielabD50.reinterpretAsLabToCmscielab();
1253 const auto rgb = fromCielabD50ToQRgbOrTransparent(cielabD50cms);
1254 return (qAlpha(rgb) != 0);
1261bool RgbColorSpace::isCielabD50InGamut(
const cmsCIELab &lab)
const
1263 if (!isInRange<
decltype(lab.L)>(0, lab.L, 100)) {
1266 const auto chromaSquare = lab.a * lab.a + lab.b * lab.b;
1267 const auto maximumChromaSquare = qPow(d_pointer->m_profileMaximumCielchD50Chroma, 2);
1268 if (chromaSquare > maximumChromaSquare) {
1271 return qAlpha(fromCielabD50ToQRgbOrTransparent(lab)) != 0;
1288QRgb RgbColorSpace::fromCielabD50ToQRgbOrTransparent(
const cmsCIELab &lab)
const
1290 constexpr QRgb transparentValue = 0;
1291 static_assert(qAlpha(transparentValue) == 0,
1292 "The alpha value of a transparent QRgb must be 0.");
1297 d_pointer->m_transformCielabD50ToRgbHandle,
1304 const bool colorIsValid =
1305 isInRange<double>(0, rgb[0], 1)
1306 && isInRange<double>(0, rgb[1], 1)
1307 && isInRange<double>(0, rgb[2], 1);
1308 if (!colorIsValid) {
1309 return transparentValue;
1313 cmsCIELab roundtripCielabD50;
1316 d_pointer->m_transformRgbToCielabD50Handle,
1318 &roundtripCielabD50,
1321 const qreal actualDeviationSquare =
1322 qPow(lab.L - roundtripCielabD50.L, 2)
1323 + qPow(lab.a - roundtripCielabD50.a, 2)
1324 + qPow(lab.b - roundtripCielabD50.b, 2);
1325 constexpr auto cielabDeviationLimitSquare =
1326 RgbColorSpacePrivate::cielabDeviationLimit
1327 * RgbColorSpacePrivate::cielabDeviationLimit;
1328 const bool actualDeviationIsOkay =
1329 actualDeviationSquare <= cielabDeviationLimitSquare;
1332 if (!actualDeviationIsOkay) {
1333 return transparentValue;
1338 static_cast<QColorFloatType
>(rgb[1]),
1339 static_cast<QColorFloatType
>(rgb[2]));
1351PerceptualColor::GenericColor RgbColorSpace::fromCielchD50ToRgb1(
const PerceptualColor::GenericColor &lch)
const
1353 const auto cielabD50 = fromLchToCmsCIELab(lch);
1357 d_pointer->m_transformCielabD50ToRgbHandle,
1362 return GenericColor(rgb[0], rgb[1], rgb[2]);
1368double RgbColorSpacePrivate::detectMaximumCielchD50Chroma()
const
1372 static_assert(0. + chromaDetectionHuePrecision > 0.);
1373 static_assert(360. + chromaDetectionHuePrecision > 360.);
1379 const auto qColorHue =
static_cast<QColorFloatType
>(
hue / 360.);
1381 result = qMax(result, q_pointer->toCielchD50(color).second);
1382 hue += chromaDetectionHuePrecision;
1384 result = result * chromaDetectionIncrementFactor + cielabDeviationLimit;
1385 return std::min<double>(result, CielchD50Values::maximumChroma);
1391double RgbColorSpacePrivate::detectMaximumOklchChroma()
const
1395 static_assert(0. + chromaDetectionHuePrecision > 0.);
1396 static_assert(360. + chromaDetectionHuePrecision > 360.);
1398 double chromaSquare = 0;
1401 const auto qColorHue =
static_cast<QColorFloatType
>(
hue / 360.);
1403 const auto cielabD50Color = q_pointer->toCielabD50(rgbColor);
1404 const auto cielabD50 = GenericColor(cielabD50Color);
1405 const auto xyzD50 = AbsoluteColor::fromCielabD50ToXyzD50(cielabD50);
1406 const auto xyzD65 = AbsoluteColor::fromXyzD50ToXyzD65(xyzD50);
1407 const auto oklab = AbsoluteColor::fromXyzD65ToOklab(xyzD65);
1408 chromaSquare = qMax(
1410 oklab.second * oklab.second + oklab.third * oklab.third);
1411 hue += chromaDetectionHuePrecision;
1413 const auto result = qSqrt(chromaSquare) * chromaDetectionIncrementFactor
1414 + oklabDeviationLimit;
1415 return std::min<double>(result, OklchValues::maximumChroma);
1427 const cmsUInt32Number intentCount =
1428 cmsGetSupportedIntents(0,
nullptr,
nullptr);
1429 cmsUInt32Number *codeArray =
new cmsUInt32Number[intentCount];
1430 char **descriptionArray =
new char *[intentCount];
1431 cmsGetSupportedIntents(intentCount, codeArray, descriptionArray);
1432 for (cmsUInt32Number i = 0; i < intentCount; ++i) {
1436 delete[] descriptionArray;
KGUIADDONS_EXPORT qreal hue(const QColor &)
QStringView countryCode(QStringView coachNumber)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
The namespace of this library.
const char * versionString()
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)