Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimationRender Namespace Reference

Functions

bool hasEvenDimensions (int width, int height)
 
bool mustHaveEvenDimensions (const QString &videoType, KisAnimationRenderingOptions::RenderMode renderMode)
 
KRITAUI_EXPORT bool render (KisDocument *doc, KisViewManager *viewManager, KisAnimationRenderingOptions encoderOptions)
 

Function Documentation

◆ hasEvenDimensions()

bool KisAnimationRender::hasEvenDimensions ( int width,
int height )

Definition at line 259 of file KisAnimationRender.cpp.

260{
261 return !((width & 0x1) || (height & 0x1));
262}

◆ mustHaveEvenDimensions()

bool KisAnimationRender::mustHaveEvenDimensions ( const QString & videoType,
KisAnimationRenderingOptions::RenderMode renderMode )

Definition at line 252 of file KisAnimationRender.cpp.

254{
256 && (looksLikeMp4(videoType) || looksLikeMatroska(videoType));
257}

References KisAnimationRenderingOptions::RENDER_FRAMES_ONLY.

◆ render()

bool KisAnimationRender::render ( KisDocument * doc,
KisViewManager * viewManager,
KisAnimationRenderingOptions encoderOptions )

Render an animation to file on disk. returns TRUE on success, FALSE on error or cancellation.

Definition at line 55 of file KisAnimationRender.cpp.

