KoColor.cpp 13.6 KB
Newer Older
1 2
/*
 *  Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>
Thomas Zander's avatar
Thomas Zander committed
3
 *  Copyright (C) 2007 Thomas Zander <zander@kde.org>
4
 *  Copyright (C) 2007 Cyrille Berger <cberger@cberger.net>
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11 12 13
 * 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
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17 18 19 20
 * 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.
*/
21 22 23

#include "KoColor.h"

24
#include <QColor>
Thomas Zander's avatar
Thomas Zander committed
25

26 27
#include <QDomDocument>

Boudewijn Rempt's avatar
Boudewijn Rempt committed
28
#include "DebugPigment.h"
29

30
#include "KoColorModelStandardIds.h"
31 32
#include "KoColorProfile.h"
#include "KoColorSpace.h"
33
#include "KoColorSpaceRegistry.h"
34
#include "KoChannelInfo.h"
35
#include "kis_assert.h"
Boudewijn Rempt's avatar
Boudewijn Rempt committed
36

37
#include <QGlobalStatic>
38

Boudewijn Rempt's avatar
Boudewijn Rempt committed
39 40 41 42 43
#include <KoConfig.h>
#ifdef HAVE_OPENEXR
#include <half.h>
#endif

44 45
namespace {

Dmitry Kazakov's avatar
Dmitry Kazakov committed
46
struct DefaultKoColorInitializer
47
{
Dmitry Kazakov's avatar
Dmitry Kazakov committed
48
    DefaultKoColorInitializer() {
49 50 51 52
        const KoColorSpace *defaultColorSpace = KoColorSpaceRegistry::instance()->rgb16(0);
        KIS_ASSERT(defaultColorSpace);

        value = new KoColor(Qt::black, defaultColorSpace);
53 54
#ifndef NODEBUG
#ifndef QT_NO_DEBUG
55 56
        // warn about rather expensive checks in assertPermanentColorspace().
        qWarning() << "KoColor debug runtime checks are active.";
57 58
#endif
#endif
59 60 61 62 63
    }

    KoColor *value = 0;
};

Boudewijn Rempt's avatar
Boudewijn Rempt committed
64
Q_GLOBAL_STATIC(DefaultKoColorInitializer, s_defaultKoColor)
65 66 67 68 69 70

}


KoColor::KoColor() {
    *this = *s_defaultKoColor->value;
71
}
72

73
KoColor::KoColor(const KoColorSpace * colorSpace)
74
{
Cyrille Berger's avatar
Cyrille Berger committed
75
    Q_ASSERT(colorSpace);
76 77 78 79
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
    m_size = m_colorSpace->pixelSize();
    Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
    memset(m_data, 0, m_size);
80 81
}

82
KoColor::KoColor(const QColor & color, const KoColorSpace * colorSpace)
83 84 85
{
    Q_ASSERT(color.isValid());
    Q_ASSERT(colorSpace);
86
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
87

88 89 90
    m_size = m_colorSpace->pixelSize();
    Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
    memset(m_data, 0, m_size);
Thomas Zander's avatar
Thomas Zander committed
91

92
    m_colorSpace->fromQColor(color, m_data);
93 94
}

95
KoColor::KoColor(const quint8 * data, const KoColorSpace * colorSpace)
96
{
Cyrille Berger's avatar
Cyrille Berger committed
97 98
    Q_ASSERT(colorSpace);
    Q_ASSERT(data);
99 100 101 102
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
    m_size = m_colorSpace->pixelSize();
    Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
    memmove(m_data, data, m_size);
103 104 105
}


