KoColorSpace.cpp 32.9 KB
Newer Older
1 2 3
/*
 *  Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9 10 11
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15 16 17 18
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/
19 20

#include "KoColorSpace.h"
21
#include "KoColorSpace_p.h"
22

23
#include "KoChannelInfo.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
24
#include "DebugPigment.h"
25
#include "KoCompositeOp.h"
Cyrille Berger's avatar
Cyrille Berger committed
26
#include "KoColorTransformation.h"
27 28
#include "KoColorTransformationFactory.h"
#include "KoColorTransformationFactoryRegistry.h"
29
#include "KoColorConversionCache.h"
30 31
#include "KoColorConversionSystem.h"
#include "KoColorSpaceRegistry.h"
32
#include "KoColorProfile.h"
33
#include "KoCopyColorConversionTransformation.h"
34
#include "KoFallBackColorTransformation.h"
35
#include "KoUniqueNumberForIdServer.h"
36
#include "KoMixColorsOp.h"
37
#include "KoConvolutionOp.h"
38
#include "KoCompositeOpRegistry.h"
39
#include "KoColorSpaceEngine.h"
40

41 42 43
#include <QThreadStorage>
#include <QByteArray>
#include <QBitArray>
44 45
#include <QPolygonF>
#include <QPointF>
46

Boudewijn Rempt's avatar
Boudewijn Rempt committed
47
#include <math.h>
48

Stefan Nikolaus's avatar
Stefan Nikolaus committed
49
KoColorSpace::KoColorSpace()
50
        : d(new Private())
Stefan Nikolaus's avatar
Stefan Nikolaus committed
51 52 53
{
}

54 55
KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp)
        : d(new Private())
56
{
57
    d->id = id;
58
    d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id);
59 60 61
    d->name = name;
    d->mixColorsOp = mixColorsOp;
    d->convolutionOp = convolutionOp;
62 63 64 65
    d->transfoToRGBA16 = 0;
    d->transfoFromRGBA16 = 0;
    d->transfoToLABA16 = 0;
    d->transfoFromLABA16 = 0;
66 67 68 69
    d->gamutXYY = QPolygonF();
    d->TRCXYY = QPolygonF();
    d->colorants = QVector <qreal> (0);
    d->lumaCoefficients = QVector <qreal> (0);
70
    d->iccEngine = 0;
71
    d->deletability = NotOwnedByRegistry;
72 73
}

74
KoColorSpace::~KoColorSpace()
75
{
76 77
    Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);

78
    qDeleteAll(d->compositeOps);
79
    Q_FOREACH (KoChannelInfo * channel, d->channels) {
Cyrille Berger's avatar
Cyrille Berger committed
80 81
        delete channel;
    }
82 83 84 85 86
    if (d->deletability == NotOwnedByRegistry) {
        KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache();
        if (cache) {
            cache->colorSpaceIsDestroyed(this);
        }
87
    }
88 89
    delete d->mixColorsOp;
    delete d->convolutionOp;
Cyrille Berger's avatar
Cyrille Berger committed
90 91 92 93
    delete d->transfoToRGBA16;
    delete d->transfoFromRGBA16;
    delete d->transfoToLABA16;
    delete d->transfoFromLABA16;
94 95 96
    delete d;
}

97 98
bool KoColorSpace::operator==(const KoColorSpace& rhs) const
{
99 100
    const KoColorProfile* p1 = rhs.profile();
    const KoColorProfile* p2 = profile();
101
    return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2));
102 103
}

104 105 106 107
QString KoColorSpace::id() const
{
    return d->id;
}
108

109 110 111 112
QString KoColorSpace::name() const
{
    return d->name;
}
113

114 115 116 117 118 119 120 121 122 123
//Color space info stuff.
QPolygonF KoColorSpace::gamutXYY() const
{
    if (d->gamutXYY.empty()) {
        //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.
        //first make a list of colors.
        qreal max = 1.0;
        if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
            //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
            max = this->channels()[0]->getUIMax();
124

125 126
        }
        int samples = 5;//amount of samples in our color space.
127 128
        QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF32")->defaultProfile();
        const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F32", name);
129
        quint8 *data = new quint8[pixelSize()];
130
        quint8 data2[16]; // xyza f32 is 4 floats, that is 16 bytes per pixel.
131 132
        //QVector <qreal> sampleCoordinates(pow(colorChannelCount(),samples));
        //sampleCoordinates.fill(0.0);
133 134 135 136

        // This is fixed to 5 since the maximum number of channels are 5 for CMYKA
        QVector <float> channelValuesF(5);//for getting the coordinates.

137 138 139 140 141 142
        for(int x=0;x<samples;x++){
            if (colorChannelCount()==1) {//gray
                channelValuesF[0]=(max/(samples-1))*(x);
                channelValuesF[1]=max;
                fromNormalisedChannelsValue(data, channelValuesF);
                convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
143
                xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
144 145
                qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
146
                d->gamutXYY << QPointF(x,y);
147 148 149 150 151
            } else {
                for(int y=0;y<samples;y++){
                    for(int z=0;z<samples;z++){
                        if (colorChannelCount()==4) {
                            for(int k=0;k<samples;k++){
152 153 154 155 156
                                channelValuesF[0] = (max / (samples - 1)) * (x);
                                channelValuesF[1] = (max / (samples - 1)) * (y);
                                channelValuesF[2] = (max / (samples - 1)) * (z);
                                channelValuesF[3] = (max / (samples - 1)) * (k);
                                channelValuesF[4] = max;
157 158
                                fromNormalisedChannelsValue(data, channelValuesF);
                                convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::adjustmentConversionFlags());
159 160 161
                                xyzColorSpace->normalisedChannelsValue(data2, channelValuesF);
                                qreal x = channelValuesF[0] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
                                qreal y = channelValuesF[1] / (channelValuesF[0] + channelValuesF[1] + channelValuesF[2]);
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
                                d->gamutXYY<< QPointF(x,y);
                            }
                        } else {
                            channelValuesF[0]=(max/(samples-1))*(x);
                            channelValuesF[1]=(max/(samples-1))*(y);
                            channelValuesF[2]=(max/(samples-1))*(z);
                            channelValuesF[3]=max;
                            if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
                                fromNormalisedChannelsValue(data, channelValuesF);
                                convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric,         KoColorConversionTransformation::adjustmentConversionFlags());
                                xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
                            }
                            qreal x = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                            qreal y = channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                            d->gamutXYY<< QPointF(x,y);
                        }
                    }
                }
180

181 182
            }
        }
183
        delete[] data;
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
        //if we ever implement a boundary-checking thing I'd add it here.
        return d->gamutXYY;
    } else {
        return d->gamutXYY;
    }
}

QPolygonF KoColorSpace::estimatedTRCXYY() const
{
    if (d->TRCXYY.empty()){
        qreal max = 1.0;
        if ((colorModelId().id()=="CMYKA" || colorModelId().id()=="LABA") && colorDepthId().id()=="F32") {
            //boundaries for cmyka/laba have trouble getting the max values for Float, and are pretty awkward in general.
            max = this->channels()[0]->getUIMax();
        }
        QString name = KoColorSpaceRegistry::instance()->colorSpaceFactory("XYZAF16")->defaultProfile();
        const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "F16", name);
201 202 203 204 205 206
        quint8 *data = new quint8[pixelSize()];
        quint8 data2[8]; // xyza is 8 bytes per pixel.

        // This is fixed to 5 since the maximum number of channels are 5 for CMYKA
        QVector <float> channelValuesF(5);//for getting the coordinates.

207
        for (quint32 i=0; i<colorChannelCount(); i++) {
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
            qreal colorantY=1.0;
            if (colorModelId().id()!="CMYKA") {
                for (int j=5; j>0; j--){
                    channelValuesF.fill(0.0);
                    channelValuesF[i] = ((max/4)*(5-j));

                    if (colorModelId().id()!="XYZA") { //no need for conversion when using xyz.
                        fromNormalisedChannelsValue(data, channelValuesF);
                        convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric,         KoColorConversionTransformation::adjustmentConversionFlags());
                        xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);
                    }

                    if (j==0) {
                        colorantY = channelValuesF[1];
                        if (d->colorants.size()<2){
                            d->colorants.resize(3*colorChannelCount());
                            d->colorants[i]  = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                            d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                            d->colorants[i+2]= channelValuesF[1];
                        }
                    }
                    d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(5-j)));
                }
            } else {
                for (int j=0; j<5; j++){
                    channelValuesF.fill(0.0);
                    channelValuesF[i] = ((max/4)*(j));
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
                    fromNormalisedChannelsValue(data, channelValuesF);

                    convertPixelsTo(data, data2, xyzColorSpace, 1, KoColorConversionTransformation::IntentAbsoluteColorimetric,         KoColorConversionTransformation::adjustmentConversionFlags());

                    xyzColorSpace->normalisedChannelsValue(data2,channelValuesF);

                    if (j==0) {
                        colorantY = channelValuesF[1];
                        if (d->colorants.size()<2){
                            d->colorants.resize(3*colorChannelCount());
                            d->colorants[i]  = channelValuesF[0]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                            d->colorants[i+1]= channelValuesF[1]/(channelValuesF[0]+channelValuesF[1]+channelValuesF[2]);
                            d->colorants[i+2]= channelValuesF[1];
                        }
                    }
                    d->TRCXYY << QPointF(channelValuesF[1]/colorantY, ((1.0/4)*(j)));
                }
            }
        }
255 256

        delete[] data;
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
        return d->TRCXYY;
    } else {
        return d->TRCXYY;
    }
}

QVector <qreal> KoColorSpace::colorants() const
{
    if (d->colorants.size()>1){
        return d->colorants;
    } else if (profile() && profile()->hasColorants()) {
        d->colorants.resize(3*colorChannelCount());
        d->colorants = profile()->getColorantsxyY();
        return d->colorants;
    } else {
        estimatedTRCXYY();
        return d->colorants;
    }
}
QVector <qreal> KoColorSpace::lumaCoefficients() const
{
    if (d->lumaCoefficients.size()>1){
        return d->lumaCoefficients;
    } else {
        d->lumaCoefficients.resize(3);
        if (colorModelId().id()!="RGBA") {
            d->lumaCoefficients.fill(0.33);
        } else {
            colorants();
            if (d->colorants[2]<0 || d->colorants[5]<0 || d->colorants[8]<0) {
                d->lumaCoefficients[0]=0.2126;
                d->lumaCoefficients[1]=0.7152;
                d->lumaCoefficients[2]=0.0722;
            } else {
                d->lumaCoefficients[0]=d->colorants[2];
                d->lumaCoefficients[1]=d->colorants[5];
                d->lumaCoefficients[2]=d->colorants[8];
            }
        }
        return d->lumaCoefficients;
    }
}

Adrian Page's avatar
q3--  
Adrian Page committed
300
QList<KoChannelInfo *> KoColorSpace::channels() const
301 302 303 304
{
    return d->channels;
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
305
QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
306
{
307
    QBitArray ba(d->channels.size());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
308
    if (!color && !alpha) return ba;
309 310 311 312

    for (int i = 0; i < d->channels.size(); ++i) {
        KoChannelInfo * channel = d->channels.at(i);
        if ((color && channel->channelType() == KoChannelInfo::COLOR) ||
Boudewijn Rempt's avatar
Boudewijn Rempt committed
313
                (alpha && channel->channelType() == KoChannelInfo::ALPHA))
314
            ba.setBit(i, true);
315 316 317 318
    }
    return ba;
}

319 320 321
void KoColorSpace::addChannel(KoChannelInfo * ci)
{
    d->channels.push_back(ci);
322
}
323 324 325 326 327
bool KoColorSpace::hasCompositeOp(const QString& id) const
{
    return d->compositeOps.contains(id);
}

328
QList<KoCompositeOp*> KoColorSpace::compositeOps() const
329
{
330 331 332
    return d->compositeOps.values();
}

333 334
KoMixColorsOp* KoColorSpace::mixColorsOp() const
{
335 336 337 338
    return d->mixColorsOp;
}


339 340
KoConvolutionOp* KoColorSpace::convolutionOp() const
{
341
    return d->convolutionOp;
342 343
}

Cyrille Berger's avatar
Cyrille Berger committed
344
const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const
345
{
346 347 348 349
    const QHash<QString, KoCompositeOp*>::ConstIterator it = d->compositeOps.constFind(id);
    if (it != d->compositeOps.constEnd()) {
        return it.value();
    }
350
    else {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
351
        warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER;
352
        return d->compositeOps.value(COMPOSITE_OVER);
353
    }
354 355 356 357
}

void KoColorSpace::addCompositeOp(const KoCompositeOp * op)
{
358 359
    if (op->colorSpace()->id() == id()) {
        d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
360 361 362
    }
}

363
const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const
364
{
365
    if (!d->transfoToLABA16) {
366
        d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
367
    }
368
    return d->transfoToLABA16;
369
}
370

371
const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const
372
{
373
    if (!d->transfoFromLABA16) {
374
        d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
375
    }
376
    return d->transfoFromLABA16;
377
}
378
const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const
379
{
380
    if (!d->transfoToRGBA16) {
381
        d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
382
    }
383
    return d->transfoToRGBA16;
384
}
385
const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const
386
{
387
    if (!d->transfoFromRGBA16) {
388
        d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) ;
389
    }
390 391 392 393 394
    return d->transfoFromRGBA16;
}

void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
395
    toLabA16Converter()->transform(src, dst, nPixels);
396 397 398 399
}

void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
400
    fromLabA16Converter()->transform(src, dst, nPixels);
401 402 403 404
}

void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
405
    toRgbA16Converter()->transform(src, dst, nPixels);
406 407 408 409
}

void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
410
    fromRgbA16Converter()->transform(src, dst, nPixels);
411 412
}

413
KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
414
{
415
    if (*this == *dstColorSpace) {
416
        return new KoCopyColorConversionTransformation(this);
417
    } else {
418
        return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
419
    }
420 421
}

422
bool KoColorSpace::convertPixelsTo(const quint8 * src,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
423 424 425
                                   quint8 * dst,
                                   const KoColorSpace * dstColorSpace,
                                   quint32 numPixels,
426
                                   KoColorConversionTransformation::Intent renderingIntent,
427
                                   KoColorConversionTransformation::ConversionFlags conversionFlags) const
428
{
429
    if (*this == *dstColorSpace) {
430 431 432
        if (src != dst) {
            memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
        }
433
    } else {
434
        KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
435 436
        cct.transformation()->transform(src, dst, numPixels);
    }
437 438
    return true;
}
439 440 441 442 443
bool KoColorSpace::proofPixelsTo(const quint8 * src,
                                   quint8 * dst,
                                   const KoColorSpace * dstColorSpace,
                                   const KoColorSpace * proofingSpace,
                                   quint32 numPixels,
444 445
                                   KoColorConversionTransformation::Intent renderingIntent,
                                   KoColorConversionTransformation::ConversionFlags conversionFlags) const
446
{
447
    /*if (*this == *dstColorSpace) {
448 449 450
        if (src != dst) {
            memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
        }
451
        return true;
452
    }*/
