File indexing completed on 2024-05-26 04:27:28
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org> 0003 * SPDX-FileCopyrightText: 2009 Sven Langkamp <sven.langkamp@gmail.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 #include <brushengine/kis_paintop_preset.h> 0008 0009 #include <QFile> 0010 #include <QSize> 0011 #include <QImage> 0012 #include <QImageWriter> 0013 #include <QImageReader> 0014 #include <QDomDocument> 0015 #include <QBuffer> 0016 0017 #include <KisDirtyStateSaver.h> 0018 0019 #include <brushengine/kis_paintop_settings.h> 0020 #include "kis_paintop_registry.h" 0021 #include "kis_painter.h" 0022 #include <brushengine/kis_paint_information.h> 0023 #include "kis_paint_device.h" 0024 #include "kis_image.h" 0025 #include "KisPaintOpPresetUpdateProxy.h" 0026 #include <brushengine/kis_paintop_config_widget.h> 0027 #include <KisRequiredResourcesOperators.h> 0028 #include <KoLocalStrokeCanvasResources.h> 0029 #include <KisResourceModel.h> 0030 #include "KisPaintopSettingsIds.h" 0031 #include <KisResourceTypes.h> 0032 #include <KisResourceModelProvider.h> 0033 #include <krita_container_utils.h> 0034 #include <KoResourceCacheInterface.h> 0035 0036 #include <KoStore.h> 0037 0038 struct Q_DECL_HIDDEN KisPaintOpPreset::Private { 0039 0040 struct UpdateListener : public KisPaintOpSettings::UpdateListener { 0041 UpdateListener(KisPaintOpPreset *parentPreset) 0042 : m_parentPreset(parentPreset) 0043 { 0044 } 0045 0046 void setDirty(bool value) override { 0047 m_parentPreset->setDirty(value); 0048 } 0049 0050 bool isDirty() const override { 0051 return m_parentPreset->isDirty(); 0052 } 0053 0054 void notifySettingsChanged() override { 0055 KisPaintOpPresetUpdateProxy* proxy = m_parentPreset->updateProxyNoCreate(); 0056 if (proxy) { 0057 proxy->notifySettingsChanged(); 0058 } 0059 } 0060 0061 private: 0062 KisPaintOpPreset *m_parentPreset; 0063 }; 0064 0065 public: 0066 Private(KisPaintOpPreset *q) 0067 : settingsUpdateListener(new UpdateListener(q)), 0068 version("5.0") 0069 { 0070 } 0071 0072 KisPaintOpSettingsSP settings {0}; 0073 QScopedPointer<KisPaintOpPresetUpdateProxy> updateProxy; 0074 KisPaintOpSettings::UpdateListenerSP settingsUpdateListener; 0075 QString version; 0076 }; 0077 0078 0079 KisPaintOpPreset::KisPaintOpPreset() 0080 : KoResource(QString()) 0081 , d(new Private(this)) 0082 { 0083 } 0084 0085 KisPaintOpPreset::KisPaintOpPreset(const QString & fileName) 0086 : KoResource(fileName) 0087 , d(new Private(this)) 0088 { 0089 setName(name().replace("_", " ")); 0090 } 0091 0092 KisPaintOpPreset::~KisPaintOpPreset() 0093 { 0094 delete d; 0095 } 0096 0097 KisPaintOpPreset::KisPaintOpPreset(const KisPaintOpPreset &rhs) 0098 : KoResource(rhs) 0099 , d(new Private(this)) 0100 { 0101 if (rhs.settings()) { 0102 setSettings(rhs.settings()); // the settings are cloned inside! 0103 } 0104 KIS_SAFE_ASSERT_RECOVER_NOOP(isDirty() == rhs.isDirty()); 0105 // only valid if we could clone the settings 0106 setValid(rhs.settings()); 0107 0108 setName(rhs.name()); 0109 setImage(rhs.image()); 0110 } 0111 0112 KoResourceSP KisPaintOpPreset::clone() const 0113 { 0114 return KoResourceSP(new KisPaintOpPreset(*this)); 0115 } 0116 0117 void KisPaintOpPreset::setPaintOp(const KoID & paintOp) 0118 { 0119 Q_ASSERT(d->settings); 0120 d->settings->setProperty("paintop", paintOp.id()); 0121 } 0122 0123 KoID KisPaintOpPreset::paintOp() const 0124 { 0125 Q_ASSERT(d->settings); 0126 return KoID(d->settings->getString("paintop")); 0127 } 0128 0129 QString KisPaintOpPreset::name() const 0130 { 0131 return KoResource::name().replace("_", " "); 0132 } 0133 0134 void KisPaintOpPreset::setSettings(KisPaintOpSettingsSP settings) 0135 { 0136 Q_ASSERT(settings); 0137 Q_ASSERT(!settings->getString("paintop", QString()).isEmpty()); 0138 0139 KisDirtyStateSaver<KisPaintOpPreset*> dirtyStateSaver(this); 0140 0141 if (d->settings) { 0142 d->settings->setUpdateListener(KisPaintOpSettings::UpdateListenerWSP()); 0143 d->settings = 0; 0144 } 0145 0146 if (settings) { 0147 d->settings = settings->clone(); 0148 d->settings->setUpdateListener(d->settingsUpdateListener); 0149 } 0150 0151 if (d->updateProxy) { 0152 d->updateProxy->notifyUniformPropertiesChanged(); 0153 d->updateProxy->notifySettingsChanged(); 0154 } 0155 setValid(true); 0156 } 0157 0158 KisPaintOpSettingsSP KisPaintOpPreset::settings() const 0159 { 0160 Q_ASSERT(d->settings); 0161 Q_ASSERT(!d->settings->getString("paintop", QString()).isEmpty()); 0162 0163 return d->settings; 0164 } 0165 0166 bool KisPaintOpPreset::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) 0167 { 0168 QImageReader reader(dev, "PNG"); 0169 0170 d->version = reader.text("version"); 0171 QString preset = reader.text("preset"); 0172 int resourceCount = reader.text("embedded_resources").toInt(); 0173 0174 if (!(d->version == "2.2" || d->version == "5.0")) { 0175 return false; 0176 } 0177 0178 QImage img; 0179 if (!reader.read(&img)) { 0180 dbgImage << "Fail to decode PNG"; 0181 return false; 0182 } 0183 0184 //Workaround for broken presets 0185 //Presets was saved with nested cdata section 0186 preset.replace("<curve><![CDATA[", "<curve>"); 0187 preset.replace("]]></curve>", "</curve>"); 0188 0189 QDomDocument doc; 0190 if (!doc.setContent(preset)) { 0191 return false; 0192 } 0193 0194 QDomElement root = doc.documentElement(); 0195 0196 if (d->version == "5.0" && resourceCount > 0) { 0197 // Load any embedded resources 0198 QDomElement e = root.firstChildElement("resources"); 0199 for (e = e.firstChildElement("resource"); !e.isNull(); e = e.nextSiblingElement("resource")) { 0200 QString name = e.attribute("name"); 0201 QString filename = e.attribute("filename"); 0202 QString resourceType = e.attribute("type"); 0203 QString md5sum = e.attribute("md5sum"); 0204 0205 KoResourceSP existingResource = resourcesInterface 0206 ->source(resourceType) 0207 .bestMatch(md5sum, filename, name); 0208 0209 if (existingResource) { 0210 continue; 0211 } 0212 0213 QByteArray ba = QByteArray::fromBase64(e.text().toLatin1()); 0214 QBuffer buf(&ba); 0215 buf.open(QBuffer::ReadOnly); 0216 0217 /// HACK ALERT: Calling importResource() 0218 /// here is technically undefined 0219 /// behavior, because this code is 0220 /// called from inside the storage's 0221 /// loadVersionedResource(). Basically 0222 /// we change underlying storage's 0223 /// storage while it is reading from 0224 /// it. 0225 0226 KisResourceModel model(resourceType); 0227 model.importResource(filename, &buf, false, "memory"); 0228 } 0229 } 0230 0231 fromXML(root, resourcesInterface); 0232 0233 if (!d->settings) { 0234 return false; 0235 } 0236 0237 setValid(d->settings->isValid()); 0238 0239 setImage(img); 0240 0241 return true; 0242 } 0243 0244 void KisPaintOpPreset::toXML(QDomDocument& doc, QDomElement& elt) const 0245 { 0246 QString paintopid = d->settings->getString("paintop", QString()); 0247 0248 elt.setAttribute("paintopid", paintopid); 0249 elt.setAttribute("name", name()); 0250 0251 0252 QList<KoResourceLoadResult> linkedResources = this->linkedResources(resourcesInterface()); 0253 0254 elt.setAttribute("embedded_resources", linkedResources.count()); 0255 0256 if (!linkedResources.isEmpty()) { 0257 QDomElement resourcesElement = doc.createElement("resources"); 0258 elt.appendChild(resourcesElement); 0259 Q_FOREACH(KoResourceLoadResult linkedResource, linkedResources) { 0260 // we have requested linked resources, how can it be an embedded one? 0261 KIS_SAFE_ASSERT_RECOVER(linkedResource.type() != KoResourceLoadResult::EmbeddedResource) { continue; } 0262 0263 KoResourceSP resource = linkedResource.resource(); 0264 0265 if (!resource) { 0266 qWarning() << "WARNING: KisPaintOpPreset::toXML couldn't fetch a linked resource" << linkedResource.signature(); 0267 continue; 0268 } 0269 0270 //KIS_SAFE_ASSERT_RECOVER_NOOP(resource->isSerializable() && "embedding non-serializable resources is not yet implemented"); 0271 if (!resource->isSerializable()) { 0272 qWarning() << "embedding non-serializable resources is not yet implemented. Resource: " << filename() << name() 0273 << "cannot embed" << resource->filename() << resource->name() << resource->resourceType().first << resource->resourceType().second; 0274 continue; 0275 } 0276 0277 QBuffer buf; 0278 buf.open(QBuffer::WriteOnly); 0279 KisResourceModel model(resource->resourceType().first); 0280 bool r = model.exportResource(resource, &buf); 0281 buf.close(); 0282 if (r) { 0283 QDomText text = doc.createCDATASection(QString::fromLatin1(buf.data().toBase64())); 0284 QDomElement e = doc.createElement("resource"); 0285 e.setAttribute("type", resource->resourceType().first); 0286 e.setAttribute("md5sum", resource->md5Sum()); 0287 e.setAttribute("name", resource->name()); 0288 e.setAttribute("filename", resource->filename()); 0289 e.appendChild(text); 0290 resourcesElement.appendChild(e); 0291 0292 } 0293 } 0294 } 0295 0296 // sanitize the settings 0297 bool hasTexture = d->settings->getBool("Texture/Pattern/Enabled"); 0298 if (!hasTexture) { 0299 Q_FOREACH (const QString & key, d->settings->getProperties().keys()) { 0300 if (key.startsWith("Texture") && key != "Texture/Pattern/Enabled") { 0301 d->settings->removeProperty(key); 0302 } 0303 } 0304 } 0305 0306 d->settings->toXML(doc, elt); 0307 } 0308 0309 void KisPaintOpPreset::fromXML(const QDomElement& presetElt, KisResourcesInterfaceSP resourcesInterface) 0310 { 0311 setName(presetElt.attribute("name")); 0312 QString paintopid = presetElt.attribute("paintopid"); 0313 0314 if (!metadata().contains("paintopid")) { 0315 addMetaData("paintopid", paintopid); 0316 } 0317 0318 if (paintopid.isEmpty()) { 0319 dbgImage << "No paintopid attribute"; 0320 setValid(false); 0321 return; 0322 } 0323 0324 if (KisPaintOpRegistry::instance()->get(paintopid) == 0) { 0325 dbgImage << "No paintop " << paintopid; 0326 setValid(false); 0327 return; 0328 } 0329 0330 KoID id(paintopid, QString()); 0331 0332 KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->createSettings(id, resourcesInterface); 0333 if (!settings) { 0334 setValid(false); 0335 warnKrita << "Could not load settings for preset" << paintopid; 0336 return; 0337 } 0338 0339 settings->fromXML(presetElt); 0340 0341 // sanitize the settings 0342 bool hasTexture = settings->getBool("Texture/Pattern/Enabled"); 0343 if (!hasTexture) { 0344 Q_FOREACH (const QString & key, settings->getProperties().keys()) { 0345 if (key.startsWith("Texture") && key != "Texture/Pattern/Enabled") { 0346 settings->removeProperty(key); 0347 } 0348 } 0349 } 0350 setSettings(settings); 0351 0352 } 0353 0354 bool KisPaintOpPreset::saveToDevice(QIODevice *dev) const 0355 { 0356 QImageWriter writer(dev, "PNG"); 0357 0358 QDomDocument doc; 0359 QDomElement root = doc.createElement("Preset"); 0360 0361 toXML(doc, root); 0362 0363 doc.appendChild(root); 0364 0365 /** 0366 * HACK ALERT: We update the version of the resource format on 0367 * the first save operation, even though there is no guarantee 0368 * that it was "save" operation, but not "export" operation. 0369 * 0370 * The only point it affects now is whether we need to check 0371 * for the presence of the linkedResources() in 0372 * updateLinkedResourcesMetaData(). The new version of the 0373 * preset format ("5.0") has all the linked resources embedded 0374 * outside KisPaintOpSettings, which are automatically 0375 * loaded on the resource activation. We we shouldn't 0376 * add them into metaData()["dependent_resources_filenames"]. 0377 */ 0378 d->version = "5.0"; 0379 0380 writer.setText("version", d->version); 0381 writer.setText("preset", doc.toString()); 0382 0383 QImage img; 0384 0385 if (image().isNull()) { 0386 img = QImage(1, 1, QImage::Format_RGB32); 0387 } else { 0388 img = image(); 0389 } 0390 0391 return writer.write(img); 0392 0393 } 0394 0395 void KisPaintOpPreset::updateLinkedResourcesMetaData(KisResourcesInterfaceSP resourcesInterface) 0396 { 0397 /** 0398 * The new preset format embeds all the linked resources outside 0399 * KisPaintOpSettings and loads them on activation, therefore we 0400 * shouldn't add them into "dependent_resources_filenames". 0401 */ 0402 0403 if (d->version == "2.2") { 0404 QList<KoResourceLoadResult> dependentResources = this->linkedResources(resourcesInterface); 0405 0406 QStringList resourceFileNames; 0407 0408 Q_FOREACH (KoResourceLoadResult resource, dependentResources) { 0409 const QString filename = resource.signature().filename; 0410 0411 if (!filename.isEmpty()) { 0412 resourceFileNames.append(filename); 0413 } 0414 } 0415 0416 KritaUtils::makeContainerUnique(resourceFileNames); 0417 0418 if (!resourceFileNames.isEmpty()) { 0419 addMetaData("dependent_resources_filenames", resourceFileNames); 0420 } 0421 } 0422 } 0423 0424 QPointer<KisPaintOpPresetUpdateProxy> KisPaintOpPreset::updateProxy() const 0425 { 0426 if (!d->updateProxy) { 0427 d->updateProxy.reset(new KisPaintOpPresetUpdateProxy()); 0428 } 0429 return d->updateProxy.data(); 0430 } 0431 0432 QPointer<KisPaintOpPresetUpdateProxy> KisPaintOpPreset::updateProxyNoCreate() const 0433 { 0434 return d->updateProxy.data(); 0435 } 0436 0437 QList<KisUniformPaintOpPropertySP> KisPaintOpPreset::uniformProperties() 0438 { 0439 /// we pass a shared pointer to settings explicitly, 0440 /// because the settings will not be able to wrap 0441 /// itself into a shared pointer 0442 return d->settings->uniformProperties(d->settings, updateProxy()); 0443 } 0444 0445 bool KisPaintOpPreset::hasMaskingPreset() const 0446 { 0447 return d->settings && d->settings->hasMaskingSettings(); 0448 } 0449 0450 KisPaintOpPresetSP KisPaintOpPreset::createMaskingPreset() const 0451 { 0452 KisPaintOpPresetSP result; 0453 0454 if (d->settings && d->settings->hasMaskingSettings()) { 0455 result.reset(new KisPaintOpPreset()); 0456 result->setSettings(d->settings->createMaskingSettings()); 0457 if (!result->valid()) { 0458 result.clear(); 0459 } 0460 } 0461 0462 return result; 0463 } 0464 0465 KisResourcesInterfaceSP KisPaintOpPreset::resourcesInterface() const 0466 { 0467 return d->settings ? d->settings->resourcesInterface() : nullptr; 0468 } 0469 0470 void KisPaintOpPreset::setResourcesInterface(KisResourcesInterfaceSP resourcesInterface) 0471 { 0472 KIS_SAFE_ASSERT_RECOVER_RETURN(d->settings); 0473 d->settings->setResourcesInterface(resourcesInterface); 0474 } 0475 0476 KoCanvasResourcesInterfaceSP KisPaintOpPreset::canvasResourcesInterface() const 0477 { 0478 return d->settings ? d->settings->canvasResourcesInterface() : nullptr; 0479 } 0480 0481 void KisPaintOpPreset::setCanvasResourcesInterface(KoCanvasResourcesInterfaceSP canvasResourcesInterface) 0482 { 0483 KIS_SAFE_ASSERT_RECOVER_RETURN(d->settings); 0484 d->settings->setCanvasResourcesInterface(canvasResourcesInterface); 0485 } 0486 0487 bool KisPaintOpPreset::hasLocalResourcesSnapshot() const 0488 { 0489 return KisRequiredResourcesOperators::hasLocalResourcesSnapshot(this); 0490 } 0491 0492 KisPaintOpPresetSP KisPaintOpPreset::cloneWithResourcesSnapshot(KisResourcesInterfaceSP globalResourcesInterface, KoCanvasResourcesInterfaceSP canvasResourcesInterface, KoResourceCacheInterfaceSP cacheInterface) const 0493 { 0494 KisPaintOpPresetSP result = 0495 KisRequiredResourcesOperators::cloneWithResourcesSnapshot<KisPaintOpPresetSP>(this, globalResourcesInterface); 0496 0497 const QList<int> canvasResources = result->requiredCanvasResources(); 0498 if (!canvasResources.isEmpty()) { 0499 KoLocalStrokeCanvasResourcesSP storage(new KoLocalStrokeCanvasResources()); 0500 Q_FOREACH (int key, canvasResources) { 0501 storage->storeResource(key, canvasResourcesInterface->resource(key)); 0502 } 0503 result->setCanvasResourcesInterface(storage); 0504 } 0505 0506 if (cacheInterface) { 0507 result->setResourceCacheInterface(cacheInterface); 0508 } else if (!canvasResources.isEmpty()) { 0509 /** 0510 * If the preset depends on any canvas resources, then we don't trust 0511 * the caches that are stored inside. Instead we just reset them. If the 0512 * preset is independent of the canvas resources, then its caches are, 0513 * most probably valid and we can reuse them. 0514 */ 0515 result->setResourceCacheInterface(nullptr); 0516 } 0517 0518 return result; 0519 } 0520 0521 QList<KoResourceLoadResult> KisPaintOpPreset::linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const 0522 { 0523 QList<KoResourceLoadResult> resources; 0524 0525 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->settings, resources); 0526 0527 KisPaintOpFactory* f = KisPaintOpRegistry::instance()->value(paintOp().id()); 0528 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(f, resources); 0529 resources << f->prepareLinkedResources(d->settings, globalResourcesInterface); 0530 0531 if (hasMaskingPreset()) { 0532 KisPaintOpPresetSP maskingPreset = createMaskingPreset(); 0533 Q_ASSERT(maskingPreset); 0534 0535 KisPaintOpFactory* f = KisPaintOpRegistry::instance()->value(maskingPreset->paintOp().id()); 0536 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(f, resources); 0537 resources << f->prepareLinkedResources(maskingPreset->settings(), globalResourcesInterface); 0538 0539 } 0540 0541 return resources; 0542 } 0543 0544 QList<KoResourceLoadResult> KisPaintOpPreset::embeddedResources(KisResourcesInterfaceSP globalResourcesInterface) const 0545 { 0546 QList<KoResourceLoadResult> resources; 0547 0548 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(d->settings, resources); 0549 0550 KisPaintOpFactory* f = KisPaintOpRegistry::instance()->value(paintOp().id()); 0551 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(f, resources); 0552 resources << f->prepareEmbeddedResources(d->settings, globalResourcesInterface); 0553 0554 if (hasMaskingPreset()) { 0555 KisPaintOpPresetSP maskingPreset = createMaskingPreset(); 0556 Q_ASSERT(maskingPreset); 0557 KisPaintOpFactory* f = KisPaintOpRegistry::instance()->value(maskingPreset->paintOp().id()); 0558 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(f, resources); 0559 resources << f->prepareEmbeddedResources(maskingPreset->settings(), globalResourcesInterface); 0560 0561 } 0562 0563 return resources; 0564 } 0565 0566 QList<int> KisPaintOpPreset::requiredCanvasResources() const 0567 { 0568 return d->settings ? d->settings->requiredCanvasResources() : QList<int>(); 0569 } 0570 0571 void KisPaintOpPreset::setResourceCacheInterface(KoResourceCacheInterfaceSP cacheInterface) 0572 { 0573 KIS_SAFE_ASSERT_RECOVER_RETURN(d->settings); 0574 d->settings->setResourceCacheInterface(cacheInterface); 0575 } 0576 0577 KoResourceCacheInterfaceSP KisPaintOpPreset::resourceCacheInterface() const 0578 { 0579 return d->settings ? d->settings->resourceCacheInterface() : KoResourceCacheInterfaceSP(); 0580 } 0581 0582 void KisPaintOpPreset::regenerateResourceCache(KoResourceCacheInterfaceSP cacheInterface) 0583 { 0584 KIS_SAFE_ASSERT_RECOVER_RETURN(d->settings); 0585 0586 d->settings->regenerateResourceCache(cacheInterface); 0587 cacheInterface->setRelatedResourceCookie(d->settings->sanityVersionCookie()); 0588 } 0589 0590 bool KisPaintOpPreset::sanityCheckResourceCacheIsValid(KoResourceCacheInterfaceSP cacheInterface) const 0591 { 0592 return d->settings->sanityVersionCookie() == cacheInterface->relatedResourceCookie(); 0593 } 0594 0595 KisPaintOpPreset::UpdatedPostponer::UpdatedPostponer(KisPaintOpPresetSP preset) 0596 : m_updateProxy(preset->updateProxyNoCreate()) 0597 { 0598 if (m_updateProxy) { 0599 m_updateProxy->postponeSettingsChanges(); 0600 } 0601 } 0602 0603 KisPaintOpPreset::UpdatedPostponer::~UpdatedPostponer() 0604 { 0605 if (m_updateProxy) { 0606 m_updateProxy->unpostponeSettingsChanges(); 0607 } 0608 }