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 <KoColorimetryUtils::xyY> (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
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[i].x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
223 d->colorants[i].y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
224 d->colorants[i].Y = 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[i].x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
242 d->colorants[i].y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
243 d->colorants[i].Y = 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 = profile()->getColorantsxyY();
270 }
271 else {
272 QPolygonF p = estimatedTRCXYY();
273 Q_UNUSED(p);
274 }
275 }
276 if (d->colorants.size() < 3 || d->colorants[0].Y<0 || d->colorants[1].Y<0 || d->colorants[2].Y<0) {
277 d->lumaCoefficients[0]=0.2126;
278 d->lumaCoefficients[1]=0.7152;
279 d->lumaCoefficients[2]=0.0722;
280 } else {
281 // luma coefficients need to add up to 1.0
282 qreal sum = d->colorants[0].Y + d->colorants[1].Y + d->colorants[2].Y;
283 d->lumaCoefficients[0] = d->colorants[0].Y / sum;
284 d->lumaCoefficients[1] = d->colorants[1].Y / sum;
285 d->lumaCoefficients[2] = d->colorants[2].Y / sum;
286 }
287 }
288 return d->lumaCoefficients;
289 }
290}
291
293{
294 return d->channels;
295}
296
297QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
298{
299 QBitArray ba(d->channels.size());
300 if (!color && !alpha) return ba;
301
302 for (int i = 0; i < d->channels.size(); ++i) {
303 KoChannelInfo * channel = d->channels.at(i);
304 if ((color && channel->channelType() == KoChannelInfo::COLOR) ||
305 (alpha && channel->channelType() == KoChannelInfo::ALPHA))
306 ba.setBit(i, true);
307 }
308 return ba;
309}
310
312{
313 d->channels.push_back(ci);
314}
315bool KoColorSpace::hasCompositeOp(const QString& id, const KoColorSpace *srcSpace) const
316{
317 if (srcSpace && preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(id)) {
318 return true;
319 }
320 return d->compositeOps.contains(id);
321}
322
324{
325 return d->compositeOps.values();
326}
327
329{
330 return d->mixColorsOp;
331}
332
333const KisDitherOp *KoColorSpace::ditherOp(const QString &depth, DitherType type) const
334{
335 const auto it = d->ditherOps.constFind(depth);
336 if (it != d->ditherOps.constEnd()) {
337 switch (type) {
338 case DITHER_FAST:
339 case DITHER_BAYER:
340 return it->constFind(DITHER_BAYER).value();
341 case DITHER_BEST:
343 return it->constFind(DITHER_BLUE_NOISE).value();
344 case DITHER_NONE:
345 default:
346 return it->constFind(DITHER_NONE).value();
347 }
348 } else {
349 warnPigment << "Asking for dither op from " << colorDepthId() << "to an unsupported depth" << depth << "!";
350 return nullptr;
351 }
352}
353
355{
356 if (op->sourceDepthId() == colorDepthId()) {
357 if (!d->ditherOps.contains(op->destinationDepthId().id())) {
358 d->ditherOps.insert(op->destinationDepthId().id(), {{op->type(), op}});
359 } else {
360 d->ditherOps[op->destinationDepthId().id()].insert(op->type(), op);
361 }
362 }
363}
364
366{
367 return d->convolutionOp;
368}
369
370const KoCompositeOp * KoColorSpace::compositeOp(const QString & id, const KoColorSpace *srcSpace) const
371{
372 if (srcSpace && preferCompositionInSourceColorSpace()) {
373 if (const KoCompositeOp *op = srcSpace->compositeOp(id)) {
374 return op;
375 }
376 }
377 const QHash<QString, KoCompositeOp*>::ConstIterator it = d->compositeOps.constFind(id);
378 if (it != d->compositeOps.constEnd()) {
379 return it.value();
380 }
381 else {
382 warnPigment << "Asking for nonexistent composite operation " << id << ", returning " << COMPOSITE_OVER;
383 return d->compositeOps.value(COMPOSITE_OVER);
384 }
385}
386
388{
389 if (op->colorSpace()->id() == id()) {
390 d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
391 }
392}
393
394void KoColorSpace::transparentColor(quint8 *dst, quint32 nPixels) const
395{
396 memset(dst, 0, pixelSize() * nPixels);
397 setOpacity(dst, OPACITY_TRANSPARENT_U8, nPixels);
398}
399
407
429
430void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
431{
432 toLabA16Converter()->transform(src, dst, nPixels);
433}
434
435void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
436{
437 fromLabA16Converter()->transform(src, dst, nPixels);
438}
439
440void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
441{
442 toRgbA16Converter()->transform(src, dst, nPixels);
443}
444
445void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
446{
447 fromRgbA16Converter()->transform(src, dst, nPixels);
448}
449
450KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
451{
452 if (*this == *dstColorSpace) {
454 } else {
455 return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
456 }
457}
458
459bool KoColorSpace::convertPixelsTo(const quint8 * src,
460 quint8 * dst,
461 const KoColorSpace * dstColorSpace,
462 quint32 numPixels,
464 KoColorConversionTransformation::ConversionFlags conversionFlags) const
465{
466 if (*this == *dstColorSpace) {
467 if (src != dst) {
468 memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
469 }
470 } else {
471 KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
472 cct.transformation()->transform(src, dst, numPixels);
473 }
474 return true;
475}
476
477KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, bool bpcFirstTransform, quint8 *gamutWarning, KoColorConversionTransformation::ConversionFlags displayConversionFlags) const
478{
479 if (!d->iccEngine) {
480 d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
481 }
482 if (!d->iccEngine) return 0;
483
484 return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, bpcFirstTransform, gamutWarning, displayConversionFlags);
485}
486
487bool KoColorSpace::proofPixelsTo(const quint8 *src,
488 quint8 *dst,
489 quint32 numPixels,
490 KoColorConversionTransformation *proofingTransform) const
491{
492 proofingTransform->transform(src, dst, numPixels);
493
494 //the transform is deleted in the destructor.
495 return true;
496}
497
500 KoColorConversionTransformation::ConversionFlags conversionFlags) const
501{
502 Q_ASSERT_X(*op->colorSpace() == *this || (preferCompositionInSourceColorSpace() && *op->colorSpace() == *srcSpace),
503 "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());
504
505 if(params.rows <= 0 || params.cols <= 0)
506 return;
507
508 if(!(*this == *srcSpace)) {
510 (*op->colorSpace() == *srcSpace || srcSpace->hasCompositeOp(op->id()))) {
511
512 quint32 conversionDstBufferStride = params.cols * srcSpace->pixelSize();
513 QVector<quint8> * conversionDstCache = d->conversionCache.get(params.rows * conversionDstBufferStride);
514 quint8* conversionDstData = conversionDstCache->data();
515
516 for(qint32 row=0; row<params.rows; row++) {
517 convertPixelsTo(params.dstRowStart + row * params.dstRowStride,
518 conversionDstData + row * conversionDstBufferStride, srcSpace, params.cols,
519 renderingIntent, conversionFlags);
520 }
521
522 // TODO: Composite op substitution should eventually be removed here, but it's not urgent.
523 // Code should just provide srcSpace to KoColorSpace::compositeOp() to avoid the lookups.
524 const KoCompositeOp *otherOp = (*op->colorSpace() == *srcSpace) ? op : srcSpace->compositeOp(op->id());
525
526 KoCompositeOp::ParameterInfo paramInfo(params);
527 paramInfo.dstRowStart = conversionDstData;
528 paramInfo.dstRowStride = conversionDstBufferStride;
529 otherOp->composite(paramInfo);
530
531 for(qint32 row=0; row<params.rows; row++) {
532 srcSpace->convertPixelsTo(conversionDstData + row * conversionDstBufferStride,
533 params.dstRowStart + row * params.dstRowStride, this, params.cols,
534 renderingIntent, conversionFlags);
535 }
536
537 } else {
538 quint32 conversionBufferStride = params.cols * pixelSize();
539 QVector<quint8> * conversionCache = d->conversionCache.get(params.rows * conversionBufferStride);
540 quint8* conversionData = conversionCache->data();
541
542
543 const bool noChannelFlags = params.channelFlags.isEmpty() ||
544 params.channelFlags == srcSpace->channelFlags(true, true);
545
546 if (noChannelFlags) {
547 for(qint32 row=0; row<params.rows; row++) {
548 srcSpace->convertPixelsTo(params.srcRowStart + row * params.srcRowStride,
549 conversionData + row * conversionBufferStride, this, params.cols,
550 renderingIntent, conversionFlags);
551 }
552
553 KoCompositeOp::ParameterInfo paramInfo(params);
554 paramInfo.srcRowStart = conversionData;
555 paramInfo.srcRowStride = conversionBufferStride;
556 paramInfo.channelFlags = QBitArray();
557 op->composite(paramInfo);
558 } else {
559 quint32 homogenizationBufferStride = params.cols * srcSpace->pixelSize();
560 QVector<quint8> * homogenizationCache = d->channelFlagsApplicationCache.get(homogenizationBufferStride);
561 quint8* homogenizationData = homogenizationCache->data();
562
563 for(qint32 row=0; row<params.rows; row++) {
564 srcSpace->convertChannelToVisualRepresentation(params.srcRowStart + row * params.srcRowStride,
565 homogenizationData, params.cols,
566 params.channelFlags | srcSpace->channelFlags(false, true));
567 srcSpace->convertPixelsTo(homogenizationData,
568 conversionData + row * conversionBufferStride, this, params.cols,
569 renderingIntent, conversionFlags);
570 }
571
572
573 KoCompositeOp::ParameterInfo paramInfo(params);
574 paramInfo.srcRowStart = conversionData;
575 paramInfo.srcRowStride = conversionBufferStride;
576 paramInfo.channelFlags = channelFlags(true, params.channelFlags.testBit(srcSpace->alphaPos()));
577 op->composite(paramInfo);
578 }
579
580
581 }
582 }
583 else {
584 op->composite(params);
585 }
586}
587
588KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
589{
591 if (!factory) return 0;
592 QPair<KoID, KoID> model(colorModelId(), colorDepthId());
593 QList< QPair<KoID, KoID> > models = factory->supportedModels();
594 if (models.isEmpty() || models.contains(model)) {
595 return factory->createTransformation(this, parameters);
596 } else {
597 // Find the best solution
598 // TODO use the color conversion cache
599 KoColorConversionTransformation* csToFallBack = 0;
600 KoColorConversionTransformation* fallBackToCs = 0;
601 KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs);
602 Q_ASSERT(csToFallBack);
603 Q_ASSERT(fallBackToCs);
604 KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters);
605 return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
606 }
607}
608
609void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{
610 int channelnumber = channelCount();
611 QVector <double> channelValues(channelnumber);
612 QVector <float> channelValuesF(channelnumber);
613 normalisedChannelsValue(pixel, channelValuesF);
614 for (int i=0;i<channelnumber;i++){
615 channelValues[i]=channelValuesF[i];
616 }
617 if (profile()->hasTRC()){
618 //only linearise and crunch the luma if there's a TRC
619 profile()->linearizeFloatValue(channelValues);
620 qreal hue, sat, luma = 0.0;
621 toHSY(channelValues, &hue, &sat, &luma);
622 luma = pow(luma, 1/2.2);
623 luma = qMin(1.0, luma + step);
624 luma = pow(luma, 2.2);
625 channelValues = fromHSY(&hue, &sat, &luma);
626 profile()->delinearizeFloatValue(channelValues);
627 } else {
628 qreal hue, sat, luma = 0.0;
629 toHSY(channelValues, &hue, &sat, &luma);
630 luma = qMin(1.0, luma + step);
631 channelValues = fromHSY(&hue, &sat, &luma);
632 }
633 for (int i=0;i<channelnumber;i++){
634 channelValuesF[i]=channelValues[i];
635 }
636 fromNormalisedChannelsValue(pixel, channelValuesF);
637 setOpacity(pixel, 1.0, 1);
638}
639void KoColorSpace::decreaseLuminosity(quint8 * pixel, qreal step) const {
640 int channelnumber = channelCount();
641 QVector <double> channelValues(channelnumber);
642 QVector <float> channelValuesF(channelnumber);
643 normalisedChannelsValue(pixel, channelValuesF);
644 for (int i=0;i<channelnumber;i++){
645 channelValues[i]=channelValuesF[i];
646 }
647 if (profile()->hasTRC()){
648 //only linearise and crunch the luma if there's a TRC
649 profile()->linearizeFloatValue(channelValues);
650 qreal hue, sat, luma = 0.0;
651 toHSY(channelValues, &hue, &sat, &luma);
652 luma = pow(luma, 1/2.2);
653 if (luma-step<0.0) {
654 luma=0.0;
655 } else {
656 luma -= step;
657 }
658 luma = pow(luma, 2.2);
659 channelValues = fromHSY(&hue, &sat, &luma);
660 profile()->delinearizeFloatValue(channelValues);
661 } else {
662 qreal hue, sat, luma = 0.0;
663 toHSY(channelValues, &hue, &sat, &luma);
664 if (luma-step<0.0) {
665 luma=0.0;
666 } else {
667 luma -= step;
668 }
669 channelValues = fromHSY(&hue, &sat, &luma);
670 }
671 for (int i=0;i<channelnumber;i++){
672 channelValuesF[i]=channelValues[i];
673 }
674 fromNormalisedChannelsValue(pixel, channelValuesF);
675 setOpacity(pixel, 1.0, 1);
676}
677void KoColorSpace::increaseSaturation(quint8 * pixel, qreal step) const{
678 int channelnumber = channelCount();
679 QVector <double> channelValues(channelnumber);
680 QVector <float> channelValuesF(channelnumber);
681 normalisedChannelsValue(pixel, channelValuesF);
682 for (int i=0;i<channelnumber;i++){
683 channelValues[i]=channelValuesF[i];
684 }
685 profile()->linearizeFloatValue(channelValues);
686 qreal hue, sat, luma = 0.0;
687 toHSY(channelValues, &hue, &sat, &luma);
688 sat += step;
689 sat = qBound(0.0, sat, 1.0);
690 channelValues = fromHSY(&hue, &sat, &luma);
691 profile()->delinearizeFloatValue(channelValues);
692 for (int i=0;i<channelnumber;i++){
693 channelValuesF[i]=channelValues[i];
694 }
695 fromNormalisedChannelsValue(pixel, channelValuesF);
696 setOpacity(pixel, 1.0, 1);
697}
698void KoColorSpace::decreaseSaturation(quint8 * pixel, qreal step) const{
699 int channelnumber = channelCount();
700 QVector <double> channelValues(channelnumber);
701 QVector <float> channelValuesF(channelnumber);
702 normalisedChannelsValue(pixel, channelValuesF);
703 for (int i=0;i<channelnumber;i++){
704 channelValues[i]=channelValuesF[i];
705 }
706 profile()->linearizeFloatValue(channelValues);
707 qreal hue, sat, luma = 0.0;
708 toHSY(channelValues, &hue, &sat, &luma);
709 sat -= step;
710 sat = qBound(0.0, sat, 1.0);
711 channelValues = fromHSY(&hue, &sat, &luma);
712 profile()->delinearizeFloatValue(channelValues);
713 for (int i=0;i<channelnumber;i++){
714 channelValuesF[i]=channelValues[i];
715 }
716 fromNormalisedChannelsValue(pixel, channelValuesF);
717 setOpacity(pixel, 1.0, 1);
718}
719void KoColorSpace::increaseHue(quint8 * pixel, qreal step) const{
720 int channelnumber = channelCount(); //doesn't work for cmyka...
721 QVector <double> channelValues(channelnumber);
722 QVector <float> channelValuesF(channelnumber);
723 normalisedChannelsValue(pixel, channelValuesF);
724 for (int i=0;i<channelnumber;i++){
725 channelValues[i]=channelValuesF[i];
726 }
727 profile()->linearizeFloatValue(channelValues);
728 qreal hue, sat, luma = 0.0;
729 toHSY(channelValues, &hue, &sat, &luma);
730 if (hue+step>1.0){
731 hue=(hue+step)- 1.0;
732 } else {
733 hue += step;
734 }
735 channelValues = fromHSY(&hue, &sat, &luma);
736 profile()->delinearizeFloatValue(channelValues);
737 for (int i=0;i<channelnumber;i++){
738 channelValuesF[i]=channelValues[i];
739 }
740 fromNormalisedChannelsValue(pixel, channelValuesF);
741 setOpacity(pixel, 1.0, 1);
742}
743void KoColorSpace::decreaseHue(quint8 * pixel, qreal step) const{
744 int channelnumber = channelCount();
745 QVector <double> channelValues(channelnumber);
746 QVector <float> channelValuesF(channelnumber);
747 normalisedChannelsValue(pixel, channelValuesF);
748 for (int i=0;i<channelnumber;i++){
749 channelValues[i]=channelValuesF[i];
750 }
751 profile()->linearizeFloatValue(channelValues);
752 qreal hue, sat, luma = 0.0;
753 toHSY(channelValues, &hue, &sat, &luma);
754 if (hue-step<0.0){
755 hue=1.0-(step-hue);
756 } else {
757 hue -= step;
758 }
759 channelValues = fromHSY(&hue, &sat, &luma);
760 profile()->delinearizeFloatValue(channelValues);
761 for (int i=0;i<channelnumber;i++){
762 channelValuesF[i]=channelValues[i];
763 }
764 fromNormalisedChannelsValue(pixel, channelValuesF);
765 setOpacity(pixel, 1.0, 1);
766}
767
768void KoColorSpace::increaseRed(quint8 * pixel, qreal step) const{
769 int channelnumber = channelCount();
770 QVector <double> channelValues(channelnumber);
771 QVector <float> channelValuesF(channelnumber);
772 normalisedChannelsValue(pixel, channelValuesF);
773 for (int i=0;i<channelnumber;i++){
774 channelValues[i]=channelValuesF[i];
775 }
776 profile()->linearizeFloatValue(channelValues);
777 qreal y, u, v = 0.0;
778 toYUV(channelValues, &y, &u, &v);
779 u += step;
780 u = qBound(0.0, u, 1.0);
781 channelValues = fromYUV(&y, &u, &v);
782 profile()->delinearizeFloatValue(channelValues);
783 for (int i=0;i<channelnumber;i++){
784 channelValuesF[i]=channelValues[i];
785 }
786 fromNormalisedChannelsValue(pixel, channelValuesF);
787 setOpacity(pixel, 1.0, 1);
788}
789void KoColorSpace::increaseGreen(quint8 * pixel, qreal step) const{
790 int channelnumber = channelCount();
791 QVector <double> channelValues(channelnumber);
792 QVector <float> channelValuesF(channelnumber);
793 normalisedChannelsValue(pixel, channelValuesF);
794 for (int i=0;i<channelnumber;i++){
795 channelValues[i]=channelValuesF[i];
796 }
797 profile()->linearizeFloatValue(channelValues);
798 qreal y, u, v = 0.0;
799 toYUV(channelValues, &y, &u, &v);
800 u -= step;
801 u = qBound(0.0, u, 1.0);
802 channelValues = fromYUV(&y, &u, &v);
803 profile()->delinearizeFloatValue(channelValues);
804 for (int i=0;i<channelnumber;i++){
805 channelValuesF[i]=channelValues[i];
806 }
807 fromNormalisedChannelsValue(pixel, channelValuesF);
808 setOpacity(pixel, 1.0, 1);
809}
810
811void KoColorSpace::increaseBlue(quint8 * pixel, qreal step) const{
812 int channelnumber = channelCount();
813 QVector <double> channelValues(channelnumber);
814 QVector <float> channelValuesF(channelnumber);
815 normalisedChannelsValue(pixel, channelValuesF);
816 for (int i=0;i<channelnumber;i++){
817 channelValues[i]=channelValuesF[i];
818 }
819 profile()->linearizeFloatValue(channelValues);
820 qreal y, u, v = 0.0;
821 toYUV(channelValues, &y, &u, &v);
822 v += step;
823 v = qBound(0.0, v, 1.0);
824 channelValues = fromYUV(&y, &u, &v);
825 profile()->delinearizeFloatValue(channelValues);
826 for (int i=0;i<channelnumber;i++){
827 channelValuesF[i]=channelValues[i];
828 }
829 fromNormalisedChannelsValue(pixel, channelValuesF);
830 setOpacity(pixel, 1.0, 1);
831}
832
833void KoColorSpace::increaseYellow(quint8 * pixel, qreal step) const{
834 int channelnumber = channelCount();
835 QVector <double> channelValues(channelnumber);
836 QVector <float> channelValuesF(channelnumber);
837 normalisedChannelsValue(pixel, channelValuesF);
838 for (int i=0;i<channelnumber;i++){
839 channelValues[i]=channelValuesF[i];
840 }
841 profile()->linearizeFloatValue(channelValues);
842 qreal y, u, v = 0.0;
843 toYUV(channelValues, &y, &u, &v);
844 v -= step;
845 v = qBound(0.0, v, 1.0);
846 channelValues = fromYUV(&y, &u, &v);
847 profile()->delinearizeFloatValue(channelValues);
848 for (int i=0;i<channelnumber;i++){
849 channelValuesF[i]=channelValues[i];
850 }
851 fromNormalisedChannelsValue(pixel, channelValuesF);
852 setOpacity(pixel, 1.0, 1);
853}
854
855QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
856 const KoColorProfile *dstProfile,
858 KoColorConversionTransformation::ConversionFlags conversionFlags) const
859
860{
861 QImage img = QImage(width, height, QImage::Format_ARGB32);
862
863 const KoColorSpace * dstCS = KoColorSpaceRegistry::instance()->rgb8(dstProfile);
864
865 if (data)
866 this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
867
868 return img;
869}
870
872{
873 return false;
874}
875
876void KoColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const
877{
878 fillGrayBrushWithColorAndLightnessWithStrength(dst, brush, brushColor, 1.0, nPixels);
879}
880
881void KoColorSpace::fillGrayBrushWithColorAndLightnessWithStrength(quint8* dst, const QRgb* brush, quint8* brushColor, qreal strength, qint32 nPixels) const
882{
885
886 const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
887 QScopedArrayPointer<quint8> rgbBuffer(new quint8[(nPixels + 1) * rgbPixelSize]);
888 quint8* rgbBrushColorBuffer = rgbBuffer.data() + nPixels * rgbPixelSize;
889
890 // NOTE: dst buffer is not read during the process, so there is
891 // no need to convert that, just pass an uninitialized array
892 this->toRgbA16(brushColor, rgbBrushColorBuffer, 1);
893 fillGrayBrushWithColorPreserveLightnessRGB<KoBgrU16Traits>(rgbBuffer.data(), brush, rgbBrushColorBuffer, strength, nPixels);
894 this->fromRgbA16(rgbBuffer.data(), dst, nPixels);
895}
896
897void KoColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *brush, qreal strength, qint32 nPixels) const
898{
901
902 const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
903 QScopedArrayPointer<quint8> dstBuffer(new quint8[nPixels * rgbPixelSize]);
904
905 this->toRgbA16(dst, dstBuffer.data(), nPixels);
906 modulateLightnessByGrayBrushRGB<KoBgrU16Traits>(dstBuffer.data(), brush, strength, nPixels);
907 this->fromRgbA16(dstBuffer.data(), dst, nPixels);
908}
#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 void linearizeFloatValue(QVector< qreal > &Value) const =0
virtual void delinearizeFloatValue(QVector< qreal > &Value) const =0
virtual QVector< KoColorimetryUtils::xyY > getColorantsxyY() 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