diff --git a/benchmarks/KisAnimationRenderingBenchmark.cpp b/benchmarks/KisAnimationRenderingBenchmark.cpp index 24ab8d8ce7..ac6ccd575f 100644 --- a/benchmarks/KisAnimationRenderingBenchmark.cpp +++ b/benchmarks/KisAnimationRenderingBenchmark.cpp @@ -1,112 +1,112 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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 "KisAnimationRenderingBenchmark.h" #include #include #include "kis_time_range.h" #include "dialogs/KisAsyncAnimationFramesSaveDialog.h" #include "kis_image_animation_interface.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_image_config.h" namespace { void removeTempFiles(const QString &filesMask) { QFileInfo info(filesMask); QDir dir(info.absolutePath()); QStringList filesList = dir.entryList({ info.fileName() }); if (!filesList.isEmpty()) { Q_FOREACH (const QString &file, filesList) { if (!dir.remove(file)) { QFAIL("Couldn't remove the old testing file!"); } } } } void runRenderingTest(KisImageSP image, int numCores, int numClones) { { KisImageConfig cfg(false); cfg.setMaxNumberOfThreads(numCores); cfg.setFrameRenderingClones(numClones); } const KisTimeRange range = image->animationInterface()->fullClipRange(); - KisAsyncAnimationFramesSaveDialog dlg(image, range, "temp_frames.png", 0, 0); + KisAsyncAnimationFramesSaveDialog dlg(image, range, "temp_frames.png", 0, false, 0); dlg.setBatchMode(true); // repeat rendering twice! for (int i = 0; i < 1; i++) { removeTempFiles(dlg.savedFilesMaskWildcard()); KisAsyncAnimationFramesSaveDialog::Result result = dlg.regenerateRange(0); QCOMPARE(result, KisAsyncAnimationFramesSaveDialog::RenderComplete); removeTempFiles(dlg.savedFilesMaskWildcard()); } } } void KisAnimationRenderingBenchmark::testCacheRendering() { const QString fileName = TestUtil::fetchDataFileLazy("miloor_turntable_002.kra", true); QVERIFY(QFileInfo(fileName).exists()); QScopedPointer doc(KisPart::instance()->createDocument()); bool loadingResult = doc->loadNativeFormat(fileName); QVERIFY(loadingResult); doc->image()->barrierLock(); doc->image()->unlock(); for (int numCores = 1; numCores <= QThread::idealThreadCount(); numCores++) { QElapsedTimer timer; timer.start(); const int numClones = qMax(1, numCores / 2); runRenderingTest(doc->image(), numCores, numClones); qDebug() << "Cores:" << numCores << "Clones:" << numClones << "Time:" << timer.elapsed(); } for (int numCores = 1; numCores <= QThread::idealThreadCount(); numCores++) { QElapsedTimer timer; timer.start(); const int numClones = numCores; runRenderingTest(doc->image(), numCores, numClones); qDebug() << "Cores:" << numCores << "Clones:" << numClones << "Time:" << timer.elapsed(); } } QTEST_MAIN(KisAnimationRenderingBenchmark) diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 72e4a63890..daba3f8856 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,990 +1,991 @@ /* * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2012 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #ifdef Q_OS_MACOS #include "osx.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoConfig.h" #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" #include "kis_spin_box_unit_manager.h" #include "kis_document_aware_spin_box_unit_manager.h" #include "KisViewManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "widgets/KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" #include #include #include "kis_file_layer.h" #include "kis_group_layer.h" #include "kis_node_commands_adapter.h" #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplication::Private { public: Private() {} QPointer splashScreen; KisAutoSaveRecoveryDialog *autosaveDialog {0}; QPointer mainWindow; // The first mainwindow we create on startup bool batchRun {false}; QVector earlyRemoteArguments; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash, int fileCount) : m_splash(splash) , m_fileCount(fileCount) { } ~ResetStarting() { if (m_splash) { m_splash->hide(); } } QPointer m_splash; int m_fileCount; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new Private) { #ifdef Q_OS_MACOS setMouseCoalescingEnabled(false); #endif QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("krita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "breeze" << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void KisApplication::initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void KisApplication::addResourceTypes() { // qDebug() << "addResourceTypes();"; // All Krita's resource types KoResourcePaths::addResourceType("markers", "data", "/styles/"); KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType(ResourceType::Brushes, "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType(ResourceType::PaintOpPresets, "data", "/paintoppresets/"); KoResourcePaths::addResourceType(ResourceType::Workspaces, "data", "/workspaces/"); KoResourcePaths::addResourceType(ResourceType::WindowLayouts, "data", "/windowlayouts/"); KoResourcePaths::addResourceType(ResourceType::Sessions, "data", "/sessions/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType(ResourceType::Patterns, "data", "/patterns/", true); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/"); KoResourcePaths::addResourceType(ResourceType::Gradients, "data", "/gradients/", true); KoResourcePaths::addResourceType(ResourceType::Palettes, "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("kis_actions", "data", "/pykrita"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType(ResourceType::FilterEffects, "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); KoResourcePaths::addResourceType("pythonscripts", "data", "/pykrita"); KoResourcePaths::addResourceType(ResourceType::Symbols, "data", "/symbols"); KoResourcePaths::addResourceType("preset_icons", "data", "/preset_icons"); KoResourcePaths::addResourceType(ResourceType::GamutMasks, "data", "/gamutmasks/", true); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir(ResourceType::Gradients, "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Gradients, QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Patterns, "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Patterns, QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Brushes, "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir(ResourceType::Brushes, QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir(ResourceType::Palettes, "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir(ResourceType::Palettes, QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/brushes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); // between 4.2.x and 4.3.0 there was a change from 'taskset' to 'tasksets' // so to make older resource folders compatible with the new version, let's rename the folder // so no tasksets are lost. if (d.exists(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/")) { d.rename(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/", QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tasksets/"); } d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tasksets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/pykrita/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/symbols/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/color-schemes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/tool_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/preset_icons/emblem_icons/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gamutmasks/"); } bool KisApplication::registerResources() { KisResourceLoaderRegistry *reg = KisResourceLoaderRegistry::instance(); reg->add(new KisResourceLoader(ResourceType::PaintOpPresets, ResourceType::PaintOpPresets, i18n("Brush presets"), QStringList() << "application/x-krita-paintoppreset")); reg->add(new KisResourceLoader(ResourceSubType::GbrBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush")); reg->add(new KisResourceLoader(ResourceSubType::GihBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/x-gimp-brush-animated")); reg->add(new KisResourceLoader(ResourceSubType::SvgBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceSubType::PngBrushes, ResourceType::Brushes, i18n("Brush tips"), QStringList() << "image/png")); reg->add(new KisResourceLoader(ResourceSubType::SegmentedGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-gimp-gradient")); reg->add(new KisResourceLoader(ResourceSubType::StopGradients, ResourceType::Gradients, i18n("Gradients"), QStringList() << "application/x-karbon-gradient" << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::Palettes, ResourceType::Palettes, i18n("Palettes"), QStringList() << KisMimeDatabase::mimeTypeForSuffix("kpl") << KisMimeDatabase::mimeTypeForSuffix("gpl") << KisMimeDatabase::mimeTypeForSuffix("pal") << KisMimeDatabase::mimeTypeForSuffix("act") << KisMimeDatabase::mimeTypeForSuffix("aco") << KisMimeDatabase::mimeTypeForSuffix("css") << KisMimeDatabase::mimeTypeForSuffix("colors") << KisMimeDatabase::mimeTypeForSuffix("xml") << KisMimeDatabase::mimeTypeForSuffix("sbz"))); QList src = QImageReader::supportedMimeTypes(); QStringList allImageMimes; Q_FOREACH(const QByteArray ba, src) { if (QImageWriter::supportedMimeTypes().contains(ba)) { allImageMimes << QString::fromUtf8(ba); } } allImageMimes << KisMimeDatabase::mimeTypeForSuffix("pat"); reg->add(new KisResourceLoader(ResourceType::Patterns, ResourceType::Patterns, i18n("Patterns"), allImageMimes)); reg->add(new KisResourceLoader(ResourceType::Workspaces, ResourceType::Workspaces, i18n("Workspaces"), QStringList() << "application/x-krita-workspace")); reg->add(new KisResourceLoader(ResourceType::Symbols, ResourceType::Symbols, i18n("SVG symbol libraries"), QStringList() << "image/svg+xml")); reg->add(new KisResourceLoader(ResourceType::WindowLayouts, ResourceType::WindowLayouts, i18n("Window layouts"), QStringList() << "application/x-krita-windowlayout")); reg->add(new KisResourceLoader(ResourceType::Sessions, ResourceType::Sessions, i18n("Sessions"), QStringList() << "application/x-krita-session")); reg->add(new KisResourceLoader(ResourceType::GamutMasks, ResourceType::GamutMasks, i18n("Gamut masks"), QStringList() << "application/x-krita-gamutmasks")); reg->add(new KisResourceLoader(ResourceType::LayerStyles, ResourceType::LayerStyles, ResourceType::LayerStyles, QStringList() << "application/x-photoshop-style")); if (!KisResourceCacheDb::initialize(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), i18n("%1\n\nKrita will quit now.", KisResourceCacheDb::lastError())); //return false; } KisResourceLocator::LocatorError r = KisResourceLocator::instance()->initialize(KoResourcePaths::getApplicationRoot() + "/share/krita"); connect(KisResourceLocator::instance(), SIGNAL(progressMessage(const QString&)), this, SLOT(setSplashScreenLoadingText(const QString&))); if (r != KisResourceLocator::LocatorError::Ok ) { QMessageBox::critical(0, i18nc("@title:window", "Krita: Fatal error"), KisResourceLocator::instance()->errorMessages().join('\n') + i18n("\n\nKrita will quit now.")); //return false; } return true; } void KisApplication::loadPlugins() { // qDebug() << "loadPlugins();"; KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KoColorSpaceRegistry::instance(); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoToolRegistry::instance(); KoDockRegistry::instance(); } void KisApplication::loadGuiPlugins() { // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); processEvents(); // qDebug() << "loading exiv2"; KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { KisConfig cfg(false); #if defined(Q_OS_WIN) #ifdef ENV32BIT if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif QString opengl = cfg.canvasState(); if (opengl == "OPENGL_NOT_TRIED" ) { cfg.setCanvasState("TRY_OPENGL"); } else if (opengl != "OPENGL_SUCCESS" && opengl != "TRY_OPENGL") { cfg.setCanvasState("OPENGL_FAILED"); } setSplashScreenLoadingText(i18n("Initializing Globals")); processEvents(); initializeGlobals(args); const bool doNewImage = args.doNewImage(); const bool doTemplate = args.doTemplate(); const bool exportAs = args.exportAs(); const bool exportSequence = args.exportSequence(); const QString exportFileName = args.exportFileName(); d->batchRun = (exportAs || exportSequence || !exportFileName.isEmpty()); const bool needsMainWindow = (!exportAs && !exportSequence); // only show the mainWindow when no command-line mode option is passed bool showmainWindow = (!exportAs && !exportSequence); // would be !batchRun; const bool showSplashScreen = !d->batchRun && qEnvironmentVariableIsEmpty("NOSPLASH"); if (showSplashScreen && d->splashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen, args.filenames().count()); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); processEvents(); addResourceTypes(); // Load the plugins loadPlugins(); // Load all resources if (!registerResources()) { return false; } // Load the gui plugins loadGuiPlugins(); KisPart *kisPart = KisPart::instance(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); processEvents(); bool sessionNeeded = true; auto sessionMode = cfg.sessionOnStartup(); if (!args.session().isEmpty()) { sessionNeeded = !kisPart->restoreSession(args.session()); } else if (sessionMode == KisConfig::SOS_ShowSessionManager) { showmainWindow = false; sessionNeeded = false; kisPart->showSessionManager(); } else if (sessionMode == KisConfig::SOS_PreviousSession) { KConfigGroup sessionCfg = KSharedConfig::openConfig()->group("session"); const QString &sessionName = sessionCfg.readEntry("previousSession"); sessionNeeded = !kisPart->restoreSession(sessionName); } if (sessionNeeded) { kisPart->startBlankSession(); } if (!args.windowLayout().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->windowLayoutServer(); KisWindowLayoutResourceSP windowLayout = rserver->resourceByName(args.windowLayout()); if (windowLayout) { windowLayout->applyLayout(); } } if (showmainWindow) { d->mainWindow = kisPart->currentMainwindow(); if (!args.workspace().isEmpty()) { KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResourceSP workspace = rserver->resourceByName(args.workspace()); if (workspace) { d->mainWindow->restoreWorkspace(workspace->resourceId()); } } if (args.canvasOnly()) { d->mainWindow->viewManager()->switchCanvasOnly(true); } if (args.fullScreen()) { d->mainWindow->showFullScreen(); } } else { d->mainWindow = kisPart->createMainWindow(); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test) if (!d->batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(QString()); // done loading, so clear out label processEvents(); //configure the unit manager KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. //the new syntax slot syntax allow to connect to a non q_object static method. // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.createDocumentFromArguments(); if (doc) { kisPart->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (d->batchRun) { continue; } if (createNewDocFromTemplate(fileName, d->mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName, false); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return false; } KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); bool result = doc->openUrl(QUrl::fromLocalFile(fileName)); if (!result) { errKrita << "Could not load " << fileName << ":" << doc->errorMessage(); QTimer::singleShot(0, this, SLOT(quit())); return false; } if (exportFileName.isEmpty()) { errKrita << "Export destination is not specified for" << fileName << "Please specify export destination with --export-filename option"; QTimer::singleShot(0, this, SLOT(quit())); return false; } qApp->processEvents(); // For vector layers to be updated doc->setFileBatchMode(true); if (!doc->exportDocumentSync(QUrl::fromLocalFile(exportFileName), outputMimetype.toLatin1())) { errKrita << "Could not export " << fileName << "to" << exportFileName << ":" << doc->errorMessage(); } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (exportSequence) { KisDocument *doc = kisPart->createDocument(); doc->setFileBatchMode(d->batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated if (!doc->image()->animationInterface()->hasAnimation()) { errKrita << "This file has no animation." << endl; QTimer::singleShot(0, this, SLOT(quit())); return false; } doc->setFileBatchMode(true); int sequenceStart = 0; KisAsyncAnimationFramesSaveDialog exporter(doc->image(), doc->image()->animationInterface()->fullClipRange(), exportFileName, sequenceStart, + false, 0); exporter.setBatchMode(d->batchRun); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(0); if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { errKrita << i18n("Failed to render animation frames!") << endl; } QTimer::singleShot(0, this, SLOT(quit())); return true; } else if (d->mainWindow) { if (fileName.endsWith(".bundle")) { d->mainWindow->installBundle(fileName); } else { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (d->mainWindow->openDocument(QUrl::fromLocalFile(fileName), flags)) { // Normal case, success numberOfOpenDocuments++; } } } } } } //add an image as file-layer if (!args.fileLayer().isEmpty()){ if (d->mainWindow->viewManager()->image()){ KisFileLayer *fileLayer = new KisFileLayer(d->mainWindow->viewManager()->image(), "", args.fileLayer(), KisFileLayer::None, d->mainWindow->viewManager()->image()->nextLayerName(), OPACITY_OPAQUE_U8); QFileInfo fi(fileLayer->path()); if (fi.exists()){ KisNodeCommandsAdapter adapter(d->mainWindow->viewManager()); adapter.addNode(fileLayer, d->mainWindow->viewManager()->activeNode()->parent(), d->mainWindow->viewManager()->activeNode()); } else{ QMessageBox::warning(nullptr, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add %1 as a file layer: the file does not exist.", fileLayer->path())); } } else if (this->isRunning()){ QMessageBox::warning(nullptr, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add the file layer: no document is open.\n\n" "You can create a new document using the --new-image option, or you can open an existing file.\n\n" "If you instead want to add the file layer to a document in an already running instance of Krita, check the \"Allow only one instance of Krita\" checkbox in the settings (Settings -> General -> Window).")); } else { QMessageBox::warning(nullptr, i18nc("@title:window", "Krita: Warning"), i18n("Cannot add the file layer: no document is open.\n" "You can either create a new file using the --new-image option, or you can open an existing file.")); } } // fixes BUG:369308 - Krita crashing on splash screen when loading. // trying to open a file before Krita has loaded can cause it to hang and crash if (d->splashScreen) { d->splashScreen->displayLinks(true); d->splashScreen->displayRecentFiles(true); } Q_FOREACH(const QByteArray &message, d->earlyRemoteArguments) { executeRemoteArguments(message, d->mainWindow); } KisUsageLogger::writeSysInfo(KisUsageLogger::screenInformation()); // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { KisResourceCacheDb::deleteTemporaryResources(); } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(const QString &textToLoad) { if (d->splashScreen) { d->splashScreen->setLoadingText(textToLoad); d->splashScreen->repaint(); } } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::executeRemoteArguments(QByteArray message, KisMainWindow *mainWindow) { KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const bool doNewImage = args.doNewImage(); const int argsCount = args.filenames().count(); bool documentCreated = false; // Create a new image, if needed if (doNewImage) { KisDocument *doc = args.createDocumentFromArguments(); if (doc) { KisPart::instance()->addDocument(doc); d->mainWindow->addViewAndNotifyLoadingCompleted(doc); } } if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { documentCreated |= createNewDocFromTemplate(filename, mainWindow); } else if (QFile(filename).exists()) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; documentCreated |= mainWindow->openDocument(QUrl::fromLocalFile(filename), flags); } } } //add an image as file-layer if called in another process and singleApplication is enabled if (!args.fileLayer().isEmpty()){ if (argsCount > 0 && !documentCreated){ //arg was passed but document was not created so don't add the file layer. QMessageBox::warning(mainWindow, i18nc("@title:window", "Krita:Warning"), i18n("Couldn't open file %1",args.filenames().at(argsCount - 1))); } else if (mainWindow->viewManager()->image()){ KisFileLayer *fileLayer = new KisFileLayer(mainWindow->viewManager()->image(), "", args.fileLayer(), KisFileLayer::None, mainWindow->viewManager()->image()->nextLayerName(), OPACITY_OPAQUE_U8); QFileInfo fi(fileLayer->path()); if (fi.exists()){ KisNodeCommandsAdapter adapter(d->mainWindow->viewManager()); adapter.addNode(fileLayer, d->mainWindow->viewManager()->activeNode()->parent(), d->mainWindow->viewManager()->activeNode()); } else{ QMessageBox::warning(mainWindow, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add %1 as a file layer: the file does not exist.", fileLayer->path())); } } else { QMessageBox::warning(mainWindow, i18nc("@title:window", "Krita:Warning"), i18n("Cannot add the file layer: no document is open.")); } } } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw && KisPart::instance()->mainWindows().size() > 0) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { d->earlyRemoteArguments << message; return; } executeRemoteArguments(message, mw); } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; mainWindow->openDocument(QUrl::fromLocalFile(url), flags); } } void KisApplication::checkAutosaveFiles() { if (d->batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! // Hidden autosave files QStringList filters = QStringList() << QString(".krita-*-*-autosave.kra"); // all autosave files for our application QStringList autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Visible autosave files filters = QStringList() << QString("krita-*-*-autosave.kra"); autosaveFiles += dir.entryList(filters, QDir::Files); // Allow the user to make their selection if (autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } d->autosaveDialog = new KisAutoSaveRecoveryDialog(autosaveFiles, activeWindow()); QDialog::DialogCode result = (QDialog::DialogCode) d->autosaveDialog->exec(); if (result == QDialog::Accepted) { QStringList filesToRecover = d->autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { KisUsageLogger::log(QString("Removing autosave file %1").arg(dir.absolutePath() + "/" + autosaveFile)); QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } autosaveFiles = filesToRecover; } else { autosaveFiles.clear(); } if (autosaveFiles.size() > 0) { QList autosaveUrls; Q_FOREACH (const QString &autoSaveFile, autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } if (d->mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisMainWindow::OpenFlags flags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; d->mainWindow->openDocument(url, flags | KisMainWindow::RecoveryFile); } } } // cleanup delete d->autosaveDialog; d->autosaveDialog = nullptr; } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); if (templateURL.scheme().isEmpty()) { templateURL.setScheme("file"); } KisMainWindow::OpenFlags batchFlags = d->batchRun ? KisMainWindow::BatchMode : KisMainWindow::None; if (mainWindow->openDocument(templateURL, KisMainWindow::Import | batchFlags)) { dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp index 9fe9df4760..15dda04570 100644 --- a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp +++ b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.cpp @@ -1,193 +1,214 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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 "KisAsyncAnimationFramesSaveDialog.h" #include #include #include #include "kis_properties_configuration.h" #include "KisMimeDatabase.h" #include #include #include struct KisAsyncAnimationFramesSaveDialog::Private { Private(KisImageSP _image, const KisTimeRange &_range, const QString &baseFilename, int _sequenceNumberingOffset, + bool _onlyUniqueFrames, KisPropertiesConfigurationSP _exportConfiguration) : originalImage(_image), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), - exportConfiguration(_exportConfiguration) + exportConfiguration(_exportConfiguration), + onlyUniqueFrames(_onlyUniqueFrames) { int baseLength = baseFilename.lastIndexOf("."); if (baseLength > -1) { filenamePrefix = baseFilename.left(baseLength); filenameSuffix = baseFilename.right(baseFilename.length() - baseLength); } else { filenamePrefix = baseFilename; } outputMimeType = KisMimeDatabase::mimeTypeForFile(baseFilename, false).toLatin1(); } KisImageSP originalImage; KisTimeRange range; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; + bool onlyUniqueFrames; int sequenceNumberingOffset; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSaveDialog::KisAsyncAnimationFramesSaveDialog(KisImageSP originalImage, const KisTimeRange &range, const QString &baseFilename, int sequenceNumberingOffset, + bool onlyUniqueFrames, KisPropertiesConfigurationSP exportConfiguration) : KisAsyncAnimationRenderDialogBase(i18n("Saving frames..."), originalImage, 0), - m_d(new Private(originalImage, range, baseFilename, sequenceNumberingOffset, exportConfiguration)) + m_d(new Private(originalImage, range, baseFilename, sequenceNumberingOffset, onlyUniqueFrames, exportConfiguration)) { } KisAsyncAnimationFramesSaveDialog::~KisAsyncAnimationFramesSaveDialog() { } KisAsyncAnimationRenderDialogBase::Result KisAsyncAnimationFramesSaveDialog::regenerateRange(KisViewManager *viewManager) { QFileInfo info(savedFilesMaskWildcard()); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); QStringList filesList = dir.entryList({ info.fileName() }); if (!filesList.isEmpty()) { if (batchMode()) { return RenderFailed; } QStringList filesWithinRange; const int numberOfDigits = 4; Q_FOREACH(const QString &filename, filesList) { // Counting based on suffix, since prefix may include the path while filename doesn't int digitsPosition = filename.length() - m_d->filenameSuffix.length() - numberOfDigits; int fileNumber = filename.midRef(digitsPosition, numberOfDigits).toInt(); auto frameNumber = fileNumber - m_d->sequenceNumberingOffset; if (m_d->range.contains(frameNumber)) { filesWithinRange.append(filename); } } filesList = filesWithinRange; QStringList truncatedList = filesList; while (truncatedList.size() > 3) { truncatedList.takeLast(); } QString exampleFiles = truncatedList.join(", "); if (truncatedList.size() != filesList.size()) { exampleFiles += QString(", ..."); } QMessageBox::StandardButton result = QMessageBox::warning(0, i18n("Delete old frames?"), i18n("Frames with the same naming " "scheme exist in the destination " "directory. They are going to be " "deleted, continue?\n\n" "Directory: %1\n" "Files: %2", info.absolutePath(), exampleFiles), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) { Q_FOREACH (const QString &file, filesList) { if (!dir.remove(file)) { QMessageBox::critical(0, i18n("Failed to delete"), i18n("Failed to delete an old frame file:\n\n" "%1\n\n" "Rendering cancelled.", dir.absoluteFilePath(file))); return RenderFailed; } } } else { return RenderCancelled; } } return KisAsyncAnimationRenderDialogBase::regenerateRange(viewManager); } QList KisAsyncAnimationFramesSaveDialog::calcDirtyFrames() const { - // TODO: optimize! QList result; - for (int i = m_d->range.start(); i <= m_d->range.end(); i++) { - result.append(i); + for (int frame = m_d->range.start(); frame <= m_d->range.end(); frame++) { + // Currently the 'both' option is not supported for only unique frames. When doing both, + // you need to have all frames available for encoding and only delete non-unique frames after + // the video is done rendering. This can be achieved, but shouldn't be done here. + if( m_d->onlyUniqueFrames ) { + KisTimeRange heldFrameTimeRange = KisTimeRange::calculateIdenticalFramesRecursive(m_d->originalImage->root(), frame); + ENTER_FUNCTION() << heldFrameTimeRange; + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(heldFrameTimeRange.isValid(), result); + + result.append(heldFrameTimeRange.start()); + + if (heldFrameTimeRange.isInfinite()) { + break; + } else { + frame = heldFrameTimeRange.end(); + } + + } else { + result.append(frame); + } } return result; } KisAsyncAnimationRendererBase *KisAsyncAnimationFramesSaveDialog::createRenderer(KisImageSP image) { return new KisAsyncAnimationFramesSavingRenderer(image, m_d->filenamePrefix, m_d->filenameSuffix, m_d->outputMimeType, m_d->range, m_d->sequenceNumberingOffset, m_d->exportConfiguration); } void KisAsyncAnimationFramesSaveDialog::initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) { Q_UNUSED(renderer); Q_UNUSED(image); Q_UNUSED(frame); } QString KisAsyncAnimationFramesSaveDialog::savedFilesMask() const { return m_d->filenamePrefix + "%04d" + m_d->filenameSuffix; } QString KisAsyncAnimationFramesSaveDialog::savedFilesMaskWildcard() const { return m_d->filenamePrefix + "????" + m_d->filenameSuffix; } diff --git a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h index cd5ba65832..dc7c1615ef 100644 --- a/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h +++ b/libs/ui/dialogs/KisAsyncAnimationFramesSaveDialog.h @@ -1,53 +1,54 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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. */ #ifndef KISASYNCANIMATIONFRAMESSAVEDIALOG_H #define KISASYNCANIMATIONFRAMESSAVEDIALOG_H #include "KisAsyncAnimationRenderDialogBase.h" #include "kis_types.h" class KRITAUI_EXPORT KisAsyncAnimationFramesSaveDialog : public KisAsyncAnimationRenderDialogBase { public: KisAsyncAnimationFramesSaveDialog(KisImageSP image, const KisTimeRange &range, const QString &baseFilename, int sequenceNumberingOffset, + bool onlyUniqueFrames, KisPropertiesConfigurationSP exportConfiguration); ~KisAsyncAnimationFramesSaveDialog(); Result regenerateRange(KisViewManager *viewManager) override; QString savedFilesMask() const; QString savedFilesMaskWildcard() const; protected: QList calcDirtyFrames() const override; KisAsyncAnimationRendererBase* createRenderer(KisImageSP image) override; void initializeRendererForFrame(KisAsyncAnimationRendererBase *renderer, KisImageSP image, int frame) override; private: struct Private; const QScopedPointer m_d; }; #endif // KISASYNCANIMATIONFRAMESSAVEDIALOG_H diff --git a/libs/ui/tests/kis_animation_exporter_test.cpp b/libs/ui/tests/kis_animation_exporter_test.cpp index c5dbe390c9..b48af75c47 100644 --- a/libs/ui/tests/kis_animation_exporter_test.cpp +++ b/libs/ui/tests/kis_animation_exporter_test.cpp @@ -1,101 +1,102 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_animation_exporter_test.h" #include "dialogs/KisAsyncAnimationFramesSaveDialog.h" #include #include #include "KisPart.h" #include "kis_image.h" #include "KisDocument.h" #include "kis_image_animation_interface.h" #include "KoColor.h" #include #include "kis_time_range.h" #include "kis_keyframe_channel.h" #include void KisAnimationExporterTest::testAnimationExport() { KisDocument *document = KisPart::instance()->createDocument(); QRect rect(0,0,512,512); QRect fillRect(10,0,502,512); TestUtil::MaskParent p(rect); document->setCurrentImage(p.image); const KoColorSpace *cs = p.image->colorSpace(); KUndo2Command parentCommand; p.layer->enableAnimation(); KisKeyframeChannel *rasterChannel = p.layer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); rasterChannel->addKeyframe(1, &parentCommand); rasterChannel->addKeyframe(2, &parentCommand); p.image->animationInterface()->setFullClipRange(KisTimeRange::fromTime(0, 2)); KisPaintDeviceSP dev = p.layer->paintDevice(); dev->fill(fillRect, KoColor(Qt::red, cs)); QImage frame0 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(1); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::green, cs)); QImage frame1 = dev->convertToQImage(0, rect); p.image->animationInterface()->switchCurrentTimeAsync(2); p.image->waitForDone(); dev->fill(fillRect, KoColor(Qt::blue, cs)); QImage frame2 = dev->convertToQImage(0, rect); KisAsyncAnimationFramesSaveDialog exporter(document->image(), KisTimeRange::fromTime(0,2), "export-test.png", 0, + false, 0); exporter.setBatchMode(true); exporter.regenerateRange(0); QTest::qWait(1000); QImage exported; QPoint errpoint; exported.load("export-test0000.png"); qDebug() << exported.size() << frame0.size(); if (!TestUtil::compareQImages(errpoint, exported, frame0)) { QFAIL(QString("Failed to export identical frame0, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } exported.load("export-test0001.png"); if (!TestUtil::compareQImages(errpoint, exported, frame1)) { QFAIL(QString("Failed to export identical frame1, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } exported.load("export-test0002.png"); if (!TestUtil::compareQImages(errpoint, exported, frame2)) { QFAIL(QString("Failed to export identical frame2, first different pixel: %1,%2 \n").arg(errpoint.x()).arg(errpoint.y()).toLatin1()); } } KISTEST_MAIN(KisAnimationExporterTest) diff --git a/plugins/extensions/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp index b8229a2ce4..ca6551b1b3 100644 --- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp @@ -1,208 +1,209 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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 "AnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "DlgAnimationRenderer.h" #include #include "video_saver.h" #include "KisAnimationRenderingOptions.h" K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin();) AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { // Shows the big dialog KisAction *action = createAction("render_animation"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation())); // Re-renders the image sequence as defined in the last render action = createAction("render_animation_again"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain())); } AnimaterionRenderer::~AnimaterionRenderer() { } void AnimaterionRenderer::slotRenderAnimation() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); DlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow()); dlgAnimationRenderer.setCaption(i18n("Render Animation")); if (dlgAnimationRenderer.exec() == QDialog::Accepted) { KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions(); renderAnimationImpl(doc, encoderOptions); } } void AnimaterionRenderer::slotRenderSequenceAgain() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); KisAnimationRenderingOptions encoderOptions; encoderOptions.fromProperties(settings); renderAnimationImpl(doc, encoderOptions); } void AnimaterionRenderer::renderAnimationImpl(KisDocument *doc, KisAnimationRenderingOptions encoderOptions) { const QString frameMimeType = encoderOptions.frameMimeType; const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory(); const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first(); const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory) .arg(encoderOptions.basename) .arg(extension); /** * The dialog should ensure that the size of the video is even */ KIS_SAFE_ASSERT_RECOVER( !((encoderOptions.width & 0x1 || encoderOptions.height & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || encoderOptions.videoMimeType == "video/x-matroska") && !(encoderOptions.renderMode() == encoderOptions.RENDER_FRAMES_ONLY))) { encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1); encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1); } const QSize scaledSize = doc->image()->bounds().size().scaled( encoderOptions.width, encoderOptions.height, Qt::KeepAspectRatio); if ((scaledSize.width() & 0x1 || scaledSize.height() & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || encoderOptions.videoMimeType == "video/x-matroska") && !(encoderOptions.renderMode() == encoderOptions.RENDER_FRAMES_ONLY)) { QString m = "Mastroska (.mkv)"; if (encoderOptions.videoMimeType == "video/mp4") { m = "Mpeg4 (.mp4)"; } qWarning() << m <<"requires width and height to be even, resize and try again!"; doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", m)); QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); return; } const bool batchMode = false; // TODO: fetch correctly! KisAsyncAnimationFramesSaveDialog exporter(doc->image(), KisTimeRange::fromTime(encoderOptions.firstFrame, encoderOptions.lastFrame), baseFileName, encoderOptions.sequenceStart, + encoderOptions.onlyRenderUniqueFrames && !encoderOptions.shouldEncodeVideo, encoderOptions.frameExportConfig); exporter.setBatchMode(batchMode); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(viewManager()->mainWindow()->viewManager()); // the folder could have been read-only or something else could happen if (encoderOptions.shouldEncodeVideo && result == KisAsyncAnimationFramesSaveDialog::RenderComplete) { const QString savedFilesMask = exporter.savedFilesMask(); const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath(); KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute()); { const QFileInfo info(resultFile); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); } KisImportExportErrorCode res; QFile fi(resultFile); if (!fi.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fi.fileName() << "for writing!"; res = KisImportExportErrorCannotWrite(fi.error()); } else { fi.close(); } QScopedPointer encoder(new VideoSaver(doc, batchMode)); res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode); if (!res.isOk()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage())); } if (encoderOptions.shouldDeleteSequence) { QDir d(framesDirectory); QStringList sequenceFiles = d.entryList(QStringList() << encoderOptions.basename + "*." + extension, QDir::Files); Q_FOREACH(const QString &f, sequenceFiles) { d.remove(f); } } } else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { viewManager()->mainWindow()->viewManager()->showFloatingMessage(i18n("Failed to render animation frames!"), QIcon()); } } #include "AnimationRenderer.moc" diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp index 2f520761bc..c1c5846da2 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp @@ -1,618 +1,643 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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 "DlgAnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_slider_spin_box.h" #include "kis_acyclic_signal_connector.h" #include "video_saver.h" #include "KisAnimationRenderingOptions.h" #include "video_export_options_dialog.h" DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent) : KoDialog(parent) , m_image(doc->image()) , m_doc(doc) { KisConfig cfg(true); setCaption(i18n("Render Animation")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgAnimationRenderer(this); m_page->layout()->setMargin(0); m_page->dirRequester->setMode(KoFileDialog::OpenDirectory); m_page->intStart->setMinimum(0); m_page->intStart->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); m_page->intStart->setValue(doc->image()->animationInterface()->playbackRange().start()); m_page->intEnd->setMinimum(doc->image()->animationInterface()->fullClipRange().start()); // animators sometimes want to export after end frame //m_page->intEnd->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end()); m_page->intHeight->setMinimum(1); m_page->intHeight->setMaximum(100000); m_page->intHeight->setValue(doc->image()->height()); m_page->intWidth->setMinimum(1); m_page->intWidth->setMaximum(100000); m_page->intWidth->setValue(doc->image()->width()); // try to lock the width and height being updated KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this); constrainsConnector->createCoordinatedConnector()->connectBackwardInt(m_page->intWidth, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsWidth(int))); constrainsConnector->createCoordinatedConnector()->connectForwardInt(m_page->intHeight, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsHeight(int))); m_page->intFramesPerSecond->setValue(doc->image()->animationInterface()->framerate()); QFileInfo audioFileInfo(doc->image()->animationInterface()->audioChannelFileName()); const bool hasAudio = audioFileInfo.exists(); m_page->chkIncludeAudio->setEnabled(hasAudio); m_page->chkIncludeAudio->setChecked(hasAudio && !doc->image()->animationInterface()->isAudioMuted()); QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimes.sort(); + filterSequenceMimeTypes(mimes); Q_FOREACH(const QString &mime, mimes) { QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbMimetype->addItem(description, mime); if (mime == "image/png") { m_page->cmbMimetype->setCurrentIndex(m_page->cmbMimetype->count() - 1); } } setMainWidget(m_page); - QVector supportedMimeType; - supportedMimeType << "video/x-matroska"; - supportedMimeType << "image/gif"; - supportedMimeType << "video/ogg"; - supportedMimeType << "video/mp4"; - + QStringList supportedMimeType = makeVideoMimeTypesList(); Q_FOREACH (const QString &mime, supportedMimeType) { QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbRenderType->addItem(description, mime); } m_page->videoFilename->setMode(KoFileDialog::SaveFile); connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeOptionsClicked())); connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderOptions())); m_page->ffmpegLocation->setMode(KoFileDialog::OpenFile); m_page->cmbRenderType->setCurrentIndex(cfg.readEntry("AnimationRenderer/render_type", 0)); connect(m_page->shouldExportOnlyImageSequence, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportOnlyVideo, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportAll, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->intFramesPerSecond, SIGNAL(valueChanged(int)), SLOT(frameRateChanged(int))); // connect and cold init connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int))); selectRenderType(m_page->cmbRenderType->currentIndex()); resize(m_page->sizeHint()); connect(this, SIGNAL(accepted()), SLOT(slotDialogAccepted())); { KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); KisAnimationRenderingOptions options; options.fromProperties(settings); loadAnimationOptions(options); } } DlgAnimationRenderer::~DlgAnimationRenderer() { delete m_page; } void DlgAnimationRenderer::getDefaultVideoEncoderOptions(const QString &mimeType, KisPropertiesConfigurationSP cfg, QString *customFFMpegOptionsString, bool *forceHDRVideo) { const VideoExportOptionsDialog::ContainerType containerType = mimeType == "video/ogg" ? VideoExportOptionsDialog::OGV : VideoExportOptionsDialog::DEFAULT; QScopedPointer encoderConfigWidget( new VideoExportOptionsDialog(containerType, 0)); // we always enable HDR, letting the user to force it encoderConfigWidget->setSupportsHDR(true); encoderConfigWidget->setConfiguration(cfg); *customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString(); *forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames(); } +void DlgAnimationRenderer::filterSequenceMimeTypes(QStringList &mimeTypes) +{ + QStringList validMimeTypes; + Q_FOREACH(const QString &mime, mimeTypes) { + if (mime.startsWith("image/") + || (mime.startsWith("application/") && + !mime.startsWith("application/x-spriter") )) { + validMimeTypes.append(mime); + } + } + + mimeTypes = validMimeTypes; +} + +QStringList DlgAnimationRenderer::makeVideoMimeTypesList() +{ + QStringList supportedMimeTypes = QStringList(); + supportedMimeTypes << "video/x-matroska"; + supportedMimeTypes << "image/gif"; + supportedMimeTypes << "video/ogg"; + supportedMimeTypes << "video/mp4"; + + return supportedMimeTypes; +} + void DlgAnimationRenderer::loadAnimationOptions(const KisAnimationRenderingOptions &options) { const QString documentPath = m_doc->localFilePath(); m_page->txtBasename->setText(options.basename); if (!options.lastDocuemntPath.isEmpty() && options.lastDocuemntPath == documentPath) { m_page->intStart->setValue(options.firstFrame); m_page->intEnd->setValue(options.lastFrame); m_page->sequenceStart->setValue(options.sequenceStart); m_page->intWidth->setValue(options.width); m_page->intHeight->setValue(options.height); m_page->intFramesPerSecond->setValue(options.frameRate); m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->videoFilename->setFileName(options.videoFileName); m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->dirRequester->setFileName(options.directory); } else { m_page->intStart->setValue(m_image->animationInterface()->playbackRange().start()); m_page->intEnd->setValue(m_image->animationInterface()->playbackRange().end()); m_page->sequenceStart->setValue(m_image->animationInterface()->playbackRange().start()); m_page->intWidth->setValue(m_image->width()); m_page->intHeight->setValue(m_image->height()); m_page->intFramesPerSecond->setValue(m_image->animationInterface()->framerate()); m_page->videoFilename->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->videoFilename->setFileName(defaultVideoFileName(m_doc, options.videoMimeType)); m_page->dirRequester->setStartDir(options.resolveAbsoluteDocumentFilePath(documentPath)); m_page->dirRequester->setFileName(options.directory); } for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == options.frameMimeType) { m_page->cmbMimetype->setCurrentIndex(i); break; } } for (int i = 0; i < m_page->cmbRenderType->count(); ++i) { if (m_page->cmbRenderType->itemData(i).toString() == options.videoMimeType) { m_page->cmbRenderType->setCurrentIndex(i); break; } } m_page->chkIncludeAudio->setChecked(options.includeAudio); + m_page->chkOnlyUniqueFrames->setChecked(options.onlyRenderUniqueFrames); if (options.shouldDeleteSequence) { KIS_SAFE_ASSERT_RECOVER_NOOP(options.shouldEncodeVideo); m_page->shouldExportOnlyVideo->setChecked(true); } else if (!options.shouldEncodeVideo) { KIS_SAFE_ASSERT_RECOVER_NOOP(!options.shouldDeleteSequence); m_page->shouldExportOnlyImageSequence->setChecked(true); } else { m_page->shouldExportAll->setChecked(true); // export to both } { KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER"); getDefaultVideoEncoderOptions(options.videoMimeType, settings, &m_customFFMpegOptionsString, &m_forceHDRVideo); } m_page->ffmpegLocation->setStartDir(QFileInfo(m_doc->localFilePath()).path()); m_page->ffmpegLocation->setFileName(findFFMpeg(options.ffmpegPath)); } QString DlgAnimationRenderer::defaultVideoFileName(KisDocument *doc, const QString &mimeType) { const QString docFileName = !doc->localFilePath().isEmpty() ? doc->localFilePath() : i18n("Untitled"); return QString("%1.%2") .arg(QFileInfo(docFileName).completeBaseName()) .arg(KisMimeDatabase::suffixesForMimeType(mimeType).first()); } void DlgAnimationRenderer::selectRenderType(int index) { const QString mimeType = m_page->cmbRenderType->itemData(index).toString(); m_page->bnRenderOptions->setEnabled(mimeType != "image/gif"); m_page->lblGifWarning->setVisible((mimeType == "image/gif" && m_page->intFramesPerSecond->value() > 50)); QString videoFileName = defaultVideoFileName(m_doc, mimeType); if (!m_page->videoFilename->fileName().isEmpty()) { const QFileInfo info = QFileInfo(m_page->videoFilename->fileName()); const QString baseName = info.completeBaseName(); const QString path = info.path(); videoFileName = QString("%1%2%3.%4").arg(path).arg(QDir::separator()).arg(baseName).arg(KisMimeDatabase::suffixesForMimeType(mimeType).first()); } m_page->videoFilename->setMimeTypeFilters(QStringList() << mimeType, mimeType); m_page->videoFilename->setFileName(videoFileName); } void DlgAnimationRenderer::selectRenderOptions() { const int index = m_page->cmbRenderType->currentIndex(); const QString mimetype = m_page->cmbRenderType->itemData(index).toString(); const VideoExportOptionsDialog::ContainerType containerType = mimetype == "video/ogg" ? VideoExportOptionsDialog::OGV : VideoExportOptionsDialog::DEFAULT; VideoExportOptionsDialog *encoderConfigWidget = new VideoExportOptionsDialog(containerType, this); // we always enable HDR, letting the user to force it encoderConfigWidget->setSupportsHDR(true); { KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("VIDEO_ENCODER"); encoderConfigWidget->setConfiguration(settings); } KoDialog dlg(this); dlg.setMainWidget(encoderConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (dlg.exec() == QDialog::Accepted) { KisConfig cfg(false); cfg.setExportConfiguration("VIDEO_ENCODER", encoderConfigWidget->configuration()); m_customFFMpegOptionsString = encoderConfigWidget->customUserOptionsString(); m_forceHDRVideo = encoderConfigWidget->forceHDRModeForFrames(); } dlg.setMainWidget(0); encoderConfigWidget->deleteLater(); } void DlgAnimationRenderer::sequenceMimeTypeOptionsClicked() { int index = m_page->cmbMimetype->currentIndex(); KisConfigWidget *frameExportConfigWidget = 0; QString mimetype = m_page->cmbMimetype->itemData(index).toString(); QSharedPointer filter(KisImportExportManager::filterForMimeType(mimetype, KisImportExportManager::Export)); if (filter) { frameExportConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (frameExportConfigWidget) { KisPropertiesConfigurationSP config = filter->lastSavedConfiguration("", mimetype.toLatin1()); if (config) { KisImportExportManager::fillStaticExportConfigurationProperties(config, m_image); } frameExportConfigWidget->setConfiguration(config); KoDialog dlg(this); dlg.setMainWidget(frameExportConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (dlg.exec() == QDialog::Accepted) { KisConfig cfg(false); cfg.setExportConfiguration(mimetype, frameExportConfigWidget->configuration()); } frameExportConfigWidget->hide(); dlg.setMainWidget(0); frameExportConfigWidget->setParent(0); frameExportConfigWidget->deleteLater(); } } } inline int roundByTwo(int value) { return value + (value & 0x1); } KisAnimationRenderingOptions DlgAnimationRenderer::getEncoderOptions() const { KisAnimationRenderingOptions options; options.lastDocuemntPath = m_doc->localFilePath(); options.videoMimeType = m_page->cmbRenderType->currentData().toString(); options.frameMimeType = m_page->cmbMimetype->currentData().toString(); options.basename = m_page->txtBasename->text(); options.directory = m_page->dirRequester->fileName(); options.firstFrame = m_page->intStart->value(); options.lastFrame = m_page->intEnd->value(); options.sequenceStart = m_page->sequenceStart->value(); options.shouldEncodeVideo = !m_page->shouldExportOnlyImageSequence->isChecked(); options.shouldDeleteSequence = m_page->shouldExportOnlyVideo->isChecked(); options.includeAudio = m_page->chkIncludeAudio->isChecked(); + options.onlyRenderUniqueFrames = m_page->chkOnlyUniqueFrames->isChecked(); options.ffmpegPath = m_page->ffmpegLocation->fileName(); options.frameRate = m_page->intFramesPerSecond->value(); if (options.frameRate > 50 && options.videoMimeType == "image/gif") { options.frameRate = 50; } options.width = roundByTwo(m_page->intWidth->value()); options.height = roundByTwo(m_page->intHeight->value()); options.videoFileName = m_page->videoFilename->fileName(); options.customFFMpegOptions = m_customFFMpegOptionsString; { KisConfig config(true); KisPropertiesConfigurationSP cfg = config.exportConfiguration(options.frameMimeType); if (cfg) { KisImportExportManager::fillStaticExportConfigurationProperties(cfg, m_image); } const bool forceHDR = m_forceHDRVideo && !m_page->shouldExportOnlyImageSequence->isChecked(); if (forceHDR) { KIS_SAFE_ASSERT_RECOVER_NOOP(options.frameMimeType == "image/png"); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveAsHDR", true); } options.frameExportConfig = cfg; } return options; } void DlgAnimationRenderer::slotButtonClicked(int button) { if (button == KoDialog::Ok && !m_page->shouldExportOnlyImageSequence->isChecked()) { QString ffmpeg = m_page->ffmpegLocation->fileName(); if (m_page->videoFilename->fileName().isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please enter a file name to render to.")); return; } else if (ffmpeg.isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Krita can't find FFmpeg!
\ Krita depends on another free program called FFmpeg to turn frame-by-frame animations into video files. (www.ffmpeg.org)

