KoColorSet.cpp 13.8 KB
Newer Older
Thomas Zander's avatar
Thomas Zander committed
1
/*  This file is part of the KDE project
2 3 4 5 6 7
   Copyright (c) 2005 Boudewijn Rempt <boud@valdyas.org>


    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
8
    version 2.1 of the License, or (at your option) any later version.
9 10 11 12 13 14 15 16 17 18

    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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
19
#include <resources/KoColorSet.h>
20

21
#include <sys/types.h>
22
#include <QtEndian> // qFromLittleEndian
23

24 25 26 27 28
#include <QImage>
#include <QPoint>
#include <QVector>
#include <QFile>
#include <QTextStream>
Sven Langkamp's avatar
Sven Langkamp committed
29
#include <QFileInfo>
30 31
#include <QBuffer>
#include <QByteArray>
32
#include <QPainter>
33

Boudewijn Rempt's avatar
Boudewijn Rempt committed
34
#include <DebugPigment.h>
35
#include <klocalizedstring.h>
36

37
#include "KoColor.h"
38
#include "KoColorSpaceRegistry.h"
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
#include "KoColorModelStandardIds.h"


KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) {

    QFileInfo fi(fileName);

    // .pal
    if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) {
        return KoColorSet::RIFF_PAL;
    }
    // .gpl
    else if (ba.startsWith("GIMP Palette")) {
        return KoColorSet::GPL;
    }
    // .pal
    else if (ba.startsWith("JASC-PAL")) {
        return KoColorSet::PSP_PAL;
    }
    else if (fi.suffix().toLower() == "aco") {
        return KoColorSet::ACO;
    }
61
    else if (fi.suffix().toLower() == "act") {
62 63 64 65 66
        return KoColorSet::ACT;
    }

    return KoColorSet::UNKNOWN;
}
67

68
KoColorSet::KoColorSet(const QString& filename)
69
    : KoResource(filename)
70
{
71
    // Implemented in KoResource class
72 73 74 75
    m_columns = 0; // Set the default value that the GIMP uses...
}

KoColorSet::KoColorSet()
76
    : KoResource("")
77 78 79 80 81 82
{
    m_columns = 0; // Set the default value that the GIMP uses...
}

/// Create an copied palette
KoColorSet::KoColorSet(const KoColorSet& rhs)
83 84
    : QObject(0)
    , KoResource("")
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
{
    setFilename(rhs.filename());
    m_ownData = false;
    m_name = rhs.m_name;
    m_comment = rhs.m_comment;
    m_columns = rhs.m_columns;
    m_colors = rhs.m_colors;
    setValid(true);
}

KoColorSet::~KoColorSet()
{
}

bool KoColorSet::load()
{
    QFile file(filename());
102
    if (file.size() == 0) return false;
103
    if (!file.open(QIODevice::ReadOnly)) {
104
        warnPigment << "Can't open file " << filename();
105 106
        return false;
    }
107 108 109 110 111 112 113
    bool res =  loadFromDevice(&file);
    file.close();
    return res;
}

bool KoColorSet::loadFromDevice(QIODevice *dev)
{
114 115
    if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);

116
    m_data = dev->readAll();
117

118 119
    Q_ASSERT(m_data.size() != 0);

120 121 122 123 124 125 126 127 128 129
    return init();
}


bool KoColorSet::save()
{
    QFile file(filename());
    if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
        return false;
    }
130
    saveToDevice(&file);
131 132 133 134 135 136 137 138 139
    file.close();
    return true;
}

qint32 KoColorSet::nColors()
{
    return m_colors.count();
}

140
bool KoColorSet::saveToDevice(QIODevice *dev) const
141
{
142
    QTextStream stream(dev);
143 144 145 146
    stream << "GIMP Palette\nName: " << name() << "\nColumns: " << m_columns << "\n#\n";

    for (int i = 0; i < m_colors.size(); i++) {
        const KoColorSetEntry& entry = m_colors.at(i);
147
        QColor c = entry.color.toQColor();
148 149 150 151 152 153
        stream << c.red() << " " << c.green() << " " << c.blue() << "\t";
        if (entry.name.isEmpty())
            stream << "Untitled\n";
        else
            stream << entry.name << "\n";
    }
154 155 156

    KoResource::saveToDevice(dev);

157
    return true;
158 159 160 161
}

bool KoColorSet::init()
{
162
    m_colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog),
163

164
    if (filename().isNull()) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
165
        warnPigment << "Cannot load palette" << name() << "there is no filename set";
166 167
        return false;
    }
168 169
    if (m_data.isNull()) {
        QFile file(filename());
170
        if (file.size() == 0) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
171
            warnPigment << "Cannot load palette" << name() << "there is no data available";
172 173
            return false;
        }
174 175 176 177 178 179
        file.open(QIODevice::ReadOnly);
        m_data = file.readAll();
        file.close();
    }

    bool res = false;
180 181
    PaletteType paletteType = detectFormat(filename(), m_data);
    switch(paletteType) {
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
    case GPL:
        res = loadGpl();
        break;
    case ACT:
        res = loadAct();
        break;
    case RIFF_PAL:
        res = loadRiff();
        break;
    case PSP_PAL:
        res = loadPsp();
        break;
    case ACO:
        res = loadAco();
        break;
    default:
        res = false;
    }
    setValid(res);

202 203 204 205
    if (m_columns == 0) {
        m_columns = 10;
    }

206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
    QImage img(m_columns * 4, (m_colors.size() / m_columns) * 4, QImage::Format_ARGB32);
    QPainter gc(&img);
    gc.fillRect(img.rect(), Qt::darkGray);
    int counter = 0;
    for(int i = 0; i < m_columns; ++i) {
        for (int j = 0; j < (m_colors.size() / m_columns); ++j) {
            if (counter < m_colors.size()) {
                QColor c = m_colors.at(counter).color.toQColor();
                gc.fillRect(i * 4, j * 4, 4, 4, c);
                counter++;
            }
            else {
                break;
            }
        }
    }
    setImage(img);

224 225 226 227 228 229 230 231 232 233 234 235
    // save some memory
    m_data.clear();
    return res;
}

void KoColorSet::add(const KoColorSetEntry & c)
{
    m_colors.push_back(c);
}

void KoColorSet::remove(const KoColorSetEntry & c)
{
236
    for (auto it = m_colors.begin(); it != m_colors.end(); /*noop*/) {
237
        if ((*it) == c) {
238
            it = m_colors.erase(it);
239 240 241 242 243 244
            return;
        }
        ++it;
    }
}

