KoColorSpace.cpp 13.3 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
#include "KoMixColorsOp.h"
37
#include "KoConvolutionOp.h"
38
#include "KoCompositeOpRegistry.h"
39

40 41 42 43
#include <QThreadStorage>
#include <QByteArray>
#include <QBitArray>

44

Stefan Nikolaus's avatar
Stefan Nikolaus committed
45
KoColorSpace::KoColorSpace()
46
        : d(new Private())
Stefan Nikolaus's avatar
Stefan Nikolaus committed
47 48 49
{
}

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

65
KoColorSpace::~KoColorSpace()
66
{
67 68
    Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);

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

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

95 96 97 98
QString KoColorSpace::id() const
{
    return d->id;
}
99

100 101 102 103
QString KoColorSpace::name() const
{
    return d->name;
}
104

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

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

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

124 125 126
void KoColorSpace::addChannel(KoChannelInfo * ci)
{
    d->channels.push_back(ci);
127
}
128 129 130 131 132
bool KoColorSpace::hasCompositeOp(const QString& id) const
{
    return d->compositeOps.contains(id);
}

133
QList<KoCompositeOp*> KoColorSpace::compositeOps() const
134
{
135 136 137
    return d->compositeOps.values();
}

138 139
KoMixColorsOp* KoColorSpace::mixColorsOp() const
{
140 141 142 143
    return d->mixColorsOp;
}


144 145
KoConvolutionOp* KoColorSpace::convolutionOp() const
{
146
    return d->convolutionOp;
147 148
}

Cyrille Berger's avatar
Cyrille Berger committed
149
const KoCompositeOp * KoColorSpace::compositeOp(const QString & id) const
150
{
151 152
    if (d->compositeOps.contains(id))
        return d->compositeOps.value(id);
153
    else {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
154
        warnPigment << "Asking for non-existent composite operation " << id << ", returning " << COMPOSITE_OVER;
155
        return d->compositeOps.value(COMPOSITE_OVER);
156
    }
157 158 159 160
}

void KoColorSpace::addCompositeOp(const KoCompositeOp * op)
{
161 162
    if (op->colorSpace()->id() == id()) {
        d->compositeOps.insert(op->id(), const_cast<KoCompositeOp*>(op));
163 164 165
    }
}

166
const KoColorConversionTransformation* KoColorSpace::toLabA16Converter() const
167
{
168
    if (!d->transfoToLABA16) {
169
        d->transfoToLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->lab16(""), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
170
    }
171
    return d->transfoToLABA16;
172
}
173

174
const KoColorConversionTransformation* KoColorSpace::fromLabA16Converter() const
175
{
176
    if (!d->transfoFromLABA16) {
177
        d->transfoFromLABA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->lab16(""), this, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
178
    }
179
    return d->transfoFromLABA16;
180
}
181
const KoColorConversionTransformation* KoColorSpace::toRgbA16Converter() const
182
{
183
    if (!d->transfoToRGBA16) {
184
        d->transfoToRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, KoColorSpaceRegistry::instance()->rgb16(""), KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
185
    }
186
    return d->transfoToRGBA16;
187
}
188
const KoColorConversionTransformation* KoColorSpace::fromRgbA16Converter() const
189
{
190
    if (!d->transfoFromRGBA16) {
191
        d->transfoFromRGBA16 = KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(KoColorSpaceRegistry::instance()->rgb16("") , this, KoColorConversionTransformation::InternalRenderingIntent, KoColorConversionTransformation::InternalConversionFlags) ;
192
    }
193 194 195 196 197
    return d->transfoFromRGBA16;
}

void KoColorSpace::toLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
198
    toLabA16Converter()->transform(src, dst, nPixels);
199 200 201 202
}

void KoColorSpace::fromLabA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
203
    fromLabA16Converter()->transform(src, dst, nPixels);
204 205 206 207
}

void KoColorSpace::toRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
208
    toRgbA16Converter()->transform(src, dst, nPixels);
209 210 211 212
}

