diff --git a/plugins/extensions/gmic/kis_gmic_plugin.cpp b/plugins/extensions/gmic/kis_gmic_plugin.cpp index 8e1888ad57..77f1372627 100644 --- a/plugins/extensions/gmic/kis_gmic_plugin.cpp +++ b/plugins/extensions/gmic/kis_gmic_plugin.cpp @@ -1,579 +1,580 @@ /* * This file is part of the KDE project * * Copyright (c) 2013-2014 Lukáš Tvrdý * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_gmic_plugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include #include "kis_gmic_parser.h" #include "Component.h" #include "kis_gmic_filter_model.h" #include "kis_gmic_widget.h" #include "kis_gmic_blacklister.h" #include "kis_input_output_mapper.h" #include "kis_gmic_simple_convertor.h" #include "kis_gmic_applicator.h" #include "kis_export_gmic_processing_visitor.h" #include "kis_gmic_command.h" #include "kis_import_gmic_processing_visitor.h" #include "kis_gmic_synchronize_layers_command.h" #include "KoColorSpaceConstants.h" #include "kis_gmic_progress_manager.h" #include #include #include "gmic.h" K_PLUGIN_FACTORY_WITH_JSON(KisGmicPluginFactory, "kritagmic.json", registerPlugin();) const QString STANDARD_GMIC_DEFINITION = "gmic_def.gmic"; KisGmicPlugin::KisGmicPlugin(QObject *parent, const QVariantList &) : KisViewPlugin(parent), m_gmicWidget(0), m_gmicApplicator(0), m_smallApplicator(0), m_progressManager(0), m_currentActivity(INIT), m_requestFinishAndClose(false), m_smallPreviewRequestCounter(0), m_onCanvasPreviewRequestCounter(0), m_filteringIsRunning(false) { KisAction *action = createAction("gmic"); + action->setActivationFlags(KisAction::ACTIVE_DEVICE); connect(action, SIGNAL(triggered()), this, SLOT(slotShowGmicDialog())); m_blacklistPath = KoResourcePaths::findResource("gmic_definitions", STANDARD_GMIC_DEFINITION + ".blacklist"); dumpCompiletimeFeatures(); } KisGmicPlugin::~KisGmicPlugin() { delete m_gmicWidget; } void KisGmicPlugin::dumpCompiletimeFeatures() { dbgPlugins << ""; #ifdef gmic_is_parallel dbgPlugins << "PTHREADS ON"; #endif #ifdef cimg_use_fftw3 dbgPlugins << "FFTW3 ON"; #endif #ifdef cimg_use_png dbgPlugins << "PNG ON"; #endif #ifdef cimg_use_zlib dbgPlugins << "ZLIB ON"; #endif #ifdef cimg_use_curl dbgPlugins << "CURL ON"; #endif #ifdef cimg_display #if cimg_display == 1 dbgPlugins << "Display:X11"; #elif cimg_display == 2 dbgPlugins << "Display:GDI"; #elif cimg_display == 3 dbgPlugins << "Display:NONE"; #endif #endif dbgPlugins << ""; } void KisGmicPlugin::setupDefinitionPaths() { m_definitionFilePaths = KoResourcePaths::findAllResources("gmic_definitions", "*.gmic"); QMutableStringListIterator it(m_definitionFilePaths); // remove all instances of gmic_def.gmic and updateXXXX.gmic, they cause problems when merged/mixed QRegExp rx("update\\d\\d\\d\\d.gmic"); while (it.hasNext()) { QFileInfo fi(it.next()); if (fi.fileName() == STANDARD_GMIC_DEFINITION) { it.remove(); } else if ( rx.exactMatch( fi.fileName() ) ) { it.remove(); } } // if we don't have updateXXXX.gmic for current version, prepend standard gmic_def.gmic int gmicVersion = gmic_version; QString updateFileName = "update" + QString::number(gmicVersion) + ".gmic"; QString updatedGmicDefinitionFilePath = KoResourcePaths::findResource("gmic_definitions", updateFileName); if (updatedGmicDefinitionFilePath.isEmpty()) { QString standardGmicDefinitionFilePath = KoResourcePaths::findResource("gmic_definitions", STANDARD_GMIC_DEFINITION); m_definitionFilePaths.prepend(standardGmicDefinitionFilePath); } else { m_definitionFilePaths.prepend(updatedGmicDefinitionFilePath); } Q_FOREACH (const QString item, m_definitionFilePaths) { dbgPlugins << "registered gmic file: " << item; } } void KisGmicPlugin::slotShowGmicDialog() { if (m_gmicWidget) { // restart here? slotCloseGmicDialog(); } // init everything here KisImageWSP image = m_view->image(); if (!image) return; KisLayerSP layer = m_view->activeLayer(); if (!layer) return; m_progressManager = new KisGmicProgressManager(m_view); connect(m_progressManager, SIGNAL(sigProgress()), this, SLOT(slotUpdateProgress())); m_gmicApplicator = new KisGmicApplicator(); connect(m_gmicApplicator, SIGNAL(gmicFinished(bool, int, QString)), this, SLOT(slotGmicFinished(bool, int, QString))); m_smallApplicator = new KisGmicSmallApplicator; connect(m_smallApplicator , SIGNAL(gmicFinished(bool,int,QString)), this, SLOT(slotGmicFinished(bool,int,QString))); connect(m_smallApplicator , SIGNAL(previewFinished(bool)), this, SLOT(slotViewportPreviewFinished(bool))); setupDefinitionPaths(); parseGmicCommandDefinitions(m_definitionFilePaths); KisGmicParser parser(m_definitionFilePaths); Component * root = parser.createFilterTree(); KisGmicFilterModel * model = new KisGmicFilterModel(root); // filter mode takes ownership of root KisGmicBlacklister * blacklister = new KisGmicBlacklister(m_blacklistPath); model->setBlacklister(blacklister); KisConfig cfg; QString gmicUpdatesUrl = cfg.readEntry("gmic_updates_url", "http://www.gmic.eu/"); QString updateUrl = gmicUpdatesUrl + QString("update") + QString::number(gmic_version) + ".gmic"; m_gmicWidget = new KisGmicWidget(model, updateUrl); // preview filter connect(m_gmicWidget, SIGNAL(sigPreviewFilterCommand(KisGmicFilterSetting*)),this, SLOT(slotPreviewGmicCommand(KisGmicFilterSetting*))); // finish filter connect(m_gmicWidget, SIGNAL(sigAcceptOnCanvasPreview()), this, SLOT(slotAcceptOnCanvasPreview())); // cancel filter connect(m_gmicWidget, SIGNAL(sigCancelOnCanvasPreview()), this, SLOT(slotCancelOnCanvasPreview())); // cancel connect(m_gmicWidget, SIGNAL(sigClose()),this, SLOT(slotCloseGmicDialog())); // filter current image with current settings connect(m_gmicWidget, SIGNAL(sigFilterCurrentImage(KisGmicFilterSetting*)), this, SLOT(slotFilterCurrentImage(KisGmicFilterSetting*))); // show active layer in preview viewport connect(m_gmicWidget, SIGNAL(sigPreviewActiveLayer()), this, SLOT(slotPreviewActiveLayer())); connect(m_gmicWidget, SIGNAL(sigRequestFinishAndClose()), this, SLOT(slotRequestFinishAndClose())); QString version = QString("%0.%1.%2.%3").arg(gmic_version/1000).arg((gmic_version/100)%10).arg((gmic_version/10)%10).arg(gmic_version%10); QString pluginName = i18n("G'MIC for Krita"); m_gmicWidget->setWindowTitle(QString("%0 %1").arg(pluginName).arg(version)); m_gmicWidget->show(); slotPreviewActiveLayer(); } void KisGmicPlugin::slotCloseGmicDialog() { dbgPlugins << "progress manager: " << m_progressManager; - // reset state + // reset state { m_gmicWidget = 0; if (m_gmicApplicator) { m_gmicApplicator->cancel(); } delete m_progressManager; m_progressManager = 0; delete m_gmicApplicator; m_gmicApplicator = 0; dbgPlugins << "Deleting " << m_smallApplicator; delete m_smallApplicator; m_smallApplicator = 0; setActivity(INIT); m_requestFinishAndClose = false; m_smallPreviewRequestCounter = 0; m_onCanvasPreviewRequestCounter = 0; m_filteringIsRunning = false; m_gmicCustomCommands.clear(); } } void KisGmicPlugin::slotPreviewGmicCommand(KisGmicFilterSetting* setting) { // Use '_none_' as a special command or preview_command to tell the plug-in that the entry requires no G'MIC call. if (setting->previewGmicCommand().startsWith("-_none_ ")) { return; } dbgPlugins << "Preview Request, preview size: " << setting->previewSize(); KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(setting->inputLayerMode()); if (!checkSettingsValidity(layers, setting)) { dbgPlugins << "Failed, some feature not implemented"; return; } if (setting->previewSize() != ON_CANVAS) { renderViewportPreview(layers, setting); } else { startOnCanvasPreview(layers, setting, PREVIEWING); } } void KisGmicPlugin::slotPreviewActiveLayer() { slotPreviewSmallWindow(m_view->activeNode()->paintDevice()); } void KisGmicPlugin::slotPreviewSmallWindow(KisPaintDeviceSP device) { if (!device) return; QRect deviceRect = device->exactBounds(); qreal aspectRatio = (qreal)deviceRect.width()/deviceRect.height(); int dstWidth = m_gmicWidget->previewWidget()->size().width(); int dstHeight = dstWidth / aspectRatio; QImage previewImage = device->createThumbnail(dstWidth, dstHeight, deviceRect); m_gmicWidget->previewWidget()->setImage(previewImage); } void KisGmicPlugin::slotAcceptOnCanvasPreview() { m_gmicApplicator->finish(); } void KisGmicPlugin::slotCancelOnCanvasPreview() { m_gmicApplicator->cancel(); } void KisGmicPlugin::slotFilterCurrentImage(KisGmicFilterSetting* setting) { if (setting->gmicCommand().startsWith("-_none_ ")) { dbgPlugins << "_none_ command does not involve g'mic call"; return; } dbgPlugins << "Filtering image on canvas!"; KisInputOutputMapper mapper(m_view->image(), m_view->activeNode()); KisNodeListSP layers = mapper.inputNodes(setting->inputLayerMode()); if (checkSettingsValidity(layers, setting)) { startOnCanvasPreview(layers, setting, FILTERING); // wait, so that next request to strokes for on-canvas preview is possible // m_view->image()->waitForDone(); } else { dbgPlugins << "Failed to filter image, some feature not implemented"; } } void KisGmicPlugin::parseGmicCommandDefinitions(const QStringList& gmicDefinitionFilePaths) { Q_FOREACH (const QString filePath, gmicDefinitionFilePaths) { QByteArray gmicCommands = KisGmicParser::extractGmicCommandsOnly(filePath); m_gmicCustomCommands.append(gmicCommands); } } void KisGmicPlugin::renderViewportPreview(KisNodeListSP layers, KisGmicFilterSetting* setting) { if (m_filteringIsRunning) { dbgPlugins << "Filtering is running..."; waitForFilterFinish(); } QSize previewSize; if (m_gmicWidget && m_gmicWidget->previewWidget()) { previewSize = m_gmicWidget->previewWidget()->size(); } else { return; } m_smallPreviewRequestCounter++; dbgPlugins << "Current number of small preview requests: " << m_smallPreviewRequestCounter; setActivity(SMALL_PREVIEW); m_view->image()->lock(); QRect canvasRect = m_view->image()->bounds(); m_view->image()->unlock(); dbgPlugins << "Unlocked image..."; m_smallApplicator->render(canvasRect,previewSize, layers, setting, m_gmicCustomCommands); startProgressReporting(); } void KisGmicPlugin::startOnCanvasPreview(KisNodeListSP layers, KisGmicFilterSetting* setting, Activity activity) { m_onCanvasPreviewRequestCounter++; m_filteringIsRunning = true; setActivity(activity); KUndo2MagicString actionName = kundo2_i18n("Gmic filter"); KisNodeSP rootNode = m_view->image()->root(); m_gmicApplicator->setProperties(m_view->image(), rootNode, actionName, layers, setting->gmicCommand(), m_gmicCustomCommands); m_gmicApplicator->preview(); // Note: do not call KisImage::waitForDone(): strokes are not finished or cancelled, it's just preview! // waitForDone would cause infinite hang dbgPlugins << valueToQString(m_currentActivity); startProgressReporting(); } void KisGmicPlugin::startProgressReporting() { if (m_progressManager->inProgress()) { m_progressManager->finishProgress(); } m_progressManager->initProgress(); } bool KisGmicPlugin::checkSettingsValidity(KisNodeListSP layers, const KisGmicFilterSetting* setting) { if (setting->isBlacklisted()) { QMessageBox::warning(m_gmicWidget, i18nc("@title:window", "Krita"), i18n("Sorry, this filter is crashing Krita and is turned off.")); return false; } if (setting->outputMode() != IN_PLACE) { QMessageBox::warning(m_gmicWidget, i18nc("@title:window", "Krita"), i18n("Sorry, this output mode is not implemented")); return false; } if (layers->isEmpty()) { QMessageBox::warning(m_gmicWidget, i18nc("@title:window", "Krita"), i18n("Sorry, this input mode is not implemented")); return false; } return true; } void KisGmicPlugin::slotUpdateProgress() { float progress = 0; if (m_currentActivity == SMALL_PREVIEW) { if (!m_smallApplicator) { dbgPlugins << "WARNING: small applicator already deleted!!!"; return; } progress = m_smallApplicator->progress(); } else { if (!m_gmicApplicator) { dbgPlugins << "WARNING: gmic applicator already deleted!!!"; return; } progress = m_gmicApplicator->getProgress(); } m_progressManager->updateProgress(progress); } void KisGmicPlugin::slotGmicFinished(bool successfully, int miliseconds, const QString& msg) { dbgPlugins << "GMIC_FINISHED : activity " << valueToQString(m_currentActivity); dbgPlugins << ppVar(m_smallPreviewRequestCounter) << " " << ppVar(m_onCanvasPreviewRequestCounter); m_progressManager->finishProgress(); if (successfully) { gmicFinished(miliseconds); } else { gmicFailed(msg); } if (m_currentActivity == FILTERING || m_currentActivity == PREVIEWING) { m_filteringIsRunning = false; emit filteringFinished(); } if (m_requestFinishAndClose) { slotRequestFinishAndClose(); } } void KisGmicPlugin::gmicFinished(int miliseconds) { if (m_currentActivity == FILTERING) { slotAcceptOnCanvasPreview(); } m_gmicWidget->setWindowTitle(QString("Filtering took ") + QString::number(miliseconds * 0.001) + QString(" seconds")); } void KisGmicPlugin::gmicFailed(const QString& msg) { dbgPlugins << "G'Mic for activity " << valueToQString(m_currentActivity) << "failed with message: " << msg; if ((m_currentActivity == PREVIEWING) || (m_currentActivity == FILTERING)) { slotCancelOnCanvasPreview(); } QMessageBox::warning(m_gmicWidget, i18nc("@title:window", "Krita"), i18n("Sorry! G'Mic failed, reason:") + msg); } void KisGmicPlugin::slotRequestFinishAndClose() { if (m_progressManager->inProgress()) { m_requestFinishAndClose = true; } else { m_gmicWidget->close(); } } void KisGmicPlugin::slotViewportPreviewFinished(bool successfully) { if (m_smallApplicator) { if (successfully) { slotPreviewSmallWindow(m_smallApplicator->preview()); } else { dbgPlugins << "Viewport preview generation failed!"; } setActivity(INIT); } } void KisGmicPlugin::setActivity(KisGmicPlugin::Activity activity) { dbgPlugins << "Changing activity from " << valueToQString(m_currentActivity) << " to " << valueToQString(activity); m_currentActivity = activity; } void KisGmicPlugin::waitForFilterFinish() { dbgPlugins << "Starting local event loop!"; QEventLoop localEventLoop; connect(this, SIGNAL(filteringFinished()), &localEventLoop, SLOT(quit())); localEventLoop.exec(); dbgPlugins << "Done"; } QLatin1String KisGmicPlugin::valueToQString(KisGmicPlugin::Activity activity) { const QMetaObject & mo = KisGmicPlugin::staticMetaObject; QMetaEnum me = mo.enumerator(mo.indexOfEnumerator("Activity")); return QLatin1String(me.valueToKey(activity)); } #include "kis_gmic_plugin.moc"