245 246 247 248 249
void KoColorSet::removeAt(quint32 index)
{
    m_colors.remove(index);
}

250 251 252 253 254
KoColorSetEntry KoColorSet::getColor(quint32 index)
{
    return m_colors[index];
}

255 256 257 258 259
void KoColorSet::setColumnCount(int columns)
{
    m_columns = columns;
}

260 261
int KoColorSet::columnCount()
{
262 263 264 265
    if (m_columns){
        return m_columns;
    }
    return 16;
266 267 268 269 270 271 272 273 274 275
}

QString KoColorSet::defaultFileExtension() const
{
    return QString(".gpl");
}


bool KoColorSet::loadGpl()
{
276 277 278
    QString s = QString::fromUtf8(m_data.data(), m_data.count());

    if (s.isEmpty() || s.isNull() || s.length() < 50) {
279
        warnPigment << "Illegal Gimp palette file: " << filename();
280 281 282
        return false;
    }

283
    quint32 index = 0;
284

285
    QStringList lines = s.split('\n', QString::SkipEmptyParts);
286

287 288 289
    if (lines.size() < 3) {
        return false;
    }
290

291 292 293
    QString columns;
    qint32 r, g, b;
    KoColorSetEntry e;
294

295 296
    // Read name
    if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) {
297
        warnPigment << "Illegal Gimp palette file: " << filename();
298 299
        return false;
    }
300

301
    setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1()));
302

303
    index = 2;
304

