Krita Source Code Documentation
Loading...
Searching...
No Matches
LcmsColorSpace.h
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
3 * SPDX-FileCopyrightText: 2005-2006 C. Boemann <cbo@boemann.dk>
4 * SPDX-FileCopyrightText: 2004, 2006-2007 Cyrille Berger <cberger@cberger.net>
5 * SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
6 *
7 * SPDX-License-Identifier: LGPL-2.1-or-later
8 */
9
10#ifndef KOLCMSCOLORSPACE_H_
11#define KOLCMSCOLORSPACE_H_
12
13#include <array>
14#include <kis_lockless_stack.h>
16
18#include "kis_assert.h"
19
20
22
24{
25 struct Private {
26 cmsUInt32Number cmType; // The colorspace type as defined by littlecms
27 cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files
28 };
29
30public:
31
32 KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature)
33 : d(new Private)
34 {
35 d->cmType = cmType;
37 }
38
39 virtual ~KoLcmsInfo()
40 {
41 delete d;
42 }
43
44 virtual quint32 colorSpaceType() const
45 {
46 return d->cmType;
47 }
48
49 virtual cmsColorSpaceSignature colorSpaceSignature() const
50 {
51 return d->colorSpaceSignature;
52 }
53
54private:
55 Private *const d;
56};
57
59 cmsHTRANSFORM toRGB;
60 cmsHTRANSFORM toRGB16;
61 cmsHTRANSFORM fromRGB;
62 static cmsHPROFILE s_RGBProfile;
63 static QMap< QString, QMap< LcmsColorProfileContainer *, KoLcmsDefaultTransformations * > > s_transformations;
64};
65
70template<class _CSTraits>
71class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo
72{
74
77 , m_colorSpace(colorSpace)
78 {
79 csProfile = 0;
80 cmstransform = 0;
82 profiles[0] = 0;
83 profiles[1] = 0;
84 profiles[2] = 0;
85 }
86
88 {
89
90 if (cmstransform) {
91 cmsDeleteTransform(cmstransform);
92 }
93 if (profiles[0] && profiles[0] != csProfile) {
94 cmsCloseProfile(profiles[0]);
95 }
96 if (profiles[1] && profiles[1] != csProfile) {
97 cmsCloseProfile(profiles[1]);
98 }
99 if (profiles[2] && profiles[2] != csProfile) {
100 cmsCloseProfile(profiles[2]);
101 }
102 }
103
104 void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override
105 {
106 cmsDoTransform(cmstransform, const_cast<quint8 *>(src), dst, nPixels);
107
108 qint32 numPixels = nPixels;
109 qint32 pixelSize = m_colorSpace->pixelSize();
110 int index = 0;
111
112 if (cmsAlphaTransform) {
113 float *alpha = new float[nPixels];
114 float *dstalpha = new float[nPixels];
115
116 while (index < nPixels) {
117 alpha[index] = m_colorSpace->opacityF(src);
118 src += pixelSize;
119 index++;
120 }
121
122 cmsDoTransform(cmsAlphaTransform, const_cast<float *>(alpha), static_cast<float *>(dstalpha), nPixels);
123 for (int i = 0; i < numPixels; i++) {
124 m_colorSpace->setOpacity(dst, dstalpha[i], 1);
125 dst += pixelSize;
126 }
127
128 delete [] alpha;
129 delete [] dstalpha;
130 } else {
131 while (numPixels > 0) {
132 qreal alpha = m_colorSpace->opacityF(src);
133 m_colorSpace->setOpacity(dst, alpha, 1);
134 src += pixelSize;
135 dst += pixelSize;
136 numPixels--;
137 }
138 }
139 }
140
142 cmsHPROFILE csProfile;
143 cmsHPROFILE profiles[3];
144 cmsHTRANSFORM cmstransform;
145 cmsHTRANSFORM cmsAlphaTransform;
146 };
147
149 cmsHPROFILE profile = nullptr; // Last used profile to transform to/from RGB
150 cmsHTRANSFORM transform = nullptr; // Last used transform to/from RGB
151
153 {
154 if (transform)
155 cmsDeleteTransform(transform);
156 }
157 };
158
160
162
173
174protected:
175
176 LcmsColorSpace(const QString &id,
177 const QString &name,
178 cmsUInt32Number cmType,
179 cmsColorSpaceSignature colorSpaceSignature,
181 : KoColorSpaceAbstract<_CSTraits>(id, name)
183 , d(new Private())
184 {
185 Q_ASSERT(p); // No profile means the lcms color space can't work
186 Q_ASSERT(profileIsCompatible(p));
188 Q_ASSERT(d->profile);
189 d->colorProfile = p;
191 }
192
194 {
195 delete d->colorProfile;
197 delete d;
198 }
199
200 void init()
201 {
203
205 KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile();
206 }
209 KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags();
212 TYPE_BGR_8,
214 this->colorSpaceType(),
216 conversionFlags);
218
219 // LCMS has a too optimistic optimization when transforming from linear color spaces
220 if (d->profile->isLinear()) {
222 }
223
224 d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(),
225 this->colorSpaceType(),
227 TYPE_BGR_8,
229 conversionFlags);
231
232 d->defaultTransformations->toRGB16 = cmsCreateTransform(d->profile->lcmsProfile(),
233 this->colorSpaceType(),
235 TYPE_BGR_16,
237 conversionFlags);
239
241 }
242 }
243
244public:
245
246 bool hasHighDynamicRange() const override
247 {
248 return false;
249 }
250
251 const KoColorProfile *profile() const override
252 {
253 return d->colorProfile;
254 }
255
256 bool profileIsCompatible(const KoColorProfile *profile) const override
257 {
258 const IccColorProfile *p = dynamic_cast<const IccColorProfile *>(profile);
259 return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature());
260 }
261
262 void fromQColor(const QColor &color, quint8 *dst) const override
263 {
264 std::array<quint8, 3> qcolordata;
265
266 qcolordata[2] = static_cast<quint8>(color.red());
267 qcolordata[1] = static_cast<quint8>(color.green());
268 qcolordata[0] = static_cast<quint8>(color.blue());
269
270 // Default sRGB
272 cmsDoTransform(d->defaultTransformations->fromRGB, qcolordata.data(), dst, 1);
273
274 this->setOpacity(dst, static_cast<quint8>(color.alpha()), 1);
275 }
276
277 void toQColor(const quint8 *src, QColor *color) const override
278 {
279 std::array<quint8, 3> qcolordata;
280
281 // Default sRGB transform
283 cmsDoTransform(d->defaultTransformations->toRGB, src, qcolordata.data(), 1);
284
285 color->setRgb(qcolordata[2], qcolordata[1], qcolordata[0]);
286 color->setAlpha(this->opacityU8(src));
287 }
288
289 void toQColor16(const quint8 *src, QColor *color) const override
290 {
291 std::array<quint16, 3> qcolordata;
292
293 // Default sRGB transform
295 cmsDoTransform(d->defaultTransformations->toRGB16, src, qcolordata.data(), 1);
296
297 color->setRgba64(QRgba64::fromRgba64(qcolordata[2], qcolordata[1], qcolordata[0], 0x0000));
298 color->setAlpha(this->opacityU8(src));
299 }
300
301 KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const override
302 {
303 if (!d->profile) {
304 return 0;
305 }
306
307 cmsToneCurve *transferFunctions[3];
308 transferFunctions[0] = cmsBuildTabulatedToneCurve16(0, 256, transferValues);
309 transferFunctions[1] = cmsBuildGamma(0, 1.0);
310 transferFunctions[2] = cmsBuildGamma(0, 1.0);
311
313 adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions);
314 cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass);
315
316 adj->profiles[0] = d->profile->lcmsProfile();
317 adj->profiles[2] = d->profile->lcmsProfile();
318 adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(),
321 adj->csProfile = d->profile->lcmsProfile();
322 return adj;
323 }
324
325 KoColorTransformation *createPerChannelAdjustment(const quint16 *const *transferValues) const override
326 {
327 if (!d->profile) {
328 return 0;
329 }
330
331 cmsToneCurve **transferFunctions = new cmsToneCurve*[ this->colorChannelCount()];
332
333 for (uint ch = 0; ch < this->colorChannelCount(); ch++) {
334 transferFunctions[ch] = transferValues[ch] ?
335 cmsBuildTabulatedToneCurve16(0, 256, transferValues[ch]) :
336 cmsBuildGamma(0, 1.0);
337 }
338
339 cmsToneCurve **alphaTransferFunctions = new cmsToneCurve*[1];
340 alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ?
341 cmsBuildTabulatedToneCurve16(0, 256, transferValues[this->colorChannelCount()]) :
342 cmsBuildGamma(0, 1.0);
343
345 adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions);
346 adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions);
347 adj->profiles[2] = 0;
348 adj->csProfile = d->profile->lcmsProfile();
349 adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), 0, this->colorSpaceType(),
352
353 adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_FLT, 0, TYPE_GRAY_FLT,
356
357 delete [] transferFunctions;
358 delete [] alphaTransferFunctions;
359 return adj;
360 }
361
362 quint8 difference(const quint8 *src1, const quint8 *src2) const override
363 {
364 quint8 lab1[8], lab2[8];
365 cmsCIELab labF1, labF2;
366
367 if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8
368 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) {
369 return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255);
370 }
371 Q_ASSERT(this->toLabA16Converter());
372 this->toLabA16Converter()->transform(src1, lab1, 1);
373 this->toLabA16Converter()->transform(src2, lab2, 1);
374 cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1);
375 cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2);
376 qreal diff = cmsDeltaE(&labF1, &labF2);
377
378 if (diff > 255.0) {
379 return 255;
380 } else {
381 return quint8(diff);
382 }
383 }
384
385 quint8 differenceA(const quint8 *src1, const quint8 *src2) const override
386 {
387 quint8 lab1[8];
388 quint8 lab2[8];
389 cmsCIELab labF1;
390 cmsCIELab labF2;
391
392 if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8
393 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) {
394
395
396 const qreal alphaScale = 100.0 / 255.0;
397 return qRound(alphaScale * qAbs(this->opacityU8(src1) - this->opacityU8(src2)));
398 }
399 Q_ASSERT(this->toLabA16Converter());
400 this->toLabA16Converter()->transform(src1, lab1, 1);
401 this->toLabA16Converter()->transform(src2, lab2, 1);
402 cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1);
403 cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2);
404
405 cmsFloat64Number dL;
406 cmsFloat64Number da;
407 cmsFloat64Number db;
408 cmsFloat64Number dAlpha;
409
410 dL = fabs((qreal)(labF1.L - labF2.L));
411 da = fabs((qreal)(labF1.a - labF2.a));
412 db = fabs((qreal)(labF1.b - labF2.b));
413
414 static const int LabAAlphaPos = 3;
415 static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits<quint16>::max;
416 quint16 alpha1 = reinterpret_cast<quint16 *>(lab1)[LabAAlphaPos];
417 quint16 alpha2 = reinterpret_cast<quint16 *>(lab2)[LabAAlphaPos];
418 dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale;
419
420 qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5);
421
422 if (diff > 255.0) {
423 return 255;
424 } else {
425 return quint8(diff);
426 }
427 }
428
429private:
430
432 {
433 return d->profile;
434 }
435
437 {
438 if (!p) {
439 return 0;
440 }
441
442 const IccColorProfile *iccp = dynamic_cast<const IccColorProfile *>(p);
443
444 if (!iccp) {
445 return 0;
446 }
447
448 Q_ASSERT(iccp->asLcms());
449
450 return iccp->asLcms();
451 }
452
453 Private *const d;
454};
455
460{
461public:
462 LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature)
464 {
465 }
466
467 bool profileIsCompatible(const KoColorProfile *profile) const override
468 {
469 const IccColorProfile *p = dynamic_cast<const IccColorProfile *>(profile);
470 return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature());
471 }
472
473 QString colorSpaceEngine() const override
474 {
475 return "icc";
476 }
477
478 bool isHdr() const override
479 {
480 return false;
481 }
482
483 int crossingCost() const override {
484 return 1;
485 }
486
488 KoColorProfile *createColorProfile(const QByteArray &rawData) const override;
489};
490
491#endif
const Params2D p
const quint8 OPACITY_TRANSPARENT_U8
unsigned int uint
LcmsColorProfileContainer * asLcms() const
void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const override
quint32 colorChannelCount() const override
quint8 opacityU8(const quint8 *U8_pixel) const override
const KoColorConversionTransformation * toLabA16Converter() const
virtual quint32 pixelSize() const =0
virtual qreal opacityF(const quint8 *pixel) const =0
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
Private *const d
virtual cmsColorSpaceSignature colorSpaceSignature() const
virtual quint32 colorSpaceType() const
KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature)
virtual ~KoLcmsInfo()
bool isHdr() const override
bool profileIsCompatible(const KoColorProfile *profile) const override
KoColorProfile * createColorProfile(const QByteArray &rawData) const override
QList< KoColorConversionTransformationFactory * > colorConversionLinks() const override
int crossingCost() const override
QString colorSpaceEngine() const override
LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature)
void toQColor(const quint8 *src, QColor *color) const override
KisLocklessStack< KisLcmsLastTransformationSP > KisLcmsTransformationStack
void fromQColor(const QColor &color, quint8 *dst) const override
QSharedPointer< KisLcmsLastTransformation > KisLcmsLastTransformationSP
bool hasHighDynamicRange() const override
void toQColor16(const quint8 *src, QColor *color) const override
KoColorTransformation * createPerChannelAdjustment(const quint16 *const *transferValues) const override
static LcmsColorProfileContainer * asLcmsProfile(const KoColorProfile *p)
Private *const d
quint8 differenceA(const quint8 *src1, const quint8 *src2) const override
LcmsColorSpace(const QString &id, const QString &name, cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature, KoColorProfile *p)
LcmsColorProfileContainer * lcmsProfile() const
KoColorTransformation * createBrightnessContrastAdjustment(const quint16 *transferValues) const override
quint8 difference(const quint8 *src1, const quint8 *src2) const override
~LcmsColorSpace() override
const KoColorProfile * profile() const override
bool profileIsCompatible(const KoColorProfile *profile) const override
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override=0
virtual bool isSuitableForOutput() const =0
static QMap< QString, QMap< LcmsColorProfileContainer *, KoLcmsDefaultTransformations * > > s_transformations
static cmsHPROFILE s_RGBProfile
cmsColorSpaceSignature colorSpaceSignature
cmsUInt32Number cmType
KoLcmsColorTransformation(const KoColorSpace *colorSpace)
void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override
KisLcmsTransformationStack fromRGBCachedTransformations
KisLcmsTransformationStack toRGB16CachedTransformations
KoLcmsDefaultTransformations * defaultTransformations
KisLcmsTransformationStack toRGBCachedTransformations
LcmsColorProfileContainer * profile
KoColorProfile * colorProfile