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