453
    // Only the icc engine can do this kind of stuff
454
    //Can we cache this, maybe???
455 456 457 458 459 460
    if (d->iccEngine) {
        d->iccEngine = KoColorSpaceEngineRegistry::instance()->get("icc");
        //qDebug() << ">>>>>>>>>>>>>>>>>>>> we got a proofing engine";
    }
    if (!d->iccEngine) return false;
    KoColorConversionTransformation *transform = d->iccEngine->createColorProofingTransformation(this, dstColorSpace, proofingSpace, renderingIntent, conversionFlags);
461 462 463 464

    Q_UNUSED(transform);

    delete transform;
465 466
    return true;
}
467

Boudewijn Rempt's avatar
Boudewijn Rempt committed
468 469 470
void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
                          KoColorConversionTransformation::Intent renderingIntent,
                          KoColorConversionTransformation::ConversionFlags conversionFlags) const
471 472
{
    Q_ASSERT_X(*op->colorSpace() == *this, "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());
473

474 475
    if(params.rows <= 0 || params.cols <= 0)
        return;
476

477
    if(!(*this == *srcSpace)) {
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
         if (preferCompositionInSourceColorSpace() &&
             srcSpace->hasCompositeOp(op->id())) {

             quint32           conversionDstBufferStride = params.cols * srcSpace->pixelSize();
             QVector<quint8> * conversionDstCache        = threadLocalConversionCache(params.rows * conversionDstBufferStride);
             quint8*           conversionDstData         = conversionDstCache->data();

             for(qint32 row=0; row<params.rows; row++) {
                 convertPixelsTo(params.dstRowStart + row * params.dstRowStride,
                                 conversionDstData  + row * conversionDstBufferStride, srcSpace, params.cols,
                                 renderingIntent, conversionFlags);
             }

             // FIXME: do not calculate the otherOp every time
             const KoCompositeOp *otherOp = srcSpace->compositeOp(op->id());

             KoCompositeOp::ParameterInfo paramInfo(params);
             paramInfo.dstRowStart  = conversionDstData;
             paramInfo.dstRowStride = conversionDstBufferStride;
             otherOp->composite(paramInfo);

             for(qint32 row=0; row<params.rows; row++) {
                 srcSpace->convertPixelsTo(conversionDstData  + row * conversionDstBufferStride,
                                           params.dstRowStart + row * params.dstRowStride, this, params.cols,
                                           renderingIntent, conversionFlags);
             }

        } else {
            quint32           conversionBufferStride = params.cols * pixelSize();
            QVector<quint8> * conversionCache        = threadLocalConversionCache(params.rows * conversionBufferStride);
            quint8*           conversionData         = conversionCache->data();

            for(qint32 row=0; row<params.rows; row++) {
                srcSpace->convertPixelsTo(params.srcRowStart + row * params.srcRowStride,
                                          conversionData     + row * conversionBufferStride, this, params.cols,
                                          renderingIntent, conversionFlags);
            }

            KoCompositeOp::ParameterInfo paramInfo(params);
            paramInfo.srcRowStart  = conversionData;
            paramInfo.srcRowStride = conversionBufferStride;
            op->composite(paramInfo);
520 521
        }
    }
522 523 524
    else {
        op->composite(params);
    }
525 526 527
}


528 529 530
QVector<quint8> * KoColorSpace::threadLocalConversionCache(quint32 size) const
{
    QVector<quint8> * ba = 0;
531 532 533 534
    if (!d->conversionCache.hasLocalData()) {
        ba = new QVector<quint8>(size, '0');
        d->conversionCache.setLocalData(ba);
    } else {
535
        ba = d->conversionCache.localData();
536 537
        if ((quint8)ba->size() < size)
            ba->resize(size);
538 539 540
    }
    return ba;
}
541

542
KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
543
{
544 545 546
    KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id);
    if (!factory) return 0;
    QPair<KoID, KoID> model(colorModelId(), colorDepthId());
547
    QList< QPair<KoID, KoID> > models = factory->supportedModels();
548 549
    if (models.isEmpty() || models.contains(model)) {
        return factory->createTransformation(this, parameters);
550 551
    } else {
        // Find the best solution
552
        // TODO use the color conversion cache
553 554 555 556 557 558
        KoColorConversionTransformation* csToFallBack = 0;
        KoColorConversionTransformation* fallBackToCs = 0;
        KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverters(this, models, csToFallBack, fallBackToCs);
        Q_ASSERT(csToFallBack);
        Q_ASSERT(fallBackToCs);
        KoColorTransformation* transfo = factory->createTransformation(fallBackToCs->srcColorSpace(), parameters);
559
        return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
560 561
    }
}
562