55 {
56 bool isTemporaryFramesDirectory = false;
57 QString framesDirectory;
58#ifdef Q_OS_ANDROID
59 // The user may cancel the dialog prompting them for a video file or a frames
60 // directory and we can't implicitly create them next to the document like on
61 // desktop due to file system restrictions. So if we don't get those paths
62 // here, we just bail out. The user knows they pressed cancel on the file
63 // dialog, so no message dialog is necessary.
64 if (encoderOptions.shouldEncodeVideo) {
65 if (encoderOptions.videoFileName.isEmpty()) {
66 return false;
67 }
68 } else if (encoderOptions.directory.isEmpty()) {
69 return false;
70 }
71
72 // Android uses weird content URIs instead of file paths and isn't allowed
73 // to scribble around in the file system without asking the user for access.
74 // We'll have to take the frames directory as it is given and if we don't
75 // get one then we'll create a temporary directory to stick our frames into.
76 std::unique_ptr<QTemporaryDir> tempDir;
77 if (encoderOptions.shouldEncodeVideo) {
78 tempDir = std::make_unique<QTemporaryDir>();
79 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(tempDir->isValid(), false);
80 framesDirectory = tempDir->path();
81 isTemporaryFramesDirectory = true;
82 } else {
83 framesDirectory = encoderOptions.directory;
84 }
85#else
86 framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory();
87#endif
88
89 const QString frameMimeType = encoderOptions.frameMimeType;
90 const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first();
91 const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory, encoderOptions.basename, extension);
92
93#ifdef Q_OS_ANDROID
94 QString videoType = encoderOptions.videoFormatKey;
95#else
96 QString videoType = encoderOptions.videoMimeType;
97#endif
98 if (mustHaveEvenDimensions(videoType, encoderOptions.renderMode())) {
99 if (hasEvenDimensions(encoderOptions.width, encoderOptions.height) != true) {
100 encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1);
101 encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1);
102 }
103 }
104
105 const QSize scaledSize = doc->image()->bounds().size().scaled(encoderOptions.width, encoderOptions.height, Qt::IgnoreAspectRatio);
106
107 if (mustHaveEvenDimensions(videoType, encoderOptions.renderMode())) {
108 if (hasEvenDimensions(scaledSize.width(), scaledSize.height()) != true) {
109 QString type = looksLikeMp4(videoType) ? "Mpeg4 (.mp4) " : "Matroska (.mkv) ";
110
111 qWarning() << type <<"requires width and height to be even, resize and try again!";
112 doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", type));
113 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage()));
114
115 return false;
116 }
117 }
118
119 const bool batchMode = false; // TODO: fetch correctly!
122 encoderOptions.lastFrame),
123 baseFileName,
124 encoderOptions.sequenceStart,
125 encoderOptions.wantsOnlyUniqueFrameSequence && !encoderOptions.shouldEncodeVideo,
126 encoderOptions.frameExportConfig);
127 exporter.setBatchMode(batchMode);
128
130 exporter.regenerateRange(viewManager->mainWindow()->viewManager());
131
132 bool delayReturnSuccess = (result == KisAsyncAnimationFramesSaveDialog::RenderComplete);
133
134 // the folder could have been read-only or something else could happen
135 if ((encoderOptions.shouldEncodeVideo || encoderOptions.wantsOnlyUniqueFrameSequence) &&
137
138 const QString savedFilesMask = exporter.savedFilesMask();
139
140 if (encoderOptions.shouldEncodeVideo) {
141 bool videoFileWriteAllowed = true;
142 // Android's weird file system doesn't work this way, the target
143 // file path is going to be a sandbox URL. Making it absolute,
144 // creating a directory above it or checking for its existence
145 // neither make sense nor are they necessary.
146#ifndef Q_OS_ANDROID
147 const QString videoOutputFilePath = encoderOptions.resolveAbsoluteVideoFilePath();
148 KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(videoOutputFilePath).isAbsolute());
149
150 const QFileInfo videoOutputFile(videoOutputFilePath);
151 QDir outputDir(videoOutputFile.absolutePath());
152
153 if (!outputDir.exists()) {
154 outputDir.mkpath(videoOutputFile.absolutePath());
155 }
156 KIS_SAFE_ASSERT_RECOVER_NOOP(outputDir.exists());
157
158 // If file exists at output path, prompt user for overwrite..
159 if (videoOutputFile.exists()) {
160 QMessageBox videoOverwritePrompt;
161
162 videoOverwritePrompt.setText(i18n("Overwrite existing video?"));
163 videoOverwritePrompt.setInformativeText(i18n("A file already exists at the path where you want to render your video [%1]... \n\
164 Are you sure you want to overwrite the existing file?", videoOutputFilePath));
165 videoOverwritePrompt.setStandardButtons(QMessageBox::Ok | QMessageBox::Abort);
166
167 videoFileWriteAllowed = videoOverwritePrompt.exec() == QMessageBox::Ok ? true : false;
168 }
169#endif
170
171 // Write the video..
172 if (videoFileWriteAllowed) {
174
175 // Let's not mess with the file on Android like this, it's slow
176 // and could cause weird behavior depending on the provider.
177 // We'll notice that the file can't be opened later anyway.
178#ifndef Q_OS_ANDROID
179 QFile videoFile(videoOutputFilePath);
180 if (!videoFile.open(QIODevice::WriteOnly)) {
181 qWarning() << "Could not open" << videoFile.fileName() << "for writing! Do you have permission to write to this file?";
182 exportResult = KisImportExportErrorCannotWrite(videoFile.error());
183 } else {
184 videoFile.close();
185 }
186#endif
187
188 if (exportResult.isOk()) {
189 QScopedPointer<KisAnimationVideoSaver> encoder(new KisAnimationVideoSaver(doc, batchMode));
190 exportResult = encoder->convert(doc,
191 framesDirectory,
192 savedFilesMask,
193 exporter.savedFiles(),
194 encoderOptions,
195 batchMode);
196 }
197
198 if (!exportResult.isOk()) {
199 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", exportResult.errorMessage()));
200
201 delayReturnSuccess = false; // Delay return to clean up exported frames.
202 }
203 }
204 }
205
206 //File cleanup
207 if (!isTemporaryFramesDirectory) {
208 QDir d(framesDirectory);
209
210#ifdef Q_OS_ANDROID
211 bool shouldDeleteSequence = false;
212#else
213 bool shouldDeleteSequence = encoderOptions.shouldDeleteSequence;
214#endif
215 if (shouldDeleteSequence || !delayReturnSuccess) {
216 QStringList savedFiles = exporter.savedFiles();
217
218 Q_FOREACH(const QString &f, savedFiles) {
219 if (d.exists(f)) {
220 d.remove(f);
221 }
222 }
223 } else if(encoderOptions.wantsOnlyUniqueFrameSequence) {
224 const QStringList fileNames = exporter.savedFiles();
225 const QStringList uniqueFrameNames = exporter.savedUniqueFiles();
226
227 Q_FOREACH(const QString &f, fileNames) {
228 if (!uniqueFrameNames.contains(f)) {
229 d.remove(f);
230 }
231 }
232 }
233
234 // We don't generate palette files on Android, that's done in memory.
235#ifndef Q_OS_ANDROID
236 QStringList paletteFiles = d.entryList(QStringList() << "KritaTempPalettegen_*.png", QDir::Files);
237
238 Q_FOREACH(const QString &f, paletteFiles) {
239 d.remove(f);
240 }
241#endif
242 }
244 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), "Animation frame rendering has timed out. Output files are incomplete.\nTry to increase \"Frame Rendering Timeout\" or reduce \"Frame Rendering Clones Limit\" in Krita settings");
246 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), i18n("Failed to render animation frames! Output files are incomplete."));
247 }
248
249 return delayReturnSuccess;
250}
QList< QString > QStringList
KisPropertiesConfigurationSP frameExportConfig
QString resolveAbsoluteVideoFilePath(const QString &documentPath) const
QString resolveAbsoluteFramesDirectory(const QString &documentPath) const
KisImageSP image
void setErrorMessage(const QString &errMsg)
QString errorMessage() const
QRect bounds() const override
KisViewManager * viewManager
static QStringList suffixesForMimeType(const QString &mimeType)
static KisTimeSpan fromTimeToTime(int start, int end)
KisMainWindow * mainWindow() const
Encoder * encoder(Imf::OutputFile &file, const ExrPaintLayerSaveInfo &info, int width)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
bool hasEvenDimensions(int width, int height)
bool mustHaveEvenDimensions(const QString &videoType, KisAnimationRenderingOptions::RenderMode renderMode)

