File indexing completed on 2024-05-12 16:01:31
0001 /* 0002 * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org> 0003 * SPDX-FileCopyrightText: 2007 Cyrille Berger <cberger@cberger.net> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kis_filter_manager.h" 0009 0010 0011 #include <QHash> 0012 #include <KisSignalMapper.h> 0013 0014 #include <QMessageBox> 0015 #include <kactionmenu.h> 0016 #include <kactioncollection.h> 0017 0018 #include <KoID.h> 0019 #include <KisMainWindow.h> 0020 0021 // krita/image 0022 #include <filter/kis_filter.h> 0023 #include <filter/kis_filter_registry.h> 0024 #include <filter/kis_filter_configuration.h> 0025 #include <kis_paint_device.h> 0026 #include <kis_paint_device_frames_interface.h> 0027 #include <kis_image_animation_interface.h> 0028 #include <kis_raster_keyframe_channel.h> 0029 #include <kis_time_span.h> 0030 #include <kis_image_config.h> 0031 #include <KisAnimAutoKey.h> 0032 0033 // krita/ui 0034 #include "KisViewManager.h" 0035 #include "kis_canvas2.h" 0036 #include <kis_bookmarked_configuration_manager.h> 0037 0038 #include "kis_action.h" 0039 #include "kis_action_manager.h" 0040 #include "kis_canvas_resource_provider.h" 0041 #include "dialogs/kis_dlg_filter.h" 0042 #include "strokes/kis_filter_stroke_strategy.h" 0043 #include "krita_utils.h" 0044 #include "kis_icon_utils.h" 0045 #include "kis_layer_utils.h" 0046 #include <KisGlobalResourcesInterface.h> 0047 0048 struct KisFilterManager::Private { 0049 KisAction* reapplyAction = nullptr; 0050 KisAction* reapplyActionReprompt = nullptr; 0051 QHash<QString, KActionMenu*> filterActionMenus; 0052 QHash<KisFilter*, QAction *> filters2Action; 0053 KisKActionCollection *actionCollection = nullptr; 0054 KisActionManager *actionManager = nullptr; 0055 KisViewManager *view = nullptr; 0056 0057 KisFilterConfigurationSP lastConfiguration; 0058 KisFilterConfigurationSP currentlyAppliedConfiguration; 0059 KisStrokeId currentStrokeId; 0060 KisFilterStrokeStrategy::ExternalCancelUpdatesStorageSP externalCancelUpdatesStorage; 0061 KisFilterStrokeStrategy::IdleBarrierData::IdleBarrierCookie idleBarrierCookie; 0062 0063 bool filterAllSelectedFrames = false; 0064 0065 KisSignalMapper actionsMapper; 0066 0067 /*! 0068 * \brief The filter dialog shown to the user 0069 * \note parent QWidget is set to mainwindow, so we delegate deletion of this widget to Qt (we don't `delete` it ourselves) 0070 */ 0071 KisDlgFilter *filterDialog = nullptr; 0072 }; 0073 0074 KisFilterManager::KisFilterManager(KisViewManager * view) 0075 : d(new Private) 0076 { 0077 d->view = view; 0078 } 0079 0080 KisFilterManager::~KisFilterManager() 0081 { 0082 } 0083 0084 void KisFilterManager::setView(QPointer<KisView>imageView) 0085 { 0086 Q_UNUSED(imageView); 0087 } 0088 0089 0090 void KisFilterManager::setup(KisKActionCollection * ac, KisActionManager *actionManager) 0091 { 0092 d->actionCollection = ac; 0093 d->actionManager = actionManager; 0094 0095 // Setup reapply action 0096 d->reapplyAction = d->actionManager->createAction("filter_apply_again"); 0097 d->reapplyAction->setActivationFlags(KisAction::ACTIVE_DEVICE); 0098 d->reapplyAction->setEnabled(false); 0099 0100 d->reapplyActionReprompt = d->actionManager->createAction("filter_apply_reprompt"); 0101 d->reapplyActionReprompt->setActivationFlags(KisAction::ACTIVE_DEVICE); 0102 d->reapplyActionReprompt->setEnabled(false); 0103 0104 connect(d->reapplyAction, SIGNAL(triggered()), SLOT(reapplyLastFilter())); 0105 connect(d->reapplyActionReprompt, SIGNAL(triggered()), SLOT(reapplyLastFilterReprompt())); 0106 0107 connect(&d->actionsMapper, SIGNAL(mapped(QString)), SLOT(showFilterDialog(QString))); 0108 0109 // Setup list of filters 0110 QStringList keys = KisFilterRegistry::instance()->keys(); 0111 keys.sort(); 0112 Q_FOREACH (const QString &filterName, keys) { 0113 insertFilter(filterName); 0114 } 0115 0116 connect(KisFilterRegistry::instance(), SIGNAL(filterAdded(QString)), SLOT(insertFilter(QString))); 0117 } 0118 0119 void KisFilterManager::insertFilter(const QString & filterName) 0120 { 0121 Q_ASSERT(d->actionCollection); 0122 0123 KisFilterSP filter = KisFilterRegistry::instance()->value(filterName); 0124 Q_ASSERT(filter); 0125 0126 if (d->filters2Action.contains(filter.data())) { 0127 warnKrita << "Filter" << filterName << " has already been inserted"; 0128 return; 0129 } 0130 0131 KoID category = filter->menuCategory(); 0132 KActionMenu* actionMenu = d->filterActionMenus[ category.id()]; 0133 if (!actionMenu) { 0134 actionMenu = new KActionMenu(category.name(), this); 0135 d->actionCollection->addAction(category.id(), actionMenu); 0136 d->filterActionMenus[category.id()] = actionMenu; 0137 } 0138 0139 KisAction *action = new KisAction(filter->menuEntry(), this); 0140 action->setDefaultShortcut(filter->shortcut()); 0141 action->setActivationFlags(KisAction::ACTIVE_DEVICE); 0142 0143 d->actionManager->addAction(QString("krita_filter_%1").arg(filterName), action); 0144 d->filters2Action[filter.data()] = action; 0145 0146 actionMenu->addAction(action); 0147 0148 d->actionsMapper.setMapping(action, filterName); 0149 connect(action, SIGNAL(triggered()), &d->actionsMapper, SLOT(map())); 0150 } 0151 0152 void KisFilterManager::updateGUI() 0153 { 0154 if (!d->view) return; 0155 0156 bool enable = false; 0157 0158 KisNodeSP activeNode = d->view->activeNode(); 0159 enable = activeNode && activeNode->hasEditablePaintDevice(); 0160 0161 d->reapplyAction->setEnabled(enable); 0162 0163 for (QHash<KisFilter*, QAction *>::iterator it = d->filters2Action.begin(); 0164 it != d->filters2Action.end(); ++it) { 0165 0166 bool localEnable = enable; 0167 0168 it.value()->setEnabled(localEnable); 0169 } 0170 } 0171 0172 void KisFilterManager::reapplyLastFilter() 0173 { 0174 if (!d->lastConfiguration) return; 0175 0176 apply(d->lastConfiguration); 0177 finish(); 0178 } 0179 0180 void KisFilterManager::reapplyLastFilterReprompt() 0181 { 0182 if (!d->lastConfiguration) return; 0183 0184 showFilterDialog(d->lastConfiguration->name(), d->lastConfiguration); 0185 } 0186 0187 void KisFilterManager::showFilterDialog(const QString &filterId, KisFilterConfigurationSP overrideDefaultConfig) 0188 { 0189 if (!d->view->activeNode()->isEditable()) { 0190 d->view->showFloatingMessage(i18n("Cannot apply filter to locked layer."), 0191 KisIconUtils::loadIcon("object-locked")); 0192 return; 0193 } 0194 0195 if (d->filterDialog && d->filterDialog->isVisible()) { 0196 KisFilterSP filter = KisFilterRegistry::instance()->value(filterId); 0197 d->filterDialog->setFilter(filter, overrideDefaultConfig); 0198 return; 0199 } 0200 0201 connect(d->view->image(), 0202 SIGNAL(sigStrokeCancellationRequested()), 0203 SLOT(slotStrokeCancelRequested()), 0204 Qt::UniqueConnection); 0205 0206 connect(d->view->image(), 0207 SIGNAL(sigStrokeEndRequested()), 0208 SLOT(slotStrokeEndRequested()), 0209 Qt::UniqueConnection); 0210 0211 /** 0212 * The UI should show only after every running stroke is finished, 0213 * so a virtual barrier is added here. 0214 */ 0215 if (!d->view->blockUntilOperationsFinished(d->view->image())) { 0216 return; 0217 } 0218 0219 Q_ASSERT(d->view); 0220 Q_ASSERT(d->view->activeNode()); 0221 0222 KisPaintDeviceSP dev = d->view->activeNode()->paintDevice(); 0223 if (!dev) { 0224 warnKrita << "KisFilterManager::showFilterDialog(): Filtering was requested for illegal active layer!" << d->view->activeNode(); 0225 return; 0226 } 0227 0228 KisFilterSP filter = KisFilterRegistry::instance()->value(filterId); 0229 0230 if (dev->colorSpace()->willDegrade(filter->colorSpaceIndependence())) { 0231 // Warning bells! 0232 if (filter->colorSpaceIndependence() == TO_LAB16) { 0233 if (QMessageBox::warning(d->view->mainWindow(), 0234 i18nc("@title:window", "Krita"), 0235 i18n("The %1 filter will convert your %2 data to 16-bit L*a*b* and vice versa. ", 0236 filter->name(), 0237 dev->colorSpace()->name()), 0238 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) 0239 != QMessageBox::Ok) return; 0240 0241 } else if (filter->colorSpaceIndependence() == TO_RGBA16) { 0242 if (QMessageBox::warning(d->view->mainWindow(), 0243 i18nc("@title:window", "Krita"), 0244 i18n("The %1 filter will convert your %2 data to 16-bit RGBA and vice versa. ", 0245 filter->name() , dev->colorSpace()->name()), 0246 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) 0247 != QMessageBox::Ok) return; 0248 } 0249 } 0250 0251 if (filter->showConfigurationWidget()) { 0252 if (!d->filterDialog) { 0253 d->filterDialog = new KisDlgFilter(d->view , d->view->activeNode(), this, d->view->mainWindow()); 0254 d->filterDialog->setAttribute(Qt::WA_DeleteOnClose); // make sure that the dialog is deleted when calling `done()` 0255 connect(d->filterDialog, SIGNAL(finished(int)), 0256 this, SLOT(filterDialogHasFinished(int))); 0257 } 0258 0259 d->filterDialog->setFilter(filter, overrideDefaultConfig); 0260 d->filterDialog->setVisible(true); 0261 } else { 0262 KisFilterConfigurationSP defaultConfiguration = 0263 overrideDefaultConfig ? overrideDefaultConfig : filter->defaultConfiguration(KisGlobalResourcesInterface::instance()); 0264 apply(defaultConfiguration); 0265 finish(); 0266 } 0267 } 0268 0269 void KisFilterManager::apply(KisFilterConfigurationSP _filterConfig) 0270 { 0271 KisFilterConfigurationSP filterConfig = _filterConfig->cloneWithResourcesSnapshot(); 0272 0273 KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name()); 0274 KisImageWSP image = d->view->image(); 0275 0276 if (d->currentStrokeId) { 0277 image->cancelStroke(d->currentStrokeId); 0278 0279 d->currentStrokeId.clear(); 0280 d->idleBarrierCookie.clear(); 0281 } else { 0282 image->waitForDone(); 0283 } 0284 0285 if (!d->externalCancelUpdatesStorage) { 0286 // Lazily initialize the cancel updates storage, just in case 0287 // if the stroke has been cancelled in the meantime. 0288 0289 d->externalCancelUpdatesStorage.reset(new KisFilterStrokeStrategy::ExternalCancelUpdatesStorage()); 0290 } 0291 0292 KoCanvasResourceProvider *resourceManager = 0293 d->view->canvasResourceProvider()->resourceManager(); 0294 0295 KisResourcesSnapshotSP resources = 0296 new KisResourcesSnapshot(image, 0297 d->view->activeNode(), 0298 resourceManager); 0299 0300 KisFilterStrokeStrategy *strategy = new KisFilterStrokeStrategy(filter, 0301 KisFilterConfigurationSP(filterConfig), 0302 resources, 0303 d->externalCancelUpdatesStorage.toWeakRef()); 0304 { 0305 KConfigGroup group( KSharedConfig::openConfig(), "filterdialog"); 0306 strategy->setForceLodModeIfPossible(group.readEntry("forceLodMode", true)); 0307 } 0308 0309 d->currentStrokeId = 0310 image->startStroke(strategy); 0311 0312 // Apply filter preview to active, visible frame only. 0313 KisImageConfig imgConf(true); 0314 image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::FilterJobData()); 0315 0316 { 0317 KisFilterStrokeStrategy::IdleBarrierData *data = 0318 new KisFilterStrokeStrategy::IdleBarrierData(); 0319 d->idleBarrierCookie = data->idleBarrierCookie(); 0320 image->addJob(d->currentStrokeId, data); 0321 } 0322 0323 d->currentlyAppliedConfiguration = filterConfig; 0324 } 0325 0326 void KisFilterManager::finish() 0327 { 0328 Q_ASSERT(d->currentStrokeId); 0329 0330 if (d->filterAllSelectedFrames) { // Apply filter to the other selected frames... 0331 KisImageSP image = d->view->image(); 0332 KisPaintDeviceSP paintDevice = d->view->activeNode()->paintDevice(); 0333 KisNodeSP node = d->view->activeNode(); 0334 0335 // Filter selected times to only those with keyframes... 0336 QSet<int> selectedTimes = image->animationInterface()->activeLayerSelectedTimes(); 0337 selectedTimes = KisLayerUtils::filterTimesForOnlyRasterKeyedTimes(node, selectedTimes); 0338 QSet<int> uniqueFrames = KisLayerUtils::fetchUniqueFrameTimes(node, selectedTimes, true); 0339 0340 Q_FOREACH(const int& frameTime, uniqueFrames) { 0341 image->addJob(d->currentStrokeId, new KisFilterStrokeStrategy::FilterJobData(frameTime)); 0342 } 0343 } 0344 0345 d->view->image()->endStroke(d->currentStrokeId); 0346 0347 KisFilterSP filter = KisFilterRegistry::instance()->value(d->currentlyAppliedConfiguration->name()); 0348 if (filter->bookmarkManager()) { 0349 filter->bookmarkManager()->save(KisBookmarkedConfigurationManager::ConfigLastUsed, 0350 d->currentlyAppliedConfiguration.data()); 0351 } 0352 0353 d->lastConfiguration = d->currentlyAppliedConfiguration; 0354 d->reapplyAction->setEnabled(true); 0355 d->reapplyAction->setText(i18n("Apply Filter Again: %1", filter->name())); 0356 0357 d->idleBarrierCookie.clear(); 0358 d->currentlyAppliedConfiguration.clear(); 0359 } 0360 0361 void KisFilterManager::cancelRunningStroke() 0362 { 0363 Q_ASSERT(d->currentStrokeId); 0364 0365 // we should to notify the stroke that it should do the updates itself. 0366 d->externalCancelUpdatesStorage->shouldIssueCancellationUpdates.ref(); 0367 d->view->image()->cancelStroke(d->currentStrokeId); 0368 0369 d->currentStrokeId.clear(); 0370 d->idleBarrierCookie.clear(); 0371 d->currentlyAppliedConfiguration.clear(); 0372 d->externalCancelUpdatesStorage.clear(); 0373 } 0374 0375 void KisFilterManager::cancelDialog() 0376 { 0377 cancelRunningStroke(); 0378 0379 d->filterDialog->reject(); 0380 } 0381 0382 bool KisFilterManager::isStrokeRunning() const 0383 { 0384 return d->currentStrokeId; 0385 } 0386 0387 bool KisFilterManager::isIdle() const 0388 { 0389 return !d->idleBarrierCookie; 0390 } 0391 0392 void KisFilterManager::setFilterAllSelectedFrames(bool filterAllSelectedFrames) 0393 { 0394 d->filterAllSelectedFrames = filterAllSelectedFrames; 0395 } 0396 0397 bool KisFilterManager::filterAllSelectedFrames() 0398 { 0399 return d->filterAllSelectedFrames; 0400 } 0401 0402 void KisFilterManager::slotStrokeEndRequested() 0403 { 0404 if (d->currentStrokeId && d->filterDialog) { 0405 d->filterDialog->accept(); 0406 } 0407 } 0408 0409 void KisFilterManager::slotStrokeCancelRequested() 0410 { 0411 if (d->currentStrokeId && d->filterDialog) { 0412 d->filterDialog->reject(); 0413 } 0414 } 0415 void KisFilterManager::filterDialogHasFinished(int) 0416 { 0417 // as far as we are concerned, filterDialog has been deleted 0418 d->filterDialog = nullptr; 0419 }