KoColorSpace.cpp 13.5 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 7
 * License as published by the Free Software Foundation; either
 * version 2 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

37 38 39 40
#include <QThreadStorage>
#include <QByteArray>
#include <QBitArray>

41

Stefan Nikolaus's avatar
Stefan Nikolaus committed
42
KoColorSpace::KoColorSpace()
43
        : d(new Private())
Stefan Nikolaus's avatar
Stefan Nikolaus committed
44 45 46
{
}

47 48
KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp)
        : d(new Private())
49
{
50
    d->id = id;
51
    d->idNumber = KoUniqueNumberForIdServer::instance()->numberForId(d->id);
52 53 54
    d->name = name;
    d->mixColorsOp = mixColorsOp;
    d->convolutionOp = convolutionOp;
55 56 57 58
    d->transfoToRGBA16 = 0;
    d->transfoFromRGBA16 = 0;
    d->transfoToLABA16 = 0;
    d->transfoFromLABA16 = 0;
59
    d->deletability = NotOwnedByRegistry;
60 61
}

62
KoColorSpace::~KoColorSpace()
63
{
64 65
    Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);

66
    qDeleteAll(d->compositeOps);
67
    foreach(KoChannelInfo * channel, d->channels) {
Cyrille Berger's avatar
Cyrille Berger committed
68 69
        delete channel;
    }
70 71 72 73 74
    if (d->deletability == NotOwnedByRegistry) {
        KoColorConversionCache* cache = KoColorSpaceRegistry::instance()->colorConversionCache();
        if (cache) {
            cache->colorSpaceIsDestroyed(this);
        }
75
    }
76 77
    delete d->mixColorsOp;
    delete d->convolutionOp;
Cyrille Berger's avatar
Cyrille Berger committed
78 79 80 81
    delete d->transfoToRGBA16;
    delete d->transfoFromRGBA16;
    delete d->transfoToLABA16;
    delete d->transfoFromLABA16;
82 83 84
    delete d;
}

85 86
bool KoColorSpace::operator==(const KoColorSpace& rhs) const
{
87 88
    const KoColorProfile* p1 = rhs.profile();
    const KoColorProfile* p2 = profile();
89
    return d->idNumber == rhs.d->idNumber && ((p1 == p2) || (*p1 == *p2));
90 91
}

92 93 94 95
QString KoColorSpace::id() const
{
    return d->id;
}
96

97 98 99 100
QString KoColorSpace::name() const
{
    return d->name;
}
101

Adrian Page's avatar
q3--  
Adrian Page committed
102
QList<KoChannelInfo *> KoColorSpace::channels() const
103 104 105 106
{
    return d->channels;
}

Boudewijn Rempt's avatar
Boudewijn Rempt committed
107
QBitArray KoColorSpace::channelFlags(bool color, bool alpha) const
108
{
109
    QBitArray ba(d->channels.size());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
110
    if (!color && !alpha) return ba;
111 112 113 114

    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
115
                (alpha && channel->channelType() == KoChannelInfo::ALPHA))
116
            ba.setBit(i, true);
117 118 119 120
    }
    return ba;
}

121 122 123
void KoColorSpace::addChannel(KoChannelInfo * ci)
{
    d->channels.push_back(ci);
124 125
}

126
quint8 *KoColorSpace::allocPixelBuffer(quint32 numPixels, bool clear, quint8 defaultvalue) const
127
{
128 129 130 131 132 133 134 135
    if (numPixels == 0) {
        return 0;
    }
    quint8* buf = new quint8[pixelSize()*numPixels];
    if (clear) {
        memset(buf, defaultvalue, pixelSize() * numPixels);
    }
    return buf;
136 137
}

138 139 140 141 142
bool KoColorSpace::hasCompositeOp(const QString& id) const
{
    return d->compositeOps.contains(id);
}

143
QList<KoCompositeOp*> KoColorSpace::compositeOps() const
144
{
145 146 147 148
    return d->compositeOps.values();
}


149 150
KoMixColorsOp* KoColorSpace::mixColorsOp() const
{
151 152 153 154
    return d->mixColorsOp;
}


155 156
KoConvolutionOp* KoColorSpace::convolutionOp() const
{
157
    return d->convolutionOp;
158 159
}

