Krita Source Code Documentation
Loading...
Searching...
No Matches
LcmsColorProfileContainer.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the KDE project
3 * SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
4 * SPDX-FileCopyrightText: 2001 John Califf
5 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
6 * SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
7 * SPDX-FileCopyrightText: 2007 Adrian Page <adrian@pagenet.plus.com>
8 *
9 * SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
13
14#include <QGenericMatrix>
15#include <QTransform>
16#include <array>
17#include <cfloat>
18#include <cmath>
19
20#include <QDebug>
21
22#include "DebugPigment.h"
23#include "kis_debug.h"
24#include "kis_dom_utils.h"
25
26#include <KisLazyStorage.h>
27#include <KisLazyValueWrapper.h>
28#include <lcms2.h>
29
30namespace {
31struct ReverseCurveWrapper
32{
33 ReverseCurveWrapper() : reverseCurve(0) {}
34
35 explicit ReverseCurveWrapper(cmsToneCurve *curve) {
36 reverseCurve = cmsReverseToneCurve(curve);
37 }
38
39 ~ReverseCurveWrapper() {
40 if (reverseCurve) {
41 cmsFreeToneCurve(reverseCurve);
42 }
43 }
44
45 operator cmsToneCurve*() const {
46 return reverseCurve;
47 }
48
49 operator cmsToneCurve*() {
50 return reverseCurve;
51 }
52
53 ReverseCurveWrapper(const ReverseCurveWrapper&rhs) = delete;
54 ReverseCurveWrapper& operator=(const ReverseCurveWrapper&rhs) = delete;
55
56 ReverseCurveWrapper(ReverseCurveWrapper&&rhs) = default;
57 ReverseCurveWrapper& operator=(ReverseCurveWrapper&&rhs) = default;
58
59 cmsToneCurve *reverseCurve {0};
60};
61} // namespace
62
64{
65public:
66 cmsHPROFILE profile;
67 cmsColorSpaceSignature colorSpaceSignature;
68 cmsProfileClassSignature deviceClass;
70 QString manufacturer;
71 QString copyright;
72 QString name;
73 float version;
75 bool valid {false};
76 bool suitableForOutput {false};
77 bool suitableForInput {false};
79
80 using LazyBool = KisLazyStorage<KisLazyValueWrapper<bool>, std::function<bool()>>;
81
82 LazyBool hasTRC = LazyBool(LazyBool::init_value_tag{}, {});
83 LazyBool isLinear = LazyBool(LazyBool::init_value_tag{}, {});
84
86 cmsCIEXYZ mediaWhitePoint;
87 cmsCIExyY whitePoint;
88 std::optional<double> hdrReferenceWhite;
89 cmsCIEXYZTRIPLE colorants;
90 cmsToneCurve *redTRC {0};
91 cmsToneCurve *greenTRC {0};
92 cmsToneCurve *blueTRC {0};
93 cmsToneCurve *grayTRC {0};
94
96
97 LazyReverseCurve redTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
98 LazyReverseCurve greenTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
99 LazyReverseCurve blueTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
100 LazyReverseCurve grayTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
101
102 cmsUInt32Number defaultIntent;
108
109 cmsVideoSignalType *cicpValues {nullptr};
110
111 QByteArray uniqueId;
112};
113
119
121 : d(new Private())
122{
123 d->data = data;
124 d->profile = 0;
125 init();
126}
127
128QByteArray LcmsColorProfileContainer::lcmsProfileToByteArray(const cmsHPROFILE profile)
129{
130 cmsUInt32Number bytesNeeded = 0;
131 // Make a raw data image ready for saving
132 cmsSaveProfileToMem(profile, 0, &bytesNeeded); // calc size
133 QByteArray rawData;
134 rawData.resize(bytesNeeded);
135 if (rawData.size() >= (int)bytesNeeded) {
136 cmsSaveProfileToMem(profile, rawData.data(), &bytesNeeded); // fill buffer
137 } else {
138 qWarning() << "Couldn't resize the profile buffer, system is probably running out of memory.";
139 rawData.resize(0);
140 }
141 return rawData;
142}
143
145{
146 IccColorProfile *iccprofile = new IccColorProfile(lcmsProfileToByteArray(profile));
147 cmsCloseProfile(profile);
148 return iccprofile;
149}
150
152{
153 cmsCloseProfile(d->profile);
154 delete d;
155}
156
157#define _BUFFER_SIZE_ 1000
158
160{
161 if (d->profile) {
162 cmsCloseProfile(d->profile);
163 }
164
165 d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size());
166
167
168#ifndef NDEBUG
169 if (d->data->rawData().size() == 4096) {
170 qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code.";
171 }
172#endif
173
174 if (d->profile) {
175 wchar_t buffer[_BUFFER_SIZE_];
176 d->colorSpaceSignature = cmsGetColorSpace(d->profile);
177 d->deviceClass = cmsGetDeviceClass(d->profile);
178 cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
179 d->name = QString::fromWCharArray(buffer);
180
181 //apparently this should give us a localised string??? Not sure about this.
182 cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
183 d->productDescription = QString::fromWCharArray(buffer);
184
185 cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
186 d->manufacturer = QString::fromWCharArray(buffer);
187
188 cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
189 d->copyright = QString::fromWCharArray(buffer);
190
191 cmsProfileClassSignature profile_class;
192 profile_class = cmsGetDeviceClass(d->profile);
193 d->valid = ( profile_class != cmsSigNamedColorClass
194 && profile_class != cmsSigLinkClass);
195
196 //This is where obtain the whitepoint, and convert it to the actual white point of the profile in the case a Chromatic adaption tag is
197 //present. This is necessary for profiles following the v4 spec.
198 cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption.
199 cmsCIEXYZ *mediaWhitePointPtr;
200 bool whiteComp[3];
201 bool whiteIsD50;
202 // Possible bug in profiles: there are in fact some that says they contain that tag
203 // but in fact the pointer is null.
204 // Let's not crash on it anyway, and assume there is no white point instead.
205 // BUG:423685
206 if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)
207 && (mediaWhitePointPtr = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag))) {
208
209 d->mediaWhitePoint = *(mediaWhitePointPtr);
210 baseMediaWhitePoint = d->mediaWhitePoint;
211
212 whiteComp[0] = std::fabs(baseMediaWhitePoint.X - cmsD50_XYZ()->X) < 0.00001;
213 whiteComp[1] = std::fabs(baseMediaWhitePoint.Y - cmsD50_XYZ()->Y) < 0.00001;
214 whiteComp[2] = std::fabs(baseMediaWhitePoint.Z - cmsD50_XYZ()->Z) < 0.00001;
215 whiteIsD50 = std::all_of(std::begin(whiteComp), std::end(whiteComp), [](bool b) {return b;});
216
217 cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
218 cmsCIEXYZ *CAM1;
219 if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)
220 && (CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag))
221 && whiteIsD50) {
222 //the chromatic adaption tag represent a matrix from the actual white point of the profile to D50.
223
224 //We first put all our data into structures we can manipulate.
225 double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z};
226 QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy);
227 QTransform invertDummy(CAM1[0].X, CAM1[0].Y, CAM1[0].Z, CAM1[1].X, CAM1[1].Y, CAM1[1].Z, CAM1[2].X, CAM1[2].Y, CAM1[2].Z);
228 //we then abuse QTransform's invert function because it probably does matrix inversion 20 times better than I can program.
229 //if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeable
230 //effect when we start multiplying.
231 QTransform invertedDummy = invertDummy.inverted();
232 //we then put the QTransform into a generic 3x3 matrix.
233 double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(),
234 invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(),
235 invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33()
236 };
237 QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy);
238 //multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint.
239 QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix;
240 //and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it
241 //uses column/row for initialising.
242 d->mediaWhitePoint.X = result(0, 0);
243 d->mediaWhitePoint.Y = result(1, 0);
244 d->mediaWhitePoint.Z = result(2, 0);
245 cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
246 }
247 }
248 //This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles.
249 cmsCIEXYZ *tempColorantsRed, *tempColorantsGreen, *tempColorantsBlue;
250 // Note: don't assume that cmsIsTag is enough to check for errors; check the pointers, too
251 // BUG:423685
252 if (cmsIsTag(d->profile, cmsSigRedColorantTag) && cmsIsTag(d->profile, cmsSigRedColorantTag) && cmsIsTag(d->profile, cmsSigRedColorantTag)
253 && (tempColorantsRed = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag))
254 && (tempColorantsGreen = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag))
255 && (tempColorantsBlue = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag))) {
256 cmsCIEXYZTRIPLE tempColorants;
257 tempColorants.Red = *tempColorantsRed;
258 tempColorants.Green = *tempColorantsGreen;
259 tempColorants.Blue = *tempColorantsBlue;
260 //convert to d65, this is useless.
261 cmsAdaptToIlluminant(&d->colorants.Red, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Red);
262 cmsAdaptToIlluminant(&d->colorants.Green, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Green);
263 cmsAdaptToIlluminant(&d->colorants.Blue, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Blue);
264 //d->colorants = tempColorants;
265 d->hasColorants = true;
266 } else {
267 //qDebug()<<d->name<<": has no colorants";
268 d->hasColorants = false;
269 }
270 //retrieve TRC.
271 if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) {
272
273 d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag));
274 d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag));
275 d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag));
279
280 d->hasTRC = Private::LazyBool([d = d] () {
281 return d->redTRC && d->greenTRC && d->blueTRC && *d->redTRCReverse && *d->greenTRCReverse && *d->blueTRCReverse;
282 });
283
284 d->isLinear = Private::LazyBool([d = d] () {
285 return *d->hasTRC
286 && cmsIsToneCurveLinear(d->redTRC)
287 && cmsIsToneCurveLinear(d->greenTRC)
288 && cmsIsToneCurveLinear(d->blueTRC);
289 });
290
291 } else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
292 d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag));
294
295 d->hasTRC = Private::LazyBool([d = d] () {
296 return d->grayTRC && *d->grayTRCReverse;
297 });
298
299 d->isLinear = Private::LazyBool([d = d] () {
300 return *d->hasTRC && cmsIsToneCurveLinear(d->grayTRC);
301 });
302 } else {
303 d->hasTRC = Private::LazyBool(Private::LazyBool::init_value_tag{}, {});
304 }
305
306 if (cmsIsTag(d->profile, cmsSigcicpTag)) {
307 d->cicpValues = ((cmsVideoSignalType *) cmsReadTag (d->profile, cmsSigcicpTag));
308 }
309
310 // Check if the profile can convert (something->this)
311 d->suitableForOutput = cmsIsIntentSupported(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT);
312 d->suitableForInput = cmsIsIntentSupported(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT);
313
314 d->version = cmsGetProfileVersion(d->profile);
315 d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile);
316 d->isMatrixShaper = cmsIsMatrixShaper(d->profile);
317 d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT);
318 d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
319 d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
320 d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT);
321
322 if (cmsIsTag(d->profile, cmsSigMetaTag)) {
323 dbgPigment << "icc profile has dictionary entries";
324
325 cmsHANDLE dictionary = (cmsHANDLE) cmsReadTag(d->profile, cmsSigMetaTag);
326 const cmsDICTentry *entry = cmsDictGetEntryList(dictionary);
327 while (entry) {
328 QString name = QString::fromWCharArray(entry->Name, -1);
329 QString value = QString::fromWCharArray(entry->Value, -1);
330 if (name.toLower() == "crwl") {
331 // https://registry.color.org/dicttype-metadata/crwl
333 d->hdrReferenceWhite = std::make_optional(hdrReferenceWhite);
334
335 }
336 dbgPigment << name << value;
337 dbgPigment << entry->DisplayName << entry->DisplayValue;
338 entry = cmsDictNextEntry(entry);
339 }
340 }
341
342 return true;
343 }
344
345 return false;
346}
347
349{
350 return d->profile;
351}
352
354{
355 return d->colorSpaceSignature;
356}
357
358cmsProfileClassSignature LcmsColorProfileContainer::deviceClass() const
359{
360 return d->deviceClass;
361}
362
364{
365 return d->manufacturer;
366}
367
369{
370 return d->copyright;
371}
372
374{
375 return d->valid;
376}
377
379{
380 return d->version;
381}
382
395
397{
398 return deviceClass() == cmsSigOutputClass;
399}
400
402{
403 return deviceClass() == cmsSigDisplayClass;
404}
405
415{
416 return d->isAbsoluteCLUT;//LCMS2 doesn't convert matrix shapers via absolute intent, because of V4 workflow.
417}
419{
421 return true;
422 }
423 return false;
424}
426{
427 return d->hasColorants;
428}
430{
431 return *d->hasTRC;
432}
434{
435 return *d->isLinear;
436}
437
438KoColorimetryUtils::XYZ fromCieXYZ(const cmsCIEXYZ &xyz) {
439 return KoColorimetryUtils::XYZ{xyz.X, xyz.Y, xyz.Z};
440}
441
442KoColorimetryUtils::xyY fromCIExyY(const cmsCIExyY &xyY) {
443 return KoColorimetryUtils::xyY{xyY.x, xyY.y, xyY.Y};
444}
445
446QVector <KoColorimetryUtils::XYZ> LcmsColorProfileContainer::getColorantsXYZ() const
447{
448 QVector <KoColorimetryUtils::XYZ> colorants(9);
449
450 colorants.append(fromCieXYZ(d->colorants.Red));
451 colorants.append(fromCieXYZ(d->colorants.Green));
452 colorants.append(fromCieXYZ(d->colorants.Blue));
453 return colorants;
454}
455
457{
458 cmsCIEXYZ temp1;
459 cmsCIExyY temp2;
461
462 temp1.X = d->colorants.Red.X;
463 temp1.Y = d->colorants.Red.Y;
464 temp1.Z = d->colorants.Red.Z;
465 cmsXYZ2xyY(&temp2, &temp1);
466 colorants.append(fromCIExyY(temp2));
467
468 temp1.X = d->colorants.Green.X;
469 temp1.Y = d->colorants.Green.Y;
470 temp1.Z = d->colorants.Green.Z;
471 cmsXYZ2xyY(&temp2, &temp1);
472 colorants.append(fromCIExyY(temp2));
473
474 temp1.X = d->colorants.Blue.X;
475 temp1.Y = d->colorants.Blue.Y;
476 temp1.Z = d->colorants.Blue.Z;
477 cmsXYZ2xyY(&temp2, &temp1);
478 colorants.append(fromCIExyY(temp2));
479
480 return colorants;
481}
482
487
492
494{
495 return d->hdrReferenceWhite;
496}
497
499{
500 QVector <double> TRCtriplet(3);
501 if (d->hasColorants && hasTRC()) {
502 if (cmsIsToneCurveLinear(d->redTRC)) {
503 TRCtriplet[0] = 1.0;
504 } else {
505 TRCtriplet[0] = cmsEstimateGamma(d->redTRC, 0.01);
506 }
507 if (cmsIsToneCurveLinear(d->greenTRC)) {
508 TRCtriplet[1] = 1.0;
509 } else {
510 TRCtriplet[1] = cmsEstimateGamma(d->greenTRC, 0.01);
511 }
512 if (cmsIsToneCurveLinear(d->blueTRC)) {
513 TRCtriplet[2] = 1.0;
514 } else {
515 TRCtriplet[2] = cmsEstimateGamma(d->blueTRC, 0.01);
516 }
517
518 } else {
519 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
520 if (cmsIsToneCurveLinear(d->grayTRC)) {
521 TRCtriplet.fill(1.0);
522 } else {
523 TRCtriplet.fill(cmsEstimateGamma(d->grayTRC, 0.01));
524 }
525 } else {
526 TRCtriplet.fill(1.0);
527 }
528 }
529 return TRCtriplet;
530}
531
533{
534 if (d->hasColorants && hasTRC()) {
535 if (!cmsIsToneCurveLinear(d->redTRC)) {
536 Value[0] = cmsEvalToneCurveFloat(d->redTRC, Value[0]);
537 }
538 if (!cmsIsToneCurveLinear(d->greenTRC)) {
539 Value[1] = cmsEvalToneCurveFloat(d->greenTRC, Value[1]);
540 }
541 if (!cmsIsToneCurveLinear(d->blueTRC)) {
542 Value[2] = cmsEvalToneCurveFloat(d->blueTRC, Value[2]);
543 }
544
545 } else {
546 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
547 Value[0] = cmsEvalToneCurveFloat(d->grayTRC, Value[0]);
548 }
549 }
550}
551
553{
554 if (d->hasColorants && hasTRC()) {
555 if (!cmsIsToneCurveLinear(d->redTRC)) {
556 Value[0] = cmsEvalToneCurveFloat(*d->redTRCReverse, Value[0]);
557 }
558 if (!cmsIsToneCurveLinear(d->greenTRC)) {
559 Value[1] = cmsEvalToneCurveFloat(*d->greenTRCReverse, Value[1]);
560 }
561 if (!cmsIsToneCurveLinear(d->blueTRC)) {
562 Value[2] = cmsEvalToneCurveFloat(*d->blueTRCReverse, Value[2]);
563 }
564
565 } else {
566 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
567 Value[0] = cmsEvalToneCurveFloat(*d->grayTRCReverse, Value[0]);
568 }
569 }
570}
571
573{
574 const qreal scale = 65535.0;
575 const qreal invScale = 1.0 / scale;
576
577 if (d->hasColorants && hasTRC()) {
578 //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
579
580 if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
581 quint16 newValue = cmsEvalToneCurve16(d->redTRC, Value[0] * scale);
582 Value[0] = newValue * invScale;
583 }
584 if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
585 quint16 newValue = cmsEvalToneCurve16(d->greenTRC, Value[1] * scale);
586 Value[1] = newValue * invScale;
587 }
588 if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
589 quint16 newValue = cmsEvalToneCurve16(d->blueTRC, Value[2] * scale);
590 Value[2] = newValue * invScale;
591 }
592 } else {
593 if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
594 quint16 newValue = cmsEvalToneCurve16(d->grayTRC, Value[0] * scale);
595 Value[0] = newValue * invScale;
596 }
597 }
598}
600{
601 const qreal scale = 65535.0;
602 const qreal invScale = 1.0 / scale;
603
604 if (d->hasColorants && hasTRC()) {
605 //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
606
607 if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
608 quint16 newValue = cmsEvalToneCurve16(*d->redTRCReverse, Value[0] * scale);
609 Value[0] = newValue * invScale;
610 }
611 if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
612 quint16 newValue = cmsEvalToneCurve16(*d->greenTRCReverse, Value[1] * scale);
613 Value[1] = newValue * invScale;
614 }
615 if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
616 quint16 newValue = cmsEvalToneCurve16(*d->blueTRCReverse, Value[2] * scale);
617 Value[2] = newValue * invScale;
618 }
619 } else {
620 if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
621 quint16 newValue = cmsEvalToneCurve16(*d->grayTRCReverse, Value[0] * scale);
622 Value[0] = newValue * invScale;
623 }
624 }
625}
626
628{
629 return d->name;
630}
631
633{
634 return d->productDescription;
635}
636
638{
639 if (d->uniqueId.isEmpty() && d->profile) {
640 QByteArray id(sizeof(cmsProfileID), 0);
641 cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
642
643 bool isNull = std::all_of(id.constBegin(),
644 id.constEnd(),
645 [](char c) {return c == 0;});
646 if (isNull) {
647 if (cmsMD5computeID(d->profile)) {
648 cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
649 isNull = false;
650 }
651 }
652
653 if (!isNull) {
654 d->uniqueId = id;
655 }
656 }
657
658 return d->uniqueId;
659}
660
662{
663 return (d->cicpValues);
664}
665
670
672{
673 if (!d->cicpValues) return TRC_UNSPECIFIED;
674 return TransferCharacteristics(d->cicpValues->TransferCharacteristics);
675}
676
678{
679 if (!*d->hasTRC) {
680 return false;
681 }
682
683 std::array<cmsFloat32Number, 2> calcValues{};
684
685 cmsToneCurve *mainCurve = [&]() {
686 if (d->hasColorants) {
687 return d->redTRC;
688 }
689 return d->grayTRC;
690 }();
691
692 cmsToneCurve *compareCurve = transferFunction(characteristics);
693
694 // Number of sweep samples across the curve
695 for (uint32_t i = 0; i < 32; i++) {
696 const float step = float(i) / 31.0f;
697 calcValues[0] = cmsEvalToneCurveFloat(mainCurve, step);
698 calcValues[1] = cmsEvalToneCurveFloat(compareCurve, step);
699 if (std::fabs(calcValues[0] - calcValues[1]) >= error) {
700 return false;
701 }
702 }
703
704 return true;
705}
706
709 cmsFloat32Number CurveVals[1024];
710 for (int i = 0; i < 1024; i++) {
711 float val = float(i)/1023.0;
713 CurveVals[i] = (removeSmpte2048Curve(val)/125.0);
714 }
715 return cmsBuildTabulatedToneCurveFloat(NULL, 1024, CurveVals);
716}
717
719{
720 cmsToneCurve *mainCurve;
721
722 // Values courtesy of Elle Stone
723 cmsFloat64Number srgb_parameters[5] =
724 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
725 cmsFloat64Number rec709_parameters[5] =
726 { 1.0 / 0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
727
728 // The following is basically a precise version of rec709.
729 cmsFloat64Number rec202012bit_parameters[5] =
730 { 1.0 / 0.45, 1.0 / 1.0993, 0.0993 / 1.0993, 1.0 / 4.5, 0.0812 };
731
732 cmsFloat64Number SMPTE_240M_parameters[5] =
733 { 1.0 / 0.45, 1.0 / 1.1115, 0.1115 / 1.1115, 1.0 / 4.0, 0.0913 };
734
735 cmsFloat64Number prophoto_parameters[5] =
736 { 1.8, 1.0, 0, 1.0 / 16, (16.0/512) };
737
738 cmsFloat64Number log_100[5] = {1.0, 10, 2.0, -2.0, 0.0};
739 cmsFloat64Number log_100_sqrt[5] = {1.0, 10, 2.5, -2.5, 0.0};
740
741 cmsFloat64Number labl_parameters[5] = {3.0, 0.862076, 0.137924, 0.110703, 0.080002};
742
743 switch (transferFunction) {
745 // Not possible in ICC due to lack of a*pow(bX+c,y) construct.
747 // This is not possible in ICC due to lack of a*pow(bX+c,y) construct.
748 qWarning() << "Neither IEC 61966 2-4 nor Bt. 1361 are supported, returning a rec 709 curve.";
749 Q_FALLTHROUGH();
753 mainCurve = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
754 break;
756 mainCurve = cmsBuildParametricToneCurve(NULL, 4, rec202012bit_parameters);
757 break;
759 mainCurve = cmsBuildGamma(NULL, 2.2);
760 break;
762 mainCurve = cmsBuildGamma(NULL, 2.8);
763 break;
764 case TRC_SMPTE_240M:
765 mainCurve = cmsBuildParametricToneCurve(NULL, 4, SMPTE_240M_parameters);
766 break;
768 mainCurve = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
769 break;
771 mainCurve = cmsBuildParametricToneCurve(NULL, 8, log_100);
772 break;
774 mainCurve = cmsBuildParametricToneCurve(NULL, 8, log_100_sqrt);
775 break;
776 case TRC_A98:
777 // gamma 563/256
778 mainCurve = cmsBuildGamma(NULL, 563.0 / 256.0);
779 break;
780 case TRC_PROPHOTO:
781 mainCurve = cmsBuildParametricToneCurve(NULL, 4, prophoto_parameters);
782 break;
783 case TRC_GAMMA_1_8:
784 mainCurve = cmsBuildGamma(NULL, 1.8);
785 break;
786 case TRC_GAMMA_2_4:
787 mainCurve = cmsBuildGamma(NULL, 2.4);
788 break;
789 case TRC_LAB_L:
790 mainCurve = cmsBuildParametricToneCurve(NULL, 4, labl_parameters);
791 break;
794 break;
796 // Requires an a*X^y construction, not possible.
798 // Hybrid log gamma.
799 qWarning() << "Cannot generate an icc profile with this transfer function, will generate a linear profile";
800 Q_FALLTHROUGH();
801 case TRC_LINEAR:
802 default:
803 mainCurve = cmsBuildGamma(NULL, 1.0);
804 break;
805 }
806
807 return mainCurve;
808}
#define dbgPigment
float value(const T *src, size_t ch)
ColorPrimaries
The colorPrimaries enum Enum of colorants, follows ITU H.273 for values 0 to 255, and has extra known...
@ PRIMARIES_UNSPECIFIED
TransferCharacteristics
The transferCharacteristics enum Enum of transfer characteristics, follows ITU H.273 for values 0 to ...
@ TRC_IEC_61966_2_4
@ TRC_ITU_R_BT_2020_2_10bit
@ TRC_LOGARITHMIC_100
@ TRC_ITU_R_BT_470_6_SYSTEM_M
@ TRC_ITU_R_BT_470_6_SYSTEM_B_G
@ TRC_ITU_R_BT_1361
@ TRC_ITU_R_BT_2100_0_HLG
@ TRC_ITU_R_BT_2100_0_PQ
@ TRC_ITU_R_BT_601_6
@ TRC_IEC_61966_2_1
@ TRC_ITU_R_BT_709_5
@ TRC_SMPTE_ST_428_1
@ TRC_LOGARITHMIC_100_sqrt10
@ TRC_ITU_R_BT_2020_2_12bit
ALWAYS_INLINE float removeSmpte2048Curve(float x, float refWhite=203.0) noexcept
KoColorimetryUtils::xyY fromCIExyY(const cmsCIExyY &xyY)
KoColorimetryUtils::XYZ fromCieXYZ(const cmsCIEXYZ &xyz)
cmsToneCurve * perceptualQuantizerDummy()
#define _BUFFER_SIZE_
KisLazyStorage< KisLazyValueWrapper< bool >, std::function< bool()> > LazyBool
KisLazyStorage< ReverseCurveWrapper, cmsToneCurve * > LazyReverseCurve
QVector< KoColorimetryUtils::XYZ > getColorantsXYZ() const override
virtual void DelinearizeFloatValue(QVector< double > &Value) const
static cmsToneCurve * transferFunction(TransferCharacteristics transferFunction)
TransferCharacteristics cicpTransfer() const override
KoColorimetryUtils::XYZ getWhitePointXYZ() const override
cmsProfileClassSignature deviceClass() const
ColorPrimaries cicpPrimaries() const override
virtual void DelinearizeFloatValueFast(QVector< double > &Value) const
std::optional< double > hdrReferenceWhite() const override
QByteArray getProfileUniqueId() const override
static IccColorProfile * createFromLcmsProfile(const cmsHPROFILE profile)
QVector< KoColorimetryUtils::xyY > getColorantsxyY() const override
bool isSuitableForWorkspace() const override
bool isSuitableForPrinting() const override
QString manufacturer() const override
QVector< double > getEstimatedTRC() const override
static QByteArray lcmsProfileToByteArray(const cmsHPROFILE profile)
bool compareTRC(TransferCharacteristics characteristics, float error) const override
cmsColorSpaceSignature colorSpaceSignature() const
virtual void LinearizeFloatValue(QVector< double > &Value) const
virtual void LinearizeFloatValueFast(QVector< double > &Value) const
KoColorimetryUtils::xyY getWhitePointxyY() const override
#define INTENT_PERCEPTUAL
Definition kis_global.h:103
#define INTENT_RELATIVE_COLORIMETRIC
Definition kis_global.h:104
#define INTENT_SATURATION
Definition kis_global.h:105
double toDouble(const QString &str, bool *ok=nullptr)