305 306 307 308 309 310 311 312 313 314 315 316 317 318
    // Read columns
    if (lines[index].startsWith("Columns: ")) {
        columns = lines[index].mid(strlen("Columns: ")).trimmed();
        m_columns = columns.toInt();
        index = 3;
    }
    for (qint32 i = index; i < lines.size(); i++) {
        if (lines[i].startsWith('#')) {
            m_comment += lines[i].mid(1).trimmed() + ' ';
        } else if (!lines[i].isEmpty()) {
            QStringList a = lines[i].replace('\t', ' ').split(' ', QString::SkipEmptyParts);

            if (a.count() < 3) {
                break;
319 320
            }

321 322 323 324 325 326 327 328 329 330
            r = a[0].toInt();
            a.pop_front();
            g = a[0].toInt();
            a.pop_front();
            b = a[0].toInt();
            a.pop_front();

            r = qBound(0, r, 255);
            g = qBound(0, g, 255);
            b = qBound(0, b, 255);
331

Sven Langkamp's avatar
Sven Langkamp committed
332 333
            e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8());
            e.color.fromQColor(QColor(r, g, b));
334 335 336 337

            QString name = a.join(" ");
            e.name = name.isEmpty() ? i18n("Untitled") : name;

Sven Langkamp's avatar
Sven Langkamp committed
338 339
            add(e);
        }
340
    }
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
    return true;
}

bool KoColorSet::loadAct()
{
    QFileInfo info(filename());
    setName(info.baseName());
    KoColorSetEntry e;
    for (int i = 0; i < m_data.size(); i += 3) {
        quint8 r = m_data[i];
        quint8 g = m_data[i+1];
        quint8 b = m_data[i+2];
        e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8());
        e.color.fromQColor(QColor(r, g, b));
        add(e);
    }
    return true;
358 359
}

360 361 362 363 364 365 366 367 368
struct RiffHeader {
    quint32 riff;
    quint32 size;
    quint32 signature;
    quint32 data;
    quint32 datasize;
    quint16 version;
    quint16 colorcount;
};
369

370 371

bool KoColorSet::loadRiff()
372
{
373 374 375 376 377 378 379
    // http://worms2d.info/Palette_file
    QFileInfo info(filename());
    setName(info.baseName());
    KoColorSetEntry e;

    RiffHeader header;
    memcpy(&header, m_data.constData(), sizeof(RiffHeader));
380
    header.colorcount = qFromBigEndian(header.colorcount);
381 382 383 384 385 386 387 388 389 390 391 392

    for (int i = sizeof(RiffHeader);
         (i < (int)(sizeof(RiffHeader) + header.colorcount) && i < m_data.size());
         i += 4) {
        quint8 r = m_data[i];
        quint8 g = m_data[i+1];
        quint8 b = m_data[i+2];
        e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8());
        e.color.fromQColor(QColor(r, g, b));
        add(e);
    }
    return true;
393 394
}

395 396

bool KoColorSet::loadPsp()
397
{
398 399 400 401
    QFileInfo info(filename());
    setName(info.baseName());
    KoColorSetEntry e;
    qint32 r, g, b;
402

403 404 405 406 407 408 409 410 411 412 413 414 415 416
    QString s = QString::fromUtf8(m_data.data(), m_data.count());
    QStringList l = s.split('\n', QString::SkipEmptyParts);
    if (l.size() < 4) return false;
    if (l[0] != "JASC-PAL") return false;
    if (l[1] != "0100") return false;

    int entries = l[2].toInt();

    for (int i = 0; i < entries; ++i)  {

        QStringList a = l[i + 3].replace('\t', ' ').split(' ', QString::SkipEmptyParts);

        if (a.count() != 3) {
            continue;
417
        }
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436

        r = a[0].toInt();
        a.pop_front();
        g = a[0].toInt();
        a.pop_front();
        b = a[0].toInt();
        a.pop_front();

        r = qBound(0, r, 255);
        g = qBound(0, g, 255);
        b = qBound(0, b, 255);

        e.color = KoColor(KoColorSpaceRegistry::instance()->rgb8());
        e.color.fromQColor(QColor(r, g, b));

        QString name = a.join(" ");
        e.name = name.isEmpty() ? i18n("Untitled") : name;

        add(e);
437
    }
438
    return true;
439 440
}

441 442 443 444
quint16 readShort(QIODevice *io) {
    quint16 val;
    quint64 read = io->read((char*)&val, 2);
    if (read != 2) return false;
445
    return qFromBigEndian(val);
446
}
Sven Langkamp's avatar
Sven Langkamp committed
447