563
void KoColorSpace::increaseLuminosity(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
564
    int channelnumber = channelCount();
565 566 567 568 569 570 571 572 573 574 575 576
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    if (profile()->hasTRC()){
        //only linearise and crunch the luma if there's a TRC
        profile()->linearizeFloatValue(channelValues);
        qreal hue, sat, luma = 0.0;
        toHSY(channelValues, &hue, &sat, &luma);
        luma = pow(luma, 1/2.2);
577
        luma = qMin(1.0, luma + step);
578 579 580 581 582 583
        luma = pow(luma, 2.2);
        channelValues = fromHSY(&hue, &sat, &luma);
    profile()->delinearizeFloatValue(channelValues);
    } else {
        qreal hue, sat, luma = 0.0;
        toHSY(channelValues, &hue, &sat, &luma);
584
        luma = qMin(1.0, luma + step);
585 586 587 588 589 590 591 592 593
        channelValues = fromHSY(&hue, &sat, &luma);
    }
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::decreaseLuminosity(quint8 * pixel, qreal step) const {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
594
    int channelnumber = channelCount();
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    if (profile()->hasTRC()){
        //only linearise and crunch the luma if there's a TRC
        profile()->linearizeFloatValue(channelValues);
        qreal hue, sat, luma = 0.0;
        toHSY(channelValues, &hue, &sat, &luma);
        luma = pow(luma, 1/2.2);
        if (luma-step<0.0) {
            luma=0.0;
        } else {
            luma -= step;
        }
        luma = pow(luma, 2.2);
        channelValues = fromHSY(&hue, &sat, &luma);
614
        profile()->delinearizeFloatValue(channelValues);
615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
    } else {
        qreal hue, sat, luma = 0.0;
        toHSY(channelValues, &hue, &sat, &luma);
        if (luma-step<0.0) {
            luma=0.0;
        } else {
            luma -= step;
        }
        channelValues = fromHSY(&hue, &sat, &luma);
    }
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseSaturation(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
632
    int channelnumber = channelCount();
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal hue, sat, luma = 0.0;
    toHSY(channelValues, &hue, &sat, &luma);
    sat += step;
    sat = qBound(0.0, sat, 1.0);
    channelValues = fromHSY(&hue, &sat, &luma);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::decreaseSaturation(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
653
    int channelnumber = channelCount();
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal hue, sat, luma = 0.0;
    toHSY(channelValues, &hue, &sat, &luma);
    sat -= step;
    sat = qBound(0.0, sat, 1.0);
    channelValues = fromHSY(&hue, &sat, &luma);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseHue(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
674
    int channelnumber = channelCount(); //doesn't work for cmyka...
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal hue, sat, luma = 0.0;
    toHSY(channelValues, &hue, &sat, &luma);
    if (hue+step>1.0){
        hue=(hue+step)- 1.0;
    } else {
        hue += step;
    }
    channelValues = fromHSY(&hue, &sat, &luma);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::decreaseHue(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
698
    int channelnumber = channelCount();
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal hue, sat, luma = 0.0;
    toHSY(channelValues, &hue, &sat, &luma);
    if (hue-step<0.0){
        hue=1.0-(step-hue);
    } else {
        hue -= step;
    }
    channelValues = fromHSY(&hue, &sat, &luma);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}

void KoColorSpace::increaseRed(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
723
    int channelnumber = channelCount();
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal y, u, v = 0.0;
    toYUV(channelValues, &y, &u, &v);
    u += step;
    u = qBound(0.0, u, 1.0);
    channelValues = fromYUV(&y, &u, &v);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseGreen(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
744
    int channelnumber = channelCount();
745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal y, u, v = 0.0;
    toYUV(channelValues, &y, &u, &v);
    u -= step;
    u = qBound(0.0, u, 1.0);
    channelValues = fromYUV(&y, &u, &v);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseBlue(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
765
    int channelnumber = channelCount();
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal y, u, v = 0.0;
    toYUV(channelValues, &y, &u, &v);
    v += step;
    v = qBound(0.0, v, 1.0);
    channelValues = fromYUV(&y, &u, &v);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
void KoColorSpace::increaseYellow(quint8 * pixel, qreal step) const{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
786
    int channelnumber = channelCount();
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805
    QVector <double> channelValues(channelnumber);
    QVector <float> channelValuesF(channelnumber);
    normalisedChannelsValue(pixel, channelValuesF);
    for (int i=0;i<channelnumber;i++){
        channelValues[i]=channelValuesF[i];
    }
    profile()->linearizeFloatValue(channelValues);
    qreal y, u, v = 0.0;
    toYUV(channelValues, &y, &u, &v);
    v -= step;
    v = qBound(0.0, v, 1.0);
    channelValues = fromYUV(&y, &u, &v);
    profile()->delinearizeFloatValue(channelValues);
    for (int i=0;i<channelnumber;i++){
        channelValuesF[i]=channelValues[i];
    }
    fromNormalisedChannelsValue(pixel, channelValuesF);
    setOpacity(pixel, 1.0, 1);
}
806
QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
807
                                     const KoColorProfile *dstProfile,
808
                                     KoColorConversionTransformation::Intent renderingIntent,
809
                                     KoColorConversionTransformation::ConversionFlags conversionFlags) const
810 811 812 813 814 815 816

{
    QImage img = QImage(width, height, QImage::Format_ARGB32);

    const KoColorSpace * dstCS = KoColorSpaceRegistry::instance()->rgb8(dstProfile);

    if (data)
817
        this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
818 819 820

    return img;
}
821 822 823 824 825

bool KoColorSpace::preferCompositionInSourceColorSpace() const
{
    return false;
}