ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
qAnimationDlg.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "qAnimationDlg.h"
9 
10 // Local
11 #include "ViewInterpolate.h"
12 
13 // CV_DB_LIB
14 #include <CVTools.h>
15 #include <ecv2DViewportObject.h>
16 #include <ecvDisplayTools.h>
17 #include <ecvMesh.h>
18 #include <ecvPointCloud.h>
19 #include <ecvPolyline.h>
20 
21 // Qt
22 #include <QApplication>
23 #include <QElapsedTimer>
24 #include <QFileDialog>
25 #include <QFileInfo>
26 #include <QMessageBox>
27 #include <QProgressDialog>
28 #include <QSettings>
29 #include <QtGui>
30 
31 // standard includes
32 #include <iomanip>
33 #include <vector>
34 
35 #ifdef QFFMPEG_SUPPORT
36 // QTFFmpeg
37 #include <QVideoEncoder.h>
38 #endif
39 
40 // System
41 #include <algorithm>
42 #if defined(CV_WINDOWS)
43 #include "windows.h"
44 #else
45 #include <unistd.h>
46 #endif
47 
48 static const QString s_stepDurationKey("StepDurationSec");
49 static const QString s_stepEnabledKey("StepEnabled");
50 
51 QStringList getImageList(const QString& path) {
52  QDir dir(path);
53  if (!dir.exists()) {
54  return QStringList();
55  }
56  dir.setFilter(QDir::Files | QDir::NoSymLinks);
57 
58  QStringList filters;
59  // we grab the list of supported image file formats (reading)
60  QList<QByteArray> formats = QImageReader::supportedImageFormats();
61  if (formats.empty()) {
62  filters << "*.bmp"
63  << "*.png"
64  << "*.jpg";
65  } else {
66  // we convert this list into a proper "filters" string
67  for (int i = 0; i < formats.size(); ++i) {
68  QString filter = QString("*.%1").arg(formats[i].data());
69  filters.append(filter);
70  }
71  }
72 
73  dir.setNameFilters(filters);
74  QFileInfoList infoList = dir.entryInfoList();
75  QStringList filesList;
76  for (size_t i = 0; i < infoList.size(); i++) {
77  filesList.append(infoList.at(i).absoluteFilePath());
78  }
79  return filesList;
80 }
81 
82 qAnimationDlg::qAnimationDlg(QWidget* view3d, QWidget* parent)
83  : QDialog(parent, Qt::Tool), Ui::AnimationDialog(), m_view3d(view3d) {
84  setupUi(this);
85 
86  // restore previous settings
87  QString defaultOutputFormat;
88  {
89  QSettings settings;
90  settings.beginGroup("qAnimation");
91 
92  // last filename
93  {
94  QString defaultDir;
95 #ifdef _MSC_VER
96  defaultDir = QApplication::applicationDirPath();
97 #else
98  defaultDir = QDir::homePath();
99 #endif
100  const QString defaultFileName(defaultDir + "/animation.mp4");
101  QString lastFilename =
102  settings.value("filename", defaultFileName).toString();
103  QString lastTexturePath =
104  settings.value("texturesPath", defaultDir).toString();
105 #ifndef QFFMPEG_SUPPORT
106  lastFilename = QFileInfo(lastFilename).absolutePath();
107 #endif
108  outputFileLineEdit->setText(lastFilename);
109  inputTexturesPathLineEdit->setText(lastTexturePath);
110  textureNumLabel->setText(
111  QString("Textures num: %1")
112  .arg(getImageList(lastTexturePath).size()));
113  }
114 
115  // other parameters
116  {
117  bool startPreviewFromSelectedStep =
118  settings.value("previewFromSelected",
119  previewFromSelectedCheckBox->isChecked())
120  .toBool();
121  bool updateTextures =
122  settings.value("updateTextures",
123  updateTexturesCheckBox->isChecked())
124  .toBool();
125  bool loop =
126  settings.value("loop", loopCheckBox->isChecked()).toBool();
127  int frameRate =
128  settings.value("frameRate", fpsSpinBox->value()).toInt();
129  int superRes =
130  settings.value("superRes", superResolutionSpinBox->value())
131  .toInt();
132  int renderingMode =
133  settings.value("renderingMode",
134  renderingModeComboBox->currentIndex())
135  .toInt();
136  int bitRate =
137  settings.value("bitRate", bitrateSpinBox->value()).toInt();
138  bool autoStepDuration =
139  settings.value("autoStepDuration",
140  autoStepDurationCheckBox->isChecked())
141  .toBool();
142  bool smoothTrajectory =
143  settings.value("smoothTrajectory",
144  smoothTrajectoryGroupBox->isChecked())
145  .toBool();
146  double smoothRatio =
147  settings.value("smoothRatio",
148  smoothRatioDoubleSpinBox->value())
149  .toDouble();
150  defaultOutputFormat = settings.value("outputFormat").toString();
151 
152  previewFromSelectedCheckBox->setChecked(
153  startPreviewFromSelectedStep);
154  updateTexturesCheckBox->setChecked(updateTextures);
155  loopCheckBox->setChecked(loop);
156  fpsSpinBox->setValue(frameRate);
157  superResolutionSpinBox->setValue(superRes);
158  renderingModeComboBox->setCurrentIndex(renderingMode);
159  bitrateSpinBox->setValue(bitRate);
160  autoStepDurationCheckBox->setChecked(
161  autoStepDuration); // this might be modified when init will
162  // be called!
163  smoothTrajectoryGroupBox->setChecked(smoothTrajectory);
164  smoothRatioDoubleSpinBox->setValue(smoothRatio);
165  }
166 
167  settings.endGroup();
168  }
169 
170  // populate the output format combo-box
171  {
172  outputFormatComboBox->addItem("Auto", QVariant(QString()));
173 #ifdef QFFMPEG_SUPPORT
174  std::vector<QVideoEncoder::OutputFormat> formats;
175  if (QVideoEncoder::GetSupportedOutputFormats(formats, true)) {
176  int defaultIndex = 0;
177  for (const QVideoEncoder::OutputFormat& f : formats) {
178  QString title = f.longName;
179  if (!f.extensions.isEmpty()) {
180  title += "[" + f.extensions + "]";
181  static const int s_maxTitleLength = 48;
182  if (title.size() > s_maxTitleLength)
183  title = title.left(s_maxTitleLength - 3) + "...";
184  }
185  outputFormatComboBox->addItem(title, QVariant(f.shortName));
186  if (defaultIndex == 0 && !defaultOutputFormat.isEmpty() &&
187  defaultOutputFormat == f.shortName) {
188  defaultIndex = outputFormatComboBox->count() - 1;
189  }
190  }
191  outputFormatComboBox->setCurrentIndex(defaultIndex);
192  }
193 #endif
194  }
195 
196  connect(autoStepDurationCheckBox, &QAbstractButton::toggled, this,
198  connect(smoothTrajectoryGroupBox, &QGroupBox::toggled, this,
200  connect(smoothRatioDoubleSpinBox,
201  static_cast<void (QDoubleSpinBox::*)(double)>(
202  &QDoubleSpinBox::valueChanged),
204 
205  connect(fpsSpinBox,
206  static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
208  connect(totalTimeDoubleSpinBox,
209  static_cast<void (QDoubleSpinBox::*)(double)>(
210  &QDoubleSpinBox::valueChanged),
212  connect(stepTimeDoubleSpinBox,
213  static_cast<void (QDoubleSpinBox::*)(double)>(
214  &QDoubleSpinBox::valueChanged),
216  connect(loopCheckBox, &QAbstractButton::toggled, this,
218 
219  connect(browseButton, &QAbstractButton::clicked, this,
221  connect(browseTextureButton, &QAbstractButton::clicked, this,
223 
224  connect(previewButton, &QAbstractButton::clicked, this,
226  connect(renderButton, &QAbstractButton::clicked, this,
228  connect(exportFramesPushButton, &QAbstractButton::clicked, this,
230  connect(buttonBox, &QDialogButtonBox::accepted, this,
232  connect(buttonBox, &QDialogButtonBox::rejected, this,
234 }
235 
237 
239  return smoothTrajectoryGroupBox->isChecked() && !m_smoothVideoSteps.empty();
240 }
241 
243  return updateTexturesCheckBox->isChecked() && !m_mesh_list.empty();
244 }
245 
246 bool qAnimationDlg::init(const std::vector<cc2DViewportObject*>& viewports,
247  const std::vector<ccMesh*>& meshes) {
248  if (viewports.size() < 2 && meshes.empty()) {
249  assert(false);
250  return false;
251  }
252 
253  try {
254  m_videoSteps.resize(viewports.size());
255  m_mesh_list.resize(meshes.size());
256  } catch (const std::bad_alloc&) {
257  // not enough memory
258  return false;
259  }
260 
261  ccBBox visibleObjectsBBox;
262  ecvDisplayTools::GetVisibleObjectsBB(visibleObjectsBBox);
263 
264  for (size_t i = 0; i < viewports.size(); ++i) {
265  cc2DViewportObject* vp = viewports[i];
266 
267  // check if the (1st) viewport has a duration in meta data (from a
268  // previous run)
269  double duration_sec = 2.0;
270  if (vp->hasMetaData(s_stepDurationKey)) {
271  duration_sec = vp->getMetaData(s_stepDurationKey).toDouble();
272  // disable "auto step duration"
273  autoStepDurationCheckBox->blockSignals(true);
274  autoStepDurationCheckBox->setChecked(false);
275  autoStepDurationCheckBox->blockSignals(false);
276  }
277  bool isChecked = true;
278  if (vp->hasMetaData(s_stepEnabledKey)) {
279  isChecked = vp->getMetaData(s_stepEnabledKey).toBool();
280  }
281 
282  QString itemName = QString("step %1 (%2)")
283  .arg(QString::number(i + 1), vp->getName());
284  QListWidgetItem* item =
285  new QListWidgetItem(itemName, stepSelectionList);
286  item->setFlags(item->flags() |
287  Qt::ItemIsUserCheckable); // set checkable flag
288  item->setCheckState(isChecked
289  ? Qt::Checked
290  : Qt::Unchecked); // initialize check state
291  stepSelectionList->addItem(item);
292 
293  m_videoSteps[i].viewport = vp;
294  m_videoSteps[i].viewportParams = vp->getParameters();
295  m_videoSteps[i].duration_sec = duration_sec;
296  m_videoSteps[i].indexInOriginalTrajectory = static_cast<int>(i);
297 
298  // compute the real camera center
299  ccGLMatrixd viewMat = vp->getParameters().computeViewMatrix();
300  m_videoSteps[i].cameraCenter =
301  viewMat.inverse().getTranslationAsVec3D();
302  }
303 
304  for (std::size_t i = 0; i < meshes.size(); ++i) {
305  m_mesh_list[i] = meshes[i];
306  }
307 
308  // manually trigger some actions if necessary
309 
310  updateCameraTrajectory(); // also takes care of the smooth version!
311 
312  connect(stepSelectionList, &QListWidget::currentRowChanged, this,
314  connect(stepSelectionList, &QListWidget::itemChanged, this,
316 
317  stepSelectionList->setCurrentRow(0); // select the first one by default
319 
320  return true;
321 }
322 
324 
326  assert(stepSelectionList->count() >= m_videoSteps.size());
327  for (size_t i = 0; i < m_videoSteps.size(); ++i) {
328  cc2DViewportObject* vp = m_videoSteps[i].viewport;
329 
330  // save the step duration as meta data
331  if (!autoStepDurationCheckBox->isChecked()) {
332  vp->setMetaData(s_stepDurationKey, m_videoSteps[i].duration_sec);
333  }
334  // save whether the step is enabled or not as meta data
335  vp->setMetaData(
337  (stepSelectionList->item(static_cast<int>(i))->checkState() ==
338  Qt::Checked));
339  }
340 
341  // store settings
342  {
343  QSettings settings;
344  settings.beginGroup("qAnimation");
345  settings.setValue("previewFromSelected",
346  previewFromSelectedCheckBox->isChecked());
347  settings.setValue("updateTextures",
348  updateTexturesCheckBox->isChecked());
349  settings.setValue("loop", loopCheckBox->isChecked());
350  settings.setValue("frameRate", fpsSpinBox->value());
351  settings.setValue("renderingMode",
352  renderingModeComboBox->currentIndex());
353  settings.setValue("superRes", superResolutionSpinBox->value());
354  settings.setValue("bitRate", bitrateSpinBox->value());
355  settings.setValue("autoStepDuration",
356  autoStepDurationCheckBox->isChecked());
357  settings.setValue("smoothTrajectory",
358  smoothTrajectoryGroupBox->isChecked());
359  settings.setValue("smoothRatio", smoothRatioDoubleSpinBox->value());
360  settings.setValue("outputFormat",
361  outputFormatComboBox->currentData().toString());
362 
363  settings.endGroup();
364  }
365 }
366 
368  if (m_videoSteps.empty()) return false;
369  m_smoothVideoSteps.clear();
370  for (Step& step : m_videoSteps) {
371  step.indexInSmoothTrajectory = -1;
372  step.length = 0;
373  }
374 
375  if (m_videoSteps.size() < 2) {
376  CVLog::Warning("Not enough animation steps");
378  return false;
379  }
380 
381  // update the segment lengths
382  size_t vp1Index = 0, vp2Index = 0;
383  while (getNextSegment(vp1Index, vp2Index)) {
384  assert(vp1Index < stepSelectionList->count());
385  Step& step1 = m_videoSteps[vp1Index];
386 
387  step1.length = (m_videoSteps[vp2Index].cameraCenter -
388  m_videoSteps[vp1Index].cameraCenter)
389  .norm();
390 
391  if (vp2Index < vp1Index) {
392  // loop mode
393  break;
394  }
395  vp1Index = vp2Index;
396  }
397 
398  bool result = true;
399  if (smoothTrajectoryGroupBox->isChecked()) {
401  }
402 
403  if (autoStepDurationCheckBox->isChecked()) {
405  } else {
407  }
408 
409  return result;
410 }
411 
413  if (m_videoSteps.empty()) return;
414  bool smoothMode = smoothModeEnabled();
415  if (!smoothMode) {
416  return;
417  }
418 
419  size_t vp1Index = 0, vp2Index = 0;
420  while (getNextSegment(vp1Index, vp2Index)) {
421  assert(vp1Index < stepSelectionList->count());
422  Step& step1 = m_videoSteps[vp1Index];
423  const Step& step2 = m_videoSteps[vp2Index];
424 
425  int i1Smooth = step1.indexInSmoothTrajectory;
426  int i2Smooth = step2.indexInSmoothTrajectory;
427  if (i1Smooth < 0 || i2Smooth < 0) {
428  assert(false);
429  continue;
430  }
431  if (i2Smooth < i1Smooth) {
432  // loop mode
433  i2Smooth += static_cast<int>(m_smoothVideoSteps.size());
434  }
435 
436  double length = 0;
437  for (int i = i1Smooth; i < i2Smooth; ++i) {
438  const Step& s = m_smoothVideoSteps[static_cast<size_t>(i) %
439  m_smoothVideoSteps.size()];
440  length += s.length;
441  }
442 
444  for (int i = i1Smooth; i < i2Smooth; ++i) {
445  Step& s = m_smoothVideoSteps[static_cast<size_t>(i) %
446  m_smoothVideoSteps.size()];
447  s.duration_sec = step1.duration_sec * (s.length / length);
448  }
449  }
450 
451  if (vp2Index < vp1Index) {
452  // loop case
453  break;
454  }
455  vp1Index = vp2Index;
456  }
457 }
458 
459 bool qAnimationDlg::smoothTrajectory(double ratio, unsigned iterationCount) {
460  if (m_videoSteps.empty()) return false;
461 
462  if (iterationCount == 0) {
463  assert(false);
464  CVLog::Warning("[smoothTrajectory] Invalid input (iteration count)");
465  return false;
466  }
467 
468  if (ratio < 0.05 || ratio > 0.45) {
469  assert(false);
470  CVLog::Warning("[smoothTrajectory] invalid input (ratio)");
471  return false;
472  }
473 
474  size_t enabledStepCount = countEnabledSteps();
475  if (enabledStepCount < 3) {
476  CVLog::Warning("[smoothTrajectory] not enough segments");
477  return false;
478  }
479 
480  try {
481  Trajectory previousTrajectory;
482  if (!getCompressedTrajectory(previousTrajectory)) {
483  CVLog::Error("Not enough memory");
484  return false;
485  }
486  const Trajectory* currentIterationTrajectory = &previousTrajectory;
487 
488  bool openPoly = !loopCheckBox->isChecked();
489 
490  for (unsigned it = 0; it < iterationCount; ++it) {
491  // reserve memory for the new steps
492  size_t vertCount = currentIterationTrajectory->size();
493  size_t segmentCount = (openPoly ? vertCount - 1 : vertCount);
494 
495  Trajectory newTrajectory;
496  newTrajectory.reserve(segmentCount * 2);
497 
498  if (openPoly) {
499  // we always keep the first step
500  newTrajectory.push_back(currentIterationTrajectory->front());
501  }
502 
503  for (size_t i = 0; i < segmentCount; ++i) {
504  size_t iP = i;
505  size_t iQ = ((iP + 1) % vertCount);
506 
507  const Step& sP = currentIterationTrajectory->at(iP);
508  const Step& sQ = currentIterationTrajectory->at(iQ);
509 
510  ViewInterpolate interpolator(sP.viewportParams,
511  sQ.viewportParams);
512 
513  if (!openPoly || i != 0) {
514  Step interpolatedStep;
515  interpolatedStep.cameraCenter =
516  (PC_ONE - ratio) * sP.cameraCenter +
517  ratio * sQ.cameraCenter;
518  interpolatedStep.duration_sec = sP.duration_sec * ratio;
519  interpolator.interpolate(interpolatedStep.viewportParams,
520  ratio);
521  interpolatedStep.indexInOriginalTrajectory =
522  (it == 0 ? -1 : sP.indexInOriginalTrajectory);
523  newTrajectory.push_back(interpolatedStep);
524  }
525 
526  if (!openPoly || i + 1 != segmentCount) {
527  Step interpolatedStep;
528  interpolatedStep.cameraCenter =
529  ratio * sP.cameraCenter +
530  (PC_ONE - ratio) * sQ.cameraCenter;
531  interpolatedStep.duration_sec =
532  sP.duration_sec * (PC_ONE - ratio);
533  interpolator.interpolate(interpolatedStep.viewportParams,
534  PC_ONE - ratio);
535  interpolatedStep.indexInOriginalTrajectory =
536  (it == 0 ? sQ.indexInOriginalTrajectory : -1);
537  newTrajectory.push_back(interpolatedStep);
538  }
539  }
540 
541  if (openPoly) {
542  // we always keep the last vertex
543  newTrajectory.push_back(currentIterationTrajectory->back());
544  }
545 
546  // last iteration?
547  if (it + 1 == iterationCount) {
548  m_smoothVideoSteps = newTrajectory;
549  } else {
550  previousTrajectory = newTrajectory;
551  currentIterationTrajectory = &previousTrajectory;
552  }
553  }
554 
555  // update the segment lengths
556  size_t smoothSegmentCount = m_smoothVideoSteps.size();
557  if (openPoly) {
558  --smoothSegmentCount;
559  }
560  for (size_t i = 0; i < smoothSegmentCount; ++i) {
561  CCVector3d d =
562  m_smoothVideoSteps[(i + 1) % m_smoothVideoSteps.size()]
563  .cameraCenter -
564  m_smoothVideoSteps[i].cameraCenter;
565  m_smoothVideoSteps[i].length = d.norm();
566  }
567 
568  // update the loop-back indexes
569  for (size_t i = 0; i < m_smoothVideoSteps.size(); ++i) {
570  const Step& s = m_smoothVideoSteps[i];
571  if (s.indexInOriginalTrajectory != -1) {
573  .indexInSmoothTrajectory < 0);
575  .indexInSmoothTrajectory = static_cast<int>(i);
576  }
577  }
578 
579  // update the durations
581  } catch (const std::bad_alloc&) {
582  CVLog::Warning("[smoothTrajectory] not enough memory");
583  m_smoothVideoSteps.clear();
584  return false;
585  }
586 
587  return true;
588 }
589 
591  // reset existing data
592  m_smoothVideoSteps.clear();
593  for (Step& step : m_videoSteps) {
594  step.indexInSmoothTrajectory = -1;
595  }
596 
597  if (!smoothTrajectoryGroupBox->isChecked()) {
598  return true;
599  }
600 
601  if (countEnabledSteps() < 3) {
602  // nothing we can do for now
603  return true;
604  }
605 
606  const unsigned chaikinIterationCount = 5;
607  const double chaikinRatio = smoothRatioDoubleSpinBox->value();
608 
609  if (!smoothTrajectory(chaikinRatio, chaikinIterationCount)) {
610  CVLog::Error("Failed to generate the smooth trajectory");
611  smoothTrajectoryGroupBox->blockSignals(true);
612  smoothTrajectoryGroupBox->setChecked(false);
613  smoothTrajectoryGroupBox->blockSignals(false);
614  return false;
615  }
616  assert(!m_smoothVideoSteps.empty());
617 
618  return true;
619 }
620 
622  if (!state) {
623  //'auto step duration' mode deactivated: nothing to do
624  return;
625  }
626 
627  if (m_videoSteps.empty()) return;
628 
629  Trajectory* referenceTrajectory = nullptr;
630  bool smoothMode = smoothModeEnabled();
631  if (smoothMode) {
632  referenceTrajectory = &m_smoothVideoSteps;
633  } else {
634  referenceTrajectory = &m_videoSteps;
635  }
636  assert(referenceTrajectory);
637 
638  // total length
639  double referenceLength = 0;
640  for (const Step& s : *referenceTrajectory) {
641  referenceLength += s.length;
642  }
643 
644  double totalTime = 0.0;
645  double totalTimeSmooth = 0.0;
646 
647  // now process each segment
648  size_t vp1Index = 0, vp2Index = 0;
649  while (getNextSegment(vp1Index, vp2Index)) {
650  assert(vp1Index < stepSelectionList->count());
651  Step& step1 = m_videoSteps[vp1Index];
652  const Step& step2 = m_videoSteps[vp2Index];
653 
654  double length = 0;
655 
656  int i1Smooth = -1, i2Smooth = -1;
657  if (smoothMode) {
658  i1Smooth = step1.indexInSmoothTrajectory;
659  i2Smooth = step2.indexInSmoothTrajectory;
660  if (i1Smooth < 0 || i2Smooth < 0) {
661  assert(false);
662  continue;
663  }
664  if (i2Smooth < i1Smooth) {
665  // loop mode
666  i2Smooth += static_cast<int>(m_smoothVideoSteps.size());
667  }
668 
669  for (int i = i1Smooth; i < i2Smooth; ++i) {
670  const Step& s = m_smoothVideoSteps[static_cast<size_t>(i) %
671  m_smoothVideoSteps.size()];
672  length += s.length;
673  }
674  } else {
675  length = step1.length;
676  }
677 
679  step1.duration_sec = 0.0;
680  } else {
681  step1.duration_sec = totalTimeDoubleSpinBox->value() *
682  (length / referenceLength);
683 
684  // update the segments time as well
685  if (smoothMode) {
686  for (int i = i1Smooth; i < i2Smooth; ++i) {
687  Step& s = m_smoothVideoSteps[static_cast<size_t>(i) %
688  m_smoothVideoSteps.size()];
689  s.duration_sec = step1.duration_sec * (s.length / length);
690  totalTimeSmooth += s.duration_sec;
691  }
692  }
693  }
694 
695  totalTime += step1.duration_sec;
696 
697  if (vp2Index < vp1Index) {
698  // loop case
699  break;
700  }
701  vp1Index = vp2Index;
702  }
703 
704  CVLog::PrintDebug(QString("Total time = %1 / Smooth time = %2")
705  .arg(totalTime)
706  .arg(totalTimeSmooth));
707 
709 }
710 
712  if (state) {
714  }
715 
716  onAutoStepsDurationToggled(autoStepDurationCheckBox->isChecked());
717 }
718 
720  if (smoothTrajectoryGroupBox->isChecked()) {
722  }
723 }
724 
726  // TODO
727  return nullptr;
728 }
729 
731  return exportTrajectoryCheckBox->isChecked();
732 }
733 
735  double totalDuration_sec = 0;
736  size_t vp1Index = 0, vp2Index = 0;
737  while (getNextSegment(vp1Index, vp2Index)) {
738  assert(vp1Index < stepSelectionList->count());
739  totalDuration_sec += m_videoSteps[vp1Index].duration_sec;
740  if (vp2Index < vp1Index) {
741  // loop case
742  break;
743  }
744  vp1Index = vp2Index;
745  }
746 
747  return totalDuration_sec;
748 }
749 
751  return stepSelectionList->currentRow();
752 }
753 
755  const ecvViewportParameters& viewportParameters) {
756  if (m_view3d) {
757  ecvDisplayTools::SetViewportParameters(viewportParameters);
759  }
760 }
761 
763  // nothing to do
764 }
765 
766 void qAnimationDlg::onTotalTimeChanged(double newTime_sec) {
767  if (m_videoSteps.empty()) return;
768  if (autoStepDurationCheckBox->isChecked()) {
770  return;
771  }
772 
773  //'manual' mode
774  double previousTime_sec = computeTotalTime();
775  if (previousTime_sec != newTime_sec) {
776  assert(previousTime_sec != 0);
777  double scale = newTime_sec / previousTime_sec;
778 
779  bool smoothMode = smoothModeEnabled();
780  double totalTime = 0.0;
781  double totalTimeSmooth = 0.0;
782 
783  size_t vp1Index = 0, vp2Index = 0;
784  while (getNextSegment(vp1Index, vp2Index)) {
785  assert(vp1Index < stepSelectionList->count());
786  Step& step1 = m_videoSteps[vp1Index];
787  const Step& step2 = m_videoSteps[vp2Index];
788 
789  step1.duration_sec *= scale;
790  totalTime += step1.duration_sec;
791 
792  if (smoothMode) {
793  int i1Smooth = step1.indexInSmoothTrajectory;
794  int i2Smooth = step2.indexInSmoothTrajectory;
795  if (i1Smooth < 0 || i2Smooth < 0) {
796  assert(false);
797  continue;
798  }
799  if (i2Smooth < i1Smooth) {
800  // loop mode
801  i2Smooth += static_cast<int>(m_smoothVideoSteps.size());
802  }
803 
804  double length = 0;
805  for (int i = i1Smooth; i < i2Smooth; ++i) {
806  const Step& s =
807  m_smoothVideoSteps[static_cast<size_t>(i) %
808  m_smoothVideoSteps.size()];
809  length += s.length;
810  }
811 
813  // divide equally over all the segments
814  size_t count = static_cast<size_t>(i2Smooth - i1Smooth);
815  for (int i = i1Smooth; i < i2Smooth; ++i) {
816  Step& s = m_smoothVideoSteps[static_cast<size_t>(i) %
817  m_smoothVideoSteps.size()];
818  s.duration_sec = step1.duration_sec / count;
819  totalTimeSmooth += s.duration_sec;
820  }
821  } else {
822  // divide over all the segments based on their respective
823  // length
824  for (int i = i1Smooth; i < i2Smooth; ++i) {
825  Step& s = m_smoothVideoSteps[static_cast<size_t>(i) %
826  m_smoothVideoSteps.size()];
827  s.duration_sec = step1.duration_sec * s.length / length;
828  totalTimeSmooth += s.duration_sec;
829  }
830  }
831  }
832 
833  if (vp2Index < vp1Index) {
834  // loop case
835  break;
836  }
837  vp1Index = vp2Index;
838  }
839 
840  CVLog::PrintDebug(QString("Total time = %1 / Smooth time = %2")
841  .arg(totalTime)
842  .arg(totalTimeSmooth));
843 
844  // update current step
846  }
847 }
848 
849 void qAnimationDlg::onStepTimeChanged(double time_sec) {
850  if (m_videoSteps.empty()) return;
851  int currentStepIndex = getCurrentStepIndex();
852  if (currentStepIndex >= 0) {
853  m_videoSteps[getCurrentStepIndex()].duration_sec = time_sec;
854  }
855 
856  // update total duration
858  // update current step
860  // we have to update the whole smooth trajectory duration as well
862 }
863 
865 #ifdef QFFMPEG_SUPPORT
866  QString filename = QFileDialog::getSaveFileName(
867  this, tr("Output animation file"), outputFileLineEdit->text());
868 #else
869  QString filename = QFileDialog::getExistingDirectory(
870  this, tr("Open Directory"), outputFileLineEdit->text(),
871  QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
872 #endif
873 
874  if (filename.isEmpty()) {
875  // cancelled by user
876  return;
877  }
878 
879  outputFileLineEdit->setText(filename);
880 }
881 
883  QString texturePath = QFileDialog::getExistingDirectory(
884  this, tr("Input textures Directory"),
885  inputTexturesPathLineEdit->text(),
886  QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
887 
888  if (texturePath.isEmpty()) {
889  // cancelled by user
890  return;
891  }
892 
893  inputTexturesPathLineEdit->setText(texturePath);
894  textureNumLabel->setText(
895  QString("Textures num: %1").arg(getImageList(texturePath).size()));
896 }
897 
899  if (m_videoSteps.empty()) return 0;
900  assert(stepSelectionList->count() == static_cast<int>(m_videoSteps.size()));
901 
902  size_t count = 0;
903  for (int i = 0; i < stepSelectionList->count(); ++i) {
904  if (stepSelectionList->item(i)->checkState() == Qt::Checked) ++count;
905  }
906 
907  return count;
908 }
909 
910 bool qAnimationDlg::getNextSegment(size_t& vp1Index, size_t& vp2Index) const {
911  if (m_videoSteps.empty()) return false;
912  assert(stepSelectionList->count() == static_cast<int>(m_videoSteps.size()));
913  if (vp1Index >= m_videoSteps.size()) {
914  assert(false);
915  return false;
916  }
917 
918  size_t inputVP1Index = vp1Index;
919  while (stepSelectionList->item(static_cast<int>(vp1Index))->checkState() ==
920  Qt::Unchecked) {
921  ++vp1Index;
922  if (vp1Index == m_videoSteps.size()) {
923  if (loopCheckBox->isChecked()) {
924  vp1Index = 0;
925  } else {
926  // no more valid start (vp1)
927  return false;
928  }
929  }
930  if (vp1Index == inputVP1Index) {
931  return false;
932  }
933  }
934 
935  // look for the next enabled viewport
936  for (vp2Index = vp1Index + 1; vp2Index <= m_videoSteps.size(); ++vp2Index) {
937  if (vp1Index == vp2Index) {
938  return false;
939  }
940 
941  if (vp2Index == m_videoSteps.size()) {
942  if (loopCheckBox->isChecked()) {
943  vp2Index = 0;
944  } else {
945  // stop
946  break;
947  }
948  }
949 
950  if (stepSelectionList->item(static_cast<int>(vp2Index))->checkState() ==
951  Qt::Checked) {
952  // we have found a valid couple (vp1, vp2)
953  return true;
954  }
955  }
956 
957  // no more valid stop (vp2)
958  return false;
959 }
960 
961 int qAnimationDlg::countFrames(size_t startIndex /*=0*/) {
962  // reset the interpolators and count the total number of frames
963  int totalFrameCount = 0;
964  {
965  double fps = fpsSpinBox->value();
966 
967  size_t vp1Index = startIndex;
968  size_t vp2Index = vp1Index + 1;
969 
970  while (getNextSegment(vp1Index, vp2Index)) {
971  const Step& currentStep = m_videoSteps[vp1Index];
972  int frameCount = static_cast<int>(fps * currentStep.duration_sec);
973  totalFrameCount += frameCount;
974 
975  // take care of the 'loop' case
976  if (vp2Index < vp1Index) {
977  assert(loopCheckBox->isChecked());
978  break;
979  }
980  vp1Index = vp2Index;
981  }
982  }
983 
984  return totalFrameCount;
985 }
986 
988  Trajectory& compressedTrajectory) const {
989  if (m_videoSteps.empty()) return true;
990 
991  compressedTrajectory.clear();
992 
993  size_t enabledStepCount = countEnabledSteps();
994  try {
995  compressedTrajectory.reserve(enabledStepCount);
996  } catch (const std::bad_alloc&) {
997  return false;
998  }
999 
1000  assert(stepSelectionList->count() == static_cast<int>(m_videoSteps.size()));
1001  for (size_t i = 0; i < m_videoSteps.size(); ++i) {
1002  if (stepSelectionList->item(static_cast<int>(i))->checkState() ==
1003  Qt::Checked) {
1004  compressedTrajectory.push_back(m_videoSteps[i]);
1005  }
1006  }
1007 
1008  return true;
1009 }
1010 
1012  setEnabled(false);
1013 
1014  bool openPoly = !loopCheckBox->isChecked();
1015  int fps = fpsSpinBox->value();
1016  double timeStep = 1.0 / fps;
1017  // theoretical waiting time per frame
1018  qint64 delay_ms = static_cast<qint64>(1000 / fps);
1019 
1020  QString inputTexturesPath = inputTexturesPathLineEdit->text();
1021  QStringList texture_files = getImageList(inputTexturesPath);
1022 
1023  bool update_textures = updateTextures() && !texture_files.empty();
1024  if (m_videoSteps.size() < 2 && update_textures) { // only update textures
1025  // show progress dialog
1026  int total_count = texture_files.size();
1027  QProgressDialog progressDialog(QString("Frames: %1").arg(total_count),
1028  "Cancel", 0, total_count, this);
1029  progressDialog.setWindowTitle("Preview");
1030  progressDialog.show();
1031  progressDialog.setModal(true);
1032  progressDialog.setAutoClose(false);
1033  QApplication::processEvents();
1034 
1035  textureAnimationPreview(texture_files, progressDialog);
1036  } else if (m_videoSteps.size() >= 2) { // preview viewports update
1037  size_t vp1Index = previewFromSelectedCheckBox->isChecked()
1038  ? static_cast<size_t>(getCurrentStepIndex())
1039  : 0;
1040  Trajectory compressedTrajectory;
1041  const Trajectory* trajectory = nullptr;
1042 
1043  bool smoothMode = smoothModeEnabled();
1044  if (smoothMode) {
1045  trajectory = &m_smoothVideoSteps;
1046  assert(m_videoSteps[vp1Index].indexInSmoothTrajectory >= 0);
1047  vp1Index = static_cast<size_t>(
1048  m_videoSteps[vp1Index].indexInSmoothTrajectory);
1049  } else {
1050  if (!getCompressedTrajectory(compressedTrajectory)) {
1051  CVLog::Error("Not enough memory");
1052  return;
1053  }
1054  trajectory = &compressedTrajectory;
1055  }
1056  assert(trajectory);
1057 
1058  size_t segmentCount = trajectory->size();
1059  if (openPoly && segmentCount != 0) {
1060  --segmentCount;
1061  }
1062 
1063  if (vp1Index >= segmentCount) {
1064  // can't start from the specified index
1065  vp1Index = 0;
1066  }
1067 
1068  double totalTime = 0;
1069  double startTime = 0;
1070  for (size_t i = 0; i < segmentCount; ++i) {
1071  totalTime += trajectory->at(i).duration_sec;
1072  if (i < vp1Index) {
1073  startTime += trajectory->at(i).duration_sec;
1074  }
1075  }
1076 
1077  // count the total number of frames
1078  double remainingTime = (openPoly ? totalTime - startTime : totalTime);
1079  int frameCount = static_cast<int>(fps * remainingTime);
1080 
1081  int total_count = frameCount;
1082  if (frameCount == 0 && update_textures) {
1083  total_count = texture_files.size();
1084  }
1085 
1086  // show progress dialog
1087  QProgressDialog progressDialog(QString("Frames: %1").arg(total_count),
1088  "Cancel", 0, total_count, this);
1089  progressDialog.setWindowTitle("Preview");
1090  progressDialog.show();
1091  progressDialog.setModal(true);
1092  progressDialog.setAutoClose(false);
1093  QApplication::processEvents();
1094 
1095  assert(stepSelectionList->count() >= m_videoSteps.size());
1096 
1097  double currentTime = startTime;
1098  double currentStepStartTime = startTime;
1099 
1100  if (frameCount == 0 && update_textures) {
1101  textureAnimationPreview(texture_files, progressDialog);
1102  } else if (frameCount > 0) {
1103  // we'll take the rendering time into account!
1104  QElapsedTimer timer;
1105  timer.start();
1106  for (int frameIndex = 0; frameIndex < frameCount;) {
1107  size_t vp2Index = vp1Index + 1;
1108  if (vp2Index == trajectory->size()) {
1109  assert(!openPoly);
1110  vp2Index = 0;
1111  }
1112 
1113  const Step& step1 = trajectory->at(vp1Index);
1114  double deltaTime = currentTime - currentStepStartTime;
1115  if (deltaTime <= step1.duration_sec) {
1116  const Step& step2 = trajectory->at(vp2Index);
1117  ViewInterpolate interpolator(step1.viewportParams,
1118  step2.viewportParams);
1119 
1120  ecvViewportParameters currentViewport;
1121  interpolator.interpolate(currentViewport,
1122  deltaTime / step1.duration_sec);
1123 
1124  timer.restart();
1125  if (update_textures) {
1126  QString texture_file =
1127  texture_files[frameIndex %
1128  texture_files.size()];
1129  if (QFileInfo::exists(texture_file)) {
1130  for (auto& mesh : m_mesh_list) {
1131  if (!mesh->updateTextures(CVTools::FromQString(
1132  texture_file))) {
1134  "Update Textures failed, please "
1135  "toggle shown material first!");
1136  }
1137  }
1138  } else {
1139  CVLog::Warning(QString("Ignoring not existing "
1140  "texture image: %1")
1141  .arg(texture_file));
1142  }
1143  }
1144 
1145  applyViewport(currentViewport);
1146 
1147  // next frame
1148  currentTime += timeStep;
1149  ++frameIndex;
1150 
1151  progressDialog.setValue(frameIndex);
1152  QApplication::processEvents();
1153  if (progressDialog.wasCanceled()) {
1154  break;
1155  }
1156 
1157  qint64 dt_ms = timer.elapsed();
1158 
1159  // remaining time
1160  if (dt_ms < delay_ms) {
1161  int wait_ms = static_cast<int>(delay_ms - dt_ms);
1162 #if defined(CV_WINDOWS)
1163  ::Sleep(wait_ms);
1164 #else
1165  usleep(wait_ms * 1000);
1166 #endif
1167  }
1168  } else {
1169  // we'll try the next step
1170  ++vp1Index;
1171 
1172  if (vp1Index == segmentCount) {
1173  if (openPoly) {
1174  break;
1175  }
1176 
1177  // else restart from 0
1178  vp1Index = 0;
1179  currentStepStartTime = 0.0;
1180  currentTime = std::max(0.0, currentTime - totalTime);
1181  } else {
1182  currentStepStartTime += step1.duration_sec;
1183  }
1184  }
1185  }
1186  } else {
1188  "Please select update tetures and set textures path or "
1189  "select at least two viewports!");
1190  }
1191  } else {
1193  "Please select update tetures and set textures path or "
1194  "select at least two viewports!");
1195  }
1196 
1197  // reset view
1199 
1200  setEnabled(true);
1201 }
1202 
1203 void qAnimationDlg::textureAnimationPreview(const QStringList& texture_files,
1204  QProgressDialog& progressDialog) {
1205  bool openPoly = !loopCheckBox->isChecked();
1206  int fps = fpsSpinBox->value();
1207  double timeStep = 1.0 / fps;
1208  // theoretical waiting time per frame
1209  qint64 delay_ms = static_cast<qint64>(1000 / fps);
1210  std::size_t total_count = texture_files.size();
1211  // we'll take the rendering time into account!
1212  QElapsedTimer timer;
1213  timer.start();
1214  for (std::size_t frameIndex = 0; frameIndex < texture_files.size();) {
1215  timer.restart();
1216  QString texture_file = texture_files[frameIndex];
1217  if (QFileInfo::exists(texture_file)) {
1218  for (auto& mesh : m_mesh_list) {
1219  if (!mesh->updateTextures(CVTools::FromQString(texture_file))) {
1221  "Update Textures failed, please "
1222  "toggle shown material first!");
1223  }
1224  }
1225  } else {
1226  CVLog::Warning(QString("Ignoring not existing texture image: %1")
1227  .arg(texture_file));
1228  }
1230 
1231  // next frame
1232  ++frameIndex;
1233 
1234  progressDialog.setValue(frameIndex);
1235  QApplication::processEvents();
1236  if (progressDialog.wasCanceled()) {
1237  break;
1238  }
1239 
1240  qint64 dt_ms = timer.elapsed();
1241 
1242  // remaining time
1243  if (dt_ms < delay_ms) {
1244  int wait_ms = static_cast<int>(delay_ms - dt_ms);
1245 #if defined(CV_WINDOWS)
1246  ::Sleep(wait_ms);
1247 #else
1248  usleep(wait_ms * 1000);
1249 #endif
1250  }
1251 
1252  if (!openPoly && frameIndex == total_count) {
1253  frameIndex = 0;
1254  }
1255  }
1256 }
1257 
1259  const QStringList& texture_files,
1260  QProgressDialog& progressDialog,
1261  bool asSeparateFrames
1262 #ifdef QFFMPEG_SUPPORT
1263  ,
1264  QScopedPointer<QVideoEncoder>& encoder
1265 #endif
1266 ) {
1267 
1268  bool success = true;
1269  bool openPoly = !loopCheckBox->isChecked();
1270  int fps = fpsSpinBox->value();
1271  double timeStep = 1.0 / fps;
1272  // theoretical waiting time per frame
1273  qint64 delay_ms = static_cast<qint64>(1000 / fps);
1274  std::size_t total_count = texture_files.size();
1275 
1276  // super resolution
1277  int superRes = superResolutionSpinBox->value();
1278  const int SUPER_RESOLUTION = 0;
1279  const int ZOOM = 1;
1280  int renderingMode = renderingModeComboBox->currentIndex();
1281  assert(renderingMode == SUPER_RESOLUTION || renderingMode == ZOOM);
1282 
1283  QString outputFilename = outputFileLineEdit->text();
1284  QDir outputDir(QFileInfo(outputFilename).absolutePath());
1285 
1286  // we'll take the rendering time into account!
1287  QElapsedTimer timer;
1288  timer.start();
1289  for (std::size_t frameIndex = 0; frameIndex < total_count;) {
1290  if (QFileInfo::exists(texture_files[frameIndex])) {
1291  for (auto& mesh : m_mesh_list) {
1292  if (!mesh->updateTextures(
1293  CVTools::FromQString(texture_files[frameIndex]))) {
1295  "Update Textures failed, please "
1296  "toggle shown material first!");
1297  };
1298  }
1300  }
1301 
1302  // render to image
1303  QImage image = ecvDisplayTools::RenderToImage(superRes, false, true, 0);
1304 
1305  if (image.isNull()) {
1306  QMessageBox::critical(this, "Error", "Failed to grab the screen!");
1307  success = false;
1308  break;
1309  }
1310 
1311  if (renderingMode == SUPER_RESOLUTION && superRes > 1) {
1312  image = image.scaled(
1313  image.width() / superRes, image.height() / superRes,
1314  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1315  }
1316 
1317  if (asSeparateFrames) {
1318  QString filename =
1319  QString("frame_%1.png").arg(frameIndex, 6, 10, QChar('0'));
1320  QString fullPath = outputDir.filePath(filename);
1321  if (!image.save(fullPath)) {
1322  QMessageBox::critical(this, "Error",
1323  QString("Failed to save frame #%1")
1324  .arg(frameIndex + 1));
1325  success = false;
1326  break;
1327  }
1328  } else {
1329 #ifdef QFFMPEG_SUPPORT
1330  QString errorString;
1331  if (!encoder->encodeImage(image, frameIndex, &errorString)) {
1332  QMessageBox::critical(this, "Error",
1333  QString("Failed to encode frame #%1: %2")
1334  .arg(frameIndex + 1)
1335  .arg(errorString));
1336  success = false;
1337  break;
1338  }
1339 #endif
1340  }
1341 
1342  // next frame
1343  ++frameIndex;
1344 
1345  progressDialog.setValue(frameIndex);
1346  QApplication::processEvents();
1347  if (progressDialog.wasCanceled()) {
1348  QMessageBox::warning(this, "Warning",
1349  QString("Process has been cancelled"));
1350  success = false;
1351  break;
1352  }
1353  }
1354 
1355  return success;
1356 }
1357 
1358 void qAnimationDlg::render(bool asSeparateFrames) {
1359  if (!m_view3d) {
1360  assert(false);
1361  return;
1362  }
1363 
1364  QString outputFilename = outputFileLineEdit->text();
1365  QString inputTexturesPath = inputTexturesPathLineEdit->text();
1366  QStringList texture_files = getImageList(inputTexturesPath);
1367 
1368  bool update_textures = updateTextures() && !texture_files.empty();
1369 
1370  // save to persistent settings
1371  {
1372  QSettings settings;
1373  settings.beginGroup("qAnimation");
1374  settings.setValue("filename", outputFilename);
1375  settings.setValue("texturesPath", inputTexturesPath);
1376  settings.endGroup();
1377  }
1378 
1379  if (!update_textures && m_videoSteps.size() < 2) {
1381  "Please select update tetures and set textures path or select "
1382  "at least two viewports!");
1383  return;
1384  }
1385 
1386  setEnabled(false);
1387 
1388  Trajectory compressedTrajectory;
1389  const Trajectory* trajectory = nullptr;
1390 
1391  bool smoothMode = smoothModeEnabled();
1392  if (smoothMode) {
1393  trajectory = &m_smoothVideoSteps;
1394  } else {
1395  if (!getCompressedTrajectory(compressedTrajectory)) {
1396  CVLog::Error("Not enough memory");
1397  return;
1398  }
1399  trajectory = &compressedTrajectory;
1400  }
1401  assert(trajectory);
1402 
1403  bool openPoly = !loopCheckBox->isChecked();
1404  size_t segmentCount = trajectory->size();
1405  if (openPoly && segmentCount != 0) {
1406  --segmentCount;
1407  }
1408 
1409  double totalTime = 0;
1410  for (size_t i = 0; i < segmentCount; ++i) {
1411  totalTime += trajectory->at(i).duration_sec;
1412  }
1413 
1414  // count the total number of frames
1415  int fps = fpsSpinBox->value();
1416  int frameCount = static_cast<int>(fps * totalTime);
1417  int total_count = frameCount;
1418  if (total_count == 0 && update_textures) {
1419  total_count = texture_files.size();
1420  }
1421 
1422  // super resolution
1423  int superRes = superResolutionSpinBox->value();
1424  const int SUPER_RESOLUTION = 0;
1425  const int ZOOM = 1;
1426  int renderingMode = renderingModeComboBox->currentIndex();
1427  assert(renderingMode == SUPER_RESOLUTION || renderingMode == ZOOM);
1428 
1429  // show progress dialog
1430  QProgressDialog progressDialog(QString("Frames: %1").arg(total_count),
1431  "Cancel", 0, total_count, this);
1432  progressDialog.setWindowTitle("Render");
1433  progressDialog.show();
1434  QApplication::processEvents();
1435 
1436 #ifdef QFFMPEG_SUPPORT
1437  QScopedPointer<QVideoEncoder> encoder(nullptr);
1438  QSize originalViewSize;
1439  if (!asSeparateFrames) {
1440  // get original viewport size
1441  originalViewSize = ecvDisplayTools::GetScreenSize();
1442 
1443  // hack: as the encoder requires that the video dimensions are multiples
1444  // of 8, we resize the window a little bit...
1445  {
1446  // find the nearest multiples of 8
1447  QSize customSize = originalViewSize;
1448  if (originalViewSize.width() % 8 || originalViewSize.height() % 8) {
1449  if (originalViewSize.width() % 8)
1450  customSize.setWidth((originalViewSize.width() / 8 + 1) * 8);
1451  if (originalViewSize.height() % 8)
1452  customSize.setHeight((originalViewSize.height() / 8 + 1) *
1453  8);
1454  m_view3d->resize(customSize);
1455  QApplication::processEvents();
1456  }
1457  }
1458 
1459  int bitrate = bitrateSpinBox->value() * 1024;
1460  int gop = fps;
1461  int animScale = 1;
1462  if (renderingMode == ZOOM) {
1463  animScale = superRes;
1464  }
1465 
1466  encoder.reset(new QVideoEncoder(
1467  outputFilename, ecvDisplayTools::GlWidth() * animScale,
1468  ecvDisplayTools::GlHeight() * animScale, bitrate, gop,
1469  static_cast<unsigned>(fpsSpinBox->value())));
1470  QStringList errors;
1471  QString outputFormat = outputFormatComboBox->currentData().toString();
1472  bool success = encoder->open(outputFormat, errors);
1473  for (const QString& e : errors) {
1474  CVLog::Warning(e);
1475  }
1476 
1477  if (!success) {
1478  QMessageBox::critical(
1479  this, "Error",
1480  QString("Failed to open file for output: %1")
1481  .arg(errors.back())); // display the last error
1482  // message
1483  setEnabled(true);
1484  return;
1485  }
1486  }
1487 #else
1488  if (!asSeparateFrames) {
1489  QMessageBox::critical(
1490  this, "Error",
1491  QString("Animation mode is not supported (no FFMPEG support)"));
1492  return;
1493  }
1494 #endif
1495 
1496  QDir outputDir(QFileInfo(outputFilename).absolutePath());
1497 
1498  bool success = true;
1499 
1500  if (frameCount == 0 && update_textures) {
1501  success = textureAnimationRender(texture_files, progressDialog,
1502  asSeparateFrames
1503 #ifdef QFFMPEG_SUPPORT
1504  ,
1505  encoder
1506 #endif
1507  );
1508  } else {
1509  double currentTime = 0.0;
1510  double currentStepStartTime = 0.0;
1511  double timeStep = 1.0 / fps;
1512  size_t vp1Index = 0;
1513  for (int frameIndex = 0; frameIndex < frameCount;) {
1514  size_t vp2Index = vp1Index + 1;
1515  if (vp2Index == trajectory->size()) {
1516  assert(!openPoly);
1517  vp2Index = 0;
1518  }
1519 
1520  const Step& step1 = trajectory->at(vp1Index);
1521  double deltaTime = currentTime - currentStepStartTime;
1522  if (deltaTime <= step1.duration_sec) {
1523  const Step& step2 = trajectory->at(vp2Index);
1524  ViewInterpolate interpolator(step1.viewportParams,
1525  step2.viewportParams);
1526 
1527  ecvViewportParameters currentViewport;
1528  interpolator.interpolate(currentViewport,
1529  deltaTime / step1.duration_sec);
1530 
1531  // update textures
1532  if (update_textures) {
1533  QString texture_file =
1534  texture_files[frameIndex % texture_files.size()];
1535  if (QFileInfo::exists(texture_file)) {
1536  for (auto& mesh : m_mesh_list) {
1537  if (!mesh->updateTextures(
1538  CVTools::FromQString(texture_file))) {
1540  "Update Textures failed, please "
1541  "toggle shown material first!");
1542  };
1543  }
1544  } else {
1545  CVLog::Warning(QString("Ignoring not existing texture "
1546  "image: %1")
1547  .arg(texture_file));
1548  }
1549  }
1550 
1551  applyViewport(currentViewport);
1552 
1553  // render to image
1554  QImage image = ecvDisplayTools::RenderToImage(superRes, false,
1555  true, 0);
1556 
1557  if (image.isNull()) {
1558  QMessageBox::critical(this, "Error",
1559  "Failed to grab the screen!");
1560  success = false;
1561  break;
1562  }
1563 
1564  if (renderingMode == SUPER_RESOLUTION && superRes > 1) {
1565  image = image.scaled(
1566  image.width() / superRes, image.height() / superRes,
1567  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
1568  }
1569 
1570  if (asSeparateFrames) {
1571  QString filename =
1572  QString("frame_%1.png")
1573  .arg(frameIndex, 6, 10, QChar('0'));
1574  QString fullPath = outputDir.filePath(filename);
1575  if (!image.save(fullPath)) {
1576  QMessageBox::critical(
1577  this, "Error",
1578  QString("Failed to save frame #%1")
1579  .arg(frameIndex + 1));
1580  success = false;
1581  break;
1582  }
1583  } else {
1584 #ifdef QFFMPEG_SUPPORT
1585  QString errorString;
1586  if (!encoder->encodeImage(image, frameIndex,
1587  &errorString)) {
1588  QMessageBox::critical(
1589  this, "Error",
1590  QString("Failed to encode frame #%1: %2")
1591  .arg(frameIndex + 1)
1592  .arg(errorString));
1593  success = false;
1594  break;
1595  }
1596 #endif
1597  }
1598 
1599  // next frame
1600  currentTime += timeStep;
1601  ++frameIndex;
1602 
1603  progressDialog.setValue(frameIndex);
1604  QApplication::processEvents();
1605  if (progressDialog.wasCanceled()) {
1606  QMessageBox::warning(this, "Warning",
1607  QString("Process has been cancelled"));
1608  success = false;
1609  break;
1610  }
1611  } else {
1612  // we'll try the next step
1613  ++vp1Index;
1614  currentStepStartTime += step1.duration_sec;
1615 
1616  if (vp1Index == segmentCount) {
1617  break;
1618  }
1619  }
1620  }
1621  }
1622 
1623 #ifdef QFFMPEG_SUPPORT
1624  if (encoder) {
1625  encoder->close();
1626 
1627  // hack: restore original size
1628  m_view3d->resize(originalViewSize);
1629  QApplication::processEvents();
1630  }
1631 #endif
1632 
1633  progressDialog.hide();
1634  QApplication::processEvents();
1635 
1636  if (success) {
1637  QMessageBox::information(this, "Job done",
1638  "The animation has been saved successfully");
1639  }
1640 
1641  setEnabled(true);
1642 }
1643 
1645  double totalDuration_sec = computeTotalTime();
1646 
1647  totalTimeDoubleSpinBox->blockSignals(true);
1648  totalTimeDoubleSpinBox->setValue(totalDuration_sec);
1649  totalTimeDoubleSpinBox->blockSignals(false);
1650 }
1651 
1653  int index = getCurrentStepIndex();
1654 
1655  stepTimeDoubleSpinBox->blockSignals(true);
1656  stepTimeDoubleSpinBox->setValue(
1657  index < 0 ? 0.0 : m_videoSteps[index].duration_sec);
1658  stepTimeDoubleSpinBox->blockSignals(false);
1659 }
1660 
1661 void qAnimationDlg::onItemChanged(QListWidgetItem*) {
1662  onCurrentStepChanged(stepSelectionList->currentRow());
1663 
1665 }
1666 
1668  // update current step descriptor
1669  stepIndexLabel->setText(QString::number(index + 1));
1670 
1672 
1673  if (index >= 0) {
1674  // apply either the current or the
1675  applyViewport(
1678  .indexInSmoothTrajectory]
1679  : m_videoSteps[index])
1680  .viewportParams);
1681  }
1682 
1683  // check that the step is enabled
1684  bool isEnabled =
1685  (index >= 0 &&
1686  stepSelectionList->item(index)->checkState() == Qt::Checked);
1687  bool isLoop = loopCheckBox->isChecked();
1688  currentStepGroupBox->setEnabled(
1689  isEnabled &&
1690  ((index >= 0 && index + 1 < m_videoSteps.size()) || isLoop));
1691 }
1692 
1693 void qAnimationDlg::onLoopToggled(bool enabled) {
1695 
1696  onCurrentStepChanged(stepSelectionList->currentRow());
1697 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
std::string filename
std::shared_ptr< core::Tensor > image
int size
int count
core::Tensor result
Definition: VtkUtils.cpp:76
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static std::string FromQString(const QString &qs)
Definition: CVTools.cpp:100
Video encoder based on FFmpeg.
Definition: QVideoEncoder.h:14
static bool GetSupportedOutputFormats(std::vector< OutputFormat > &formats, bool ignoreIfNoFileExtension=true)
Returns the list of supported output formats.
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
The ViewInterpolate class.
bool interpolate(ecvViewportParameters &a_returned_viewport, double ratio) const
Interpolates the 2 viewports at a given (relative) position.
2D viewport object
const ecvViewportParameters & getParameters() const
Gets parameters.
Bounding box structure.
Definition: ecvBBox.h:25
Vector3Tpl< T > getTranslationAsVec3D() const
Returns a copy of the translation as a CCVector3.
ccGLMatrixTpl< T > inverse() const
Returns inverse transformation.
Double version of ccGLMatrixTpl.
Definition: ecvGLMatrix.h:56
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
bool hasMetaData(const QString &key) const
Returns whether a meta-data element with the given key exists or not.
Colored polyline.
Definition: ecvPolyline.h:24
static int GlWidth()
Returns the OpenGL context width.
static void SetViewportParameters(const ecvViewportParameters &params)
static int GlHeight()
Returns the OpenGL context height.
static QSize GetScreenSize()
static void UpdateScreen()
static QImage RenderToImage(int zoomFactor=1, bool renderOverlayItems=false, bool silent=false, int viewport=0)
static void GetVisibleObjectsBB(ccBBox &box)
Returns the visible objects bounding-box.
Standard parameters for GL displays/viewports.
ccGLMatrixd computeViewMatrix() const
Computes the view matrix.
Trajectory m_smoothVideoSteps
Smoothed animation.
int countFrames(size_t startIndex=0)
void renderFrames()
Definition: qAnimationDlg.h:80
void onTotalTimeChanged(double)
int getCurrentStepIndex()
bool updateCameraTrajectory()
bool getNextSegment(size_t &vp1, size_t &vp2) const
bool smoothTrajectory(double ratio, unsigned iterationCount)
std::vector< Step > Trajectory
size_t countEnabledSteps() const
MeshList m_mesh_list
void textureAnimationPreview(const QStringList &texture_files, QProgressDialog &progressDialog)
void onBrowseTexturesButtonClicked()
void onCurrentStepChanged(int)
QWidget * m_view3d
Associated 3D view.
qAnimationDlg(QWidget *view3d, QWidget *parent=nullptr)
Default constructor.
void renderAnimation()
Definition: qAnimationDlg.h:79
void onSmoothRatioChanged(double)
void updateSmoothTrajectoryDurations()
void onStepTimeChanged(double)
Trajectory m_videoSteps
Animation.
void onItemChanged(QListWidgetItem *)
bool exportTrajectoryOnExit()
bool updateTextures() const
bool getCompressedTrajectory(Trajectory &compressedTrajectory) const
void onFPSChanged(int)
void updateTotalDuration()
double computeTotalTime()
virtual ~qAnimationDlg()
Destrcuctor.
bool smoothModeEnabled() const
void onAutoStepsDurationToggled(bool)
void onLoopToggled(bool)
void updateCurrentStepDuration()
bool textureAnimationRender(const QStringList &texture_files, QProgressDialog &progressDialog, bool asSeparateFrames)
void onBrowseButtonClicked()
ccPolyline * getTrajectory()
bool init(const std::vector< cc2DViewportObject * > &viewports, const std::vector< ccMesh * > &meshes)
Initialize the dialog with a set of viewports.
void applyViewport(const ecvViewportParameters &viewportParameters)
bool updateSmoothCameraTrajectory()
void onSmoothTrajectoryToggled(bool)
void render(bool asSeparateFrames)
__host__ __device__ float length(float2 v)
Definition: cutil_math.h:1162
int max(int a, int b)
Definition: cutil_math.h:48
static const std::string path
Definition: PointCloud.cpp:59
void Sleep(int milliseconds)
Definition: Helper.cpp:278
bool GreaterThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:37
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
static const QString s_stepDurationKey("StepDurationSec")
QStringList getImageList(const QString &path)
static const QString s_stepEnabledKey("StepEnabled")
double timer
Definition: struct.h:215
Simple step (viewport + time)
CCVector3d cameraCenter
ecvViewportParameters viewportParams