448
bool KoColorSet::loadAco()
Sven Langkamp's avatar
Sven Langkamp committed
449
{
450 451 452 453
    QFileInfo info(filename());
    setName(info.baseName());

    QBuffer buf(&m_data);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
454 455
    buf.open(QBuffer::ReadOnly);

456 457 458 459 460 461 462
    quint16 version = readShort(&buf);
    quint16 numColors = readShort(&buf);
    KoColorSetEntry e;

    const quint16 quint16_MAX = 65535;

    for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
Boudewijn Rempt's avatar
Boudewijn Rempt committed
463

464 465 466 467 468
        quint16 colorSpace = readShort(&buf);
        quint16 ch1 = readShort(&buf);
        quint16 ch2 = readShort(&buf);
        quint16 ch3 = readShort(&buf);
        quint16 ch4 = readShort(&buf);
Boudewijn Rempt's avatar
Boudewijn Rempt committed
469

470 471 472
        bool skip = false;
        if (colorSpace == 0) { // RGB
            e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
473 474 475
            reinterpret_cast<quint16*>(e.color.data())[0] = ch3;
            reinterpret_cast<quint16*>(e.color.data())[1] = ch2;
            reinterpret_cast<quint16*>(e.color.data())[2] = ch1;
476 477 478 479 480 481 482 483 484
            e.color.setOpacity(OPACITY_OPAQUE_U8);
        }
        else if (colorSpace == 1) { // HSB
            e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16());
            QColor c;
            c.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0);
            e.color.fromQColor(c);
            e.color.setOpacity(OPACITY_OPAQUE_U8);
        }
Boudewijn Rempt's avatar
Boudewijn Rempt committed
485
        else if (colorSpace == 2) { // CMYK
486
            e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), ""));
Boudewijn Rempt's avatar
Boudewijn Rempt committed
487 488 489
            reinterpret_cast<quint16*>(e.color.data())[0] = quint16_MAX - ch1;
            reinterpret_cast<quint16*>(e.color.data())[1] = quint16_MAX - ch2;
            reinterpret_cast<quint16*>(e.color.data())[2] = quint16_MAX - ch3;
490
            reinterpret_cast<quint16*>(e.color.data())[3] = quint16_MAX - ch4;
491 492 493 494
            e.color.setOpacity(OPACITY_OPAQUE_U8);
        }
        else if (colorSpace == 7) { // LAB
            e.color = KoColor(KoColorSpaceRegistry::instance()->lab16());
Boudewijn Rempt's avatar
Boudewijn Rempt committed
495 496 497
            reinterpret_cast<quint16*>(e.color.data())[0] = ch3;
            reinterpret_cast<quint16*>(e.color.data())[1] = ch2;
            reinterpret_cast<quint16*>(e.color.data())[2] = ch1;
498 499 500 501
            e.color.setOpacity(OPACITY_OPAQUE_U8);
        }
        else if (colorSpace == 8) { // GRAY
            e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), ""));
Boudewijn Rempt's avatar
Boudewijn Rempt committed
502
            reinterpret_cast<quint16*>(e.color.data())[0] = ch1 * (quint16_MAX / 10000);
503 504 505
            e.color.setOpacity(OPACITY_OPAQUE_U8);
        }
        else {
506
            warnPigment << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")";
507 508 509 510 511
            skip = true;
        }
        if (version == 2) {
            quint16 v2 = readShort(&buf);
            if (v2 != 2) {
512
                warnPigment << "Version 2 block is not version 2" << filename() << "(" << v2 << ")";
513 514 515 516 517
                return false;
            }
            quint16 size = readShort(&buf);
            QByteArray ba = buf.read(size);
            if (ba.size() != size) {
518
                warnPigment << "Version 2 name block is the wrong size" << filename();
519 520 521 522 523 524 525 526 527
                return false;
            }
            e.name = QString::fromUtf8(ba.constData(), ba.size());
        }
        if (!skip) {
            add(e);
        }
    }
    return true;
Sven Langkamp's avatar
Sven Langkamp committed
528 529
}