1 
2 
3 #include "imageviewer.h"
4 
5 // Tnz6 includes
6 #include "tapp.h"
7 #include "menubarcommandids.h"
8 #include "flipbook.h"
9 #include "histogrampopup.h"
10 #include "sceneviewer.h"  // ToggleCommandHandler
11 #include "mainwindow.h"   //RecentFiles
12 
13 // TnzTools includes
14 #include "tools/toolcommandids.h"
15 #include "tools/cursors.h"
16 #include "tools/cursormanager.h"
17 #include "tools/stylepicker.h"
18 
19 // TnzQt includes
20 #include "toonzqt/menubarcommand.h"
21 #include "toonzqt/viewcommandids.h"
22 #include "toonzqt/imageutils.h"
23 #include "toonzqt/lutcalibrator.h"
24 
25 // TnzLib includes
26 #include "toonz/tscenehandle.h"
27 #include "toonz/toonzscene.h"
28 #include "toonz/sceneproperties.h"
29 #include "toonz/palettecontroller.h"
30 #include "toonz/tpalettehandle.h"
31 #include "toonz/preferences.h"
32 
33 // TnzCore includes
34 #include "tgl.h"
35 
36 // Qt includes
37 #include <QMenu>
38 #include <QAction>
39 #include <QMouseEvent>
40 #include <QWheelEvent>
41 #include <QOpenGLFramebufferObject>
42 #include <QGestureEvent>
43 
44 //===================================================================================
45 
46 extern ToggleCommandHandler safeAreaToggle;
47 extern void getSafeAreaData(double &_smallSize, double &_largeSize);
48 // enable to choose safe area from a list, and enable to draw multiple lines
49 extern void getSafeAreaSizeList(QList<QList<double>> &_sizeList);
50 
51 //===================================================================================
52 
53 //==============================
54 //    Local namespace stuff
55 //------------------------------
56 
57 namespace {
58 // enable to choose safe area from a list, and enable to draw multiple lines
drawSafeArea(const TRectD box)59 void drawSafeArea(const TRectD box) {
60   glPushMatrix();
61   glLoadIdentity();
62 
63   tglColor(TPixel32::Red);
64   glLineStipple(1, 0xCCCC);
65   glEnable(GL_LINE_STIPPLE);
66 
67   tglDrawRect(box);
68 
69   QList<QList<double>> sizeList;
70 
71   getSafeAreaSizeList(sizeList);
72 
73   for (int i = 0; i < sizeList.size(); i++) {
74     QList<double> curSize = sizeList.at(i);
75     if (curSize.size() == 5)
76       tglColor(
77           TPixel((int)curSize.at(2), (int)curSize.at(3), (int)curSize.at(4)));
78     else
79       tglColor(TPixel32::Red);
80 
81     double facX = -0.5 * (1 - curSize.at(0) / 100.0);
82     double facY = -0.5 * (1 - curSize.at(1) / 100.0);
83     tglDrawRect(box.enlarge(facX * box.getLx(), facY * box.getLy()));
84   }
85 
86   glDisable(GL_LINE_STIPPLE);
87   glPopMatrix();
88 }
89 
90 //-----------------------------------------------------------------------------
91 
getImageBounds(const TImageP & img)92 inline TRect getImageBounds(const TImageP &img) {
93   if (TRasterImageP ri = img)
94     return ri->getRaster()->getBounds();
95   else if (TToonzImageP ti = img)
96     return ti->getRaster()->getBounds();
97   else {
98     TVectorImageP vi = img;
99     return convert(vi->getBBox());
100   }
101 }
102 
103 //-----------------------------------------------------------------------------
104 
getImageBoundsD(const TImageP & img)105 inline TRectD getImageBoundsD(const TImageP &img) {
106   if (TRasterImageP ri = img)
107     return TRectD(0, 0, ri->getRaster()->getLx(), ri->getRaster()->getLy());
108   else if (TToonzImageP ti = img)
109     return TRectD(0, 0, ti->getSize().lx, ti->getSize().ly);
110   else {
111     TVectorImageP vi = img;
112     return vi->getBBox();
113   }
114 }
115 
116 //-----------------------------------------------------------------------------
117 
118 class FlipZoomer final : public ImageUtils::ShortcutZoomer {
119 public:
FlipZoomer(ImageViewer * parent)120   FlipZoomer(ImageViewer *parent) : ShortcutZoomer(parent) {}
121 
zoom(bool zoomin,bool resetView)122   bool zoom(bool zoomin, bool resetView) override {
123     static_cast<ImageViewer *>(getWidget())->zoomQt(zoomin, resetView);
124     return true;
125   }
126 
fit()127   bool fit() override {
128     static_cast<ImageViewer *>(getWidget())->fitView();
129     return true;
130   }
131 
resetZoom()132   bool resetZoom() override {
133     static_cast<ImageViewer *>(getWidget())->resetZoom();
134     return true;
135   }
136 
toggleFullScreen(bool quit)137   bool toggleFullScreen(bool quit) override {
138     if (ImageUtils::FullScreenWidget *fsWidget =
139             dynamic_cast<ImageUtils::FullScreenWidget *>(
140                 getWidget()->parentWidget()))
141       return fsWidget->toggleFullScreen(quit);
142 
143     return false;
144   }
145 };
146 
147 //-----------------------------------------------------------------------------
148 
149 class ImageViewerShortcutReceiver {
150   FlipBook *m_flipbook;
151 
152 public:
ImageViewerShortcutReceiver(FlipBook * flipbook)153   ImageViewerShortcutReceiver(FlipBook *flipbook) : m_flipbook(flipbook) {}
~ImageViewerShortcutReceiver()154   ~ImageViewerShortcutReceiver() {}
155 
exec(QKeyEvent * event)156   bool exec(QKeyEvent *event) {
157     CommandManager *cManager = CommandManager::instance();
158 
159     if (event->key() == cManager->getKeyFromId(MI_FreezePreview)) {
160       m_flipbook->isFreezed() ? m_flipbook->unfreezePreview()
161                               : m_flipbook->freezePreview();
162       return true;
163     }
164 
165     if (event->key() == cManager->getKeyFromId(MI_ClonePreview)) {
166       m_flipbook->clonePreview();
167       return true;
168     }
169 
170     if (event->key() == cManager->getKeyFromId(MI_RegeneratePreview)) {
171       m_flipbook->regenerate();
172       return true;
173     }
174 
175     if (event->key() == cManager->getKeyFromId(MI_RegenerateFramePr)) {
176       m_flipbook->regenerateFrame();
177       return true;
178     }
179 
180     return false;
181   }
182 };
183 }  // namespace
184 
185 //=============================================================================
186 
187 //==========================
188 //    ImageViewer class
189 //--------------------------
190 
191 /*! \class ImageViewer
192                 \brief The ImageViewer class provides to view an image.
193 
194                 Inherits \b QOpenGLWidget.
195 
196                 The object allows also to manage pan and zoom event. It's
197    possible to set a
198                 color mask TRop::ColorMask to image view.
199 */
200 /*! \fn void ImageViewer::setColorMask(UCHAR colorMask)
201                 Set current TRop::ColorMask color mask to \b colorMask.
202 */
203 /*! \fn TImageP ImageViewer::getImage()
204                 Return current image viewer.
205 */
206 /*! \fn TAffine ImageViewer::getViewAff()
207                 Return current viewer matrix trasformation.
208 */
209 
ImageViewer(QWidget * parent,FlipBook * flipbook,bool showHistogram)210 ImageViewer::ImageViewer(QWidget *parent, FlipBook *flipbook,
211                          bool showHistogram)
212     : GLWidgetForHighDpi(parent)
213     , m_pressedMousePos(0, 0)
214     , m_mouseButton(Qt::NoButton)
215     , m_draggingZoomSelection(false)
216     , m_image()
217     , m_FPS(0)
218     , m_viewAff()
219     , m_pos(0, 0)
220     , m_visualSettings()
221     , m_compareSettings()
222     , m_isHistogramEnable(showHistogram)
223     , m_flipbook(flipbook)
224     , m_isColorModel(false)
225     , m_histogramPopup(0)
226     , m_isRemakingPreviewFx(false)
227     , m_rectRGBPick(false)
228     , m_firstImage(true) {
229   m_visualSettings.m_sceneProperties =
230       TApp::instance()->getCurrentScene()->getScene()->getProperties();
231   m_visualSettings.m_drawExternalBG = true;
232   setAttribute(Qt::WA_KeyCompression);
233   setFocusPolicy(Qt::StrongFocus);
234 
235   setMouseTracking(true);
236 
237   setAttribute(Qt::WA_AcceptTouchEvents);
238   grabGesture(Qt::SwipeGesture);
239   grabGesture(Qt::PanGesture);
240   grabGesture(Qt::PinchGesture);
241 
242   if (m_isHistogramEnable)
243     m_histogramPopup = new HistogramPopup(tr("Flipbook Histogram"));
244 
245   if (Preferences::instance()->isColorCalibrationEnabled())
246     m_lutCalibrator = new LutCalibrator();
247 }
248 
249 //-----------------------------------------------------------------------------
250 
contextMenuEvent(QContextMenuEvent * event)251 void ImageViewer::contextMenuEvent(QContextMenuEvent *event) {
252   QAction *action;
253 
254   if (m_isColorModel) {
255     event->ignore();
256     return;
257   }
258 
259   QMenu *menu = new QMenu(this);
260 
261   if (m_flipbook) {
262     if (m_flipbook->getPreviewedFx()) {
263       if (!(windowState() & Qt::WindowFullScreen)) {
264         action = menu->addAction(tr("Clone Preview"));
265         action->setShortcut(QKeySequence(
266             CommandManager::instance()->getKeyFromId(MI_ClonePreview)));
267         connect(action, SIGNAL(triggered()), m_flipbook, SLOT(clonePreview()));
268       }
269 
270       if (m_flipbook->isFreezed()) {
271         action = menu->addAction(tr("Unfreeze Preview"));
272         action->setShortcut(QKeySequence(
273             CommandManager::instance()->getKeyFromId(MI_FreezePreview)));
274         connect(action, SIGNAL(triggered()), m_flipbook,
275                 SLOT(unfreezePreview()));
276       } else {
277         action = menu->addAction(tr("Freeze Preview"));
278         action->setShortcut(QKeySequence(
279             CommandManager::instance()->getKeyFromId(MI_FreezePreview)));
280         connect(action, SIGNAL(triggered()), m_flipbook, SLOT(freezePreview()));
281       }
282 
283       action = menu->addAction(tr("Regenerate Preview"));
284       action->setShortcut(QKeySequence(
285           CommandManager::instance()->getKeyFromId(MI_RegeneratePreview)));
286       connect(action, SIGNAL(triggered()), m_flipbook, SLOT(regenerate()));
287 
288       action = menu->addAction(tr("Regenerate Frame Preview"));
289       action->setShortcut(QKeySequence(
290           CommandManager::instance()->getKeyFromId(MI_RegenerateFramePr)));
291       connect(action, SIGNAL(triggered()), m_flipbook, SLOT(regenerateFrame()));
292 
293       menu->addSeparator();
294     }
295 
296     action = menu->addAction(tr("Load / Append Images"));
297     connect(action, SIGNAL(triggered()), m_flipbook, SLOT(loadImages()));
298 
299     // history of the loaded paths of flipbook
300     action = CommandManager::instance()->getAction(MI_LoadRecentImage);
301     menu->addAction(action);
302     action->setParent(m_flipbook);
303 
304     menu->addSeparator();
305   }
306 
307   QAction *reset = menu->addAction(tr("Reset View"));
308   reset->setShortcut(
309       QKeySequence(CommandManager::instance()->getKeyFromId(V_ViewReset)));
310   connect(reset, SIGNAL(triggered()), SLOT(resetView()));
311 
312   QAction *fit = menu->addAction(tr("Fit To Window"));
313   fit->setShortcut(
314       QKeySequence(CommandManager::instance()->getKeyFromId(V_ZoomFit)));
315   connect(fit, SIGNAL(triggered()), SLOT(fitView()));
316 
317   if (m_flipbook) {
318 #ifdef _WIN32
319     if (ImageUtils::FullScreenWidget *fsWidget =
320             dynamic_cast<ImageUtils::FullScreenWidget *>(parentWidget())) {
321       bool isFullScreen = (fsWidget->windowState() & Qt::WindowFullScreen) != 0;
322 
323       action = menu->addAction(isFullScreen ? tr("Exit Full Screen Mode")
324                                             : tr("Full Screen Mode"));
325 
326       action->setShortcut(QKeySequence(
327           CommandManager::instance()->getKeyFromId(V_ShowHideFullScreen)));
328       connect(action, SIGNAL(triggered()), fsWidget, SLOT(toggleFullScreen()));
329     }
330 
331 #endif
332 
333     bool addedSep = false;
334 
335     if (m_isHistogramEnable &&
336         visibleRegion().contains(event->pos() * getDevPixRatio())) {
337       menu->addSeparator();
338       addedSep = true;
339       action   = menu->addAction(tr("Show Histogram"));
340       connect(action, SIGNAL(triggered()), SLOT(showHistogram()));
341     }
342 
343     if (m_visualSettings.m_doCompare) {
344       if (!addedSep) {
345         menu->addSeparator();
346         addedSep = true;
347       }
348       action = menu->addAction(tr("Swap Compared Images"));
349       connect(action, SIGNAL(triggered()), SLOT(swapCompared()));
350     }
351 
352     if (m_flipbook->isSavable()) {
353       if (!addedSep) menu->addSeparator();
354       action = menu->addAction(tr("Save Images"));
355       connect(action, SIGNAL(triggered()), m_flipbook, SLOT(saveImages()));
356     }
357   }
358 
359   menu->exec(event->globalPos());
360 
361   if (m_flipbook) {
362     action = CommandManager::instance()->getAction(MI_LoadRecentImage);
363     action->setParent(0);
364   }
365 
366   delete menu;
367   update();
368 }
369 
370 //-----------------------------------------------------------------------------
371 
setVisual(const ImagePainter::VisualSettings & settings)372 void ImageViewer::setVisual(const ImagePainter::VisualSettings &settings) {
373   m_visualSettings = settings;
374   m_visualSettings.m_sceneProperties =
375       TApp::instance()->getCurrentScene()->getScene()->getProperties();
376   m_visualSettings.m_drawExternalBG = true;
377 }
378 
379 //-----------------------------------------------------------------------------
380 
~ImageViewer()381 ImageViewer::~ImageViewer() {
382   // iwsw commented out temporarily
383   /*
384   if (m_ghibli3DLutUtil)
385   {
386           m_ghibli3DLutUtil->onEnd();
387           delete m_ghibli3DLutUtil;
388   }
389   */
390   if (m_fbo) delete m_fbo;
391 }
392 
393 //-----------------------------------------------------------------------------
394 /*! Set current image to \b image and update. If Histogram is visible set its
395  * image.
396  */
setImage(TImageP image)397 void ImageViewer::setImage(TImageP image) {
398   m_image = image;
399 
400   if (m_image && m_firstImage) {
401     m_firstImage = false;
402     fitView();
403     // when the viewer size is large enough, limit the zoom ratio to 100% so
404     // that the image is shown in actual pixel size without jaggies due to
405     // resampling.
406     if (fabs(m_viewAff.det()) > 1.0) resetView();
407   }
408 
409   if (m_isHistogramEnable && m_histogramPopup->isVisible())
410     m_histogramPopup->setImage(image);
411   if (!isColorModel())
412     repaint();
413   else
414     update();
415 }
416 
417 //-------------------------------------------------------------------
418 
setHistogramTitle(QString levelName)419 void ImageViewer::setHistogramTitle(QString levelName) {
420   if (m_isHistogramEnable && m_histogramPopup->isVisible()) {
421     QString histogramTitle = QString("Histogram  ::  ") + levelName;
422     m_histogramPopup->setTitle(histogramTitle);
423   }
424 }
425 //-------------------------------------------------------------------
426 
setHistogramEnable(bool enable)427 void ImageViewer::setHistogramEnable(bool enable) {
428   m_isHistogramEnable = enable;
429   if (m_isHistogramEnable && !m_histogramPopup)
430     m_histogramPopup = new HistogramPopup(tr("Flipbook Histogram"));
431 }
432 
433 //-----------------------------------------------------------------------------
434 
hideHistogram()435 void ImageViewer::hideHistogram() {
436   if (!m_isHistogramEnable) return;
437   m_histogramPopup->setImage(TImageP());
438   if (m_histogramPopup->isVisible()) {
439     m_histogramPopup->hide();
440     m_histogramPopup->setTitle(tr("Flipbook Histogram"));
441   }
442 }
443 
444 //-------------------------------------------------------------------
445 
initializeGL()446 void ImageViewer::initializeGL() {
447   initializeOpenGLFunctions();
448 
449   // to be computed once through the software
450   if (m_lutCalibrator && !m_lutCalibrator->isInitialized()) {
451     m_lutCalibrator->initialize();
452     connect(context(), SIGNAL(aboutToBeDestroyed()), this,
453             SLOT(onContextAboutToBeDestroyed()));
454   }
455 
456   // glClearColor(1.0,1.0,1.0,1);
457   glClear(GL_COLOR_BUFFER_BIT);
458 
459   if (m_firstInitialized)
460     m_firstInitialized = false;
461   else {
462     resizeGL(width(), height());
463     update();
464   }
465 }
466 
467 //-----------------------------------------------------------------------------
468 
resizeGL(int w,int h)469 void ImageViewer::resizeGL(int w, int h) {
470   w *= getDevPixRatio();
471   h *= getDevPixRatio();
472   glViewport(0, 0, w, h);
473   glMatrixMode(GL_PROJECTION);
474   glLoadIdentity();
475   double nearPlane   = 100;
476   double farPlane    = 2500;
477   double centerPlane = 1100;
478   glOrtho(0, w, 0, h, -4000, 4000);
479   glMatrixMode(GL_MODELVIEW);
480   glLoadIdentity();
481   glTranslatef(0.375, 0.375, 0.0);
482   glTranslated(w * 0.5, h * 0.5, 0);
483 
484   // remake fbo with new size
485   if (m_lutCalibrator && m_lutCalibrator->isValid()) {
486     if (m_fbo) delete m_fbo;
487     m_fbo = new QOpenGLFramebufferObject(w, h);
488   }
489 }
490 
491 //-----------------------------------------------------------------------------
492 
paintGL()493 void ImageViewer::paintGL() {
494   if (m_lutCalibrator && m_lutCalibrator->isValid()) m_fbo->bind();
495 
496   TDimension viewerSize(width(), height());
497   TAffine aff = m_viewAff;
498 
499   // if (!m_visualSettings.m_defineLoadbox && m_flipbook &&
500   // m_flipbook->getLoadbox()!=TRect())
501   //  offs =
502   //  convert(m_flipbook->getLoadbox().getP00())-TPointD(m_flipbook->getImageSize().lx/2.0,
503   //  m_flipbook->getImageSize().ly/2.0);
504 
505   TDimension imageSize;
506   TRect loadbox;
507 
508   if (m_flipbook) {
509     QString title =
510         (!m_image)
511             ? m_flipbook->getTitle() + tr("  ::  Zoom : ") +
512                   QString::number(tround(sqrt(m_viewAff.det()) * 100)) + " %"
513             : m_flipbook->getLevelZoomTitle() + tr("  ::  Zoom : ") +
514                   QString::number(tround(sqrt(m_viewAff.det()) * 100)) + " %";
515     m_flipbook->parentWidget()->setWindowTitle(title);
516     imageSize = m_flipbook->getImageSize();
517     if (m_visualSettings.m_useLoadbox && m_flipbook->getLoadbox() != TRect())
518       loadbox = m_flipbook->getLoadbox();
519   }
520   m_visualSettings.m_sceneProperties =
521       TApp::instance()->getCurrentScene()->getScene()->getProperties();
522   // enable checks only in the color model
523   m_visualSettings.m_useChecks = m_isColorModel;
524   ImagePainter::paintImage(m_image, imageSize, viewerSize, aff,
525                            m_visualSettings, m_compareSettings, loadbox);
526 
527   // when fx parameter is modified with showing the fx preview,
528   // a flipbook shows a red border line before the rendered result is shown.
529   if (m_isRemakingPreviewFx) {
530     glPushMatrix();
531     glLoadIdentity();
532     glColor3d(1.0, 0.0, 0.0);
533 
534     glBegin(GL_LINE_LOOP);
535     glVertex2d(5, 5);
536     glVertex2d(5, height() - 5);
537     glVertex2d(width() - 5, height() - 5);
538     glVertex2d(width() - 5, 5);
539     glEnd();
540 
541     glBegin(GL_LINE_LOOP);
542     glVertex2d(10, 10);
543     glVertex2d(10, height() - 10);
544     glVertex2d(width() - 10, height() - 10);
545     glVertex2d(width() - 10, 10);
546     glEnd();
547     glPopMatrix();
548   }
549 
550   if (!m_image) {
551     if (m_lutCalibrator && m_lutCalibrator->isValid())
552       m_lutCalibrator->onEndDraw(m_fbo);
553     return;
554   }
555 
556   if (safeAreaToggle.getStatus() && !m_isColorModel) {
557     TRasterImageP rimg = (TRasterImageP)m_image;
558     TVectorImageP vimg = (TVectorImageP)m_image;
559     TToonzImageP timg  = (TToonzImageP)m_image;
560     TRect bbox;
561 
562     TPointD centerD;
563     TRect bounds;
564     if (rimg) {
565       centerD = rimg->getRaster()->getCenterD();
566       bounds  = rimg->getRaster()->getBounds();
567     } else if (timg) {
568       centerD = timg->getRaster()->getCenterD();
569       bounds  = timg->getRaster()->getBounds();
570     }
571 
572     if (!vimg) {
573       TAffine aff = TTranslation(viewerSize.lx * 0.5, viewerSize.ly * 0.5) *
574                     m_viewAff * TTranslation(-centerD);
575       TRectD bbox = aff * TRectD(0, 0, bounds.getLx() - 1, bounds.getLy() - 1);
576       drawSafeArea(bbox);
577     }
578   }
579   TPoint fromPos, toPos;
580 
581   if (m_visualSettings.m_defineLoadbox && m_flipbook) {
582     TRect loadbox =
583         convert(getImgToWidgetAffine() * convert(m_flipbook->getLoadbox()));
584     if (loadbox != TRect()) {
585       TPoint p00 = loadbox.getP00();
586       TPoint p11 = loadbox.getP11();
587       fromPos =
588           TPoint(p00.x - width() * 0.5,
589                  height() * 0.5 - p00.y);  // m_flipbook->getLoadbox().getP00();
590       toPos =
591           TPoint(p11.x - width() * 0.5,
592                  height() * 0.5 - p11.y);  // m_flipbook->getLoadbox().getP11();
593     }
594   } else if (m_draggingZoomSelection || m_rectRGBPick) {
595     fromPos = TPoint(m_pressedMousePos.x - width() * 0.5,
596                      height() * 0.5 - m_pressedMousePos.y);
597     toPos   = TPoint(m_pos.x() - width() * 0.5, height() * 0.5 - m_pos.y());
598   }
599   if (fromPos != TPoint() || toPos != TPoint()) {
600     if (m_rectRGBPick) {
601       tglColor(TPixel32::Red);
602       // TODO: glLineStipple is deprecated in the latest OpenGL. Need to be
603       // replaced. (shun_iwasawa 2015/12/25)
604       glLineStipple(1, 0x3F33);
605       glEnable(GL_LINE_STIPPLE);
606 
607       glBegin(GL_LINE_STRIP);
608       // do not draw the rect around the mouse cursor
609       int margin = (fromPos.y < toPos.y) ? -3 : 3;
610       glVertex2i(toPos.x, toPos.y + margin);
611       glVertex2i(toPos.x, fromPos.y);
612       glVertex2i(fromPos.x, fromPos.y);
613       glVertex2i(fromPos.x, toPos.y);
614       margin = (fromPos.x < toPos.x) ? -3 : 3;
615       glVertex2i(toPos.x + margin, toPos.y);
616       glEnd();
617       glDisable(GL_LINE_STIPPLE);
618     } else {
619       tglColor(m_draggingZoomSelection ? TPixel32::Red : TPixel32::Blue);
620       glBegin(GL_LINE_STRIP);
621       glVertex2i(fromPos.x, fromPos.y);
622       glVertex2i(fromPos.x, toPos.y);
623       glVertex2i(toPos.x, toPos.y);
624       glVertex2i(toPos.x, fromPos.y);
625       glVertex2i(fromPos.x, fromPos.y);
626       glEnd();
627     }
628   }
629 
630   if (m_lutCalibrator && m_lutCalibrator->isValid())
631     m_lutCalibrator->onEndDraw(m_fbo);
632 }
633 
634 //------------------------------------------------------------------------------
635 /*! Add to current trasformation matrix a \b delta traslation.
636  */
panQt(const QPoint & delta)637 void ImageViewer::panQt(const QPoint &delta) {
638   if (delta == QPoint()) return;
639 
640   // stop panning when the image is at the edge of window
641   QPoint delta_(delta.x(), delta.y());
642 
643   TToonzImageP timg  = (TToonzImageP)m_image;
644   TRasterImageP rimg = (TRasterImageP)m_image;
645   if (timg || rimg) {
646     bool isXPlus = delta.x() > 0;
647     bool isYPlus = delta.y() > 0;
648 
649     TDimension imgSize((timg) ? timg->getSize() : rimg->getRaster()->getSize());
650     int subSampling = (timg) ? timg->getSubsampling() : rimg->getSubsampling();
651 
652     TPointD cornerPos = TPointD(imgSize.lx * ((isXPlus) ? -1 : 1),
653                                 imgSize.ly * ((isYPlus) ? 1 : -1)) *
654                         (0.5 / (double)subSampling);
655     cornerPos = m_viewAff * cornerPos;
656 
657     if ((cornerPos.x > 0) == isXPlus) delta_.setX(0);
658     if ((cornerPos.y < 0) == isYPlus) delta_.setY(0);
659   }
660 
661   setViewAff(TTranslation(delta_.x(), -delta_.y()) * m_viewAff);
662 
663   update();
664 }
665 
666 //-----------------------------------------------------------------------------
667 /*! Add to current trasformation matrix a \b center traslation matched with a
668                 scale of factor \b factor. Apply a zoom of factor \b factor with
669    center
670                 \b center.
671 */
zoomQt(const QPoint & center,double factor)672 void ImageViewer::zoomQt(const QPoint &center, double factor) {
673   if (factor == 1.0) return;
674   TPointD delta(center.x(), center.y());
675   double scale2 = fabs(m_viewAff.det());
676   if ((scale2 < 100000 || factor < 1) && (scale2 > 0.001 * 0.05 || factor > 1))
677     setViewAff(TTranslation(delta) * TScale(factor) * TTranslation(-delta) *
678                m_viewAff);
679   update();
680 }
681 
682 //-----------------------------------------------------------------------------
683 
zoomQt(bool forward,bool reset)684 void ImageViewer::zoomQt(bool forward, bool reset) {
685   double scale2 = m_viewAff.det();
686   if (reset)
687     setViewAff(TAffine());
688   else if (((scale2 < 100000 || !forward) &&
689             (scale2 > 0.001 * 0.05 || forward))) {
690     double oldZoomScale = sqrt(scale2);
691     double zoomScale =
692         reset ? 1 : ImageUtils::getQuantizedZoomFactor(oldZoomScale, forward);
693     setViewAff(TScale(zoomScale / oldZoomScale) * m_viewAff);
694   }
695   update();
696 }
697 
698 //-----------------------------------------------------------------------------
699 
resetZoom()700 void ImageViewer::resetZoom() {
701   double oldZoomScale = sqrt(m_viewAff.det());
702   setViewAff(TScale(1.0 / oldZoomScale) * m_viewAff);
703   update();
704 }
705 
706 //-----------------------------------------------------------------------------
707 
dragCompare(const QPoint & dp)708 void ImageViewer::dragCompare(const QPoint &dp) {
709   if (m_compareSettings.m_dragCompareX)
710     m_compareSettings.m_compareX += ((double)dp.x()) / width();
711   else if (m_compareSettings.m_dragCompareY)
712     m_compareSettings.m_compareY -= ((double)dp.y()) / height();
713 
714   m_compareSettings.m_compareX = tcrop(m_compareSettings.m_compareX, 0.0, 1.0);
715   m_compareSettings.m_compareY = tcrop(m_compareSettings.m_compareY, 0.0, 1.0);
716 
717   update();
718 }
719 
720 //-----------------------------------------------------------------------------
721 
updateLoadbox(const TPoint & curPos)722 void ImageViewer::updateLoadbox(const TPoint &curPos) {
723   TAffine aff = getImgToWidgetAffine();
724   TRect r =
725       m_flipbook
726           ->getLoadbox();  // convert(aff*convert(m_flipbook->getLoadbox()));
727   if (m_dragType == eDrawRect)
728     r = convert(aff.inv() *
729                 convert(TRect(m_pressedMousePos,
730                               curPos)));  // TRect(m_pressedMousePos, curPos);
731   else if (m_dragType == eMoveRect)
732     r = r + (TPoint(curPos.x, -curPos.y) - TPoint(m_pos.x(), -m_pos.y()));
733   else {
734     double fac = sqrt(1.0 / fabs(aff.det()));
735 
736     if (m_dragType & eMoveLeft) r.x0 += fac * (curPos.x - m_pos.x());
737     if (m_dragType & eMoveRight) r.x1 += fac * (curPos.x - m_pos.x());
738     if (m_dragType & eMoveDown) r.y1 -= fac * (curPos.y - m_pos.y());
739     if (m_dragType & eMoveUp) r.y0 -= fac * (curPos.y - m_pos.y());
740   }
741   m_flipbook->setLoadbox(r);  // convert(aff.inv() * convert(r)));
742 }
743 
744 //--------------------------------------------------------
updateCursor(const TPoint & curPos)745 void ImageViewer::updateCursor(const TPoint &curPos) {
746   int dragType = getDragType(
747       curPos,
748       convert(getImgToWidgetAffine() * convert(m_flipbook->getLoadbox())));
749 
750   switch (dragType) {
751   case eMoveRect:
752     setCursor(Qt::SizeAllCursor);
753     break;
754   case eMoveLeft:
755   case eMoveRight:
756     setCursor(Qt::SizeHorCursor);
757     break;
758   case eMoveDown:
759   case eMoveUp:
760     setCursor(Qt::SizeVerCursor);
761     break;
762   case eMoveLeft | eMoveUp:
763   case eMoveRight | eMoveDown:
764     setCursor(Qt::SizeBDiagCursor);
765     break;
766   case eMoveLeft | eMoveDown:
767   case eMoveRight | eMoveUp:
768     setCursor(Qt::SizeFDiagCursor);
769     break;
770   default:
771     setCursor(Qt::ArrowCursor);
772     break;
773   }
774 }
775 
776 //---------------------------------------------------------------------------------------------
777 
showEvent(QShowEvent *)778 void ImageViewer::showEvent(QShowEvent *) {
779   TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();
780   bool ret = connect(sceneHandle, SIGNAL(preferenceChanged(const QString &)),
781                      this, SLOT(onPreferenceChanged(const QString &)));
782   onPreferenceChanged("ColorCalibration");
783   assert(ret);
784 }
785 
786 //---------------------------------------------------------------------------------------------
787 
hideEvent(QHideEvent *)788 void ImageViewer::hideEvent(QHideEvent *) {
789   TSceneHandle *sceneHandle = TApp::instance()->getCurrentScene();
790   if (sceneHandle) sceneHandle->disconnect(this);
791 }
792 
793 //---------------------------------------------------------------------------------------------
794 /*! If middle button is pressed pan the image. Update current mouse position.
795  */
mouseMoveEvent(QMouseEvent * event)796 void ImageViewer::mouseMoveEvent(QMouseEvent *event) {
797   if (!m_image) return;
798 
799   if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen &&
800       !m_stylusUsed) {
801     return;
802   }
803 
804   QPoint curQPos = event->pos() * getDevPixRatio();
805 
806   TPoint curPos = TPoint(curQPos.x(), curQPos.y());
807 
808   if (m_visualSettings.m_defineLoadbox && m_flipbook) {
809     if (m_mouseButton == Qt::LeftButton)
810       updateLoadbox(curPos);
811     else if (m_mouseButton == Qt::MidButton)
812       panQt(curQPos - m_pos);
813     else
814       updateCursor(curPos);
815     update();
816     event->ignore();
817     m_pos = curQPos;
818     return;
819   }
820 
821   // setting the cursors
822   if (!m_isColorModel) {
823     // when the histogram window is opened, switch to the RGB picker tool
824     if (m_isHistogramEnable && m_histogramPopup->isVisible())
825       setToolCursor(this, ToolCursor::PickerRGB);
826     else
827       setCursor(Qt::ArrowCursor);
828   }
829 
830   if (m_visualSettings.m_doCompare && m_mouseButton == Qt::NoButton) {
831     if (fabs(curPos.x - width() * m_compareSettings.m_compareX) < 20)
832       setToolCursor(this, ToolCursor::ScaleHCursor);
833     else if (fabs((height() - curPos.y) -
834                   height() * m_compareSettings.m_compareY) < 20)
835       setToolCursor(this, ToolCursor::ScaleVCursor);
836   }
837 
838   if (m_compareSettings.m_dragCompareX || m_compareSettings.m_dragCompareY)
839     dragCompare(curQPos - m_pos);
840   else if (m_mouseButton == Qt::MidButton)
841     panQt(curQPos - m_pos);
842 
843   m_pos = curQPos;
844 
845   // pick the color if the histogram popup is opened
846   if (m_isHistogramEnable && m_histogramPopup->isVisible() && !m_isColorModel) {
847     // Rect Pick
848     if (m_mouseButton == Qt::LeftButton &&
849         (event->modifiers() & Qt::ControlModifier)) {
850       if (!m_rectRGBPick && !m_visualSettings.m_defineLoadbox &&
851           (abs(m_pos.x() - m_pressedMousePos.x) > 10 ||
852            abs(m_pos.y() - m_pressedMousePos.y) > 10) &&
853           !(m_compareSettings.m_dragCompareX ||
854             m_compareSettings.m_dragCompareY) &&
855           m_flipbook)
856         m_rectRGBPick = true;
857 
858       if (m_rectRGBPick) {
859         update();
860         rectPickColor();
861       }
862     }
863 
864     update();
865     pickColor(event);
866     return;
867   }
868 
869   if (m_mouseButton == Qt::LeftButton && !m_isColorModel &&
870       (event->modifiers() & Qt::AltModifier)) {
871     if (!m_draggingZoomSelection && !m_visualSettings.m_defineLoadbox &&
872         (abs(m_pos.x() - m_pressedMousePos.x) > 10 ||
873          abs(m_pos.y() - m_pressedMousePos.y) > 10) &&
874         !(m_compareSettings.m_dragCompareX ||
875           m_compareSettings.m_dragCompareY) &&
876         m_flipbook)
877       m_draggingZoomSelection = true;
878 
879     if (m_draggingZoomSelection) update();
880   } else
881     m_draggingZoomSelection = false;
882 
883   if (m_isColorModel && m_mouseButton == Qt::LeftButton) {
884     event->ignore();
885   }
886 }
887 
888 //---------------------------------------------------------------------------------------------
889 /*! notify the color picked by rgb picker to palette controller
890  */
setPickedColorToStyleEditor(const TPixel32 & color)891 void ImageViewer::setPickedColorToStyleEditor(const TPixel32 &color) {
892   // do not modify the style #0
893   TPaletteHandle *ph =
894       TApp::instance()->getPaletteController()->getCurrentPalette();
895   if (ph->getStyleIndex() == 0) return;
896 
897   TApp::instance()->getPaletteController()->setColorSample(color);
898 }
899 
900 //---------------------------------------------------------------------------------------------
901 
getPickedImage(QPointF mousePos)902 TImageP ImageViewer::getPickedImage(QPointF mousePos) {
903   bool cursorIsInSnapShot = false;
904   if (m_visualSettings.m_doCompare) {
905     if (m_compareSettings.m_compareY == ImagePainter::DefaultCompareValue)
906       cursorIsInSnapShot =
907           m_compareSettings.m_swapCompared ==
908           (mousePos.x() > width() * m_compareSettings.m_compareX);
909     else
910       cursorIsInSnapShot =
911           m_compareSettings.m_swapCompared ==
912           (height() - mousePos.y() > height() * m_compareSettings.m_compareY);
913   }
914   if (cursorIsInSnapShot)
915     return TImageCache::instance()->get(QString("TnzCompareImg"), false);
916   else
917     return m_image;
918 }
919 
920 //---------------------------------------------------------------------------------------------
921 /*! rgb picking
922  */
pickColor(QMouseEvent * event,bool putValueToStyleEditor)923 void ImageViewer::pickColor(QMouseEvent *event, bool putValueToStyleEditor) {
924   if (!m_isHistogramEnable) return;
925   if (!m_histogramPopup->isVisible()) return;
926 
927   QPointF curPos = event->localPos() * getDevPixRatio();
928 
929   // avoid to pick outside of the flip
930   if ((!m_image) || !rect().contains(curPos.toPoint())) {
931     // throw transparent color
932     m_histogramPopup->updateInfo(TPixel32::Transparent, TPointD(-1, -1));
933     return;
934   }
935 
936   TImageP img = getPickedImage(curPos);
937   if (!img) {
938     m_histogramPopup->updateInfo(TPixel32::Transparent, TPointD(-1, -1));
939     return;
940   }
941 
942   StylePicker picker(img);
943 
944   TPointD mousePos = TPointD(curPos.x(), height() - 1 - curPos.y());
945   TRectD area      = TRectD(mousePos.x, mousePos.y, mousePos.x, mousePos.y);
946 
947   TPointD pos =
948       getViewAff().inv() * TPointD(curPos.x() - (qreal)(width()) / 2,
949                                    -curPos.y() + (qreal)(height()) / 2);
950 
951   TRectD imgRect = (img->raster()) ? convert(TRect(img->raster()->getSize()))
952                                    : img->getBBox();
953   TPointD imagePos =
954       TPointD(0.5 * imgRect.getLx() + pos.x, 0.5 * imgRect.getLy() + pos.y);
955 
956   TPixel32 pix;
957   if (m_lutCalibrator && m_lutCalibrator->isValid()) {
958     // for specifiying pixel range on picking vector
959     double scale2 = getViewAff().det();
960     pix           = picker.pickColor(pos + TPointD(-0.5, -0.5), 10.0, scale2);
961   } else
962     pix = picker.pickColor(area);
963 
964   if (!img->raster() || imgRect.contains(imagePos)) {
965     // throw the picked color to the histogram
966     m_histogramPopup->updateInfo(pix, imagePos);
967     // throw it to the style editor as well
968     if (putValueToStyleEditor) setPickedColorToStyleEditor(pix);
969   } else {
970     // throw transparent color if picking outside of the image
971     m_histogramPopup->updateInfo(TPixel32::Transparent, TPointD(-1, -1));
972   }
973 }
974 
975 //---------------------------------------------------------------------------------------------
976 /*! rectangular rgb picking. The picked color will be an average of pixels in
977  * specified rectangle
978  */
rectPickColor(bool putValueToStyleEditor)979 void ImageViewer::rectPickColor(bool putValueToStyleEditor) {
980   auto isRas32 = [this](TImageP img) -> bool {
981     TRasterImageP ri = img;
982     if (!ri) return false;
983     TRaster32P ras32 = ri->getRaster();
984     if (!ras32) return false;
985     return true;
986   };
987 
988   if (!m_isHistogramEnable) return;
989   if (!m_histogramPopup->isVisible()) return;
990 
991   TImageP img = getPickedImage(m_pos);
992   if (!img) {
993     m_histogramPopup->updateAverageColor(TPixel32::Transparent);
994     return;
995   }
996 
997   StylePicker picker(img);
998 
999   TPoint startPos =
1000       TPoint(m_pressedMousePos.x, height() - 1 - m_pressedMousePos.y);
1001   TPoint endPos = TPoint(m_pos.x(), height() - 1 - m_pos.y());
1002   TRectD area   = TRectD(convert(startPos), convert(endPos));
1003   area          = area.enlarge(-1, -1);
1004   if (area.getLx() < 2 || area.getLy() < 2) {
1005     m_histogramPopup->updateAverageColor(TPixel32::Transparent);
1006     return;
1007   }
1008 
1009   TPixel32 pix;
1010   if (m_lutCalibrator && m_lutCalibrator->isValid() && isRas32(img)) {
1011     TPointD start = getViewAff().inv() * TPointD(startPos.x - width() / 2,
1012                                                  startPos.y - height() / 2);
1013     TPointD end   = getViewAff().inv() *
1014                   TPointD(endPos.x - width() / 2, endPos.y - height() / 2);
1015     pix = picker.pickAverageColor(TRectD(start, end));
1016   } else
1017     pix = picker.pickColor(area.enlarge(-1, -1));
1018 
1019   // throw the picked color to the histogram
1020   m_histogramPopup->updateAverageColor(pix);
1021   // throw it to the style editor as well
1022   if (putValueToStyleEditor) setPickedColorToStyleEditor(pix);
1023 }
1024 
1025 //-----------------------------------------------------------------------------
1026 /*! Set current position and current mouse button event.
1027                 Ignore event, so access to parent mousePressEvent.
1028 */
1029 
getDragType(const TPoint & pos,const TRect & loadbox)1030 int ImageViewer::getDragType(const TPoint &pos, const TRect &loadbox) {
1031   if (loadbox == TRect()) return eDrawRect;
1032 
1033   int ret = 0, dx = std::min(abs(loadbox.x0 - pos.x), abs(loadbox.x1 - pos.x)),
1034       dy = std::min(abs(loadbox.y0 - pos.y), abs(loadbox.y1 - pos.y));
1035 
1036   if (dx > 10 && dy > 10)
1037     return (loadbox.contains(pos)) ? eMoveRect : eDrawRect;
1038 
1039   if (dx <= 10 && pos.y >= loadbox.y0 - 10 && pos.y <= loadbox.y1 + 10) {
1040     if (dx == abs(loadbox.x0 - pos.x))
1041       ret = eMoveLeft;
1042     else if (dx == abs(loadbox.x1 - pos.x))
1043       ret = eMoveRight;
1044   } else if (dy <= 10 && pos.x >= loadbox.x0 - 10 && pos.x <= loadbox.x1 + 10) {
1045     if (dy == abs(loadbox.y0 - pos.y))
1046       return eMoveDown;
1047     else
1048       return eMoveUp;
1049   } else
1050     return eDrawRect;
1051 
1052   if (dy > 10) return ret;
1053 
1054   return ret | (dy == (abs(loadbox.y0 - pos.y)) ? eMoveDown : eMoveUp);
1055 }
1056 
1057 //-------------------------------------------------------------------------------
mouseDoubleClickEvent(QMouseEvent * event)1058 void ImageViewer::mouseDoubleClickEvent(QMouseEvent *event) {
1059   if (!m_image) return;
1060   // qDebug() << "[mouseDoubleClickEvent]";
1061   if (m_gestureActive && !m_stylusUsed) {
1062     m_gestureActive = false;
1063     fitView();
1064     return;
1065   }
1066 
1067   if (m_visualSettings.m_defineLoadbox && m_flipbook) {
1068     m_flipbook->setLoadbox(TRect());
1069     update();
1070     event->ignore();
1071   }
1072 }
1073 
1074 //------------------------------------------------------------------------------
1075 
mousePressEvent(QMouseEvent * event)1076 void ImageViewer::mousePressEvent(QMouseEvent *event) {
1077   if (!m_image) return;
1078 
1079   // qDebug() << "[mousePressEvent]";
1080   if (m_gestureActive && m_touchDevice == QTouchDevice::TouchScreen &&
1081       !m_stylusUsed) {
1082     return;
1083   }
1084 
1085   m_pos                   = event->pos() * getDevPixRatio();
1086   m_pressedMousePos       = TPoint(m_pos.x(), m_pos.y());
1087   m_mouseButton           = event->button();
1088   m_draggingZoomSelection = false;
1089 
1090   if (m_mouseButton != Qt::LeftButton) {
1091     event->ignore();
1092     return;
1093   }
1094   if (m_visualSettings.m_defineLoadbox && m_flipbook)
1095     m_dragType = getDragType(
1096         m_pressedMousePos,
1097         convert(getImgToWidgetAffine() * convert(m_flipbook->getLoadbox())));
1098   else if (m_visualSettings.m_doCompare) {
1099     if (fabs(m_pos.x() - width() * m_compareSettings.m_compareX) < 20) {
1100       m_compareSettings.m_dragCompareX = true;
1101       m_compareSettings.m_dragCompareY = false;
1102       m_compareSettings.m_compareY     = ImagePainter::DefaultCompareValue;
1103       update();
1104     } else if (fabs((height() - m_pos.y()) -
1105                     height() * m_compareSettings.m_compareY) < 20) {
1106       m_compareSettings.m_dragCompareY = true;
1107       m_compareSettings.m_dragCompareX = false;
1108       m_compareSettings.m_compareX     = ImagePainter::DefaultCompareValue;
1109       update();
1110     } else
1111       m_compareSettings.m_dragCompareX = m_compareSettings.m_dragCompareY =
1112           false;
1113   }
1114 
1115   // pick color and throw it to the style editor
1116   if (m_isHistogramEnable && m_histogramPopup->isVisible() && !m_isColorModel &&
1117       !(event->modifiers() & Qt::ControlModifier))  // if ctrl button is
1118                                                     // pressed, which means
1119                                                     // rectangular pick
1120   {
1121     update();
1122     pickColor(event, true);
1123   }
1124 
1125   event->ignore();
1126 }
1127 
1128 //-----------------------------------------------------------------------------
1129 /*! Reset current mouse position and current mouse button event.
1130  */
mouseReleaseEvent(QMouseEvent * event)1131 void ImageViewer::mouseReleaseEvent(QMouseEvent *event) {
1132   if (!m_image) return;
1133   if (m_draggingZoomSelection && !m_visualSettings.m_defineLoadbox) {
1134     m_draggingZoomSelection = false;
1135 
1136     int left, right, top, bottom;
1137     if (m_pos.x() < m_pressedMousePos.x)
1138       left = m_pos.x(), right = m_pressedMousePos.x;
1139     else
1140       right = m_pos.x(), left = m_pressedMousePos.x;
1141     if (m_pos.y() < m_pressedMousePos.y)
1142       top = m_pos.y(), bottom = m_pressedMousePos.y;
1143     else
1144       bottom = m_pos.y(), top = m_pressedMousePos.y;
1145 
1146     adaptView(QRect(QPoint(left, top), QPoint(right, bottom)));
1147   }
1148 
1149   if (m_rectRGBPick) {
1150     // pick color in the rectangular region
1151     rectPickColor(true);
1152     // release the flag
1153     m_rectRGBPick = false;
1154     update();
1155   }
1156 
1157   m_pos                            = QPoint(0, 0);
1158   m_mouseButton                    = Qt::NoButton;
1159   m_compareSettings.m_dragCompareX = m_compareSettings.m_dragCompareY = false;
1160 
1161   m_gestureActive = false;
1162   m_zooming       = false;
1163   m_panning       = false;
1164   m_stylusUsed    = false;
1165 
1166   event->ignore();
1167 }
1168 
1169 //-----------------------------------------------------------------------------
1170 /*! Apply zoom.
1171  */
wheelEvent(QWheelEvent * event)1172 void ImageViewer::wheelEvent(QWheelEvent *event) {
1173   if (!m_image) return;
1174   if (event->orientation() == Qt::Horizontal) return;
1175   int delta = 0;
1176   switch (event->source()) {
1177   case Qt::MouseEventNotSynthesized: {
1178     if (event->modifiers() & Qt::AltModifier)
1179       delta = event->angleDelta().x();
1180     else
1181       delta = event->angleDelta().y();
1182     break;
1183   }
1184 
1185   case Qt::MouseEventSynthesizedBySystem: {
1186     QPoint numPixels  = event->pixelDelta();
1187     QPoint numDegrees = event->angleDelta() / 8;
1188     if (!numPixels.isNull()) {
1189       delta = event->pixelDelta().y();
1190     } else if (!numDegrees.isNull()) {
1191       QPoint numSteps = numDegrees / 15;
1192       delta           = numSteps.y();
1193     }
1194     break;
1195   }
1196 
1197   default:  // Qt::MouseEventSynthesizedByQt,
1198             // Qt::MouseEventSynthesizedByApplication
1199   {
1200     std::cout << "not supported event: Qt::MouseEventSynthesizedByQt, "
1201                  "Qt::MouseEventSynthesizedByApplication"
1202               << std::endl;
1203     break;
1204   }
1205 
1206   }  // end switch
1207 
1208   if (abs(delta) > 0) {
1209     if ((m_gestureActive == true &&
1210          m_touchDevice == QTouchDevice::TouchScreen) ||
1211         m_gestureActive == false) {
1212       int delta = event->delta() > 0 ? 120 : -120;
1213       QPoint center(event->pos().x() * getDevPixRatio() - width() / 2,
1214                     -event->pos().y() * getDevPixRatio() + height() / 2);
1215       zoomQt(center, exp(0.001 * delta));
1216     }
1217   }
1218   event->accept();
1219 }
1220 
1221 //-----------------------------------------------------------------------------
1222 
swapCompared()1223 void ImageViewer::swapCompared() {
1224   m_compareSettings.m_swapCompared = !m_compareSettings.m_swapCompared;
1225   update();
1226 }
1227 
1228 //-----------------------------------------------------------------------------
1229 
setViewAff(TAffine viewAff)1230 void ImageViewer::setViewAff(TAffine viewAff) {
1231   m_viewAff = viewAff;
1232   update();
1233   if (m_flipbook && m_flipbook->getPreviewedFx())
1234     // Update the observable fx region
1235     m_flipbook->schedulePreviewedFxUpdate();
1236 }
1237 
1238 //-----------------------------------------------------------------------------
1239 
resetView()1240 void ImageViewer::resetView() { zoomQt(false, true); }
1241 
1242 //-----------------------------------------------------------------------------
1243 
fitView()1244 void ImageViewer::fitView() {
1245   if (!m_image) return;
1246   TRect imgBounds(getImageBounds(m_image));
1247   adaptView(imgBounds, imgBounds);
1248 }
1249 
1250 //-----------------------------------------------------------------------------
1251 /*! Update image viewer, reset current matrix trasformation, current position
1252                 and update view.
1253 */
updateImageViewer()1254 void ImageViewer::updateImageViewer() {
1255   setViewAff(TAffine());
1256   m_pos = QPoint(0, 0);
1257   update();
1258 }
1259 
1260 //-----------------------------------------------------------------------------
1261 
getImgToWidgetAffine() const1262 TAffine ImageViewer::getImgToWidgetAffine() const {
1263   assert(m_image);
1264   return getImgToWidgetAffine(getImageBoundsD(m_image));
1265 }
1266 
1267 //-----------------------------------------------------------------------------
1268 
1269 //! Returns the affine transform from image reference to widget's pixel one.
getImgToWidgetAffine(const TRectD & geom) const1270 TAffine ImageViewer::getImgToWidgetAffine(const TRectD &geom) const {
1271   TPointD geomCenter((geom.x0 + geom.x1) * 0.5, (geom.y0 + geom.y1) * 0.5);
1272 
1273   QRect widGeom(rect());
1274 
1275   TPointD viewerCenter((widGeom.left() + widGeom.right() + 1) * 0.5,
1276                        (widGeom.top() + widGeom.bottom() + 1) * 0.5);
1277 
1278   return TAffine(TAffine(1.0, 0.0, 0.0, 0.0, -1.0, height()) *
1279                  TTranslation(viewerCenter) * m_viewAff *
1280                  TTranslation(-geomCenter));
1281 }
1282 
1283 //-----------------------------------------------------------------------------
1284 
1285 //! Adapts image viewer's affine to display the passed image rect at maximized
1286 //! ratio
adaptView(const TRect & imgRect,const TRect & viewRect)1287 void ImageViewer::adaptView(const TRect &imgRect, const TRect &viewRect) {
1288   QRect viewerRect(rect());
1289 
1290   if (viewerRect.isEmpty()) return;
1291 
1292   double imageScale = std::min(viewerRect.width() / (double)viewRect.getLx(),
1293                                viewerRect.height() / (double)viewRect.getLy());
1294 
1295   TPointD viewRectCenter((viewRect.x0 + viewRect.x1 + 1) * 0.5,
1296                          (viewRect.y0 + viewRect.y1 + 1) * 0.5);
1297   TPointD imgRectCenter((imgRect.x0 + imgRect.x1 + 1) * 0.5,
1298                         (imgRect.y0 + imgRect.y1 + 1) * 0.5);
1299 
1300   TAffine newViewAff(TScale(imageScale, imageScale) *
1301                      TTranslation(imgRectCenter - viewRectCenter));
1302   setViewAff(newViewAff);
1303 }
1304 
1305 //-----------------------------------------------------------------------------
1306 
1307 //! Adapts image viewer's affine to display the passed viewer rect at maximized
1308 //! ratio
adaptView(const QRect & geomRect)1309 void ImageViewer::adaptView(const QRect &geomRect) {
1310   if (!m_image) return;
1311 
1312   // Retrieve the rect in image reference and call the associated adaptView
1313   TRect imgBounds(getImageBounds(m_image));
1314   TRectD imgBoundsD(imgBounds.x0, imgBounds.y0, imgBounds.x1 + 1,
1315                     imgBounds.y1 + 1);
1316 
1317   TRectD geomRectD(geomRect.left(), geomRect.top(), geomRect.right() + 1,
1318                    geomRect.bottom() + 1);
1319   TRectD viewRectD(getImgToWidgetAffine().inv() * geomRectD);
1320   TRect viewRect(tfloor(viewRectD.x0), tfloor(viewRectD.y0),
1321                  tceil(viewRectD.x1) - 1, tceil(viewRectD.y1) - 1);
1322 
1323   adaptView(imgBounds, viewRect);
1324 }
1325 
doSwapBuffers()1326 void ImageViewer::doSwapBuffers() { glFlush(); }
1327 
changeSwapBehavior(bool enable)1328 void ImageViewer::changeSwapBehavior(bool enable) {
1329   // do nothing for now as setUpdateBehavior is not available with QGLWidget
1330   // setUpdateBehavior(enable ? PartialUpdate : NoPartialUpdate);
1331 }
1332 
1333 //-----------------------------------------------------------------------------
1334 
keyPressEvent(QKeyEvent * event)1335 void ImageViewer::keyPressEvent(QKeyEvent *event) {
1336   if (FlipZoomer(this).exec(event)) return;
1337 
1338   ImageViewerShortcutReceiver(m_flipbook).exec(event);
1339 }
1340 
1341 //-----------------------------------------------------------------------------
1342 
onContextAboutToBeDestroyed()1343 void ImageViewer::onContextAboutToBeDestroyed() {
1344   if (!m_lutCalibrator) return;
1345   makeCurrent();
1346   m_lutCalibrator->cleanup();
1347   doneCurrent();
1348   disconnect(context(), SIGNAL(aboutToBeDestroyed()), this,
1349              SLOT(onContextAboutToBeDestroyed()));
1350 }
1351 
1352 //-----------------------------------------------------------------------------
1353 
onPreferenceChanged(const QString & prefName)1354 void ImageViewer::onPreferenceChanged(const QString &prefName) {
1355   if (prefName == "ColorCalibration") {
1356     if (Preferences::instance()->isColorCalibrationEnabled()) {
1357       // if the window is so shriked that the gl widget is empty,
1358       // showEvent can be called before creating the context.
1359       if (!context()) return;
1360       makeCurrent();
1361       if (!m_lutCalibrator)
1362         m_lutCalibrator = new LutCalibrator();
1363       else
1364         m_lutCalibrator->cleanup();
1365       m_lutCalibrator->initialize();
1366       connect(context(), SIGNAL(aboutToBeDestroyed()), this,
1367               SLOT(onContextAboutToBeDestroyed()));
1368       if (m_lutCalibrator->isValid() && !m_fbo)
1369         m_fbo = new QOpenGLFramebufferObject(width(), height());
1370       doneCurrent();
1371     }
1372     update();
1373   }
1374 }
1375 
1376 //------------------------------------------------------------------
1377 
tabletEvent(QTabletEvent * e)1378 void ImageViewer::tabletEvent(QTabletEvent *e) {
1379   // qDebug() << "[tabletEvent]";
1380   if (e->type() == QTabletEvent::TabletPress) {
1381     m_stylusUsed = e->pointerType() ? true : false;
1382   } else if (e->type() == QTabletEvent::TabletRelease) {
1383     m_stylusUsed = false;
1384   }
1385 
1386   e->accept();
1387 }
1388 
1389 //------------------------------------------------------------------
1390 
gestureEvent(QGestureEvent * e)1391 void ImageViewer::gestureEvent(QGestureEvent *e) {
1392   // qDebug() << "[gestureEvent]";
1393   m_gestureActive = false;
1394   if (QGesture *swipe = e->gesture(Qt::SwipeGesture)) {
1395     m_gestureActive = true;
1396   } else if (QGesture *pan = e->gesture(Qt::PanGesture)) {
1397     m_gestureActive = true;
1398   }
1399   if (QGesture *pinch = e->gesture(Qt::PinchGesture)) {
1400     QPinchGesture *gesture = static_cast<QPinchGesture *>(pinch);
1401     QPinchGesture::ChangeFlags changeFlags = gesture->changeFlags();
1402     QPoint firstCenter                     = gesture->centerPoint().toPoint();
1403     if (m_touchDevice == QTouchDevice::TouchScreen)
1404       firstCenter = mapFromGlobal(firstCenter);
1405 
1406     if (gesture->state() == Qt::GestureStarted) {
1407       m_gestureActive = true;
1408     } else if (gesture->state() == Qt::GestureFinished) {
1409       m_gestureActive = false;
1410       m_zooming       = false;
1411       m_scaleFactor   = 0.0;
1412     } else {
1413       if (changeFlags & QPinchGesture::ScaleFactorChanged) {
1414         double scaleFactor = gesture->scaleFactor();
1415         // the scale factor makes for too sensitive scaling
1416         // divide the change in half
1417         if (scaleFactor > 1) {
1418           double decimalValue = scaleFactor - 1;
1419           decimalValue /= 1.5;
1420           scaleFactor = 1 + decimalValue;
1421         } else if (scaleFactor < 1) {
1422           double decimalValue = 1 - scaleFactor;
1423           decimalValue /= 1.5;
1424           scaleFactor = 1 - decimalValue;
1425         }
1426         if (!m_zooming) {
1427           double delta = scaleFactor - 1;
1428           m_scaleFactor += delta;
1429           if (m_scaleFactor > .2 || m_scaleFactor < -.2) {
1430             m_zooming = true;
1431           }
1432         }
1433         if (m_zooming) {
1434           const QPoint center(
1435               firstCenter.x() * getDevPixRatio() - width() / 2,
1436               -firstCenter.y() * getDevPixRatio() + height() / 2);
1437           zoomQt(center, scaleFactor);
1438           m_panning = false;
1439         }
1440         m_gestureActive = true;
1441       }
1442 
1443       if (changeFlags & QPinchGesture::CenterPointChanged) {
1444         QPointF centerDelta = (gesture->centerPoint() * getDevPixRatio()) -
1445                               (gesture->lastCenterPoint() * getDevPixRatio());
1446         if (centerDelta.manhattanLength() > 1) {
1447           // panQt(centerDelta.toPoint());
1448         }
1449         m_gestureActive = true;
1450       }
1451     }
1452   }
1453   e->accept();
1454 }
1455 
touchEvent(QTouchEvent * e,int type)1456 void ImageViewer::touchEvent(QTouchEvent *e, int type) {
1457   // qDebug() << "[touchEvent]";
1458   if (type == QEvent::TouchBegin) {
1459     m_touchActive   = true;
1460     m_firstPanPoint = e->touchPoints().at(0).pos();
1461     // obtain device type
1462     m_touchDevice = e->device()->type();
1463   } else if (m_touchActive) {
1464     // touchpads must have 2 finger panning for tools and navigation to be
1465     // functional on other devices, 1 finger panning is preferred
1466     if ((e->touchPoints().count() == 2 &&
1467          m_touchDevice == QTouchDevice::TouchPad) ||
1468         (e->touchPoints().count() == 1 &&
1469          m_touchDevice == QTouchDevice::TouchScreen)) {
1470       QTouchEvent::TouchPoint panPoint = e->touchPoints().at(0);
1471       if (!m_panning) {
1472         QPointF deltaPoint = panPoint.pos() - m_firstPanPoint;
1473         // minimize accidental and jerky zooming/rotating during 2 finger
1474         // panning
1475         if ((deltaPoint.manhattanLength() > 100) && !m_zooming) {
1476           m_panning = true;
1477         }
1478       }
1479       if (m_panning) {
1480         QPoint curPos      = panPoint.pos().toPoint() * getDevPixRatio();
1481         QPoint lastPos     = panPoint.lastPos().toPoint() * getDevPixRatio();
1482         QPoint centerDelta = curPos - lastPos;
1483         panQt(centerDelta);
1484       }
1485     }
1486   }
1487   if (type == QEvent::TouchEnd || type == QEvent::TouchCancel) {
1488     m_touchActive = false;
1489     m_panning     = false;
1490   }
1491   e->accept();
1492 }
1493 
event(QEvent * e)1494 bool ImageViewer::event(QEvent *e) {
1495   /*
1496   switch (e->type()) {
1497   case QEvent::TabletPress: {
1498   QTabletEvent *te = static_cast<QTabletEvent *>(e);
1499   qDebug() << "[event] TabletPress: pointerType(" << te->pointerType()
1500   << ") device(" << te->device() << ")";
1501   } break;
1502   case QEvent::TabletRelease:
1503   qDebug() << "[event] TabletRelease";
1504   break;
1505   case QEvent::TouchBegin:
1506   qDebug() << "[event] TouchBegin";
1507   break;
1508   case QEvent::TouchEnd:
1509   qDebug() << "[event] TouchEnd";
1510   break;
1511   case QEvent::TouchCancel:
1512   qDebug() << "[event] TouchCancel";
1513   break;
1514   case QEvent::MouseButtonPress:
1515   qDebug() << "[event] MouseButtonPress";
1516   break;
1517   case QEvent::MouseButtonDblClick:
1518   qDebug() << "[event] MouseButtonDblClick";
1519   break;
1520   case QEvent::MouseButtonRelease:
1521   qDebug() << "[event] MouseButtonRelease";
1522   break;
1523   case QEvent::Gesture:
1524   qDebug() << "[event] Gesture";
1525   break;
1526   default:
1527   break;
1528   }
1529   */
1530 
1531   if (e->type() == QEvent::Gesture && CommandManager::instance()
1532                                           ->getAction(MI_TouchGestureControl)
1533                                           ->isChecked()) {
1534     gestureEvent(static_cast<QGestureEvent *>(e));
1535     return true;
1536   }
1537   if ((e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchEnd ||
1538        e->type() == QEvent::TouchCancel || e->type() == QEvent::TouchUpdate) &&
1539       CommandManager::instance()
1540           ->getAction(MI_TouchGestureControl)
1541           ->isChecked()) {
1542     touchEvent(static_cast<QTouchEvent *>(e), e->type());
1543     m_gestureActive = true;
1544     return true;
1545   }
1546   return GLWidgetForHighDpi::event(e);
1547 }
1548 
1549 //-----------------------------------------------------------------------------
1550 /*! load image from history
1551  */
1552 class LoadRecentFlipbookImagesCommandHandler final : public MenuItemHandler {
1553 public:
LoadRecentFlipbookImagesCommandHandler()1554   LoadRecentFlipbookImagesCommandHandler()
1555       : MenuItemHandler(MI_LoadRecentImage) {}
execute()1556   void execute() override {
1557     QAction *act = CommandManager::instance()->getAction(MI_LoadRecentImage);
1558 
1559     /*--- 右クリックで呼ばれないとここにWidgetが入らない ---*/
1560     FlipBook *flip = qobject_cast<FlipBook *>(act->parentWidget());
1561     if (!flip) return;
1562 
1563     DVMenuAction *menu = dynamic_cast<DVMenuAction *>(act->menu());
1564     int index          = menu->getTriggeredActionIndex();
1565     QString path =
1566         RecentFiles::instance()->getFilePath(index, RecentFiles::Flip);
1567 
1568     TFilePath fp(path.toStdWString());
1569 
1570     /*--- shrinkは1でロードする ---*/
1571     ::viewFile(fp, -1, -1, -1, 1, 0, flip, false);
1572 
1573     RecentFiles::instance()->moveFilePath(index, 0, RecentFiles::Flip);
1574   }
1575 } loadRecentFlipbookImagesCommandHandler;
1576 
1577 //-----------------------------------------------------------------------------
1578 /*! clear the history
1579  */
1580 class ClearRecentFlipbookImagesCommandHandler final : public MenuItemHandler {
1581 public:
ClearRecentFlipbookImagesCommandHandler()1582   ClearRecentFlipbookImagesCommandHandler()
1583       : MenuItemHandler(MI_ClearRecentImage) {}
execute()1584   void execute() override {
1585     RecentFiles::instance()->clearRecentFilesList(RecentFiles::Flip);
1586   }
1587 } clearRecentFlipbookImagesCommandHandler;
1588