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 }