void KoColorSpace::fromRgbA16(const quint8 * src, quint8 * dst, quint32 nPixels) const
{
213
    fromRgbA16Converter()->transform(src, dst, nPixels);
214 215
}

216
KoColorConversionTransformation* KoColorSpace::createColorConverter(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
217
{
218
    if (*this == *dstColorSpace) {
219
        return new KoCopyColorConversionTransformation(this);
220
    } else {
221
        return KoColorSpaceRegistry::instance()->colorConversionSystem()->createColorConverter(this, dstColorSpace, renderingIntent, conversionFlags);
222
    }
223 224
}

225
bool KoColorSpace::convertPixelsTo(const quint8 * src,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
226 227 228
                                   quint8 * dst,
                                   const KoColorSpace * dstColorSpace,
                                   quint32 numPixels,
229
                                   KoColorConversionTransformation::Intent renderingIntent,
230
                                   KoColorConversionTransformation::ConversionFlags conversionFlags) const
231
{
232
    if (*this == *dstColorSpace) {
233 234 235
        if (src != dst) {
            memcpy(dst, src, numPixels * sizeof(quint8) * pixelSize());
        }
236
    } else {
237
        KoCachedColorConversionTransformation cct = KoColorSpaceRegistry::instance()->colorConversionCache()->cachedConverter(this, dstColorSpace, renderingIntent, conversionFlags);
238 239
        cct.transformation()->transform(src, dst, numPixels);
    }
240 241
    return true;
}
242

243

Boudewijn Rempt's avatar
Boudewijn Rempt committed
244 245 246
void KoColorSpace::bitBlt(const KoColorSpace* srcSpace, const KoCompositeOp::ParameterInfo& params, const KoCompositeOp* op,
                          KoColorConversionTransformation::Intent renderingIntent,
                          KoColorConversionTransformation::ConversionFlags conversionFlags) const
247 248
{
    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());
249

250 251
    if(params.rows <= 0 || params.cols <= 0)
        return;
252

253
    if(!(*this == *srcSpace)) {
254 255 256 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
         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);
296 297
        }
    }
298 299 300
    else {
        op->composite(params);
    }
301 302 303
}


304 305 306
QVector<quint8> * KoColorSpace::threadLocalConversionCache(quint32 size) const
{
    QVector<quint8> * ba = 0;
307 308 309 310
    if (!d->conversionCache.hasLocalData()) {
        ba = new QVector<quint8>(size, '0');
        d->conversionCache.setLocalData(ba);
    } else {
311
        ba = d->conversionCache.localData();
312 313
        if ((quint8)ba->size() < size)
            ba->resize(size);
314 315 316
    }
    return ba;
}
317

318
KoColorTransformation* KoColorSpace::createColorTransformation(const QString & id, const QHash<QString, QVariant> & parameters) const
319
{
320 321 322
    KoColorTransformationFactory* factory = KoColorTransformationFactoryRegistry::instance()->get(id);
    if (!factory) return 0;
    QPair<KoID, KoID> model(colorModelId(), colorDepthId());
323
    QList< QPair<KoID, KoID> > models = factory->supportedModels();
324 325
    if (models.isEmpty() || models.contains(model)) {
        return factory->createTransformation(this, parameters);
326 327
    } else {
        // Find the best solution
328
        // TODO use the color conversion cache
329 330 331 332 333 334
        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);
335
        return new KoFallBackColorTransformation(csToFallBack, fallBackToCs, transfo);
336 337
    }
}
338

339
QImage KoColorSpace::convertToQImage(const quint8 *data, qint32 width, qint32 height,
Boudewijn Rempt's avatar
Boudewijn Rempt committed
340
                                     const KoColorProfile *dstProfile,
341
                                     KoColorConversionTransformation::Intent renderingIntent,
342
                                     KoColorConversionTransformation::ConversionFlags conversionFlags) const
343 344 345 346 347 348 349

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

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

    if (data)
350
        this->convertPixelsTo(const_cast<quint8 *>(data), img.bits(), dstCS, width * height, renderingIntent, conversionFlags);
351 352 353

    return img;
}
354 355 356 357 358

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