Krita Source Code Documentation
Loading...
Searching...
No Matches
KoColorSpace.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7
8#include "KoColorSpace.h"
9#include "KoColorSpace_p.h"
10
11#include "KoChannelInfo.h"
12#include "DebugPigment.h"
13#include "KoCompositeOp.h"
20#include "KoColorProfile.h"
23#include "KoMixColorsOp.h"
24#include "KoConvolutionOp.h"
26#include "KoColorSpaceEngine.h"
27#include <KoColorSpaceTraits.h>
29#include "KisDitherOp.h"
30
31#include <cmath>
32
33#include <QThreadStorage>
34#include <QBitArray>
35#include <QPolygonF>
36#include <QPointF>
37
38
40 : d(new Private())
41{
42}
43
44KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp *mixColorsOp, KoConvolutionOp *convolutionOp)
45 : d(new Private())
46{
47 d->id = id;
48 d->idNumber = qHash(d->id);
49 d->name = name;
50 d->mixColorsOp = mixColorsOp;
51 d->convolutionOp = convolutionOp;
52 d->transfoToRGBA16 = 0;
53 d->transfoFromRGBA16 = 0;
54 d->transfoToLABA16 = 0;
55 d->transfoFromLABA16 = 0;
56 d->gamutXYY = QPolygonF();
57 d->TRCXYY = QPolygonF();
58 d->colorants = QVector <qreal> (0);
59 d->lumaCoefficients = QVector <qreal> (0);
60 d->iccEngine = 0;
61 d->deletability = NotOwnedByRegistry;
62}
63
65{
66 Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);
67
68 Q_FOREACH(const KoCompositeOp *op, d->compositeOps) {
69 delete op;
70 }
71 d->compositeOps.clear();
72 for (const auto& map: d->ditherOps) {
73 qDeleteAll(map);
74 }
75 d->ditherOps.clear();
76 Q_FOREACH (KoChannelInfo * channel, d->channels) {
77 delete channel;
78 }
79 d->channels.clear();
80 if (d->deletability == NotOwnedByRegistry) {
82 if (cache) {
83 cache->colorSpaceIsDestroyed(this);
84 }
85 }
86 delete d->mixColorsOp;
87 delete d->convolutionOp;
88 delete d->transfoToRGBA16;
89 delete d->transfoFromRGBA16;
90 delete d->transfoToLABA16;
91 delete d->transfoFromLABA16;
92 delete d;
93}
94
96{
97 const KoColorProfile* p1 = rhs.profile();
98 const KoColorProfile* p2 = profile();
99 return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2));
100}
101
102QString KoColorSpace::id() const
103{
104 return d->id;
105}
106
107QString KoColorSpace::name() const
108{
109 return d->name;
110}
111
112//Color space info stuff.
113QPolygonF KoColorSpace::gamutXYY() const
114{
115 if (d->gamutXYY.empty()) {
116 //now, let's decide on the boundary. This is a bit tricky because icc profiles can be both matrix-shaper and cLUT at once if the maker so pleases.
117 //first make a list of colors.
118 qreal max = 1.0;
119 if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
120 //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
121 max = this->channels()[0]->getUIMax();
122
123 }
124 int samples = 5;//amount of samples in our color space.
125 const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
126 quint8 *data = new quint8[pixelSize()];
127 quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel.
128 //QVector <qreal> sampleCoordinates(pow(colorChannelCount(),samples));
129 //sampleCoordinates.fill(0.0);
130
131 // This is fixed to 5 since the maximum number of channels are 5 for CMYKA
132 QVector <float> channelValuesF(5);//for getting the coordinates.
133
134 for(int x=0;x<samples;x++){
135 if (colorChannelCount()==1) {//gray
136 channelValuesF[0]=(max/(samples-1))*(x);
137 channelValuesF[1]=max;
138 fromNormalisedChannelsValue(data, channelValuesF);
140 xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
141 qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
142 qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
143 d->gamutXYY << QPointF(x,y);
144 } else {
145 for(int y=0;y<samples;y++){
146 for(int z=0;z<samples;z++){
147 if (colorChannelCount()==4) {
148 for(int k=0;k<samples;k++){
149 channelValuesF[0] = (max / (samples - 1)) * (x);
150 channelValuesF[1] = (max / (samples - 1)) * (y);
151 channelValuesF[2] = (max / (samples - 1)) * (z);
152 channelValuesF[3] = (max / (samples - 1)) * (k);
153 channelValuesF[4] = max;
154 fromNormalisedChannelsValue(data, channelValuesF);
156 xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
157 qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
158 qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
159 d->gamutXYY<< QPointF(x,y);
160 }
161 } else {
162 channelValuesF[0]=(max/(samples-1))*(x);
163 channelValuesF[1]=(max/(samples-1))*(y);
164 channelValuesF[2]=(max/(samples-1))*(z);
165 channelValuesF[3]=max;
166 if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
167 fromNormalisedChannelsValue(data, channelValuesF);
169 xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
170 }
171 qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
172 qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
173 d->gamutXYY<< QPointF(x,y);
174 }
175 }
176 }
177
178 }
179 }
180 delete[] data;
181 //if we ever implement a boundary-checking thing I'd add it here.
182 return d->gamutXYY;
183 } else {
184 return d->gamutXYY;
185 }
186}
187
189{
190 if (d->TRCXYY.empty()){
191 const QList<KoChannelInfo *> channelInfo = this->channels();
192
193 qreal max = 1.0;
194 if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
195 //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
196 max = channelInfo[0]->getUIMax();
197 }
198 const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
199 quint8 *data = new quint8[pixelSize()];
200 quint8 *data2 = new quint8[xyzColorSpace->pixelSize()];
201
202 // This is fixed to 5 since the maximum number of channels are 5 for CMYKA
203 QVector <float> channelValuesF(5);//for getting the coordinates.
204
205 d->colorants.resize(3*colorChannelCount());
206
207 const int segments = 10;
208 for (quint32 i=0; i<colorChannelCount(); i++) {
209 qreal colorantY=1.0;
210 if (colorModelId().id()!="CMYKA") {
211 for (int j = 0; j <= segments; j++) {
212 channelValuesF.fill(0.0);
213 channelValuesF[channelInfo[i]->displayPosition()] = ((max/segments)*(segments-j));
214
215 if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
216 fromNormalisedChannelsValue(data, channelValuesF);
218 xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
219 }
220 if (j==0) {
221 colorantY = channelValuesF[1];
222 d->colorants[3*i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
223 d->colorants[3*i+1] = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
224 d->colorants[3*i+2] = channelValuesF[1];
225 }
226 d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/segments)*(segments-j)));
227 }
228 } else {
229 for (int j = 0; j <= segments; j++) {
230 channelValuesF.fill(0.0);
231 channelValuesF[i] = ((max/segments)*(j));
232
233 fromNormalisedChannelsValue(data, channelValuesF);
234
236
237 xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
238
239 if (j==0) {
240 colorantY = channelValuesF[1];
241 d->colorants[3*i] = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
242 d->colorants[3*i+1] = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
243 d->colorants[3*i+2] = channelValuesF[1];
244 }
245 d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/segments)*(j)));
246 }
247 }
248 }
249
250 delete[] data;
251 delete[] data2;
252 return d->TRCXYY;
253 } else {
254 return d->TRCXYY;
255 }
256}
257
258QVector <qreal> KoColorSpace::lumaCoefficients() const
259{
260 if (d->lumaCoefficients.size()>1){
261 return d->lumaCoefficients;
262 } else {
263 d->lumaCoefficients.resize(3);
264 if (colorModelId().id()!="RGBA") {
265 d->lumaCoefficients.fill(0.33);
266 } else {
267 if (d->colorants.size() <= 0) {
268 if (profile() && profile()->hasColorants()) {
269 d->colorants.resize(3 * colorChannelCount());
270 d->colorants = profile()->getColorantsxyY();
271 }
272 else {
273 QPolygonF p = estimatedTRCXYY();
274 Q_UNUSED(p);
275 }
276 }
277 if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) {
278 d->lumaCoefficients[0]=0.2126;
279 d->lumaCoefficients[1]=0.7152;
280 d->lumaCoefficients[2]=0.0722;
281 } else {
282 // luma coefficients need to add up to 1.0
283 qreal sum = d->colorants[2] + d->colorants[5] + d->colorants[8];
284 d->lumaCoefficients[0] = d->colorants[2] / sum;
285 d->lumaCoefficients[1] = d->colorants[5] / sum;
286 d->lumaCoefficients[2] = d->colorants[8] / sum;
287 }
288 }
289 return d->lumaCoefficients;
290 }
291}
292
294{
295 return d->channels;
296}
297
298QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
299{
300 QBitArray ba(d->channels.size());
301 if (!color && !alpha) return ba;
302
303 for (int i = 0; i < d->channels.size(); ++i) {
304 KoChannelInfo * channel = d->channels.at(i);
305 if ((color && channel->channelType() == KoChannelInfo::COLOR) ||
306 (alpha && channel->channelType() == KoChannelInfo::ALPHA))
307 ba.setBit(i, true);
308 }
309 return ba;
310}
311
313{
314 d->channels.push_back(ci);
315}
316bool KoColorSpace::hasCompositeOp(const QString& id, const KoColorSpace *srcSpace) const
317{
318 if (srcSpace && preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(id)) {
319 return true;
320 }
321 return d->compositeOps.contains(id);
322}
323
325{
326 return d->compositeOps.values();
327}
328
330{
331 return d->mixColorsOp;
332}
333
334const KisDitherOp *KoColorSpace::ditherOp(const QString &depth, DitherType type) const
335{
336 const auto it = d->ditherOps.constFind(depth);
337 if (it != d->ditherOps.constEnd()) {
338 switch (type) {
339 case DITHER_FAST:
340 case DITHER_BAYER:
341 return it->constFind(DITHER_BAYER).value();
342 case DITHER_BEST:
344 return it->constFind(DITHER_BLUE_NOISE).value();
345 case DITHER_NONE:
346 default:
347 return it->constFind(DITHER_NONE).value();
348 }
349 } else {
350 warnPigment << "Asking for dither op from " << colorDepthId() << "to an unsupported depth" << depth << "!";
351 return nullptr;
352 }
353}
354
356{
357 if (op->sourceDepthId() == colorDepthId()) {
358 if (!d->ditherOps.contains(op->destinationDepthId().id())) {
359 d->ditherOps.insert(op->destinationDepthId().id(), {{op->type(), op}});
360 } else {
361 d->ditherOps[op->destinationDepthId().id()].insert(op->type(), op);
362 }
363 }
364}
365
367{
368 return d->convolutionOp;
369}
370
371const KoCompositeOp * KoColorSpace::compositeOp(const QString & id, const KoColorSpace *srcSpace) const
372{
373 if (srcSpace && preferCompositionInSourceColorSpace()) {
374 if (const KoCompositeOp *op = srcSpace->compositeOp(id)) {
375 return op;
376 }
377 }
378 const QHash<QString, KoCompositeOp*>::ConstIterator it = d->compositeOps.constFind(id);
379 if (it != d->compositeOps.constEnd()) {
380 return it.value();
381 }
382 else {
383 warnPigment << "Asking for nonexistent composite operation " << id << ", returning " << COMPOSITE_OVER;
384 return d->compositeOps.value(COMPOSITE_OVER);
385 }
386}
387
389{
390 if (op->colorSpace()->id() == id()) {
391 d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
392 }
393}
394
395void KoColorSpace::transparentColor(quint8 *dst, quint32 nPixels) const
396{
397 memset(dst, 0, pixelSize() * nPixels);
398 setOpacity(dst, OPACITY_TRANSPARENT_U8, nPixels);
399}
400
408
430
431void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
432{
433 toLabA16Converter()->transform(src, dst, nPixels);
434}
435
436void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
437{
438 fromLabA16Converter()->transform(src, dst, nPixels);
439}
440
441void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
442{
443 toRgbA16Converter()->transform(src, dst, nPixels);
444}
445
446void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
447{
448 fromRgbA16Converter()->transform(src, dst, nPixels);
449}
450
451KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
452{
453 if (*this == *dstColorSpace) {
455 } else {
456 return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
457 }
458}
459
460bool KoColorSpace::convertPixelsTo(const quint8 * src,
461 quint8 * dst,
462 const KoColorSpace * dstColorSpace,
463 quint32 numPixels,
465 KoColorConversionTransformation::ConversionFlags conversionFlags) const
466{
467 if (*this == *dstColorSpace) {
468 if (src != dst) {
469 memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
470 }
471 } else {
472 KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
473 cct.transformation()->transform(src, dst, numPixels);
474 }
475 return true;
476}
477
478KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, bool bpcFirstTransform, quint8 *gamutWarning, KoColorConversionTransformation::ConversionFlags displayConversionFlags) const
479{
480 if (!d->iccEngine) {
481 d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
482 }
483 if (!d->iccEngine) return 0;
484
485 return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, bpcFirstTransform, gamutWarning, displayConversionFlags);
486}
487
488bool KoColorSpace::proofPixelsTo(const quint8 *src,
489 quint8 *dst,
490 quint32 numPixels,
491 KoColorConversionTransformation *proofingTransform) const
492{
493 proofingTransform->transform(src, dst, numPixels);
494
495 //the transform is deleted in the destructor.
496 return true;
497}
498
501 KoColorConversionTransformation::ConversionFlags conversionFlags) const
502{
503 Q_ASSERT_X(*op->colorSpace() == *this || (preferCompositionInSourceColorSpace() && *op->colorSpace() == *srcSpace),
504 "KoColorSpace::bitBlt", QString("Composite op is for color space %1 (%2) while this is %3 (%4)").arg(op->colorSpace()->id()).arg(op->colorSpace()->profile()->name()).arg(id()).arg(profile()->name()).toLatin1());
505
506 if(params.rows <= 0 || params.cols <= 0)
507 return;
508
509 if(!(*this == *srcSpace)) {
511 (*op->colorSpace() == *srcSpace || srcSpace->hasCompositeOp(op->id()))) {
512
513 quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize();
514 QVector<quint8> * conversionDstCache = d->conversionCache.get(params.rows * conversionDstBufferStride);
515 quint8* conversionDstData = conversionDstCache->data();
516
517 for(qint32 row=0; row<params.rows; row++) {
518 convertPixelsTo(params.dstRowStart + row * params.dstRowStride,
519 conversionDstData + row * conversionDstBufferStride, srcSpace, params.cols,
520 renderingIntent, conversionFlags);
521 }
522
523 // TODO: Composite op substitution should eventually be removed here, but it's not urgent.
524 // Code should just provide srcSpace to KoColorSpace::compositeOp() to avoid the lookups.
525 const KoCompositeOp *otherOp = (*op->colorSpace() == *srcSpace) ? op : srcSpace->compositeOp(op->id());
526
527 KoCompositeOp::ParameterInfo paramInfo(params);
528 paramInfo.dstRowStart = conversionDstData;
529 paramInfo.dstRowStride = conversionDstBufferStride;
530 otherOp->composite(paramInfo);
531
532 for(qint32 row=0; row<params.rows; row++) {
533 srcSpace->convertPixelsTo(conversionDstData + row * conversionDstBufferStride,
534 params.dstRowStart + row * params.dstRowStride, this, params.cols,
535 renderingIntent, conversionFlags);
536 }
537
538 } else {
539 quint32 conversionBufferStride = params.cols * pixelSize();
540 QVector<quint8> * conversionCache = d->conversionCache.get(params.rows * conversionBufferStride);
541 quint8* conversionData = conversionCache->data();
542
543
544 const bool noChannelFlags = params.channelFlags.isEmpty() ||
545 params.channelFlags == srcSpace->channelFlags(true, true);
546
547 if (noChannelFlags) {
548 for(qint32 row=0; row<params.rows; row++) {
549 srcSpace->convertPixelsTo(params.srcRowStart + row * params.srcRowStride,
550 conversionData + row * conversionBufferStride, this, params.cols,
551 renderingIntent, conversionFlags);
552 }
553
554 KoCompositeOp::ParameterInfo paramInfo(params);
555 paramInfo.srcRowStart = conversionData;
556 paramInfo.srcRowStride = conversionBufferStride;
557 paramInfo.channelFlags = QBitArray();
558 op->composite(paramInfo);
559 } else {
560 quint32 homogenizationBufferStride = params.cols * srcSpace->pixelSize();
561 QVector<quint8> * homogenizationCache = d->channelFlagsApplicationCache.get(homogenizationBufferStride);
562 quint8* homogenizationData = homogenizationCache->data();
563
564 for(qint32 row=0; row<params.rows; row++) {
565 srcSpace->convertChannelToVisualRepresentation(params.srcRowStart + row * params.srcRowStride,
566 homogenizationData, params.cols,
567 params.channelFlags | srcSpace->channelFlags(false, true));
568 srcSpace->convertPixelsTo(homogenizationData,
569 conversionData + row * conversionBufferStride, this, params.cols,
570 renderingIntent, conversionFlags);
571 }
572
573
574 KoCompositeOp::ParameterInfo paramInfo(params);
575 paramInfo.srcRowStart = conversionData;
576 paramInfo.srcRowStride = conversionBufferStride;
577 paramInfo.channelFlags = channelFlags(true, params.channelFlags.testBit(srcSpace->alphaPos()));
578 op->composite(paramInfo);
579 }
580
581
582 }
583 }
584 else {
585 op->composite(params);
586 }
587}
588
589KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
590{
592 if (!factory) return 0;
593 QPair<KoID, KoID> model(colorModelId(), colorDepthId());
594 QList< QPair<KoID, KoID> > models = factory->supportedModels();
595 if (models.isEmpty() || models.contains(model)) {
596 return factory->createTransformation(this, parameters);
597 } else {
598 // Find the best solution
599 // TODO use the color conversion cache
600 KoColorConversionTransformation* csToFallBack = 0;
601 KoColorConversionTransformation* fallBackToCs = 0;
602 KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs);
603 Q_ASSERT(csToFallBack);
604 Q_ASSERT(fallBackToCs);
605 KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters);
606 return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
607 }
608}
609
610void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{
611 int channelnumber = channelCount();
612 QVector <double> channelValues(channelnumber);
613 QVector <float> channelValuesF(channelnumber);
614 normalisedChannelsValue(pixel, channelValuesF);
615 for (int i=0;i<channelnumber;i++){
616 channelValues[i]=channelValuesF[i];
617 }
618 if (profile()->hasTRC()){
619 //only linearise and crunch the luma if there's a TRC
620 profile()->linearizeFloatValue(channelValues);
621 qreal hue, sat, luma = 0.0;
622 toHSY(channelValues, &hue, &sat, &luma);
623 luma = pow(luma, 1/2.2);
624 luma = qMin(1.0, luma + step);
625 luma = pow(luma, 2.2);
626 channelValues = fromHSY(&hue, &sat, &luma);
627 profile()->delinearizeFloatValue(channelValues);
628 } else {
629 qreal hue, sat, luma = 0.0;
630 toHSY(channelValues, &hue, &sat, &luma);
631 luma = qMin(1.0, luma + step);
632 channelValues = fromHSY(&hue, &sat, &luma);
633 }
634 for (int i=0;i<channelnumber;i++){
635 channelValuesF[i]=channelValues[i];
636 }
637 fromNormalisedChannelsValue(pixel, channelValuesF);
638 setOpacity(pixel, 1.0, 1);
639}
640void KoColorSpace::decreaseLuminosity(quint8 * pixel, qreal step) const {
641 int channelnumber = channelCount();
642 QVector <double> channelValues(channelnumber);
643 QVector <float> channelValuesF(channelnumber);
644 normalisedChannelsValue(pixel, channelValuesF);
645 for (int i=0;i<channelnumber;i++){
646 channelValues[i]=channelValuesF[i];
647 }
648 if (profile()->hasTRC()){
649 //only linearise and crunch the luma if there's a TRC
650 profile()->linearizeFloatValue(channelValues);
651 qreal hue, sat, luma = 0.0;
652 toHSY(channelValues, &hue, &sat, &luma);
653 luma = pow(luma, 1/2.2);
654 if (luma-step<0.0) {
655 luma=0.0;
656 } else {
657 luma -= step;
658 }
659 luma = pow(luma, 2.2);
660 channelValues = fromHSY(&hue, &sat, &luma);
661 profile()->delinearizeFloatValue(channelValues);
662 } else {
663 qreal hue, sat, luma = 0.0;
664 toHSY(channelValues, &hue, &sat, &luma);
665 if (luma-step<0.0) {
666 luma=0.0;
667 } else {
668 luma -= step;
669 }
670 channelValues = fromHSY(&hue, &sat, &luma);
671 }
672 for (int i=0;i<channelnumber;i++){
673 channelValuesF[i]=channelValues[i];
674 }
675 fromNormalisedChannelsValue(pixel, channelValuesF);
676 setOpacity(pixel, 1.0, 1);
677}
678void KoColorSpace::increaseSaturation(quint8 * pixel, qreal step) const{
679 int channelnumber = channelCount();
680 QVector <double> channelValues(channelnumber);
681 QVector <float> channelValuesF(channelnumber);
682 normalisedChannelsValue(pixel, channelValuesF);
683 for (int i=0;i<channelnumber;i++){
684 channelValues[i]=channelValuesF[i];
685 }
686 profile()->linearizeFloatValue(channelValues);
687 qreal hue, sat, luma = 0.0;
688 toHSY(channelValues, &hue, &sat, &luma);
689 sat += step;
690 sat = qBound(0.0, sat, 1.0);
691 channelValues = fromHSY(&hue, &sat, &luma);
692 profile()->delinearizeFloatValue(channelValues);
693 for (int i=0;i<channelnumber;i++){
694 channelValuesF[i]=channelValues[i];
695 }
696 fromNormalisedChannelsValue(pixel, channelValuesF);
697 setOpacity(pixel, 1.0, 1);
698}
699void KoColorSpace::decreaseSaturation(quint8 * pixel, qreal step) const{
700 int channelnumber = channelCount();
701 QVector <double> channelValues(channelnumber);
702 QVector <float> channelValuesF(channelnumber);
703 normalisedChannelsValue(pixel, channelValuesF);
704 for (int i=0;i<channelnumber;i++){
705 channelValues[i]=channelValuesF[i];
706 }
707 profile()->linearizeFloatValue(channelValues);
708 qreal hue, sat, luma = 0.0;
709 toHSY(channelValues, &hue, &sat, &luma);
710 sat -= step;
711 sat = qBound(0.0, sat, 1.0);
712 channelValues = fromHSY(&hue, &sat, &luma);
713 profile()->delinearizeFloatValue(channelValues);
714 for (int i=0;i<channelnumber;i++){
715 channelValuesF[i]=channelValues[i];
716 }
717 fromNormalisedChannelsValue(pixel, channelValuesF);
718 setOpacity(pixel, 1.0, 1);
719}
720void KoColorSpace::increaseHue(quint8 * pixel, qreal step) const{
721 int channelnumber = channelCount(); //doesn't work for cmyka...
722 QVector <double> channelValues(channelnumber);
723 QVector <float> channelValuesF(channelnumber);
724 normalisedChannelsValue(pixel, channelValuesF);
725 for (int i=0;i<channelnumber;i++){
726 channelValues[i]=channelValuesF[i];
727 }
728 profile()->linearizeFloatValue(channelValues);
729 qreal hue, sat, luma = 0.0;
730 toHSY(channelValues, &hue, &sat, &luma);
731 if (hue+step>1.0){
732 hue=(hue+step)- 1.0;
733 } else {
734 hue += step;
735 }
736 channelValues = fromHSY(&hue, &sat, &luma);
737 profile()->delinearizeFloatValue(channelValues);
738 for (int i=0;i<channelnumber;i++){
739 channelValuesF[i]=channelValues[i];
740 }
741 fromNormalisedChannelsValue(pixel, channelValuesF);
742 setOpacity(pixel, 1.0, 1);
743}
744void KoColorSpace::decreaseHue(quint8 * pixel, qreal step) const{
745 int channelnumber = channelCount();
746 QVector <double> channelValues(channelnumber);
747 QVector <float> channelValuesF(channelnumber);
748 normalisedChannelsValue(pixel, channelValuesF);
749 for (int i=0;i<channelnumber;i++){
750 channelValues[i]=channelValuesF[i];
751 }
752 profile()->linearizeFloatValue(channelValues);
753 qreal hue, sat, luma = 0.0;
754 toHSY(channelValues, &hue, &sat, &luma);
755 if (hue-step<0.0){
756 hue=1.0-(step-hue);
757 } else {
758 hue -= step;
759 }
760 channelValues = fromHSY(&hue, &sat, &luma);
761 profile()->delinearizeFloatValue(channelValues);
762 for (int i=0;i<channelnumber;i++){
763 channelValuesF[i]=channelValues[i];
764 }
765 fromNormalisedChannelsValue(pixel, channelValuesF);
766 setOpacity(pixel, 1.0, 1);
767}
768
769void KoColorSpace::increaseRed(quint8 * pixel, qreal step) const{
770 int channelnumber = channelCount();
771 QVector <double> channelValues(channelnumber);
772 QVector <float> channelValuesF(channelnumber);
773 normalisedChannelsValue(pixel, channelValuesF);
774 for (int i=0;i<channelnumber;i++){
775 channelValues[i]=channelValuesF[i];
776 }
777 profile()->linearizeFloatValue(channelValues);
778 qreal y, u, v = 0.0;
779 toYUV(channelValues, &y, &u, &v);
780 u += step;
781 u = qBound(0.0, u, 1.0);
782 channelValues = fromYUV(&y, &u, &v);
783 profile()->delinearizeFloatValue(channelValues);
784 for (int i=0;i<channelnumber;i++){
785 channelValuesF[i]=channelValues[i];
786 }
787 fromNormalisedChannelsValue(pixel, channelValuesF);
788 setOpacity(pixel, 1.0, 1);
789}
790void KoColorSpace::increaseGreen(quint8 * pixel, qreal step) const{
791 int channelnumber = channelCount();
792 QVector <double> channelValues(channelnumber);
793 QVector <float> channelValuesF(channelnumber);
794 normalisedChannelsValue(pixel, channelValuesF);
795 for (int i=0;i<channelnumber;i++){
796 channelValues[i]=channelValuesF[i];
797 }
798 profile()->linearizeFloatValue(channelValues);
799 qreal y, u, v = 0.0;
800 toYUV(channelValues, &y, &u, &v);
801 u -= step;
802 u = qBound(0.0, u, 1.0);
803 channelValues = fromYUV(&y, &u, &v);
804 profile()->delinearizeFloatValue(channelValues);
805 for (int i=0;i<channelnumber;i++){
806 channelValuesF[i]=channelValues[i];
807 }
808 fromNormalisedChannelsValue(pixel, channelValuesF);
809 setOpacity(pixel, 1.0, 1);
810}
811
812void KoColorSpace::increaseBlue(quint8 * pixel, qreal step) const{
813 int channelnumber = channelCount();
814 QVector <double> channelValues(channelnumber);
815 QVector <float> channelValuesF(channelnumber);
816 normalisedChannelsValue(pixel, channelValuesF);
817 for (int i=0;i<channelnumber;i++){
818 channelValues[i]=channelValuesF[i];
819 }
820 profile()->linearizeFloatValue(channelValues);
821 qreal y, u, v = 0.0;
822 toYUV(channelValues, &y, &u, &v);
823 v += step;
824 v = qBound(0.0, v, 1.0);
825 channelValues = fromYUV(&y, &u, &v);
826 profile()->delinearizeFloatValue(channelValues);
827 for (int i=0;i<channelnumber;i++){
828 channelValuesF[i]=channelValues[i];
829 }
830 fromNormalisedChannelsValue(pixel, channelValuesF);
831 setOpacity(pixel, 1.0, 1);
832}
833
834void KoColorSpace::increaseYellow(quint8 * pixel, qreal step) const{
835 int channelnumber = channelCount();
836 QVector <double> channelValues(channelnumber);
837 QVector <float> channelValuesF(channelnumber);
838 normalisedChannelsValue(pixel, channelValuesF);
839 for (int i=0;i<channelnumber;i++){
840 channelValues[i]=channelValuesF[i];
841 }
842 profile()->linearizeFloatValue(channelValues);
843 qreal y, u, v = 0.0;
844 toYUV(channelValues, &y, &u, &v);
845 v -= step;
846 v = qBound(0.0, v, 1.0);
847 channelValues = fromYUV(&y, &u, &v);
848 profile()->delinearizeFloatValue(channelValues);
849 for (int i=0;i<channelnumber;i++){
850 channelValuesF[i]=channelValues[i];
851 }
852 fromNormalisedChannelsValue(pixel, channelValuesF);
853 setOpacity(pixel, 1.0, 1);
854}
855
856QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
857 const KoColorProfile *dstProfile,
859 KoColorConversionTransformation::ConversionFlags conversionFlags) const
860
861{
862 QImage img = QImage(width, height, QImage::Format_ARGB32);
863
864 const KoColorSpace * dstCS = KoColorSpaceRegistry::instance()->rgb8(dstProfile);
865
866 if (data)
867 this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
868
869 return img;
870}
871
873{
874 return false;
875}
876
877void KoColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const
878{
879 fillGrayBrushWithColorAndLightnessWithStrength(dst, brush, brushColor, 1.0, nPixels);
880}
881
882void KoColorSpace::fillGrayBrushWithColorAndLightnessWithStrength(quint8* dst, const QRgb* brush, quint8* brushColor, qreal strength, qint32 nPixels) const
883{
886
887 const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
888 QScopedArrayPointer<quint8> rgbBuffer(new quint8[(nPixels + 1) * rgbPixelSize]);
889 quint8* rgbBrushColorBuffer = rgbBuffer.data() + nPixels * rgbPixelSize;
890
891 // NOTE: dst buffer is not read during the process, so there is
892 // no need to convert that, just pass an uninitialized array
893 this->toRgbA16(brushColor, rgbBrushColorBuffer, 1);
894 fillGrayBrushWithColorPreserveLightnessRGB<KoBgrU16Traits>(rgbBuffer.data(), brush, rgbBrushColorBuffer, strength, nPixels);
895 this->fromRgbA16(rgbBuffer.data(), dst, nPixels);
896}
897
898void KoColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *brush, qreal strength, qint32 nPixels) const
899{
902
903 const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
904 QScopedArrayPointer<quint8> dstBuffer(new quint8[nPixels * rgbPixelSize]);
905
906 this->toRgbA16(dst, dstBuffer.data(), nPixels);
907 modulateLightnessByGrayBrushRGB<KoBgrU16Traits>(dstBuffer.data(), brush, strength, nPixels);
908 this->fromRgbA16(dstBuffer.data(), dst, nPixels);
909}
#define warnPigment
const Params2D p
qreal v
qreal u
QPointF p2
QPointF p1
DitherType
Definition KisDitherOp.h:21
@ DITHER_NONE
Definition KisDitherOp.h:22
@ DITHER_BLUE_NOISE
Definition KisDitherOp.h:27
@ DITHER_BAYER
Definition KisDitherOp.h:26
@ DITHER_FAST
Definition KisDitherOp.h:23
@ DITHER_BEST
Definition KisDitherOp.h:24
const quint8 OPACITY_TRANSPARENT_U8
@ NotOwnedByRegistry
@ OwnedByRegistryDoNotDelete
const QString COMPOSITE_OVER
uint qHash(const KoInputDevice &key)
virtual KoID destinationDepthId() const =0
virtual KoID sourceDepthId() const =0
virtual DitherType type() const =0
const KoColorConversionTransformation * transformation() const
@ ALPHA
The channel represents the opacity of a pixel.
@ COLOR
The channel represents a color.
enumChannelType channelType() const
KoCachedColorConversionTransformation cachedConverter(const KoColorSpace *src, const KoColorSpace *dst, KoColorConversionTransformation::Intent _renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
void colorSpaceIsDestroyed(const KoColorSpace *src)
static KoColorSpaceEngineRegistry * instance()
virtual quint32 alphaPos() const =0
const KoColorConversionTransformation * toLabA16Converter() const
virtual void modulateLightnessByGrayBrush(quint8 *dst, const QRgb *brush, qreal strength, qint32 nPixels) const
virtual void increaseHue(quint8 *pixel, qreal step) const
virtual quint32 pixelSize() const =0
virtual void toRgbA16(const quint8 *src, quint8 *dst, quint32 nPixels) const
virtual void increaseBlue(quint8 *pixel, qreal step) const
virtual void convertChannelToVisualRepresentation(const quint8 *src, quint8 *dst, quint32 nPixels, const qint32 selectedChannelIndex) const =0
virtual QImage convertToQImage(const quint8 *data, qint32 width, qint32 height, const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
virtual void increaseRed(quint8 *pixel, qreal step) const
ThreadLocalCache conversionCache
QPolygonF gamutXYY
const KoColorConversionTransformation * fromRgbA16Converter() const
virtual void addDitherOp(KisDitherOp *op)
KoColorSpace()
Only for use by classes that serve as baseclass for real color spaces.
virtual void decreaseHue(quint8 *pixel, qreal step) const
virtual void fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const
QBitArray channelFlags(bool color=true, bool alpha=false) const
virtual void toHSY(const QVector< double > &channelValues, qreal *hue, qreal *sat, qreal *luma) const =0
KoConvolutionOp * convolutionOp
virtual void increaseSaturation(quint8 *pixel, qreal step) const
virtual void toYUV(const QVector< double > &channelValues, qreal *y, qreal *u, qreal *v) const =0
virtual void addCompositeOp(const KoCompositeOp *op)
virtual bool preferCompositionInSourceColorSpace() const
const KoColorConversionTransformation * fromLabA16Converter() const
virtual void toLabA16(const quint8 *src, quint8 *dst, quint32 nPixels) const
virtual void bitBlt(const KoColorSpace *srcSpace, const KoCompositeOp::ParameterInfo &params, const KoCompositeOp *op, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
virtual ~KoColorSpace()
virtual void increaseGreen(quint8 *pixel, qreal step) const
virtual void decreaseLuminosity(quint8 *pixel, qreal step) const
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
QList< KoChannelInfo * > channels
virtual void increaseLuminosity(quint8 *pixel, qreal step) const
virtual const KisDitherOp * ditherOp(const QString &depth, DitherType type) const
virtual QVector< double > fromYUV(qreal *y, qreal *u, qreal *v) const =0
Private *const d
virtual KoID colorModelId() const =0
QHash< QString, KoCompositeOp * > compositeOps
QVector< qreal > lumaCoefficients
virtual KoColorConversionTransformation * createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, bool bpcFirstTransform, quint8 *gamutWarning, KoColorConversionTransformation::ConversionFlags displayConversionFlags) const
createProofingTransform Create a proofing transform. This is a two part transform that can also do ga...
virtual void fromRgbA16(const quint8 *src, quint8 *dst, quint32 nPixels) const
virtual void fromLabA16(const quint8 *src, quint8 *dst, quint32 nPixels) const
virtual bool proofPixelsTo(const quint8 *src, quint8 *dst, quint32 numPixels, KoColorConversionTransformation *proofingTransform) const
proofPixelsTo
virtual quint32 channelCount() const =0
virtual KoID colorDepthId() const =0
virtual void normalisedChannelsValue(const quint8 *pixel, QVector< float > &channels) const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
virtual bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace *dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
virtual void fillGrayBrushWithColorAndLightnessWithStrength(quint8 *dst, const QRgb *brush, quint8 *brushColor, qreal strength, qint32 nPixels) const
virtual quint32 colorChannelCount() const =0
virtual bool hasCompositeOp(const QString &id, const KoColorSpace *srcSpace=nullptr) const
virtual bool operator==(const KoColorSpace &rhs) const
KoColorTransformation * createColorTransformation(const QString &id, const QHash< QString, QVariant > &parameters) const
const KoCompositeOp * compositeOp(const QString &id, const KoColorSpace *srcSpace=nullptr) const
virtual void addChannel(KoChannelInfo *ci)
KoMixColorsOp * mixColorsOp
virtual QVector< double > fromHSY(qreal *hue, qreal *sat, qreal *luma) const =0
virtual void transparentColor(quint8 *dst, quint32 nPixels) const
virtual const KoColorProfile * profile() const =0
virtual void decreaseSaturation(quint8 *pixel, qreal step) const
virtual KoColorConversionTransformation * createColorConverter(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
virtual void increaseYellow(quint8 *pixel, qreal step) const
QPolygonF estimatedTRCXYY() const
const KoColorConversionTransformation * toRgbA16Converter() const
T get(const QString &id) const
QString id() const
Definition KoID.cpp:63
unsigned int QRgb
constexpr std::enable_if< sizeof...(values)==0, size_t >::type max()
void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override=0
virtual QVector< qreal > getColorantsxyY() const =0
virtual void linearizeFloatValue(QVector< qreal > &Value) const =0
virtual void delinearizeFloatValue(QVector< qreal > &Value) const =0
KoColorConversionCache * colorConversionCache
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
KoColorConversionTransformation * createColorConverter(const KoColorSpace *srcColorSpace, const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
void createColorConverters(const KoColorSpace *colorSpace, const QList< QPair< KoID, KoID > > &possibilities, KoColorConversionTransformation *&fromCS, KoColorConversionTransformation *&toCS) const
static KoColorTransformationFactoryRegistry * instance()
virtual KoColorTransformation * createTransformation(const KoColorSpace *colorSpace, QHash< QString, QVariant > parameters) const =0
virtual QList< QPair< KoID, KoID > > supportedModels() const =0
void composite(quint8 *dstRowStart, qint32 dstRowStride, const quint8 *srcRowStart, qint32 srcRowStride, const quint8 *maskRowStart, qint32 maskRowStride, qint32 rows, qint32 numColumns, float opacity, const QBitArray &channelFlags=QBitArray()) const
const KoColorSpace * colorSpace