Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_paintop_preset.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2009 Sven Langkamp <sven.langkamp@gmail.com>
4 *
5 * SPDX-License-Identifier: LGPL-2.0-or-later
6 */
8
9#include <QFile>
10#include <QSize>
11#include <QImage>
12#include <QImageWriter>
13#include <QImageReader>
14#include <QDomDocument>
15#include <QBuffer>
16
17#include <KisDirtyStateSaver.h>
18
21#include "kis_painter.h"
23#include "kis_paint_device.h"
24#include "kis_image.h"
30#include <KisResourceModel.h>
32#include <KisResourceTypes.h>
36
37#include <KoStore.h>
38
39struct Q_DECL_HIDDEN KisPaintOpPreset::Private {
40
43 : m_parentPreset(parentPreset)
44 {
45 }
46
47 void setDirty(bool value) override {
48 m_parentPreset->setDirty(value);
49 }
50
51 bool isDirty() const override {
52 return m_parentPreset->isDirty();
53 }
54
55 void notifySettingsChanged() override {
56 KisPaintOpPresetUpdateProxy* proxy = m_parentPreset->updateProxyNoCreate();
57 if (proxy) {
58 proxy->notifySettingsChanged();
59 }
60 }
61
62 private:
64 };
65
66public:
68 : settingsUpdateListener(new UpdateListener(q)),
69 version("5.0")
70 {
71 }
72
74 QScopedPointer<KisPaintOpPresetUpdateProxy> updateProxy;
76 QString version;
78};
79
80
82 : KoResource(QString())
83 , d(new Private(this))
84{
85}
86
87KisPaintOpPreset::KisPaintOpPreset(const QString & fileName)
88 : KoResource(fileName)
89 , d(new Private(this))
90{
91 setName(name().replace("_", " "));
92}
93
98
100 : KoResource(rhs)
101 , d(new Private(this))
102{
103 if (rhs.settings()) {
104 setSettings(rhs.settings()); // the settings are cloned inside!
105 }
107 // only valid if we could clone the settings
108 setValid(rhs.settings());
109
110 setName(rhs.name());
111 setImage(rhs.image());
112}
113
115{
116 return KoResourceSP(new KisPaintOpPreset(*this));
117}
118
120{
121 Q_ASSERT(d->settings);
122 d->settings->setProperty("paintop", paintOp.id());
123}
124
126{
127 Q_ASSERT(d->settings);
128 return KoID(d->settings->getString("paintop"));
129}
130
132{
133 return KoResource::name().replace("_", " ");
134}
135
137{
138 Q_ASSERT(settings);
139 Q_ASSERT(!settings->getString("paintop", QString()).isEmpty());
140
141 KisDirtyStateSaver<KisPaintOpPreset*> dirtyStateSaver(this);
142
143 if (d->settings) {
144 d->settings->setUpdateListener(KisPaintOpSettings::UpdateListenerWSP());
145 d->settings = 0;
146 }
147
148 if (settings) {
149 d->settings = settings->clone();
150 d->settings->setUpdateListener(d->settingsUpdateListener);
151 }
152
153 if (d->updateProxy) {
154 d->updateProxy->notifyUniformPropertiesChanged();
155 d->updateProxy->notifySettingsChanged();
156 }
157 setValid(true);
158}
159
161{
162 Q_ASSERT(d->settings);
163 Q_ASSERT(!d->settings->getString("paintop", QString()).isEmpty());
164
165 return d->settings;
166}
167
168bool KisPaintOpPreset::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
169{
170 QImageReader reader(dev, "PNG");
171
172 d->version = reader.text("version");
173 QString preset = reader.text("preset");
174
175 if (!(d->version == "2.2" || d->version == "5.0")) {
176 return false;
177 }
178
179 QImage img;
180 if (!reader.read(&img)) {
181 dbgImage << "Fail to decode PNG";
182 return false;
183 }
184
185 //Workaround for broken presets
186 //Presets was saved with nested cdata section
187 preset.replace("<curve><![CDATA[", "<curve>");
188 preset.replace("]]></curve>", "</curve>");
189 //Presets with non-base64 pattern md5
190 QRegularExpressionMatch patternMd5 = QRegularExpression("<param (?:type=\"string\" )?name=\"Texture/Pattern/PatternMD5\"(?: type=\"string\")?><!\\[CDATA\\[(.+?)\\]\\]></param>").match(preset);
191 if (patternMd5.hasMatch() && patternMd5.captured(1).contains(QRegularExpression("[^a-zA-Z0-9+/=]"))) {
192 preset.replace(patternMd5.captured(0), "");
193 }
194
195 QDomDocument doc;
196 if (!doc.setContent(preset)) {
197 return false;
198 }
199
200 QDomElement root = doc.documentElement();
201
202 if (d->version == "5.0") {
203 // Load any embedded resources
204 QDomElement e = root.firstChildElement("resources");
205 if (!e.isNull()) {
206 for (e = e.firstChildElement("resource"); !e.isNull(); e = e.nextSiblingElement("resource")) {
207 QString name = e.attribute("name");
208 QString filename = e.attribute("filename");
209 QString resourceType = e.attribute("type");
210 QString md5sum = e.attribute("md5sum");
211
212 KoResourceSP existingResource = resourcesInterface
213 ->source(resourceType)
214 .bestMatch(md5sum, filename, name);
215
216 if (existingResource) {
217 continue;
218 }
219
220 QByteArray ba = QByteArray::fromBase64(e.text().toLatin1());
221 QBuffer buf(&ba);
222 buf.open(QBuffer::ReadOnly);
223
224 d->sideLoadedResources.append(
227 ba));
228 }
229 }
230 }
231
233
234 if (!d->settings) {
235 return false;
236 }
237
238 setValid(d->settings->isValid());
239
240 if (!img.textKeys().isEmpty()) {
241 QImage strippedImage(img.size(), img.format());
242 memcpy(strippedImage.bits(), img.bits(), img.sizeInBytes());
243
244 if (img.format() == QImage::Format_Indexed8) {
245 strippedImage.setColorTable(img.colorTable());
246 }
247
248 setImage(strippedImage);
249 } else {
250 setImage(img);
251 }
252
254
255 return true;
256}
257
258void KisPaintOpPreset::toXML(QDomDocument& doc, QDomElement& elt) const
259{
260 QString paintopid = d->settings->getString("paintop", QString());
261
262 elt.setAttribute("paintopid", paintopid);
263 elt.setAttribute("name", name());
264
265
267
268 elt.setAttribute("embedded_resources", linkedResources.count());
269
270 if (!linkedResources.isEmpty()) {
271 QDomElement resourcesElement = doc.createElement("resources");
272 elt.appendChild(resourcesElement);
273 Q_FOREACH(KoResourceLoadResult linkedResource, linkedResources) {
274 // we have requested linked resources, how can it be an embedded one?
276
277 KoResourceSP resource = linkedResource.resource();
278
279 if (!resource) {
280 qWarning() << "WARNING: KisPaintOpPreset::toXML couldn't fetch a linked resource" << linkedResource.signature();
281 continue;
282 }
283
284 //KIS_SAFE_ASSERT_RECOVER_NOOP(resource->isSerializable() && "embedding non-serializable resources is not yet implemented");
285 if (!resource->isSerializable()) {
286 qWarning() << "embedding non-serializable resources is not yet implemented. Resource: " << filename() << name()
287 << "cannot embed" << resource->filename() << resource->name() << resource->resourceType().first << resource->resourceType().second;
288 continue;
289 }
290
291 QBuffer buf;
292 buf.open(QBuffer::WriteOnly);
293 KisResourceModel model(resource->resourceType().first);
294 bool r = model.exportResource(resource, &buf);
295 buf.close();
296 if (r) {
297 QDomText text = doc.createCDATASection(QString::fromLatin1(buf.data().toBase64()));
298 QDomElement e = doc.createElement("resource");
299 e.setAttribute("type", resource->resourceType().first);
300 e.setAttribute("md5sum", resource->md5Sum());
301 e.setAttribute("name", resource->name());
302 e.setAttribute("filename", resource->filename());
303 e.appendChild(text);
304 resourcesElement.appendChild(e);
305
306 }
307 }
308 }
309
310 // sanitize the settings
311 bool hasTexture = d->settings->getBool("Texture/Pattern/Enabled");
312 if (!hasTexture) {
313 Q_FOREACH (const QString & key, d->settings->getProperties().keys()) {
314 if (key.startsWith("Texture") && key != "Texture/Pattern/Enabled") {
315 d->settings->removeProperty(key);
316 }
317 }
318 }
319
320 d->settings->toXML(doc, elt);
321}
322
323void KisPaintOpPreset::fromXML(const QDomElement& presetElt, KisResourcesInterfaceSP resourcesInterface)
324{
325 setName(presetElt.attribute("name"));
326 QString paintopid = presetElt.attribute("paintopid");
327
328 if (!metadata().contains("paintopid")) {
329 addMetaData("paintopid", paintopid);
330 }
331
332 if (paintopid.isEmpty()) {
333 dbgImage << "No paintopid attribute";
334 setValid(false);
335 return;
336 }
337
338 if (KisPaintOpRegistry::instance()->get(paintopid) == 0) {
339 dbgImage << "No paintop " << paintopid;
340 setValid(false);
341 return;
342 }
343
344 KoID id(paintopid, QString());
345
347 if (!settings) {
348 setValid(false);
349 warnKrita << "Could not load settings for preset" << paintopid;
350 return;
351 }
352
353 settings->fromXML(presetElt);
354
355 // sanitize the settings
356 bool hasTexture = settings->getBool("Texture/Pattern/Enabled");
357 if (!hasTexture) {
358 Q_FOREACH (const QString & key, settings->getProperties().keys()) {
359 if (key.startsWith("Texture") && key != "Texture/Pattern/Enabled") {
360 settings->removeProperty(key);
361 }
362 }
363 }
365
366}
367
368bool KisPaintOpPreset::saveToDevice(QIODevice *dev) const
369{
370 QImageWriter writer(dev, "PNG");
371
372 QDomDocument doc;
373 QDomElement root = doc.createElement("Preset");
374
375 toXML(doc, root);
376
377 doc.appendChild(root);
378
392 d->version = "5.0";
393
395
396 writer.setText("version", d->version);
397 writer.setText("preset", doc.toString());
398
399 QImage img;
400
401 if (image().isNull()) {
402 img = QImage(1, 1, QImage::Format_RGB32);
403 } else {
404 img = image();
405 }
406
407 return writer.write(img);
408}
409
411{
418 if (d->version == "2.2") {
419 KisResourcesInterfaceSP fakeResourcesInterface(new KisLocalStrokeResources());
420 QList<KoResourceLoadResult> dependentResources = this->linkedResources(fakeResourcesInterface);
421
422 QStringList resourceFileNames;
423
424 Q_FOREACH (KoResourceLoadResult resource, dependentResources) {
425 const QString filename = resource.signature().filename;
426
427 if (!filename.isEmpty()) {
428 resourceFileNames.append(filename);
429 }
430 }
431
432 KritaUtils::makeContainerUnique(resourceFileNames);
433
434 if (!resourceFileNames.isEmpty()) {
435 addMetaData("dependent_resources_filenames", resourceFileNames);
436 }
437 } else {
438 addMetaData("dependent_resources_filenames", QStringList());
439 }
440}
441
443{
444 if (!d->updateProxy) {
445 d->updateProxy.reset(new KisPaintOpPresetUpdateProxy());
446 }
447 return d->updateProxy.data();
448}
449
451{
452 return d->updateProxy.data();
453}
454
456{
460 return d->settings->uniformProperties(d->settings, updateProxy());
461}
462
464{
465 return d->settings && d->settings->hasMaskingSettings();
466}
467
469{
470 KisPaintOpPresetSP result;
471
472 if (d->settings && d->settings->hasMaskingSettings()) {
473 result.reset(new KisPaintOpPreset());
474 result->setSettings(d->settings->createMaskingSettings());
475 if (!result->valid()) {
476 result.clear();
477 }
478 }
479
480 return result;
481}
482
484{
485 return d->settings ? d->settings->resourcesInterface() : nullptr;
486}
487
489{
491 d->settings->setResourcesInterface(resourcesInterface);
492}
493
495{
496 return d->settings ? d->settings->canvasResourcesInterface() : nullptr;
497}
498
500{
502 d->settings->setCanvasResourcesInterface(canvasResourcesInterface);
503}
504
509
511{
512 KisPaintOpPresetSP result =
513 KisRequiredResourcesOperators::cloneWithResourcesSnapshot<KisPaintOpPresetSP>(this, globalResourcesInterface);
514
515 const QList<int> canvasResources = result->requiredCanvasResources();
516 if (!canvasResources.isEmpty()) {
518 Q_FOREACH (int key, canvasResources) {
519 storage->storeResource(key, canvasResourcesInterface->resource(key));
520 }
521 result->setCanvasResourcesInterface(storage);
522 }
523
524 if (cacheInterface) {
525 result->setResourceCacheInterface(cacheInterface);
526 } else if (!canvasResources.isEmpty()) {
533 result->setResourceCacheInterface(nullptr);
534 }
535
536 return result;
537}
538
540{
542
543 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->settings, resources);
544
547 resources << f->prepareLinkedResources(d->settings, globalResourcesInterface);
548
549 if (hasMaskingPreset()) {
550 KisPaintOpPresetSP maskingPreset = createMaskingPreset();
551 Q_ASSERT(maskingPreset);
552
553 KisPaintOpFactory* f = KisPaintOpRegistry::instance()->value(maskingPreset->paintOp().id());
555 resources << f->prepareLinkedResources(maskingPreset->settings(), globalResourcesInterface);
556
557 }
558
559 return resources;
560}
561
563{
565
566 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->settings, resources);
567
570 resources << f->prepareEmbeddedResources(d->settings, globalResourcesInterface);
571
572 if (hasMaskingPreset()) {
573 KisPaintOpPresetSP maskingPreset = createMaskingPreset();
574 Q_ASSERT(maskingPreset);
575 KisPaintOpFactory* f = KisPaintOpRegistry::instance()->value(maskingPreset->paintOp().id());
577 resources << f->prepareEmbeddedResources(maskingPreset->settings(), globalResourcesInterface);
578
579 }
580
581 return resources;
582}
583
585{
587
588 Q_FOREACH(const KoResourceLoadResult &resource, d->sideLoadedResources) {
589 KoResourceSignature sig = resource.signature();
590
594 if (!globalResourcesInterface->source(sig.type)
595 .bestMatch(sig.md5sum, sig.filename, sig.name)) {
596
597 resources << resource;
598 }
599 }
600
601 return resources;
602}
603
605{
606 d->sideLoadedResources.clear();
607}
608
610{
611 return d->settings ? d->settings->requiredCanvasResources() : QList<int>();
612}
613
615{
617 d->settings->setResourceCacheInterface(cacheInterface);
618}
619
621{
622 return d->settings ? d->settings->resourceCacheInterface() : KoResourceCacheInterfaceSP();
623}
624
626{
628
629 d->settings->regenerateResourceCache(cacheInterface);
630 cacheInterface->setRelatedResourceCookie(d->settings->sanityVersionCookie());
631}
632
634{
635 return d->settings->sanityVersionCookie() == cacheInterface->relatedResourceCookie();
636}
637
639 : m_updateProxy(preset->updateProxyNoCreate())
640{
641 if (m_updateProxy) {
642 m_updateProxy->postponeSettingsChanges();
643 }
644}
645
647{
648 if (m_updateProxy) {
649 m_updateProxy->unpostponeSettingsChanges();
650 }
651}
float value(const T *src, size_t ch)
VertexDescriptor get(PredecessorMap const &m, VertexDescriptor v)
QList< QString > QStringList
a KisResourcesInterface-like resources storage for preloaded resources
The KisPaintOpPresetUpdateProxy class.
QPointer< KisPaintOpPresetUpdateProxy > m_updateProxy
UpdatedPostponer(KisPaintOpPresetSP preset)
static KisPaintOpRegistry * instance()
KisPaintOpSettingsSP createSettings(const KoID &id, KisResourcesInterfaceSP resourcesInterface) const
The KisResourceModel class provides the main access to resources. It is possible to filter the resour...
bool exportResource(KoResourceSP resource, QIODevice *device) override
exportResource exports a resource into a QIODevice
const T value(const QString &id) const
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
KoResourceSP resource() const noexcept
KoResourceSignature signature() const
A simple wrapper object for the main information about the resource.
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define warnKrita
Definition kis_debug.h:87
#define dbgImage
Definition kis_debug.h:46
QSharedPointer< KoResource > KoResourceSP
QSharedPointer< KoResourceCacheInterface > KoResourceCacheInterfaceSP
void makeContainerUnique(C &container)
void setDirty(bool value) override
UpdateListener(KisPaintOpPreset *parentPreset)
KisResourcesInterfaceSP resourcesInterface() const
void regenerateResourceCache(KoResourceCacheInterfaceSP cacheInterface)
KisPaintOpPresetSP cloneWithResourcesSnapshot(KisResourcesInterfaceSP globalResourcesInterface, KoCanvasResourcesInterfaceSP canvasResourcesInterface, KoResourceCacheInterfaceSP cacheInterface) const
QList< int > requiredCanvasResources() const override
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
void setResourceCacheInterface(KoResourceCacheInterfaceSP cacheInterface)
QPointer< KisPaintOpPresetUpdateProxy > updateProxyNoCreate() const
KoResourceSP clone() const override
bool saveToDevice(QIODevice *dev) const override
void setResourcesInterface(KisResourcesInterfaceSP resourcesInterface)
QList< KoResourceLoadResult > linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const override
void toXML(QDomDocument &doc, QDomElement &elt) const
KoID paintOp() const
return the id of the paintop plugin
KoCanvasResourcesInterfaceSP canvasResourcesInterface() const
void setCanvasResourcesInterface(KoCanvasResourcesInterfaceSP canvasResourcesInterface)
void fromXML(const QDomElement &elt, KisResourcesInterfaceSP resourcesInterface)
void setPaintOp(const KoID &paintOp)
set the id of the paintop plugin
QList< KoResourceLoadResult > embeddedResources(KisResourcesInterfaceSP globalResourcesInterface) const override
QString name() const override
bool hasMaskingPreset() const
void setSettings(KisPaintOpSettingsSP settings)
replace the current settings object with the specified settings
KisPaintOpSettingsSP settings
QList< KoResourceLoadResult > sideLoadedResources
KisPaintOpPresetSP createMaskingPreset() const
void clearSideLoadedResources() override
KisPaintOpSettings::UpdateListenerSP settingsUpdateListener
bool sanityCheckResourceCacheIsValid(KoResourceCacheInterfaceSP cacheInterface) const
QPair< QString, QString > resourceType() const override
QList< KisUniformPaintOpPropertySP > uniformProperties()
QScopedPointer< KisPaintOpPresetUpdateProxy > updateProxy
KoResourceCacheInterfaceSP resourceCacheInterface() const
bool hasLocalResourcesSnapshot() const
Private(KisPaintOpPreset *q)
void setValid(bool valid)
void setName(const QString &name)
void addMetaData(QString key, QVariant value)
store the given key, value pair in the resource
QImage image
QString filename
void setImage(const QImage &image)
QMap< QString, QVariant > metadata
bool isDirty() const
QString md5sum
QString name