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 }