1 
2 
3 // System includes
4 #include "tsystem.h"
5 #include "timagecache.h"
6 
7 // Geometry
8 #include "tgeometry.h"
9 
10 // Image
11 #include "tiio.h"
12 #include "timageinfo.h"
13 #include "trop.h"
14 #include "tropcm.h"
15 
16 // Sound
17 #include "tsop.h"
18 #include "tsound.h"
19 
20 // Strings
21 #include "tconvert.h"
22 
23 // File-related includes
24 #include "tfilepath.h"
25 #include "tfiletype.h"
26 #include "filebrowsermodel.h"
27 #include "fileviewerpopup.h"
28 
29 // OpenGL
30 #include "tgl.h"
31 #include "tvectorgl.h"
32 #include "tvectorrenderdata.h"
33 
34 // Qt helpers
35 #include "toonzqt/gutil.h"
36 #include "toonzqt/imageutils.h"
37 
38 // App-Stage includes
39 #include "tapp.h"
40 #include "toutputproperties.h"
41 #include "toonz/txsheethandle.h"
42 #include "toonz/txshsimplelevel.h"
43 #include "toonz/levelproperties.h"
44 #include "toonz/tscenehandle.h"
45 #include "toonz/toonzscene.h"
46 #include "toonz/sceneproperties.h"
47 #include "toonz/txshlevelhandle.h"
48 #include "toonz/tcamera.h"
49 #include "toonz/preferences.h"
50 #include "toonz/tproject.h"
51 
52 // Image painting
53 #include "toonz/imagepainter.h"
54 
55 // Preview
56 #include "previewfxmanager.h"
57 
58 // Panels
59 #include "pane.h"
60 
61 // recent files
62 #include "mainwindow.h"
63 
64 // Other widgets
65 #include "toonzqt/flipconsole.h"
66 #include "toonzqt/dvdialog.h"
67 #include "filmstripselection.h"
68 #include "castselection.h"
69 #include "histogrampopup.h"
70 
71 // Qt includes
72 #include <QApplication>
73 #include <QDesktopWidget>
74 #include <QSettings>
75 #include <QPainter>
76 #include <QDialogButtonBox>
77 #include <QAbstractButton>
78 #include <QLabel>
79 #include <QRadioButton>
80 #include <QSlider>
81 #include <QButtonGroup>
82 #include <QToolBar>
83 #include <QMainWindow>
84 #include <QUrl>
85 #include <QObject>
86 #include <QDesktopServices>
87 #include <QVBoxLayout>
88 #include <QHBoxLayout>
89 #include <QPushButton>
90 
91 #include <stdint.h>  // for uintptr_t
92 
93 #ifdef _WIN32
94 #include "avicodecrestrictions.h"
95 #endif
96 
97 #include "flipbook.h"
98 
99 //-----------------------------------------------------------------------------
100 
101 using namespace ImageUtils;
102 
103 namespace {
getShortcut(const char * id)104 QString getShortcut(const char *id) {
105   return QString::fromStdString(
106       CommandManager::instance()->getShortcutFromId(id));
107 }
108 }  // namespace
109 
110 //-----------------------------------------------------------------------------
111 
112 namespace {
113 /* inline TRect getImageBounds(const TImageP& img)
114   {
115     if(TRasterImageP ri= img)
116       return ri->getRaster()->getBounds();
117     else if(TToonzImageP ti= img)
118       return ti->getRaster()->getBounds();
119     else
120     {
121       TVectorImageP vi= img;
122       return convert(vi->getBBox());
123     }
124   }*/
125 
126 //-----------------------------------------------------------------------------
127 
getImageBoundsD(const TImageP & img)128 inline TRectD getImageBoundsD(const TImageP &img) {
129   if (TRasterImageP ri = img)
130     return TRectD(0, 0, ri->getRaster()->getLx(), ri->getRaster()->getLy());
131   else if (TToonzImageP ti = img)
132     return TRectD(0, 0, ti->getSize().lx, ti->getSize().ly);
133   else {
134     TVectorImageP vi = img;
135     return vi->getBBox();
136   }
137 }
138 }  // namespace
139 
140 //=============================================================================
141 /*! \class FlipBook
142                 \brief The FlipBook class provides to view level images.
143 
144                 Inherits \b QWidget.
145 
146                 The object is composed of grid layout \b QGridLayout which
147    contains an image
148                 viewer \b ImageViewer and a double button bar. It is possible
149    decide widget
150                 title and which button bar show by setting QString and bool in
151    constructor.
152 
153                 You can set level to show in FlipBook using \b setLevel(); and
154    call
155                 onDrawFrame() to show FlipBook current frame. The current frame
156    can be
157                 set directly using setCurrentFrame(int index) or using slot
158    methods connected
159                 to button bar.
160 
161     \sa FlipBookPool class.
162 */
FlipBook(QWidget * parent,QString viewerTitle,std::vector<int> flipConsoleButtonMask,UCHAR flags,bool isColorModel)163 FlipBook::FlipBook(QWidget *parent, QString viewerTitle,
164                    std::vector<int> flipConsoleButtonMask, UCHAR flags,
165                    bool isColorModel)  //, bool showOnlyPlayBackgroundButton)
166     : QWidget(parent),
167       m_viewerTitle(viewerTitle),
168       m_levelNames(),
169       m_levels(),
170       m_playSound(false),
171       m_snd(0),
172       m_player(0)
173       //, m_doCompare(false)
174       ,
175       m_currentFrameToSave(0),
176       m_lw(),
177       m_lr(),
178       m_loadPopup(0),
179       m_savePopup(0),
180       m_shrink(1),
181       m_isPreviewFx(false),
182       m_previewedFx(0),
183       m_previewXsh(0),
184       m_previewUpdateTimer(this),
185       m_xl(0),
186       m_title1(),
187       m_poolIndex(-1),
188       m_freezed(false),
189       m_loadbox(),
190       m_dim(),
191       m_loadboxes(),
192       m_freezeButton(0),
193       m_flags(flags) {
194   setAcceptDrops(true);
195   setFocusPolicy(Qt::StrongFocus);
196 
197   // flipConsoleButtonMask = flipConsoleButtonMask & ~FlipConsole::eSubCamera;
198 
199   ImageUtils::FullScreenWidget *fsWidget =
200       new ImageUtils::FullScreenWidget(this);
201 
202   m_imageViewer = new ImageViewer(
203       fsWidget, this,
204       std::find(flipConsoleButtonMask.begin(), flipConsoleButtonMask.end(),
205                 FlipConsole::eHisto) == flipConsoleButtonMask.end());
206   fsWidget->setWidget(m_imageViewer);
207 
208   setFocusProxy(m_imageViewer);
209   m_title = m_viewerTitle;
210   m_imageViewer->setIsColorModel(isColorModel);
211 
212   // layout
213   QVBoxLayout *mainLayout = new QVBoxLayout(this);
214   mainLayout->setMargin(0);
215   mainLayout->setSpacing(0);
216   {
217     mainLayout->addWidget(fsWidget, 1);
218     m_flipConsole = new FlipConsole(
219         mainLayout, flipConsoleButtonMask, true, 0,
220         (viewerTitle == "") ? "FlipConsole" : viewerTitle, this, !isColorModel);
221     mainLayout->addWidget(m_flipConsole);
222   }
223   setLayout(mainLayout);
224 
225   // signal-slot connection
226   bool ret = connect(m_flipConsole, SIGNAL(buttonPressed(FlipConsole::EGadget)),
227                      this, SLOT(onButtonPressed(FlipConsole::EGadget)));
228 
229   m_flipConsole->setFrameRate(TApp::instance()
230                                   ->getCurrentScene()
231                                   ->getScene()
232                                   ->getProperties()
233                                   ->getOutputProperties()
234                                   ->getFrameRate());
235 
236   mainLayout->addWidget(m_flipConsole);
237 
238   m_previewUpdateTimer.setSingleShot(true);
239 
240   ret = ret && connect(parentWidget(), SIGNAL(closeButtonPressed()), this,
241                        SLOT(onCloseButtonPressed()));
242   ret = ret && connect(parentWidget(), SIGNAL(doubleClick(QMouseEvent *)), this,
243                        SLOT(onDoubleClick(QMouseEvent *)));
244   ret = ret && connect(&m_previewUpdateTimer, SIGNAL(timeout()), this,
245                        SLOT(performFxUpdate()));
246 
247   assert(ret);
248 
249   m_viewerTitle = (m_viewerTitle.isEmpty()) ? tr("Flipbook") : m_viewerTitle;
250   parentWidget()->setWindowTitle(m_viewerTitle);
251 }
252 
253 //-----------------------------------------------------------------------------
254 /*! add freeze button to the flipbook. called from the function
255    PreviewFxManager::openFlipBook.
256         this button will hide for re-use, at onCloseButtonPressed
257 */
addFreezeButtonToTitleBar()258 void FlipBook::addFreezeButtonToTitleBar() {
259   // If there is the button already, then reuse it.
260   if (m_freezeButton) {
261     m_freezeButton->show();
262     return;
263   }
264 
265   // If there is not, then newly make it
266   TPanel *panel = qobject_cast<TPanel *>(parentWidget());
267   if (panel) {
268     TPanelTitleBar *titleBar = panel->getTitleBar();
269     m_freezeButton           = new TPanelTitleBarButton(
270         titleBar, getIconThemePath("actions/20/pane_freeze.svg"));
271     m_freezeButton->setToolTip("Freeze");
272     titleBar->add(QPoint(-64, 0), m_freezeButton);
273     connect(m_freezeButton, SIGNAL(toggled(bool)), this, SLOT(freeze(bool)));
274     QPoint p(titleBar->width() - 64, 0);
275     m_freezeButton->move(p);
276     m_freezeButton->show();
277   }
278 }
279 
280 //-----------------------------------------------------------------------------
281 
freeze(bool on)282 void FlipBook::freeze(bool on) {
283   if (on)
284     freezePreview();
285   else
286     unfreezePreview();
287 }
288 
289 //-----------------------------------------------------------------------------
290 
focusInEvent(QFocusEvent * e)291 void FlipBook::focusInEvent(QFocusEvent *e) {
292   m_flipConsole->makeCurrent();
293   QWidget::focusInEvent(e);
294 }
295 
296 //-----------------------------------------------------------------------------
297 
298 namespace {
299 
300 enum { eBegin, eIncrement, eEnd };
301 
302 static DVGui::ProgressDialog *Pd = 0;
303 
304 class ProgressBarMessager final : public TThread::Message {
305 public:
306   int m_choice;
307   int m_val;
308   QString m_str;
ProgressBarMessager(int choice,int val,const QString & str="")309   ProgressBarMessager(int choice, int val, const QString &str = "")
310       : m_choice(choice), m_val(val), m_str(str) {}
onDeliver()311   void onDeliver() override {
312     switch (m_choice) {
313     case eBegin:
314       if (!Pd)
315         Pd = new DVGui::ProgressDialog(
316             QObject::tr("Saving previewed frames...."), QObject::tr("Cancel"),
317             0, m_val);
318       else
319         Pd->setMaximum(m_val);
320       Pd->show();
321       break;
322     case eIncrement:
323       if (Pd->wasCanceled()) {
324         delete Pd;
325         Pd = 0;
326       } else {
327         // if (m_val==Pd->maximum()) Pd->hide();
328         Pd->setValue(m_val);
329       }
330       break;
331     case eEnd: {
332       DVGui::info(m_str);
333       delete Pd;
334       Pd = 0;
335     } break;
336     default:
337       assert(false);
338     }
339   }
340 
clone() const341   TThread::Message *clone() const override {
342     return new ProgressBarMessager(*this);
343   }
344 };
345 
346 }  // namespace
347 
348 //=============================================================================
349 
LoadImagesPopup(FlipBook * flip)350 LoadImagesPopup::LoadImagesPopup(FlipBook *flip)
351     : FileBrowserPopup(tr("Load Images"), Options(WITH_APPLY_BUTTON),
352                        tr("Append"), new QFrame(0))
353     , m_flip(flip)
354     , m_minFrame(0)
355     , m_maxFrame(1000000)
356     , m_step(1)
357     , m_shrink(1) {
358   QFrame *frameRangeFrame = (QFrame *)m_customWidget;
359 
360   frameRangeFrame->setObjectName("customFrame");
361   frameRangeFrame->setFrameStyle(QFrame::StyledPanel);
362   // frameRangeFrame->setFixedHeight(30);
363 
364   m_fromField   = new DVGui::LineEdit(this);
365   m_toField     = new DVGui::LineEdit(this);
366   m_stepField   = new DVGui::LineEdit("1", this);
367   m_shrinkField = new DVGui::LineEdit("1", this);
368 
369   // Define the append/load filter types
370   m_appendFilterTypes << "3gp"
371                       << "mov"
372                       << "jpg"
373                       << "png"
374                       << "tga"
375                       << "tif"
376                       << "tiff"
377                       << "bmp"
378                       << "sgi"
379                       << "rgb"
380                       << "nol";
381 
382 #ifdef _WIN32
383   m_appendFilterTypes << "avi";
384 #endif
385 
386   m_loadFilterTypes << "tlv"
387                     << "pli" << m_appendFilterTypes;
388   m_appendFilterTypes << "psd";
389 
390   // layout
391   QHBoxLayout *frameRangeLayout = new QHBoxLayout();
392   frameRangeLayout->setMargin(5);
393   frameRangeLayout->setSpacing(5);
394   {
395     frameRangeLayout->addStretch(1);
396 
397     frameRangeLayout->addWidget(new QLabel(tr("From:")), 0);
398     frameRangeLayout->addWidget(m_fromField, 0);
399 
400     frameRangeLayout->addSpacing(5);
401 
402     frameRangeLayout->addWidget(new QLabel(tr("To:")), 0);
403     frameRangeLayout->addWidget(m_toField, 0);
404 
405     frameRangeLayout->addSpacing(10);
406 
407     frameRangeLayout->addWidget(new QLabel(tr("Step:")), 0);
408     frameRangeLayout->addWidget(m_stepField, 0);
409 
410     frameRangeLayout->addSpacing(5);
411 
412     frameRangeLayout->addWidget(new QLabel(tr("Shrink:")));
413     frameRangeLayout->addWidget(m_shrinkField, 0);
414   }
415   frameRangeFrame->setLayout(frameRangeLayout);
416 
417   // Make signal-slot connections
418   bool ret = true;
419 
420   ret = ret && connect(m_fromField, SIGNAL(editingFinished()), this,
421                        SLOT(onEditingFinished()));
422   ret = ret && connect(m_toField, SIGNAL(editingFinished()), this,
423                        SLOT(onEditingFinished()));
424   ret = ret && connect(m_stepField, SIGNAL(editingFinished()), this,
425                        SLOT(onEditingFinished()));
426   ret = ret && connect(m_shrinkField, SIGNAL(editingFinished()), this,
427                        SLOT(onEditingFinished()));
428 
429   ret = ret && connect(this, SIGNAL(filePathClicked(const TFilePath &)),
430                        SLOT(onFilePathClicked(const TFilePath &)));
431 
432   assert(ret);
433 
434   setOkText(tr("Load"));
435 
436   setWindowTitle(tr("Load / Append Images"));
437 }
438 
439 //-----------------------------------------------------------------------------
440 
onEditingFinished()441 void LoadImagesPopup::onEditingFinished() {
442   int val;
443   val    = m_fromField->text().toInt();
444   m_from = (val < m_minFrame) ? m_minFrame : val;
445   val    = m_toField->text().toInt();
446   m_to   = (val > m_maxFrame) ? m_maxFrame : val;
447 
448   if (m_to < m_from) m_to = m_from;
449 
450   val      = m_stepField->text().toInt();
451   m_step   = (val < 1) ? 1 : val;
452   val      = m_shrinkField->text().toInt();
453   m_shrink = (val < 1) ? 1 : val;
454 
455   m_fromField->setText(QString::number(m_from));
456   m_toField->setText(QString::number(m_to));
457   m_stepField->setText(QString::number(m_step));
458   m_shrinkField->setText(QString::number(m_shrink));
459 }
460 
461 //-----------------------------------------------------------------------------
462 
execute()463 bool LoadImagesPopup::execute() { return doLoad(false); }
464 
465 //-----------------------------------------------------------------------------
466 /*! Append images with apply button
467  */
executeApply()468 bool LoadImagesPopup::executeApply() { return doLoad(true); }
469 
470 //-----------------------------------------------------------------------------
471 
doLoad(bool append)472 bool LoadImagesPopup::doLoad(bool append) {
473   if (m_selectedPaths.empty()) return false;
474 
475   ::viewFile(*m_selectedPaths.begin(), m_from, m_to, m_step, m_shrink, 0,
476              m_flip, append);
477 
478   // register recent files
479   std::set<TFilePath>::const_iterator pt;
480   for (pt = m_selectedPaths.begin(); pt != m_selectedPaths.end(); ++pt) {
481     RecentFiles::instance()->addFilePath(
482         toQString(
483             TApp::instance()->getCurrentScene()->getScene()->decodeFilePath(
484                 (*pt))),
485         RecentFiles::Flip);
486   }
487   return true;
488 }
489 
490 //-----------------------------------------------------------------------------
491 
onFilePathClicked(const TFilePath & fp)492 void LoadImagesPopup::onFilePathClicked(const TFilePath &fp) {
493   TLevel::Iterator it;
494   TLevelP level;
495   TLevelReaderP lr;
496 
497   if (fp == TFilePath()) goto clear;
498 
499   lr = TLevelReaderP(fp);
500   if (!lr) goto clear;
501 
502   level = lr->loadInfo();
503 
504   if (!level || level->getFrameCount() == 0) goto clear;
505 
506   it   = level->begin();
507   m_to = m_from = it->first.getNumber();
508 
509   for (; it != level->end(); ++it) m_to = it->first.getNumber();
510 
511   if (m_from == -2 && m_to == -2) m_from = m_to = 1;
512 
513   m_minFrame = m_from;
514   m_maxFrame = m_to;
515   m_fromField->setText(QString::number(m_from));
516   m_toField->setText(QString::number(m_to));
517   return;
518 
519 clear:
520 
521   m_minFrame = 0;
522   m_maxFrame = 10000000;
523   m_fromField->setText("");
524   m_toField->setText("");
525 }
526 
527 //=============================================================================
528 
SaveImagesPopup(FlipBook * flip)529 SaveImagesPopup::SaveImagesPopup(FlipBook *flip)
530     : FileBrowserPopup(tr("Save Flipbook Images")), m_flip(flip) {
531   setOkText(tr("Save"));
532 }
533 
execute()534 bool SaveImagesPopup::execute() {
535   if (m_selectedPaths.empty()) return false;
536 
537   return m_flip->doSaveImages(*m_selectedPaths.begin());
538 }
539 
540 //=============================================================================
loadImages()541 void FlipBook::loadImages() {
542   if (!m_loadPopup) {
543     m_loadPopup = new LoadImagesPopup(this);  //, frameRangeFrame);
544     // move the initial folder to the project root
545     m_loadPopup->setFolder(
546         TProjectManager::instance()->getCurrentProjectPath().getParentDir());
547   }
548 
549   m_loadPopup->show();
550   m_loadPopup->raise();
551   m_loadPopup->activateWindow();
552 }
553 
554 //=============================================================================
555 
canAppend()556 bool FlipBook::canAppend() {
557   // Images can be appended if:
558   //  a) There is a name (in particular, an extension) representing currently
559   //  held ones.
560   //  b) This flipbook is not holding a preview (inappropriate and problematic).
561   //  c) The level has no palette. Otherwise, appended images may have a
562   //  different palette.
563   return !m_levels.empty() && !m_previewedFx && !m_palette;
564 }
565 
566 //=============================================================================
567 
saveImages()568 void FlipBook::saveImages() {
569   if (!m_savePopup) m_savePopup = new SaveImagesPopup(this);
570 
571   // initialize the default path every time
572   TOutputProperties *op = TApp::instance()
573                               ->getCurrentScene()
574                               ->getScene()
575                               ->getProperties()
576                               ->getOutputProperties();
577   m_savePopup->setFolder(TApp::instance()
578                              ->getCurrentScene()
579                              ->getScene()
580                              ->decodeFilePath(op->getPath())
581                              .getParentDir());
582   m_savePopup->setFilename(op->getPath().withFrame().withoutParentDir());
583 
584   m_savePopup->show();
585   m_savePopup->raise();
586   m_savePopup->activateWindow();
587 }
588 
589 //=============================================================================
590 
~FlipBook()591 FlipBook::~FlipBook() {
592   if (m_loadPopup) delete m_loadPopup;
593   if (m_savePopup) delete m_savePopup;
594 }
595 
596 //=============================================================================
597 
doSaveImages(TFilePath fp)598 bool FlipBook::doSaveImages(TFilePath fp) {
599   QStringList formats;
600   TLevelWriter::getSupportedFormats(formats, true);
601   Tiio::Writer::getSupportedFormats(formats, true);
602 
603   ToonzScene *scene = TApp::instance()->getCurrentScene()->getScene();
604   TOutputProperties *outputSettings =
605       scene->getProperties()->getOutputProperties();
606 
607   std::string ext = fp.getType();
608 
609   // Open a notice that the previewFx is rendered in 8bpc regardless of the
610   // output settings.
611   if (m_isPreviewFx && outputSettings->getRenderSettings().m_bpp == 64) {
612     QString question =
613         "Save previewed images :\nImages will be saved in 8 bit per channel "
614         "with this command.\nDo you want to save images?";
615     int ret =
616         DVGui::MsgBox(question, QObject::tr("Save"), QObject::tr("Cancel"), 0);
617     if (ret == 2 || ret == 0) return false;
618   }
619 
620 #ifdef _WIN32
621   if (ext == "avi") {
622     TPropertyGroup *props = outputSettings->getFileFormatProperties(ext);
623     std::string codecName = props->getProperty(0)->getValueAsString();
624     TDimension res        = scene->getCurrentCamera()->getRes();
625     if (!AviCodecRestrictions::canWriteMovie(::to_wstring(codecName), res)) {
626       QString msg(
627           QObject::tr("The resolution of the output camera does not fit with "
628                       "the options chosen for the output file format."));
629       DVGui::warning(msg);
630       return false;
631     }
632   }
633 #endif
634 
635   if (ext == "") {
636     ext = outputSettings->getPath().getType();
637     fp  = fp.withType(ext);
638   }
639   if (fp.getName() == "") {
640     DVGui::warning(
641         tr("The file name cannot be empty or contain any of the following "
642            "characters:(new line)  \\ / : * ? \"  |"));
643     return false;
644   }
645 
646   if (!formats.contains(QString::fromStdString(ext))) {
647     DVGui::warning(
648         tr("It is not possible to save because the selected file format is not "
649            "supported."));
650     return false;
651   }
652 
653   int from, to, step;
654   m_flipConsole->getFrameRange(from, to, step);
655 
656   if (m_currentFrameToSave != 0) {
657     DVGui::info("Already saving!");
658     return true;
659   }
660 
661   if (TFileType::getInfo(fp) == TFileType::RASTER_IMAGE || ext == "pct" ||
662       ext == "pic" || ext == "pict")  // pct e' un formato"livello" (ha i
663                                       // settings di quicktime) ma fatto di
664                                       // diversi frames
665     fp = fp.withFrame(TFrameId::EMPTY_FRAME);
666 
667   fp          = scene->decodeFilePath(fp);
668   bool exists = TFileStatus(fp.getParentDir()).doesExist();
669   if (!exists) {
670     try {
671       TFilePath parent = fp.getParentDir();
672       TSystem::mkDir(parent);
673       DvDirModel::instance()->refreshFolder(parent.getParentDir());
674     } catch (TException &e) {
675       DVGui::error("Cannot create " + toQString(fp.getParentDir()) + " : " +
676                    QString(::to_string(e.getMessage()).c_str()));
677       return false;
678     } catch (...) {
679       DVGui::error("Cannot create " + toQString(fp.getParentDir()));
680       return false;
681     }
682   }
683 
684   if (TSystem::doesExistFileOrLevel(fp)) {
685     QString question(tr("File %1 already exists.\nDo you want to overwrite it?")
686                          .arg(toQString(fp)));
687     int ret = DVGui::MsgBox(question, QObject::tr("Overwrite"),
688                             QObject::tr("Cancel"));
689     if (ret == 2) return false;
690   }
691 
692   try {
693     m_lw = TLevelWriterP(fp,
694                          outputSettings->getFileFormatProperties(fp.getType()));
695   } catch (...) {
696     DVGui::error("It is not possible to save Flipbook content.");
697     return false;
698   }
699 
700   m_lw->setFrameRate(outputSettings->getFrameRate());
701 
702   m_currentFrameToSave = 1;
703 
704   ProgressBarMessager(eBegin, m_framesCount).sendBlocking();
705 
706   QTimer::singleShot(50, this, SLOT(saveImage()));
707   return true;
708 }
709 
710 //-----------------------------------------------------------------------------
711 
saveImage()712 void FlipBook::saveImage() {
713   static int savedFrames = 0;
714 
715   assert(Pd);
716   int from, to, step;
717 
718   m_flipConsole->getFrameRange(from, to, step);
719 
720   for (; m_currentFrameToSave <= m_framesCount; m_currentFrameToSave++) {
721     ProgressBarMessager(eIncrement, m_currentFrameToSave).sendBlocking();
722     if (!Pd) break;
723 
724     int actualFrame = from + (m_currentFrameToSave - 1) * step;
725     TImageP img     = getCurrentImage(actualFrame);
726     if (!img) continue;
727     TImageWriterP writer = m_lw->getFrameWriter(TFrameId(actualFrame));
728     bool failureOnSaving = false;
729     if (!writer) continue;
730     try {
731       writer->save(img);
732     } catch (...) {
733       QString str(tr("It is not possible to save Flipbook content."));
734       ProgressBarMessager(eEnd, 0, str).send();
735       m_currentFrameToSave = 0;
736       m_lw                 = TLevelWriterP();
737       savedFrames          = 0;
738       return;
739     }
740     savedFrames++;
741     //		if (!m_pb->changeFraction(m_currentFrameToSave,
742     // TApp::instance()->getCurrentXsheet()->getXsheet()->getFrameCount()))
743     //			break;
744     m_currentFrameToSave++;
745 
746     QTimer::singleShot(50, this, SLOT(saveImage()));
747     return;
748   }
749 
750   QString str = tr("Saved %1 frames out of %2 in %3")
751                     .arg(std::to_string(savedFrames).c_str())
752                     .arg(std::to_string(m_framesCount).c_str())
753                     .arg(::to_string(m_lw->getFilePath()).c_str());
754 
755   if (!Pd) str = "Canceled! " + str;
756 
757   ProgressBarMessager(eEnd, 0, str).send();
758 
759   m_currentFrameToSave = 0;
760   m_lw                 = TLevelWriterP();
761   savedFrames          = 0;
762 }
763 
764 //=============================================================================
765 
onButtonPressed(FlipConsole::EGadget button)766 void FlipBook::onButtonPressed(FlipConsole::EGadget button) {
767   switch (button) {
768   case FlipConsole::eSound:
769     m_playSound = !m_playSound;
770     break;
771 
772   case FlipConsole::eHisto:
773     m_imageViewer->showHistogram();
774     break;
775 
776   case FlipConsole::eSaveImg: {
777     TRect loadbox = m_loadbox;
778     m_loadbox     = TRect();
779     TImageP img   = getCurrentImage(m_flipConsole->getCurrentFrame());
780     m_loadbox     = loadbox;
781     if (!img) {
782       DVGui::warning(tr("There are no rendered images to save."));
783       return;
784     } else if ((TVectorImageP)img) {
785       DVGui::warning(
786           tr("It is not possible to take or compare snapshots for Toonz vector "
787              "levels."));
788       return;
789     }
790     TRasterImageP ri(img);
791     TToonzImageP ti(img);
792     TImageP clonedImg;
793     if (ri)
794       clonedImg = TRasterImageP(ri->getRaster()->clone());
795     else
796       clonedImg = TToonzImageP(ti->getRaster()->clone(), ti->getSavebox());
797     TImageCache::instance()->add(QString("TnzCompareImg"), clonedImg);
798     break;
799   }
800 
801   case FlipConsole::eCompare:
802     if ((TVectorImageP)getCurrentImage(m_flipConsole->getCurrentFrame())) {
803       DVGui::warning(
804           tr("It is not possible to take or compare snapshots for Toonz vector "
805              "levels."));
806       m_flipConsole->setChecked(FlipConsole::eCompare, false);
807       return;
808     }
809     break;
810 
811   case FlipConsole::eSave:
812     saveImages();
813     break;
814   default:
815     break;
816   }
817 }
818 
819 //=============================================================================
820 // FlipBookPool
821 //-----------------------------------------------------------------------------
822 
823 /*! \class FlipBookPool
824                 \brief The FlipBookPool class is used to store used flipbook
825    viewers.
826 
827     Flipbooks are generally intended as temporary but friendly floating widgets,
828     that gets displayed when a rendered scene or image needs to be shown.
829     Since a user may require that the geometry of a flipbook is to be remembered
830     between rendering tasks - perhaps even between different Toonz sessions -
831     flipbooks are always stored for later use in a \b FlipBookPool class.
832 
833     This class implements the basical features to \b pop a flipbook from the
834    pool
835     or \b push a used one; plus, it provides the \b save and \b load functions
836     for persistent storage between Toonz sessions.
837 
838     \sa FlipBook class.
839 */
840 
FlipBookPool()841 FlipBookPool::FlipBookPool() : m_overallFlipCount(0) {
842   qRegisterMetaType<ImagePainter::VisualSettings>(
843       "ImagePainter::VisualSettings");
844 }
845 
846 //-----------------------------------------------------------------------------
847 
~FlipBookPool()848 FlipBookPool::~FlipBookPool() {}
849 
850 //-----------------------------------------------------------------------------
851 
instance()852 FlipBookPool *FlipBookPool::instance() {
853   static FlipBookPool poolInstance;
854   return &poolInstance;
855 }
856 
857 //-----------------------------------------------------------------------------
858 
push(FlipBook * flipbook)859 void FlipBookPool::push(FlipBook *flipbook) {
860   m_pool.insert(pair<int, FlipBook *>(flipbook->getPoolIndex(), flipbook));
861 }
862 
863 //-----------------------------------------------------------------------------
864 
865 //! Extracts the first unused flipbook from the flipbook pool.
866 //! If all known flipbooks are shown, allocates a new flipbook with the
867 //! first unused flipbook geometry in a geometry pool.
868 //! Again, if all recorded geometry are used by some existing flipbook, a
869 //! default geometry is used.
pop()870 FlipBook *FlipBookPool::pop() {
871   FlipBook *flipbook;
872   TPanel *panel;
873 
874   TMainWindow *currentRoom = TApp::instance()->getCurrentRoom();
875 
876   if (m_pool.empty()) {
877     panel = TPanelFactory::createPanel(currentRoom, "FlipBook");
878     panel->setFloating(true);
879 
880     flipbook = static_cast<FlipBook *>(panel->widget());
881 
882     // Set geometry
883     static int x = 0, y = 0;
884     if (m_geometryPool.empty()) {
885       panel->setGeometry(x += 50, y += 50, 400, 300);
886       flipbook->setPoolIndex(m_overallFlipCount);
887       m_overallFlipCount++;
888     } else {
889       flipbook->setPoolIndex(m_geometryPool.begin()->first);
890       QRect geometry(m_geometryPool.begin()->second);
891       panel->setGeometry(geometry);
892       if ((geometry & QApplication::desktop()->availableGeometry(panel))
893               .isEmpty())
894         panel->move(x += 50, y += 50);
895       m_geometryPool.erase(m_geometryPool.begin());
896     }
897   } else {
898     flipbook = m_pool.begin()->second;
899     panel    = (TPanel *)flipbook->parent();
900     m_pool.erase(m_pool.begin());
901   }
902 
903   // The panel need to be added to currentRoom's layout control.
904   currentRoom->addDockWidget(panel);
905   panel->raise();
906   panel->show();
907 
908   return flipbook;
909 }
910 
911 //-----------------------------------------------------------------------------
912 
913 //! Saves the content of this flipbook pool.
save() const914 void FlipBookPool::save() const {
915   QSettings history(toQString(m_historyPath), QSettings::IniFormat);
916   history.clear();
917 
918   history.setValue("count", m_overallFlipCount);
919 
920   history.beginGroup("flipbooks");
921 
922   std::map<int, FlipBook *>::const_iterator it;
923   for (it = m_pool.begin(); it != m_pool.end(); ++it) {
924     history.beginGroup(QString::number(it->first));
925     TPanel *panel = static_cast<TPanel *>(it->second->parent());
926     history.setValue("geometry", panel->geometry());
927     history.endGroup();
928   }
929 
930   std::map<int, QRect>::const_iterator jt;
931   for (jt = m_geometryPool.begin(); jt != m_geometryPool.end(); ++jt) {
932     history.beginGroup(QString::number(jt->first));
933     history.setValue("geometry", jt->second);
934     history.endGroup();
935   }
936 
937   history.endGroup();
938 }
939 
940 //-----------------------------------------------------------------------------
941 
942 //! Loads the pool from input history
load(const TFilePath & historyPath)943 void FlipBookPool::load(const TFilePath &historyPath) {
944   QSettings history(toQString(historyPath), QSettings::IniFormat);
945   m_historyPath = historyPath;
946 
947   m_pool.clear();
948   m_geometryPool.clear();
949 
950   m_overallFlipCount = history.value("count").toInt();
951 
952   history.beginGroup("flipbooks");
953 
954   QStringList flipBooks(history.childGroups());
955   QStringList::iterator it;
956   for (it = flipBooks.begin(); it != flipBooks.end(); ++it) {
957     history.beginGroup(*it);
958 
959     // Retrieve flipbook geometry
960     QVariant geom = history.value("geometry");
961 
962     // Insert geometry
963     m_geometryPool.insert(pair<int, QRect>(it->toInt(), geom.toRect()));
964 
965     history.endGroup();
966   }
967 
968   history.endGroup();
969 }
970 
971 //=============================================================================
972 
973 //! Returns the level frame number corresponding to passed flipbook index
flipbookIndexToLevelFrame(int index)974 TFrameId FlipBook::Level::flipbookIndexToLevelFrame(int index) {
975   TLevel::Iterator it;
976   int levelPos;
977   if (m_incrementalIndexing) {
978     levelPos = (index - 1) * m_step;
979     it       = m_level->getTable()->find(m_fromIndex);
980     advance(it, levelPos);
981   } else {
982     levelPos = m_fromIndex + (index - 1) * m_step;
983     it       = m_level->getTable()->find(levelPos);
984   }
985   if (it == m_level->end()) return TFrameId();
986   return it->first;
987 }
988 
989 //-----------------------------------------------------------------------------
990 
991 //! Returns the number of flipbook indexes available for this level
getIndexesCount()992 int FlipBook::Level::getIndexesCount() {
993   return m_incrementalIndexing ? (m_level->getFrameCount() - 1) / m_step + 1
994                                : (m_toIndex - m_fromIndex) / m_step + 1;
995 }
996 
997 //=============================================================================
998 
isSavable() const999 bool FlipBook::isSavable() const {
1000   if (m_levels.empty()) return false;
1001 
1002   for (int i = 0; i < m_levels.size(); i++)
1003     if (m_levels[i].m_fp != TFilePath() &&
1004         (m_levels[i].m_fp.getType() == "tlv" ||
1005          m_levels[i].m_fp.getType() == "pli"))
1006       return false;
1007 
1008   return true;
1009 }
1010 
1011 //=============================================================================
1012 
1013 /*! Set the level contained in \b fp to FlipBook; if level exist show in image
1014                 viewer its first frame, set current frame to 1.
1015                 It's possible to change level palette, in fact if \b palette is
1016    different
1017                 from 0 set level palette to \b palette.
1018 */
setLevel(const TFilePath & fp,TPalette * palette,int from,int to,int step,int shrink,TSoundTrack * snd,bool append,bool isToonzOutput)1019 void FlipBook::setLevel(const TFilePath &fp, TPalette *palette, int from,
1020                         int to, int step, int shrink, TSoundTrack *snd,
1021                         bool append, bool isToonzOutput) {
1022   try {
1023     if (!append) {
1024       clearCache();
1025       m_levelNames.clear();
1026       m_levels.clear();
1027     }
1028     m_snd = 0;
1029     m_xl  = 0;
1030 
1031     m_flipConsole->enableProgressBar(false);
1032     m_flipConsole->setProgressBarStatus(0);
1033     m_flipConsole->enableButton(FlipConsole::eSound, snd != 0);
1034     m_flipConsole->enableButton(FlipConsole::eDefineLoadBox, true);
1035     m_flipConsole->enableButton(FlipConsole::eUseLoadBox, true);
1036     if (fp == TFilePath()) return;
1037 
1038     m_shrink = shrink;
1039 
1040     if (fp.getDots() == ".." && fp.getType() != "noext")
1041       m_levelNames.push_back(toQString(fp.withoutParentDir().withFrame()));
1042     else
1043       m_levelNames.push_back(toQString(fp.withoutParentDir()));
1044 
1045     m_snd = snd;
1046 
1047     if (TSystem::doesExistFileOrLevel(fp))  // is a  viewfile
1048     {
1049       // m_flipConsole->enableButton(FlipConsole::eCheckBg,
1050       // true);//fp.getType()!="pli");
1051 
1052       m_lr = TLevelReaderP(fp);
1053 
1054       bool supportsRandomAccess = doesSupportRandomAccess(fp, isToonzOutput);
1055       if (supportsRandomAccess) m_lr->enableRandomAccessRead(isToonzOutput);
1056 
1057       bool randomAccessRead    = supportsRandomAccess && isToonzOutput;
1058       bool incrementalIndexing = m_isPreviewFx ? true : false;
1059 
1060       TLevelP level = m_lr->loadInfo();
1061 
1062       if (!level || level->getFrameCount() == 0) {
1063         if (m_flags & eDontKeepFilesOpened) m_lr = TLevelReaderP();
1064         return;
1065       }
1066 
1067       // For the color model, get the reference fids from palette and delete
1068       // unneeded from the table
1069       if (m_imageViewer->isColorModel() && palette) {
1070         std::vector<TFrameId> fids = palette->getRefLevelFids();
1071 
1072         // when loading a single-frame, standard raster image into the
1073         // ColorModel, skip here.
1074         // If the fid == NO_FRAME(=-2), fids stores 0.
1075         if (!fids.empty() && !(fids.size() == 1 && fids[0].getNumber() == 0)) {
1076           // make the fid list to be deleted
1077           std::vector<TFrameId> deleteList;
1078           TLevel::Iterator it;
1079           for (it = level->begin(); it != level->end(); it++) {
1080             // If the fid is not included in the reference list, then delete it
1081             int i;
1082             for (i = 0; i < (int)fids.size(); i++) {
1083               if (fids[i].getNumber() == it->first.getNumber()) break;
1084             }
1085             if (i == fids.size()) {
1086               deleteList.push_back(it->first);
1087             }
1088           }
1089           // delete list items here
1090           if (!deleteList.empty())
1091             for (int i = 0; i < (int)deleteList.size(); i++)
1092               level->getTable()->erase(deleteList[i]);
1093         }
1094       }
1095 
1096       int fromIndex, toIndex;
1097 
1098       // in order to avoid that the current frame unexpectedly moves to 1 on the
1099       // Color Model once editing the style
1100       int current = -1;
1101 
1102       if (from == -1 && to == -1) {
1103         fromIndex = level->begin()->first.getNumber();
1104         toIndex   = (--level->end())->first.getNumber();
1105         if (m_imageViewer->isColorModel())
1106           current           = m_flipConsole->getCurrentFrame();
1107         incrementalIndexing = true;
1108       } else {
1109         TLevel::Iterator it = level->begin();
1110 
1111         // Adjust the frame interval to read. There is one special case:
1112         //  If the level read did not support random access, *AND* the level to
1113         //  show was just rendered,
1114         //  we have to assume that no level update happened, and the
1115         //  from-to-step infos are lost.
1116         //  So, shift the requested interval from 1 and place step to 1.
1117         fromIndex = from;
1118         toIndex   = to;
1119         if (isToonzOutput && !supportsRandomAccess) {
1120           fromIndex = 1;
1121           toIndex   = level->getFrameCount();
1122           step      = 1;
1123         }
1124 
1125         if (level->begin()->first.getNumber() != TFrameId::NO_FRAME) {
1126           fromIndex = std::max(fromIndex, level->begin()->first.getNumber());
1127           toIndex   = std::min(toIndex, (--level->end())->first.getNumber());
1128         } else {
1129           fromIndex           = level->begin()->first.getNumber();
1130           toIndex             = (--level->end())->first.getNumber();
1131           incrementalIndexing = true;
1132         }
1133 
1134         // Workaround to display simple background images when loading from
1135         // the right-click menu context
1136         fromIndex = std::min(fromIndex, toIndex);
1137       }
1138 
1139       Level levelToPush(level, fp, fromIndex, toIndex, step);
1140       levelToPush.m_randomAccessRead    = randomAccessRead;
1141       levelToPush.m_incrementalIndexing = incrementalIndexing;
1142 
1143       int formatIdx = Preferences::instance()->matchLevelFormat(fp);
1144       if (formatIdx >= 0 &&
1145           Preferences::instance()
1146               ->levelFormat(formatIdx)
1147               .m_options.m_premultiply) {
1148         levelToPush.m_premultiply = true;
1149       }
1150 
1151       m_levels.push_back(levelToPush);
1152 
1153       // Get the frames count to be shown in this flipbook level
1154       m_framesCount = levelToPush.getIndexesCount();
1155 
1156       assert(m_framesCount <= level->getFrameCount());
1157 
1158       // this value will be used in loadAndCacheAllTlvImages later
1159       int addingFrameAmount = m_framesCount;
1160 
1161       if (append && !m_levels.empty()) {
1162         int oldFrom, oldTo, oldStep;
1163         m_flipConsole->getFrameRange(oldFrom, oldTo, oldStep);
1164         assert(oldFrom == 1);
1165         assert(oldStep == 1);
1166         m_framesCount += oldTo;
1167       }
1168 
1169       m_flipConsole->setFrameRange(1, m_framesCount, 1, current);
1170 
1171       if (palette && level->getPalette() != palette) level->setPalette(palette);
1172 
1173       m_palette = level->getPalette();
1174 
1175       const TImageInfo *ii = m_lr->getImageInfo();
1176 
1177       if (ii) m_dim = TDimension(ii->m_lx / m_shrink, ii->m_ly / m_shrink);
1178 
1179       int levelFrameCount = 0;
1180       for (int lev = 0; lev < m_levels.size(); lev++)
1181         levelFrameCount += m_levels[lev].m_level->getFrameCount();
1182 
1183       if (levelFrameCount == 1)
1184         m_title = "  ::  1 Frame";
1185       else
1186         m_title = "  ::  " + QString::number(levelFrameCount) + " Frames";
1187 
1188       // color model does not concern about the pixel size
1189       if (ii && !m_imageViewer->isColorModel())
1190         m_title = m_title + "  ::  " + QString::number(ii->m_lx) + "x" +
1191                   QString::number(ii->m_ly) + " Pixels";
1192 
1193       if (shrink > 1)
1194         m_title = m_title + "  ::  " + "Shrink: " + QString::number(shrink);
1195 
1196       // when using the flip module, this signal is to show the loaded level
1197       // names in application's title bar
1198       QString arg = QString("Flip : %1").arg(m_levelNames[0]);
1199       emit imageLoaded(arg);
1200 
1201       // When viewing the tlv, try to cache all frames at the beginning.
1202       if (!m_imageViewer->isColorModel() && fp.getType() == "tlv" &&
1203           !(m_flags & eDontKeepFilesOpened) && !m_isPreviewFx) {
1204         loadAndCacheAllTlvImages(levelToPush,
1205                                  m_framesCount - addingFrameAmount + 1,  // from
1206                                  m_framesCount);                         // to
1207       }
1208 
1209       // An old archived bug says that simulatenous open for read of the same
1210       // tlv are not allowed...
1211       // if(m_lr && m_lr->getFilePath().getType()=="tlv")
1212       //  m_lr = TLevelReaderP();
1213     } else  // is a render
1214     {
1215       m_flipConsole->enableButton(FlipConsole::eCheckBg, true);
1216       m_previewedFx = 0;
1217       m_previewXsh  = 0;
1218       m_levels.clear();
1219       m_flipConsole->setFrameRange(from, to, step);
1220 
1221       m_framesCount = (to - from) / step + 1;
1222       m_title       = tr("Rendered Frames  ::  From %1 To %2  ::  Step %3")
1223                     .arg(QString::number(from))
1224                     .arg(QString::number(to))
1225                     .arg(QString::number(step));
1226       if (shrink > 1)
1227         m_title = m_title + tr("  ::  Shrink ") + QString::number(shrink);
1228     }
1229 
1230     // parentWidget()->setWindowTitle(m_title);
1231     m_imageViewer->setHistogramEnable(true);
1232     m_imageViewer->setHistogramTitle(m_levelNames[0]);
1233     m_flipConsole->enableButton(FlipConsole::eSave, isSavable());
1234     m_flipConsole->showCurrentFrame();
1235     if (m_flags & eDontKeepFilesOpened) m_lr = TLevelReaderP();
1236   } catch (...) {
1237     return;
1238   }
1239 }
1240 
1241 //-----------------------------------------------------------------------------
1242 
setTitle(const QString & title)1243 void FlipBook::setTitle(const QString &title) {
1244   m_viewerTitle = title;
1245   if (!m_previewedFx && !m_levelNames.empty())
1246     m_title = m_viewerTitle + "  ::  " + m_levelNames[0];
1247   else
1248     m_title = m_viewerTitle;
1249 }
1250 
1251 //-----------------------------------------------------------------------------
1252 
setLevel(TXshSimpleLevel * xl)1253 void FlipBook::setLevel(TXshSimpleLevel *xl) {
1254   try {
1255     clearCache();
1256 
1257     m_xl = xl;
1258 
1259     m_levelNames.push_back(QString::fromStdWString(xl->getName()));
1260     m_snd         = 0;
1261     m_previewedFx = 0;
1262     m_previewXsh  = 0;
1263     m_levels.clear();
1264 
1265     m_flipConsole->enableButton(FlipConsole::eSound, false);
1266 
1267     m_shrink = 1;
1268     int step = 1;
1269 
1270     m_framesCount = (m_xl->getFrameCount() - 1) / step + 1;
1271     m_flipConsole->setFrameRange(1, m_framesCount, step);
1272     m_flipConsole->enableProgressBar(false);
1273     m_flipConsole->setProgressBarStatus(0);
1274     m_palette = m_xl->getPalette();
1275 
1276     const LevelProperties *p = m_xl->getProperties();
1277 
1278     m_title = m_viewerTitle + "  ::  " + m_levelNames[0];
1279 
1280     if (m_framesCount == 1)
1281       m_title = m_title + "  ::  1 Frame";
1282     else
1283       m_title = m_title + "  ::  " + QString::number(m_framesCount) + " Frames";
1284 
1285     if (p) m_dim = p->getImageRes();
1286 
1287     if (p)
1288       m_title = m_title + "  ::  " + QString::number(p->getImageRes().lx) +
1289                 "x" + QString::number(p->getImageRes().ly) + " Pixels";
1290 
1291     if (m_shrink > 1)
1292       m_title = m_title + "  ::  " + "Shrink: " + QString::number(m_shrink);
1293 
1294     m_imageViewer->setHistogramEnable(true);
1295     m_imageViewer->setHistogramTitle(m_levelNames[0]);
1296 
1297     m_flipConsole->showCurrentFrame();
1298 
1299   } catch (...) {
1300     return;
1301   }
1302 }
1303 
1304 //-----------------------------------------------------------------------------
1305 
setLevel(TFx * previewedFx,TXsheet * xsh,TLevel * level,TPalette * palette,int from,int to,int step,int currentFrame,TSoundTrack * snd)1306 void FlipBook::setLevel(TFx *previewedFx, TXsheet *xsh, TLevel *level,
1307                         TPalette *palette, int from, int to, int step,
1308                         int currentFrame, TSoundTrack *snd) {
1309   m_xl          = 0;
1310   m_previewedFx = previewedFx;
1311   m_previewXsh  = xsh;
1312   m_isPreviewFx = true;
1313   m_levels.clear();
1314   m_levels.push_back(Level(level, TFilePath(), from - 1, to - 1, step));
1315   m_levelNames.clear();
1316   m_levelNames.push_back(QString::fromStdString(level->getName()));
1317   m_title = m_viewerTitle;
1318   m_flipConsole->setFrameRange(from, to, step, currentFrame);
1319   m_flipConsole->enableProgressBar(true);
1320 
1321   m_flipConsole->enableButton(FlipConsole::eDefineLoadBox, false);
1322   m_flipConsole->enableButton(FlipConsole::eUseLoadBox, false);
1323   m_flipConsole->enableButton(FlipConsole::eSound, snd != 0);
1324   m_snd         = snd;
1325   m_framesCount = (to - from) / step + 1;
1326 
1327   m_imageViewer->setHistogramEnable(true);
1328   m_imageViewer->setHistogramTitle(m_levelNames[0]);
1329   m_flipConsole->showCurrentFrame();
1330 }
1331 
1332 //-----------------------------------------------------------------------------
1333 
getPreviewedFx() const1334 TFx *FlipBook::getPreviewedFx() const {
1335   return m_isPreviewFx ? m_previewedFx.getPointer() : 0;
1336 }
1337 
1338 //-----------------------------------------------------------------------------
1339 
getPreviewXsheet() const1340 TXsheet *FlipBook::getPreviewXsheet() const {
1341   return m_isPreviewFx ? m_previewXsh.getPointer() : 0;
1342 }
1343 
1344 //-----------------------------------------------------------------------------
1345 
getPreviewedImageGeometry() const1346 TRectD FlipBook::getPreviewedImageGeometry() const {
1347   if (!m_isPreviewFx) return TRectD();
1348 
1349   // Build viewer's geometry
1350   QRect viewerGeom(m_imageViewer->geometry());
1351   viewerGeom.adjust(-1, -1, 1, 1);
1352   TRectD viewerGeomD(viewerGeom.left(), viewerGeom.top(),
1353                      viewerGeom.right() + 1, viewerGeom.bottom() + 1);
1354   TPointD viewerCenter((viewerGeomD.x0 + viewerGeomD.x1) * 0.5,
1355                        (viewerGeomD.y0 + viewerGeomD.y1) * 0.5);
1356 
1357   // NOTE: The above adjust() is imposed to counter the geometry removal
1358   // specified in function
1359   // FlipBook::onDoubleClick.
1360 
1361   // Build viewer-to-camera affine
1362   TAffine viewToCam(m_imageViewer->getViewAff().inv() *
1363                     TTranslation(-viewerCenter));
1364 
1365   return viewToCam * viewerGeomD;
1366 }
1367 
1368 //-----------------------------------------------------------------------------
1369 
schedulePreviewedFxUpdate()1370 void FlipBook::schedulePreviewedFxUpdate() {
1371   if (m_previewedFx)
1372     m_previewUpdateTimer.start(
1373         1000);  // The effective fx update will happen in 1 msec.
1374 }
1375 
1376 //-----------------------------------------------------------------------------
1377 
performFxUpdate()1378 void FlipBook::performFxUpdate() {
1379   // refresh only when the subcamera is active
1380   if (PreviewFxManager::instance()->isSubCameraActive(m_previewedFx))
1381     PreviewFxManager::instance()->refreshView(m_previewedFx);
1382 }
1383 
1384 //-----------------------------------------------------------------------------
1385 
regenerate()1386 void FlipBook::regenerate() {
1387   PreviewFxManager::instance()->reset(TFxP(m_previewedFx));
1388 }
1389 
1390 //-----------------------------------------------------------------------------
1391 
regenerateFrame()1392 void FlipBook::regenerateFrame() {
1393   PreviewFxManager::instance()->reset(m_previewedFx, getCurrentFrame() - 1);
1394 }
1395 
1396 //-----------------------------------------------------------------------------
1397 
clonePreview()1398 void FlipBook::clonePreview() {
1399   if (!m_previewedFx) return;
1400 
1401   FlipBook *newFlip =
1402       PreviewFxManager::instance()->showNewPreview(m_previewedFx, true);
1403   newFlip->m_imageViewer->setViewAff(m_imageViewer->getViewAff());
1404   PreviewFxManager::instance()->refreshView(m_previewedFx);
1405 }
1406 
1407 //-----------------------------------------------------------------------------
1408 
freezePreview()1409 void FlipBook::freezePreview() {
1410   if (!m_previewedFx) return;
1411 
1412   PreviewFxManager::instance()->freeze(this);
1413 
1414   m_freezed = true;
1415 
1416   // sync the button state when triggered by shotcut
1417   if (m_freezeButton) m_freezeButton->setPressed(true);
1418 }
1419 
1420 //-----------------------------------------------------------------------------
1421 
unfreezePreview()1422 void FlipBook::unfreezePreview() {
1423   if (!m_previewedFx) return;
1424 
1425   PreviewFxManager::instance()->unfreeze(this);
1426 
1427   m_freezed = false;
1428 
1429   // sync the button state when triggered by shotcut
1430   if (m_freezeButton) m_freezeButton->setPressed(false);
1431 }
1432 
1433 //-----------------------------------------------------------------------------
1434 
setProgressBarStatus(const std::vector<UCHAR> * pbStatus)1435 void FlipBook::setProgressBarStatus(const std::vector<UCHAR> *pbStatus) {
1436   m_flipConsole->setProgressBarStatus(pbStatus);
1437 }
1438 
1439 //-----------------------------------------------------------------------------
1440 
getProgressBarStatus() const1441 const std::vector<UCHAR> *FlipBook::getProgressBarStatus() const {
1442   return m_flipConsole->getProgressBarStatus();
1443 }
1444 
1445 //-----------------------------------------------------------------------------
1446 
showFrame(int frame)1447 void FlipBook::showFrame(int frame) {
1448   if (frame < 0) return;
1449   m_flipConsole->setCurrentFrame(frame);
1450   m_flipConsole->showCurrentFrame();
1451 }
1452 
1453 //-----------------------------------------------------------------------------
1454 
playAudioFrame(int frame)1455 void FlipBook::playAudioFrame(int frame) {
1456   static bool first = true;
1457   static bool audioCardInstalled;
1458   if (!m_snd || !m_playSound) return;
1459 
1460   if (first) {
1461     audioCardInstalled = TSoundOutputDevice::installed();
1462     first              = false;
1463   }
1464 
1465   if (!audioCardInstalled) return;
1466 
1467   if (!m_player) {
1468     m_player = new TSoundOutputDevice();
1469     m_player->attach(this);
1470   }
1471   if (m_player) {
1472     // Flipbook does not currently support double fps - thus, casting to int in
1473     // soundtrack playback, too
1474     int fps = TApp::instance()
1475                   ->getCurrentScene()
1476                   ->getScene()
1477                   ->getProperties()
1478                   ->getOutputProperties()
1479                   ->getFrameRate();
1480 
1481     int samplePerFrame = int(m_snd->getSampleRate()) / fps;
1482     TINT32 firstSample = (frame - 1) * samplePerFrame;
1483     TINT32 lastSample  = firstSample + samplePerFrame;
1484 
1485     try {
1486       m_player->play(m_snd, firstSample, lastSample, false, false);
1487     } catch (TSoundDeviceException &e) {
1488       std::string msg;
1489       if (e.getType() == TSoundDeviceException::UnsupportedFormat) {
1490         try {
1491           TSoundTrackFormat fmt =
1492               m_player->getPreferredFormat(m_snd->getFormat());
1493           m_player->play(TSop::convert(m_snd, fmt), firstSample, lastSample,
1494                          false, false);
1495         } catch (TSoundDeviceException &ex) {
1496           throw TException(ex.getMessage());
1497           return;
1498         }
1499       }
1500     }
1501   }
1502 }
1503 
1504 //-----------------------------------------------------------------------------
1505 
getCurrentImage(int frame)1506 TImageP FlipBook::getCurrentImage(int frame) {
1507   std::string id = "";
1508   TFrameId fid;
1509   TFilePath fp;
1510 
1511   bool randomAccessRead    = false;
1512   bool incrementalIndexing = false;
1513   bool premultiply         = false;
1514   if (m_xl)  // is an xsheet level
1515   {
1516     if (m_xl->getFrameCount() <= 0) return 0;
1517     return m_xl->getFrame(m_xl->index2fid(frame - 1), false);
1518   } else if (!m_levels.empty())  // is a viewfile or a previewFx
1519   {
1520     TLevelP level;
1521     QString levelName;
1522     int from, to, step;
1523     m_flipConsole->getFrameRange(from, to, step);
1524 
1525     int frameIndex = m_previewedFx ? ((frame - from) / step) + 1 : frame;
1526 
1527     int i = 0;
1528     // Search all subsequent levels on the flipbook and retrieve the one
1529     // containing the required frame
1530     for (i = 0; i < m_levels.size(); i++) {
1531       int frameIndexesCount = m_levels[i].getIndexesCount();
1532       if (frameIndex > 0 && frameIndex <= frameIndexesCount) break;
1533       frameIndex -= frameIndexesCount;
1534     }
1535 
1536     if (i == m_levels.size() || frame < 0) return 0;
1537 
1538     frame--;
1539 
1540     // Now, get the right frame from the level
1541 
1542     level               = m_levels[i].m_level;
1543     fp                  = m_levels[i].m_fp;  // fp=empty when previewing fx
1544     randomAccessRead    = m_levels[i].m_randomAccessRead;
1545     incrementalIndexing = m_levels[i].m_incrementalIndexing;
1546     levelName           = m_levelNames[i];
1547     fid                 = m_levels[i].flipbookIndexToLevelFrame(frameIndex);
1548     premultiply         = m_levels[i].m_premultiply;
1549     if (fid == TFrameId()) return 0;
1550     id = levelName.toStdString() + fid.expand(TFrameId::NO_PAD) +
1551          ((m_isPreviewFx) ? "" : ::to_string(this));
1552 
1553     if (!m_isPreviewFx)
1554       m_title1 = m_viewerTitle + " :: " + fp.withoutParentDir().withFrame(fid);
1555     else
1556       m_title1 = "";
1557   } else if (m_levelNames.empty())
1558     return 0;
1559   else  // is a render
1560     id = m_levelNames[0].toStdString() + std::to_string(frame);
1561 
1562   bool showSub = m_flipConsole->isChecked(FlipConsole::eUseLoadBox);
1563 
1564   if (TImageCache::instance()->isCached(id)) {
1565     TRect loadbox;
1566     std::map<std::string, TRect>::const_iterator it = m_loadboxes.find(id);
1567     if (it != m_loadboxes.end()) loadbox = it->second;
1568 
1569     // Resubmit the image to the cache as the 'last one' seen by the flipbook.
1570     // TImageCache::instance()->add(toString(m_poolIndex) + "lastFlipFrame",
1571     // img);
1572     // m_lastViewedFrame = frame+1;
1573     if ((showSub && m_loadbox == loadbox) || (!showSub && loadbox == TRect()))
1574       return TImageCache::instance()->get(id, false);
1575     else
1576       TImageCache::instance()->remove(id);
1577   }
1578   if (fp != TFilePath() && !m_isPreviewFx) {
1579     int lx = 0, oriLx = 0;
1580     // TLevelReaderP lr(fp);
1581     if (!m_lr || (fp != m_lr->getFilePath())) {
1582       m_lr = TLevelReaderP(fp);
1583       m_lr->enableRandomAccessRead(randomAccessRead);
1584     }
1585     if (!m_lr) return 0;
1586     // try to get image info only when loading tlv or pli as it is quite time
1587     // consuming
1588     if (fp.getType() == "tlv" || fp.getType() == "pli") {
1589       if (m_lr->getImageInfo()) lx = oriLx = m_lr->getImageInfo()->m_lx;
1590     }
1591     TImageReaderP ir = m_lr->getFrameReader(fid);
1592     ir->setShrink(m_shrink);
1593     if (m_loadbox != TRect() && showSub) {
1594       ir->setRegion(m_loadbox);
1595       lx = m_loadbox.getLx();
1596     }
1597 
1598     TImageP img = ir->load();
1599 
1600     if (img) {
1601       TRasterImageP ri = ((TRasterImageP)img);
1602       TToonzImageP ti  = ((TToonzImageP)img);
1603       if (premultiply) {
1604         if (ri)
1605           TRop::premultiply(ri->getRaster());
1606         else if (ti)
1607           TRop::premultiply(ti->getRaster());
1608       }
1609 
1610       // se e' stata caricata una sottoimmagine alcuni formati in realta'
1611       // caricano tutto il raster e fanno extract, non si ha quindi alcun
1612       // risparmio di occupazione di memoria; alloco un raster grande
1613       // giusto copio la region e butto quello originale.
1614       if (ri && showSub && m_loadbox != TRect() &&
1615           ri->getRaster()->getLx() == oriLx)  // questo serve perche' per avi e
1616                                               // mov la setRegion e'
1617                                               // completamente ignorata...
1618         ri->setRaster(ri->getRaster()->extract(m_loadbox)->clone());
1619       else if (ri && ri->getRaster()->getWrap() > ri->getRaster()->getLx())
1620         ri->setRaster(ri->getRaster()->clone());
1621       else if (ti && ti->getCMapped()->getWrap() > ti->getCMapped()->getLx())
1622         ti->setCMapped(ti->getCMapped()->clone());
1623 
1624       if ((fp.getType() == "tlv" || fp.getType() == "pli") && m_shrink > 1 &&
1625           (lx == 0 || (ri && ri->getRaster()->getLx() == lx) ||
1626            (ti && ti->getRaster()->getLx() == lx))) {
1627         if (ri)
1628           ri->setRaster(TRop::shrink(ri->getRaster(), m_shrink));
1629         else if (ti)
1630           ti->setCMapped(TRop::shrink(ti->getRaster(), m_shrink));
1631       }
1632 
1633       TPalette *palette = img->getPalette();
1634       if (m_palette && (!palette || palette != m_palette))
1635         img->setPalette(m_palette);
1636       TImageCache::instance()->add(id, img);
1637       m_loadboxes[id] = showSub ? m_loadbox : TRect();
1638     }
1639 
1640     // An old archived bug says that simulatenous open for read of the same tlv
1641     // are not allowed...
1642     // if(fp.getType()=="tlv")
1643     //  m_lr = TLevelReaderP();
1644     if (m_flags & eDontKeepFilesOpened) m_lr = TLevelReaderP();
1645     return img;
1646   } else if (fp == TFilePath() && m_isPreviewFx) {
1647     if (!TImageCache::instance()->isCached(id)) {
1648       /*string lastFrameCacheId(toString(m_poolIndex) + "lastFlipFrame");
1649 if(TImageCache::instance()->isCached(lastFrameCacheId))
1650 return TImageCache::instance()->get(lastFrameCacheId, false);
1651 else*/
1652       return 0;
1653       // showFrame(m_lastViewedFrame);
1654     }
1655   }
1656 
1657   return 0;
1658 }
1659 
1660 //-----------------------------------------------------------------------------
1661 
1662 /*! Set current level frame to image viewer. Add the view image in cache.
1663  */
onDrawFrame(int frame,const ImagePainter::VisualSettings & vs)1664 void FlipBook::onDrawFrame(int frame, const ImagePainter::VisualSettings &vs) {
1665   try {
1666     m_imageViewer->setVisual(vs);
1667 
1668     TImageP img = getCurrentImage(frame);
1669 
1670     if (!img) return;
1671 
1672     m_imageViewer->setImage(img);
1673   } catch (...) {
1674     m_imageViewer->setImage(TImageP());
1675   }
1676 
1677   if (m_playSound && !vs.m_drawBlankFrame) playAudioFrame(frame);
1678 }
1679 
1680 //-----------------------------------------------------------------------------
1681 
swapBuffers()1682 void FlipBook::swapBuffers() { m_imageViewer->doSwapBuffers(); }
1683 
1684 //-----------------------------------------------------------------------------
1685 
changeSwapBehavior(bool enable)1686 void FlipBook::changeSwapBehavior(bool enable) {
1687   m_imageViewer->changeSwapBehavior(enable);
1688 }
1689 
1690 //-----------------------------------------------------------------------------
1691 
setLoadbox(const TRect & box)1692 void FlipBook::setLoadbox(const TRect &box) {
1693   m_loadbox =
1694       (m_dim.lx > 0) ? box * TRect(0, 0, m_dim.lx - 1, m_dim.ly - 1) : box;
1695 }
1696 
1697 //----------------------------------------------------------------
1698 
clearCache()1699 void FlipBook::clearCache() {
1700   TLevel::Iterator it;
1701 
1702   if (m_levelNames.empty()) return;
1703   int i;
1704 
1705   if (!m_levels.empty())  // is a viewfile
1706     for (i = 0; i < m_levels.size(); i++)
1707       for (it = m_levels[i].m_level->begin(); it != m_levels[i].m_level->end();
1708            ++it)
1709         TImageCache::instance()->remove(
1710             m_levelNames[i].toStdString() +
1711             std::to_string(it->first.getNumber()) +
1712             ((m_isPreviewFx) ? "" : ::to_string(this)));
1713   else {
1714     int from, to, step;
1715     m_flipConsole->getFrameRange(from, to, step);
1716     for (int i = from; i <= to; i += step)  // is a render
1717       // color model may loading a part of frames in the level
1718       if (m_imageViewer->isColorModel() && m_palette) {
1719         // get the actually-loaded frame list
1720         std::vector<TFrameId> fids(m_palette->getRefLevelFids());
1721         if (!fids.empty() && (int)fids.size() >= i) {
1722           int frame = fids[i - 1].getNumber();
1723           TImageCache::instance()->remove(m_levelNames[0].toStdString() +
1724                                           std::to_string(frame));
1725         } else {
1726           TImageCache::instance()->remove(m_levelNames[0].toStdString() +
1727                                           std::to_string(i));
1728         }
1729       } else
1730         TImageCache::instance()->remove(m_levelNames[0].toStdString() +
1731                                         std::to_string(i));
1732   }
1733 }
1734 
1735 //-----------------------------------------------------------------------------
1736 
onCloseButtonPressed()1737 void FlipBook::onCloseButtonPressed() {
1738   m_flipConsole->setActive(false);
1739   closeFlipBook(this);
1740 
1741   reset();
1742 
1743   // hide freeze button in preview fx window
1744   if (m_freezeButton) {
1745     m_freezeButton->hide();
1746     m_imageViewer->setIsRemakingPreviewFx(false);
1747   }
1748 
1749   // Return the flipbook to the pool in case it was popped from it.
1750   if (m_poolIndex >= 0) FlipBookPool::instance()->push(this);
1751 }
1752 
1753 //-----------------------------------------------------------------------------
1754 
showHistogram()1755 void ImageViewer::showHistogram() {
1756   if (!m_isHistogramEnable) return;
1757   if (m_histogramPopup->isVisible())
1758     m_histogramPopup->raise();
1759   else {
1760     m_histogramPopup->setImage(getImage());
1761     m_histogramPopup->show();
1762   }
1763 }
1764 
1765 //-----------------------------------------------------------------------------
1766 
dragEnterEvent(QDragEnterEvent * e)1767 void FlipBook::dragEnterEvent(QDragEnterEvent *e) {
1768   const QMimeData *mimeData = e->mimeData();
1769   bool isResourceDrop       = acceptResourceDrop(mimeData->urls());
1770   if (!isResourceDrop &&
1771       !mimeData->hasFormat("application/vnd.toonz.drawings") &&
1772       !mimeData->hasFormat(CastItems::getMimeFormat()))
1773     return;
1774 
1775   for (const QUrl &url : mimeData->urls()) {
1776     TFilePath fp(url.toLocalFile().toStdWString());
1777     std::string type = fp.getType();
1778     if (type == "tzp" || type == "tzu" || type == "tnz" || type == "scr" ||
1779         type == "mesh")
1780       return;
1781   }
1782   if (mimeData->hasFormat(CastItems::getMimeFormat())) {
1783     const CastItems *items = dynamic_cast<const CastItems *>(mimeData);
1784     if (!items) return;
1785 
1786     int i;
1787     for (i = 0; i < items->getItemCount(); i++) {
1788       CastItem *item      = items->getItem(i);
1789       TXshSimpleLevel *sl = item->getSimpleLevel();
1790       if (!sl) return;
1791     }
1792   }
1793 
1794   if (isResourceDrop) {
1795     // Force CopyAction
1796     e->setDropAction(Qt::CopyAction);
1797     // For files, don't accept original proposed action in case it's a move
1798     e->accept();
1799   } else
1800     e->acceptProposedAction();
1801 }
1802 
1803 //-----------------------------------------------------------------------------
1804 
dropEvent(QDropEvent * e)1805 void FlipBook::dropEvent(QDropEvent *e) {
1806   const QMimeData *mimeData = e->mimeData();
1807   bool isResourceDrop       = acceptResourceDrop(mimeData->urls());
1808   if (mimeData->hasUrls()) {
1809     for (const QUrl &url : mimeData->urls()) {
1810       TFilePath fp(url.toLocalFile().toStdWString());
1811       if (TFileType::getInfo(fp) != TFileType::UNKNOW_FILE) setLevel(fp);
1812       if (isResourceDrop) {
1813         // Force CopyAction
1814         e->setDropAction(Qt::CopyAction);
1815         // For files, don't accept original proposed action in case it's a move
1816         e->accept();
1817       } else
1818         e->acceptProposedAction();
1819       return;
1820     }
1821   } else if (mimeData->hasFormat(
1822                  "application/vnd.toonz.drawings"))  // drag-drop from film
1823                                                      // strip
1824   {
1825     TFilmstripSelection *s =
1826         dynamic_cast<TFilmstripSelection *>(TSelection::getCurrent());
1827     TXshSimpleLevel *sl = TApp::instance()->getCurrentLevel()->getSimpleLevel();
1828     if (!s || !sl) return;
1829     TXshSimpleLevel *newSl = new TXshSimpleLevel();
1830     newSl->setScene(sl->getScene());
1831     newSl->setType(sl->getType());
1832     newSl->setPalette(sl->getPalette());
1833     newSl->clonePropertiesFrom(sl);
1834     const std::set<TFrameId> &fids = s->getSelectedFids();
1835     std::set<TFrameId>::const_iterator it;
1836     for (it = fids.begin(); it != fids.end(); ++it)
1837       newSl->setFrame(*it, sl->getFrame(*it, false)->cloneImage());
1838     setLevel(newSl);
1839   } else if (mimeData->hasFormat(
1840                  CastItems::getMimeFormat()))  // Drag-Drop from castviewer
1841   {
1842     const CastItems *items = dynamic_cast<const CastItems *>(mimeData);
1843     if (!items) return;
1844 
1845     int i;
1846     for (i = 0; i < items->getItemCount(); i++) {
1847       CastItem *item = items->getItem(i);
1848       if (TXshSimpleLevel *sl = item->getSimpleLevel()) setLevel(sl);
1849     }
1850   }
1851   m_flipConsole->makeCurrent();
1852 }
1853 
1854 //-------------------------------------------------------------------
1855 
reset()1856 void FlipBook::reset() {
1857   if (!m_isPreviewFx)  // The cache is owned by the PreviewFxManager otherwise
1858     clearCache();
1859   else
1860     PreviewFxManager::instance()->detach(this);
1861 
1862   m_levelNames.clear();
1863   m_levels.clear();
1864   m_framesCount = 0;
1865   m_palette     = 0;
1866   m_imageViewer->setImage(TImageP());
1867   m_imageViewer->hideHistogram();
1868   m_isPreviewFx = false;
1869   m_previewedFx = 0;
1870   m_previewXsh  = 0;
1871   m_freezed     = false;
1872   // sync the freeze button
1873   if (m_freezeButton) m_freezeButton->setPressed(false);
1874   m_flipConsole->pressButton(FlipConsole::ePause);
1875   if (m_playSound) m_flipConsole->pressButton(FlipConsole::eSound);
1876   if (m_player) m_player->stop();
1877   if (m_flipConsole->isChecked(FlipConsole::eDefineLoadBox))
1878     m_flipConsole->pressButton(FlipConsole::eDefineLoadBox);
1879   if (m_flipConsole->isChecked(FlipConsole::eUseLoadBox))
1880     m_flipConsole->pressButton(FlipConsole::eUseLoadBox);
1881 
1882   m_flipConsole->enableButton(FlipConsole::eDefineLoadBox, true);
1883   m_flipConsole->enableButton(FlipConsole::eUseLoadBox, true);
1884 
1885   m_lr = TLevelReaderP();
1886 
1887   m_dim     = TDimension();
1888   m_loadbox = TRect();
1889   m_loadboxes.clear();
1890   // m_lastViewedFrame = -1;
1891   // TImageCache::instance()->remove(toString(m_poolIndex) + "lastFlipFrame");
1892 
1893   m_flipConsole->enableProgressBar(false);
1894   m_flipConsole->setProgressBarStatus(0);
1895   m_flipConsole->setFrameRange(1, 1, 1);
1896 
1897   setTitle(tr("Flipbook"));
1898   parentWidget()->setWindowTitle(m_title);
1899 
1900   update();
1901 }
1902 
1903 //-------------------------------------------------------------------
1904 
showEvent(QShowEvent * e)1905 void FlipBook::showEvent(QShowEvent *e) {
1906   TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();
1907   connect(sceneHandle, SIGNAL(sceneChanged()), m_imageViewer, SLOT(update()));
1908   // for updating the blank frame button
1909   if (!m_imageViewer->isColorModel()) {
1910     connect(sceneHandle, SIGNAL(preferenceChanged(const QString &)),
1911             m_flipConsole, SLOT(onPreferenceChanged(const QString &)));
1912     m_flipConsole->onPreferenceChanged("");
1913   }
1914   m_flipConsole->setActive(true);
1915   m_imageViewer->update();
1916 }
1917 
1918 //-------------------------------------------------------------------
1919 
hideEvent(QHideEvent * e)1920 void FlipBook::hideEvent(QHideEvent *e) {
1921   TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();
1922   disconnect(sceneHandle, SIGNAL(sceneChanged()), m_imageViewer,
1923              SLOT(update()));
1924   if (!m_imageViewer->isColorModel()) {
1925     disconnect(sceneHandle, SIGNAL(preferenceChanged(const QString &)),
1926                m_flipConsole, SLOT(onPreferenceChanged(const QString &)));
1927   }
1928   m_flipConsole->setActive(false);
1929 }
1930 
1931 //-----------------------------------------------------------------------------
1932 
resizeEvent(QResizeEvent * e)1933 void FlipBook::resizeEvent(QResizeEvent *e) { schedulePreviewedFxUpdate(); }
1934 
1935 //-----------------------------------------------------------------------------
1936 
adaptGeometry(const TRect & interestingImgRect,const TRect & imgRect)1937 void FlipBook::adaptGeometry(const TRect &interestingImgRect,
1938                              const TRect &imgRect) {
1939   TRectD imgRectD(imgRect.x0, imgRect.y0, imgRect.x1 + 1, imgRect.y1 + 1);
1940   TRectD interestingImgRectD(interestingImgRect.x0, interestingImgRect.y0,
1941                              interestingImgRect.x1 + 1,
1942                              interestingImgRect.y1 + 1);
1943 
1944   TAffine toWidgetRef(m_imageViewer->getImgToWidgetAffine(imgRectD));
1945   TRectD interestGeomD(toWidgetRef * interestingImgRectD);
1946   TRectD imageGeomD(toWidgetRef * imgRectD);
1947   adaptWidGeometry(
1948       TRect(tceil(interestGeomD.x0), tceil(interestGeomD.y0),
1949             tfloor(interestGeomD.x1) - 1, tfloor(interestGeomD.y1) - 1),
1950       TRect(tceil(imageGeomD.x0), tceil(imageGeomD.y0),
1951             tfloor(imageGeomD.x1) - 1, tfloor(imageGeomD.y1) - 1),
1952       true);
1953 }
1954 
1955 //-----------------------------------------------------------------------------
1956 /*! When Fx preview is called without the subcamera, render the full region
1957     of camera by resize flipbook and zoom-out the rendered image.
1958 */
adaptGeometryForFullPreview(const TRect & imgRect)1959 void FlipBook::adaptGeometryForFullPreview(const TRect &imgRect) {
1960   TRectD imgRectD(imgRect.x0, imgRect.y0, imgRect.x1 + 1, imgRect.y1 + 1);
1961 
1962   // Get screen geometry
1963   TPanel *panel = static_cast<TPanel *>(parentWidget());
1964   if (!panel->isFloating()) return;
1965   QDesktopWidget *desk =
1966       static_cast<QApplication *>(QApplication::instance())->desktop();
1967   QRect screenGeom = desk->availableGeometry(panel);
1968 
1969   while (1) {
1970     TAffine toWidgetRef(m_imageViewer->getImgToWidgetAffine(imgRectD));
1971     TRectD imageGeomD(toWidgetRef * imgRectD);
1972     TRect imageGeom(tceil(imageGeomD.x0) - 1, tceil(imageGeomD.y0) - 1,
1973                     tfloor(imageGeomD.x1) + 1, tfloor(imageGeomD.y1) + 1);
1974 
1975     if (imageGeom.getLx() <= screenGeom.width() &&
1976         imageGeom.getLy() <= screenGeom.height()) {
1977       adaptWidGeometry(imageGeom, imageGeom, false);
1978       break;
1979     } else
1980       m_imageViewer->zoomQt(false, false);
1981   }
1982 }
1983 
1984 //-----------------------------------------------------------------------------
1985 
1986 //! Adapts panel geometry to that of passed rect.
adaptWidGeometry(const TRect & interestWidGeom,const TRect & imgWidGeom,bool keepPosition)1987 void FlipBook::adaptWidGeometry(const TRect &interestWidGeom,
1988                                 const TRect &imgWidGeom, bool keepPosition) {
1989   TPanel *panel = static_cast<TPanel *>(parentWidget());
1990   if (!panel->isFloating()) return;
1991 
1992   // Extract image position in screen coordinates
1993   QRect qgeom(interestWidGeom.x0, interestWidGeom.y0, interestWidGeom.getLx(),
1994               interestWidGeom.getLy());
1995   QRect interestGeom(m_imageViewer->mapToGlobal(qgeom.topLeft()),
1996                      m_imageViewer->mapToGlobal(qgeom.bottomRight()));
1997   qgeom = QRect(imgWidGeom.x0, imgWidGeom.y0, imgWidGeom.getLx(),
1998                 imgWidGeom.getLy());
1999   QRect imageGeom(m_imageViewer->mapToGlobal(qgeom.topLeft()),
2000                   m_imageViewer->mapToGlobal(qgeom.bottomRight()));
2001 
2002   // qDebug("tgeom= [%d, %d] x [%d, %d]", tgeom.x0, tgeom.x1, tgeom.y0,
2003   // tgeom.y1);
2004   // qDebug("imagegeom= [%d, %d] x [%d, %d]", imageGeom.left(),
2005   // imageGeom.right(),
2006   //  imageGeom.top(), imageGeom.bottom());
2007 
2008   // Get screen geometry
2009   QDesktopWidget *desk =
2010       static_cast<QApplication *>(QApplication::instance())->desktop();
2011   QRect screenGeom = desk->availableGeometry(panel);
2012 
2013   // Get panel margin measures
2014   QRect margins;
2015   QRect currView(m_imageViewer->geometry());
2016   currView.moveTo(m_imageViewer->mapToGlobal(currView.topLeft()));
2017   QRect panelGeom(panel->geometry());
2018 
2019   margins.setLeft(panelGeom.left() - currView.left());
2020   margins.setRight(panelGeom.right() - currView.right());
2021   margins.setTop(panelGeom.top() - currView.top());
2022   margins.setBottom(panelGeom.bottom() - currView.bottom());
2023 
2024   // Build the minimum flipbook geometry. Adjust the interesting geometry
2025   // according to it.
2026   QSize flipMinimumSize(panel->minimumSize());
2027   flipMinimumSize -=
2028       QSize(margins.right() - margins.left(), margins.bottom() - margins.top());
2029   QSize minAddition(
2030       tceil(std::max(0, flipMinimumSize.width() - interestGeom.width()) * 0.5),
2031       tceil(std::max(0, flipMinimumSize.height() - interestGeom.height()) *
2032             0.5));
2033   interestGeom.adjust(-minAddition.width(), -minAddition.height(),
2034                       minAddition.width(), minAddition.height());
2035 
2036   // Translate to keep the current view top-left corner, if required
2037   if (keepPosition) {
2038     QPoint shift(currView.topLeft() - interestGeom.topLeft());
2039     interestGeom.translate(shift);
2040     imageGeom.translate(shift);
2041   }
2042 
2043   // Intersect with the screen geometry
2044   QRect newViewerGeom(screenGeom);
2045   newViewerGeom.adjust(-margins.left(), -margins.top(), -margins.right(),
2046                        -margins.bottom());
2047 
2048   // when fx previewing in full size (i.e. keepPosition is false ),
2049   // try to translate geometry and keep the image inside the viewer as much as
2050   // posiible
2051   if (keepPosition)
2052     newViewerGeom &= interestGeom;
2053   else if (newViewerGeom.intersects(interestGeom)) {
2054     int d_ns = 0;
2055     int d_ew = 0;
2056     if (interestGeom.top() < newViewerGeom.top())
2057       d_ns = newViewerGeom.top() - interestGeom.top();
2058     else if (interestGeom.bottom() > newViewerGeom.bottom())
2059       d_ns = newViewerGeom.bottom() - interestGeom.bottom();
2060     if (interestGeom.left() < newViewerGeom.left())
2061       d_ew = newViewerGeom.left() - interestGeom.left();
2062     else if (interestGeom.right() > newViewerGeom.right())
2063       d_ew = newViewerGeom.right() - interestGeom.right();
2064     if (d_ns || d_ew) {
2065       interestGeom.translate(d_ew, d_ns);
2066       imageGeom.translate(d_ew, d_ns);
2067     }
2068     newViewerGeom &= interestGeom;
2069   }
2070 
2071   // qDebug("new Viewer= [%d, %d] x [%d, %d]", newViewerGeom.left(),
2072   // newViewerGeom.right(),
2073   //  newViewerGeom.top(), newViewerGeom.bottom());
2074 
2075   // Calculate the pan of content image in order to compensate for our geometry
2076   // change
2077   QPointF imageGeomCenter((imageGeom.left() + imageGeom.right() + 1) * 0.5,
2078                           (imageGeom.top() + imageGeom.bottom() + 1) * 0.5);
2079   QPointF newViewerGeomCenter(
2080       (newViewerGeom.left() + newViewerGeom.right() + 1) * 0.5,
2081       (newViewerGeom.top() + newViewerGeom.bottom() + 1) * 0.5);
2082 
2083   /*QPointF imageGeomCenter(
2084 (imageGeom.width()) * 0.5,
2085 (imageGeom.height()) * 0.5
2086 );
2087 QPointF newViewerGeomCenter(
2088 (newViewerGeom.width()) * 0.5,
2089 (newViewerGeom.height()) * 0.5
2090 );*/
2091 
2092   // NOTE: If delta == (0,0) the image is at center. Typically happens when
2093   // imageGeom doesn't intersect
2094   // the screen geometry.
2095   QPointF delta(imageGeomCenter - newViewerGeomCenter);
2096   TAffine aff(m_imageViewer->getViewAff());
2097   aff.a13 = delta.x();
2098   aff.a23 = -delta.y();
2099 
2100   // Calculate new panel geometry
2101   newViewerGeom.adjust(margins.left(), margins.top(), margins.right(),
2102                        margins.bottom());
2103 
2104   // Apply changes
2105   m_imageViewer->setViewAff(aff);
2106   panel->setGeometry(newViewerGeom);
2107 }
2108 
2109 //-----------------------------------------------------------------------------
2110 
onDoubleClick(QMouseEvent * me)2111 void FlipBook::onDoubleClick(QMouseEvent *me) {
2112   TImageP img(m_imageViewer->getImage());
2113   if (!img) return;
2114 
2115   TAffine toWidgetRef(m_imageViewer->getImgToWidgetAffine());
2116   TRectD pixGeomD(TScale(1.0 / (double)getDevPixRatio()) * toWidgetRef *
2117                   getImageBoundsD(img));
2118   // TRectD pixGeomD(toWidgetRef  * getImageBoundsD(img));
2119   TRect pixGeom(tceil(pixGeomD.x0), tceil(pixGeomD.y0), tfloor(pixGeomD.x1) - 1,
2120                 tfloor(pixGeomD.y1) - 1);
2121 
2122   // NOTE: The previous line has ceils and floor inverted on purpose. The reason
2123   // is the following:
2124   // As the viewer's zoom level is arbitrary, the image is likely to have a not
2125   // integer geometry
2126   // with respect to the widget - the problem is, we cannot take the closest
2127   // integer rect ENCLOSING ours,
2128   // or the ImageViewer class adds blank lines on image rendering.
2129   // So, we do the converse - take the closest ENCLOSED one - eventually to be
2130   // compensated when
2131   // performing the inverse.
2132 
2133   adaptWidGeometry(pixGeom, pixGeom, false);
2134 }
2135 
2136 //-----------------------------------------------------------------------------
2137 
minimize(bool doMinimize)2138 void FlipBook::minimize(bool doMinimize) {
2139   m_imageViewer->setVisible(!doMinimize);
2140   m_flipConsole->showHideAllParts(!doMinimize);
2141 }
2142 
2143 //-----------------------------------------------------------------------------
2144 /*! When viewing the tlv, try to cache all frames at the beginning.
2145         NOTE : fromFrame and toFrame are frame numbers displayed on the flipbook
2146 */
loadAndCacheAllTlvImages(Level level,int fromFrame,int toFrame)2147 void FlipBook::loadAndCacheAllTlvImages(Level level, int fromFrame,
2148                                         int toFrame) {
2149   TFilePath fp                                   = level.m_fp;
2150   if (!m_lr || (fp != m_lr->getFilePath())) m_lr = TLevelReaderP(fp);
2151   if (!m_lr) return;
2152 
2153   // show the wait cursor when loading a level with more than 50 frames
2154   if (toFrame - fromFrame > 50) QApplication::setOverrideCursor(Qt::WaitCursor);
2155 
2156   int lx = 0, oriLx = 0;
2157   if (m_lr->getImageInfo()) lx = oriLx = m_lr->getImageInfo()->m_lx;
2158 
2159   std::string fileName = toQString(fp.withoutParentDir()).toStdString();
2160 
2161   for (int f = fromFrame; f <= toFrame; f++) {
2162     TFrameId fid = level.flipbookIndexToLevelFrame(f);
2163     if (fid == TFrameId()) continue;
2164 
2165     std::string id =
2166         fileName + fid.expand(TFrameId::NO_PAD) + ::to_string(this);
2167 
2168     TImageReaderP ir = m_lr->getFrameReader(fid);
2169     ir->setShrink(m_shrink);
2170 
2171     TImageP img = ir->load();
2172 
2173     if (!img) continue;
2174 
2175     TToonzImageP ti = ((TToonzImageP)img);
2176     if (!ti) continue;
2177 
2178     if (ti->getCMapped()->getWrap() > ti->getCMapped()->getLx())
2179       ti->setCMapped(ti->getCMapped()->clone());
2180     if (m_shrink > 1 && (lx == 0 || ti->getRaster()->getLx() == lx))
2181       ti->setCMapped(TRop::shrink(ti->getRaster(), m_shrink));
2182 
2183     TPalette *palette = img->getPalette();
2184 
2185     if (m_palette && (!palette || palette != m_palette))
2186       img->setPalette(m_palette);
2187 
2188     TImageCache::instance()->add(id, img);
2189     m_loadboxes[id] = TRect();
2190   }
2191 
2192   m_lr = TLevelReaderP();
2193 
2194   // revert the cursor
2195   if (toFrame - fromFrame > 50) QApplication::restoreOverrideCursor();
2196 }
2197 
2198 //=============================================================================
2199 // Utility
2200 
2201 //-----------------------------------------------------------------------------
2202 
2203 //! Displays the passed file on a Flipbook, supporting a wide range of options.
2204 //! Possible options include:
2205 //! \li The range, step and shrink parameters for the loaded level
2206 //! \li A soundtrack to accompany the level's images
2207 //! \li The flipbook where the file is to be opened. If none, a new one is
2208 //! created.
2209 //! \li Whether the level must replace an existing one on the flipbook, or it
2210 //! must
2211 //! rather be appended at its end
2212 //! \li In case the file has a movie format and it is known to be a toonz
2213 //! output,
2214 //! some additional random access information may be retrieved (i.e. images may
2215 //! map
2216 //! to specific frames).
2217 // returns pointer to the opened flipbook to control modality.
viewFile(const TFilePath & path,int from,int to,int step,int shrink,TSoundTrack * snd,FlipBook * flipbook,bool append,bool isToonzOutput)2218 FlipBook *viewFile(const TFilePath &path, int from, int to, int step,
2219                    int shrink, TSoundTrack *snd, FlipBook *flipbook,
2220                    bool append, bool isToonzOutput) {
2221   // In case the step and shrink informations are invalid, load them from
2222   // preferences
2223   if (step == -1 || shrink == -1) {
2224     int _step = 1, _shrink = 1;
2225     Preferences::instance()->getViewValues(_shrink, _step);
2226     if (step == -1) step     = _step;
2227     if (shrink == -1) shrink = _shrink;
2228   }
2229 
2230   // Movie files must not have the ".." extension
2231   if ((path.getType() == "mov" || path.getType() == "avi" ||
2232        path.getType() == "3gp") &&
2233       path.isLevelName()) {
2234     DVGui::warning(QObject::tr("%1  has an invalid extension format.")
2235                        .arg(QString::fromStdString(path.getLevelName())));
2236     return NULL;
2237   }
2238 
2239   // Windows Screen Saver - avoid
2240   if (path.getType() == "scr") return NULL;
2241 
2242   // Avi and movs may be viewed by an external viewer, depending on preferences
2243   if ((path.getType() == "mov" || path.getType() == "avi") && !flipbook) {
2244     QString str;
2245     QSettings().value("generatedMovieViewEnabled", str);
2246     if (str.toInt() != 0) {
2247       TSystem::showDocument(path);
2248       return NULL;
2249     }
2250   }
2251 
2252   // Retrieve a blank flipbook
2253   if (!flipbook)
2254     flipbook = FlipBookPool::instance()->pop();
2255   else if (!append)
2256     flipbook->reset();
2257 
2258   // Assign the passed level with associated infos
2259   flipbook->setLevel(path, 0, from, to, step, shrink, snd, append,
2260                      isToonzOutput);
2261   return flipbook;
2262 }
2263 
2264 //-----------------------------------------------------------------------------
2265