106
KoColor::KoColor(const KoColor &src, const KoColorSpace * colorSpace)
107
{
Cyrille Berger's avatar
Cyrille Berger committed
108
    Q_ASSERT(colorSpace);
109 110 111 112
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
    m_size = m_colorSpace->pixelSize();
    Q_ASSERT(m_size <= MAX_PIXEL_SIZE);
    memset(m_data, 0, m_size);
113

114
    src.colorSpace()->convertPixelsTo(src.m_data, m_data, colorSpace, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
115 116
}

117
void KoColor::convertTo(const KoColorSpace * cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
118
{
Boudewijn Rempt's avatar
Boudewijn Rempt committed
119
    //dbgPigment <<"Our colormodel:" << d->colorSpace->id().name()
120 121
    //      << ", new colormodel: " << cs->id().name() << "\n";

122
    if (*m_colorSpace == *cs)
123 124
        return;

125 126 127 128
    quint8 data[MAX_PIXEL_SIZE];
    const size_t size = cs->pixelSize();
    Q_ASSERT(size <= MAX_PIXEL_SIZE);
    memset(data, 0, size);
129

130
    m_colorSpace->convertPixelsTo(m_data, data, cs, 1, renderingIntent, conversionFlags);
131

132 133 134
    memcpy(m_data, data, size);
    m_size = size;
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(cs);
135 136
}

137 138 139
void KoColor::convertTo(const KoColorSpace * cs)
{
    convertTo(cs,
140 141
              KoColorConversionTransformation::internalRenderingIntent(),
              KoColorConversionTransformation::internalConversionFlags());
142
}
143

144 145 146 147 148 149 150 151 152 153 154 155 156 157
KoColor KoColor::convertedTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
    KoColor result(*this);
    result.convertTo(cs, renderingIntent, conversionFlags);
    return result;
}

KoColor KoColor::convertedTo(const KoColorSpace *cs) const
{
    return convertedTo(cs,
                       KoColorConversionTransformation::internalRenderingIntent(),
                       KoColorConversionTransformation::internalConversionFlags());
}

158 159 160
void KoColor::setProfile(const KoColorProfile *profile)
{
    const KoColorSpace *dstColorSpace =
161
            KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile);
162 163
    if (!dstColorSpace) return;

164
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(dstColorSpace);
165 166
}

167
void KoColor::setColor(const quint8 * data, const KoColorSpace * colorSpace)
168
{
Cyrille Berger's avatar
Cyrille Berger committed
169
    Q_ASSERT(colorSpace);
170 171 172 173 174 175

    const size_t size = colorSpace->pixelSize();
    Q_ASSERT(size <= MAX_PIXEL_SIZE);

    memcpy(m_data, data, size);
    m_colorSpace = KoColorSpaceRegistry::instance()->permanentColorspace(colorSpace);
176 177
}

178 179 180
// To save the user the trouble of doing color->colorSpace()->toQColor(color->data(), &c, &a, profile
void KoColor::toQColor(QColor *c) const
{
Cyrille Berger's avatar
Cyrille Berger committed
181
    Q_ASSERT(c);
182 183
    if (m_colorSpace) {
        m_colorSpace->toQColor(m_data, c);
184
    }
185 186 187 188 189 190 191 192
}

QColor KoColor::toQColor() const
{
    QColor c;
    toQColor(&c);
    return c;
}
193

194
void KoColor::fromQColor(const QColor& c)
195
{
196 197
    if (m_colorSpace) {
        m_colorSpace->fromQColor(c, m_data);
198 199 200
    }
}

201 202 203 204 205 206 207 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 235 236 237 238 239 240 241 242 243 244 245 246 247 248
void KoColor::subtract(const KoColor &value)
{
    KIS_SAFE_ASSERT_RECOVER_RETURN(*m_colorSpace == *value.colorSpace());

    QVector<float> channels1(m_colorSpace->channelCount());
    QVector<float> channels2(m_colorSpace->channelCount());

    m_colorSpace->normalisedChannelsValue(m_data, channels1);
    m_colorSpace->normalisedChannelsValue(value.data(), channels2);

    for (int i = 0; i < channels1.size(); i++) {
        channels1[i] -= channels2[i];
    }

    m_colorSpace->fromNormalisedChannelsValue(m_data, channels1);
}

KoColor KoColor::subtracted(const KoColor &value) const
{
    KoColor result(*this);
    result.subtract(value);
    return result;
}

