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 if (!file.open(QIODevice::ReadOnly)) {
1290 warnPigment << "Cannot load palette" << colorSet->name() << ":" << file.errorString();
1291 return false;
1292 }
1293 data = file.readAll();
1294 file.close();
1295 }
1296
1297 bool res = false;
1298 paletteType = detectFormat(colorSet->filename(), data);
1299 switch(paletteType) {
1300 case GPL:
1301 res = loadGpl();
1302 break;
1303 case ACT:
1304 res = loadAct();
1305 break;
1306 case RIFF_PAL:
1307 res = loadRiff();
1308 break;
1309 case PSP_PAL:
1310 res = loadPsp();
1311 break;
1312 case ACO:
1313 res = loadAco();
1314 break;
1315 case XML:
1316 res = loadXml();
1317 break;
1318 case KPL:
1319 res = loadKpl();
1320 break;
1321 case SBZ:
1322 res = loadSbz();
1323 break;
1324 case ASE:
1325 res = loadAse();
1326 break;
1327 case ACB:
1328 res = loadAcb();
1329 break;
1330 case CSS:
1331 res = loadCss();
1332 break;
1333 default:
1334 res = false;
1335 }
1336 if (paletteType != KPL) {
1337 int rowCount = global()->colorCount() / global()->columnCount();
1338 if (global()->colorCount() % global()->columnCount() > 0) {
1339 rowCount ++;
1340 }
1341 global()->setRowCount(rowCount);
1342 }
1343 colorSet->setValid(res);
1344 colorSet->updateThumbnail();
1345
1346 data.clear();
1347 undoStack.clear();
1348
1349 return res;
1350}
1351
1352bool KoColorSet::Private::saveGpl(QIODevice *dev) const
1353{
1354 Q_ASSERT(dev->isOpen());
1355 Q_ASSERT(dev->isWritable());
1356
1357 QTextStream stream(dev);
1359 stream << "GIMP Palette\nName: " << colorSet->name() << "\nColumns: " << colorSet->columnCount() << "\n#\n";
1360
1361 KisSwatchGroupSP global = colorSet->getGlobalGroup();
1362 for (int y = 0; y < global->rowCount(); y++) {
1363 for (int x = 0; x < colorSet->columnCount(); x++) {
1364 if (!global->checkSwatchExists(x, y)) {
1365 continue;
1366 }
1367 const KisSwatch& entry = global->getSwatch(x, y);
1368 QColor c = entry.color().toQColor();
1369 stream << c.red() << " " << c.green() << " " << c.blue() << "\t";
1370 if (entry.name().isEmpty())
1371 stream << "Untitled\n";
1372 else
1373 stream << entry.name() << "\n";
1374 }
1375 }
1376
1377 return true;
1378}
1379
1381{
1382 if (data.isEmpty() || data.isNull() || data.length() < 50) {
1383 warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
1384 return false;
1385 }
1386
1387 quint32 index = 0;
1388
1389 QStringList lines = readAllLinesSafe(&data);
1390
1391 if (lines.size() < 3) {
1392 warnPigment << "Not enough lines in palette file: " << colorSet->filename();
1393 return false;
1394 }
1395
1396 QString columnsText;
1397 qint32 r, g, b;
1398 KisSwatch swatch;
1399
1400 // Read name
1401 if (!lines[0].startsWith("GIMP") || !lines[1].toLower().contains("name")) {
1402 warnPigment << "Illegal Gimp palette file: " << colorSet->filename();
1403 return false;
1404 }
1405
1406 // translated name will be in a tooltip, here don't translate
1407 colorSet->setName(lines[1].split(":")[1].trimmed());
1408
1409 index = 2;
1410
1411 // Read columns
1412 int columns = 0;
1413 if (lines[index].toLower().contains("columns")) {
1414 columnsText = lines[index].split(":")[1].trimmed();
1415 columns = columnsText.toInt();
1416 if (columns > MAXIMUM_ALLOWED_COLUMNS) {
1417 warnPigment << "Refusing to set unreasonable number of columns (" << columns << ") in GIMP Palette file " << colorSet->filename() << " - using maximum number of allowed columns instead";
1418 global()->setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
1419 }
1420 else {
1421 global()->setColumnCount(columns);
1422 }
1423 index = 3;
1424 }
1425
1426
1427 for (qint32 i = index; i < lines.size(); i++) {
1428 if (lines[i].startsWith('#')) {
1429 comment += lines[i].mid(1).trimmed() + ' ';
1430 } else if (!lines[i].isEmpty()) {
1431 QStringList a = lines[i].replace('\t', ' ').split(' ', Qt::SkipEmptyParts);
1432
1433 if (a.count() < 3) {
1434 continue;
1435 }
1436
1437 r = qBound(0, a[0].toInt(), 255);
1438 g = qBound(0, a[1].toInt(), 255);
1439 b = qBound(0, a[2].toInt(), 255);
1440
1441 swatch.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
1442
1443 for (int i = 0; i != 3; i++) {
1444 a.pop_front();
1445 }
1446 QString name = a.join(" ");
1447 swatch.setName(name.isEmpty() || name == "Untitled" ? i18n("Untitled") : name);
1448
1449 global()->addSwatch(swatch);
1450 }
1451 }
1452 return true;
1453}
1454
1456{
1457 QFileInfo info(colorSet->filename());
1458 colorSet->setName(info.completeBaseName());
1459 KisSwatch swatch;
1460 int numOfTriplets = int(data.size() / 3);
1461 for (int i = 0; i < numOfTriplets * 3; i += 3) {
1462 quint8 r = data[i];
1463 quint8 g = data[i+1];
1464 quint8 b = data[i+2];
1465 swatch.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
1466 global()->addSwatch(swatch);
1467 }
1468 return true;
1469}
1470
1472{
1473 // https://worms2d.info/Palette_file
1474 QFileInfo info(colorSet->filename());
1475 colorSet->setName(info.completeBaseName());
1476 KisSwatch swatch;
1477
1478 RiffHeader header;
1479 memcpy(&header, data.constData(), sizeof(RiffHeader));
1480 header.colorcount = qFromBigEndian(header.colorcount);
1481
1482 for (int i = sizeof(RiffHeader);
1483 (i < (int)(sizeof(RiffHeader) + (header.colorcount * 4)) && i < data.size());
1484 i += 4) {
1485 quint8 r = data[i];
1486 quint8 g = data[i+1];
1487 quint8 b = data[i+2];
1488 swatch.setColor(KoColor(QColor(r, g, b), KoColorSpaceRegistry::instance()->rgb8()));
1489 colorSet->getGlobalGroup()->addSwatch(swatch);
1490 }
1491 return true;
1492}
1493
1494
1496{
1497 QFileInfo info(colorSet->filename());
1498 colorSet->setName(info.completeBaseName());
1499 KisSwatch swatch;
1500 qint32 r, g, b;
1501
1502 QStringList l = readAllLinesSafe(&data);
1503 if (l.size() < 4) return false;
1504 if (l[0] != "JASC-PAL") return false;
1505 if (l[1] != "0100") return false;
1506
1507 int entries = l[2].toInt();
1508
1509 KisSwatchGroupSP global = colorSet->getGlobalGroup();
1510
1511 for (int i = 0; i < entries; ++i) {
1512
1513 QStringList a = l[i + 3].replace('\t', ' ').split(' ', Qt::SkipEmptyParts);
1514
1515 if (a.count() != 3) {
1516 continue;
1517 }
1518
1519 r = qBound(0, a[0].toInt(), 255);
1520 g = qBound(0, a[1].toInt(), 255);
1521 b = qBound(0, a[2].toInt(), 255);
1522
1523 swatch.setColor(KoColor(QColor(r, g, b),
1525
1526 QString name = a.join(" ");
1527 swatch.setName(name.isEmpty() ? i18n("Untitled") : name);
1528
1529 global->addSwatch(swatch);
1530 }
1531 return true;
1532}
1533
1535{
1536 QFileInfo info(colorSet->filename());
1537 colorSet->setName(info.completeBaseName());
1538
1539 QString text = readAllLinesSafe(&data).join("").replace("\t", "").replace(" ", "");
1540
1541 QRegularExpression re("/\\*.*?\\*/");
1542
1543 text.remove(re); // Remove comments
1544
1545 KisSwatch swatch;
1546
1547 // Regex to detect a color in the palette
1548 QRegularExpression palette("(.*?){(?:[^:;]+:[^;]+;)*?color:(.*?)(?:;.*?)*?}");
1549
1550 QRegularExpressionMatchIterator colors = palette.globalMatch(text);
1551
1552 if (!colors.hasNext()) {
1553 warnPigment << "No color found in CSS palette : " << colorSet->filename();
1554 return false;
1555 }
1556
1557 while (colors.hasNext()) {
1558 QRegularExpressionMatch match = colors.next();
1559 QString colorInfo = match.captured();
1560 QString colorName = match.captured(1);
1561 QString colorValue = match.captured(2);
1562
1563 if (!colorInfo.startsWith(".") || colorValue.isEmpty()) {
1564 warnPigment << "Illegal CSS palette syntax : " << colorInfo;
1565 return false;
1566 }
1567
1568 QColor qColor;
1569
1570 colorName.remove(".");
1571 swatch.setName(colorName);
1572
1573 if (colorValue.startsWith("rgb")) {
1574 QStringList color;
1575
1576 if (colorValue.startsWith("rgba")) {
1577 colorValue.remove("rgba(").remove(")");
1578 color = colorValue.split(",");
1579
1580 if (color.size() != 4) {
1581 warnPigment << "Invalid RGBA color definition : " << colorInfo;
1582 return false;
1583 }
1584
1585 int alpha = color[3].toFloat() * 255;
1586
1587 if (alpha < 0 || alpha > 255) {
1588 warnPigment << "Invalid alpha parameter : " << colorInfo;
1589 return false;
1590 }
1591 }
1592 else {
1593 colorValue.remove("rgb(").remove(")");
1594
1595 color = colorValue.split(",");
1596
1597 if (color.size() != 3) {
1598 warnPigment << "Invalid RGB color definition : " << colorInfo;
1599 return false;
1600 }
1601 }
1602
1603 int rgb[3];
1604
1605 for (int i = 0; i < 3; i++) {
1606 if (color[i].endsWith("%")) {
1607 color[i].replace("%", "");
1608 rgb[i] = color[i].toFloat() / 100 * 255 ;
1609 }
1610 else {
1611 rgb[i] = color[i].toInt();
1612 };
1613 }
1614
1615 qColor = QColor(rgb[0], rgb[1], rgb[2]);
1616 }
1617 else if (colorValue.startsWith("hsl")) {
1618 QStringList color;
1619
1620 if (colorValue.startsWith("hsla")) {
1621 colorValue.remove("hsla(").remove(")").replace("%", "");
1622 color = colorValue.split(",");
1623 if (color.size() != 4) {
1624 warnPigment << "Invalid HSLA color definition : " << colorInfo;
1625 return false;
1626 }
1627
1628 float alpha = color[3].toFloat();
1629
1630 if (alpha < 0.0 || alpha > 1.0) {
1631 warnPigment << "Invalid alpha parameter : " << colorInfo;
1632 return false;
1633 }
1634
1635 }
1636 else {
1637 colorValue.remove("hsl(").remove(")").replace("%", "");
1638 color = colorValue.split(",");
1639 if (color.size() != 3) {
1640 warnPigment << "Invalid HSL color definition : " << colorInfo;
1641 return false;
1642 }
1643 }
1644
1645 float hue = color[0].toFloat() / 359;
1646 float saturation = color[1].toFloat() / 100;
1647 float lightness = color[2].toFloat() / 100;
1648
1649 if (hue < 0.0 || hue > 1.0) {
1650 warnPigment << "Invalid hue parameter : " << colorInfo;
1651 return false;
1652 }
1653
1654 if (saturation < 0.0 || saturation > 1.0) {
1655 warnPigment << "Invalid saturation parameter : " << colorInfo;
1656 return false;
1657 }
1658
1659 if (lightness < 0.0 || lightness > 1.0) {
1660 warnPigment << "Invalid lightness parameter : " << colorInfo;
1661 return false;
1662 }
1663
1664 qColor = QColor::fromHslF(hue, saturation, lightness);
1665
1666 }
1667 else if (colorValue.startsWith("#")) {
1668 if (colorValue.size() == 9) {
1669 // Convert the CSS format #RRGGBBAA to #RRGGBB
1670 // Due to QColor's 8 digits format being #AARRGGBB and that we do not load the alpha channel
1671 colorValue.truncate(7);
1672 }
1673
1674 qColor = QColor(colorValue);
1675 }
1676 else {
1677 warnPigment << "Unknown color declaration : " << colorInfo;
1678 return false;
1679 }
1680
1681 if (!qColor.isValid()) {
1682 warnPigment << "Invalid color definition : " << colorInfo;
1683 return false;
1684 }
1685
1686 swatch.setColor(KoColor(qColor, KoColorSpaceRegistry::instance()->rgb8()));
1687
1688 global()->addSwatch(swatch);
1689 }
1690
1691 return true;
1692}
1693
1694const KoColorProfile *KoColorSet::Private::loadColorProfile(QScopedPointer<KoStore> &store,
1695 const QString &path,
1696 const QString &modelId,
1697 const QString &colorDepthId)
1698{
1699 if (!store->open(path)) {
1700 return nullptr;
1701 }
1702
1703 QByteArray bytes = store->read(store->size());
1704 store->close();
1705
1707 ->createColorProfile(modelId, colorDepthId, bytes);
1708 if (!profile || !profile->valid()) {
1709 return nullptr;
1710 }
1711
1713 return profile;
1714}
1715
1716bool KoColorSet::Private::loadKplProfiles(QScopedPointer<KoStore> &store)
1717{
1718 if (!store->open("profiles.xml")) {
1719 return false;
1720 }
1721
1722 QByteArray bytes = store->read(store->size());
1723 store->close();
1724
1725 QDomDocument doc;
1726 if(!doc.setContent(bytes)) {
1727 return false;
1728 }
1729
1730 QDomElement root = doc.documentElement();
1731 for (QDomElement c = root.firstChildElement(KPL_PALETTE_PROFILE_TAG);
1732 !c.isNull();
1733 c = c.nextSiblingElement(KPL_PALETTE_PROFILE_TAG)) {
1734 QString name = c.attribute(KPL_PALETTE_NAME_ATTR);
1735 QString filename = c.attribute(KPL_PALETTE_FILENAME_ATTR);
1736 QString colorModelId = c.attribute(KPL_COLOR_MODEL_ID_ATTR);
1737 QString colorDepthId = c.attribute(KPL_COLOR_DEPTH_ID_ATTR);
1738
1740 continue;
1741 }
1742
1743 loadColorProfile(store, filename, colorModelId, colorDepthId);
1744 // TODO: What should happen if this fails?
1745 }
1746
1747 return true;
1748}
1749
1750bool KoColorSet::Private::loadKplColorset(QScopedPointer<KoStore> &store)
1751{
1752 if (!store->open("colorset.xml")) {
1753 return false;
1754 }
1755
1756 QByteArray bytes = store->read(store->size());
1757 store->close();
1758
1759 QDomDocument doc;
1760 if (!doc.setContent(bytes)) {
1761 return false;
1762 }
1763
1764 QDomElement root = doc.documentElement();
1765 colorSet->setName(root.attribute(KPL_PALETTE_NAME_ATTR));
1766 QString version = root.attribute(KPL_VERSION_ATTR);
1767 comment = root.attribute(KPL_PALETTE_COMMENT_ATTR);
1768
1769 int desiredColumnCount = root.attribute(KPL_PALETTE_COLUMN_COUNT_ATTR).toInt();
1770 if (desiredColumnCount > MAXIMUM_ALLOWED_COLUMNS) {
1771 warnPigment << "Refusing to set unreasonable number of columns (" << desiredColumnCount
1772 << ") in KPL palette file " << colorSet->filename()
1773 << " - setting maximum allowed column count instead.";
1774 colorSet->setColumnCount(MAXIMUM_ALLOWED_COLUMNS);
1775 } else {
1776 colorSet->setColumnCount(desiredColumnCount);
1777 }
1778
1779 loadKplGroup(doc, root, colorSet->getGlobalGroup(), version);
1780
1781 for (QDomElement g = root.firstChildElement(KPL_GROUP_TAG);
1782 !g.isNull();
1783 g = g.nextSiblingElement(KPL_GROUP_TAG)) {
1784 QString groupName = g.attribute(KPL_GROUP_NAME_ATTR);
1785 colorSet->addGroup(groupName);
1786 loadKplGroup(doc, g, colorSet->getGroup(groupName), version);
1787 }
1788
1789 return true;
1790}
1791
1793{
1794 QBuffer buf(&data);
1795 buf.open(QBuffer::ReadOnly);
1796
1797 QScopedPointer<KoStore> store(
1799 "application/x-krita-palette",
1800 KoStore::Zip));
1801 if (!store || store->bad()) {
1802 return false;
1803 }
1804
1805 if (store->hasFile("profiles.xml") && !loadKplProfiles(store)) {
1806 return false;
1807 }
1808
1809 if (!loadKplColorset(store)) {
1810 return false;
1811 }
1812
1813 buf.close();
1814 return true;
1815}
1816
1818{
1819 QFileInfo info(colorSet->filename());
1820 colorSet->setName(info.completeBaseName());
1821
1822 QBuffer buf(&data);
1823 buf.open(QBuffer::ReadOnly);
1824
1825 quint16 version = readShort(&buf);
1826 quint16 numColors = readShort(&buf);
1827 KisSwatch swatch;
1828
1829 if (version == 1 && buf.size() > 4+numColors*10) {
1830 buf.seek(4+numColors*10);
1831 version = readShort(&buf);
1832 numColors = readShort(&buf);
1833 }
1834
1835 const quint16 quint16_MAX = 65535;
1836
1837 KisSwatchGroupSP group = colorSet->getGlobalGroup();
1838
1839 for (int i = 0; i < numColors && !buf.atEnd(); ++i) {
1840
1841 quint16 colorSpace = readShort(&buf);
1842 quint16 ch1 = readShort(&buf);
1843 quint16 ch2 = readShort(&buf);
1844 quint16 ch3 = readShort(&buf);
1845 quint16 ch4 = readShort(&buf);
1846
1847 bool skip = false;
1848 if (colorSpace == 0) { // RGB
1850 KoColor c(KoColorSpaceRegistry::instance()->rgb16(srgb));
1851 reinterpret_cast<quint16*>(c.data())[0] = ch3;
1852 reinterpret_cast<quint16*>(c.data())[1] = ch2;
1853 reinterpret_cast<quint16*>(c.data())[2] = ch1;
1855 swatch.setColor(c);
1856 }
1857 else if (colorSpace == 1) { // HSB
1858 QColor qc;
1859 qc.setHsvF(ch1 / 65536.0, ch2 / 65536.0, ch3 / 65536.0);
1860 KoColor c(qc, KoColorSpaceRegistry::instance()->rgb16());
1862 swatch.setColor(c);
1863 }
1864 else if (colorSpace == 2) { // CMYK
1866 reinterpret_cast<quint16*>(c.data())[0] = quint16_MAX - ch1;
1867 reinterpret_cast<quint16*>(c.data())[1] = quint16_MAX - ch2;
1868 reinterpret_cast<quint16*>(c.data())[2] = quint16_MAX - ch3;
1869 reinterpret_cast<quint16*>(c.data())[3] = quint16_MAX - ch4;
1871 swatch.setColor(c);
1872 }
1873 else if (colorSpace == 7) { // LAB
1875 reinterpret_cast<quint16*>(c.data())[0] = ch3;
1876 reinterpret_cast<quint16*>(c.data())[1] = ch2;
1877 reinterpret_cast<quint16*>(c.data())[2] = ch1;
1879 swatch.setColor(c);
1880 }
1881 else if (colorSpace == 8) { // GRAY
1883 reinterpret_cast<quint16*>(c.data())[0] = ch1 * (quint16_MAX / 10000);
1885 swatch.setColor(c);
1886 }
1887 else {
1888 warnPigment << "Unsupported colorspace in palette" << colorSet->filename() << "(" << colorSpace << ")";
1889 skip = true;
1890 }
1891
1892 if (version == 2) {
1893 QString name = readUnicodeString(&buf, true);
1894 swatch.setName(name);
1895 }
1896 if (!skip) {
1897 group->addSwatch(swatch);
1898 }
1899 }
1900 return true;
1901}
1902
1903bool KoColorSet::Private::loadSbzSwatchbook(QScopedPointer<KoStore> &store)
1904{
1905 if (!store->open("swatchbook.xml")) {
1906 return false;
1907 }
1908
1909 QByteArray bytes = store->read(store->size());
1910 store->close();
1911
1912 dbgPigment << "XML palette: " << colorSet->filename() << ", SwatchBooker format";
1913
1914 QDomDocument doc;
1915#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
1916 int errorLine, errorColumn;
1917 QString errorMessage;
1918 if (!doc.setContent(bytes, &errorMessage, &errorLine, &errorColumn)) {
1919 warnPigment << "Illegal XML palette:" << colorSet->filename();
1920 warnPigment << "Error (line" << errorLine
1921 << ", column" << errorColumn
1922 << "):" << errorMessage;
1923#else
1924 QDomDocument::ParseResult result = doc.setContent(bytes);
1925 if (!result) {
1926 warnPigment << "Illegal XML palette:" << colorSet->filename();
1927 warnPigment << "Error (line" << result.errorLine
1928 << ", column" << result.errorColumn
1929 << "):" << result.errorMessage;
1930#endif
1931 return false;
1932 }
1933
1934 QDomElement root = doc.documentElement(); // SwatchBook
1935
1936 // Start reading properties...
1937 QDomElement metadata = root.firstChildElement("metadata");
1938 if (metadata.isNull()) {
1939 warnPigment << "Palette metadata not found";
1940 return false;
1941 }
1942
1943 QDomElement title = metadata.firstChildElement("dc:title");
1944 QString colorName = title.text();
1945 colorName = colorName.isEmpty() ? i18n("Untitled") : colorName;
1946 colorSet->setName(colorName);
1947 dbgPigment << "Processed name of palette:" << colorSet->name();
1948 // End reading properties
1949
1950 // Also read the swatch book...
1951 QDomElement book = root.firstChildElement("book");
1952 if (book.isNull()) {
1953 warnPigment << "Palette book (swatch composition) not found (line" << root.lineNumber()
1954 << ", column" << root.columnNumber()
1955 << ")";
1956 return false;
1957 }
1958
1959 // Which has lots of "swatch"es (todo: support groups)
1960 QDomElement swatch = book.firstChildElement();
1961 if (swatch.isNull()) {
1962 warnPigment << "Swatches/groups definition not found (line" << book.lineNumber()
1963 << ", column" << book.columnNumber()
1964 << ")";
1965 return false;
1966 }
1967
1968 // Now read colors...
1969 QDomElement materials = root.firstChildElement("materials");
1970 if (materials.isNull()) {
1971 warnPigment << "Materials (color definitions) not found";
1972 return false;
1973 }
1974
1975 // This one has lots of "color" elements
1976 if (materials.firstChildElement("color").isNull()) {
1977 warnPigment << "Color definitions not found (line" << materials.lineNumber()
1978 << ", column" << materials.columnNumber()
1979 << ")";
1980 return false;
1981 }
1982
1983 // We'll store colors here, and as we process swatches
1984 // we'll add them to the palette
1985 QHash<QString, KisSwatch> materialsBook;
1986 QHash<QString, const KoColorSpace*> fileColorSpaces;
1987
1988 // Color processing
1989 store->enterDirectory("profiles"); // Color profiles (icc files) live here
1990 for (QDomElement colorElement = materials.firstChildElement("color");
1991 !colorElement.isNull();
1992 colorElement = colorElement.nextSiblingElement("color")) {
1993 KisSwatch currentEntry;
1994 // Set if color is spot
1995 currentEntry.setSpotColor(colorElement.attribute("usage") == "spot");
1996
1997 // <metadata> inside contains id and name
1998 // one or more <values> define the color
1999 QDomElement currentColorMetadata = colorElement.firstChildElement("metadata");
2000 // Get color name
2001 QDomElement colorTitle = currentColorMetadata.firstChildElement("dc:title");
2002 QDomElement colorId = currentColorMetadata.firstChildElement("dc:identifier");
2003 // Is there an id? (we need that at the very least for identifying a color)
2004 if (colorId.text().isEmpty()) {
2005 warnPigment << "Unidentified color (line" << colorId.lineNumber()
2006 << ", column" << colorId.columnNumber()
2007 << ")";
2008 return false;
2009 }
2010
2011 if (materialsBook.contains(colorId.text())) {
2012 warnPigment << "Duplicated color definition (line" << colorId.lineNumber()
2013 << ", column" << colorId.columnNumber()
2014 << ")";
2015 return false;
2016 }
2017
2018 // Get a valid color name
2019 currentEntry.setId(colorId.text());
2020 currentEntry.setName(colorTitle.text().isEmpty() ? colorId.text() : colorTitle.text());
2021
2022 // Get a valid color definition
2023 if (colorElement.firstChildElement("values").isNull()) {
2024 warnPigment << "Color definitions not found (line" << colorElement.lineNumber()
2025 << ", column" << colorElement.columnNumber()
2026 << ")";
2027 return false;
2028 }
2029
2030 bool status;
2031 bool firstDefinition = false;
2033 const QString colorDepthId = Float32BitsColorDepthID.id();
2034 // Priority: Lab, otherwise the first definition found
2035 for (QDomElement colorValueE = colorElement.firstChildElement("values");
2036 !colorValueE.isNull();
2037 colorValueE = colorValueE.nextSiblingElement("values")) {
2038 QString model = colorValueE.attribute("model");
2039
2040 QString modelId;
2041 const KoColorProfile *profile = nullptr;
2042 if (model == "Lab") {
2043 modelId = LABAColorModelID.id();
2044 } else if (model == "sRGB") {
2045 modelId = RGBAColorModelID.id();
2046 profile = colorSpaceRegistry->rgb8()->profile();
2047 } else if (model == "XYZ") {
2048 modelId = XYZAColorModelID.id();
2049 } else if (model == "CMYK") {
2050 modelId = CMYKAColorModelID.id();
2051 } else if (model == "GRAY") {
2052 modelId = GrayAColorModelID.id();
2053 } else if (model == "RGB") {
2054 modelId = RGBAColorModelID.id();
2055 } else {
2056 warnPigment << "Color space not implemented:" << model
2057 << "(line" << colorValueE.lineNumber()
2058 << ", column "<< colorValueE.columnNumber()
2059 << ")";
2060 continue;
2061 }
2062
2063 const KoColorSpace *colorSpace = colorSpaceRegistry->colorSpace(modelId, colorDepthId, profile);
2064
2065 // The 'space' attribute is the name of the icc file
2066 // sitting in the 'profiles' directory in the zip.
2067 QString space = colorValueE.attribute("space");
2068 if (!space.isEmpty()) {
2069 if (fileColorSpaces.contains(space)) {
2070 colorSpace = fileColorSpaces.value(space);
2071 } else {
2072 // Try loading the profile and add it to the registry
2073 profile = loadColorProfile(store, space, modelId, colorDepthId);
2074 if (profile) {
2075 colorSpace = colorSpaceRegistry->colorSpace(modelId, colorDepthId, profile);
2076 fileColorSpaces.insert(space, colorSpace);
2077 }
2078 }
2079 }
2080
2081 KoColor c(colorSpace);
2082
2083 // sRGB,RGB,HSV,HSL,CMY,CMYK,nCLR: 0 -> 1
2084 // YIQ: Y 0 -> 1 : IQ -0.5 -> 0.5
2085 // Lab: L 0 -> 100 : ab -128 -> 127
2086 // XYZ: 0 -> ~100
2087 QVector<float> channels;
2088 for (const QString &str : colorValueE.text().split(" ")) {
2089 float channelValue = str.toFloat(&status);
2090 if (!status) {
2091 warnPigment << "Invalid float definition (line" << colorValueE.lineNumber()
2092 << ", column" << colorValueE.columnNumber()
2093 << ")";
2094
2095 channelValue = 0;
2096 }
2097
2098 channels.append(channelValue);
2099 }
2100 channels.append(OPACITY_OPAQUE_F); // Alpha channel
2101 colorSpace->fromNormalisedChannelsValue(c.data(), channels);
2102
2103 currentEntry.setColor(c);
2104 firstDefinition = true;
2105
2106 if (model == "Lab") {
2107 break; // Immediately add this one
2108 }
2109 }
2110
2111 if (firstDefinition) {
2112 materialsBook.insert(currentEntry.id(), currentEntry);
2113 } else {
2114 warnPigment << "No supported color spaces for the current color (line" << colorElement.lineNumber()
2115 << ", column "<< colorElement.columnNumber()
2116 << ")";
2117 return false;
2118 }
2119 }
2120
2121 store->leaveDirectory(); // Return to root
2122 // End colors
2123 // Now decide which ones will go into the palette
2124
2125 KisSwatchGroupSP global = colorSet->getGlobalGroup();
2126 for(; !swatch.isNull(); swatch = swatch.nextSiblingElement()) {
2127 QString type = swatch.tagName();
2128 if (type.isEmpty() || type.isNull()) {
2129 warnPigment << "Invalid swatch/group definition (no id) (line" << swatch.lineNumber()
2130 << ", column" << swatch.columnNumber()
2131 << ")";
2132 return false;
2133 } else if (type == "swatch") {
2134 QString id = swatch.attribute("material");
2135 if (id.isEmpty() || id.isNull()) {
2136 warnPigment << "Invalid swatch definition (no material id) (line" << swatch.lineNumber()
2137 << ", column" << swatch.columnNumber()
2138 << ")";
2139 return false;
2140 }
2141
2142 if (materialsBook.contains(id)) {
2143 global->addSwatch(materialsBook.value(id));
2144 } else {
2145 warnPigment << "Invalid swatch definition (material not found) (line" << swatch.lineNumber()
2146 << ", column" << swatch.columnNumber()
2147 << ")";
2148 return false;
2149 }
2150 } else if (type == "group") {
2151 QDomElement groupMetadata = swatch.firstChildElement("metadata");
2152 if (groupMetadata.isNull()) {
2153 warnPigment << "Invalid group definition (missing metadata) (line" << groupMetadata.lineNumber()
2154 << ", column" << groupMetadata.columnNumber()
2155 << ")";
2156 return false;
2157 }
2158 QDomElement groupTitle = metadata.firstChildElement("dc:title");
2159 if (groupTitle.isNull()) {
2160 warnPigment << "Invalid group definition (missing title) (line" << groupTitle.lineNumber()
2161 << ", column" << groupTitle.columnNumber()
2162 << ")";
2163 return false;
2164 }
2165 QString currentGroupName = groupTitle.text();
2166 colorSet->addGroup(currentGroupName);
2167
2168 for (QDomElement groupSwatch = swatch.firstChildElement("swatch");
2169 !groupSwatch.isNull();
2170 groupSwatch = groupSwatch.nextSiblingElement("swatch")) {
2171 QString id = groupSwatch.attribute("material");
2172 if (id.isEmpty() || id.isNull()) {
2173 warnPigment << "Invalid swatch definition (no material id) (line" << groupSwatch.lineNumber()
2174 << ", column" << groupSwatch.columnNumber()
2175 << ")";
2176 return false;
2177 }
2178
2179 if (materialsBook.contains(id)) {
2180 colorSet->getGroup(currentGroupName)->addSwatch(materialsBook.value(id));
2181 } else {
2182 warnPigment << "Invalid swatch definition (material not found) (line" << groupSwatch.lineNumber()
2183 << ", column" << groupSwatch.columnNumber()
2184 << ")";
2185 return false;
2186 }
2187 }
2188 }
2189 }
2190 // End palette
2191
2192 return true;
2193}
2194
2196 QBuffer buf(&data);
2197 buf.open(QBuffer::ReadOnly);
2198
2199 // &buf is a subclass of QIODevice
2200 QScopedPointer<KoStore> store(
2202 "application/x-swatchbook",
2203 KoStore::Zip));
2204 if (!store || store->bad()) {
2205 return false;
2206 }
2207
2208 if (store->hasFile("swatchbook.xml") && !loadSbzSwatchbook(store)) {
2209 return false;
2210 }
2211
2212 buf.close();
2213 return true;
2214}
2215
2217{
2218 QFileInfo info(colorSet->filename());
2219 colorSet->setName(info.completeBaseName());
2220
2221 QBuffer buf(&data);
2222 buf.open(QBuffer::ReadOnly);
2223
2224 QByteArray signature; // should be "ASEF";
2225 signature = buf.read(4);
2226 quint16 version = readShort(&buf);
2227 quint16 version2 = readShort(&buf);
2228
2229 if (signature != "ASEF" && version!= 1 && version2 != 0) {
2230 qWarning() << "incorrect header:" << signature << version << version2;
2231 return false;
2232 }
2233 qint32 numBlocks = readInt(&buf);
2234
2235 QByteArray groupStart("\xC0\x01");
2236 QByteArray groupEnd("\xC0\x02");
2237 QByteArray swatchSig("\x00\x01");
2238
2239 bool inGroup = false;
2240 QString groupName;
2241 for (qint32 i = 0; i < numBlocks; i++) {
2242 QByteArray blockType;
2243 blockType = buf.read(2);
2244 qint32 blockSize = readInt(&buf);
2245 qint64 pos = buf.pos();
2246
2247 if (blockType == groupStart) {
2248 groupName = readUnicodeString(&buf);
2249 colorSet->addGroup(groupName);
2250 inGroup = true;
2251 }
2252 else if (blockType == groupEnd) {
2253 int colorCount = colorSet->getGroup(groupName)->colorCount();
2254 int columns = colorSet->columnCount();
2255 int rows = colorCount/columns;
2256 if (colorCount % columns > 0) {
2257 rows += 1;
2258 }
2259 colorSet->getGroup(groupName)->setRowCount(rows);
2260 inGroup = false;
2261 }
2262 else /* if (blockType == swatchSig)*/ {
2263 KisSwatch swatch;
2264 swatch.setName(readUnicodeString(&buf).trimmed());
2265 QByteArray colorModel;
2266 QDomDocument doc;
2267 colorModel = buf.read(4);
2268 if (colorModel == "RGB ") {
2269 QDomElement elt = doc.createElement("sRGB");
2270
2271 elt.setAttribute("r", readFloat(&buf));
2272 elt.setAttribute("g", readFloat(&buf));
2273 elt.setAttribute("b", readFloat(&buf));
2274
2275 KoColor color = KoColor::fromXML(elt, "U8");
2276 swatch.setColor(color);
2277 } else if (colorModel == "CMYK") {
2278 QDomElement elt = doc.createElement("CMYK");
2279
2280 elt.setAttribute("c", readFloat(&buf));
2281 elt.setAttribute("m", readFloat(&buf));
2282 elt.setAttribute("y", readFloat(&buf));
2283 elt.setAttribute("k", readFloat(&buf));
2284 //try to select the default PS icc profile if possible.
2285 elt.setAttribute("space", "U.S. Web Coated (SWOP) v2");
2286
2287 KoColor color = KoColor::fromXML(elt, "U8");
2288 swatch.setColor(color);
2289 } else if (colorModel == "LAB ") {
2290 QDomElement elt = doc.createElement("Lab");
2291
2292 elt.setAttribute("L", readFloat(&buf)*100.0);
2293 elt.setAttribute("a", readFloat(&buf));
2294 elt.setAttribute("b", readFloat(&buf));
2295
2296 KoColor color = KoColor::fromXML(elt, "U16");
2297 swatch.setColor(color);
2298 } else if (colorModel == "GRAY") {
2299 QDomElement elt = doc.createElement("Gray");
2300
2301 elt.setAttribute("g", readFloat(&buf));
2302
2303 KoColor color = KoColor::fromXML(elt, "U8");
2304 swatch.setColor(color);
2305 }
2306 qint16 type = readShort(&buf);
2307 if (type == 1) { //0 is global, 2 is regular;
2308 swatch.setSpotColor(true);
2309 }
2310 if (inGroup) {
2311 colorSet->addSwatch(swatch, groupName);
2312 } else {
2313 colorSet->addSwatch(swatch);
2314 }
2315 }
2316 buf.seek(pos + qint64(blockSize));
2317 }
2318 return true;
2319}
2320
2322{
2323
2324 QFileInfo info(colorSet->filename());
2325
2326 QBuffer buf(&data);
2327 buf.open(QBuffer::ReadOnly);
2328
2329 QByteArray signature; // should be "8BCB";
2330 signature = buf.read(4);
2331 quint16 version = readShort(&buf);
2332 quint16 bookID = readShort(&buf);
2333 Q_UNUSED(bookID);
2334
2335 if (signature != "8BCB" && version!= 1) {
2336 return false;
2337 }
2338
2340 for (int i = 0; i< 4; i++) {
2341
2342 QString metadataString = readUnicodeString(&buf, true);
2343 if (metadataString.startsWith("\"")) {
2344 metadataString = metadataString.remove(0, 1);
2345 }
2346 if (metadataString.endsWith("\"")) {
2347 metadataString.chop(1);
2348 }
2349 if (metadataString.startsWith("$$$/")) {
2350 if (metadataString.contains("=")) {
2351 metadataString = metadataString.split("=").last();
2352 } else {
2353 metadataString = QString();
2354 }
2355 }
2356 metadata.append(metadataString);
2357 }
2358 QString title = metadata.at(0);
2359 colorSet->setName(title);
2360 QString prefix = metadata.at(1);
2361 QString postfix = metadata.at(2);
2362 QString description = metadata.at(3);
2363 colorSet->setComment(description);
2364
2365 quint16 numColors = readShort(&buf);
2366 quint16 numColumns = readShort(&buf);
2367 numColumns = numColumns > 0 ? numColumns : 8; // overwrite with sane default in case of 0
2368 colorSet->setColumnCount(numColumns);
2369 quint16 numKeyColorPage = readShort(&buf);
2370 Q_UNUSED(numKeyColorPage);
2371 quint16 colorType = readShort(&buf);
2372
2374 if (colorType == 2) {
2375 QString profileName = "U.S. Web Coated (SWOP) v2";
2377 } else if (colorType == 7) {
2379 }
2380
2381 for (quint16 i = 0; i < numColors; i++) {
2382 KisSwatch swatch;
2384 name << prefix;
2385 name << readUnicodeString(&buf, true);
2386 name << postfix;
2387 swatch.setName(name.join(" ").trimmed());
2388 QByteArray key; // should be "8BCB";
2389 key = buf.read(6);
2390 swatch.setId(QString::fromLatin1(key));
2391 swatch.setSpotColor(true);
2392 quint8 c1 = readByte(&buf);
2393 quint8 c2 = readByte(&buf);
2394 quint8 c3 = readByte(&buf);
2395 KoColor c(cs);
2396 if (colorType == 0) {
2397 c.data()[0] = c3;
2398 c.data()[1] = c2;
2399 c.data()[2] = c1;
2400 } else if (colorType == 2) {
2401 quint8 c4 = readByte(&buf);
2402 c.data()[0] = c1;
2403 c.data()[1] = c2;
2404 c.data()[2] = c3;
2405 c.data()[3] = c4;
2406 } else if (colorType == 7) {
2407 c.data()[0] = c1;
2408 c.data()[1] = c2;
2409 c.data()[2] = c3;
2410 }
2411 c.setOpacity(1.0);
2412 swatch.setColor(c);
2413 colorSet->addSwatch(swatch);
2414 }
2415
2416 return true;
2417}
2418
2420 bool res = false;
2421
2422 QXmlStreamReader *xml = new QXmlStreamReader(data);
2423
2424 if (xml->readNextStartElement()) {
2425 auto paletteId = xml->name();
2426 if (paletteId.compare(QString("SCRIBUSCOLORS"), Qt::CaseInsensitive) == 0) { // Scribus
2427 dbgPigment << "XML palette: " << colorSet->filename() << ", Scribus format";
2428 res = loadScribusXmlPalette(colorSet, xml);
2429 }
2430 else {
2431 // Unknown XML format
2432 xml->raiseError("Unknown XML palette format. Expected SCRIBUSCOLORS, found " + paletteId);
2433 }
2434 }
2435
2436 // If there is any error (it should be returned through the stream)
2437 if (xml->hasError() || !res) {
2438 warnPigment << "Illegal XML palette:" << colorSet->filename();
2439 warnPigment << "Error (line"<< xml->lineNumber() << ", column" << xml->columnNumber() << "):" << xml->errorString();
2440 return false;
2441 }
2442 else {
2443 dbgPigment << "XML palette parsed successfully:" << colorSet->filename();
2444 return true;
2445 }
2446}
2447
2448bool KoColorSet::Private::saveKpl(QIODevice *dev) const
2449{
2450 QScopedPointer<KoStore> store(KoStore::createStore(dev, KoStore::Write, "application/x-krita-palette", KoStore::Zip));
2451 if (!store || store->bad()) {
2452 qWarning() << "saveKpl could not create store";
2453 return false;
2454 }
2455
2456 QSet<const KoColorSpace *> colorSpaces;
2457
2458 {
2459 QDomDocument doc;
2460 QDomElement root = doc.createElement(KPL_PALETTE_TAG);
2461 root.setAttribute(KPL_VERSION_ATTR, "2.0");
2462 root.setAttribute(KPL_PALETTE_NAME_ATTR, colorSet->name());
2463 root.setAttribute(KPL_PALETTE_COMMENT_ATTR, comment);
2464 root.setAttribute(KPL_PALETTE_COLUMN_COUNT_ATTR, colorSet->columnCount());
2465 root.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, colorSet->getGlobalGroup()->rowCount());
2466
2467 saveKplGroup(doc, root, colorSet->getGroup(KoColorSet::GLOBAL_GROUP_NAME), colorSpaces);
2468
2469 for (const KisSwatchGroupSP &group : swatchGroups) {
2470 if (group->name() == KoColorSet::GLOBAL_GROUP_NAME) { continue; }
2471 QDomElement gl = doc.createElement(KPL_GROUP_TAG);
2472 gl.setAttribute(KPL_GROUP_NAME_ATTR, group->name());
2473 root.appendChild(gl);
2474 saveKplGroup(doc, gl, group, colorSpaces);
2475 }
2476
2477 doc.appendChild(root);
2478 if (!store->open("colorset.xml")) { return false; }
2479 QByteArray ba = doc.toByteArray();
2480 if (store->write(ba) != ba.size()) { return false; }
2481 if (!store->close()) { return false; }
2482 }
2483
2484 QDomDocument doc;
2485 QDomElement profileElement = doc.createElement("Profiles");
2486
2487 for (const KoColorSpace *colorSpace : colorSpaces) {
2488 QString fn = QFileInfo(colorSpace->profile()->fileName()).fileName();
2489 if (!store->open(fn)) { qWarning() << "Could not open the store for profiles directory"; return false; }
2490 QByteArray profileRawData = colorSpace->profile()->rawData();
2491 if (!store->write(profileRawData)) { qWarning() << "Could not write the profiles data into the store"; return false; }
2492 if (!store->close()) { qWarning() << "Could not close the store for profiles directory"; return false; }
2493 QDomElement el = doc.createElement(KPL_PALETTE_PROFILE_TAG);
2494 el.setAttribute(KPL_PALETTE_FILENAME_ATTR, fn);
2495 el.setAttribute(KPL_PALETTE_NAME_ATTR, colorSpace->profile()->name());
2496 el.setAttribute(KPL_COLOR_MODEL_ID_ATTR, colorSpace->colorModelId().id());
2497 el.setAttribute(KPL_COLOR_DEPTH_ID_ATTR, colorSpace->colorDepthId().id());
2498 profileElement.appendChild(el);
2499
2500 }
2501 doc.appendChild(profileElement);
2502
2503 if (!store->open("profiles.xml")) { qWarning() << "Could not open profiles.xml"; return false; }
2504 QByteArray ba = doc.toByteArray();
2505
2506 int bytesWritten = store->write(ba);
2507 if (bytesWritten != ba.size()) { qWarning() << "Bytes written is wrong" << ba.size(); return false; }
2508
2509 if (!store->close()) { qWarning() << "Could not close the store"; return false; }
2510
2511 bool r = store->finalize();
2512 if (!r) { qWarning() << "Could not finalize the store"; }
2513 return r;
2514}
2515
2517 QDomElement &groupEle,
2518 const KisSwatchGroupSP group,
2519 QSet<const KoColorSpace *> &colorSetSet) const
2520{
2521 groupEle.setAttribute(KPL_GROUP_ROW_COUNT_ATTR, QString::number(group->rowCount()));
2522
2523 for (const KisSwatchGroup::SwatchInfo &info : group->infoList()) {
2524 const KoColorProfile *profile = info.swatch.color().colorSpace()->profile();
2525 // Only save non-builtin profiles.=
2526 if (!profile->fileName().isEmpty()) {
2527 bool alreadyIncluded = false;
2528 Q_FOREACH(const KoColorSpace* colorSpace, colorSetSet) {
2529 if (colorSpace->profile()->fileName() == profile->fileName()) {
2530 alreadyIncluded = true;
2531 break;
2532 }
2533 }
2534 if(!alreadyIncluded) {
2535 colorSetSet.insert(info.swatch.color().colorSpace());
2536 }
2537 }
2538 QDomElement swatchEle = doc.createElement(KPL_SWATCH_TAG);
2539 swatchEle.setAttribute(KPL_SWATCH_NAME_ATTR, info.swatch.name());
2540 swatchEle.setAttribute(KPL_SWATCH_ID_ATTR, info.swatch.id());
2541 swatchEle.setAttribute(KPL_SWATCH_SPOT_ATTR, info.swatch.spotColor() ? "true" : "false");
2542 swatchEle.setAttribute(KPL_SWATCH_BITDEPTH_ATTR, info.swatch.color().colorSpace()->colorDepthId().id());
2543 info.swatch.color().toXML(doc, swatchEle);
2544
2545 QDomElement positionEle = doc.createElement(KPL_SWATCH_POS_TAG);
2546 positionEle.setAttribute(KPL_SWATCH_ROW_ATTR, info.row);
2547 positionEle.setAttribute(KPL_SWATCH_COL_ATTR, info.column);
2548 swatchEle.appendChild(positionEle);
2549
2550 groupEle.appendChild(swatchEle);
2551 }
2552}
2553
2554void KoColorSet::Private::loadKplGroup(const QDomDocument &doc, const QDomElement &parentEle, KisSwatchGroupSP group, QString version)
2555{
2556 Q_UNUSED(doc);
2557 if (!parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()) {
2558 group->setRowCount(parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).toInt());
2559 }
2560 group->setColumnCount(colorSet->columnCount());
2561
2562 for (QDomElement swatchEle = parentEle.firstChildElement(KPL_SWATCH_TAG);
2563 !swatchEle.isNull();
2564 swatchEle = swatchEle.nextSiblingElement(KPL_SWATCH_TAG)) {
2565 QString colorDepthId = swatchEle.attribute(KPL_SWATCH_BITDEPTH_ATTR, Integer8BitsColorDepthID.id());
2566 KisSwatch swatch;
2567
2568 if (version == "1.0" && swatchEle.firstChildElement().tagName() == "Lab") {
2569 // previous version of krita had the values wrong, and scaled everything between 0 to 1,
2570 // but lab requires L = 0-100 and AB = -128-127.
2571 // TODO: write unittest for this.
2572 QDomElement el = swatchEle.firstChildElement();
2573 double L = KisDomUtils::toDouble(el.attribute("L"));
2574 el.setAttribute("L", L*100.0);
2575 double ab = KisDomUtils::toDouble(el.attribute("a"));
2576 if (ab <= .5) {
2577 ab = (0.5 - ab) * 2 * -128.0;
2578 } else {
2579 ab = (ab - 0.5) * 2 * 127.0;
2580 }
2581 el.setAttribute("a", ab);
2582
2583 ab = KisDomUtils::toDouble(el.attribute("b"));
2584 if (ab <= .5) {
2585 ab = (0.5 - ab) * 2 * -128.0;
2586 } else {
2587 ab = (ab - 0.5) * 2 * 127.0;
2588 }
2589 el.setAttribute("b", ab);
2590 swatch.setColor(KoColor::fromXML(el, colorDepthId));
2591 } else {
2592 swatch.setColor(KoColor::fromXML(swatchEle.firstChildElement(), colorDepthId));
2593 }
2594 swatch.setName(swatchEle.attribute(KPL_SWATCH_NAME_ATTR));
2595 swatch.setId(swatchEle.attribute(KPL_SWATCH_ID_ATTR));
2596 swatch.setSpotColor(swatchEle.attribute(KPL_SWATCH_SPOT_ATTR, "false") == "true" ? true : false);
2597 QDomElement positionEle = swatchEle.firstChildElement(KPL_SWATCH_POS_TAG);
2598 if (!positionEle.isNull()) {
2599 int rowNumber = positionEle.attribute(KPL_SWATCH_ROW_ATTR).toInt();
2600 int columnNumber = positionEle.attribute(KPL_SWATCH_COL_ATTR).toInt();
2601 if (columnNumber < 0 ||
2602 columnNumber >= colorSet->columnCount() ||
2603 rowNumber < 0
2604 ) {
2605 warnPigment << "Swatch" << swatch.name()
2606 << "of palette" << colorSet->name()
2607 << "has invalid position.";
2608 continue;
2609 }
2610 group->setSwatch(swatch, columnNumber, rowNumber);
2611 } else {
2612 group->addSwatch(swatch);
2613 }
2614 }
2615
2616 if (parentEle.attribute(KPL_GROUP_ROW_COUNT_ATTR).isNull()
2617 && group->colorCount() > 0
2618 && group->columnCount() > 0
2619 && (group->colorCount() / (group->columnCount()) + 1) < 20) {
2620 group->setRowCount((group->colorCount() / group->columnCount()) + 1);
2621 }
2622
2623}
#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
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