File indexing completed on 2024-05-19 04:27:23

0001 /*
0002  *  SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud@valdyas.org>
0003  * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-or-later
0006  */
0007 
0008 #include "KoColorSpace.h"
0009 #include "KoColorSpace_p.h"
0010 
0011 #include "KoChannelInfo.h"
0012 #include "DebugPigment.h"
0013 #include "KoCompositeOp.h"
0014 #include "KoColorTransformation.h"
0015 #include "KoColorTransformationFactory.h"
0016 #include "KoColorTransformationFactoryRegistry.h"
0017 #include "KoColorConversionCache.h"
0018 #include "KoColorConversionSystem.h"
0019 #include "KoColorSpaceRegistry.h"
0020 #include "KoColorProfile.h"
0021 #include "KoCopyColorConversionTransformation.h"
0022 #include "KoFallBackColorTransformation.h"
0023 #include "KoMixColorsOp.h"
0024 #include "KoConvolutionOp.h"
0025 #include "KoCompositeOpRegistry.h"
0026 #include "KoColorSpaceEngine.h"
0027 #include <KoColorSpaceTraits.h>
0028 #include <KoColorSpacePreserveLightnessUtils.h>
0029 #include "KisDitherOp.h"
0030 
0031 #include <cmath>
0032 
0033 #include <QThreadStorage>
0034 #include <QByteArray>
0035 #include <QBitArray>
0036 #include <QPolygonF>
0037 #include <QPointF>
0038 
0039 
0040 KoColorSpace::KoColorSpace()
0041     : d(new Private())
0042 {
0043 }
0044 
0045 KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp *mixColorsOp, KoConvolutionOp *convolutionOp)
0046     : d(new Private())
0047 {
0048     d->id = id;
0049     d->idNumber = qHash(d->id);
0050     d->name = name;
0051     d->mixColorsOp = mixColorsOp;
0052     d->convolutionOp = convolutionOp;
0053     d->transfoToRGBA16 = 0;
0054     d->transfoFromRGBA16 = 0;
0055     d->transfoToLABA16 = 0;
0056     d->transfoFromLABA16 = 0;
0057     d->gamutXYY = QPolygonF();
0058     d->TRCXYY = QPolygonF();
0059     d->colorants = QVector <qreal> (0);
0060     d->lumaCoefficients = QVector <qreal> (0);
0061     d->iccEngine = 0;
0062     d->deletability = NotOwnedByRegistry;
0063 }
0064 
0065 KoColorSpace::~KoColorSpace()
0066 {
0067     Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);
0068 
0069     Q_FOREACH(const KoCompositeOp *op, d->compositeOps) {
0070         delete op;
0071     }
0072     d->compositeOps.clear();
0073     for (const auto& map: d->ditherOps) {
0074         qDeleteAll(map);
0075     }
0076     d->ditherOps.clear();
0077     Q_FOREACH (KoChannelInfo * channel, d->channels) {
0078         delete channel;
0079     }
0080     d->channels.clear();
0081     if (d->deletability == NotOwnedByRegistry) {
0082         KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache();
0083         if (cache) {
0084             cache->colorSpaceIsDestroyed(this);
0085         }
0086     }
0087     delete d->mixColorsOp;
0088     delete d->convolutionOp;
0089     delete d->transfoToRGBA16;
0090     delete d->transfoFromRGBA16;
0091     delete d->transfoToLABA16;
0092     delete d->transfoFromLABA16;
0093     delete d;
0094 }
0095 
0096 bool KoColorSpace::operator==(const KoColorSpace& rhs) const
0097 {
0098     const KoColorProfile* p1 = rhs.profile();
0099     const KoColorProfile* p2 = profile();
0100     return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2));
0101 }
0102 
0103 QString KoColorSpace::id() const
0104 {
0105     return d->id;
0106 }
0107 
0108 QString KoColorSpace::name() const
0109 {
0110     return d->name;
0111 }
0112 
0113 //Color space info stuff.
0114 QPolygonF KoColorSpace::gamutXYY() const
0115 {
0116     if (d->gamutXYY.empty()) {
0117         //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.
0118         //first make a list of colors.
0119         qreal max = 1.0;
0120         if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
0121             //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
0122             max = this->channels()[0]->getUIMax();
0123 
0124         }
0125         int samples = 5;//amount of samples in our color space.
0126         const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
0127         quint8 *data = new quint8[pixelSize()];
0128         quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel.
0129         //QVector <qreal> sampleCoordinates(pow(colorChannelCount(),samples));
0130         //sampleCoordinates.fill(0.0);
0131 
0132         // This is fixed to 5 since the maximum number of channels are 5 for CMYKA
0133         QVector <float> channelValuesF(5);//for getting the coordinates.
0134 
0135         for(int x=0;x<samples;x++){
0136             if (colorChannelCount()==1) {//gray
0137                 channelValuesF[0]=(max/(samples-1))*(x);
0138                 channelValuesF[1]=max;
0139                 fromNormalisedChannelsValue(data, channelValuesF);
0140                 convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
0141                 xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
0142                 qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0143                 qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0144                 d->gamutXYY << QPointF(x,y);
0145             } else {
0146                 for(int y=0;y<samples;y++){
0147                     for(int z=0;z<samples;z++){
0148                         if (colorChannelCount()==4) {
0149                             for(int k=0;k<samples;k++){
0150                                 channelValuesF[0] = (max / (samples - 1)) * (x);
0151                                 channelValuesF[1] = (max / (samples - 1)) * (y);
0152                                 channelValuesF[2] = (max / (samples - 1)) * (z);
0153                                 channelValuesF[3] = (max / (samples - 1)) * (k);
0154                                 channelValuesF[4] = max;
0155                                 fromNormalisedChannelsValue(data, channelValuesF);
0156                                 convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
0157                                 xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
0158                                 qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
0159                                 qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
0160                                 d->gamutXYY<< QPointF(x,y);
0161                             }
0162                         } else {
0163                             channelValuesF[0]=(max/(samples-1))*(x);
0164                             channelValuesF[1]=(max/(samples-1))*(y);
0165                             channelValuesF[2]=(max/(samples-1))*(z);
0166                             channelValuesF[3]=max;
0167                             if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
0168                                 fromNormalisedChannelsValue(data, channelValuesF);
0169                                 convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric,         KoColorConversionTransformation::adjustmentConversionFlags());
0170                                 xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
0171                             }
0172                             qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0173                             qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0174                             d->gamutXYY<< QPointF(x,y);
0175                         }
0176                     }
0177                 }
0178 
0179             }
0180         }
0181         delete[] data;
0182         //if we ever implement a boundary-checking thing I'd add it here.
0183         return d->gamutXYY;
0184     } else {
0185         return d->gamutXYY;
0186     }
0187 }
0188 
0189 QPolygonF KoColorSpace::estimatedTRCXYY() const
0190 {
0191     if (d->TRCXYY.empty()){
0192         const QList<KoChannelInfo *> channelInfo = this->channels();
0193 
0194         qreal max = 1.0;
0195         if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
0196             //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
0197             max = channelInfo[0]->getUIMax();
0198         }
0199         const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32");
0200         quint8 *data = new quint8[pixelSize()];
0201         quint8 *data2 = new quint8[xyzColorSpace->pixelSize()];
0202 
0203         // This is fixed to 5 since the maximum number of channels are 5 for CMYKA
0204         QVector <float> channelValuesF(5);//for getting the coordinates.
0205 
0206         d->colorants.resize(3*colorChannelCount());
0207 
0208         const int segments = 10;
0209         for (quint32 i=0; i<colorChannelCount(); i++) {
0210             qreal colorantY=1.0;
0211             if (colorModelId().id()!="CMYKA") {
0212                 for (int j = 0; j <= segments; j++) {
0213                     channelValuesF.fill(0.0);
0214                     channelValuesF[channelInfo[i]->displayPosition()] = ((max/segments)*(segments-j));
0215 
0216                     if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
0217                         fromNormalisedChannelsValue(data, channelValuesF);
0218                         convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric,         KoColorConversionTransformation::adjustmentConversionFlags());
0219                         xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
0220                     }
0221                     if (j==0) {
0222                         colorantY = channelValuesF[1];
0223                         d->colorants[3*i]   = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0224                         d->colorants[3*i+1] = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0225                         d->colorants[3*i+2] = channelValuesF[1];
0226                     }
0227                     d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/segments)*(segments-j)));
0228                 }
0229             } else {
0230                 for (int j = 0; j <= segments; j++) {
0231                     channelValuesF.fill(0.0);
0232                     channelValuesF[i] = ((max/segments)*(j));
0233 
0234                     fromNormalisedChannelsValue(data, channelValuesF);
0235 
0236                     convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric,         KoColorConversionTransformation::adjustmentConversionFlags());
0237 
0238                     xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
0239 
0240                     if (j==0) {
0241                         colorantY = channelValuesF[1];
0242                         d->colorants[3*i]   = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0243                         d->colorants[3*i+1] = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
0244                         d->colorants[3*i+2] = channelValuesF[1];
0245                     }
0246                     d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/segments)*(j)));
0247                 }
0248             }
0249         }
0250 
0251         delete[] data;
0252         delete[] data2;
0253         return d->TRCXYY;
0254     } else {
0255         return d->TRCXYY;
0256     }
0257 }
0258 
0259 QVector <qreal> KoColorSpace::lumaCoefficients() const
0260 {
0261     if (d->lumaCoefficients.size()>1){
0262         return d->lumaCoefficients;
0263     } else {
0264         d->lumaCoefficients.resize(3);
0265         if (colorModelId().id()!="RGBA") {
0266             d->lumaCoefficients.fill(0.33);
0267         } else {
0268             if (d->colorants.size() <= 0) {
0269                 if (profile() && profile()->hasColorants()) {
0270                     d->colorants.resize(3 * colorChannelCount());
0271                     d->colorants = profile()->getColorantsxyY();
0272                 }
0273                 else {
0274                     QPolygonF p = estimatedTRCXYY();
0275                     Q_UNUSED(p);
0276                 }
0277             }
0278             if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) {
0279                 d->lumaCoefficients[0]=0.2126;
0280                 d->lumaCoefficients[1]=0.7152;
0281                 d->lumaCoefficients[2]=0.0722;
0282             } else {
0283                 // luma coefficients need to add up to 1.0
0284                 qreal sum = d->colorants[2] + d->colorants[5] + d->colorants[8];
0285                 d->lumaCoefficients[0] = d->colorants[2] / sum;
0286                 d->lumaCoefficients[1] = d->colorants[5] / sum;
0287                 d->lumaCoefficients[2] = d->colorants[8] / sum;
0288             }
0289         }
0290         return d->lumaCoefficients;
0291     }
0292 }
0293 
0294 QList<KoChannelInfo *> KoColorSpace::channels() const
0295 {
0296     return d->channels;
0297 }
0298 
0299 QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
0300 {
0301     QBitArray ba(d->channels.size());
0302     if (!color && !alpha) return ba;
0303 
0304     for (int i = 0; i < d->channels.size(); ++i) {
0305         KoChannelInfo * channel = d->channels.at(i);
0306         if ((color && channel->channelType() == KoChannelInfo::COLOR) ||
0307                 (alpha && channel->channelType() == KoChannelInfo::ALPHA))
0308             ba.setBit(i, true);
0309     }
0310     return ba;
0311 }
0312 
0313 void KoColorSpace::addChannel(KoChannelInfo * ci)
0314 {
0315     d->channels.push_back(ci);
0316 }
0317 bool KoColorSpace::hasCompositeOp(const QString& id, const KoColorSpace *srcSpace) const
0318 {
0319     if (srcSpace && preferCompositionInSourceColorSpace() && srcSpace->hasCompositeOp(id)) {
0320         return true;
0321     }
0322     return d->compositeOps.contains(id);
0323 }
0324 
0325 QList<KoCompositeOp*> KoColorSpace::compositeOps() const
0326 {
0327     return d->compositeOps.values();
0328 }
0329 
0330 KoMixColorsOp* KoColorSpace::mixColorsOp() const
0331 {
0332     return d->mixColorsOp;
0333 }
0334 
0335 const KisDitherOp *KoColorSpace::ditherOp(const QString &depth, DitherType type) const
0336 {
0337     const auto it = d->ditherOps.constFind(depth);
0338     if (it != d->ditherOps.constEnd()) {
0339         switch (type) {
0340         case DITHER_FAST:
0341         case DITHER_BAYER:
0342             return it->constFind(DITHER_BAYER).value();
0343         case DITHER_BEST:
0344         case DITHER_BLUE_NOISE:
0345             return it->constFind(DITHER_BLUE_NOISE).value();
0346         case DITHER_NONE:
0347         default:
0348             return it->constFind(DITHER_NONE).value();
0349         }
0350     } else {
0351         warnPigment << "Asking for dither op from " << colorDepthId() << "to an unsupported depth" << depth << "!";
0352         return nullptr;
0353     }
0354 }
0355 
0356 void KoColorSpace::addDitherOp(KisDitherOp *op)
0357 {
0358     if (op->sourceDepthId() == colorDepthId()) {
0359         if (!d->ditherOps.contains(op->destinationDepthId().id())) {
0360             d->ditherOps.insert(op->destinationDepthId().id(), {{op->type(), op}});
0361         } else {
0362             d->ditherOps[op->destinationDepthId().id()].insert(op->type(), op);
0363         }
0364     }
0365 }
0366 
0367 KoConvolutionOp* KoColorSpace::convolutionOp() const
0368 {
0369     return d->convolutionOp;
0370 }
0371 
0372 const KoCompositeOp * KoColorSpace::compositeOp(const QString & id, const KoColorSpace *srcSpace) const
0373 {
0374     if (srcSpace && preferCompositionInSourceColorSpace()) {
0375         if (const KoCompositeOp *op = srcSpace->compositeOp(id)) {
0376             return op;
0377         }
0378     }
0379     const QHash<QString, KoCompositeOp*>::ConstIterator it = d->compositeOps.constFind(id);
0380     if (it != d->compositeOps.constEnd()) {
0381         return it.value();
0382     }
0383     else {
0384         warnPigment << "Asking for nonexistent composite operation " << id << ", returning " << COMPOSITE_OVER;
0385         return d->compositeOps.value(COMPOSITE_OVER);
0386     }
0387 }
0388 
0389 void KoColorSpace::addCompositeOp(const KoCompositeOp * op)
0390 {
0391     if (op->colorSpace()->id() == id()) {
0392         d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
0393     }
0394 }
0395 
0396 void KoColorSpace::transparentColor(quint8 *dst, quint32 nPixels) const
0397 {
0398     memset(dst, 0, pixelSize() * nPixels);
0399     setOpacity(dst, OPACITY_TRANSPARENT_U8, nPixels);
0400 }
0401 
0402 const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const
0403 {
0404     if (!d->transfoToLABA16) {
0405         d->transfoToLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
0406     }
0407     return d->transfoToLABA16;
0408 }
0409 
0410 const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const
0411 {
0412     if (!d->transfoFromLABA16) {
0413         d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
0414     }
0415     return d->transfoFromLABA16;
0416 }
0417 const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const
0418 {
0419     if (!d->transfoToRGBA16) {
0420         d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
0421     }
0422     return d->transfoToRGBA16;
0423 }
0424 const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const
0425 {
0426     if (!d->transfoFromRGBA16) {
0427         d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16() , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
0428     }
0429     return d->transfoFromRGBA16;
0430 }
0431 
0432 void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
0433 {
0434     toLabA16Converter()->transform(src, dst, nPixels);
0435 }
0436 
0437 void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
0438 {
0439     fromLabA16Converter()->transform(src, dst, nPixels);
0440 }
0441 
0442 void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
0443 {
0444     toRgbA16Converter()->transform(src, dst, nPixels);
0445 }
0446 
0447 void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
0448 {
0449     fromRgbA16Converter()->transform(src, dst, nPixels);
0450 }
0451 
0452 KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
0453 {
0454     if (*this == *dstColorSpace) {
0455         return new KoCopyColorConversionTransformation(this);
0456     } else {
0457         return KoColorSpaceRegistry::instance()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
0458     }
0459 }
0460 
0461 bool KoColorSpace::convertPixelsTo(const quint8 * src,
0462                                    quint8 * dst,
0463                                    const KoColorSpace * dstColorSpace,
0464                                    quint32 numPixels,
0465                                    KoColorConversionTransformation::Intent renderingIntent,
0466                                    KoColorConversionTransformation::ConversionFlags conversionFlags) const
0467 {
0468     if (*this == *dstColorSpace) {
0469         if (src != dst) {
0470             memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
0471         }
0472     } else {
0473         KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
0474         cct.transformation()->transform(src, dst, numPixels);
0475     }
0476     return true;
0477 }
0478 
0479 KoColorConversionTransformation * KoColorSpace::createProofingTransform(const KoColorSpace *dstColorSpace, const KoColorSpace *proofingSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::Intent proofingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, quint8 *gamutWarning, double adaptationState) const
0480 {
0481     if (!d->iccEngine) {
0482         d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
0483     }
0484     if (!d->iccEngine) return 0;
0485 
0486     return d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState);
0487 }
0488 
0489 bool KoColorSpace::proofPixelsTo(const quint8 *src,
0490                                  quint8 *dst,
0491                                  quint32 numPixels,
0492                                  KoColorConversionTransformation *proofingTransform) const
0493 {
0494     proofingTransform->transform(src, dst, numPixels);
0495 
0496     //the transform is deleted in the destructor.
0497     return true;
0498 }
0499 
0500 void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
0501                           KoColorConversionTransformation::Intent renderingIntent,
0502                           KoColorConversionTransformation::ConversionFlags conversionFlags) const
0503 {
0504     Q_ASSERT_X(*op->colorSpace() == *this || (preferCompositionInSourceColorSpace() && *op->colorSpace() == *srcSpace),
0505                "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());
0506 
0507     if(params.rows <= 0 || params.cols <= 0)
0508         return;
0509 
0510     if(!(*this == *srcSpace)) {
0511         if (preferCompositionInSourceColorSpace() &&
0512                 (*op->colorSpace() == *srcSpace || srcSpace->hasCompositeOp(op->id()))) {
0513 
0514             quint32           conversionDstBufferStride = params.cols * srcSpace->pixelSize();
0515             QVector<quint8> * conversionDstCache        = d->conversionCache.get(params.rows * conversionDstBufferStride);
0516             quint8*           conversionDstData         = conversionDstCache->data();
0517 
0518             for(qint32 row=0; row<params.rows; row++) {
0519                 convertPixelsTo(params.dstRowStart + row * params.dstRowStride,
0520                                 conversionDstData  + row * conversionDstBufferStride, srcSpace, params.cols,
0521                                 renderingIntent, conversionFlags);
0522             }
0523 
0524             // TODO: Composite op substitution should eventually be removed here, but it's not urgent.
0525             //       Code should just provide srcSpace to KoColorSpace::compositeOp() to avoid the lookups.
0526             const KoCompositeOp *otherOp = (*op->colorSpace() == *srcSpace) ? op : srcSpace->compositeOp(op->id());
0527 
0528             KoCompositeOp::ParameterInfo paramInfo(params);
0529             paramInfo.dstRowStart  = conversionDstData;
0530             paramInfo.dstRowStride = conversionDstBufferStride;
0531             otherOp->composite(paramInfo);
0532 
0533             for(qint32 row=0; row<params.rows; row++) {
0534                 srcSpace->convertPixelsTo(conversionDstData  + row * conversionDstBufferStride,
0535                                           params.dstRowStart + row * params.dstRowStride, this, params.cols,
0536                                           renderingIntent, conversionFlags);
0537             }
0538 
0539         } else {
0540             quint32           conversionBufferStride = params.cols * pixelSize();
0541             QVector<quint8> * conversionCache        = d->conversionCache.get(params.rows * conversionBufferStride);
0542             quint8*           conversionData         = conversionCache->data();
0543 
0544 
0545             const bool noChannelFlags = params.channelFlags.isEmpty() ||
0546                     params.channelFlags == srcSpace->channelFlags(true, true);
0547 
0548             if (noChannelFlags) {
0549                 for(qint32 row=0; row<params.rows; row++) {
0550                     srcSpace->convertPixelsTo(params.srcRowStart + row * params.srcRowStride,
0551                                               conversionData     + row * conversionBufferStride, this, params.cols,
0552                                               renderingIntent, conversionFlags);
0553                 }
0554 
0555                 KoCompositeOp::ParameterInfo paramInfo(params);
0556                 paramInfo.srcRowStart  = conversionData;
0557                 paramInfo.srcRowStride = conversionBufferStride;
0558                 paramInfo.channelFlags = QBitArray();
0559                 op->composite(paramInfo);
0560             } else {
0561                 quint32           homogenizationBufferStride = params.cols * srcSpace->pixelSize();
0562                 QVector<quint8> * homogenizationCache        = d->channelFlagsApplicationCache.get(homogenizationBufferStride);
0563                 quint8*           homogenizationData         = homogenizationCache->data();
0564 
0565                 for(qint32 row=0; row<params.rows; row++) {
0566                     srcSpace->convertChannelToVisualRepresentation(params.srcRowStart + row * params.srcRowStride,
0567                                                                    homogenizationData, params.cols,
0568                                                                    params.channelFlags | srcSpace->channelFlags(false, true));
0569                     srcSpace->convertPixelsTo(homogenizationData,
0570                                               conversionData + row * conversionBufferStride, this, params.cols,
0571                                               renderingIntent, conversionFlags);
0572                 }
0573 
0574 
0575                 KoCompositeOp::ParameterInfo paramInfo(params);
0576                 paramInfo.srcRowStart  = conversionData;
0577                 paramInfo.srcRowStride = conversionBufferStride;
0578                 paramInfo.channelFlags = channelFlags(true, params.channelFlags.testBit(srcSpace->alphaPos()));
0579                 op->composite(paramInfo);
0580             }
0581 
0582 
0583         }
0584     }
0585     else {
0586         op->composite(params);
0587     }
0588 }
0589 
0590 KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
0591 {
0592     KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id);
0593     if (!factory) return 0;
0594     QPair<KoID, KoID> model(colorModelId(), colorDepthId());
0595     QList< QPair<KoID, KoID> > models = factory->supportedModels();
0596     if (models.isEmpty() || models.contains(model)) {
0597         return factory->createTransformation(this, parameters);
0598     } else {
0599         // Find the best solution
0600         // TODO use the color conversion cache
0601         KoColorConversionTransformation* csToFallBack = 0;
0602         KoColorConversionTransformation* fallBackToCs = 0;
0603         KoColorSpaceRegistry::instance()->createColorConverters(this, models, csToFallBack, fallBackToCs);
0604         Q_ASSERT(csToFallBack);
0605         Q_ASSERT(fallBackToCs);
0606         KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters);
0607         return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
0608     }
0609 }
0610 
0611 void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{
0612     int channelnumber = channelCount();
0613     QVector <double> channelValues(channelnumber);
0614     QVector <float> channelValuesF(channelnumber);
0615     normalisedChannelsValue(pixel, channelValuesF);
0616     for (int i=0;i<channelnumber;i++){
0617         channelValues[i]=channelValuesF[i];
0618     }
0619     if (profile()->hasTRC()){
0620         //only linearise and crunch the luma if there's a TRC
0621         profile()->linearizeFloatValue(channelValues);
0622         qreal hue, sat, luma = 0.0;
0623         toHSY(channelValues, &hue, &sat, &luma);
0624         luma = pow(luma, 1/2.2);
0625         luma = qMin(1.0, luma + step);
0626         luma = pow(luma, 2.2);
0627         channelValues = fromHSY(&hue, &sat, &luma);
0628         profile()->delinearizeFloatValue(channelValues);
0629     } else {
0630         qreal hue, sat, luma = 0.0;
0631         toHSY(channelValues, &hue, &sat, &luma);
0632         luma = qMin(1.0, luma + step);
0633         channelValues = fromHSY(&hue, &sat, &luma);
0634     }
0635     for (int i=0;i<channelnumber;i++){
0636         channelValuesF[i]=channelValues[i];
0637     }
0638     fromNormalisedChannelsValue(pixel, channelValuesF);
0639     setOpacity(pixel, 1.0, 1);
0640 }
0641 void KoColorSpace::decreaseLuminosity(quint8 * pixel, qreal step) const {
0642     int channelnumber = channelCount();
0643     QVector <double> channelValues(channelnumber);
0644     QVector <float> channelValuesF(channelnumber);
0645     normalisedChannelsValue(pixel, channelValuesF);
0646     for (int i=0;i<channelnumber;i++){
0647         channelValues[i]=channelValuesF[i];
0648     }
0649     if (profile()->hasTRC()){
0650         //only linearise and crunch the luma if there's a TRC
0651         profile()->linearizeFloatValue(channelValues);
0652         qreal hue, sat, luma = 0.0;
0653         toHSY(channelValues, &hue, &sat, &luma);
0654         luma = pow(luma, 1/2.2);
0655         if (luma-step<0.0) {
0656             luma=0.0;
0657         } else {
0658             luma -= step;
0659         }
0660         luma = pow(luma, 2.2);
0661         channelValues = fromHSY(&hue, &sat, &luma);
0662         profile()->delinearizeFloatValue(channelValues);
0663     } else {
0664         qreal hue, sat, luma = 0.0;
0665         toHSY(channelValues, &hue, &sat, &luma);
0666         if (luma-step<0.0) {
0667             luma=0.0;
0668         } else {
0669             luma -= step;
0670         }
0671         channelValues = fromHSY(&hue, &sat, &luma);
0672     }
0673     for (int i=0;i<channelnumber;i++){
0674         channelValuesF[i]=channelValues[i];
0675     }
0676     fromNormalisedChannelsValue(pixel, channelValuesF);
0677     setOpacity(pixel, 1.0, 1);
0678 }
0679 void KoColorSpace::increaseSaturation(quint8 * pixel, qreal step) const{
0680     int channelnumber = channelCount();
0681     QVector <double> channelValues(channelnumber);
0682     QVector <float> channelValuesF(channelnumber);
0683     normalisedChannelsValue(pixel, channelValuesF);
0684     for (int i=0;i<channelnumber;i++){
0685         channelValues[i]=channelValuesF[i];
0686     }
0687     profile()->linearizeFloatValue(channelValues);
0688     qreal hue, sat, luma = 0.0;
0689     toHSY(channelValues, &hue, &sat, &luma);
0690     sat += step;
0691     sat = qBound(0.0, sat, 1.0);
0692     channelValues = fromHSY(&hue, &sat, &luma);
0693     profile()->delinearizeFloatValue(channelValues);
0694     for (int i=0;i<channelnumber;i++){
0695         channelValuesF[i]=channelValues[i];
0696     }
0697     fromNormalisedChannelsValue(pixel, channelValuesF);
0698     setOpacity(pixel, 1.0, 1);
0699 }
0700 void KoColorSpace::decreaseSaturation(quint8 * pixel, qreal step) const{
0701     int channelnumber = channelCount();
0702     QVector <double> channelValues(channelnumber);
0703     QVector <float> channelValuesF(channelnumber);
0704     normalisedChannelsValue(pixel, channelValuesF);
0705     for (int i=0;i<channelnumber;i++){
0706         channelValues[i]=channelValuesF[i];
0707     }
0708     profile()->linearizeFloatValue(channelValues);
0709     qreal hue, sat, luma = 0.0;
0710     toHSY(channelValues, &hue, &sat, &luma);
0711     sat -= step;
0712     sat = qBound(0.0, sat, 1.0);
0713     channelValues = fromHSY(&hue, &sat, &luma);
0714     profile()->delinearizeFloatValue(channelValues);
0715     for (int i=0;i<channelnumber;i++){
0716         channelValuesF[i]=channelValues[i];
0717     }
0718     fromNormalisedChannelsValue(pixel, channelValuesF);
0719     setOpacity(pixel, 1.0, 1);
0720 }
0721 void KoColorSpace::increaseHue(quint8 * pixel, qreal step) const{
0722     int channelnumber = channelCount(); //doesn't work for cmyka...
0723     QVector <double> channelValues(channelnumber);
0724     QVector <float> channelValuesF(channelnumber);
0725     normalisedChannelsValue(pixel, channelValuesF);
0726     for (int i=0;i<channelnumber;i++){
0727         channelValues[i]=channelValuesF[i];
0728     }
0729     profile()->linearizeFloatValue(channelValues);
0730     qreal hue, sat, luma = 0.0;
0731     toHSY(channelValues, &hue, &sat, &luma);
0732     if (hue+step>1.0){
0733         hue=(hue+step)- 1.0;
0734     } else {
0735         hue += step;
0736     }
0737     channelValues = fromHSY(&hue, &sat, &luma);
0738     profile()->delinearizeFloatValue(channelValues);
0739     for (int i=0;i<channelnumber;i++){
0740         channelValuesF[i]=channelValues[i];
0741     }
0742     fromNormalisedChannelsValue(pixel, channelValuesF);
0743     setOpacity(pixel, 1.0, 1);
0744 }
0745 void KoColorSpace::decreaseHue(quint8 * pixel, qreal step) const{
0746     int channelnumber = channelCount();
0747     QVector <double> channelValues(channelnumber);
0748     QVector <float> channelValuesF(channelnumber);
0749     normalisedChannelsValue(pixel, channelValuesF);
0750     for (int i=0;i<channelnumber;i++){
0751         channelValues[i]=channelValuesF[i];
0752     }
0753     profile()->linearizeFloatValue(channelValues);
0754     qreal hue, sat, luma = 0.0;
0755     toHSY(channelValues, &hue, &sat, &luma);
0756     if (hue-step<0.0){
0757         hue=1.0-(step-hue);
0758     } else {
0759         hue -= step;
0760     }
0761     channelValues = fromHSY(&hue, &sat, &luma);
0762     profile()->delinearizeFloatValue(channelValues);
0763     for (int i=0;i<channelnumber;i++){
0764         channelValuesF[i]=channelValues[i];
0765     }
0766     fromNormalisedChannelsValue(pixel, channelValuesF);
0767     setOpacity(pixel, 1.0, 1);
0768 }
0769 
0770 void KoColorSpace::increaseRed(quint8 * pixel, qreal step) const{
0771     int channelnumber = channelCount();
0772     QVector <double> channelValues(channelnumber);
0773     QVector <float> channelValuesF(channelnumber);
0774     normalisedChannelsValue(pixel, channelValuesF);
0775     for (int i=0;i<channelnumber;i++){
0776         channelValues[i]=channelValuesF[i];
0777     }
0778     profile()->linearizeFloatValue(channelValues);
0779     qreal y, u, v = 0.0;
0780     toYUV(channelValues, &y, &u, &v);
0781     u += step;
0782     u = qBound(0.0, u, 1.0);
0783     channelValues = fromYUV(&y, &u, &v);
0784     profile()->delinearizeFloatValue(channelValues);
0785     for (int i=0;i<channelnumber;i++){
0786         channelValuesF[i]=channelValues[i];
0787     }
0788     fromNormalisedChannelsValue(pixel, channelValuesF);
0789     setOpacity(pixel, 1.0, 1);
0790 }
0791 void KoColorSpace::increaseGreen(quint8 * pixel, qreal step) const{
0792     int channelnumber = channelCount();
0793     QVector <double> channelValues(channelnumber);
0794     QVector <float> channelValuesF(channelnumber);
0795     normalisedChannelsValue(pixel, channelValuesF);
0796     for (int i=0;i<channelnumber;i++){
0797         channelValues[i]=channelValuesF[i];
0798     }
0799     profile()->linearizeFloatValue(channelValues);
0800     qreal y, u, v = 0.0;
0801     toYUV(channelValues, &y, &u, &v);
0802     u -= step;
0803     u = qBound(0.0, u, 1.0);
0804     channelValues = fromYUV(&y, &u, &v);
0805     profile()->delinearizeFloatValue(channelValues);
0806     for (int i=0;i<channelnumber;i++){
0807         channelValuesF[i]=channelValues[i];
0808     }
0809     fromNormalisedChannelsValue(pixel, channelValuesF);
0810     setOpacity(pixel, 1.0, 1);
0811 }
0812 
0813 void KoColorSpace::increaseBlue(quint8 * pixel, qreal step) const{
0814     int channelnumber = channelCount();
0815     QVector <double> channelValues(channelnumber);
0816     QVector <float> channelValuesF(channelnumber);
0817     normalisedChannelsValue(pixel, channelValuesF);
0818     for (int i=0;i<channelnumber;i++){
0819         channelValues[i]=channelValuesF[i];
0820     }
0821     profile()->linearizeFloatValue(channelValues);
0822     qreal y, u, v = 0.0;
0823     toYUV(channelValues, &y, &u, &v);
0824     v += step;
0825     v = qBound(0.0, v, 1.0);
0826     channelValues = fromYUV(&y, &u, &v);
0827     profile()->delinearizeFloatValue(channelValues);
0828     for (int i=0;i<channelnumber;i++){
0829         channelValuesF[i]=channelValues[i];
0830     }
0831     fromNormalisedChannelsValue(pixel, channelValuesF);
0832     setOpacity(pixel, 1.0, 1);
0833 }
0834 
0835 void KoColorSpace::increaseYellow(quint8 * pixel, qreal step) const{
0836     int channelnumber = channelCount();
0837     QVector <double> channelValues(channelnumber);
0838     QVector <float> channelValuesF(channelnumber);
0839     normalisedChannelsValue(pixel, channelValuesF);
0840     for (int i=0;i<channelnumber;i++){
0841         channelValues[i]=channelValuesF[i];
0842     }
0843     profile()->linearizeFloatValue(channelValues);
0844     qreal y, u, v = 0.0;
0845     toYUV(channelValues, &y, &u, &v);
0846     v -= step;
0847     v = qBound(0.0, v, 1.0);
0848     channelValues = fromYUV(&y, &u, &v);
0849     profile()->delinearizeFloatValue(channelValues);
0850     for (int i=0;i<channelnumber;i++){
0851         channelValuesF[i]=channelValues[i];
0852     }
0853     fromNormalisedChannelsValue(pixel, channelValuesF);
0854     setOpacity(pixel, 1.0, 1);
0855 }
0856 
0857 QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
0858                                      const KoColorProfile *dstProfile,
0859                                      KoColorConversionTransformation::Intent renderingIntent,
0860                                      KoColorConversionTransformation::ConversionFlags conversionFlags) const
0861 
0862 {
0863     QImage img = QImage(width, height, QImage::Format_ARGB32);
0864 
0865     const KoColorSpace * dstCS = KoColorSpaceRegistry::instance()->rgb8(dstProfile);
0866 
0867     if (data)
0868         this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
0869 
0870     return img;
0871 }
0872 
0873 bool KoColorSpace::preferCompositionInSourceColorSpace() const
0874 {
0875     return false;
0876 }
0877 
0878 void KoColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst, const QRgb *brush, quint8 *brushColor, qint32 nPixels) const
0879 {
0880     fillGrayBrushWithColorAndLightnessWithStrength(dst, brush, brushColor, 1.0, nPixels);
0881 }
0882 
0883 void KoColorSpace::fillGrayBrushWithColorAndLightnessWithStrength(quint8* dst, const QRgb* brush, quint8* brushColor, qreal strength, qint32 nPixels) const
0884 {
0885     /// Fallback implementation. All RGB color spaces have their own
0886     /// implementation without any conversions.
0887 
0888     const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
0889     QScopedArrayPointer<quint8> rgbBuffer(new quint8[(nPixels + 1) * rgbPixelSize]);
0890     quint8* rgbBrushColorBuffer = rgbBuffer.data() + nPixels * rgbPixelSize;
0891 
0892     // NOTE: dst buffer is not read during the process, so there is
0893     //       no need to convert that, just pass an uninitialized array
0894     this->toRgbA16(brushColor, rgbBrushColorBuffer, 1);
0895     fillGrayBrushWithColorPreserveLightnessRGB<KoBgrU16Traits>(rgbBuffer.data(), brush, rgbBrushColorBuffer, strength, nPixels);
0896     this->fromRgbA16(rgbBuffer.data(), dst, nPixels);
0897 }
0898 
0899 void KoColorSpace::modulateLightnessByGrayBrush(quint8 *dst, const QRgb *brush, qreal strength, qint32 nPixels) const
0900 {
0901     /// Fallback implementation. All RGB color spaces have their own
0902     /// implementation without any conversions.
0903 
0904     const int rgbPixelSize = sizeof(KoBgrU16Traits::Pixel);
0905     QScopedArrayPointer<quint8> dstBuffer(new quint8[nPixels * rgbPixelSize]);
0906 
0907     this->toRgbA16(dst, dstBuffer.data(), nPixels);
0908     modulateLightnessByGrayBrushRGB<KoBgrU16Traits>(dstBuffer.data(), brush, strength, nPixels);
0909     this->fromRgbA16(dstBuffer.data(), dst, nPixels);
0910 }