void KoColor::add(const KoColor &value)
{
    KIS_SAFE_ASSERT_RECOVER_RETURN(*m_colorSpace == *value.colorSpace());

    QVector<float> channels1(m_colorSpace->channelCount());
    QVector<float> channels2(m_colorSpace->channelCount());

    m_colorSpace->normalisedChannelsValue(m_data, channels1);
    m_colorSpace->normalisedChannelsValue(value.data(), channels2);

    for (int i = 0; i < channels1.size(); i++) {
        channels1[i] += channels2[i];
    }

    m_colorSpace->fromNormalisedChannelsValue(m_data, channels1);
}

KoColor KoColor::added(const KoColor &value) const
{
    KoColor result(*this);
    result.add(value);
    return result;
}

Thomas Zander's avatar
Thomas Zander committed
249
#ifndef NDEBUG
250 251
void KoColor::dump() const
{
252 253
    dbgPigment <<"KoColor (" << this <<")," << m_colorSpace->id() <<"";
    QList<KoChannelInfo *> channels = m_colorSpace->channels();
254

Laurent Montel's avatar
Laurent Montel committed
255 256
    QList<KoChannelInfo *>::const_iterator begin = channels.constBegin();
    QList<KoChannelInfo *>::const_iterator end = channels.constEnd();
257

258
    for (QList<KoChannelInfo *>::const_iterator it = begin; it != end; ++it) {
259 260 261 262
        KoChannelInfo * ch = (*it);
        // XXX: setNum always takes a byte.
        if (ch->size() == sizeof(quint8)) {
            // Byte
263
            dbgPigment <<"Channel (byte):" << ch->name() <<":" << QString().setNum(m_data[ch->pos()]) <<"";
264
        } else if (ch->size() == sizeof(quint16)) {
265
            // Short (may also by an nvidia half)
266
            dbgPigment <<"Channel (short):" << ch->name() <<":" << QString().setNum(*((const quint16 *)(m_data+ch->pos())))  <<"";
267
        } else if (ch->size() == sizeof(quint32)) {
268
            // Integer (may also be float... Find out how to distinguish these!)
269
            dbgPigment <<"Channel (int):" << ch->name() <<":" << QString().setNum(*((const quint32 *)(m_data+ch->pos())))  <<"";
270 271 272
        }
    }
}
Thomas Zander's avatar
Thomas Zander committed
273
#endif
274 275 276

void KoColor::fromKoColor(const KoColor& src)
{
277
    src.colorSpace()->convertPixelsTo(src.m_data, m_data, colorSpace(), 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
278
}
Cyrille Berger's avatar
Cyrille Berger committed
279

280
const KoColorProfile *KoColor::profile() const
Cyrille Berger's avatar
Cyrille Berger committed
281
{
282
    return m_colorSpace->profile();
Thomas Zander's avatar
Thomas Zander committed
283 284
}

285 286
void KoColor::toXML(QDomDocument& doc, QDomElement& colorElt) const
{
287
    m_colorSpace->colorToXML(m_data, doc, colorElt);
288 289
}

290
void KoColor::setOpacity(quint8 alpha)
291
{
292
    m_colorSpace->setOpacity(m_data, alpha, 1);
293
}
294
void KoColor::setOpacity(qreal alpha)
295
{
296
    m_colorSpace->setOpacity(m_data, alpha, 1);
297 298 299
}
quint8 KoColor::opacityU8() const
{
300
    return m_colorSpace->opacityU8(m_data);
301 302 303
}
qreal KoColor::opacityF() const
{
304
    return m_colorSpace->opacityF(m_data);
305 306
}

307
KoColor KoColor::fromXML(const QDomElement& elt, const QString& bitDepthId)
308
{
309 310 311 312 313 314 315
    bool ok;
    return fromXML(elt, bitDepthId, &ok);
}

KoColor KoColor::fromXML(const QDomElement& elt, const QString& bitDepthId, bool* ok)
{
    *ok = true;
316
    QString modelId;
317
    if (elt.tagName() == "CMYK") {
318
        modelId = CMYKAColorModelID.id();
319
    } else if (elt.tagName() == "RGB") {
320
        modelId = RGBAColorModelID.id();
321
    } else if (elt.tagName() == "sRGB") {
322
        modelId = RGBAColorModelID.id();
323
    } else if (elt.tagName() == "Lab") {
324
        modelId = LABAColorModelID.id();
325
    } else if (elt.tagName() == "XYZ") {
326
        modelId = XYZAColorModelID.id();
327
    } else if (elt.tagName() == "Gray") {
328
        modelId = GrayAColorModelID.id();
329
    } else if (elt.tagName() == "YCbCr") {
330 331 332
        modelId = YCbCrAColorModelID.id();
    }
    QString profileName;
333 334 335
    if (elt.tagName() != "sRGB") {
        profileName = elt.attribute("space", "");
        if (!KoColorSpaceRegistry::instance()->profileByName(profileName)) {
336
            profileName.clear();
337
        }
338
    }
339 340
    const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, bitDepthId, profileName);
    if (cs == 0) {
341 342
        QList<KoID> list =  KoColorSpaceRegistry::instance()->colorDepthList(modelId, KoColorSpaceRegistry::AllColorSpaces);
        if (!list.empty()) {
343
            cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, list[0].id(), profileName);
344 345
        }
    }
