Krita Source Code Documentation
Loading...
Searching...
No Matches
IccColorProfile.cpp
Go to the documentation of this file.
1/*
2* SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net>
3* SPDX-FileCopyrightText: 2021 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
4*
5* SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "IccColorProfile.h"
9
10#include <cmath>
11#include <cstdint>
12#include <limits>
13
14#include <lcms2.h>
15
16#include <QDebug>
17#include <QFile>
18#include <QSharedPointer>
19
20#include <KoColorConversions.h>
21#include <kis_assert.h>
22
24
25#include <KisLazyStorage.h>
26#include <KisLazyValueWrapper.h>
27
28
32
38 : d(new Private)
39{
40 d->rawData = rawData;
41}
42
46
48{
49 return d->rawData;
50}
51
53{
54 d->rawData = rawData;
55}
56
60
64
70
72
73 struct Shared {
74 QScopedPointer<IccColorProfile::Data> data;
75 QScopedPointer<LcmsColorProfileContainer> lcmsProfile;
76 LazyProfileInfo profileInfo = LazyProfileInfo(LazyProfileInfo::init_value_tag{}, {});
77
79 : data(new IccColorProfile::Data())
80 {
81 }
82 };
83
85 : shared(QSharedPointer<Shared>::create())
86 {
87 }
89
91};
92
97
98IccColorProfile::IccColorProfile(const QByteArray &rawData)
99 : KoColorProfile(QString()), d(new Private)
100{
102 init();
103}
104
106 const ColorPrimaries colorPrimariesType,
107 const TransferCharacteristics transferFunction)
108: KoColorProfile(QString()), d(new Private)
109{
111
113 (!colorants.isEmpty() || colorPrimariesType != PRIMARIES_UNSPECIFIED)
114 && transferFunction != TRC_UNSPECIFIED);
115
116 cmsCIExyY whitePoint;
117
118 QVector<double> modifiedColorants = colorants;
119
120 KoColorProfile::colorantsForType(colorPrimariesType, modifiedColorants);
121
122 if (modifiedColorants.size()>=2) {
123 whitePoint.x = modifiedColorants[0];
124 whitePoint.y = modifiedColorants[1];
125 whitePoint.Y = 1.0;
126 }
127
128 cmsToneCurve *mainCurve = LcmsColorProfileContainer::transferFunction(transferFunction);
129
130 cmsCIExyYTRIPLE primaries;
131
132 if (modifiedColorants.size()>2 && modifiedColorants.size() <= 8) {
133 primaries = {{modifiedColorants[2], modifiedColorants[3], 1.0},
134 {modifiedColorants[4], modifiedColorants[5], 1.0},
135 {modifiedColorants[6], modifiedColorants[7], 1.0}};
136 }
137
138 cmsHPROFILE iccProfile = nullptr;
139
140 if (colorants.size() == 2) {
141 iccProfile = cmsCreateGrayProfile(&whitePoint, mainCurve);
142 } else /*if (colorants.size()>2 || colorPrimariesType != 2)*/ {
143 // generate rgb profile.
144 cmsToneCurve *curve[3];
145 curve[0] = curve[1] = curve[2] = mainCurve;
146 iccProfile = cmsCreateRGBProfile(&whitePoint, &primaries, curve);
147 }
148
149 if (!iccProfile) {
150 qWarning() << "WARNING: LCMS failed to create a profile for the requested parameters";
151 qWarning().nospace() << " transfer function: " << getTransferCharacteristicName(transferFunction) << " (" << transferFunction << ")";
152 qWarning().nospace() << " named primaries:" << getColorPrimariesName(colorPrimariesType) << " (" << colorPrimariesType << ")";
153 qWarning() << " requested colorants:" << colorants;
154 // leave the profile in invalid state and return
155 return;
156 }
157
159 name.append("Krita");
160 name.append(KoColorProfile::getColorPrimariesName(colorPrimariesType));
161 name.append(KoColorProfile::getTransferCharacteristicName(transferFunction));
162
163 cmsCIEXYZ media_blackpoint = {0.0, 0.0, 0.0};
164 cmsWriteTag (iccProfile, cmsSigMediaBlackPointTag, &media_blackpoint);
165
166 //set the color profile info on the iccProfile;
167 cmsMLU *mlu;
168 mlu = cmsMLUalloc (NULL, 1);
169 cmsMLUsetASCII (mlu, "en", "US", name.join(" ").toLatin1());
170 cmsWriteTag (iccProfile, cmsSigProfileDescriptionTag, mlu);
171 cmsMLUfree (mlu);
172 mlu = cmsMLUalloc (NULL, 1);
173 cmsMLUsetASCII (mlu, "en", "US", QString("Profile generated by Krita, Public domain.").toLatin1());
174 cmsWriteTag(iccProfile, cmsSigCopyrightTag, mlu);
175 cmsMLUfree (mlu);
176
177 setCharacteristics(colorPrimariesType, transferFunction);
178
180 cmsCloseProfile(iccProfile);
181 setFileName(name.join(" ").split(" ").join("-")+".icc");
182 init();
183}
184
186 : KoColorProfile(rhs)
187 , d(new Private(*rhs.d))
188{
189 Q_ASSERT(d->shared);
190}
191
193{
194 Q_ASSERT(d->shared);
195}
196
198{
199 return new IccColorProfile(*this);
200}
201
202QByteArray IccColorProfile::rawData() const
203{
204 return d->shared->data->rawData();
205}
206
207void IccColorProfile::setRawData(const QByteArray &rawData)
208{
209 d->shared->data->setRawData(rawData);
210}
211
213{
214 if (d->shared->lcmsProfile) {
215 return d->shared->lcmsProfile->valid();
216 }
217 return false;
218}
220{
221 if (d->shared->lcmsProfile) {
222 return d->shared->lcmsProfile->version();
223 }
224 return 0.0;
225}
226
228{
229 QString model;
230
231 switch (d->shared->lcmsProfile->colorSpaceSignature()) {
232 case cmsSigRgbData:
233 model = "RGBA";
234 break;
235 case cmsSigLabData:
236 model = "LABA";
237 break;
238 case cmsSigCmykData:
239 model = "CMYKA";
240 break;
241 case cmsSigGrayData:
242 model = "GRAYA";
243 break;
244 case cmsSigXYZData:
245 model = "XYZA";
246 break;
247 case cmsSigYCbCrData:
248 model = "YCbCrA";
249 break;
250 default:
251 // In theory we should be able to interpret the colorspace signature as a 4 char array...
252 model = QString();
253 }
254
255 return model;
256}
258{
259 if (d->shared->lcmsProfile) {
260 return d->shared->lcmsProfile->isSuitableForOutput() && d->shared->profileInfo->value.canCreateCyclicTransform;
261 }
262 return false;
263}
265{
266 if (d->shared->lcmsProfile) {
267 return d->shared->lcmsProfile->isSuitableForInput() && d->shared->profileInfo->value.canCreateCyclicTransform;
268 }
269 return false;
270}
272{
273 if (d->shared->lcmsProfile) {
274 return d->shared->lcmsProfile->isSuitableForWorkspace() && d->shared->profileInfo->value.canCreateCyclicTransform;
275 }
276 return false;
277}
278
279
281{
282 if (d->shared->lcmsProfile) {
283 return d->shared->lcmsProfile->isSuitableForPrinting();
284 }
285 return false;
286}
287
289{
290 if (d->shared->lcmsProfile) {
291 return d->shared->lcmsProfile->isSuitableForDisplay();
292 }
293 return false;
294}
295
297{
298 if (d->shared->lcmsProfile) {
299 return d->shared->lcmsProfile->supportsPerceptual();
300 }
301 return false;
302}
304{
305 if (d->shared->lcmsProfile) {
306 return d->shared->lcmsProfile->supportsSaturation();
307 }
308 return false;
309}
311{
312 if (d->shared->lcmsProfile) {
313 return d->shared->lcmsProfile->supportsAbsolute();
314 }
315 return false;
316}
318{
319 if (d->shared->lcmsProfile) {
320 return d->shared->lcmsProfile->supportsRelative();
321 }
322 return false;
323}
325{
326 if (d->shared->lcmsProfile) {
327 return d->shared->lcmsProfile->hasColorants();
328 }
329 return false;
330}
332{
333 if (d->shared->lcmsProfile)
334 return d->shared->lcmsProfile->hasTRC();
335 return false;
336}
338{
339 if (d->shared->lcmsProfile)
340 return d->shared->lcmsProfile->isLinear();
341 return false;
342}
343QVector <qreal> IccColorProfile::getColorantsXYZ() const
344{
345 if (d->shared->lcmsProfile) {
346 return d->shared->lcmsProfile->getColorantsXYZ();
347 }
348 return QVector<qreal>(9);
349}
350QVector <qreal> IccColorProfile::getColorantsxyY() const
351{
352 if (d->shared->lcmsProfile) {
353 return d->shared->lcmsProfile->getColorantsxyY();
354 }
355 return QVector<qreal>(9);
356}
358{
359 QVector <qreal> d50Dummy(3);
360 d50Dummy << 0.9642 << 1.0000 << 0.8249;
361 if (d->shared->lcmsProfile) {
362 return d->shared->lcmsProfile->getWhitePointXYZ();
363 }
364 return d50Dummy;
365}
367{
368 QVector <qreal> d50Dummy(3);
369 d50Dummy << 0.34773 << 0.35952 << 1.0;
370 if (d->shared->lcmsProfile) {
371 return d->shared->lcmsProfile->getWhitePointxyY();
372 }
373 return d50Dummy;
374}
375QVector <qreal> IccColorProfile::getEstimatedTRC() const
376{
377 QVector <qreal> dummy(3);
378 dummy.fill(2.2);//estimated sRGB trc.
379 if (d->shared->lcmsProfile) {
380 return d->shared->lcmsProfile->getEstimatedTRC();
381 }
382 return dummy;
383}
384
385bool IccColorProfile::compareTRC(TransferCharacteristics characteristics, float error) const
386{
387 if (d->shared->lcmsProfile) {
388 return d->shared->lcmsProfile->compareTRC(characteristics, error);
389 }
390 return false;
391}
392
393void IccColorProfile::linearizeFloatValue(QVector <qreal> & Value) const
394{
395 if (d->shared->lcmsProfile)
396 d->shared->lcmsProfile->LinearizeFloatValue(Value);
397}
399{
400 if (d->shared->lcmsProfile)
401 d->shared->lcmsProfile->DelinearizeFloatValue(Value);
402}
404{
405 if (d->shared->lcmsProfile)
406 d->shared->lcmsProfile->LinearizeFloatValueFast(Value);
407}
409{
410 if (d->shared->lcmsProfile)
411 d->shared->lcmsProfile->DelinearizeFloatValueFast(Value);
412}
413
415{
416 QByteArray dummy;
417 if (d->shared->lcmsProfile) {
418 dummy = d->shared->lcmsProfile->getProfileUniqueId();
419 }
420 return dummy;
421}
422
424{
425 QFile file(fileName());
426 file.open(QIODevice::ReadOnly);
427 QByteArray rawData = file.readAll();
429 file.close();
430 if (init()) {
431 return true;
432 }
433 qWarning() << "Failed to load profile from " << fileName();
434 return false;
435}
436
438{
439 return false;
440}
441
443{
444 if (!d->shared->lcmsProfile) {
445 d->shared->lcmsProfile.reset(new LcmsColorProfileContainer(d->shared->data.data()));
446 }
447 if (d->shared->lcmsProfile->init()) {
448 setName(d->shared->lcmsProfile->name());
449 setInfo(d->shared->lcmsProfile->info());
450 setManufacturer(d->shared->lcmsProfile->manufacturer());
451 setCopyright(d->shared->lcmsProfile->copyright());
452 if (d->shared->lcmsProfile->valid()) {
453 d->shared->profileInfo = Private::LazyProfileInfo([this] () {
454 return d->calculateFloatUIMinMax();
455 });
456 }
457 return true;
458 } else {
459 return false;
460 }
461}
462
464{
465 Q_ASSERT(d->shared->lcmsProfile);
466 return d->shared->lcmsProfile.data();
467}
468
470{
471 const IccColorProfile *rhsIcc = dynamic_cast<const IccColorProfile *>(&rhs);
472 if (rhsIcc) {
473 return d->shared == rhsIcc->d->shared;
474 }
475 return false;
476}
477
479{
480 Q_ASSERT(!d->shared->profileInfo->value.uiMinMaxes.isEmpty());
481 return d->shared->profileInfo->value.uiMinMaxes;
482}
483
486{
489
490 cmsHPROFILE cprofile = shared->lcmsProfile->lcmsProfile();
491 Q_ASSERT(cprofile);
492
493 cmsColorSpaceSignature color_space_sig = cmsGetColorSpace(cprofile);
494 unsigned int num_channels = cmsChannelsOf(color_space_sig);
495 unsigned int color_space_mask = _cmsLCMScolorSpace(color_space_sig);
496
497 Q_ASSERT(num_channels >= 1 && num_channels <= 4); // num_channels==1 is for grayscale, we need to handle it
498 Q_ASSERT(color_space_mask);
499
500 // to try to find the max range of float/doubles for this profile,
501 // pass in min/max int and make the profile convert that
502 // this is far from perfect, we need a better way, if possible to get the "bounds" of a profile
503
504 uint16_t in_min_pixel[4] = {0, 0, 0, 0};
505 uint16_t in_max_pixel[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
506 qreal out_min_pixel[4] = {0, 0, 0, 0};
507 qreal out_max_pixel[4] = {0, 0, 0, 0};
508
509 cmsHTRANSFORM trans = cmsCreateTransform(
510 cprofile,
511 (COLORSPACE_SH(color_space_mask) | CHANNELS_SH(num_channels) | BYTES_SH(2)),
512 cprofile,
513 (COLORSPACE_SH(color_space_mask) | FLOAT_SH(1) | CHANNELS_SH(num_channels) | BYTES_SH(0)), //NOTE THAT 'BYTES' FIELD IS SET TO ZERO ON DLB because 8 bytes overflows the bitfield
514 INTENT_ABSOLUTE_COLORIMETRIC, 0); // does the intent matter in this case?
515 // absolute colorimetric gives bigger bounds with cmyk's Chemical Proof
516
517 if (trans) {
518 cmsDoTransform(trans, in_min_pixel, out_min_pixel, 1);
519 cmsDoTransform(trans, in_max_pixel, out_max_pixel, 1);
520 cmsDeleteTransform(trans);
521 }//else, we'll just default to [0..1] below
522
523 // Some (calibration) profiles may have a weird RGB->XYZ transformation matrix,
524 // which is not invertible. Therefore, such profile cannot be used as
525 // a workspace color profile and we should convert the image to sRGB
526 // right on image loading
527
528 // LCMS doesn't have a separate method for checking if conversion matrix
529 // is invertible, therefore we just try to create a simple transformation,
530 // where the profile is both, input and output. If the transformation
531 // is created successfully, then this profile is probably suitable for
532 // usage as a working color space.
533
534 info.canCreateCyclicTransform = bool(trans);
535
536 ret.resize(num_channels);
537 for (unsigned int i = 0; i < num_channels; ++i) {
538 if (color_space_sig == cmsSigYCbCrData) {
539 // Although YCbCr profiles are essentially LUT-based
540 // (due to the inability of ICC to represent multiple successive
541 // matrix transforms except with BtoD0 tags in V4),
542 // YCbCr is intended to be a roundtrip transform to the
543 // corresponding RGB transform (BT.601, BT.709).
544 // Force enable the full range of values.
545 ret[i].minVal = 0;
546 ret[i].maxVal = 1;
547 } else if (out_min_pixel[i] < out_max_pixel[i]) {
548 ret[i].minVal = out_min_pixel[i];
549 ret[i].maxVal = out_max_pixel[i];
550 } else {
551 // apparently we can't even guarantee that converted_to_double(0x0000) < converted_to_double(0xFFFF)
552 // assume [0..1] in such cases
553 // we need to find a really solid way of determining the bounds of a profile, if possible
554 ret[i].minVal = 0;
555 ret[i].maxVal = 1;
556 }
557 }
558
559 return info;
560}
561
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 ...
QScopedPointer< Private > const d
void setRawData(const QByteArray &)
bool isSuitableForDisplay() const override
bool isSuitableForInput() const override
virtual bool save()
void linearizeFloatValueFast(QVector< qreal > &Value) const override
QVector< qreal > getColorantsxyY() const override
void setRawData(const QByteArray &rawData)
bool operator==(const KoColorProfile &) const override
~IccColorProfile() override
LcmsColorProfileContainer * asLcms() const
QVector< qreal > getColorantsXYZ() const override
bool supportsRelative() const override
bool hasColorants() const override
bool isSuitableForWorkspace() const override
bool supportsPerceptual() const override
bool isSuitableForPrinting() const override
QVector< qreal > getWhitePointXYZ() const override
bool isLinear() const override
void delinearizeFloatValue(QVector< qreal > &Value) const override
QString colorModelID() const override
QByteArray rawData() const override
void linearizeFloatValue(QVector< qreal > &Value) const override
const QVector< KoChannelInfo::DoubleRange > & getFloatUIMinMax(void) const
bool isSuitableForOutput() const override
bool load() override
bool supportsSaturation() const override
bool valid() const override
bool hasTRC() const override
IccColorProfile(const QString &fileName=QString())
float version() const override
QByteArray uniqueId() const override
void delinearizeFloatValueFast(QVector< qreal > &Value) const override
KoColorProfile * clone() const override
QScopedPointer< Private > d
QVector< qreal > getWhitePointxyY() const override
bool compareTRC(TransferCharacteristics characteristics, float error) const override
bool supportsAbsolute() const override
QVector< qreal > getEstimatedTRC() const override
static cmsToneCurve * transferFunction(TransferCharacteristics transferFunction)
static QByteArray lcmsProfileToByteArray(const cmsHPROFILE profile)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define INTENT_ABSOLUTE_COLORIMETRIC
Definition kis_global.h:106
QVector< KoChannelInfo::DoubleRange > uiMinMaxes
QScopedPointer< LcmsColorProfileContainer > lcmsProfile
QScopedPointer< IccColorProfile::Data > data
ProfileInfo calculateFloatUIMinMax() const
QSharedPointer< Shared > shared
KisLazyStorage< KisLazyValueWrapper< ProfileInfo >, std::function< ProfileInfo()> > LazyProfileInfo
static void colorantsForType(ColorPrimaries primaries, QVector< double > &colorants)
colorantsForPrimaries fills a QVector<float> with the xy values of the whitepoint and red,...
void setFileName(const QString &filename)
TransferCharacteristics characteristics
void setCopyright(const QString &copyright)
static QString getTransferCharacteristicName(TransferCharacteristics curve)
getTransferCharacteristicName
static QString getColorPrimariesName(ColorPrimaries primaries)
getColorPrimariesName
void setName(const QString &name)
void setManufacturer(const QString &manufacturer)
void setCharacteristics(ColorPrimaries primaries, TransferCharacteristics curve)
setCharacteristics ideally, we'd read this from the icc profile curve, but that can be tricky,...
void setInfo(const QString &info)