| |
| |
|
|
| #include "toonz/multimediarenderer.h" |
| #include "toonz/movierenderer.h" |
| #include "trenderer.h" |
| |
| |
| #include "toonz/toonzscene.h" |
| #include "toonz/txsheet.h" |
| #include "toonz/fxdag.h" |
| #include "toonz/tcolumnfxset.h" |
| |
| |
| #include "toonz/scenefx.h" |
| #include "toonz/tcolumnfx.h" |
| |
| |
| #include <QEventLoop> |
| |
| |
| |
| |
| |
| namespace { |
| std::wstring removeSpaces(const std::wstring &str) { |
| std::wstring result; |
| std::wstring::size_type a = 0, b; |
| while ((b = str.find_first_of(L" ", a)) != std::wstring::npos) { |
| result += str.substr(a, b - a); |
| a = b + 1; |
| } |
| result += str.substr(a, std::wstring::npos); |
| return result; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| class MultimediaRenderer::Imp final : public MovieRenderer::Listener, |
| public TSmartObject { |
| public: |
| ToonzScene *m_scene; |
| TFilePath m_fp; |
| int m_threadCount; |
| bool m_cacheResults; |
| |
| |
| double m_xDpi, m_yDpi; |
| |
| TRenderSettings m_renderSettings; |
| |
| std::vector<MultimediaRenderer::Listener *> m_listeners; |
| |
| bool m_precomputingEnabled; |
| bool m_canceled; |
| |
| int m_currentFx; |
| set<double>::iterator m_currentFrame; |
| TRenderer *m_currentTRenderer; |
| |
| TFxSet m_fxsToRender; |
| set<double> m_framesToRender; |
| |
| QEventLoop m_eventLoop; |
| |
| int m_multimediaMode; |
| |
| Imp(ToonzScene *scene, const TFilePath &moviePath, int multimediaMode, |
| int threadCount, bool cacheResults); |
| |
| ~Imp(); |
| |
| void scanSceneForRenderNodes(); |
| void scanSceneForColumns(); |
| void scanSceneForLayers(); |
| bool scanColsRecursive(TFx *fx); |
| TColumnFx *searchColumn(TFxP fx); |
| TFxP addPostProcessing(TFxP fx, TFxP postProc); |
| void addPostProcessingRecursive(TFxP fx, TFxP postProc); |
| |
| void start(); |
| |
| bool onFrameCompleted(int frame) override; |
| bool onFrameFailed(int frame, TException &e) override; |
| void onSequenceCompleted(const TFilePath &fp) override; |
| void onRenderCompleted(); |
| }; |
| |
| |
| |
| MultimediaRenderer::Imp::Imp(ToonzScene *scene, const TFilePath &moviePath, |
| int multimediaMode, int threadCount, |
| bool cacheResults) |
| : m_scene(scene) |
| , m_fp(moviePath) |
| , m_threadCount(threadCount) |
| , m_cacheResults(cacheResults) |
| , m_xDpi(72) |
| , m_yDpi(72) |
| , m_renderSettings() |
| , m_listeners() |
| , m_precomputingEnabled(true) |
| , m_canceled(false) |
| , m_currentFx(0) |
| , m_currentFrame() |
| , m_multimediaMode(multimediaMode) { |
| |
| scanSceneForRenderNodes(); |
| } |
| |
| |
| |
| MultimediaRenderer::Imp::~Imp() {} |
| |
| |
| |
| void MultimediaRenderer::Imp::scanSceneForRenderNodes() { |
| switch (m_multimediaMode) { |
| case COLUMNS: |
| scanSceneForColumns(); |
| break; |
| case LAYERS: |
| scanSceneForLayers(); |
| break; |
| default: |
| assert(0); |
| } |
| } |
| |
| |
| |
| |
| |
| void MultimediaRenderer::Imp::scanSceneForColumns() { |
| |
| |
| TXsheet *xsh = m_scene->getXsheet(); |
| TFxSet *fxs = xsh->getFxDag()->getTerminalFxs(); |
| |
| |
| for (int i = 0; i < fxs->getFxCount(); ++i) { |
| TFx *fx = fxs->getFx(i); |
| if (!fx) continue; |
| bool isItARenderFx = scanColsRecursive(fx); |
| if (isItARenderFx) m_fxsToRender.addFx(fx); |
| } |
| } |
| |
| |
| |
| |
| bool MultimediaRenderer::Imp::scanColsRecursive(TFx *fx) { |
| |
| if (dynamic_cast<TColumnFx *>(fx)) return true; |
| |
| |
| bool isChildAnFxRepres; |
| for (int i = 0; i < fx->getInputPortCount(); ++i) { |
| TFx *childFx = fx->getInputPort(i)->getFx(); |
| if (!childFx) continue; |
| isChildAnFxRepres = scanColsRecursive(childFx); |
| if (isChildAnFxRepres && fx->getInputPortCount() > 1) |
| m_fxsToRender.addFx(childFx); |
| } |
| |
| if (isChildAnFxRepres && fx->getInputPortCount() == 1) return true; |
| return false; |
| } |
| |
| |
| |
| |
| |
| void MultimediaRenderer::Imp::scanSceneForLayers() { |
| |
| |
| TXsheet *xsh = m_scene->getXsheet(); |
| TFxSet *fxs = xsh->getFxDag()->getTerminalFxs(); |
| |
| |
| |
| for (int i = 0; i < fxs->getFxCount(); ++i) { |
| TFx *fx = fxs->getFx(i); |
| TFxPort *leftXSheetPort; |
| |
| retry: |
| |
| if (!fx) continue; |
| leftXSheetPort = fx->getXsheetPort(); |
| |
| if (!leftXSheetPort) { |
| m_fxsToRender.addFx(fx); |
| continue; |
| } |
| |
| |
| if (leftXSheetPort->isConnected()) |
| m_fxsToRender.addFx(fx); |
| else { |
| fx = fx->getInputPort(0)->getFx(); |
| goto retry; |
| } |
| } |
| } |
| |
| |
| |
| TColumnFx *MultimediaRenderer::Imp::searchColumn(TFxP fx) { |
| |
| TFx *currFx = fx.getPointer(); |
| TColumnFx *colFx = dynamic_cast<TColumnFx *>(currFx); |
| while (!colFx) { |
| if (fx->getInputPortCount() <= 0) break; |
| |
| currFx = currFx->getInputPort(0)->getFx(); |
| |
| if (!currFx) break; |
| colFx = dynamic_cast<TColumnFx *>(currFx); |
| } |
| |
| return colFx; |
| } |
| |
| |
| |
| TFxP MultimediaRenderer::Imp::addPostProcessing(TFxP fx, TFxP postProc) { |
| if (dynamic_cast<TXsheetFx *>(postProc.getPointer())) return fx; |
| |
| |
| postProc = postProc->clone(true); |
| addPostProcessingRecursive(fx, postProc); |
| |
| return postProc; |
| } |
| |
| |
| |
| void MultimediaRenderer::Imp::addPostProcessingRecursive(TFxP fx, |
| TFxP postProc) { |
| if (!postProc) return; |
| |
| int i, count = postProc->getInputPortCount(); |
| for (i = 0; i < count; ++i) { |
| TFxPort *port = postProc->getInputPort(i); |
| TFx *childFx = port->getFx(); |
| |
| if (dynamic_cast<TXsheetFx *>(childFx)) |
| port->setFx(fx.getPointer()); |
| else |
| addPostProcessingRecursive(fx, childFx); |
| } |
| } |
| |
| |
| |
| void MultimediaRenderer::Imp::start() { |
| |
| double stretchTo = m_renderSettings.m_timeStretchTo; |
| double stretchFrom = m_renderSettings.m_timeStretchFrom; |
| |
| double timeStretchFactor = stretchFrom / stretchTo; |
| bool fieldRendering = |
| m_renderSettings.m_fieldPrevalence != TRenderSettings::NoField; |
| |
| std::wstring modeStr; |
| switch (m_multimediaMode) { |
| case COLUMNS: |
| modeStr = L"_col"; |
| break; |
| case LAYERS: |
| modeStr = L"_lay"; |
| break; |
| default: |
| assert(0); |
| } |
| |
| |
| std::vector<TFxP> postFxs(m_framesToRender.size()); |
| |
| std::set<double>::iterator jt; |
| int j; |
| for (j = 0, jt = m_framesToRender.begin(); jt != m_framesToRender.end(); |
| ++j, ++jt) |
| postFxs[j] = |
| buildPostSceneFx(m_scene, *jt, m_renderSettings.m_shrinkX, |
| false); |
| |
| |
| int i, count = m_fxsToRender.getFxCount(); |
| for (i = 0; i < count; ++i) { |
| |
| if (m_canceled) return; |
| |
| |
| std::vector<std::pair<double, TFxPair>> pairsToBeRendered; |
| |
| |
| |
| |
| |
| |
| const BSFX_Transforms_Enum transforms = |
| BSFX_Transforms_Enum(BSFX_COLUMN_TR); |
| |
| int j; |
| for (j = 0, jt = m_framesToRender.begin(); jt != m_framesToRender.end(); |
| ++j, ++jt) { |
| TFxPair fx; |
| |
| if (m_renderSettings.m_stereoscopic) |
| m_scene->shiftCameraX(-m_renderSettings.m_stereoscopicShift / 2); |
| |
| fx.m_frameA = |
| buildSceneFx(m_scene, *jt, 0, m_fxsToRender.getFx(i), transforms); |
| |
| if (m_renderSettings.m_stereoscopic) { |
| m_scene->shiftCameraX(m_renderSettings.m_stereoscopicShift); |
| fx.m_frameB = |
| buildSceneFx(m_scene, *jt, 0, m_fxsToRender.getFx(i), transforms); |
| m_scene->shiftCameraX(-m_renderSettings.m_stereoscopicShift / 2); |
| } else if (fieldRendering) |
| fx.m_frameB = buildSceneFx(m_scene, *jt + 0.5 * timeStretchFactor, 0, |
| m_fxsToRender.getFx(i), transforms); |
| else |
| fx.m_frameB = TRasterFxP(); |
| |
| |
| if (fx.m_frameA) fx.m_frameA = addPostProcessing(fx.m_frameA, postFxs[j]); |
| if (fx.m_frameB) fx.m_frameB = addPostProcessing(fx.m_frameB, postFxs[j]); |
| |
| if (fx.m_frameA) |
| |
| pairsToBeRendered.push_back(std::pair<double, TFxPair>(*jt, fx)); |
| } |
| |
| if (pairsToBeRendered.size() == 0) |
| continue; |
| |
| |
| TColumnFx *colFx = searchColumn(m_fxsToRender.getFx(i)); |
| TFx *currFx = m_fxsToRender.getFx(i); |
| assert(colFx); |
| if (!colFx) continue; |
| |
| int columnIndex = colFx->getColumnIndex(); |
| std::wstring columnName(colFx->getColumnName()); |
| std::wstring columnId(colFx->getColumnId()); |
| std::wstring fxName(currFx->getName()); |
| std::wstring fxNameNoSpaces(::removeSpaces(fxName)); |
| std::wstring fxId(currFx->getFxId()); |
| |
| std::wstring fpName = |
| m_fp.getWideName() + L"_" + columnName + |
| (columnId == columnName ? L"" : L"(" + columnId + L")") + |
| (fxId.empty() |
| ? L"" |
| : L"_" + fxName + |
| (fxId == fxNameNoSpaces ? L"" : L"(" + fxId + L")")); |
| TFilePath movieFp(m_fp.withName(fpName)); |
| |
| |
| MovieRenderer movieRenderer(m_scene, movieFp, m_threadCount, false); |
| movieRenderer.setRenderSettings(m_renderSettings); |
| movieRenderer.setDpi(m_xDpi, m_yDpi); |
| movieRenderer.enablePrecomputing(m_precomputingEnabled); |
| movieRenderer.addListener(this); |
| |
| for (unsigned int j = 0; j < pairsToBeRendered.size(); ++j) { |
| std::pair<double, TFxPair> ¤tPair = pairsToBeRendered[j]; |
| movieRenderer.addFrame(currentPair.first, currentPair.second); |
| } |
| |
| |
| m_currentFx = i; |
| m_currentFrame = m_framesToRender.begin(); |
| m_currentTRenderer = movieRenderer.getTRenderer(); |
| |
| movieRenderer.start(); |
| |
| |
| |
| |
| |
| |
| |
| |
| m_eventLoop.exec(); |
| |
| |
| } |
| |
| |
| onRenderCompleted(); |
| } |
| |
| |
| |
| bool MultimediaRenderer::Imp::onFrameCompleted(int frame) { |
| |
| for (unsigned int i = 0; i < m_listeners.size(); ++i) |
| m_listeners[i]->onFrameCompleted(*m_currentFrame, m_currentFx); |
| |
| m_currentFrame++; |
| return !m_canceled; |
| } |
| |
| |
| |
| bool MultimediaRenderer::Imp::onFrameFailed(int frame, TException &e) { |
| |
| for (unsigned int i = 0; i < m_listeners.size(); ++i) |
| m_listeners[i]->onFrameFailed(*m_currentFrame, m_currentFx, e); |
| |
| m_currentFrame++; |
| return !m_canceled; |
| } |
| |
| |
| |
| void MultimediaRenderer::Imp::onSequenceCompleted(const TFilePath &fp) { |
| |
| m_currentTRenderer = 0; |
| |
| for (unsigned int i = 0; i < m_listeners.size(); ++i) |
| m_listeners[i]->onSequenceCompleted(m_currentFx); |
| |
| m_eventLoop.quit(); |
| } |
| |
| |
| |
| void MultimediaRenderer::Imp::onRenderCompleted() { |
| for (unsigned int i = 0; i < m_listeners.size(); ++i) |
| m_listeners[i]->onRenderCompleted(); |
| } |
| |
| |
| |
| |
| |
| |
| |
| MultimediaRenderer::MultimediaRenderer(ToonzScene *scene, |
| const TFilePath &moviePath, |
| int multimediaMode, int threadCount, |
| bool cacheResults) |
| : m_imp(new Imp(scene, moviePath, multimediaMode, threadCount, |
| cacheResults)) { |
| m_imp->addRef(); |
| } |
| |
| |
| |
| MultimediaRenderer::~MultimediaRenderer() { m_imp->release(); } |
| |
| |
| |
| const TFilePath &MultimediaRenderer::getFilePath() { return m_imp->m_fp; } |
| |
| |
| |
| int MultimediaRenderer::getFrameCount() { |
| return m_imp->m_framesToRender.size(); |
| } |
| |
| |
| |
| int MultimediaRenderer::getColumnsCount() { |
| return m_imp->m_fxsToRender.getFxCount(); |
| } |
| |
| |
| |
| int MultimediaRenderer::getMultimediaMode() const { |
| return m_imp->m_multimediaMode; |
| } |
| |
| |
| |
| void MultimediaRenderer::setRenderSettings( |
| const TRenderSettings &renderSettings) { |
| |
| m_imp->m_renderSettings = renderSettings; |
| } |
| |
| |
| |
| void MultimediaRenderer::setDpi(double xDpi, double yDpi) { |
| m_imp->m_xDpi = xDpi; |
| m_imp->m_yDpi = yDpi; |
| } |
| |
| |
| |
| void MultimediaRenderer::addListener(Listener *listener) { |
| m_imp->m_listeners.push_back(listener); |
| } |
| |
| |
| |
| void MultimediaRenderer::addFrame(double frame) { |
| m_imp->m_framesToRender.insert(frame); |
| } |
| |
| |
| |
| void MultimediaRenderer::enablePrecomputing(bool on) { |
| m_imp->m_precomputingEnabled = on; |
| } |
| |
| |
| |
| |
| |
| |
| TRenderer *MultimediaRenderer::getTRenderer() { |
| return m_imp->m_currentTRenderer; |
| } |
| |
| |
| |
| bool MultimediaRenderer::isPrecomputingEnabled() const { |
| return m_imp->m_precomputingEnabled; |
| } |
| |
| |
| |
| void MultimediaRenderer::start() { m_imp->start(); } |
| |
| |
| |
| void MultimediaRenderer::onCanceled() { m_imp->m_canceled = true; } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |