File indexing completed on 2024-05-12 15:59:33

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