Cyrille Berger's avatar
Cyrille Berger committed
160
const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const
161
{
162 163
    if (d->compositeOps.contains(id))
        return d->compositeOps.value(id);
164
    else {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
165
        warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER;
166
        return d->compositeOps.value(COMPOSITE_OVER);
167
    }
168 169 170 171
}

void KoColorSpace::addCompositeOp(const KoCompositeOp * op)
{
172 173
    if (op->colorSpace()->id() == id()) {
        d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
174 175 176
    }
}

177
const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const
178
{
179
    if (!d->transfoToLABA16) {
180
        d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
181
    }
182
    return d->transfoToLABA16;
183
}
184

185
const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const
186
{
187
    if (!d->transfoFromLABA16) {
188
        d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
189
    }
190
    return d->transfoFromLABA16;
191
}
192
const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const
193
{
194
    if (!d->transfoToRGBA16) {
195
        d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
196
    }
197
    return d->transfoToRGBA16;
198
}
199
const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const
200
{
201
    if (!d->transfoFromRGBA16) {
202
        d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
203
    }
204 205 206 207 208
    return d->transfoFromRGBA16;
}

void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
209
    toLabA16Converter()->transform(src, dst, nPixels);
210 211 212 213
}

void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
214
    fromLabA16Converter()->transform(src, dst, nPixels);
215 216 217 218
}

void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
219
    toRgbA16Converter()->transform(src, dst, nPixels);
220 221 222 223
}

void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
224
    fromRgbA16Converter()->transform(src, dst, nPixels);
225 226
}

227
KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
228
{
229
    if (*this == *dstColorSpace) {
230
        return new KoCopyColorConversionTransformation(this);
231
    } else {
232
        return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
233
    }
234 235
}

236
bool KoColorSpace::convertPixelsTo(const quint8 * src,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
237 238 239
                                   quint8 * dst,
                                   const KoColorSpace * dstColorSpace,
                                   quint32 numPixels,
240
                                   KoColorConversionTransformation::Intent renderingIntent,
241
                                   KoColorConversionTransformation::ConversionFlags conversionFlags) const
242
{
243
    if (*this == *dstColorSpace) {
244 245 246
        if (src != dst) {
            memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
        }
247
    } else {
248
        KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
249 250
        cct.transformation()->transform(src, dst, numPixels);
    }
251 252
    return true;
}
253

254

Boudewijn Rempt's avatar
Boudewijn Rempt committed
255 256 257
void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
                          KoColorConversionTransformation::Intent renderingIntent,
                          KoColorConversionTransformation::ConversionFlags conversionFlags) const
258 259
{
    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());
260

261 262
    if(params.rows <= 0 || params.cols <= 0)
        return;
263

264
    if(!(*this == *srcSpace)) {
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 300 301 302 303 304 305 306
         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);
307 308
        }
    }
309 310 311
    else {
        op->composite(params);
    }
312 313 314
}


315 316 317
QVector<quint8> * KoColorSpace::threadLocalConversionCache(quint32 size) const
{
    QVector<quint8> * ba = 0;
318 319 320 321
    if (!d->conversionCache.hasLocalData()) {
        ba = new QVector<quint8>(size, '0');
        d->conversionCache.setLocalData(ba);
    } else {
322
        ba = d->conversionCache.localData();
323 324
        if ((quint8)ba->size() < size)
            ba->resize(size);
325 326 327
    }
    return ba;
}
328

329
KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
330
{
331 332 333
    KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id);
    if (!factory) return 0;
    QPair<KoID, KoID> model(colorModelId(), colorDepthId());
334
    QList< QPair<KoID, KoID> > models = factory->supportedModels();
335 336
    if (models.isEmpty() || models.contains(model)) {
        return factory->createTransformation(this, parameters);
337 338
    } else {
        // Find the best solution
339
        // TODO use the color conversion cache
340 341 342 343 344 345
        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);
346
        return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
347 348
    }
}
349

350
QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
351
                                     const KoColorProfile *dstProfile,
352
                                     KoColorConversionTransformation::Intent renderingIntent,
353
                                     KoColorConversionTransformation::ConversionFlags conversionFlags) const
354 355 356 357 358 359 360

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

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

    if (data)
361
        this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
362 363 364

    return img;
}
365 366 367 368 369

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