bool LayerCamera::addNewKeyFrameAt( int frameNumber ) { QMatrix viewMatrix = getViewAtFrame( frameNumber ); Camera* pCamera = new Camera( viewMatrix ); pCamera->setPos( frameNumber ); return addKeyFrame( frameNumber, pCamera ); }
bool LayerCamera::addNewKeyAt( int frameNumber ) { QTransform view = getViewAtFrame( frameNumber ); Camera* pCamera = new Camera( view ); pCamera->setPos( frameNumber ); return addKeyFrame( frameNumber, pCamera ); }
Status MovieExporter::generateImageSequence( const Object* obj, std::function<void(float)> progress ) { int frameStart = mDesc.startFrame; int frameEnd = mDesc.endFrame; QSize exportSize = mDesc.exportSize; bool transparency = false; QString strCameraName = mDesc.strCameraName; auto cameraLayer = (LayerCamera*)obj->findLayerByName( strCameraName, Layer::CAMERA ); if ( cameraLayer == nullptr ) { cameraLayer = obj->getLayersByType< LayerCamera >().front(); } for ( int currentFrame = frameStart; currentFrame <= frameEnd; currentFrame++ ) { if ( mCanceled ) { return Status::CANCELED; } QImage imageToExport( exportSize, QImage::Format_ARGB32_Premultiplied ); QColor bgColor = Qt::white; if ( transparency ) { bgColor.setAlpha( 0 ); } imageToExport.fill( bgColor ); QPainter painter( &imageToExport ); QTransform view = cameraLayer->getViewAtFrame( currentFrame ); QSize camSize = cameraLayer->getViewSize(); QTransform centralizeCamera; centralizeCamera.translate( camSize.width() / 2, camSize.height() / 2 ); painter.setWorldTransform( view * centralizeCamera ); painter.setWindow( QRect( 0, 0, camSize.width(), camSize.height() ) ); obj->paintImage( painter, currentFrame, false, true ); QString imageFileWithFrameNumber = QString().sprintf( IMAGE_FILENAME, currentFrame ); QString strImgPath = mTempWorkDir + imageFileWithFrameNumber; bool bSave = imageToExport.save( strImgPath ); Q_ASSERT( bSave ); qDebug() << "Save img to: " << strImgPath; float fProgressValue = ( currentFrame / (float)( frameEnd - frameStart ) ); progress( 0.1f + ( fProgressValue * 0.99f ) ); } return Status::OK; }
bool LayerCamera::addImageAtFrame(int frameNumber) { int index = getIndexAtFrame(frameNumber); if(index == -1) { //framesImage.append(new QImage(imageSize, QImage::Format_ARGB32_Premultiplied)); Camera* camera = new Camera(); camera->view = getViewAtFrame(frameNumber); framesCamera.append(camera); framesPosition.append(frameNumber); framesSelected.append(false); framesFilename.append(""); framesModified.append(false); bubbleSort(); int frameNumber1 = frameNumber; int frameNumber2 = frameNumber; if(index>0) frameNumber1 = framesPosition.at(index-1); if(index<framesPosition.size()-1) frameNumber1 = framesPosition.at(index+1); emit imageAdded(frameNumber1, frameNumber2); return true; } else { return false; } }
/** Exports obj to a gif image at strOut using FFmpeg. * * @param[in] obj An Object containing the animation to export. * @param[in] ffmpegPath The path to the FFmpeg binary. * @param[in] strOut The output path. Should end with .gif. * @param[out] progress A function that takes one float argument * (the percentage of the gif generation complete) and * may display the output to the user in any way it * sees fit. * * @return Returns the final status of the operation (ok or canceled) */ Status MovieExporter::generateGif( const Object* obj, QString ffmpegPath, QString strOut, std::function<void(float)> progress) { if (mCanceled) { return Status::CANCELED; } // Frame generation setup int frameStart = mDesc.startFrame; int frameEnd = mDesc.endFrame; const QSize exportSize = mDesc.exportSize; bool transparency = false; QString strCameraName = mDesc.strCameraName; bool loop = mDesc.loop; int bytesWritten; auto cameraLayer = static_cast<LayerCamera*>(obj->findLayerByName(strCameraName, Layer::CAMERA)); if (cameraLayer == nullptr) { cameraLayer = obj->getLayersByType< LayerCamera >().front(); } int currentFrame = frameStart; /* We create an image with the correct dimensions and background * color here and then copy this and draw over top of it to * generate each frame. This is faster than having to generate * a new background image for each frame. */ QImage imageToExportBase(exportSize, QImage::Format_ARGB32_Premultiplied); QColor bgColor = Qt::white; if (transparency) { bgColor.setAlpha(0); } imageToExportBase.fill(bgColor); QSize camSize = cameraLayer->getViewSize(); QTransform centralizeCamera; centralizeCamera.translate(camSize.width() / 2, camSize.height() / 2); // Build FFmpeg command QString strCmd = QString("\"%1\"").arg(ffmpegPath); strCmd += QString(" -f rawvideo -pixel_format bgra"); strCmd += QString(" -video_size %1x%2").arg(exportSize.width()).arg(exportSize.height()); strCmd += QString(" -framerate %1").arg(mDesc.fps); strCmd += " -i -"; strCmd += " -y"; strCmd += " -filter_complex \"[0:v]palettegen [p]; [0:v][p] paletteuse\""; strCmd += QString(" -loop %1").arg(loop ? "0" : "-1"); strCmd += QString(" \"%1\"").arg(strOut); // Run FFmpeg command STATUS_CHECK(executeFFMpegPipe(strCmd, progress, [&](QProcess& ffmpeg, int framesProcessed) { /* The GIF FFmpeg command requires the entires stream to be * written before FFmpeg can encode the GIF. This is because * the generated pallete is based off of the colors in all * frames. The only way to avoid this would be to generate * all the frames twice and run two separate commands, which * would likely have unacceptable speed costs. */ Q_UNUSED(framesProcessed); if(currentFrame > frameEnd) { ffmpeg.closeWriteChannel(); return false; } QImage imageToExport = imageToExportBase.copy(); QPainter painter(&imageToExport); QTransform view = cameraLayer->getViewAtFrame(currentFrame); painter.setWorldTransform(view * centralizeCamera); painter.setWindow(QRect(0, 0, camSize.width(), camSize.height())); obj->paintImage(painter, currentFrame, false, true); bytesWritten = ffmpeg.write(reinterpret_cast<const char*>(imageToExport.constBits()), imageToExport.byteCount()); Q_ASSERT(bytesWritten == imageToExport.byteCount()); currentFrame++; return true; })); return Status::OK; }
/** Exports obj to a movie image at strOut using FFmpeg. * * @param[in] obj An Object containing the animation to export. * @param[in] ffmpegPath The path to the FFmpeg binary. * @param[in] strOutputFile The output path. Should end with .gif. * @param[out] progress A function that takes one float argument * (the percentage of the gif generation complete) and * may display the output to the user in any way it * sees fit. * * The movie formats supported by this operation are any file * formats that the referenced FFmpeg binary supports and that have * the required features (ex. video and audio support) * * @return Returns the final status of the operation (ok or canceled) */ Status MovieExporter::generateMovie( const Object* obj, QString ffmpegPath, QString strOutputFile, std::function<void(float)> progress) { if (mCanceled) { return Status::CANCELED; } // Frame generation setup int frameStart = mDesc.startFrame; int frameEnd = mDesc.endFrame; const QSize exportSize = mDesc.exportSize; bool transparency = mDesc.alpha; QString strCameraName = mDesc.strCameraName; bool loop = mDesc.loop; auto cameraLayer = static_cast<LayerCamera*>(obj->findLayerByName(strCameraName, Layer::CAMERA)); if (cameraLayer == nullptr) { cameraLayer = obj->getLayersByType< LayerCamera >().front(); } int currentFrame = frameStart; /* We create an image with the correct dimensions and background * color here and then copy this and draw over top of it to * generate each frame. This is faster than having to generate * a new background image for each frame. */ QImage imageToExportBase(exportSize, QImage::Format_ARGB32_Premultiplied); QColor bgColor = Qt::white; if (transparency) { bgColor.setAlpha(0); } imageToExportBase.fill(bgColor); QSize camSize = cameraLayer->getViewSize(); QTransform centralizeCamera; centralizeCamera.translate(camSize.width() / 2, camSize.height() / 2); int failCounter = 0; /* Movie export uses a "sliding window" to reduce memory usage * while having a relatively small impact on speed. This basically * means that there is a maximum number of frames that can be waiting * to be encoded by ffmpeg at any one time. The limit is set by the * frameWindow variable which is designed to take up a maximum of * about 1GB of memory */ int frameWindow = static_cast<int>(1e9 / (camSize.width() * camSize.height() * 4.0)); // Build FFmpeg command //int exportFps = mDesc.videoFps; const QString tempAudioPath = mTempWorkDir + "/tmpaudio.wav"; QString strCmd = QString("\"%1\"").arg(ffmpegPath); strCmd += QString(" -f rawvideo -pixel_format bgra"); strCmd += QString(" -video_size %1x%2").arg(exportSize.width()).arg(exportSize.height()); strCmd += QString(" -framerate %1").arg(mDesc.fps); //strCmd += QString( " -r %1").arg( exportFps ); strCmd += QString(" -i -"); strCmd += QString(" -threads %1").arg(QThread::idealThreadCount() == 1 ? 0 : QThread::idealThreadCount()); if (QFile::exists(tempAudioPath)) { strCmd += QString(" -i \"%1\" ").arg(tempAudioPath); } if (strOutputFile.endsWith(".apng", Qt::CaseInsensitive)) { strCmd += QString(" -plays %1").arg(loop ? "0" : "1"); } if (strOutputFile.endsWith("mp4", Qt::CaseInsensitive)) { strCmd += QString(" -pix_fmt yuv420p"); } if (strOutputFile.endsWith(".avi", Qt::CaseInsensitive)) { strCmd += " -q:v 5"; } strCmd += " -y"; strCmd += QString(" \"%1\"").arg(strOutputFile); // Run FFmpeg command STATUS_CHECK(executeFFMpegPipe(strCmd, progress, [&](QProcess& ffmpeg, int framesProcessed) { if(framesProcessed < 0) { failCounter++; } if(currentFrame > frameEnd) { ffmpeg.closeWriteChannel(); return false; } if((currentFrame - frameStart <= framesProcessed + frameWindow || failCounter > 10) && currentFrame <= frameEnd) { QImage imageToExport = imageToExportBase.copy(); QPainter painter(&imageToExport); QTransform view = cameraLayer->getViewAtFrame(currentFrame); painter.setWorldTransform(view * centralizeCamera); painter.setWindow(QRect(0, 0, camSize.width(), camSize.height())); obj->paintImage(painter, currentFrame, false, true); painter.end(); // Should use sizeInBytes instead of byteCount to support large images, // but this is only supported in QT 5.10+ int bytesWritten = ffmpeg.write(reinterpret_cast<const char*>(imageToExport.constBits()), imageToExport.byteCount()); Q_ASSERT(bytesWritten == imageToExport.byteCount()); currentFrame++; failCounter = 0; return true; } return false; })); return Status::OK; }