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 }