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
25#include <KisLazyStorage.h>
26#include <KisLazyValueWrapper.h>
27#include <lcms2.h>
28
29namespace {
30struct ReverseCurveWrapper
31{
32 ReverseCurveWrapper() : reverseCurve(0) {}
33
34 explicit ReverseCurveWrapper(cmsToneCurve *curve) {
35 reverseCurve = cmsReverseToneCurve(curve);
36 }
37
38 ~ReverseCurveWrapper() {
39 if (reverseCurve) {
40 cmsFreeToneCurve(reverseCurve);
41 }
42 }
43
44 operator cmsToneCurve*() const {
45 return reverseCurve;
46 }
47
48 operator cmsToneCurve*() {
49 return reverseCurve;
50 }
51
52 ReverseCurveWrapper(const ReverseCurveWrapper&rhs) = delete;
53 ReverseCurveWrapper& operator=(const ReverseCurveWrapper&rhs) = delete;
54
55 ReverseCurveWrapper(ReverseCurveWrapper&&rhs) = default;
56 ReverseCurveWrapper& operator=(ReverseCurveWrapper&&rhs) = default;
57
58 cmsToneCurve *reverseCurve {0};
59};
60} // namespace
61
63{
64public:
65 cmsHPROFILE profile;
66 cmsColorSpaceSignature colorSpaceSignature;
67 cmsProfileClassSignature deviceClass;
69 QString manufacturer;
70 QString copyright;
71 QString name;
72 float version;
74 bool valid {false};
75 bool suitableForOutput {false};
76 bool suitableForInput {false};
78
79 using LazyBool = KisLazyStorage<KisLazyValueWrapper<bool>, std::function<bool()>>;
80
81 LazyBool hasTRC = LazyBool(LazyBool::init_value_tag{}, {});
82 LazyBool isLinear = LazyBool(LazyBool::init_value_tag{}, {});
83
85 cmsCIEXYZ mediaWhitePoint;
86 cmsCIExyY whitePoint;
87 cmsCIEXYZTRIPLE colorants;
88 cmsToneCurve *redTRC {0};
89 cmsToneCurve *greenTRC {0};
90 cmsToneCurve *blueTRC {0};
91 cmsToneCurve *grayTRC {0};
92
94
95 LazyReverseCurve redTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
96 LazyReverseCurve greenTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
97 LazyReverseCurve blueTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
98 LazyReverseCurve grayTRCReverse = LazyReverseCurve(LazyReverseCurve::init_value_tag{}, {});
99
100 cmsUInt32Number defaultIntent;
106
107 cmsVideoSignalType *cicpValues {nullptr};
108
109 QByteArray uniqueId;
110};
111
117
119 : d(new Private())
120{
121 d->data = data;
122 d->profile = 0;
123 init();
124}
125
126QByteArray LcmsColorProfileContainer::lcmsProfileToByteArray(const cmsHPROFILE profile)
127{
128 cmsUInt32Number bytesNeeded = 0;
129 // Make a raw data image ready for saving
130 cmsSaveProfileToMem(profile, 0, &bytesNeeded); // calc size
131 QByteArray rawData;
132 rawData.resize(bytesNeeded);
133 if (rawData.size() >= (int)bytesNeeded) {
134 cmsSaveProfileToMem(profile, rawData.data(), &bytesNeeded); // fill buffer
135 } else {
136 qWarning() << "Couldn't resize the profile buffer, system is probably running out of memory.";
137 rawData.resize(0);
138 }
139 return rawData;
140}
141
143{
144 IccColorProfile *iccprofile = new IccColorProfile(lcmsProfileToByteArray(profile));
145 cmsCloseProfile(profile);
146 return iccprofile;
147}
148
150{
151 cmsCloseProfile(d->profile);
152 delete d;
153}
154
155#define _BUFFER_SIZE_ 1000
156
158{
159 if (d->profile) {
160 cmsCloseProfile(d->profile);
161 }
162
163 d->profile = cmsOpenProfileFromMem((void *)d->data->rawData().constData(), d->data->rawData().size());
164
165
166#ifndef NDEBUG
167 if (d->data->rawData().size() == 4096) {
168 qWarning() << "Profile has a size of 4096, which is suspicious and indicates a possible misuse of QIODevice::read(int), check your code.";
169 }
170#endif
171
172 if (d->profile) {
173 wchar_t buffer[_BUFFER_SIZE_];
174 d->colorSpaceSignature = cmsGetColorSpace(d->profile);
175 d->deviceClass = cmsGetDeviceClass(d->profile);
176 cmsGetProfileInfo(d->profile, cmsInfoDescription, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
177 d->name = QString::fromWCharArray(buffer);
178
179 //apparently this should give us a localised string??? Not sure about this.
180 cmsGetProfileInfo(d->profile, cmsInfoModel, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
181 d->productDescription = QString::fromWCharArray(buffer);
182
183 cmsGetProfileInfo(d->profile, cmsInfoManufacturer, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
184 d->manufacturer = QString::fromWCharArray(buffer);
185
186 cmsGetProfileInfo(d->profile, cmsInfoCopyright, cmsNoLanguage, cmsNoCountry, buffer, _BUFFER_SIZE_);
187 d->copyright = QString::fromWCharArray(buffer);
188
189 cmsProfileClassSignature profile_class;
190 profile_class = cmsGetDeviceClass(d->profile);
191 d->valid = ( profile_class != cmsSigNamedColorClass
192 && profile_class != cmsSigLinkClass);
193
194 //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
195 //present. This is necessary for profiles following the v4 spec.
196 cmsCIEXYZ baseMediaWhitePoint;//dummy to hold copy of mediawhitepoint if this is modified by chromatic adaption.
197 cmsCIEXYZ *mediaWhitePointPtr;
198 bool whiteComp[3];
199 bool whiteIsD50;
200 // Possible bug in profiles: there are in fact some that says they contain that tag
201 // but in fact the pointer is null.
202 // Let's not crash on it anyway, and assume there is no white point instead.
203 // BUG:423685
204 if (cmsIsTag(d->profile, cmsSigMediaWhitePointTag)
205 && (mediaWhitePointPtr = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigMediaWhitePointTag))) {
206
207 d->mediaWhitePoint = *(mediaWhitePointPtr);
208 baseMediaWhitePoint = d->mediaWhitePoint;
209
210 whiteComp[0] = std::fabs(baseMediaWhitePoint.X - cmsD50_XYZ()->X) < 0.00001;
211 whiteComp[1] = std::fabs(baseMediaWhitePoint.Y - cmsD50_XYZ()->Y) < 0.00001;
212 whiteComp[2] = std::fabs(baseMediaWhitePoint.Z - cmsD50_XYZ()->Z) < 0.00001;
213 whiteIsD50 = std::all_of(std::begin(whiteComp), std::end(whiteComp), [](bool b) {return b;});
214
215 cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
216 cmsCIEXYZ *CAM1;
217 if (cmsIsTag(d->profile, cmsSigChromaticAdaptationTag)
218 && (CAM1 = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigChromaticAdaptationTag))
219 && whiteIsD50) {
220 //the chromatic adaption tag represent a matrix from the actual white point of the profile to D50.
221
222 //We first put all our data into structures we can manipulate.
223 double d3dummy [3] = {d->mediaWhitePoint.X, d->mediaWhitePoint.Y, d->mediaWhitePoint.Z};
224 QGenericMatrix<1, 3, double> whitePointMatrix(d3dummy);
225 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);
226 //we then abuse QTransform's invert function because it probably does matrix inversion 20 times better than I can program.
227 //if the matrix is uninvertable, invertedDummy will be an identity matrix, which for us means that it won't give any noticeable
228 //effect when we start multiplying.
229 QTransform invertedDummy = invertDummy.inverted();
230 //we then put the QTransform into a generic 3x3 matrix.
231 double d9dummy [9] = {invertedDummy.m11(), invertedDummy.m12(), invertedDummy.m13(),
232 invertedDummy.m21(), invertedDummy.m22(), invertedDummy.m23(),
233 invertedDummy.m31(), invertedDummy.m32(), invertedDummy.m33()
234 };
235 QGenericMatrix<3, 3, double> chromaticAdaptionMatrix(d9dummy);
236 //multiplying our inverted adaption matrix with the whitepoint gives us the right whitepoint.
237 QGenericMatrix<1, 3, double> result = chromaticAdaptionMatrix * whitePointMatrix;
238 //and then we pour the matrix into the whitepoint variable. Generic matrix does row/column for indices even though it
239 //uses column/row for initialising.
240 d->mediaWhitePoint.X = result(0, 0);
241 d->mediaWhitePoint.Y = result(1, 0);
242 d->mediaWhitePoint.Z = result(2, 0);
243 cmsXYZ2xyY(&d->whitePoint, &d->mediaWhitePoint);
244 }
245 }
246 //This is for RGB profiles, but it only works for matrix profiles. Need to design it to work with non-matrix profiles.
247 cmsCIEXYZ *tempColorantsRed, *tempColorantsGreen, *tempColorantsBlue;
248 // Note: don't assume that cmsIsTag is enough to check for errors; check the pointers, too
249 // BUG:423685
250 if (cmsIsTag(d->profile, cmsSigRedColorantTag) && cmsIsTag(d->profile, cmsSigRedColorantTag) && cmsIsTag(d->profile, cmsSigRedColorantTag)
251 && (tempColorantsRed = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigRedColorantTag))
252 && (tempColorantsGreen = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigGreenColorantTag))
253 && (tempColorantsBlue = (cmsCIEXYZ *)cmsReadTag(d->profile, cmsSigBlueColorantTag))) {
254 cmsCIEXYZTRIPLE tempColorants;
255 tempColorants.Red = *tempColorantsRed;
256 tempColorants.Green = *tempColorantsGreen;
257 tempColorants.Blue = *tempColorantsBlue;
258 //convert to d65, this is useless.
259 cmsAdaptToIlluminant(&d->colorants.Red, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Red);
260 cmsAdaptToIlluminant(&d->colorants.Green, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Green);
261 cmsAdaptToIlluminant(&d->colorants.Blue, cmsD50_XYZ(), &d->mediaWhitePoint, &tempColorants.Blue);
262 //d->colorants = tempColorants;
263 d->hasColorants = true;
264 } else {
265 //qDebug()<<d->name<<": has no colorants";
266 d->hasColorants = false;
267 }
268 //retrieve TRC.
269 if (cmsIsTag(d->profile, cmsSigRedTRCTag) && cmsIsTag(d->profile, cmsSigBlueTRCTag) && cmsIsTag(d->profile, cmsSigGreenTRCTag)) {
270
271 d->redTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigRedTRCTag));
272 d->greenTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGreenTRCTag));
273 d->blueTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigBlueTRCTag));
277
278 d->hasTRC = Private::LazyBool([d = d] () {
279 return d->redTRC && d->greenTRC && d->blueTRC && *d->redTRCReverse && *d->greenTRCReverse && *d->blueTRCReverse;
280 });
281
282 d->isLinear = Private::LazyBool([d = d] () {
283 return *d->hasTRC
284 && cmsIsToneCurveLinear(d->redTRC)
285 && cmsIsToneCurveLinear(d->greenTRC)
286 && cmsIsToneCurveLinear(d->blueTRC);
287 });
288
289 } else if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
290 d->grayTRC = ((cmsToneCurve *)cmsReadTag (d->profile, cmsSigGrayTRCTag));
292
293 d->hasTRC = Private::LazyBool([d = d] () {
294 return d->grayTRC && *d->grayTRCReverse;
295 });
296
297 d->isLinear = Private::LazyBool([d = d] () {
298 return *d->hasTRC && cmsIsToneCurveLinear(d->grayTRC);
299 });
300 } else {
301 d->hasTRC = Private::LazyBool(Private::LazyBool::init_value_tag{}, {});
302 }
303
304 if (cmsIsTag(d->profile, cmsSigcicpTag)) {
305 d->cicpValues = ((cmsVideoSignalType *) cmsReadTag (d->profile, cmsSigcicpTag));
306 }
307
308 // Check if the profile can convert (something->this)
309 d->suitableForOutput = cmsIsIntentSupported(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT);
310 d->suitableForInput = cmsIsIntentSupported(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT);
311
312 d->version = cmsGetProfileVersion(d->profile);
313 d->defaultIntent = cmsGetHeaderRenderingIntent(d->profile);
314 d->isMatrixShaper = cmsIsMatrixShaper(d->profile);
315 d->isPerceptualCLUT = cmsIsCLUT(d->profile, INTENT_PERCEPTUAL, LCMS_USED_AS_INPUT);
316 d->isSaturationCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
317 d->isAbsoluteCLUT = cmsIsCLUT(d->profile, INTENT_SATURATION, LCMS_USED_AS_INPUT);
318 d->isRelativeCLUT = cmsIsCLUT(d->profile, INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_INPUT);
319
320 if (cmsIsTag(d->profile, cmsSigMetaTag)) {
321 dbgPigment << "icc profile has dictionary entries";
322
323 cmsHANDLE dictionary = (cmsHANDLE) cmsReadTag(d->profile, cmsSigMetaTag);
324 const cmsDICTentry *entry = cmsDictGetEntryList(dictionary);
325 while (entry) {
326 dbgPigment << QString::fromWCharArray(entry->Name, -1) << QString::fromWCharArray(entry->Value, -1);;
327 dbgPigment << entry->DisplayName << entry->DisplayValue;
328 entry = cmsDictNextEntry(entry);
329 }
330 }
331
332 return true;
333 }
334
335 return false;
336}
337
339{
340 return d->profile;
341}
342
344{
345 return d->colorSpaceSignature;
346}
347
348cmsProfileClassSignature LcmsColorProfileContainer::deviceClass() const
349{
350 return d->deviceClass;
351}
352
354{
355 return d->manufacturer;
356}
357
359{
360 return d->copyright;
361}
362
364{
365 return d->valid;
366}
367
369{
370 return d->version;
371}
372
385
387{
388 return deviceClass() == cmsSigOutputClass;
389}
390
392{
393 return deviceClass() == cmsSigDisplayClass;
394}
395
405{
406 return d->isAbsoluteCLUT;//LCMS2 doesn't convert matrix shapers via absolute intent, because of V4 workflow.
407}
409{
411 return true;
412 }
413 return false;
414}
416{
417 return d->hasColorants;
418}
420{
421 return *d->hasTRC;
422}
424{
425 return *d->isLinear;
426}
427
428KoColorimetryUtils::XYZ fromCieXYZ(const cmsCIEXYZ &xyz) {
429 return KoColorimetryUtils::XYZ{xyz.X, xyz.Y, xyz.Z};
430}
431
432KoColorimetryUtils::xyY fromCIExyY(const cmsCIExyY &xyY) {
433 return KoColorimetryUtils::xyY{xyY.x, xyY.y, xyY.Y};
434}
435
436QVector <KoColorimetryUtils::XYZ> LcmsColorProfileContainer::getColorantsXYZ() const
437{
438 QVector <KoColorimetryUtils::XYZ> colorants(9);
439
440 colorants.append(fromCieXYZ(d->colorants.Red));
441 colorants.append(fromCieXYZ(d->colorants.Green));
442 colorants.append(fromCieXYZ(d->colorants.Blue));
443 return colorants;
444}
445
447{
448 cmsCIEXYZ temp1;
449 cmsCIExyY temp2;
451
452 temp1.X = d->colorants.Red.X;
453 temp1.Y = d->colorants.Red.Y;
454 temp1.Z = d->colorants.Red.Z;
455 cmsXYZ2xyY(&temp2, &temp1);
456 colorants.append(fromCIExyY(temp2));
457
458 temp1.X = d->colorants.Green.X;
459 temp1.Y = d->colorants.Green.Y;
460 temp1.Z = d->colorants.Green.Z;
461 cmsXYZ2xyY(&temp2, &temp1);
462 colorants.append(fromCIExyY(temp2));
463
464 temp1.X = d->colorants.Blue.X;
465 temp1.Y = d->colorants.Blue.Y;
466 temp1.Z = d->colorants.Blue.Z;
467 cmsXYZ2xyY(&temp2, &temp1);
468 colorants.append(fromCIExyY(temp2));
469
470 return colorants;
471}
472
477
482
484{
485 QVector <double> TRCtriplet(3);
486 if (d->hasColorants && hasTRC()) {
487 if (cmsIsToneCurveLinear(d->redTRC)) {
488 TRCtriplet[0] = 1.0;
489 } else {
490 TRCtriplet[0] = cmsEstimateGamma(d->redTRC, 0.01);
491 }
492 if (cmsIsToneCurveLinear(d->greenTRC)) {
493 TRCtriplet[1] = 1.0;
494 } else {
495 TRCtriplet[1] = cmsEstimateGamma(d->greenTRC, 0.01);
496 }
497 if (cmsIsToneCurveLinear(d->blueTRC)) {
498 TRCtriplet[2] = 1.0;
499 } else {
500 TRCtriplet[2] = cmsEstimateGamma(d->blueTRC, 0.01);
501 }
502
503 } else {
504 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
505 if (cmsIsToneCurveLinear(d->grayTRC)) {
506 TRCtriplet.fill(1.0);
507 } else {
508 TRCtriplet.fill(cmsEstimateGamma(d->grayTRC, 0.01));
509 }
510 } else {
511 TRCtriplet.fill(1.0);
512 }
513 }
514 return TRCtriplet;
515}
516
518{
519 if (d->hasColorants && hasTRC()) {
520 if (!cmsIsToneCurveLinear(d->redTRC)) {
521 Value[0] = cmsEvalToneCurveFloat(d->redTRC, Value[0]);
522 }
523 if (!cmsIsToneCurveLinear(d->greenTRC)) {
524 Value[1] = cmsEvalToneCurveFloat(d->greenTRC, Value[1]);
525 }
526 if (!cmsIsToneCurveLinear(d->blueTRC)) {
527 Value[2] = cmsEvalToneCurveFloat(d->blueTRC, Value[2]);
528 }
529
530 } else {
531 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
532 Value[0] = cmsEvalToneCurveFloat(d->grayTRC, Value[0]);
533 }
534 }
535}
536
538{
539 if (d->hasColorants && hasTRC()) {
540 if (!cmsIsToneCurveLinear(d->redTRC)) {
541 Value[0] = cmsEvalToneCurveFloat(*d->redTRCReverse, Value[0]);
542 }
543 if (!cmsIsToneCurveLinear(d->greenTRC)) {
544 Value[1] = cmsEvalToneCurveFloat(*d->greenTRCReverse, Value[1]);
545 }
546 if (!cmsIsToneCurveLinear(d->blueTRC)) {
547 Value[2] = cmsEvalToneCurveFloat(*d->blueTRCReverse, Value[2]);
548 }
549
550 } else {
551 if (cmsIsTag(d->profile, cmsSigGrayTRCTag)) {
552 Value[0] = cmsEvalToneCurveFloat(*d->grayTRCReverse, Value[0]);
553 }
554 }
555}
556
558{
559 const qreal scale = 65535.0;
560 const qreal invScale = 1.0 / scale;
561
562 if (d->hasColorants && hasTRC()) {
563 //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
564
565 if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
566 quint16 newValue = cmsEvalToneCurve16(d->redTRC, Value[0] * scale);
567 Value[0] = newValue * invScale;
568 }
569 if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
570 quint16 newValue = cmsEvalToneCurve16(d->greenTRC, Value[1] * scale);
571 Value[1] = newValue * invScale;
572 }
573 if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
574 quint16 newValue = cmsEvalToneCurve16(d->blueTRC, Value[2] * scale);
575 Value[2] = newValue * invScale;
576 }
577 } else {
578 if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
579 quint16 newValue = cmsEvalToneCurve16(d->grayTRC, Value[0] * scale);
580 Value[0] = newValue * invScale;
581 }
582 }
583}
585{
586 const qreal scale = 65535.0;
587 const qreal invScale = 1.0 / scale;
588
589 if (d->hasColorants && hasTRC()) {
590 //we can only reliably delinearise in the 0-1.0 range, outside of that leave the value alone.
591
592 if (!cmsIsToneCurveLinear(d->redTRC) && Value[0]<1.0) {
593 quint16 newValue = cmsEvalToneCurve16(*d->redTRCReverse, Value[0] * scale);
594 Value[0] = newValue * invScale;
595 }
596 if (!cmsIsToneCurveLinear(d->greenTRC) && Value[1]<1.0) {
597 quint16 newValue = cmsEvalToneCurve16(*d->greenTRCReverse, Value[1] * scale);
598 Value[1] = newValue * invScale;
599 }
600 if (!cmsIsToneCurveLinear(d->blueTRC) && Value[2]<1.0) {
601 quint16 newValue = cmsEvalToneCurve16(*d->blueTRCReverse, Value[2] * scale);
602 Value[2] = newValue * invScale;
603 }
604 } else {
605 if (cmsIsTag(d->profile, cmsSigGrayTRCTag) && Value[0]<1.0) {
606 quint16 newValue = cmsEvalToneCurve16(*d->grayTRCReverse, Value[0] * scale);
607 Value[0] = newValue * invScale;
608 }
609 }
610}
611
613{
614 return d->name;
615}
616
618{
619 return d->productDescription;
620}
621
623{
624 if (d->uniqueId.isEmpty() && d->profile) {
625 QByteArray id(sizeof(cmsProfileID), 0);
626 cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
627
628 bool isNull = std::all_of(id.constBegin(),
629 id.constEnd(),
630 [](char c) {return c == 0;});
631 if (isNull) {
632 if (cmsMD5computeID(d->profile)) {
633 cmsGetHeaderProfileID(d->profile, (quint8*)id.data());
634 isNull = false;
635 }
636 }
637
638 if (!isNull) {
639 d->uniqueId = id;
640 }
641 }
642
643 return d->uniqueId;
644}
645
647{
648 return (d->cicpValues);
649}
650
655
657{
658 if (!d->cicpValues) return TRC_UNSPECIFIED;
659 return TransferCharacteristics(d->cicpValues->TransferCharacteristics);
660}
661
663{
664 if (!*d->hasTRC) {
665 return false;
666 }
667
668 std::array<cmsFloat32Number, 2> calcValues{};
669
670 cmsToneCurve *mainCurve = [&]() {
671 if (d->hasColorants) {
672 return d->redTRC;
673 }
674 return d->grayTRC;
675 }();
676
677 cmsToneCurve *compareCurve = transferFunction(characteristics);
678
679 // Number of sweep samples across the curve
680 for (uint32_t i = 0; i < 32; i++) {
681 const float step = float(i) / 31.0f;
682 calcValues[0] = cmsEvalToneCurveFloat(mainCurve, step);
683 calcValues[1] = cmsEvalToneCurveFloat(compareCurve, step);
684 if (std::fabs(calcValues[0] - calcValues[1]) >= error) {
685 return false;
686 }
687 }
688
689 return true;
690}
691
694 cmsFloat32Number CurveVals[1024];
695 for (int i = 0; i < 1024; i++) {
696 float val = float(i)/1023.0;
698 CurveVals[i] = (removeSmpte2048Curve(val)/125.0);
699 }
700 return cmsBuildTabulatedToneCurveFloat(NULL, 1024, CurveVals);
701}
702
704{
705 cmsToneCurve *mainCurve;
706
707 // Values courtesy of Elle Stone
708 cmsFloat64Number srgb_parameters[5] =
709 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
710 cmsFloat64Number rec709_parameters[5] =
711 { 1.0 / 0.45, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
712
713 // The following is basically a precise version of rec709.
714 cmsFloat64Number rec202012bit_parameters[5] =
715 { 1.0 / 0.45, 1.0 / 1.0993, 0.0993 / 1.0993, 1.0 / 4.5, 0.0812 };
716
717 cmsFloat64Number SMPTE_240M_parameters[5] =
718 { 1.0 / 0.45, 1.0 / 1.1115, 0.1115 / 1.1115, 1.0 / 4.0, 0.0913 };
719
720 cmsFloat64Number prophoto_parameters[5] =
721 { 1.8, 1.0, 0, 1.0 / 16, (16.0/512) };
722
723 cmsFloat64Number log_100[5] = {1.0, 10, 2.0, -2.0, 0.0};
724 cmsFloat64Number log_100_sqrt[5] = {1.0, 10, 2.5, -2.5, 0.0};
725
726 cmsFloat64Number labl_parameters[5] = {3.0, 0.862076, 0.137924, 0.110703, 0.080002};
727
728 switch (transferFunction) {
730 // Not possible in ICC due to lack of a*pow(bX+c,y) construct.
732 // This is not possible in ICC due to lack of a*pow(bX+c,y) construct.
733 qWarning() << "Neither IEC 61966 2-4 nor Bt. 1361 are supported, returning a rec 709 curve.";
734 Q_FALLTHROUGH();
738 mainCurve = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
739 break;
741 mainCurve = cmsBuildParametricToneCurve(NULL, 4, rec202012bit_parameters);
742 break;
744 mainCurve = cmsBuildGamma(NULL, 2.2);
745 break;
747 mainCurve = cmsBuildGamma(NULL, 2.8);
748 break;
749 case TRC_SMPTE_240M:
750 mainCurve = cmsBuildParametricToneCurve(NULL, 4, SMPTE_240M_parameters);
751 break;
753 mainCurve = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
754 break;
756 mainCurve = cmsBuildParametricToneCurve(NULL, 8, log_100);
757 break;
759 mainCurve = cmsBuildParametricToneCurve(NULL, 8, log_100_sqrt);
760 break;
761 case TRC_A98:
762 // gamma 563/256
763 mainCurve = cmsBuildGamma(NULL, 563.0 / 256.0);
764 break;
765 case TRC_PROPHOTO:
766 mainCurve = cmsBuildParametricToneCurve(NULL, 4, prophoto_parameters);
767 break;
768 case TRC_GAMMA_1_8:
769 mainCurve = cmsBuildGamma(NULL, 1.8);
770 break;
771 case TRC_GAMMA_2_4:
772 mainCurve = cmsBuildGamma(NULL, 2.4);
773 break;
774 case TRC_LAB_L:
775 mainCurve = cmsBuildParametricToneCurve(NULL, 4, labl_parameters);
776 break;
779 break;
781 // Requires an a*X^y construction, not possible.
783 // Hybrid log gamma.
784 qWarning() << "Cannot generate an icc profile with this transfer function, will generate a linear profile";
785 Q_FALLTHROUGH();
786 case TRC_LINEAR:
787 default:
788 mainCurve = cmsBuildGamma(NULL, 1.0);
789 break;
790 }
791
792 return mainCurve;
793}
#define dbgPigment
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) 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
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