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