346
    if (cs) {
347
        KoColor c(cs);
348
        // TODO: Provide a way for colorFromXML() to notify the caller if parsing failed. Currently it returns default values on failure.
349
        cs->colorFromXML(c.data(), elt);
350 351
        return c;
    } else {
352
        *ok = false;
353 354
        return KoColor();
    }
355
}
356 357 358 359

QString KoColor::toQString(const KoColor &color)
{
    QStringList ls;
360
    Q_FOREACH (KoChannelInfo *channel, KoChannelInfo::displayOrderSorted(color.colorSpace()->channels())) {
361
        int realIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), color.colorSpace()->channels());
362
        ls << channel->name();
363
        ls << color.colorSpace()->channelValueText(color.data(), realIndex);
364 365 366
    }
    return ls.join(" ");
}
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392

QDebug operator<<(QDebug dbg, const KoColor &color)
{
    dbg.nospace() << "KoColor (" << color.colorSpace()->id();

    QList<KoChannelInfo*> channels = color.colorSpace()->channels();
    for (auto it = channels.constBegin(); it != channels.constEnd(); ++it) {

        KoChannelInfo *ch = (*it);

        dbg.nospace() << ", " << ch->name() << ":";

        switch (ch->channelValueType()) {
        case KoChannelInfo::UINT8: {
            const quint8 *ptr = reinterpret_cast<const quint8*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::UINT16: {
            const quint16 *ptr = reinterpret_cast<const quint16*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::UINT32: {
            const quint32 *ptr = reinterpret_cast<const quint32*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::FLOAT16: {
393 394

#ifdef HAVE_OPENEXR
395 396
            const half *ptr = reinterpret_cast<const half*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
397 398 399
#else
            const quint16 *ptr = reinterpret_cast<const quint16*>(color.data() + ch->pos());
            dbg.nospace() << "UNSUPPORTED_F16(" << *ptr << ")";
Boudewijn Rempt's avatar
Boudewijn Rempt committed
400
#endif
401
            break;
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
        } case KoChannelInfo::FLOAT32: {
            const float *ptr = reinterpret_cast<const float*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::FLOAT64: {
            const double *ptr = reinterpret_cast<const double*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::INT8: {
            const qint8 *ptr = reinterpret_cast<const qint8*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::INT16: {
            const qint16 *ptr = reinterpret_cast<const qint16*>(color.data() + ch->pos());
            dbg.nospace() << *ptr;
            break;
        } case KoChannelInfo::OTHER: {
            const quint8 *ptr = reinterpret_cast<const quint8*>(color.data() + ch->pos());
            dbg.nospace() << "undef(" << *ptr << ")";
            break;
        }
        }
    }
    dbg.nospace() << ")";
    return dbg.space();
}