Krita Source Code Documentation
Loading...
Searching...
No Matches
KoColorSet.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 *
3 * SPDX-FileCopyrightText: 2016 L. E. Segovia <amy@amyspark.me>
4 * SPDX-FileCopyrightText: 2005..2022 Halla Rempt <halla@valdyas.org>
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 */
7#include <sys/types.h>
8
9#include <QFile>
10#include <QFileInfo>
11#include <QBuffer>
12#include <QVector>
13#include <QTextStream>
14#include <QTextCodec>
15#include <QHash>
16#include <QByteArray>
17#include <QDomDocument>
18#include <QDomElement>
19#include <QDomNodeList>
20#include <QString>
21#include <QStringList>
22
23#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
24#include <QStringView>
25#else
26#include <QStringRef>
27#endif
28
29#include <QImage>
30#include <QPainter>
31#include <QXmlStreamReader>
32#include <QXmlStreamAttributes>
33#include <QtEndian> // qFromLittleEndian
34#include <QRegularExpression>
35#include <QRegularExpressionMatch>
36
37#include <DebugPigment.h>
38#include <klocalizedstring.h>
39#include <kundo2command.h>
40
41#include <KoStore.h>
42#include <KoColor.h>
43#include <KoColorSpace.h>
45#include <KoColorProfile.h>
47#include "KisSwatch.h"
48#include "kis_dom_utils.h"
49
50#include "KoColorSet.h"
51#include "KoColorSet_p.h"
52
53namespace {
54
61QStringList readAllLinesSafe(QByteArray *data)
62{
63 QStringList lines;
64
65 QBuffer buffer(data);
66 buffer.open(QBuffer::ReadOnly);
67 QTextStream stream(&buffer);
69
70 QString line;
71 while (stream.readLineInto(&line)) {
72 lines << line;
73 }
74
75 return lines;
76}
77}
78
79const QString KoColorSet::GLOBAL_GROUP_NAME = QString();
80const QString KoColorSet::KPL_VERSION_ATTR = "version";
81const QString KoColorSet::KPL_GROUP_ROW_COUNT_ATTR = "rows";
82const QString KoColorSet::KPL_PALETTE_COLUMN_COUNT_ATTR = "columns";
83const QString KoColorSet::KPL_PALETTE_NAME_ATTR = "name";
84const QString KoColorSet::KPL_PALETTE_COMMENT_ATTR = "comment";
85const QString KoColorSet::KPL_PALETTE_FILENAME_ATTR = "filename";
86const QString KoColorSet::KPL_PALETTE_READONLY_ATTR = "readonly";
87const QString KoColorSet::KPL_COLOR_MODEL_ID_ATTR = "colorModelId";
88const QString KoColorSet::KPL_COLOR_DEPTH_ID_ATTR = "colorDepthId";
89const QString KoColorSet::KPL_GROUP_NAME_ATTR = "name";
90const QString KoColorSet::KPL_SWATCH_ROW_ATTR = "row";
91const QString KoColorSet::KPL_SWATCH_COL_ATTR = "column";
92const QString KoColorSet::KPL_SWATCH_NAME_ATTR = "name";
93const QString KoColorSet::KPL_SWATCH_ID_ATTR = "id";
94const QString KoColorSet::KPL_SWATCH_SPOT_ATTR = "spot";
95const QString KoColorSet::KPL_SWATCH_BITDEPTH_ATTR = "bitdepth";
96const QString KoColorSet::KPL_PALETTE_PROFILE_TAG = "Profile";
97const QString KoColorSet::KPL_SWATCH_POS_TAG = "Position";
98const QString KoColorSet::KPL_SWATCH_TAG = "ColorSetEntry";
99const QString KoColorSet::KPL_GROUP_TAG = "Group";
100const QString KoColorSet::KPL_PALETTE_TAG = "ColorSet";
101
102const int MAXIMUM_ALLOWED_COLUMNS = 4096;
103
104
106{
107
108 AddSwatchCommand(KoColorSet *colorSet, const KisSwatch &swatch, const QString &groupName, int column, int row)
109 : m_colorSet(colorSet)
110 , m_swatch(swatch)
111 , m_groupName(groupName)
112 , m_x(column)
113 , m_y(row)
114 {
115 }
116
117 ~AddSwatchCommand() override {}
118
120 void redo() override
121 {
123 if (m_x < 0 || m_y < 0) {
124 QPair<int, int> pos = modifiedGroup->addSwatch(m_swatch);
125 m_x = pos.first;
126 m_y = pos.second;
127 }
128 else {
129 modifiedGroup->setSwatch(m_swatch, m_x, m_y);
130 }
132 }
133
135 void undo() override
136 {
138 modifiedGroup->removeSwatch(m_x, m_y);
140 }
141
142private:
145 QString m_groupName;
146 int m_x;
147 int m_y;
148};
149
150
152{
153 RemoveSwatchCommand(KoColorSet *colorSet, int column, int row, KisSwatchGroupSP group)
154 : m_colorSet(colorSet)
155 , m_swatch(group->getSwatch(column, row))
156 , m_group(group)
157 , m_x(column)
158 , m_y(row)
159
160 {
161 }
162
164 void redo() override
165 {
166 m_group->removeSwatch(m_x, m_y);
168 }
169
171 void undo() override
172 {
173 m_group->setSwatch(m_swatch, m_x, m_y);
175 }
176
177private:
181 int m_x;
182 int m_y;
183};
184
186{
187
188 ChangeGroupNameCommand(KoColorSet *colorSet, QString oldGroupName, const QString &newGroupName)
189 : m_colorSet(colorSet)
190 , m_oldGroupName(oldGroupName)
191 , m_newGroupName(newGroupName)
192 {
193 }
194
196
198 void redo() override
199 {
201 group->setName(m_newGroupName);
203 }
204
206 void undo() override
207 {
209 group->setName(m_oldGroupName);
211 }
212
213private:
217};
218
220{
221
222 MoveGroupCommand(KoColorSet *colorSet, QString groupName, const QString &groupNameInsertBefore)
223 : m_colorSet(colorSet)
224 , m_groupName(groupName)
225 , m_groupNameInsertBefore(groupNameInsertBefore)
226 {
227 int idx = 0;
228 for (const KisSwatchGroupSP &group : m_colorSet->d->swatchGroups) {
229
230 if (group->name() == m_groupName) {
231 m_oldIndex = idx;
232 }
233
234 if (group->name() == m_groupNameInsertBefore) {
235 m_newIndex = idx;
236 }
237
238 idx++;
239 }
240 }
241
243 void redo() override
244 {
247 {
249 KisSwatchGroupSP group = m_colorSet->d->swatchGroups.takeAt(m_oldIndex);
250 m_colorSet->d->swatchGroups.insert(m_newIndex, group);
251 Q_EMIT m_colorSet->layoutChanged();
252 }
253 }
254
255
257 void undo() override
258 {
260 KisSwatchGroupSP group = m_colorSet->d->swatchGroups.takeAt(m_newIndex);
261 m_colorSet->d->swatchGroups.insert(m_oldIndex, group);
262 Q_EMIT m_colorSet->layoutChanged();
263 }
264
265private:
267 QString m_groupName;
271};
272
274{
275
276 AddGroupCommand(KoColorSet *colorSet, QString groupName, int columnCount, int rowCount)
277 : m_colorSet(colorSet)
278 , m_groupName(groupName)
279 , m_columnCount(columnCount)
280 , m_rowCount(rowCount)
281 {
282 }
283
285 void redo() override
286 {
288 group->setName(m_groupName);
289 group->setColumnCount(m_columnCount);
290 group->setRowCount(m_rowCount);
292 m_colorSet->d->swatchGroups.append(group);
293 Q_EMIT m_colorSet->layoutChanged();
294 }
295
296
298 void undo() override
299 {
300 int idx = 0;
301 bool found = false;
302 for(const KisSwatchGroupSP &group : m_colorSet->d->swatchGroups) {
303 if (group->name() == m_groupName) {
304 found = true;
305 break;
306 }
307 idx++;
308 }
309 if (found) {
311 m_colorSet->d->swatchGroups.takeAt(idx);
312 Q_EMIT m_colorSet->layoutChanged();
313 }
314 }
315
316private:
318 QString m_groupName;
321};
322
324{
325 RemoveGroupCommand(KoColorSet *colorSet, QString groupName, bool keepColors = true)
326 : m_colorSet(colorSet)
327 , m_groupName(groupName)
328 , m_keepColors(keepColors)
329 , m_oldGroup(m_colorSet->getGroup(groupName))
330 , m_startingRow(m_colorSet->getGlobalGroup()->rowCount())
331 {
332 for (m_groupIndex = 0; m_groupIndex < colorSet->d->swatchGroups.size(); ++ m_groupIndex) {
333 if (colorSet->d->swatchGroups[m_groupIndex]->name() == m_oldGroup->name()) {
334 break;
335 }
336 }
337 }
338
340 void redo() override
341 {
342 if (m_keepColors) {
343 // put all colors directly below global
345 for (const KisSwatchGroup::SwatchInfo &info : m_oldGroup->infoList()) {
346 globalGroup->setSwatch(info.swatch,
347 info.column,
348 info.row + m_startingRow);
349 }
350 }
351
353 m_colorSet->d->swatchGroups.removeOne(m_oldGroup);
354 Q_EMIT m_colorSet->layoutChanged();
355 }
356
358 void undo() override
359 {
361 m_colorSet->d->swatchGroups.insert(m_groupIndex, m_oldGroup);
362
363 // remove all colors that were inserted into global
364 if (m_keepColors) {
366 for (const KisSwatchGroup::SwatchInfo &info : globalGroup->infoList()) {
367 m_oldGroup->setSwatch(info.swatch, info.column, info.row - m_startingRow);
368 globalGroup->removeSwatch(info.column,
369 info.row + m_startingRow);
370 }
371 }
372 Q_EMIT m_colorSet->layoutChanged();
373 }
374
375private:
377 QString m_groupName;
382};
383
385{
387 : m_colorSet(colorSet)
388 , m_OldColorSet(new KoColorSet(*colorSet))
389 {
390 }
391
392
393 ~ClearCommand() override
394 {
395 delete m_OldColorSet;
396 }
397
399 void redo() override
400 {
401 m_colorSet->d->swatchGroups.clear();
403 global->setName(KoColorSet::GLOBAL_GROUP_NAME);
405 m_colorSet->d->swatchGroups.append(global);
406 Q_EMIT m_colorSet->layoutChanged();
407 }
408
409
411 void undo() override
412 {
414 m_colorSet->d->swatchGroups = m_OldColorSet->d->swatchGroups;
416 Q_EMIT m_colorSet->layoutChanged();
417 }
418
419private:
422};
423
425{
426 SetColumnCountCommand(KoColorSet *colorSet, int columnCount)
427 : m_colorSet(colorSet)
428 , m_columnsCount(columnCount)
429 , m_oldColumnsCount(colorSet->columnCount())
430 {
431 }
432
434 void redo() override
435 {
437 for (KisSwatchGroupSP &group : m_colorSet->d->swatchGroups) {
438 group->setColumnCount(m_columnsCount);
439 }
440 m_colorSet->d->columns = m_columnsCount;
441 Q_EMIT m_colorSet->layoutChanged();
442 }
443
444
446 void undo() override
447 {
449 for (KisSwatchGroupSP &group : m_colorSet->d->swatchGroups) {
450 group->setColumnCount(m_oldColumnsCount);
451 }
452 m_colorSet->d->columns = m_oldColumnsCount;
453 Q_EMIT m_colorSet->layoutChanged();
454 }
455
456private:
460};
461
462
464{
465 SetCommentCommand(KoColorSet *colorSet, const QString &comment)
466 : m_colorSet(colorSet)
467 , m_comment(comment)
468 , m_oldComment(colorSet->comment())
469 {
470 }
471
473 void redo() override
474 {
475 m_colorSet->d->comment = m_comment;
476 }
477
478
480 void undo() override
481 {
482 m_colorSet->d->comment = m_oldComment;
483 }
484
485private:
487 QString m_comment;
489};
490
492{
494 : m_colorSet(colorSet)
495 , m_paletteType(paletteType)
496 , m_oldPaletteType(colorSet->paletteType())
497 {
498 }
499
501 void redo() override
502 {
503 m_colorSet->d->paletteType = m_paletteType;
504 QStringList fileName = m_colorSet->filename().split(".");
505 fileName.last() = suffix(m_paletteType).replace(".", "");
506 m_colorSet->setFilename(fileName.join("."));
507 }
508
509
511 void undo() override
512 {
513 m_colorSet->d->paletteType = m_oldPaletteType;
514 QStringList fileName = m_colorSet->filename().split(".");
515 fileName.last() = suffix(m_oldPaletteType).replace(".", "");
516 m_colorSet->setFilename(fileName.join("."));
517 }
518
519private:
520
521 QString suffix(KoColorSet::PaletteType paletteType) const
522 {
523 QString suffix;
524 switch(paletteType) {
525 case KoColorSet::GPL:
526 suffix = ".gpl";
527 break;
528 case KoColorSet::ACT:
529 suffix = ".act";
530 break;
533 suffix = ".pal";
534 break;
535 case KoColorSet::ACO:
536 suffix = ".aco";
537 break;
538 case KoColorSet::XML:
539 suffix = ".xml";
540 break;
541 case KoColorSet::KPL:
542 suffix = ".kpl";
543 break;
544 case KoColorSet::SBZ:
545 suffix = ".sbz";
546 break;
547 case KoColorSet::CSS:
548 suffix = ".css";
549 break;
550 default:
552 }
553 return suffix;
554 }
555
559};
560
561KoColorSet::KoColorSet(const QString& filename)
562 : QObject()
563 , KoResource(filename)
564 , d(new Private(this))
565{
566 connect(&d->undoStack, SIGNAL(canUndoChanged(bool)), this, SLOT(canUndoChanged(bool)));
567 connect(&d->undoStack, SIGNAL(canRedoChanged(bool)), this, SLOT(canRedoChanged(bool)));
568}
569
572 : QObject()
573 , KoResource(rhs)
574 , d(new Private(this))
575{
576 d->paletteType = rhs.d->paletteType;
577 d->data = rhs.d->data;
578 d->comment = rhs.d->comment;
579 d->swatchGroups = rhs.d->swatchGroups;
580}
581
585
587{
588 return KoResourceSP(new KoColorSet(*this));
589}
590
591bool KoColorSet::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
592{
593 Q_UNUSED(resourcesInterface);
594
595 if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
596
597 d->data = dev->readAll();
598
599 Q_ASSERT(d->data.size() != 0);
600
601 return d->init();
602}
603
604bool KoColorSet::saveToDevice(QIODevice *dev) const
605{
606 bool res = false;
607 switch(d->paletteType) {
608 case GPL:
609 res = d->saveGpl(dev);
610 break;
611 default:
612 res = d->saveKpl(dev);
613 }
614
615 if (res) const_cast<KoColorSet*>(this)->setDirty(false);
616 d->undoStack.clear();
617
618 return res;
619}
620
621bool KoColorSet::fromByteArray(QByteArray &data, KisResourcesInterfaceSP resourcesInterface)
622{
623 QBuffer buf(&data);
624 buf.open(QIODevice::ReadOnly);
625 return loadFromDevice(&buf, resourcesInterface);
626}
627
629{
630 return d->paletteType;
631}
632
634{
635 if (d->isLocked || paletteType == d->paletteType) return;
636
638
639 d->undoStack.push(cmd);
640}
641
642
643void KoColorSet::addSwatch(const KisSwatch &swatch, const QString &groupName, int column, int row)
644{
645 if (d->isLocked) return;
646
647 AddSwatchCommand *cmd = new AddSwatchCommand(this, swatch, groupName, column, row);
648
649 d->undoStack.push(cmd);
650
651}
652
653void KoColorSet::removeSwatch(int column, int row, KisSwatchGroupSP group)
654{
655 if (d->isLocked) return;
656 RemoveSwatchCommand *cmd = new RemoveSwatchCommand(this, column, row, group);
657
658 d->undoStack.push(cmd);
659}
660
662{
663 if (d->isLocked) return;
664
665 ClearCommand *cmd = new ClearCommand(this);
666
667 d->undoStack.push(cmd);
668}
669
670KisSwatch KoColorSet::getColorGlobal(quint32 column, quint32 row) const
671{
672 KisSwatchGroupSP group = getGroup(row);
673 Q_ASSERT(group);
674
675 int titleRow = startRowForGroup(group->name());
676 int rowInGroup = -1;
677
678 if (group->name().isEmpty()) {
679 rowInGroup = (int)row - titleRow;
680 }
681 else {
682 rowInGroup = (int)row - (titleRow + 1);
683 }
684
685 Q_ASSERT((isGroupTitleRow(titleRow) && titleRow > 0) || titleRow == 0);
686 Q_ASSERT(rowInGroup < group->rowCount());
687
688 return group->getSwatch(column, rowInGroup);
689
690}
691
692KisSwatch KoColorSet::getSwatchFromGroup(quint32 column, quint32 row, QString groupName) const
693{
694 KisSwatch swatch;
695 for (const KisSwatchGroupSP &group: d->swatchGroups) {
696 if (group->name() == groupName) {
697 if (group->checkSwatchExists(column, row)) {
698 swatch = group->getSwatch(column, row);
699 }
700 break;
701 }
702 }
703 return swatch;
704}
705
707{
708 QStringList groupNames;
709 for (const KisSwatchGroupSP &group : d->swatchGroups) {
710 groupNames << group->name();
711 }
712 return groupNames;
713}
714
716{
717 int idx = 0;
718 for (const KisSwatchGroupSP &group : d->swatchGroups) {
719 idx += group->rowCount();
720 if (group->name() != KoColorSet::GLOBAL_GROUP_NAME) {
721 idx++;
722 }
723 if (idx == row) {
724 return true;
725 }
726 }
727 return false;
728}
729
730int KoColorSet::startRowForGroup(const QString &groupName) const
731{
732 if (groupName.isEmpty()) return 0;
733
734 int row = 0;
735 for (const KisSwatchGroupSP &group : d->swatchGroups) {
736 if (group->name() == groupName) {
737 return row;
738 }
739 row += group->rowCount();
740 if (group->name() != KoColorSet::GLOBAL_GROUP_NAME) {
741 row++;
742 }
743 }
744 return row;
745}
746
747int KoColorSet::rowNumberInGroup(int rowNumber) const
748{
749 if (isGroupTitleRow(rowNumber)) {
750 return -1;
751 }
752
753 int rowInGroup = -1;
754 for (int i = rowNumber; i > -1; i--) {
755 if (isGroupTitleRow(i)) {
756 return rowInGroup;
757 }
758 else {
759 rowInGroup++;
760 }
761 }
762
763 return rowInGroup;
764}
765
766void KoColorSet::setModified(bool _modified)
767{
768 setDirty(_modified);
769 if (_modified) {
770 Q_EMIT modified();
771 }
772}
773
774void KoColorSet::notifySwatchChanged(const QString &groupName, int column, int row)
775{
776 int startRow = 0;
777 if (!groupName.isEmpty()) {
778 startRow = startRowForGroup(groupName) + 1;
779 }
780 Q_EMIT entryChanged(column, startRow + row);
781}
782
783
785{
786 if (canUndo) {
787 setModified(true);
788 }
789 else {
790 setModified(false);
791 }
792}
793
794void KoColorSet::canRedoChanged(bool /*canRedo*/)
795{
796 if (d->undoStack.canUndo()) {
797 setModified(true);
798 }
799 else {
800 setModified(false);
801 }
802}
803
804void KoColorSet::changeGroupName(const QString &oldGroupName, const QString &newGroupName)
805{
806 if (!swatchGroupNames().contains(oldGroupName) || (oldGroupName == newGroupName) || d->isLocked) return;
807
808 ChangeGroupNameCommand *cmd = new ChangeGroupNameCommand(this, oldGroupName, newGroupName);
809 d->undoStack.push(cmd);
810}
811
813{
814 if (d->isLocked || (columns == d->columns)) return;
815
816 SetColumnCountCommand *cmd = new SetColumnCountCommand (this, columns);
817
818 d->undoStack.push(cmd);
819}
820
822{
823 Q_ASSERT(d->swatchGroups.size() > 0);
824
825 return d->swatchGroups.first()->columnCount();
826}
827
829{
830 return d->comment;
831}
832
833void KoColorSet::setComment(QString comment)
834{
835 if (d->isLocked || comment == d->comment) return;
836
838
839 d->undoStack.push(cmd);
840}
841
842void KoColorSet::addGroup(const QString &groupName, int columnCount, int rowCount)
843{
844 if (swatchGroupNames().contains(groupName) || d->isLocked) return;
845
846 AddGroupCommand *cmd = new AddGroupCommand(this, groupName, columnCount, rowCount);
847
848 d->undoStack.push(cmd);
849}
850
851void KoColorSet::moveGroup(const QString &groupName, const QString &groupNameInsertBefore)
852{
853 QStringList groupNames = swatchGroupNames();
854 if (!groupNames.contains(groupName)
855 || !groupNames.contains(groupNameInsertBefore)
856 || d->isLocked) return;
857
858 MoveGroupCommand *cmd = new MoveGroupCommand(this, groupName, groupNameInsertBefore);
859
860 d->undoStack.push(cmd);
861
862}
863
864void KoColorSet::removeGroup(const QString &groupName, bool keepColors)
865{
866
867 if (!swatchGroupNames().contains(groupName) || (groupName == GLOBAL_GROUP_NAME) || d->isLocked) return;
868
869 RemoveGroupCommand *cmd = new RemoveGroupCommand(this, groupName, keepColors);
870
871 d->undoStack.push(cmd);
872}
873
875{
876 return (d->paletteType == GPL) ? ".gpl" : ".kpl";
877}
878
880{
881 return &d->undoStack;
882}
883
885{
886 d->isLocked = lock;
887}
888
890{
891 return d->isLocked;
892}
893
895{
896 int res = 0;
897 for (const KisSwatchGroupSP &group : d->swatchGroups) {
898 res += group->rowCount();
899 }
900 return res;
901}
902
904{
905 return rowCount() + d->swatchGroups.size() - 1;
906}
907
909{
910 int colorCount = 0;
911 for (const KisSwatchGroupSP &group : d->swatchGroups) {
912 colorCount += group->colorCount();
913 }
914 return colorCount;
915}
916
918{
919 int slotCount = 0;
920 for (const KisSwatchGroupSP &group : d->swatchGroups) {
921 slotCount += group->slotCount();
922 }
923 return slotCount;
924}
925
926KisSwatchGroupSP KoColorSet::getGroup(const QString &name) const
927{
928 for (KisSwatchGroupSP &group : d->swatchGroups) {
929 if (group->name() == name) {
930 return group;
931 }
932 }
933 return 0;
934}
935
937{
938// qDebug() << "------------";
939
940 if (row >= rowCountWithTitles()) return nullptr;
941
942 int currentRow = 0;
943
944 for (KisSwatchGroupSP &group : d->swatchGroups) {
945
946 int groupRowCount = group->rowCount();
947 if (group->name() != KoColorSet::GLOBAL_GROUP_NAME) {
948 groupRowCount++;
949 }
950
951// qDebug() << group->name()
952// << "row" << row << "currentRow" << currentRow << "group rowcount" << groupRowCount
953// << "hit" << (currentRow <= row && row < currentRow + groupRowCount);
954
955 bool hit = (currentRow <= row && row < currentRow + groupRowCount);
956
957 if (hit) {
958 return group;
959 }
960
961 currentRow += group->rowCount();
962
963 if (group->name() != KoColorSet::GLOBAL_GROUP_NAME) {
964 currentRow += 1;
965 }
966
967 if (currentRow >= rowCountWithTitles()) return nullptr;
968 }
969
970 return nullptr;
971
972}
973
975{
976 Q_ASSERT(d->swatchGroups.size() > 0);
977 Q_ASSERT(d->swatchGroups.first()->name() == GLOBAL_GROUP_NAME);
978 return d->swatchGroups.first();
979}
980
981KisSwatchGroup::SwatchInfo KoColorSet::getClosestSwatchInfo(KoColor compare, bool useGivenColorSpace) const
982{
983 KisSwatchGroup::SwatchInfo closestSwatch;
984
985 quint8 highestPercentage = 0;
986 quint8 testPercentage = 0;
987
988 for (const KisSwatchGroupSP &group : d->swatchGroups) {
989 for (const KisSwatchGroup::SwatchInfo &currInfo : group->infoList()) {
990 KoColor color = currInfo.swatch.color();
991 if (useGivenColorSpace == true && compare.colorSpace() != color.colorSpace()) {
992 color.convertTo(compare.colorSpace());
993
994 } else if (compare.colorSpace() != color.colorSpace()) {
995 compare.convertTo(color.colorSpace());
996 }
997 testPercentage = (255 - compare.colorSpace()->difference(compare.data(), color.data()));
998 if (testPercentage > highestPercentage)
999 {
1000 highestPercentage = testPercentage;
1001 closestSwatch = currInfo;
1002 }
1003 }
1004 }
1005 return closestSwatch;
1006}
1007
1009{
1010 int rows = 0;
1011
1012 // Determine the last filled row in each group
1013 for (const KisSwatchGroupSP &group : d->swatchGroups) {
1014 int lastRowInGroup = 0;
1015 for (const KisSwatchGroup::SwatchInfo &info : group->infoList()) {
1016 lastRowInGroup = qMax(lastRowInGroup, info.row);
1017 }
1018 rows += (lastRowInGroup + 1);
1019 }
1020
1021 QImage img(d->global()->columnCount() * 4, rows * 4, QImage::Format_ARGB32);
1022 QPainter gc(&img);
1023 gc.fillRect(img.rect(), Qt::darkGray);
1024
1025 int lastRow = 0;
1026 for (const KisSwatchGroupSP &group : d->swatchGroups) {
1027 int lastRowGroup = 0;
1028 for (const KisSwatchGroup::SwatchInfo &info : group->infoList()) {
1029 QColor c = info.swatch.color().toQColor();
1030 gc.fillRect(info.column * 4, (lastRow + info.row) * 4, 4, 4, c);
1031 lastRowGroup = qMax(lastRowGroup, info.row);
1032 }
1033 lastRow += (lastRowGroup + 1);
1034 }
1035
1036 setImage(img);
1037}
1038
1039/********************************KoColorSet::Private**************************/
1040
1042 : colorSet(a_colorSet)
1043{
1046 group->setName(KoColorSet::GLOBAL_GROUP_NAME);
1047 swatchGroups.clear();
1048 swatchGroups.append(group);
1049}
1050
1051KoColorSet::PaletteType KoColorSet::Private::detectFormat(const QString &fileName, const QByteArray &ba)
1052{
1053 QFileInfo fi(fileName);
1054
1055 // .pal
1056 if (ba.startsWith("RIFF") && ba.indexOf("PAL data", 8)) {
1057 return KoColorSet::RIFF_PAL;
1058 }
1059 // .gpl
1060 else if (ba.startsWith("GIMP Palette")) {
1061 return KoColorSet::GPL;
1062 }
1063 // .pal
1064 else if (ba.startsWith("JASC-PAL")) {
1065 return KoColorSet::PSP_PAL;
1066 }
1067 else if (ba.contains("krita/x-colorset") || ba.contains("application/x-krita-palette")) {
1068 return KoColorSet::KPL;
1069 }
1070 else if (fi.suffix().toLower() == "aco") {
1071 return KoColorSet::ACO;
1072 }
1073 else if (fi.suffix().toLower() == "act") {
1074 return KoColorSet::ACT;
1075 }
1076 else if (fi.suffix().toLower() == "xml") {
1077 return KoColorSet::XML;
1078 }
1079 else if (fi.suffix().toLower() == "sbz") {
1080 return KoColorSet::SBZ;
1081 }
1082 else if (fi.suffix().toLower() == "ase" || ba.startsWith("ASEF")) {
1083 return KoColorSet::ASE;
1084 }
1085 else if (fi.suffix().toLower() == "acb" || ba.startsWith("8BCB")) {
1086 return KoColorSet::ACB;
1087 }
1088 else if (fi.suffix().toLower() == "css") {
1089 return KoColorSet::CSS;
1090 }
1091 return KoColorSet::UNKNOWN;
1092}
1093
1095{
1096 KisSwatch colorEntry;
1097 // It's a color, retrieve it
1098 QXmlStreamAttributes colorProperties = xml->attributes();
1099 auto colorName = colorProperties.value("NAME");
1100 colorEntry.setName(colorName.isEmpty() || colorName.isNull() ? i18n("Untitled") : colorName.toString());
1101
1102 // RGB or CMYK?
1103 if (colorProperties.hasAttribute("RGB")) {
1104 dbgPigment << "Color " << colorProperties.value("NAME") << ", RGB " << colorProperties.value("RGB");
1105
1106 KoColor currentColor(KoColorSpaceRegistry::instance()->rgb8());
1107 auto colorValue = colorProperties.value("RGB");
1108
1109 if (colorValue.length() != 7 && colorValue.at(0) != '#') { // Color is a hexadecimal number
1110 xml->raiseError("Invalid rgb8 color (malformed): " + colorValue);
1111 return;
1112 } else {
1113 bool rgbOk;
1114 quint32 rgb = colorValue.mid(1).toUInt(&rgbOk, 16);
1115 if (!rgbOk) {
1116 xml->raiseError("Invalid rgb8 color (unable to convert): " + colorValue);
1117 return;
1118 }
1119
1120 quint8 r = rgb >> 16 & 0xff;
1121 quint8 g = rgb >> 8 & 0xff;
1122 quint8 b = rgb & 0xff;
1123
1124 dbgPigment << "Color parsed: "<< r << g << b;
1125
1126 currentColor.data()[0] = r;
1127 currentColor.data()[1] = g;
1128 currentColor.data()[2] = b;
1129 currentColor.setOpacity(OPACITY_OPAQUE_U8);
1130 colorEntry.setColor(currentColor);
1131
1132 set->addSwatch(colorEntry);
1133
1134 while(xml->readNextStartElement()) {
1135 //ignore - these are all unknown or the /> element tag
1136 xml->skipCurrentElement();
1137 }
1138 return;
1139 }
1140 }
1141 else if (colorProperties.hasAttribute("CMYK")) {
1142 dbgPigment << "Color " << colorProperties.value("NAME") << ", CMYK " << colorProperties.value("CMYK");
1143
1144 KoColor currentColor(KoColorSpaceRegistry::instance()->colorSpace(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id(), QString()));
1145 auto colorValue = colorProperties.value("CMYK");
1146
1147 if (colorValue.length() != 9 && colorValue.at(0) != '#') { // Color is a hexadecimal number
1148 xml->raiseError("Invalid cmyk color (malformed): " % colorValue);
1149 return;
1150 }
1151 else {
1152 bool cmykOk;
1153 quint32 cmyk = colorValue.mid(1).toUInt(&cmykOk, 16); // cmyk uses the full 32 bits
1154 if (!cmykOk) {
1155 xml->raiseError("Invalid cmyk color (unable to convert): " % colorValue);
1156 return;
1157 }
1158
1159 quint8 c = cmyk >> 24 & 0xff;
1160 quint8 m = cmyk >> 16 & 0xff;
1161 quint8 y = cmyk >> 8 & 0xff;
1162 quint8 k = cmyk & 0xff;
1163
1164 dbgPigment << "Color parsed: "<< c << m << y << k;
1165
1166 currentColor.data()[0] = c;
1167 currentColor.data()[1] = m;
1168 currentColor.data()[2] = y;
1169 currentColor.data()[3] = k;
1170 currentColor.setOpacity(OPACITY_OPAQUE_U8);
1171 colorEntry.setColor(currentColor);
1172
1173 set->addSwatch(colorEntry);
1174
1175 while(xml->readNextStartElement()) {
1176 //ignore - these are all unknown or the /> element tag
1177 xml->skipCurrentElement();
1178 }
1179 return;
1180 }
1181 }
1182 else {
1183 xml->raiseError("Unknown color space for color " + colorEntry.name());
1184 }
1185}
1186
1188{
1189
1190 //1. Get name
1191 QXmlStreamAttributes paletteProperties = xml->attributes();
1192 auto paletteName = paletteProperties.value("Name");
1193 dbgPigment << "Processed name of palette:" << paletteName;
1194 set->setName(paletteName.toString());
1195
1196 //2. Inside the SCRIBUSCOLORS, there are lots of colors. Retrieve them
1197
1198 while(xml->readNextStartElement()) {
1199 auto currentElement = xml->name();
1200 if (currentElement.compare(QString("COLOR"), Qt::CaseInsensitive) == 0) {
1201 scribusParseColor(set, xml);
1202 }
1203 else {
1204 xml->skipCurrentElement();
1205 }
1206 }
1207
1208 if(xml->hasError()) {
1209 return false;
1210 }
1211
1212 return true;
1213}
1214
1216{
1217 quint8 val;
1218 quint64 read = io->read((char*)&val, 1);
1219 if (read != 1) return false;
1220 return val;
1221}
1222
1223quint16 KoColorSet::Private::readShort(QIODevice *io) {
1224 quint16 val;
1225 quint64 read = io->read((char*)&val, 2);
1226 if (read != 2) return false;
1227 return qFromBigEndian(val);
1228}
1229
1230qint32 KoColorSet::Private::readInt(QIODevice *io)
1231{
1232 qint32 val;
1233 quint64 read = io->read((char*)&val, 4);
1234 if (read != 4) return false;
1235 return qFromBigEndian(val);
1236}
1237
1239{
1240 float val;
1241 quint64 read = io->read((char*)&val, 4);
1242 if (read != 4) return false;
1243 return qFromBigEndian(val);
1244}
1245
1246QString KoColorSet::Private::readUnicodeString(QIODevice *io, bool sizeIsInt)
1247{
1248 QString unicode;
1249 qint32 size = 0;
1250 if (sizeIsInt) {
1251 size = readInt(io);
1252 } else {
1253 size = readShort(io)-1;
1254 }
1255 if (size>0) {
1256 QByteArray ba = io->read(size*2);
1257 if (ba.size() == int(size)*2) {
1258 QTextCodec *Utf16Codec = QTextCodec::codecForName("UTF-16BE");
1259 unicode = Utf16Codec->toUnicode(ba);
1260 } else {
1261 warnPigment << "Unicode name block is the wrong size" << colorSet->filename();
1262 }
1263 }
1264 if (!sizeIsInt) {
1265 readShort(io); // when the size is quint16, the string is 00 terminated;
1266 }
1267 return unicode.trimmed();
1268}
1269
1271{
1272 // just in case this is a reload (eg by KoEditColorSetDialog),
1273 swatchGroups.clear();
1274 KisSwatchGroupSP globalGroup(new KisSwatchGroup);
1275 globalGroup->setName(KoColorSet::GLOBAL_GROUP_NAME);
1276 swatchGroups.append(globalGroup);
1277 undoStack.clear();
1278
1279 if (colorSet->filename().isNull()) {
1280 warnPigment << "Cannot load palette" << colorSet->name() << "there is no filename set";
1281 return false;
1282 }
1283 if (data.isNull()) {
1284 QFile file(colorSet->filename());
1285 if (file.size() == 0) {
1286 warnPigment << "Cannot load palette" << colorSet->name() << "there is no data available";
1287 return false;
1288 }
1289 file.open(QIODevice::ReadOnly);
1290 data = file.readAll();
1291 file.close();
1292 }
1293
1294 bool res = false;
1295 paletteType = detectFormat(colorSet->filename(), data);
1296 switch(paletteType) {
1297 case GPL:
1298 res = loadGpl();
1299 break;
1300 case ACT:
1301 res = loadAct();
1302 break;
1303 case RIFF_PAL:
1304 res = loadRiff();
1305 break;
1306 case PSP_PAL:
1307 res = loadPsp();
1308 break;
1309 case ACO:
1310 res = loadAco();
1311 break;
1312 case XML:
1313 res = loadXml();
1314 break;
1315 case KPL:
1316 res = loadKpl();
1317 break;
1318 case SBZ:
1319 res = loadSbz();
1320 break;
1321 case ASE:
1322 res = loadAse();
1323 break;
1324 case ACB:
1325 res = loadAcb();
1326 break;
1327 case CSS:
1328 res = loadCss();
1329 break;
1330 default:
1331 res = false;
1332 }
1333 if (paletteType != KPL) {
1334 int rowCount = global()->colorCount() / global()->columnCount();
1335 if (global()->colorCount() % global()->columnCount() > 0) {
1336 rowCount ++;
1337 }
1338 global()->setRowCount(rowCount);
1339 }
1340 colorSet->setValid(res);
1341 colorSet->updateThumbnail();
1342
1343 data.clear();
1344 undoStack.clear();
1345
1346 return res;
1347}
1348
1349bool KoColorSet::Private::saveGpl(QIODevice *dev) const
1350{
1351 Q_ASSERT(dev->isOpen());
1352 Q_ASSERT(dev->isWritable());
1353
1354 QTextStream stream(dev);
1356 stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n";
1357
1358 KisSwatchGroupSP global = colorSet->getGlobalGroup();
1359 for (int y = 0; y < global->rowCount(); y++) {
1360 for (int x = 0; x < colorSet->columnCount(); x++) {
1361 if (!global->checkSwatchExists(x, y)) {
1362 continue;
1363 }
1364 const KisSwatch& entry = global->getSwatch(x, y);
1365 QColor c = entry.color().toQColor();
1366 stream << c.red() << " " << c.green() << " " << c.blue() << "\t";
1367 if (entry.name().isEmpty())
1368 stream << "Untitled\n";
1369 else
1370 stream << entry.name() << "\n";
1371 }
1372 }
1373
1374 return true;
1375}
1376
1378{
1379 if (data.isEmpty() || data.isNull() || data.length() < 50) {
1380 warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
1381 return false;
1382 }
1383
1384 quint32 index = 0;
1385
1386 QStringList lines = readAllLinesSafe(&data);
1387
1388 if (lines.size() < 3) {
1389 warnPigment << "Not enough lines in palette file: " << colorSet->filename();
1390 return false;
1391 }
1392
1393 QString columnsText;
1394 qint32 r, g, b;
1395 KisSwatch swatch;
1396
1397 // Read name
1398 if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) {
1399 warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
1400 return false;
1401 }
1402
1403 // translated name will be in a tooltip, here don't translate
1404 colorSet->setName(lines[1].split(":")[1].trimmed());
1405
1406 index = 2;
1407
1408 // Read columns
1409 int columns = 0;
1410 if (lines[index].toLower().contains("columns")) {
1411 columnsText = lines[index].split(":")[1].trimmed();
1412 columns = columnsText.toInt();
1413 if (columns > MAXIMUM_ALLOWED_COLUMNS) {
1414 warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead";
1415 global()->setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
1416 }
1417 else {
1418 global()->setColumnCount(columns);
1419 }
1420 index = 3;
1421 }
1422
1423
1424 for (qint32 i = index; i < lines.size(); i++) {
1425 if (lines[i].startsWith('#')) {
1426 comment += lines[i].mid(1).trimmed() + ' ';
1427 } else if (!lines[i].isEmpty()) {
1428 QStringList a = lines[i].replace('\t', ' ').split(' ', Qt::SkipEmptyParts);
1429
1430 if (a.count() < 3) {
1431 continue;
1432 }
1433
1434 r = qBound(0, a[0].toInt(), 255);
1435 g = qBound(0, a[1].toInt(), 255);
1436 b = qBound(0, a[2].toInt(), 255);
1437
1438 swatch.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
1439
1440 for (int i = 0; i != 3; i++) {
1441 a.pop_front();
1442 }
1443 QString name = a.join(" ");
1444 swatch.setName(name.isEmpty() || name == "Untitled" ? i18n("Untitled") : name);
1445
1446 global()->addSwatch(swatch);
1447 }
1448 }
1449 return true;
1450}
1451
1453{
1454 QFileInfo info(colorSet->filename());
1455 colorSet->setName(info.completeBaseName());
1456 KisSwatch swatch;
1457 int numOfTriplets = int(data.size() / 3);
1458 for (int i = 0; i < numOfTriplets * 3; i += 3) {
1459 quint8 r = data[i];
1460 quint8 g = data[i+1];
1461 quint8 b = data[i+2];
1462 swatch.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
1463 global()->addSwatch(swatch);
1464 }
1465 return true;
1466}
1467
1469{
1470 // https://worms2d.info/Palette_file
1471 QFileInfo info(colorSet->filename());
1472 colorSet->setName(info.completeBaseName());
1473 KisSwatch swatch;
1474
1475 RiffHeader header;
1476 memcpy(&header, data.constData(), sizeof(RiffHeader));
1477 header.colorcount = qFromBigEndian(header.colorcount);
1478
1479 for (int i = sizeof(RiffHeader);
1480 (i < (int)(sizeof(RiffHeader) + (header.colorcount * 4)) && i < data.size());
1481 i += 4) {
1482 quint8 r = data[i];
1483 quint8 g = data[i+1];
1484 quint8 b = data[i+2];
1485 swatch.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
1486 colorSet->getGlobalGroup()->addSwatch(swatch);
1487 }
1488 return true;
1489}
1490
1491
1493{
1494 QFileInfo info(colorSet->filename());
1495 colorSet->setName(info.completeBaseName());
1496 KisSwatch swatch;
1497 qint32 r, g, b;
1498
1499 QStringList l = readAllLinesSafe(&data);
1500 if (l.size() < 4) return false;
1501 if (l[0] != "JASC-PAL") return false;
1502 if (l[1] != "0100") return false;
1503
1504 int entries = l[2].toInt();
1505
1506 KisSwatchGroupSP global = colorSet->getGlobalGroup();
1507
1508 for (int i = 0; i < entries; ++i) {
1509
1510 QStringList a = l[i + 3].replace('\t', ' ').split(' ', Qt::SkipEmptyParts);
1511
1512 if (a.count() != 3) {
1513 continue;
1514 }
1515
1516 r = qBound(0, a[0].toInt(), 255);
1517 g = qBound(0, a[1].toInt(), 255);
1518 b = qBound(0, a[2].toInt(), 255);
1519
1520 swatch.setColor(KoColor(QColor(r, g, b),
1522
1523 QString name = a.join(" ");
1524 swatch.setName(name.isEmpty() ? i18n("Untitled") : name);
1525
1526 global->addSwatch(swatch);
1527 }
1528 return true;
1529}
1530
1532{
1533 QFileInfo info(colorSet->filename());
1534 colorSet->setName(info.completeBaseName());
1535
1536 QString text = readAllLinesSafe(&data).join("").replace("\t", "").replace(" ", "");
1537
1538 QRegularExpression re("/\\*.*?\\*/");
1539
1540 text.remove(re); // Remove comments
1541
1542 KisSwatch swatch;
1543
1544 // Regex to detect a color in the palette
1545 QRegularExpression palette("(.*?){(?:[^:;]+:[^;]+;)*?color:(.*?)(?:;.*?)*?}");
1546
1547 QRegularExpressionMatchIterator colors = palette.globalMatch(text);
1548
1549 if (!colors.hasNext()) {
1550 warnPigment << "No color found in CSS palette : " << colorSet->filename();
1551 return false;
1552 }
1553
1554 while (colors.hasNext()) {
1555 QRegularExpressionMatch match = colors.next();
1556 QString colorInfo = match.captured();
1557 QString colorName = match.captured(1);
1558 QString colorValue = match.captured(2);
1559
1560 if (!colorInfo.startsWith(".") || colorValue.isEmpty()) {
1561 warnPigment << "Illegal CSS palette syntax : " << colorInfo;
1562 return false;
1563 }
1564
1565 QColor qColor;
1566
1567 colorName.remove(".");
1568 swatch.setName(colorName);
1569
1570 if (colorValue.startsWith("rgb")) {
1571 QStringList color;
1572
1573 if (colorValue.startsWith("rgba")) {
1574 colorValue.remove("rgba(").remove(")");
1575 color = colorValue.split(",");
1576
1577 if (color.size() != 4) {
1578 warnPigment << "Invalid RGBA color definition : " << colorInfo;
1579 return false;
1580 }
1581
1582 int alpha = color[3].toFloat() * 255;
1583
1584 if (alpha < 0 || alpha > 255) {
1585 warnPigment << "Invalid alpha parameter : " << colorInfo;
1586 return false;
1587 }
1588 }
1589 else {
1590 colorValue.remove("rgb(").remove(")");
1591
1592 color = colorValue.split(",");
1593
1594 if (color.size() != 3) {
1595 warnPigment << "Invalid RGB color definition : " << colorInfo;
1596 return false;
1597 }
1598 }
1599
1600 int rgb[3];
1601
1602 for (int i = 0; i < 3; i++) {
1603 if (color[i].endsWith("%")) {
1604 color[i].replace("%", "");
1605 rgb[i] = color[i].toFloat() / 100 * 255 ;
1606 }
1607 else {
1608 rgb[i] = color[i].toInt();
1609 };
1610 }
1611
1612 qColor = QColor(rgb[0], rgb[1], rgb[2]);
1613 }
1614 else if (colorValue.startsWith("hsl")) {
1615 QStringList color;
1616
1617 if (colorValue.startsWith("hsla")) {
1618 colorValue.remove("hsla(").remove(")").replace("%", "");
1619 color = colorValue.split(",");
1620 if (color.size() != 4) {
1621 warnPigment << "Invalid HSLA color definition : " << colorInfo;
1622 return false;
1623 }
1624
1625 float alpha = color[3].toFloat();
1626
1627 if (alpha < 0.0 || alpha > 1.0) {
1628 warnPigment << "Invalid alpha parameter : " << colorInfo;
1629 return false;
1630 }
1631
1632 }
1633 else {
1634 colorValue.remove("hsl(").remove(")").replace("%", "");
1635 color = colorValue.split(",");
1636 if (color.size() != 3) {
1637 warnPigment << "Invalid HSL color definition : " << colorInfo;
1638 return false;
1639 }
1640 }
1641
1642 float hue = color[0].toFloat() / 359;
1643 float saturation = color[1].toFloat() / 100;
1644 float lightness = color[2].toFloat() / 100;
1645
1646 if (hue < 0.0 || hue > 1.0) {
1647 warnPigment << "Invalid hue parameter : " << colorInfo;
1648 return false;
1649 }
1650
1651 if (saturation < 0.0 || saturation > 1.0) {
1652 warnPigment << "Invalid saturation parameter : " << colorInfo;
1653 return false;
1654 }
1655
1656 if (lightness < 0.0 || lightness > 1.0) {
1657 warnPigment << "Invalid lightness parameter : " << colorInfo;
1658 return false;
1659 }
1660
1661 qColor = QColor::fromHslF(hue, saturation, lightness);
1662
1663 }
1664 else if (colorValue.startsWith("#")) {
1665 if (colorValue.size() == 9) {
1666 // Convert the CSS format #RRGGBBAA to #RRGGBB
1667 // Due to QColor's 8 digits format being #AARRGGBB and that we do not load the alpha channel
1668 colorValue.truncate(7);
1669 }
1670
1671 qColor = QColor(colorValue);
1672 }
1673 else {
1674 warnPigment << "Unknown color declaration : " << colorInfo;
1675 return false;
1676 }
1677
1678 if (!qColor.isValid()) {
1679 warnPigment << "Invalid color definition : " << colorInfo;
1680 return false;
1681 }
1682
1683 swatch.setColor(KoColor(qColor, KoColorSpaceRegistry::instance()->rgb8()));
1684
1685 global()->addSwatch(swatch);
1686 }
1687
1688 return true;
1689}
1690
1691const KoColorProfile *KoColorSet::Private::loadColorProfile(QScopedPointer<KoStore> &store,
1692 const QString &path,
1693 const QString &modelId,
1694 const QString &colorDepthId)
1695{
1696 if (!store->open(path)) {
1697 return nullptr;
1698 }
1699
1700 QByteArray bytes = store->read(store->size());
1701 store->close();
1702
1704 ->createColorProfile(modelId, colorDepthId, bytes);
1705 if (!profile || !profile->valid()) {
1706 return nullptr;
1707 }
1708
1710 return profile;
1711}
1712
1713bool KoColorSet::Private::loadKplProfiles(QScopedPointer<KoStore> &store)
1714{
1715 if (!store->open("profiles.xml")) {
1716 return false;
1717 }
1718
1719 QByteArray bytes = store->read(store->size());
1720 store->close();
1721
1722 QDomDocument doc;
1723 if(!doc.setContent(bytes)) {
1724 return false;
1725 }
1726
1727 QDomElement root = doc.documentElement();
1728 for (QDomElement c = root.firstChildElement(KPL_PALETTE_PROFILE_TAG);
1729 !c.isNull();
1730 c = c.nextSiblingElement(KPL_PALETTE_PROFILE_TAG)) {
1731 QString name = c.attribute(KPL_PALETTE_NAME_ATTR);
1732 QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR);
1733 QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR);
1734 QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR);
1735
1737 continue;
1738 }
1739
1740 loadColorProfile(store, filename, colorModelId, colorDepthId);
1741 // TODO: What should happen if this fails?
1742 }
1743
1744 return true;
1745}
1746
1747bool KoColorSet::Private::loadKplColorset(QScopedPointer<KoStore> &store)
1748{
1749 if (!store->open("colorset.xml")) {
1750 return false;
1751 }
1752
1753 QByteArray bytes = store->read(store->size());
1754 store->close();
1755
1756 QDomDocument doc;
1757 if (!doc.setContent(bytes)) {
1758 return false;
1759 }
1760
1761 QDomElement root = doc.documentElement();
1762 colorSet->setName(root.attribute(KPL_PALETTE_NAME_ATTR));
1763 QString version = root.attribute(KPL_VERSION_ATTR);
1764 comment = root.attribute(KPL_PALETTE_COMMENT_ATTR);
1765
1766 int desiredColumnCount = root.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt();
1767 if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) {
1768 warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount
1769 << ") in KPL palette file " << colorSet->filename()
1770 << " - setting maximum allowed column count instead.";
1771 colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
1772 } else {
1773 colorSet->setColumnCount(desiredColumnCount);
1774 }
1775
1776 loadKplGroup(doc, root, colorSet->getGlobalGroup(), version);
1777
1778 for (QDomElement g = root.firstChildElement(KPL_GROUP_TAG);
1779 !g.isNull();
1780 g = g.nextSiblingElement(KPL_GROUP_TAG)) {
1781 QString groupName = g.attribute(KPL_GROUP_NAME_ATTR);
1782 colorSet->addGroup(groupName);
1783 loadKplGroup(doc, g, colorSet->getGroup(groupName), version);
1784 }
1785
1786 return true;
1787}
1788
1790{
1791 QBuffer buf(&data);
1792 buf.open(QBuffer::ReadOnly);
1793
1794 QScopedPointer<KoStore> store(
1796 "application/x-krita-palette",
1797 KoStore::Zip));
1798 if (!store || store->bad()) {
1799 return false;
1800 }
1801
1802 if (store->hasFile("profiles.xml") && !loadKplProfiles(store)) {
1803 return false;
1804 }
1805
1806 if (!loadKplColorset(store)) {
1807 return false;
1808 }
1809
1810 buf.close();
1811 return true;
1812}
1813
1815{
1816 QFileInfo info(colorSet->filename());
1817 colorSet->setName(info.completeBaseName());
1818
1819 QBuffer buf(&data);
1820 buf.open(QBuffer::ReadOnly);
1821
1822 quint16 version = readShort(&buf);
1823 quint16 numColors = readShort(&buf);
1824 KisSwatch swatch;
1825
1826 if (version == 1 && buf.size() > 4+numColors*10) {
1827 buf.seek(4+numColors*10);
1828 version = readShort(&buf);
1829 numColors = readShort(&buf);
1830 }
1831
1832 const quint16 quint16_MAX = 65535;
1833
1834 KisSwatchGroupSP group = colorSet->getGlobalGroup();
1835
1836 for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
1837
1838 quint16 colorSpace = readShort(&buf);
1839 quint16 ch1 = readShort(&buf);
1840 quint16 ch2 = readShort(&buf);
1841 quint16 ch3 = readShort(&buf);
1842 quint16 ch4 = readShort(&buf);
1843
1844 bool skip = false;
1845 if (colorSpace == 0) { // RGB
1847 KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb));
1848 reinterpret_cast<quint16*>(c.data())[0] = ch3;
1849 reinterpret_cast<quint16*>(c.data())[1] = ch2;
1850 reinterpret_cast<quint16*>(c.data())[2] = ch1;
1852 swatch.setColor(c);
1853 }
1854 else if (colorSpace == 1) { // HSB
1855 QColor qc;
1856 qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0);
1857 KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16());
1859 swatch.setColor(c);
1860 }
1861 else if (colorSpace == 2) { // CMYK
1863 reinterpret_cast<quint16*>(c.data())[0] = quint16_MAX - ch1;
1864 reinterpret_cast<quint16*>(c.data())[1] = quint16_MAX - ch2;
1865 reinterpret_cast<quint16*>(c.data())[2] = quint16_MAX - ch3;
1866 reinterpret_cast<quint16*>(c.data())[3] = quint16_MAX - ch4;
1868 swatch.setColor(c);
1869 }
1870 else if (colorSpace == 7) { // LAB
1872 reinterpret_cast<quint16*>(c.data())[0] = ch3;
1873 reinterpret_cast<quint16*>(c.data())[1] = ch2;
1874 reinterpret_cast<quint16*>(c.data())[2] = ch1;
1876 swatch.setColor(c);
1877 }
1878 else if (colorSpace == 8) { // GRAY
1880 reinterpret_cast<quint16*>(c.data())[0] = ch1 * (quint16_MAX / 10000);
1882 swatch.setColor(c);
1883 }
1884 else {
1885 warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")";
1886 skip = true;
1887 }
1888
1889 if (version == 2) {
1890 QString name = readUnicodeString(&buf, true);
1891 swatch.setName(name);
1892 }
1893 if (!skip) {
1894 group->addSwatch(swatch);
1895 }
1896 }
1897 return true;
1898}
1899
1900bool KoColorSet::Private::loadSbzSwatchbook(QScopedPointer<KoStore> &store)
1901{
1902 if (!store->open("swatchbook.xml")) {
1903 return false;
1904 }
1905
1906 QByteArray bytes = store->read(store->size());
1907 store->close();
1908
1909 dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format";
1910
1911 QDomDocument doc;
1912 int errorLine, errorColumn;
1913 QString errorMessage;
1914 if (!doc.setContent(bytes, &errorMessage, &errorLine, &errorColumn)) {
1915 warnPigment << "Illegal XML palette:" << colorSet->filename();
1916 warnPigment << "Error (line" << errorLine
1917 << ", column" << errorColumn
1918 << "):" << errorMessage;
1919 return false;
1920 }
1921
1922 QDomElement root = doc.documentElement(); // SwatchBook
1923
1924 // Start reading properties...
1925 QDomElement metadata = root.firstChildElement("metadata");
1926 if (metadata.isNull()) {
1927 warnPigment << "Palette metadata not found";
1928 return false;
1929 }
1930
1931 QDomElement title = metadata.firstChildElement("dc:title");
1932 QString colorName = title.text();
1933 colorName = colorName.isEmpty() ? i18n("Untitled") : colorName;
1934 colorSet->setName(colorName);
1935 dbgPigment << "Processed name of palette:" << colorSet->name();
1936 // End reading properties
1937
1938 // Also read the swatch book...
1939 QDomElement book = root.firstChildElement("book");
1940 if (book.isNull()) {
1941 warnPigment << "Palette book (swatch composition) not found (line" << root.lineNumber()
1942 << ", column" << root.columnNumber()
1943 << ")";
1944 return false;
1945 }
1946
1947 // Which has lots of "swatch"es (todo: support groups)
1948 QDomElement swatch = book.firstChildElement();
1949 if (swatch.isNull()) {
1950 warnPigment << "Swatches/groups definition not found (line" << book.lineNumber()
1951 << ", column" << book.columnNumber()
1952 << ")";
1953 return false;
1954 }
1955
1956 // Now read colors...
1957 QDomElement materials = root.firstChildElement("materials");
1958 if (materials.isNull()) {
1959 warnPigment << "Materials (color definitions) not found";
1960 return false;
1961 }
1962
1963 // This one has lots of "color" elements
1964 if (materials.firstChildElement("color").isNull()) {
1965 warnPigment << "Color definitions not found (line" << materials.lineNumber()
1966 << ", column" << materials.columnNumber()
1967 << ")";
1968 return false;
1969 }
1970
1971 // We'll store colors here, and as we process swatches
1972 // we'll add them to the palette
1973 QHash<QString, KisSwatch> materialsBook;
1974 QHash<QString, const KoColorSpace*> fileColorSpaces;
1975
1976 // Color processing
1977 store->enterDirectory("profiles"); // Color profiles (icc files) live here
1978 for (QDomElement colorElement = materials.firstChildElement("color");
1979 !colorElement.isNull();
1980 colorElement = colorElement.nextSiblingElement("color")) {
1981 KisSwatch currentEntry;
1982 // Set if color is spot
1983 currentEntry.setSpotColor(colorElement.attribute("usage") == "spot");
1984
1985 // <metadata> inside contains id and name
1986 // one or more <values> define the color
1987 QDomElement currentColorMetadata = colorElement.firstChildElement("metadata");
1988 // Get color name
1989 QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title");
1990 QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier");
1991 // Is there an id? (we need that at the very least for identifying a color)
1992 if (colorId.text().isEmpty()) {
1993 warnPigment << "Unidentified color (line" << colorId.lineNumber()
1994 << ", column" << colorId.columnNumber()
1995 << ")";
1996 return false;
1997 }
1998
1999 if (materialsBook.contains(colorId.text())) {
2000 warnPigment << "Duplicated color definition (line" << colorId.lineNumber()
2001 << ", column" << colorId.columnNumber()
2002 << ")";
2003 return false;
2004 }
2005
2006 // Get a valid color name
2007 currentEntry.setId(colorId.text());
2008 currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text());
2009
2010 // Get a valid color definition
2011 if (colorElement.firstChildElement("values").isNull()) {
2012 warnPigment << "Color definitions not found (line" << colorElement.lineNumber()
2013 << ", column" << colorElement.columnNumber()
2014 << ")";
2015 return false;
2016 }
2017
2018 bool status;
2019 bool firstDefinition = false;
2021 const QString colorDepthId = Float32BitsColorDepthID.id();
2022 // Priority: Lab, otherwise the first definition found
2023 for (QDomElement colorValueE = colorElement.firstChildElement("values");
2024 !colorValueE.isNull();
2025 colorValueE = colorValueE.nextSiblingElement("values")) {
2026 QString model = colorValueE.attribute("model");
2027
2028 QString modelId;
2029 const KoColorProfile *profile = nullptr;
2030 if (model == "Lab") {
2031 modelId = LABAColorModelID.id();
2032 } else if (model == "sRGB") {
2033 modelId = RGBAColorModelID.id();
2034 profile = colorSpaceRegistry->rgb8()->profile();
2035 } else if (model == "XYZ") {
2036 modelId = XYZAColorModelID.id();
2037 } else if (model == "CMYK") {
2038 modelId = CMYKAColorModelID.id();
2039 } else if (model == "GRAY") {
2040 modelId = GrayAColorModelID.id();
2041 } else if (model == "RGB") {
2042 modelId = RGBAColorModelID.id();
2043 } else {
2044 warnPigment << "Color space not implemented:" << model
2045 << "(line" << colorValueE.lineNumber()
2046 << ", column "<< colorValueE.columnNumber()
2047 << ")";
2048 continue;
2049 }
2050
2051 const KoColorSpace *colorSpace = colorSpaceRegistry->colorSpace(modelId, colorDepthId, profile);
2052
2053 // The 'space' attribute is the name of the icc file
2054 // sitting in the 'profiles' directory in the zip.
2055 QString space = colorValueE.attribute("space");
2056 if (!space.isEmpty()) {
2057 if (fileColorSpaces.contains(space)) {
2058 colorSpace = fileColorSpaces.value(space);
2059 } else {
2060 // Try loading the profile and add it to the registry
2061 profile = loadColorProfile(store, space, modelId, colorDepthId);
2062 if (profile) {
2063 colorSpace = colorSpaceRegistry->colorSpace(modelId, colorDepthId, profile);
2064 fileColorSpaces.insert(space, colorSpace);
2065 }
2066 }
2067 }
2068
2069 KoColor c(colorSpace);
2070
2071 // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1
2072 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5
2073 // Lab: L 0 -> 100 : ab -128 -> 127
2074 // XYZ: 0 -> ~100
2075 QVector<float> channels;
2076 for (const QString &str : colorValueE.text().split(" ")) {
2077 float channelValue = str.toFloat(&status);
2078 if (!status) {
2079 warnPigment << "Invalid float definition (line" << colorValueE.lineNumber()
2080 << ", column" << colorValueE.columnNumber()
2081 << ")";
2082
2083 channelValue = 0;
2084 }
2085
2086 channels.append(channelValue);
2087 }
2088 channels.append(OPACITY_OPAQUE_F); // Alpha channel
2089 colorSpace->fromNormalisedChannelsValue(c.data(), channels);
2090
2091 currentEntry.setColor(c);
2092 firstDefinition = true;
2093
2094 if (model == "Lab") {
2095 break; // Immediately add this one
2096 }
2097 }
2098
2099 if (firstDefinition) {
2100 materialsBook.insert(currentEntry.id(), currentEntry);
2101 } else {
2102 warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber()
2103 << ", column "<< colorElement.columnNumber()
2104 << ")";
2105 return false;
2106 }
2107 }
2108
2109 store->leaveDirectory(); // Return to root
2110 // End colors
2111 // Now decide which ones will go into the palette
2112
2113 KisSwatchGroupSP global = colorSet->getGlobalGroup();
2114 for(; !swatch.isNull(); swatch = swatch.nextSiblingElement()) {
2115 QString type = swatch.tagName();
2116 if (type.isEmpty() || type.isNull()) {
2117 warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber()
2118 << ", column" << swatch.columnNumber()
2119 << ")";
2120 return false;
2121 } else if (type == "swatch") {
2122 QString id = swatch.attribute("material");
2123 if (id.isEmpty() || id.isNull()) {
2124 warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber()
2125 << ", column" << swatch.columnNumber()
2126 << ")";
2127 return false;
2128 }
2129
2130 if (materialsBook.contains(id)) {
2131 global->addSwatch(materialsBook.value(id));
2132 } else {
2133 warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber()
2134 << ", column" << swatch.columnNumber()
2135 << ")";
2136 return false;
2137 }
2138 } else if (type == "group") {
2139 QDomElement groupMetadata = swatch.firstChildElement("metadata");
2140 if (groupMetadata.isNull()) {
2141 warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber()
2142 << ", column" << groupMetadata.columnNumber()
2143 << ")";
2144 return false;
2145 }
2146 QDomElement groupTitle = metadata.firstChildElement("dc:title");
2147 if (groupTitle.isNull()) {
2148 warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber()
2149 << ", column" << groupTitle.columnNumber()
2150 << ")";
2151 return false;
2152 }
2153 QString currentGroupName = groupTitle.text();
2154 colorSet->addGroup(currentGroupName);
2155
2156 for (QDomElement groupSwatch = swatch.firstChildElement("swatch");
2157 !groupSwatch.isNull();
2158 groupSwatch = groupSwatch.nextSiblingElement("swatch")) {
2159 QString id = groupSwatch.attribute("material");
2160 if (id.isEmpty() || id.isNull()) {
2161 warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber()
2162 << ", column" << groupSwatch.columnNumber()
2163 << ")";
2164 return false;
2165 }
2166
2167 if (materialsBook.contains(id)) {
2168 colorSet->getGroup(currentGroupName)->addSwatch(materialsBook.value(id));
2169 } else {
2170 warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber()
2171 << ", column" << groupSwatch.columnNumber()
2172 << ")";
2173 return false;
2174 }
2175 }
2176 }
2177 }
2178 // End palette
2179
2180 return true;
2181}
2182
2184 QBuffer buf(&data);
2185 buf.open(QBuffer::ReadOnly);
2186
2187 // &buf is a subclass of QIODevice
2188 QScopedPointer<KoStore> store(
2190 "application/x-swatchbook",
2191 KoStore::Zip));
2192 if (!store || store->bad()) {
2193 return false;
2194 }
2195
2196 if (store->hasFile("swatchbook.xml") && !loadSbzSwatchbook(store)) {
2197 return false;
2198 }
2199
2200 buf.close();
2201 return true;
2202}
2203
2205{
2206 QFileInfo info(colorSet->filename());
2207 colorSet->setName(info.completeBaseName());
2208
2209 QBuffer buf(&data);
2210 buf.open(QBuffer::ReadOnly);
2211
2212 QByteArray signature; // should be "ASEF";
2213 signature = buf.read(4);
2214 quint16 version = readShort(&buf);
2215 quint16 version2 = readShort(&buf);
2216
2217 if (signature != "ASEF" && version!= 1 && version2 != 0) {
2218 qWarning() << "incorrect header:" << signature << version << version2;
2219 return false;
2220 }
2221 qint32 numBlocks = readInt(&buf);
2222
2223 QByteArray groupStart("\xC0\x01");
2224 QByteArray groupEnd("\xC0\x02");
2225 QByteArray swatchSig("\x00\x01");
2226
2227 bool inGroup = false;
2228 QString groupName;
2229 for (qint32 i = 0; i < numBlocks; i++) {
2230 QByteArray blockType;
2231 blockType = buf.read(2);
2232 qint32 blockSize = readInt(&buf);
2233 qint64 pos = buf.pos();
2234
2235 if (blockType == groupStart) {
2236 groupName = readUnicodeString(&buf);
2237 colorSet->addGroup(groupName);
2238 inGroup = true;
2239 }
2240 else if (blockType == groupEnd) {
2241 int colorCount = colorSet->getGroup(groupName)->colorCount();
2242 int columns = colorSet->columnCount();
2243 int rows = colorCount/columns;
2244 if (colorCount % columns > 0) {
2245 rows += 1;
2246 }
2247 colorSet->getGroup(groupName)->setRowCount(rows);
2248 inGroup = false;
2249 }
2250 else /* if (blockType == swatchSig)*/ {
2251 KisSwatch swatch;
2252 swatch.setName(readUnicodeString(&buf).trimmed());
2253 QByteArray colorModel;
2254 QDomDocument doc;
2255 colorModel = buf.read(4);
2256 if (colorModel == "RGB ") {
2257 QDomElement elt = doc.createElement("sRGB");
2258
2259 elt.setAttribute("r", readFloat(&buf));
2260 elt.setAttribute("g", readFloat(&buf));
2261 elt.setAttribute("b", readFloat(&buf));
2262
2263 KoColor color = KoColor::fromXML(elt, "U8");
2264 swatch.setColor(color);
2265 } else if (colorModel == "CMYK") {
2266 QDomElement elt = doc.createElement("CMYK");
2267
2268 elt.setAttribute("c", readFloat(&buf));
2269 elt.setAttribute("m", readFloat(&buf));
2270 elt.setAttribute("y", readFloat(&buf));
2271 elt.setAttribute("k", readFloat(&buf));
2272 //try to select the default PS icc profile if possible.
2273 elt.setAttribute("space", "U.S. Web Coated (SWOP) v2");
2274
2275 KoColor color = KoColor::fromXML(elt, "U8");
2276 swatch.setColor(color);
2277 } else if (colorModel == "LAB ") {
2278 QDomElement elt = doc.createElement("Lab");
2279
2280 elt.setAttribute("L", readFloat(&buf)*100.0);
2281 elt.setAttribute("a", readFloat(&buf));
2282 elt.setAttribute("b", readFloat(&buf));
2283
2284 KoColor color = KoColor::fromXML(elt, "U16");
2285 swatch.setColor(color);
2286 } else if (colorModel == "GRAY") {
2287 QDomElement elt = doc.createElement("Gray");
2288
2289 elt.setAttribute("g", readFloat(&buf));
2290
2291 KoColor color = KoColor::fromXML(elt, "U8");
2292 swatch.setColor(color);
2293 }
2294 qint16 type = readShort(&buf);
2295 if (type == 1) { //0 is global, 2 is regular;
2296 swatch.setSpotColor(true);
2297 }
2298 if (inGroup) {
2299 colorSet->addSwatch(swatch, groupName);
2300 } else {
2301 colorSet->addSwatch(swatch);
2302 }
2303 }
2304 buf.seek(pos + qint64(blockSize));
2305 }
2306 return true;
2307}
2308
2310{
2311
2312 QFileInfo info(colorSet->filename());
2313
2314 QBuffer buf(&data);
2315 buf.open(QBuffer::ReadOnly);
2316
2317 QByteArray signature; // should be "8BCB";
2318 signature = buf.read(4);
2319 quint16 version = readShort(&buf);
2320 quint16 bookID = readShort(&buf);
2321 Q_UNUSED(bookID);
2322
2323 if (signature != "8BCB" && version!= 1) {
2324 return false;
2325 }
2326
2328 for (int i = 0; i< 4; i++) {
2329
2330 QString metadataString = readUnicodeString(&buf, true);
2331 if (metadataString.startsWith("\"")) {
2332 metadataString = metadataString.remove(0, 1);
2333 }
2334 if (metadataString.endsWith("\"")) {
2335 metadataString.chop(1);
2336 }
2337 if (metadataString.startsWith("$$$/")) {
2338 if (metadataString.contains("=")) {
2339 metadataString = metadataString.split("=").last();
2340 } else {
2341 metadataString = QString();
2342 }
2343 }
2344 metadata.append(metadataString);
2345 }
2346 QString title = metadata.at(0);
2347 colorSet->setName(title);
2348 QString prefix = metadata.at(1);
2349 QString postfix = metadata.at(2);
2350 QString description = metadata.at(3);
2351 colorSet->setComment(description);
2352
2353 quint16 numColors = readShort(&buf);
2354 quint16 numColumns = readShort(&buf);
2355 numColumns = numColumns > 0 ? numColumns : 8; // overwrite with sane default in case of 0
2356 colorSet->setColumnCount(numColumns);
2357 quint16 numKeyColorPage = readShort(&buf);
2358 Q_UNUSED(numKeyColorPage);
2359 quint16 colorType = readShort(&buf);
2360
2362 if (colorType == 2) {
2363 QString profileName = "U.S. Web Coated (SWOP) v2";
2365 } else if (colorType == 7) {
2367 }
2368
2369 for (quint16 i = 0; i < numColors; i++) {
2370 KisSwatch swatch;
2372 name << prefix;
2373 name << readUnicodeString(&buf, true);
2374 name << postfix;
2375 swatch.setName(name.join(" ").trimmed());
2376 QByteArray key; // should be "8BCB";
2377 key = buf.read(6);
2378 swatch.setId(QString::fromLatin1(key));
2379 swatch.setSpotColor(true);
2380 quint8 c1 = readByte(&buf);
2381 quint8 c2 = readByte(&buf);
2382 quint8 c3 = readByte(&buf);
2383 KoColor c(cs);
2384 if (colorType == 0) {
2385 c.data()[0] = c3;
2386 c.data()[1] = c2;
2387 c.data()[2] = c1;
2388 } else if (colorType == 2) {
2389 quint8 c4 = readByte(&buf);
2390 c.data()[0] = c1;
2391 c.data()[1] = c2;
2392 c.data()[2] = c3;
2393 c.data()[3] = c4;
2394 } else if (colorType == 7) {
2395 c.data()[0] = c1;
2396 c.data()[1] = c2;
2397 c.data()[2] = c3;
2398 }
2399 c.setOpacity(1.0);
2400 swatch.setColor(c);
2401 colorSet->addSwatch(swatch);
2402 }
2403
2404 return true;
2405}
2406
2408 bool res = false;
2409
2410 QXmlStreamReader *xml = new QXmlStreamReader(data);
2411
2412 if (xml->readNextStartElement()) {
2413 auto paletteId = xml->name();
2414 if (paletteId.compare(QString("SCRIBUSCOLORS"), Qt::CaseInsensitive) == 0) { // Scribus
2415 dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format";
2416 res = loadScribusXmlPalette(colorSet, xml);
2417 }
2418 else {
2419 // Unknown XML format
2420 xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId);
2421 }
2422 }
2423
2424 // If there is any error (it should be returned through the stream)
2425 if (xml->hasError() || !res) {
2426 warnPigment << "Illegal XML palette:" << colorSet->filename();
2427 warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString();
2428 return false;
2429 }
2430 else {
2431 dbgPigment << "XML palette parsed successfully:" << colorSet->filename();
2432 return true;
2433 }
2434}
2435
2436bool KoColorSet::Private::saveKpl(QIODevice *dev) const
2437{
2438 QScopedPointer<KoStore> store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip));
2439 if (!store || store->bad()) {
2440 qWarning() << "saveKpl could not create store";
2441 return false;
2442 }
2443
2444 QSet<const KoColorSpace *> colorSpaces;
2445
2446 {
2447 QDomDocument doc;
2448 QDomElement root = doc.createElement(KPL_PALETTE_TAG);
2449 root.setAttribute(KPL_VERSION_ATTR, "2.0");
2450 root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name());
2451 root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment);
2452 root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount());
2453 root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, colorSet->getGlobalGroup()->rowCount());
2454
2455 saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces);
2456
2457 for (const KisSwatchGroupSP &group : swatchGroups) {
2458 if (group->name() == KoColorSet::GLOBAL_GROUP_NAME) { continue; }
2459 QDomElement gl = doc.createElement(KPL_GROUP_TAG);
2460 gl.setAttribute(KPL_GROUP_NAME_ATTR, group->name());
2461 root.appendChild(gl);
2462 saveKplGroup(doc, gl, group, colorSpaces);
2463 }
2464
2465 doc.appendChild(root);
2466 if (!store->open("colorset.xml")) { return false; }
2467 QByteArray ba = doc.toByteArray();
2468 if (store->write(ba) != ba.size()) { return false; }
2469 if (!store->close()) { return false; }
2470 }
2471
2472 QDomDocument doc;
2473 QDomElement profileElement = doc.createElement("Profiles");
2474
2475 for (const KoColorSpace *colorSpace : colorSpaces) {
2476 QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName();
2477 if (!store->open(fn)) { qWarning() << "Could not open the store for profiles directory"; return false; }
2478 QByteArray profileRawData = colorSpace->profile()->rawData();
2479 if (!store->write(profileRawData)) { qWarning() << "Could not write the profiles data into the store"; return false; }
2480 if (!store->close()) { qWarning() << "Could not close the store for profiles directory"; return false; }
2481 QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG);
2482 el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn);
2483 el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name());
2484 el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id());
2485 el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id());
2486 profileElement.appendChild(el);
2487
2488 }
2489 doc.appendChild(profileElement);
2490
2491 if (!store->open("profiles.xml")) { qWarning() << "Could not open profiles.xml"; return false; }
2492 QByteArray ba = doc.toByteArray();
2493
2494 int bytesWritten = store->write(ba);
2495 if (bytesWritten != ba.size()) { qWarning() << "Bytes written is wrong" << ba.size(); return false; }
2496
2497 if (!store->close()) { qWarning() << "Could not close the store"; return false; }
2498
2499 bool r = store->finalize();
2500 if (!r) { qWarning() << "Could not finalize the store"; }
2501 return r;
2502}
2503
2505 QDomElement &groupEle,
2506 const KisSwatchGroupSP group,
2507 QSet<const KoColorSpace *> &colorSetSet) const
2508{
2509 groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount()));
2510
2511 for (const KisSwatchGroup::SwatchInfo &info : group->infoList()) {
2512 const KoColorProfile *profile = info.swatch.color().colorSpace()->profile();
2513 // Only save non-builtin profiles.=
2514 if (!profile->fileName().isEmpty()) {
2515 bool alreadyIncluded = false;
2516 Q_FOREACH(const KoColorSpace* colorSpace, colorSetSet) {
2517 if (colorSpace->profile()->fileName() == profile->fileName()) {
2518 alreadyIncluded = true;
2519 break;
2520 }
2521 }
2522 if(!alreadyIncluded) {
2523 colorSetSet.insert(info.swatch.color().colorSpace());
2524 }
2525 }
2526 QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG);
2527 swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name());
2528 swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id());
2529 swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false");
2530 swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id());
2531 info.swatch.color().toXML(doc, swatchEle);
2532
2533 QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG);
2534 positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row);
2535 positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column);
2536 swatchEle.appendChild(positionEle);
2537
2538 groupEle.appendChild(swatchEle);
2539 }
2540}
2541
2542void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroupSP group, QString version)
2543{
2544 Q_UNUSED(doc);
2545 if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) {
2546 group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt());
2547 }
2548 group->setColumnCount(colorSet->columnCount());
2549
2550 for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG);
2551 !swatchEle.isNull();
2552 swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) {
2553 QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id());
2554 KisSwatch swatch;
2555
2556 if (version == "1.0" && swatchEle.firstChildElement().tagName() == "Lab") {
2557 // previous version of krita had the values wrong, and scaled everything between 0 to 1,
2558 // but lab requires L = 0-100 and AB = -128-127.
2559 // TODO: write unittest for this.
2560 QDomElement el = swatchEle.firstChildElement();
2561 double L = KisDomUtils::toDouble(el.attribute("L"));
2562 el.setAttribute("L", L*100.0);
2563 double ab = KisDomUtils::toDouble(el.attribute("a"));
2564 if (ab <= .5) {
2565 ab = (0.5 - ab) * 2 * -128.0;
2566 } else {
2567 ab = (ab - 0.5) * 2 * 127.0;
2568 }
2569 el.setAttribute("a", ab);
2570
2571 ab = KisDomUtils::toDouble(el.attribute("b"));
2572 if (ab <= .5) {
2573 ab = (0.5 - ab) * 2 * -128.0;
2574 } else {
2575 ab = (ab - 0.5) * 2 * 127.0;
2576 }
2577 el.setAttribute("b", ab);
2578 swatch.setColor(KoColor::fromXML(el, colorDepthId));
2579 } else {
2580 swatch.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId));
2581 }
2582 swatch.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR));
2583 swatch.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR));
2584 swatch.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false);
2585 QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG);
2586 if (!positionEle.isNull()) {
2587 int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt();
2588 int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt();
2589 if (columnNumber < 0 ||
2590 columnNumber >= colorSet->columnCount() ||
2591 rowNumber < 0
2592 ) {
2593 warnPigment << "Swatch" << swatch.name()
2594 << "of palette" << colorSet->name()
2595 << "has invalid position.";
2596 continue;
2597 }
2598 group->setSwatch(swatch, columnNumber, rowNumber);
2599 } else {
2600 group->addSwatch(swatch);
2601 }
2602 }
2603
2604 if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()
2605 && group->colorCount() > 0
2606 && group->columnCount() > 0
2607 && (group->colorCount() / (group->columnCount()) + 1) < 20) {
2608 group->setRowCount((group->colorCount() / group->columnCount()) + 1);
2609 }
2610
2611}
#define dbgPigment
#define warnPigment
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID XYZAColorModelID("XYZA", ki18n("XYZ/Alpha"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
const KoID CMYKAColorModelID("CMYKA", ki18n("CMYK/Alpha"))
const KoID LABAColorModelID("LABA", ki18n("L*a*b*/Alpha"))
const KoID RGBAColorModelID("RGBA", ki18n("RGB/Alpha"))
const int MAXIMUM_ALLOWED_COLUMNS
const qreal OPACITY_OPAQUE_F
const quint8 OPACITY_OPAQUE_U8
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
virtual void undo()
void setUndoLimit(int limit)
The KisSwatchGroup class stores a matrix of color swatches swatches can accessed using (x,...
void setSpotColor(bool spotColor)
Definition KisSwatch.cpp:38
KoColor color() const
Definition KisSwatch.h:30
QString name() const
Definition KisSwatch.h:24
void setColor(const KoColor &color)
Definition KisSwatch.cpp:32
void setId(const QString &id)
Definition KisSwatch.cpp:26
void setName(const QString &name)
Definition KisSwatch.cpp:20
QString id() const
Definition KisSwatch.h:27
bool saveKpl(QIODevice *dev) const
KUndo2Stack undoStack
KoColorSet::PaletteType detectFormat(const QString &fileName, const QByteArray &ba)
bool loadSbzSwatchbook(QScopedPointer< KoStore > &store)
quint8 readByte(QIODevice *io)
const KoColorProfile * loadColorProfile(QScopedPointer< KoStore > &store, const QString &path, const QString &modelId, const QString &colorDepthId)
bool loadKplColorset(QScopedPointer< KoStore > &store)
Private(KoColorSet *a_colorSet)
void scribusParseColor(KoColorSet *set, QXmlStreamReader *xml)
bool loadKplProfiles(QScopedPointer< KoStore > &store)
float readFloat(QIODevice *io)
void saveKplGroup(QDomDocument &doc, QDomElement &groupEle, const KisSwatchGroupSP group, QSet< const KoColorSpace * > &colorSetSet) const
bool saveGpl(QIODevice *dev) const
bool loadScribusXmlPalette(KoColorSet *set, QXmlStreamReader *xml)
quint16 readShort(QIODevice *io)
QList< KisSwatchGroupSP > swatchGroups
QString readUnicodeString(QIODevice *io, bool sizeIsInt=false)
void loadKplGroup(const QDomDocument &doc, const QDomElement &parentElement, KisSwatchGroupSP group, QString version)
qint32 readInt(QIODevice *io)
static const QString KPL_COLOR_DEPTH_ID_ATTR
Definition KoColorSet.h:42
static const QString KPL_SWATCH_TAG
Definition KoColorSet.h:52
int rowNumberInGroup(int rowNumber) const
rowNumberInGroup calculates the row number in the group from the global rownumber
void setModified(bool)
KoColorSet(const QString &filename=QString())
static const QString KPL_SWATCH_ROW_ATTR
Definition KoColorSet.h:44
int rowCountWithTitles() const
friend struct SetColumnCountCommand
Definition KoColorSet.h:279
bool isLocked() const
static const QString KPL_COLOR_MODEL_ID_ATTR
Definition KoColorSet.h:41
void setColumnCount(int columns)
void removeGroup(const QString &groupName, bool keepColors=true)
removeGroup Remove a group from the KoColorSet
KUndo2Stack * undoStack() const
static const QString KPL_SWATCH_COL_ATTR
Definition KoColorSet.h:45
static const QString KPL_PALETTE_PROFILE_TAG
Definition KoColorSet.h:50
static const QString KPL_PALETTE_COMMENT_ATTR
Definition KoColorSet.h:38
void modified()
static const QString KPL_PALETTE_NAME_ATTR
Definition KoColorSet.h:37
void canUndoChanged(bool canUndo)
void layoutChanged()
int rowCount() const
friend struct MoveGroupCommand
Definition KoColorSet.h:282
KisSwatchGroup::SwatchInfo getClosestSwatchInfo(KoColor compare, bool useGivenColorSpace=true) const
getIndexClosestColor function that matches the color to all colors in the colorset,...
void setLocked(bool lock)
bool isGroupTitleRow(int row) const
isGroupRow checks whether the current row is a group title
int startRowForGroup(const QString &groupName) const
rowForNamedGroup returns the row the group's title is on
void updateThumbnail() override
updateThumbnail updates the thumbnail for this resource. Reimplement if your thumbnail is something e...
bool saveToDevice(QIODevice *dev) const override
~KoColorSet() override
static const QString KPL_SWATCH_POS_TAG
Definition KoColorSet.h:51
static const QString KPL_PALETTE_READONLY_ATTR
Definition KoColorSet.h:40
bool fromByteArray(QByteArray &data, KisResourcesInterfaceSP resourcesInterface)
friend struct AddSwatchCommand
Definition KoColorSet.h:273
KisSwatchGroupSP getGlobalGroup() const
getGlobalGroup
friend struct SetPaletteTypeCommand
Definition KoColorSet.h:281
void setPaletteType(PaletteType paletteType)
static const QString KPL_PALETTE_COLUMN_COUNT_ATTR
Definition KoColorSet.h:36
const QScopedPointer< Private > d
Definition KoColorSet.h:287
quint32 slotCount() const
static const QString KPL_GROUP_TAG
Definition KoColorSet.h:53
static const QString GLOBAL_GROUP_NAME
Definition KoColorSet.h:33
KisSwatch getColorGlobal(quint32 column, quint32 row) const
getColorGlobal A function for getting a color based on a global index. Useful for iterating through a...
QStringList swatchGroupNames() const
getGroupNames
void addSwatch(const KisSwatch &swatch, const QString &groupName=GLOBAL_GROUP_NAME, int column=-1, int row=-1)
Add a color to the palette.
static const QString KPL_GROUP_NAME_ATTR
Definition KoColorSet.h:43
static const QString KPL_PALETTE_TAG
Definition KoColorSet.h:54
friend struct ClearCommand
Definition KoColorSet.h:278
KisSwatchGroupSP getGroup(const QString &name) const
getGroup
KisSwatch getSwatchFromGroup(quint32 column, quint32 row, QString groupName=KoColorSet::GLOBAL_GROUP_NAME) const
getColorGroup A function for getting the color from a specific group.
static const QString KPL_VERSION_ATTR
Definition KoColorSet.h:34
friend struct SetCommentCommand
Definition KoColorSet.h:280
friend struct AddGroupCommand
Definition KoColorSet.h:276
static const QString KPL_GROUP_ROW_COUNT_ATTR
Definition KoColorSet.h:35
void notifySwatchChanged(const QString &groupName, int column, int row)
QString comment()
int columnCount() const
friend struct RemoveGroupCommand
Definition KoColorSet.h:277
static const QString KPL_SWATCH_BITDEPTH_ATTR
Definition KoColorSet.h:49
static const QString KPL_PALETTE_FILENAME_ATTR
Definition KoColorSet.h:39
void moveGroup(const QString &groupName, const QString &groupNameInsertBefore=GLOBAL_GROUP_NAME)
moveGroup Move a group in the internal stringlist.
void clear()
clears the complete colorset
void setComment(QString comment)
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
void canRedoChanged(bool canRedo)
void addGroup(const QString &groupName, int columnCount=KisSwatchGroup::DEFAULT_COLUMN_COUNT, int rowCount=KisSwatchGroup::DEFAULT_ROW_COUNT)
addGroup Adds a new group.
friend struct ChangeGroupNameCommand
Definition KoColorSet.h:275
KoResourceSP clone() const override
friend struct RemoveSwatchCommand
Definition KoColorSet.h:274
void removeSwatch(int column, int row, KisSwatchGroupSP group)
remove the swatch from the given group at column and row
static const QString KPL_SWATCH_SPOT_ATTR
Definition KoColorSet.h:47
quint32 colorCount() const
PaletteType paletteType() const
void entryChanged(int column, int row)
static const QString KPL_SWATCH_NAME_ATTR
Definition KoColorSet.h:46
void layoutAboutToChange()
void changeGroupName(const QString &oldGroupName, const QString &newGroupName)
changeGroupName
QString defaultFileExtension() const override
static const QString KPL_SWATCH_ID_ATTR
Definition KoColorSet.h:48
virtual quint8 difference(const quint8 *src1, const quint8 *src2) const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
virtual const KoColorProfile * profile() const =0
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
static KoColor fromXML(const QDomElement &elt, const QString &channelDepthId)
Definition KoColor.cpp:350
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 * data()
Definition KoColor.h:144
const KoColorSpace * colorSpace() const
return the current colorSpace
Definition KoColor.h:82
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
QString id() const
Definition KoID.cpp:63
@ Read
Definition KoStore.h:29
@ Write
Definition KoStore.h:29
@ Zip
Definition KoStore.h:30
static KoStore * createStore(const QString &fileName, Mode mode, const QByteArray &appIdentification=QByteArray(), Backend backend=Auto, bool writeMimetype=true)
Definition KoStore.cpp:39
const quint16 quint16_MAX
Definition kis_global.h:25
QSharedPointer< KoResource > KoResourceSP
double toDouble(const QString &str, bool *ok=nullptr)
void setUtf8OnStream(QTextStream &stream)
rgba palette[MAX_PALETTE]
Definition palette.c:35
AddGroupCommand(KoColorSet *colorSet, QString groupName, int columnCount, int rowCount)
void undo() override
revert the actions done in redo
void redo() override
redo the command
KoColorSet * m_colorSet
void undo() override
revert the actions done in redo
~AddSwatchCommand() override
KoColorSet * m_colorSet
AddSwatchCommand(KoColorSet *colorSet, const KisSwatch &swatch, const QString &groupName, int column, int row)
void redo() override
redo the command
~ChangeGroupNameCommand() override
void undo() override
revert the actions done in redo
ChangeGroupNameCommand(KoColorSet *colorSet, QString oldGroupName, const QString &newGroupName)
void redo() override
redo the command
ClearCommand(KoColorSet *colorSet)
void undo() override
revert the actions done in redo
KoColorSet * m_OldColorSet
KoColorSet * m_colorSet
void redo() override
redo the command
~ClearCommand() override
virtual bool valid() const =0
const KoColorProfile * profileByName(const QString &name) const
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorProfile * createColorProfile(const QString &colorModelId, const QString &colorDepthId, const QByteArray &rawData)
void addProfile(KoColorProfile *profile)
void setName(const QString &name)
void setFilename(const QString &filename)
QString filename
void setDirty(bool value)
Mark the preset as modified but not saved.
void setImage(const QImage &image)
QMap< QString, QVariant > metadata
KoResourceSignature signature() const
QString name
MoveGroupCommand(KoColorSet *colorSet, QString groupName, const QString &groupNameInsertBefore)
QString m_groupNameInsertBefore
KoColorSet * m_colorSet
void redo() override
redo the command
void undo() override
revert the actions done in redo
RemoveGroupCommand(KoColorSet *colorSet, QString groupName, bool keepColors=true)
void redo() override
redo the command
void undo() override
revert the actions done in redo
KoColorSet * m_colorSet
KisSwatchGroupSP m_oldGroup
KisSwatchGroupSP m_group
void undo() override
revert the actions done in redo
void redo() override
redo the command
KoColorSet * m_colorSet
RemoveSwatchCommand(KoColorSet *colorSet, int column, int row, KisSwatchGroupSP group)
quint16 colorcount
void redo() override
redo the command
SetColumnCountCommand(KoColorSet *colorSet, int columnCount)
void undo() override
revert the actions done in redo
KoColorSet * m_colorSet
KoColorSet * m_colorSet
SetCommentCommand(KoColorSet *colorSet, const QString &comment)
void redo() override
redo the command
void undo() override
revert the actions done in redo
void undo() override
revert the actions done in redo
KoColorSet::PaletteType m_paletteType
KoColorSet * m_colorSet
QString suffix(KoColorSet::PaletteType paletteType) const
SetPaletteTypeCommand(KoColorSet *colorSet, const KoColorSet::PaletteType &paletteType)
void redo() override
redo the command
KoColorSet::PaletteType m_oldPaletteType