\ To learn more about setting up Krita for rendering animations, please visit this section of our User Manual.")); return; } else { QFileInfo fi(ffmpeg); if (!fi.exists()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system.")); return; } if (fi.fileName().endsWith("zip")) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please extract ffmpeg from the archive first.")); return; } if (fi.fileName().endsWith("tar.bz2")) { #ifdef Q_OS_WIN QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("This is a source code archive. Please download the application archive ('Windows Builds'), unpack it and then provide the path to the extracted ffmpeg file.")); #else QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("This is a source code archive. Please install ffmpeg instead.")); #endif return; } if (!fi.isExecutable()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system.")); return; } } } KoDialog::slotButtonClicked(button); } void DlgAnimationRenderer::slotDialogAccepted() { KisConfig cfg(false); KisAnimationRenderingOptions options = getEncoderOptions(); cfg.setExportConfiguration("ANIMATION_EXPORT", options.toProperties()); } QString DlgAnimationRenderer::findFFMpeg(const QString &customLocation) { QString result; QStringList proposedPaths; if (!customLocation.isEmpty()) { proposedPaths << customLocation; proposedPaths << customLocation + QDir::separator() + "ffmpeg"; } proposedPaths << KoResourcePaths::getApplicationRoot() + QDir::separator() + "bin" + QDir::separator() + "ffmpeg"; #ifndef Q_OS_WIN proposedPaths << QDir::homePath() + "/bin/ffmpeg"; proposedPaths << "/usr/bin/ffmpeg"; proposedPaths << "/usr/local/bin/ffmpeg"; #endif Q_FOREACH (QString path, proposedPaths) { if (path.isEmpty()) continue; #ifdef Q_OS_WIN path = QDir::toNativeSeparators(QDir::cleanPath(path)); if (path.endsWith(QDir::separator())) { continue; } if (!path.endsWith(".exe")) { if (!QFile::exists(path)) { path += ".exe"; if (!QFile::exists(path)) { continue; } } } #endif QProcess testProcess; testProcess.start(path, QStringList() << "-version"); if (testProcess.waitForStarted(1000)) { testProcess.waitForFinished(1000); } const bool successfulStart = testProcess.state() == QProcess::NotRunning && testProcess.error() == QProcess::UnknownError; if (successfulStart) { result = path; break; } } return result; } void DlgAnimationRenderer::slotExportTypeChanged() { KisConfig cfg(false); bool willEncodeVideo = m_page->shouldExportAll->isChecked() || m_page->shouldExportOnlyVideo->isChecked(); // if a video format needs to be outputted if (willEncodeVideo) { // videos always uses PNG for creating video, so disable the ability to change the format m_page->cmbMimetype->setEnabled(false); for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == "image/png") { m_page->cmbMimetype->setCurrentIndex(i); break; } } } m_page->intWidth->setVisible(willEncodeVideo); m_page->intHeight->setVisible(willEncodeVideo); m_page->intFramesPerSecond->setVisible(willEncodeVideo); m_page->fpsLabel->setVisible(willEncodeVideo); m_page->lblWidth->setVisible(willEncodeVideo); m_page->lblHeight->setVisible(willEncodeVideo); + m_page->chkOnlyUniqueFrames->setVisible(false); // if only exporting video if (m_page->shouldExportOnlyVideo->isChecked()) { m_page->cmbMimetype->setEnabled(false); // allow to change image format m_page->imageSequenceOptionsGroup->setVisible(false); m_page->videoOptionsGroup->setVisible(false); //shrinks the horizontal space temporarily to help resize() work m_page->videoOptionsGroup->setVisible(true); } // if only an image sequence needs to be output if (m_page->shouldExportOnlyImageSequence->isChecked()) { m_page->cmbMimetype->setEnabled(true); // allow to change image format m_page->videoOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(true); + m_page->chkOnlyUniqueFrames->setVisible(true); } // show all options if (m_page->shouldExportAll->isChecked() ) { m_page->imageSequenceOptionsGroup->setVisible(true); m_page->videoOptionsGroup->setVisible(true); } // for the resize to work as expected, try to hide elements first before displaying other ones. // if the widget gets bigger at any point, the resize will use that, even if elements are hidden later to make it smaller resize(m_page->sizeHint()); } void DlgAnimationRenderer::frameRateChanged(int framerate) { const QString mimeType = m_page->cmbRenderType->itemData(m_page->cmbRenderType->currentIndex()).toString(); m_page->lblGifWarning->setVisible((mimeType == "image/gif" && framerate > 50)); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsWidth(int width) { Q_UNUSED(width); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update height here float newHeight = m_page->intWidth->value() / aspectRatio ; m_page->intHeight->setValue(newHeight); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsHeight(int height) { Q_UNUSED(height); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update width here float newWidth = aspectRatio * m_page->intHeight->value(); m_page->intWidth->setValue(newWidth); } diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.h b/plugins/extensions/animationrenderer/DlgAnimationRenderer.h index 098b11f169..0a6a7827f2 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.h +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.h @@ -1,106 +1,108 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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. */ #ifndef DLG_ANIMATIONRENDERERIMAGE #define DLG_ANIMATIONRENDERERIMAGE #include #include #include "ui_wdg_animationrenderer.h" #include #include #include class KisDocument; class KisImportExportFilter; class KisConfigWidget; class QHBoxLayout; class VideoSaver; class KisAnimationRenderingOptions; class WdgAnimationRenderer : public QWidget, public Ui::WdgAnimaterionRenderer { Q_OBJECT public: WdgAnimationRenderer(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgAnimationRenderer: public KoDialog { Q_OBJECT public: DlgAnimationRenderer(KisDocument *doc, QWidget *parent = 0); ~DlgAnimationRenderer() override; KisAnimationRenderingOptions getEncoderOptions() const; private Q_SLOTS: void selectRenderType(int i); void selectRenderOptions(); /** * @brief sequenceMimeTypeSelected * calls the dialog for the export widget. */ void sequenceMimeTypeOptionsClicked(); void slotLockAspectRatioDimensionsWidth(int width); void slotLockAspectRatioDimensionsHeight(int height); void slotExportTypeChanged(); void frameRateChanged(int framerate); protected Q_SLOTS: void slotButtonClicked(int button) override; void slotDialogAccepted(); private: void loadAnimationOptions(const KisAnimationRenderingOptions &options); static QString defaultVideoFileName(KisDocument *doc, const QString &mimeType); static void getDefaultVideoEncoderOptions(const QString &mimeType, KisPropertiesConfigurationSP cfg, QString *customFFMpegOptionsString, bool *forceHDRVideo); + void filterSequenceMimeTypes(QStringList &mimeTypes); + QStringList makeVideoMimeTypesList(); private: static QString findFFMpeg(const QString &customLocation); KisImageSP m_image; KisDocument *m_doc; WdgAnimationRenderer *m_page {0}; QString m_customFFMpegOptionsString; bool m_forceHDRVideo = false; }; #endif // DLG_ANIMATIONRENDERERIMAGE diff --git a/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.cpp b/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.cpp index 990676e685..a795b84d31 100644 --- a/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.cpp +++ b/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.cpp @@ -1,136 +1,138 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KisAnimationRenderingOptions.h" #include #include #include KisAnimationRenderingOptions::KisAnimationRenderingOptions() : videoMimeType("video/mp4"), frameMimeType("image/png"), basename("frame"), directory("") { } QString KisAnimationRenderingOptions::resolveAbsoluteDocumentFilePath(const QString &documentPath) const { return !documentPath.isEmpty() ? documentPath : QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } QString KisAnimationRenderingOptions::resolveAbsoluteVideoFilePath(const QString &documentPath) const { const QString basePath = resolveAbsoluteDocumentFilePath(documentPath); return KritaUtils::resolveAbsoluteFilePath(basePath, videoFileName); } QString KisAnimationRenderingOptions::resolveAbsoluteFramesDirectory(const QString &documentPath) const { if (renderMode() == RENDER_VIDEO_ONLY) { return QFileInfo(resolveAbsoluteVideoFilePath()).absolutePath(); } const QString basePath = resolveAbsoluteDocumentFilePath(documentPath); return KritaUtils::resolveAbsoluteFilePath(basePath, directory); } QString KisAnimationRenderingOptions::resolveAbsoluteVideoFilePath() const { return resolveAbsoluteVideoFilePath(lastDocuemntPath); } QString KisAnimationRenderingOptions::resolveAbsoluteFramesDirectory() const { return resolveAbsoluteFramesDirectory(lastDocuemntPath); } KisAnimationRenderingOptions::RenderMode KisAnimationRenderingOptions::renderMode() const { if (shouldDeleteSequence) { KIS_SAFE_ASSERT_RECOVER_NOOP(shouldEncodeVideo); return RENDER_VIDEO_ONLY; } else if (!shouldEncodeVideo) { KIS_SAFE_ASSERT_RECOVER_NOOP(!shouldDeleteSequence); return RENDER_FRAMES_ONLY; } else { return RENDER_FRAMES_AND_VIDEO; } } KisPropertiesConfigurationSP KisAnimationRenderingOptions::toProperties() const { KisPropertiesConfigurationSP config = new KisPropertiesConfiguration(); config->setProperty("basename", basename); config->setProperty("last_document_path", lastDocuemntPath); config->setProperty("directory", directory); config->setProperty("first_frame", firstFrame); config->setProperty("last_frame", lastFrame); config->setProperty("sequence_start", sequenceStart); config->setProperty("video_mimetype", videoMimeType); config->setProperty("frame_mimetype", frameMimeType); config->setProperty("encode_video", shouldEncodeVideo); config->setProperty("delete_sequence", shouldDeleteSequence); + config->setProperty("only_unique_frames", onlyRenderUniqueFrames); config->setProperty("ffmpeg_path", ffmpegPath); config->setProperty("framerate", frameRate); config->setProperty("height", height); config->setProperty("width", width); config->setProperty("include_audio", includeAudio); config->setProperty("filename", videoFileName); config->setProperty("custom_ffmpeg_options", customFFMpegOptions); config->setPrefixedProperties("frame_export/", frameExportConfig); return config; } void KisAnimationRenderingOptions::fromProperties(KisPropertiesConfigurationSP config) { basename = config->getPropertyLazy("basename", basename); lastDocuemntPath = config->getPropertyLazy("last_document_path", ""); directory = config->getPropertyLazy("directory", directory); firstFrame = config->getPropertyLazy("first_frame", 0); lastFrame = config->getPropertyLazy("last_frame", 0); sequenceStart = config->getPropertyLazy("sequence_start", 0); videoMimeType = config->getPropertyLazy("video_mimetype", videoMimeType); frameMimeType = config->getPropertyLazy("frame_mimetype", frameMimeType); shouldEncodeVideo = config->getPropertyLazy("encode_video", false); shouldDeleteSequence = config->getPropertyLazy("delete_sequence", false); + onlyRenderUniqueFrames = config->getPropertyLazy("only_unique_frames", false); ffmpegPath = config->getPropertyLazy("ffmpeg_path", ""); frameRate = config->getPropertyLazy("framerate", 25); height = config->getPropertyLazy("height", 0); width = config->getPropertyLazy("width", 0); includeAudio = config->getPropertyLazy("include_audio", true); videoFileName = config->getPropertyLazy("filename", ""); customFFMpegOptions = config->getPropertyLazy("custom_ffmpeg_options", ""); frameExportConfig = new KisPropertiesConfiguration(); frameExportConfig->setPrefixedProperties("frame_export/", frameExportConfig); } diff --git a/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h b/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h index 97dbe3eb69..50377c568f 100644 --- a/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h +++ b/plugins/extensions/animationrenderer/KisAnimationRenderingOptions.h @@ -1,76 +1,77 @@ /* * Copyright (c) 2019 Dmitry Kazakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KISANIMATIONRENDERINGOPTIONS_H #define KISANIMATIONRENDERINGOPTIONS_H #include #include "kis_properties_configuration.h" class KisAnimationRenderingOptions { public: KisAnimationRenderingOptions(); QString lastDocuemntPath; QString videoMimeType; QString frameMimeType; QString basename; QString directory; int firstFrame = 0; int lastFrame = 0; int sequenceStart = 0; bool shouldEncodeVideo = false; bool shouldDeleteSequence = false; bool includeAudio = false; + bool onlyRenderUniqueFrames = false; QString ffmpegPath; int frameRate = 25; int width = 0; int height = 0; QString videoFileName; QString customFFMpegOptions; KisPropertiesConfigurationSP frameExportConfig; QString resolveAbsoluteDocumentFilePath(const QString &documentPath) const; QString resolveAbsoluteVideoFilePath(const QString &documentPath) const; QString resolveAbsoluteFramesDirectory(const QString &documentPath) const; QString resolveAbsoluteVideoFilePath() const; QString resolveAbsoluteFramesDirectory() const; enum RenderMode { RENDER_FRAMES_ONLY, RENDER_VIDEO_ONLY, RENDER_FRAMES_AND_VIDEO }; RenderMode renderMode() const; KisPropertiesConfigurationSP toProperties() const; void fromProperties(KisPropertiesConfigurationSP config); }; #endif // KISANIMATIONRENDERINGOPTIONS_H diff --git a/plugins/extensions/animationrenderer/video_export_options_dialog.cpp b/plugins/extensions/animationrenderer/video_export_options_dialog.cpp index 3cfb85da6b..cc1e8d4691 100644 --- a/plugins/extensions/animationrenderer/video_export_options_dialog.cpp +++ b/plugins/extensions/animationrenderer/video_export_options_dialog.cpp @@ -1,406 +1,404 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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 "video_export_options_dialog.h" #include "ui_video_export_options_dialog.h" #include #include #include #include "VideoHDRMetadataOptionsDialog.h" #include "KisHDRMetadataOptions.h" struct VideoExportOptionsDialog::Private { Private(ContainerType _containerType) : containerType(_containerType) { if (containerType == DEFAULT) { codecs << KoID("libx264", i18nc("h264 codec name, check simplescreenrecorder for standard translations", "H.264, MPEG-4 Part 10")); codecs << KoID("libx265", i18nc("h265 codec name, check simplescreenrecorder for standard translations", "H.265, MPEG-H Part 2 (HEVC)")); } else { codecs << KoID("libtheora", i18nc("theora codec name, check simplescreenrecorder for standard translations", "Theora")); } presets << KoID("ultrafast", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "ultrafast")); presets << KoID("superfast", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "superfast")); presets << KoID("veryfast", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "veryfast")); presets << KoID("faster", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "faster")); presets << KoID("fast", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "fast")); presets << KoID("medium", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "medium")); presets << KoID("slow", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "slow")); presets << KoID("slower", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "slower")); presets << KoID("veryslow", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "veryslow")); presets << KoID("placebo", i18nc("h264 preset name, check simplescreenrecorder for standard translations", "placebo")); profilesH264 << KoID("baseline", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "baseline")); profilesH264 << KoID("main", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "main")); profilesH264 << KoID("high", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "high")); profilesH264 << KoID("high10", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "high10")); profilesH264 << KoID("high422", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "high422")); profilesH264 << KoID("high444", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "high444")); profilesH265 << KoID("main", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "main")); profilesH265 << KoID("main10", i18nc("h264 profile name, check simplescreenrecorder for standard translations", "main10 (HDR)")); // TODO: add "none" tune option tunesH264 << KoID("film", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "film")); tunesH264 << KoID("animation", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "animation")); tunesH264 << KoID("grain", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "grain")); tunesH264 << KoID("stillimage", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "stillimage")); tunesH264 << KoID("psnr", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "psnr")); tunesH264 << KoID("ssim", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "ssim")); tunesH264 << KoID("fastdecode", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "fastdecode")); tunesH264 << KoID("zerolatency", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "zerolatency")); tunesH265 << KoID("none", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "none")); tunesH265 << KoID("animation", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "animation")); tunesH265 << KoID("grain", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "grain")); tunesH265 << KoID("psnr", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "psnr")); tunesH265 << KoID("ssim", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "ssim")); tunesH265 << KoID("fastdecode", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "fastdecode")); tunesH265 << KoID("zero-latency", i18nc("h264 tune option name, check simplescreenrecorder for standard translations", "zero-latency")); } QVector codecs; QVector presets; QVector profilesH264; QVector profilesH265; QVector tunesH264; QVector tunesH265; bool supportsHDR = false; ContainerType containerType; QString currentCustomLine; KisHDRMetadataOptions hdrMetadataOptions; }; void populateComboWithKoIds(QComboBox *combo, const QVector &ids, int defaultIndex) { Q_FOREACH (const KoID &id, ids) { combo->insertItem(combo->count(), id.name()); } combo->setCurrentIndex(defaultIndex); } VideoExportOptionsDialog::VideoExportOptionsDialog(ContainerType containerType, QWidget *parent) : KisConfigWidget(parent), ui(new Ui::VideoExportOptionsDialog), m_d(new Private(containerType)) { ui->setupUi(this); ui->intCRFH264->setRange(0, 51); ui->intCRFH264->setValue(28); ui->intCRFH265->setRange(0, 51); ui->intCRFH265->setValue(28); populateComboWithKoIds(ui->cmbPresetH264, m_d->presets, 5); populateComboWithKoIds(ui->cmbPresetH265, m_d->presets, 5); populateComboWithKoIds(ui->cmbProfileH264, m_d->profilesH264, 0); populateComboWithKoIds(ui->cmbProfileH265, m_d->profilesH265, 0); populateComboWithKoIds(ui->cmbTuneH264, m_d->tunesH264, 0); populateComboWithKoIds(ui->cmbTuneH265, m_d->tunesH265, 0); ui->intBitrate->setRange(10, 50000); ui->intBitrate->setValue(5000); ui->intBitrate->setSuffix(i18nc("kilo-bits-per-second, video bitrate suffix", "kbps")); populateComboWithKoIds(ui->cmbCodec, m_d->codecs, 0); connect(ui->cmbCodec, SIGNAL(currentIndexChanged(int)), SLOT(slotCodecSelected(int))); slotCodecSelected(0); // TODO: temporarily hidden! Some combinations of 'tune' and // 'profile' options make ffmpeg generate empty file. // We should not let the user shoot into his own foot! ui->cmbTuneH264->setVisible(false); ui->lblTuneH264->setVisible(false); ui->cmbTuneH265->setVisible(false); ui->lblTuneH265->setVisible(false); slotCustomLineToggled(false); connect(ui->chkCustomLine, SIGNAL(toggled(bool)), SLOT(slotCustomLineToggled(bool))); connect(ui->txtCustomLine, SIGNAL(editingFinished()), SLOT(slotSaveCustomLine())); connect(ui->btnResetCustomLine, SIGNAL(clicked()), SLOT(slotResetCustomLine())); connect(ui->chkUseHDRMetadata, SIGNAL(toggled(bool)), ui->btnHdrMetadata, SLOT(setEnabled(bool))); connect(ui->cmbProfileH265, SIGNAL(currentIndexChanged(int)), SLOT(slotH265ProfileChanged(int))); slotH265ProfileChanged(ui->cmbProfileH265->currentIndex()); connect(ui->btnHdrMetadata, SIGNAL(clicked()), SLOT(slotEditHDRMetadata())); setSupportsHDR(false); } VideoExportOptionsDialog::~VideoExportOptionsDialog() { delete ui; } void VideoExportOptionsDialog::setSupportsHDR(bool value) { m_d->supportsHDR = value; slotH265ProfileChanged(ui->cmbProfileH265->currentIndex()); } KisPropertiesConfigurationSP VideoExportOptionsDialog::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("CodecId", currentCodecId()); cfg->setProperty("h264PresetIndex", ui->cmbPresetH264->currentIndex()); cfg->setProperty("h264ConstantRateFactor", ui->intCRFH264->value()); cfg->setProperty("h264ProfileIndex", ui->cmbProfileH264->currentIndex()); cfg->setProperty("h264TuneIndex", ui->cmbTuneH264->currentIndex()); cfg->setProperty("h265PresetIndex", ui->cmbPresetH265->currentIndex()); cfg->setProperty("h265ConstantRateFactor", ui->intCRFH265->value()); cfg->setProperty("h265ProfileIndex", ui->cmbProfileH265->currentIndex()); cfg->setProperty("h265TuneIndex", ui->cmbTuneH265->currentIndex()); cfg->setProperty("h265UseHDRMetadata", ui->chkUseHDRMetadata->isChecked()); cfg->setProperty("TheoraBitrate", ui->intBitrate->value()); cfg->setProperty("CustomLineValue", ui->txtCustomLine->text()); cfg->setProperty("customUserOptions", customUserOptions().join(' ')); cfg->setPrefixedProperties("hdrMetadata/", m_d->hdrMetadataOptions.toProperties()); return cfg; } void VideoExportOptionsDialog::slotCustomLineToggled(bool value) { QString customLine = m_d->currentCustomLine; if (m_d->currentCustomLine.isEmpty() && value) { customLine = generateCustomLine().join(" "); } else if (!value) { customLine = QString(); m_d->currentCustomLine = QString(); } ui->txtCustomLine->setText(customLine); ui->stackedWidget->setEnabled(!value); ui->txtCustomLine->setEnabled(value); ui->btnResetCustomLine->setEnabled(value); } void VideoExportOptionsDialog::slotResetCustomLine() { ui->txtCustomLine->setText(generateCustomLine().join(" ")); slotSaveCustomLine(); } void VideoExportOptionsDialog::slotCodecSelected(int index) { const QString codec = m_d->codecs[index].id(); if (codec == "libx264") { ui->stackedWidget->setCurrentIndex(CODEC_H264); } else if (codec == "libx265") { ui->stackedWidget->setCurrentIndex(CODEC_H265); } else if (codec == "libtheora") { ui->stackedWidget->setCurrentIndex(CODEC_THEORA); } } void VideoExportOptionsDialog::slotSaveCustomLine() { m_d->currentCustomLine = ui->txtCustomLine->text(); } QStringList VideoExportOptionsDialog::customUserOptions() const { return ui->chkCustomLine->isChecked() ? ui->txtCustomLine->text().split(" ", QString::SkipEmptyParts) : generateCustomLine(); } QString VideoExportOptionsDialog::customUserOptionsString() const { return customUserOptions().join(' '); } bool VideoExportOptionsDialog::forceHDRModeForFrames() const { return currentCodecId() == "libx265" && ui->chkUseHDRMetadata->isEnabled() && ui->chkUseHDRMetadata->isChecked(); } int findIndexById(const QString &id, const QVector &ids) { int index = -1; auto it = std::find_if(ids.begin(), ids.end(), [id] (const KoID &item) { return item.id() == id; }); if (it != ids.end()) { index = std::distance(ids.begin(), it); } return index; } void VideoExportOptionsDialog::setConfiguration(const KisPropertiesConfigurationSP cfg) { ui->cmbPresetH264->setCurrentIndex(cfg->getInt("h264PresetIndex", 5)); ui->intCRFH264->setValue(cfg->getInt("h264ConstantRateFactor", 23)); ui->cmbProfileH264->setCurrentIndex(cfg->getInt("h264ProfileIndex", 0)); ui->cmbTuneH264->setCurrentIndex(cfg->getInt("h264TuneIndex", 1)); ui->cmbPresetH265->setCurrentIndex(cfg->getInt("h265PresetIndex", 5)); ui->intCRFH265->setValue(cfg->getInt("h265ConstantRateFactor", 23)); ui->cmbProfileH265->setCurrentIndex(cfg->getInt("h265ProfileIndex", 0)); ui->cmbTuneH265->setCurrentIndex(cfg->getInt("h265TuneIndex", 1)); ui->chkUseHDRMetadata->setChecked(cfg->getBool("h265UseHDRMetadata", false)); ui->intBitrate->setValue(cfg->getInt("TheoraBitrate", 5000)); m_d->currentCustomLine = cfg->getString("CustomLineValue", QString()); ui->chkCustomLine->setChecked(!m_d->currentCustomLine.isEmpty()); slotCustomLineToggled(ui->chkCustomLine->isChecked()); const QString codecId = cfg->getString("CodecId", ""); const int index = qMax(0, findIndexById(codecId, m_d->codecs)); ui->cmbCodec->setCurrentIndex(index); slotCodecSelected(index); slotH265ProfileChanged(ui->cmbProfileH265->currentIndex()); KisPropertiesConfigurationSP metadataProperties = new KisPropertiesConfiguration(); cfg->getPrefixedProperties("hdrMetadata/", metadataProperties); m_d->hdrMetadataOptions.fromProperties(metadataProperties); } QStringList VideoExportOptionsDialog::generateCustomLine() const { QStringList options; if (currentCodecId() == "libx264") { options << "-crf" << QString::number(ui->intCRFH264->value()); const int presetIndex = ui->cmbPresetH264->currentIndex(); options << "-preset" << m_d->presets[presetIndex].id(); const int profileIndex = ui->cmbProfileH264->currentIndex(); options << "-profile:v" << m_d->profilesH264[profileIndex].id(); if (m_d->profilesH264[profileIndex].id() == "high422") { options << "-pix_fmt" << "yuv422p"; } else if (m_d->profilesH264[profileIndex].id() == "high444") { options << "-pix_fmt" << "yuv444p"; } else { options << "-pix_fmt" << "yuv420p"; } // Disabled! see the comment in c-tor! //const int tuneIndex = ui->cmbTune->currentIndex(); //options << "-tune" << m_d->tunes[tuneIndex].id(); } else if (currentCodecId() == "libx265") { const bool enableHDR = ui->chkUseHDRMetadata->isEnabled() && ui->chkUseHDRMetadata->isChecked(); if (enableHDR) { options << "-colorspace" << "bt2020c" << "-color_trc" << "smpte2084" << "-color_primaries" << "bt2020"; } options << "-c:v" << "libx265"; options << "-crf" << QString::number(ui->intCRFH265->value()); const int presetIndex = ui->cmbPresetH265->currentIndex(); options << "-preset" << m_d->presets[presetIndex].id(); const int profileIndex = ui->cmbProfileH265->currentIndex(); options << "-profile:v" << m_d->profilesH265[profileIndex].id(); if (m_d->profilesH265[profileIndex].id() == "main") { options << "-pix_fmt" << "yuv420p"; } else if (m_d->profilesH265[profileIndex].id() == "main10") { options << "-pix_fmt" << "yuv420p10le"; } else { KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "Unknown profile selected for h265 encoder"); } if (enableHDR) { const QString metadataLine = m_d->hdrMetadataOptions.generateFFMpegOptions(); options << metadataLine.split(" "); } } else if (currentCodecId() == "libtheora") { options << "-b" << QString::number(ui->intBitrate->value()) + "k"; } return options; } QString VideoExportOptionsDialog::currentCodecId() const { return m_d->codecs[ui->cmbCodec->currentIndex()].id(); } void VideoExportOptionsDialog::slotH265ProfileChanged(int index) { - ENTER_FUNCTION() << ppVar(m_d->profilesH265[index].id()); - const bool enableHDR = m_d->supportsHDR && index >= 0 && m_d->profilesH265[index].id() == "main10"; ui->chkUseHDRMetadata->setEnabled(enableHDR); ui->btnHdrMetadata->setEnabled(enableHDR && ui->chkUseHDRMetadata->isChecked()); QString hdrToolTip; if (!m_d->supportsHDR) { hdrToolTip = i18nc("@info:tooltip", "Exported animation format does not support HDR"); } else if (!enableHDR) { hdrToolTip = i18nc("@info:tooltip", "HDR metadata available only with \"main10\" profile"); } ui->chkUseHDRMetadata->setToolTip(hdrToolTip); ui->btnHdrMetadata->setToolTip(hdrToolTip); } void VideoExportOptionsDialog::slotEditHDRMetadata() { VideoHDRMetadataOptionsDialog dlg(this); dlg.setHDRMetadataOptions(m_d->hdrMetadataOptions); if (dlg.exec() == QDialog::Accepted) { m_d->hdrMetadataOptions = dlg.hdrMetadataOptions(); } } diff --git a/plugins/extensions/animationrenderer/wdg_animationrenderer.ui b/plugins/extensions/animationrenderer/wdg_animationrenderer.ui index bef5cbfe95..d7554efb3f 100644 --- a/plugins/extensions/animationrenderer/wdg_animationrenderer.ui +++ b/plugins/extensions/animationrenderer/wdg_animationrenderer.ui @@ -1,479 +1,488 @@ WdgAnimaterionRenderer 0 0 621 381 0 0 AnimationRenderer Image - - - 20 - - - 0 - - - - - Export: - - - - - - - Image Se&quence - - - - - - - &Video - - - - - - - Both - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + 20 + + + 1 + + + + + Export: + + + + + + + Image Se&quence + + + + + + + &Video + + + + + + + Both + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + 0 0 First frame: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LeftToRight FPS: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 1 1 0 0 10000 Last frame: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 px 0 0 Width: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Height: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 px 25 0 0 Video Options - - - - Render as: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + 1 + 0 + - - - Select the ffmpeg render options. ... - - + + - Video Location: + FF&Mpeg: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + ffmpegLocation + - - - - - 1 - 0 - + + + + + + + Render as: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + - FF&Mpeg: + Video Location: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - ffmpegLocation - - + 0 0 Include Audio <html><head/><body><p><span style=" font-weight:600;">Warning:</span> animated gif images cannot have a framerate higher than 50. The framerate will be reduced to 50 frames per second.</p></body></html> true Qt::Vertical 20 10 Image Sequence Options - - - - - 0 - 0 - - + + - Base name: + Fi&le format: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + label_8 + 2 0 Select the file format for the image sequence. If you want to render to video or animated gif, you can only select PNG Select the frame export options ... - - - - - 1 - 0 - - - - - - + + - Fi&le format: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - label_8 + Start numbering at: 1 0 999 - - + + + + + 0 + 0 + + - Image location: + Base name: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter frame - - + + - Start numbering at: + Image location: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + + + + Only Unique Frames Qt::Vertical 20 10 Qt::Vertical 20 10 KisFileNameRequester QWidget
kis_file_name_requester.h
1
cmbMimetype cmbRenderType