References KisAnimationRenderingOptions::basename, KisImage::bounds(), KisAnimationRenderingOptions::directory, encoder(), KisDocument::errorMessage(), KisImportExportErrorCode::errorMessage(), KisAnimationRenderingOptions::firstFrame, KisAnimationRenderingOptions::frameExportConfig, KisAnimationRenderingOptions::frameMimeType, KisTimeSpan::fromTimeToTime(), hasEvenDimensions(), KisAnimationRenderingOptions::height, KisDocument::image, KisImportExportErrorCode::isOk(), KIS_SAFE_ASSERT_RECOVER_NOOP, KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, KisAnimationRenderingOptions::lastFrame, KisViewManager::mainWindow(), mustHaveEvenDimensions(), ImportExportCodes::OK, KisAsyncAnimationFramesSaveDialog::regenerateRange(), KisAsyncAnimationRenderDialogBase::RenderComplete, KisAsyncAnimationRenderDialogBase::RenderFailed, KisAnimationRenderingOptions::renderMode(), KisAsyncAnimationRenderDialogBase::RenderTimedOut, KisAnimationRenderingOptions::resolveAbsoluteFramesDirectory(), KisAnimationRenderingOptions::resolveAbsoluteVideoFilePath(), KisAsyncAnimationFramesSaveDialog::savedFiles(), KisAsyncAnimationFramesSaveDialog::savedFilesMask(), KisAsyncAnimationFramesSaveDialog::savedUniqueFiles(), KisAnimationRenderingOptions::sequenceStart, KisAsyncAnimationRenderDialogBase::setBatchMode(), KisDocument::setErrorMessage(), KisAnimationRenderingOptions::shouldDeleteSequence, KisAnimationRenderingOptions::shouldEncodeVideo, KisMimeDatabase::suffixesForMimeType(), KisAnimationRenderingOptions::videoFileName, KisAnimationRenderingOptions::videoMimeType, KisMainWindow::viewManager, KisAnimationRenderingOptions::wantsOnlyUniqueFrameSequence, and KisAnimationRenderingOptions::width.