Commit 02fb85d7 authored by Halla Rempt's avatar Halla Rempt
Browse files

Feature: implement support for more types of palette

A todo from 2004, we now support RIFF_PAL palettes.

For good measure, also add support for paintshop pro palettes and Photoshop
ACO palettes (which support rgb, cmyk, hsv, lab and gray, at 16
bits/channel, so a nice addition to default gimp palettes).
parent 8e643e17
......@@ -18,6 +18,9 @@
*/
#include "KoColorSet.h"
#include <sys/types.h>
#include <netinet/in.h> // htonl
#include <QImage>
#include <QPoint>
#include <QVector>
......@@ -31,7 +34,38 @@
#include <kdebug.h>
#include <klocale.h>
#include "KoColor.h"
#include "KoColorSpaceRegistry.h"
#include "KoColorModelStandardIds.h"
KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba) {
QFileInfo fi(fileName);
if (!fi.exists()) return KoColorSet::UNKNOWN;
// .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;
}
else if (fi.size() == 768 && fi.suffix().toLower() == "act") {
return KoColorSet::ACT;
}
return KoColorSet::UNKNOWN;
}
KoColorSet::KoColorSet(const QString& filename)
: KoResource(filename)
......@@ -41,15 +75,15 @@ KoColorSet::KoColorSet(const QString& filename)
}
KoColorSet::KoColorSet()
: KoResource("")
: KoResource("")
{
m_columns = 0; // Set the default value that the GIMP uses...
}
/// Create an copied palette
KoColorSet::KoColorSet(const KoColorSet& rhs)
: QObject(0)
, KoResource("")
: QObject(0)
, KoResource("")
{
setFilename(rhs.filename());
m_ownData = false;
......@@ -109,8 +143,6 @@ QByteArray KoColorSet::generateMD5() const
void KoColorSet::save(QIODevice *io) const
{
QTextStream stream(io);
// Header: Magic\nName: <name>\nColumns: <no idea what this means, but default = 0>
// In any case, we don't use Columns...
stream << "GIMP Palette\nName: " << name() << "\nColumns: " << m_columns << "\n#\n";
for (int i = 0; i < m_colors.size(); i++) {
......@@ -129,6 +161,79 @@ bool KoColorSet::init()
{
m_colors.clear(); // just in case this is a reload (eg by KoEditColorSetDialog),
if (filename().isNull()) return false;
if (m_data.isNull()) {
QFile file(filename());
if (file.size() == 0) return false;
file.open(QIODevice::ReadOnly);
m_data = file.readAll();
file.close();
}
bool res = false;
switch(detectFormat(filename(), m_data)) {
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);
// 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)
{
QVector<KoColorSetEntry>::iterator it = m_colors.begin();
QVector<KoColorSetEntry>::iterator end = m_colors.end();
while (it != end) {
if ((*it) == c) {
m_colors.erase(it);
return;
}
++it;
}
}
KoColorSetEntry KoColorSet::getColor(quint32 index)
{
return m_colors[index];
}
int KoColorSet::columnCount()
{
return m_columns;
}
QString KoColorSet::defaultFileExtension() const
{
return QString(".gpl");
}
bool KoColorSet::loadGpl()
{
QString s = QString::fromUtf8(m_data.data(), m_data.count());
if (s.isEmpty() || s.isNull() || s.length() < 50) {
......@@ -136,124 +241,246 @@ bool KoColorSet::init()
return false;
}
quint32 index = 0;
if (s.startsWith("RIFF") || s.startsWith("PAL data")) {
} else if (s.startsWith("GIMP Palette")) {
// XXX: No checks for wrong input yet!
quint32 index = 0;
QStringList lines = s.split('\n', QString::SkipEmptyParts);
QStringList lines = s.split('\n', QString::SkipEmptyParts);
if (lines.size() < 3) {
return false;
}
if (lines.size() < 3) {
return false;
}
QString entry, channel, columns;
QStringList c;
qint32 r, g, b;
QColor color;
KoColorSetEntry e;
QString columns;
qint32 r, g, b;
KoColorSetEntry e;
// Read name
if (!lines[1].startsWith("Name: ") || !lines[0].startsWith("GIMP")) {
kWarning(30009) << "Illegal Gimp palette file: " << filename();
return false;
}
// Read name
if (!lines[0].startsWith("GIMP") || !lines[1].startsWith("Name: ")) {
kWarning(30009) << "Illegal Gimp palette file: " << filename();
return false;
}
setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1()));
setName(i18n(lines[1].mid(strlen("Name: ")).trimmed().toLatin1()));
index = 2;
index = 2;
// 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;
}
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);
// 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;
}
}
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);
setValid(true);
return true;
} else if (m_data.length() == 768) {
// Photoshop .act files
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));
QString name = a.join(" ");
e.name = name.isEmpty() ? i18n("Untitled") : name;
add(e);
}
setValid(true);
return true;
}
return false;
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;
}
struct RiffHeader {
quint32 riff;
quint32 size;
quint32 signature;
quint32 data;
quint32 datasize;
quint16 version;
quint16 colorcount;
};
void KoColorSet::add(const KoColorSetEntry & c)
bool KoColorSet::loadRiff()
{
m_colors.push_back(c);
// http://worms2d.info/Palette_file
QFileInfo info(filename());
setName(info.baseName());
KoColorSetEntry e;
RiffHeader header;
memcpy(&header, m_data.constData(), sizeof(RiffHeader));
header.colorcount = ntohl(header.colorcount);
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;
}
void KoColorSet::remove(const KoColorSetEntry & c)
bool KoColorSet::loadPsp()
{
QVector<KoColorSetEntry>::iterator it = m_colors.begin();
QVector<KoColorSetEntry>::iterator end = m_colors.end();
QFileInfo info(filename());
setName(info.baseName());
KoColorSetEntry e;
qint32 r, g, b;
while (it != end) {
if ((*it) == c) {
m_colors.erase(it);
return;
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;
}
++it;
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);
}
return true;
}
KoColorSetEntry KoColorSet::getColor(quint32 index)
{
return m_colors[index];
quint16 readShort(QIODevice *io) {
quint16 val;
quint64 read = io->read((char*)&val, 2);
if (read != 2) return false;
return ntohs(val);
}
int KoColorSet::columnCount()
bool KoColorSet::loadAco()
{
return m_columns;
QFileInfo info(filename());
setName(info.baseName());
QBuffer buf(&m_data);
quint16 version = readShort(&buf);
quint16 numColors = readShort(&buf);
KoColorSetEntry e;
const quint16 quint16_MAX = 65535;
for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
quint16 colorSpace = readShort(&buf);
quint16 ch1 = readShort(&buf);
quint16 ch2 = readShort(&buf);
quint16 ch3 = readShort(&buf);
quint16 ch4 = readShort(&buf);
bool skip = false;
if (colorSpace == 0) { // RGB
e.color = KoColor(KoColorSpaceRegistry::instance()->rgb16());
e.color.data()[0] = ch3;
e.color.data()[1] = ch2;
e.color.data()[2] = ch1;
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);
}
else if (colorSpace == 2) { //
e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id(), ""));
e.color.data()[0] = quint16_MAX - ch1;
e.color.data()[1] = quint16_MAX - ch2;
e.color.data()[2] = quint16_MAX - ch3;
e.color.data()[4] = quint16_MAX - ch4;
e.color.setOpacity(OPACITY_OPAQUE_U8);
}
else if (colorSpace == 7) { // LAB
e.color = KoColor(KoColorSpaceRegistry::instance()->lab16());
e.color.data()[0] = ch3;
e.color.data()[1] = ch2;
e.color.data()[2] = ch1;
e.color.setOpacity(OPACITY_OPAQUE_U8);
}
else if (colorSpace == 8) { // GRAY
e.color = KoColor(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer16BitsColorDepthID.id(), ""));
e.color.data()[0] = ch1 * (quint16_MAX / 10000);
e.color.setOpacity(OPACITY_OPAQUE_U8);
}
else {
kWarning() << "Unsupported colorspace in palette" << filename() << "(" << colorSpace << ")";
skip = true;
}
if (version == 2) {
quint16 v2 = readShort(&buf);
if (v2 != 2) {
kWarning() << "Version 2 block is not version 2" << filename() << "(" << v2 << ")";
return false;
}
quint16 size = readShort(&buf);
QByteArray ba = buf.read(size);
if (ba.size() != size) {
kWarning() << "Version 2 name block is the wrong size" << filename();
return false;
}
e.name = QString::fromUtf8(ba.constData(), ba.size());
}
if (!skip) {
add(e);
}
}
return true;
}
QString KoColorSet::defaultFileExtension() const
{
return QString(".gpl");
}
......@@ -46,6 +46,17 @@ class PIGMENTCMS_EXPORT KoColorSet : public QObject, public KoResource
{
Q_OBJECT
public:
enum PaletteType {
UNKNOWN = 0,
GPL, // GIMP
RIFF_PAL, // RIFF
ACT, // Photoshop binary
PSP_PAL, // PaintShop Pro
ACO // Photoshop Swatches
};
/**
* Load a color set from a file. This can be a Gimp
* palette, a RIFF palette or a Photoshop palette.
......@@ -83,7 +94,11 @@ private:
void save(QIODevice *io) const;
bool init();
private:
bool loadGpl();
bool loadAct();
bool loadRiff();
bool loadPsp();
bool loadAco();
QByteArray m_data;
bool m_ownData;
......
......@@ -184,7 +184,7 @@ KoResourceServerProvider::KoResourceServerProvider() : d(new Private)
d->gradientThread = new KoResourceLoaderThread(d->m_gradientServer);
d->gradientThread->start();
d->m_paletteServer = new KoResourceServer<KoColorSet>("ko_palettes", "*.gpl:*.pal:*.act");
d->m_paletteServer = new KoResourceServer<KoColorSet>("ko_palettes", "*.gpl:*.pal:*.act:*.aco:*.css:*.colors");
d->paletteThread = new KoResourceLoaderThread(d->m_paletteServer);
d->paletteThread->start();
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment