All Classes Namespaces Functions Variables Enumerations Properties Pages
scribblearea.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5 Copyright (C) 2012-2020 Matthew Chiawen Chang
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; version 2 of the License.
10 
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 
16 */
17 
18 #include "scribblearea.h"
19 
20 #include <cmath>
21 #include <QGuiApplication>
22 #include <QMessageBox>
23 #include <QPixmapCache>
24 
25 #include "pointerevent.h"
26 #include "beziercurve.h"
27 #include "object.h"
28 #include "editor.h"
29 #include "layerbitmap.h"
30 #include "layervector.h"
31 #include "layercamera.h"
32 #include "bitmapimage.h"
33 #include "vectorimage.h"
34 
35 #include "colormanager.h"
36 #include "toolmanager.h"
37 #include "strokemanager.h"
38 #include "layermanager.h"
39 #include "playbackmanager.h"
40 #include "viewmanager.h"
41 #include "selectionmanager.h"
42 #include "overlaymanager.h"
43 
44 ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent)
45 {
46  setObjectName("ScribbleArea");
47 
48  // Qt::WA_StaticContents ensure that the widget contents are rooted to the top-left corner
49  // and don't change when the widget is resized.
51 
52  mStrokeManager.reset(new StrokeManager);
53 }
54 
55 ScribbleArea::~ScribbleArea()
56 {
57  delete mBufferImg;
58 }
59 
60 bool ScribbleArea::init()
61 {
62  mPrefs = mEditor->preference();
63  mDoubleClickTimer = new QTimer(this);
64 
65  connect(mPrefs, &PreferenceManager::optionChanged, this, &ScribbleArea::settingUpdated);
66  connect(mDoubleClickTimer, &QTimer::timeout, this, &ScribbleArea::handleDoubleClick);
67 
68  connect(mEditor->select(), &SelectionManager::selectionChanged, this, &ScribbleArea::onSelectionChanged);
69  connect(mEditor->select(), &SelectionManager::needPaintAndApply, this, &ScribbleArea::applySelectionChanges);
70  connect(mEditor->select(), &SelectionManager::needDeleteSelection, this, &ScribbleArea::deleteSelection);
71 
72  mDoubleClickTimer->setInterval(50);
73 
74  const int curveSmoothingLevel = mPrefs->getInt(SETTING::CURVE_SMOOTHING);
75  mCurveSmoothingLevel = curveSmoothingLevel / 20.0; // default value is 1.0
76 
77  mQuickSizing = mPrefs->isOn(SETTING::QUICK_SIZING);
78  mMakeInvisible = false;
79 
80  mIsSimplified = mPrefs->isOn(SETTING::OUTLINES);
81  mMultiLayerOnionSkin = mPrefs->isOn(SETTING::MULTILAYER_ONION);
82 
83  mLayerVisibility = static_cast<LayerVisibility>(mPrefs->getInt(SETTING::LAYER_VISIBILITY));
84 
85  mBufferImg = new BitmapImage;
86 
87  updateCanvasCursor();
88 
89  setMouseTracking(true); // reacts to mouse move events, even if the button is not pressed
90 
91 #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
92  setTabletTracking(true); // tablet tracking first added in 5.9
93 #endif
94 
96 
97  QPixmapCache::setCacheLimit(100 * 1024); // unit is kb, so it's 100MB cache
98  mPixmapCacheKeys.clear();
99 
100  return true;
101 }
102 
103 void ScribbleArea::settingUpdated(SETTING setting)
104 {
105  switch (setting)
106  {
107  case SETTING::CURVE_SMOOTHING:
108  setCurveSmoothing(mPrefs->getInt(SETTING::CURVE_SMOOTHING));
109  break;
110  case SETTING::TOOL_CURSOR:
111  updateToolCursor();
112  break;
113  case SETTING::ONION_PREV_FRAMES_NUM:
114  case SETTING::ONION_NEXT_FRAMES_NUM:
115  case SETTING::ONION_MIN_OPACITY:
116  case SETTING::ONION_MAX_OPACITY:
118  break;
119  case SETTING::ANTIALIAS:
120  case SETTING::GRID:
121  case SETTING::GRID_SIZE_W:
122  case SETTING::GRID_SIZE_H:
123  case SETTING::OVERLAY_CENTER:
124  case SETTING::OVERLAY_THIRDS:
125  case SETTING::OVERLAY_GOLDEN:
126  case SETTING::OVERLAY_SAFE:
127  case SETTING::OVERLAY_PERSPECTIVE1:
128  case SETTING::OVERLAY_PERSPECTIVE2:
129  case SETTING::OVERLAY_PERSPECTIVE3:
130  case SETTING::ACTION_SAFE_ON:
131  case SETTING::ACTION_SAFE:
132  case SETTING::TITLE_SAFE_ON:
133  case SETTING::TITLE_SAFE:
134  case SETTING::OVERLAY_SAFE_HELPER_TEXT_ON:
135  case SETTING::PREV_ONION:
136  case SETTING::NEXT_ONION:
137  case SETTING::ONION_BLUE:
138  case SETTING::ONION_RED:
139  case SETTING::INVISIBLE_LINES:
140  case SETTING::OUTLINES:
141  case SETTING::ONION_TYPE:
143  break;
144  case SETTING::QUICK_SIZING:
145  mQuickSizing = mPrefs->isOn(SETTING::QUICK_SIZING);
146  break;
147  case SETTING::MULTILAYER_ONION:
148  mMultiLayerOnionSkin = mPrefs->isOn(SETTING::MULTILAYER_ONION);
150  break;
151  case SETTING::LAYER_VISIBILITY_THRESHOLD:
152  case SETTING::LAYER_VISIBILITY:
153  setLayerVisibility(static_cast<LayerVisibility>(mPrefs->getInt(SETTING::LAYER_VISIBILITY)));
154  break;
155  default:
156  break;
157  }
158 
159 }
160 
161 void ScribbleArea::updateToolCursor()
162 {
163  setCursor(currentTool()->cursor());
164  updateCanvasCursor();
165 }
166 
167 void ScribbleArea::setCurveSmoothing(int newSmoothingLevel)
168 {
169  mCurveSmoothingLevel = newSmoothingLevel / 20.0;
171 }
172 
173 void ScribbleArea::setEffect(SETTING e, bool isOn)
174 {
175  mPrefs->set(e, isOn);
177 }
178 
179 /************************************************************************************/
180 // update methods
181 
183 {
184  updateFrame(mEditor->currentFrame());
185 }
186 
188 {
189  Q_ASSERT(frame >= 0);
190  update();
191 }
192 
194 {
195  Layer* currentLayer = mEditor->layers()->currentLayer();
196  for (int pos : currentLayer->dirtyFrames()) {
197 
200  }
201  currentLayer->clearDirtyFrames();
202 }
203 
205 {
206  if (frameNumber < 0) { return; }
207 
208  bool isOnionAbsolute = mPrefs->getString(SETTING::ONION_TYPE) == "absolute";
209  Layer *layer = mEditor->layers()->currentLayer(0);
210 
211  // The current layer can be null if updateFrame is triggered when creating a new project
212  if (!layer) return;
213 
214  if (mPrefs->isOn(SETTING::PREV_ONION))
215  {
216  int onionFrameNumber = frameNumber;
217  if (isOnionAbsolute)
218  {
219  onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber + 1, true);
220  }
221 
222  for(int i = 1; i <= mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM); i++)
223  {
224  onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isOnionAbsolute);
225  if (onionFrameNumber < 0) break;
226 
227  invalidateCacheForFrame(onionFrameNumber);
228  }
229  }
230 
231  if (mPrefs->isOn(SETTING::NEXT_ONION))
232  {
233  int onionFrameNumber = frameNumber;
234 
235  for(int i = 1; i <= mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM); i++)
236  {
237  onionFrameNumber = layer->getNextFrameNumber(onionFrameNumber, isOnionAbsolute);
238  if (onionFrameNumber < 0) break;
239 
240  invalidateCacheForFrame(onionFrameNumber);
241  }
242  }
243 }
244 
246 {
248  mPixmapCacheKeys.clear();
250  mEditor->layers()->currentLayer()->clearDirtyFrames();
251 
252  update();
253 }
254 
256 {
257  auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned int>(frameNumber));
258  if (cacheKeyIter != mPixmapCacheKeys.end())
259  {
260  QPixmapCache::remove(cacheKeyIter.value());
261  unsigned int key = cacheKeyIter.key();
262  mPixmapCacheKeys.remove(key);
263  }
264 }
265 
267 {
268  mCanvasPainter.resetLayerCache();
269  update();
270 }
271 
273 {
274  int currentFrame = mEditor->currentFrame();
275  if (mPrefs->isOn(SETTING::PREV_ONION) ||
276  mPrefs->isOn(SETTING::NEXT_ONION)) {
278  }
279 
280  updateFrame(currentFrame);
281 }
282 
283 void ScribbleArea::onScrubbed(int frameNumber)
284 {
286  updateFrame(frameNumber);
287 }
288 
290 {
292  if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) {
294  }
295  update();
296 }
297 
299 {
300  onFrameModified(mEditor->currentFrame());
301 }
302 
303 void ScribbleArea::onFrameModified(int frameNumber)
304 {
305  if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) {
306  invalidateOnionSkinsCacheAround(frameNumber);
308  }
309  invalidateCacheForFrame(frameNumber);
310  updateFrame(frameNumber);
311 }
312 
314 {
316 }
317 
319 {
321 }
322 
324 {
325  update();
326 }
327 
329 {
331 }
332 
334 {
336 }
337 
338 void ScribbleArea::setModified(const Layer* layer, int frameNumber)
339 {
340  if (layer == nullptr) { return; }
341 
342  layer->setModified(frameNumber, true);
343 
344  onFrameModified(frameNumber);
345 }
346 
347 void ScribbleArea::setModified(int layerNumber, int frameNumber)
348 {
349  Layer* layer = mEditor->object()->getLayer(layerNumber);
350  if (layer == nullptr) { return; }
351 
352  setModified(layer, frameNumber);
353  emit modified(layerNumber, frameNumber);
354 }
355 
356 bool ScribbleArea::event(QEvent *event)
357 {
358  if (event->type() == QEvent::WindowDeactivate)
359  {
360  editor()->tools()->clearTemporaryTool();
361  }
362  return QWidget::event(event);
363 }
364 
365 /************************************************************************/
366 /* key event handlers */
367 /************************************************************************/
368 
369 void ScribbleArea::keyPressEvent(QKeyEvent *event)
370 {
371  // Don't handle this event on auto repeat
372  if (event->isAutoRepeat()) { return; }
373 
374  mKeyboardInUse = true;
375 
376  if (isPointerInUse()) { return; } // prevents shortcuts calls while drawing
377 
378  if (currentTool()->keyPressEvent(event))
379  {
380  return; // has been handled by tool
381  }
382 
383  // --- fixed control key shortcuts ---
384  if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier) &&
385  editor()->tools()->setTemporaryTool(ERASER, {}, event->modifiers()))
386  {
387  return;
388  }
389 
390  // ---- fixed normal keys ----
391 
392  auto selectMan = mEditor->select();
393  bool isSomethingSelected = selectMan->somethingSelected();
394  if (isSomethingSelected)
395  {
396  keyEventForSelection(event);
397  }
398  else
399  {
400  keyEvent(event);
401  }
402 }
403 
404 void ScribbleArea::keyEventForSelection(QKeyEvent* event)
405 {
406  auto selectMan = mEditor->select();
407  switch (event->key())
408  {
409  case Qt::Key_Right:
410  selectMan->translate(QPointF(1, 0));
411  paintTransformedSelection();
412  return;
413  case Qt::Key_Left:
414  selectMan->translate(QPointF(-1, 0));
415  paintTransformedSelection();
416  return;
417  case Qt::Key_Up:
418  selectMan->translate(QPointF(0, -1));
419  paintTransformedSelection();
420  return;
421  case Qt::Key_Down:
422  selectMan->translate(QPointF(0, 1));
423  paintTransformedSelection();
424  return;
425  case Qt::Key_Return:
426  applyTransformedSelection();
427  mEditor->deselectAll();
428  return;
429  case Qt::Key_Escape:
430  cancelTransformedSelection();
431  mEditor->deselectAll();
432  return;
433  case Qt::Key_Backspace:
434  deleteSelection();
435  mEditor->deselectAll();
436  return;
437  case Qt::Key_Space:
438  if (editor()->tools()->setTemporaryTool(HAND, Qt::Key_Space, Qt::NoModifier))
439  {
440  return;
441  }
442  break;
443  default:
444  break;
445  }
446  event->ignore();
447 }
448 
449 void ScribbleArea::keyEvent(QKeyEvent* event)
450 {
451  switch (event->key())
452  {
453  case Qt::Key_Right:
454  mEditor->scrubForward();
455  break;
456  case Qt::Key_Left:
457  mEditor->scrubBackward();
458  break;
459  case Qt::Key_Up:
460  mEditor->layers()->gotoNextLayer();
461  break;
462  case Qt::Key_Down:
463  mEditor->layers()->gotoPreviouslayer();
464  break;
465  case Qt::Key_Space:
466  if(editor()->tools()->setTemporaryTool(HAND, Qt::Key_Space, Qt::NoModifier))
467  {
468  return;
469  }
470  break;
471  default:
472  break;
473  }
474  event->ignore();
475 }
476 
477 void ScribbleArea::keyReleaseEvent(QKeyEvent *event)
478 {
479  // Don't handle this event on auto repeat
480  //
481  if (event->isAutoRepeat()) {
482  return;
483  }
484 
485  mKeyboardInUse = false;
486 
487  if (event->key() == 0)
488  {
489  editor()->tools()->tryClearTemporaryTool(Qt::Key_unknown);
490  }
491  else
492  {
493  editor()->tools()->tryClearTemporaryTool(static_cast<Qt::Key>(event->key()));
494  }
495 
496  if (isPointerInUse()) { return; }
497 
498  if (currentTool()->keyReleaseEvent(event))
499  {
500  // has been handled by tool
501  return;
502  }
503 }
504 
505 /************************************************************************************/
506 // mouse and tablet event handlers
507 void ScribbleArea::wheelEvent(QWheelEvent* event)
508 {
509  // Don't change view if tool is in use
510  if (isPointerInUse()) return;
511 
512  Layer* layer = mEditor->layers()->currentLayer();
513  if (layer->type() == Layer::CAMERA && !layer->visible())
514  {
515  showLayerNotVisibleWarning(); // FIXME: crash when using tablets
516  return;
517  }
518 
519  static const bool isX11 = QGuiApplication::platformName() == "xcb";
520  const QPoint pixels = event->pixelDelta();
521  const QPoint angle = event->angleDelta();
522  const QPointF offset = mEditor->view()->mapScreenToCanvas(event->posF());
523 
524  const qreal currentScale = mEditor->view()->scaling();
525  // From the pixelDelta documentation: On X11 this value is driver-specific and unreliable, use angleDelta() instead
526  if (!isX11 && !pixels.isNull())
527  {
528  // XXX: This pixel-based zooming algorithm currently has some shortcomings compared to the angle-based one:
529  // Zooming in is faster than zooming out and scrolling twice with delta x yields different zoom than
530  // scrolling once with delta 2x. Someone with the ability to test this code might want to "upgrade" it.
531  const int delta = pixels.y();
532  const qreal newScale = currentScale * (1 + (delta * 0.01));
533  mEditor->view()->scaleWithOffset(newScale, offset);
534  }
535  else if (!angle.isNull())
536  {
537  const int delta = angle.y();
538  // 12 rotation steps at "standard" wheel resolution (120/step) result in 100x zoom
539  const qreal newScale = currentScale * std::pow(100, delta / (12.0 * 120));
540  mEditor->view()->scaleWithOffset(newScale, offset);
541  }
542  updateCanvasCursor();
543  event->accept();
544 }
545 
546 void ScribbleArea::tabletEvent(QTabletEvent *e)
547 {
548  PointerEvent event(e);
549 
550  if (event.pointerType() == QTabletEvent::Eraser)
551  {
552  editor()->tools()->tabletSwitchToEraser();
553  }
554  else
555  {
556  editor()->tools()->tabletRestorePrevTool();
557  }
558 
559  if (event.eventType() == QTabletEvent::TabletPress)
560  {
561  event.accept();
562  mStrokeManager->pointerPressEvent(&event);
563  mStrokeManager->setTabletInUse(true);
564  if (mIsFirstClick)
565  {
566  mIsFirstClick = false;
567  mDoubleClickTimer->start();
568  pointerPressEvent(&event);
569  }
570  else
571  {
572  qreal distance = QLineF(currentTool()->getCurrentPressPoint(), currentTool()->getLastPressPoint()).length();
573 
574  if (mDoubleClickMillis <= DOUBLE_CLICK_THRESHOLD && distance < 5.0) {
575  currentTool()->pointerDoubleClickEvent(&event);
576  }
577  else
578  {
579  // in case we handled the event as double click but really should have handled it as single click.
580  pointerPressEvent(&event);
581  }
582  }
583  mTabletInUse = event.isAccepted();
584  }
585  else if (event.eventType() == QTabletEvent::TabletMove)
586  {
587  if (!(event.buttons() & (Qt::LeftButton | Qt::RightButton)) || mTabletInUse)
588  {
589  mStrokeManager->pointerMoveEvent(&event);
590  pointerMoveEvent(&event);
591  }
592  }
593  else if (event.eventType() == QTabletEvent::TabletRelease)
594  {
595  if (mTabletInUse)
596  {
597  mStrokeManager->pointerReleaseEvent(&event);
598  pointerReleaseEvent(&event);
599  mStrokeManager->setTabletInUse(false);
600  mTabletInUse = false;
601  }
602  }
603 
604 #if defined Q_OS_LINUX
605  // Generate mouse events on linux to work around bug where the
606  // widget will not receive mouseEnter/mouseLeave
607  // events and the cursor will not update correctly.
608  // See https://codereview.qt-project.org/c/qt/qtbase/+/255384
609  // Scribblearea should not do anything with the mouse event when mTabletInUse is true.
610  event.ignore();
611 #else
612  // Always accept so that mouse events are not generated (theoretically)
613  // Unfortunately Windows sometimes generates the events anyway
614  // As long as mTabletInUse is true, mouse events *should* be ignored even when
615  // the are generated
616  event.accept();
617 #endif
618 }
619 
620 void ScribbleArea::pointerPressEvent(PointerEvent* event)
621 {
622  bool isCameraLayer = mEditor->layers()->currentLayer()->type() == Layer::CAMERA;
623  if ((currentTool()->type() != HAND || isCameraLayer) && (event->button() != Qt::RightButton) && (event->button() != Qt::MidButton || isCameraLayer))
624  {
625  Layer* layer = mEditor->layers()->currentLayer();
626  if (!layer->visible())
627  {
628  event->ignore();
629  // This needs to be async so that mTabletInUse is set to false before
630  // further events are created (modal dialogs do not currently block tablet events)
631  QTimer::singleShot(0, this, &ScribbleArea::showLayerNotVisibleWarning);
632  return;
633  }
634  }
635 
636  if (event->buttons() & (Qt::MidButton | Qt::RightButton) &&
637  editor()->tools()->setTemporaryTool(HAND, event->buttons()))
638  {
639  currentTool()->pointerPressEvent(event);
640  }
641 
642  const bool isPressed = event->buttons() & Qt::LeftButton;
643  if (isPressed && mQuickSizing)
644  {
645  //qDebug() << "Start Adjusting" << event->buttons();
646  if (currentTool()->startAdjusting(event->modifiers(), 1))
647  {
648  return;
649  }
650  }
651 
652  if (event->button() == Qt::LeftButton)
653  {
654  currentTool()->pointerPressEvent(event);
655  }
656 }
657 
658 void ScribbleArea::pointerMoveEvent(PointerEvent* event)
659 {
660  updateCanvasCursor();
661 
662  if (event->buttons() & (Qt::LeftButton | Qt::RightButton))
663  {
664 
665  // --- use SHIFT + drag to resize WIDTH / use CTRL + drag to resize FEATHER ---
666  if (currentTool()->isAdjusting())
667  {
668  currentTool()->adjustCursor(event->modifiers());
669  return;
670  }
671  }
672 
673  currentTool()->pointerMoveEvent(event);
674 }
675 
676 void ScribbleArea::pointerReleaseEvent(PointerEvent* event)
677 {
678  if (currentTool()->isAdjusting())
679  {
680  currentTool()->stopAdjusting();
681  mEditor->tools()->setWidth(static_cast<float>(currentTool()->properties.width));
682  return; // [SHIFT]+drag OR [CTRL]+drag
683  }
684 
685  if (event->buttons() & (Qt::RightButton | Qt::MiddleButton))
686  {
687  mMouseRightButtonInUse = false;
688  return;
689  }
690 
691  //qDebug() << "release event";
692  currentTool()->pointerReleaseEvent(event);
693 
694  editor()->tools()->tryClearTemporaryTool(event->button());
695 }
696 
697 void ScribbleArea::handleDoubleClick()
698 {
699  mDoubleClickMillis += 100;
700 
701  if (mDoubleClickMillis >= DOUBLE_CLICK_THRESHOLD)
702  {
703  mDoubleClickMillis = 0;
704  mIsFirstClick = true;
705  mDoubleClickTimer->stop();
706  }
707 }
708 
709 bool ScribbleArea::isLayerPaintable() const
710 {
711  Layer* layer = mEditor->layers()->currentLayer();
712  if (layer == nullptr) { return false; }
713 
714  return layer->type() == Layer::BITMAP || layer->type() == Layer::VECTOR;
715 }
716 
717 void ScribbleArea::mousePressEvent(QMouseEvent* e)
718 {
719  if (mTabletInUse)
720  {
721  e->ignore();
722  return;
723  }
724 
725  PointerEvent event(e);
726 
727  mStrokeManager->pointerPressEvent(&event);
728 
729  pointerPressEvent(&event);
730  mMouseInUse = event.isAccepted();
731 }
732 
733 void ScribbleArea::mouseMoveEvent(QMouseEvent* e)
734 {
735  PointerEvent event(e);
736 
737  mStrokeManager->pointerMoveEvent(&event);
738 
739  pointerMoveEvent(&event);
740 }
741 
742 void ScribbleArea::mouseReleaseEvent(QMouseEvent* e)
743 {
744  PointerEvent event(e);
745 
746  mStrokeManager->pointerReleaseEvent(&event);
747 
748  pointerReleaseEvent(&event);
749  mMouseInUse = (e->buttons() & Qt::RightButton) || (e->buttons() & Qt::LeftButton);
750 }
751 
752 void ScribbleArea::mouseDoubleClickEvent(QMouseEvent* e)
753 {
754  if (mStrokeManager->isTabletInUse()) { e->ignore(); return; }
755  PointerEvent event(e);
756  mStrokeManager->pointerPressEvent(&event);
757 
758  currentTool()->pointerDoubleClickEvent(&event);
759 }
760 
761 void ScribbleArea::resizeEvent(QResizeEvent* event)
762 {
763  QWidget::resizeEvent(event);
764  mDevicePixelRatio = devicePixelRatioF();
765  mCanvas = QPixmap(QSizeF(size() * mDevicePixelRatio).toSize());
766  mCanvas.fill(Qt::transparent);
767 
768  mEditor->view()->setCanvasSize(size());
769 
770  invalidateCacheForFrame(mEditor->currentFrame());
772 }
773 
774 void ScribbleArea::showLayerNotVisibleWarning()
775 {
776  QMessageBox::warning(this, tr("Warning"),
777  tr("You are trying to modify a hidden layer! Please select another layer (or make the current layer visible)."),
780 }
781 
782 void ScribbleArea::updateOriginalPolygonF()
783 {
784  if (mEditor->select()->somethingSelected() && mOriginalPolygonF.isEmpty())
785  mOriginalPolygonF = mEditor->select()->currentSelectionPolygonF();
786  else
787  mOriginalPolygonF = QPolygonF();
788 }
789 
790 void ScribbleArea::paintBitmapBuffer()
791 {
792  LayerBitmap* layer = static_cast<LayerBitmap*>(mEditor->layers()->currentLayer());
793  Q_ASSERT(layer);
794  Q_ASSERT(layer->type() == Layer::BITMAP);
795 
796  int frameNumber = mEditor->currentFrame();
797 
798  // If there is no keyframe at or before the current position,
799  // just return (since we have nothing to paint on).
800  if (layer->getLastKeyFrameAtPosition(frameNumber) == nullptr)
801  {
803  return;
804  }
805 
806  // Clear the temporary pixel path
807  BitmapImage* targetImage = currentBitmapImage(layer);
808  if (targetImage != nullptr)
809  {
811  switch (currentTool()->type())
812  {
813  case ERASER:
815  break;
816  case BRUSH:
817  case PEN:
818  case PENCIL:
819  if (getTool(currentTool()->type())->properties.preserveAlpha)
820  {
822  }
823  break;
824  default: //nothing
825  break;
826  }
827  targetImage->paste(mBufferImg, cm);
828  }
829 
830  QRect rect = mEditor->view()->mapCanvasToScreen(mBufferImg->bounds()).toRect();
831 
832  drawCanvas(frameNumber, rect.adjusted(-1, -1, 1, 1));
833  update(rect);
834 
835  // Update the cache for the last key-frame.
836  updateFrame(frameNumber);
837  layer->setModified(frameNumber, true);
838 
839  mBufferImg->clear();
840 }
841 
842 void ScribbleArea::paintBitmapBufferRect(const QRect& rect)
843 {
844  if (mEditor->playback()->isPlaying())
845  {
846  Layer* layer = mEditor->layers()->currentLayer();
847  Q_ASSERT(layer);
848 
849  BitmapImage* targetImage = currentBitmapImage(layer);
850 
851  if (targetImage != nullptr)
852  {
854  switch (currentTool()->type())
855  {
856  case ERASER:
858  break;
859  case BRUSH:
860  case PEN:
861  case PENCIL:
862  if (getTool(currentTool()->type())->properties.preserveAlpha)
863  {
865  }
866  break;
867  default: //nothing
868  break;
869  }
870  targetImage->paste(mBufferImg, cm);
871  }
872 
873  // Clear the buffer
874  mBufferImg->clear();
875 
876  int frameNumber = mEditor->currentFrame();
877  layer->setModified(frameNumber, true);
878 
879  updateFrame(frameNumber);
880 
881  drawCanvas(frameNumber, rect.adjusted(-1, -1, 1, 1));
882  update(rect);
883  }
884 }
885 
886 void ScribbleArea::clearBitmapBuffer()
887 {
888  mBufferImg->clear();
889 }
890 
891 void ScribbleArea::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm)
892 {
893  mBufferImg->drawLine(P1, P2, pen, cm, mPrefs->isOn(SETTING::ANTIALIAS));
894 }
895 
896 void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm)
897 {
898  mBufferImg->drawPath(path, pen, brush, cm, mPrefs->isOn(SETTING::ANTIALIAS));
899 }
900 
901 void ScribbleArea::refreshBitmap(const QRectF& rect, int rad)
902 {
903  QRectF updatedRect = mEditor->view()->mapCanvasToScreen(rect.normalized().adjusted(-rad, -rad, +rad, +rad));
904  update(updatedRect.toRect());
905 }
906 
907 void ScribbleArea::refreshVector(const QRectF& rect, int rad)
908 {
909  rad += 1;
910  //QRectF updatedRect = mEditor->view()->mapCanvasToScreen( rect.normalized().adjusted( -rad, -rad, +rad, +rad ) );
911  update(rect.normalized().adjusted(-rad, -rad, +rad, +rad).toRect());
912 
913  //qDebug() << "Logical: " << rect;
914  //qDebug() << "Physical: " << mEditor->view()->mapCanvasToScreen( rect.normalized() );
915  //update();
916 }
917 
918 void ScribbleArea::paintCanvasCursor(QPainter& painter)
919 {
920  QTransform view = mEditor->view()->getView();
921  QPointF mousePos = currentTool()->isAdjusting() ? currentTool()->getCurrentPressPoint() : currentTool()->getCurrentPoint();
922  int centerCal = mCursorImg.width() / 2;
923 
924  mTransformedCursorPos = view.map(mousePos);
925 
926  // reset matrix
927  view.reset();
928 
929  painter.setTransform(view);
930  mCursorCenterPos.setX(centerCal);
931  mCursorCenterPos.setY(centerCal);
932 
933  painter.drawPixmap(QPoint(static_cast<int>(mTransformedCursorPos.x() - mCursorCenterPos.x()),
934  static_cast<int>(mTransformedCursorPos.y() - mCursorCenterPos.y())),
935  mCursorImg);
936 
937  // update center of transformed img for rect only
938  mTransCursImg = mCursorImg.transformed(view);
939 
940  mCursorCenterPos.setX(centerCal);
941  mCursorCenterPos.setY(centerCal);
942 }
943 
944 void ScribbleArea::updateCanvasCursor()
945 {
946  float scalingFac = mEditor->view()->scaling();
947  qreal brushWidth = currentTool()->properties.width;
948  qreal brushFeather = currentTool()->properties.feather;
949  if (currentTool()->isAdjusting())
950  {
951  mCursorImg = currentTool()->quickSizeCursor(scalingFac);
952  }
953  else if (mEditor->preference()->isOn(SETTING::DOTTED_CURSOR))
954  {
955  bool useFeather = currentTool()->properties.useFeather;
956  mCursorImg = currentTool()->canvasCursor(static_cast<float>(brushWidth), static_cast<float>(brushFeather), useFeather, scalingFac, width());
957  }
958  else
959  {
960  mCursorImg = QPixmap(); // if above does not comply, deallocate image
961  }
962 
963  // update cursor rect
964  QPoint translatedPos = QPoint(static_cast<int>(mTransformedCursorPos.x() - mCursorCenterPos.x()),
965  static_cast<int>(mTransformedCursorPos.y() - mCursorCenterPos.y()));
966 
967  update(mTransCursImg.rect().adjusted(-1, -1, 1, 1)
968  .translated(translatedPos));
969 
970 }
971 
973 {
974  auto layer = mEditor->layers()->currentLayer();
975 
976  if (!layer || !layer->isPaintable())
977  {
978  return;
979  }
980 
981  if (currentTool()->type() == ERASER) {
982  return;
983  }
984 
985  int frameNumber = mEditor->currentFrame();
986  if (layer->getKeyFrameAt(frameNumber)) { return; }
987 
988  // Drawing on an empty frame; take action based on preference.
989  int action = mPrefs->getInt(SETTING::DRAW_ON_EMPTY_FRAME_ACTION);
990  auto previousKeyFrame = layer->getLastKeyFrameAtPosition(frameNumber);
991  switch (action)
992  {
993  case KEEP_DRAWING_ON_PREVIOUS_KEY:
994  {
995  if (previousKeyFrame == nullptr) {
996  mEditor->addNewKey();
997  } else {
998  onFrameModified(previousKeyFrame->pos());
999  }
1000  break;
1001  }
1002  case DUPLICATE_PREVIOUS_KEY:
1003  {
1004  if (previousKeyFrame != nullptr) {
1005  KeyFrame* dupKey = previousKeyFrame->clone();
1006  layer->addKeyFrame(frameNumber, dupKey);
1007  mEditor->scrubTo(frameNumber);
1008  break;
1009  }
1010  }
1011  // if the previous keyframe doesn't exist,
1012  // an empty keyframe needs to be created, so
1013  // fallthrough
1014  case CREATE_NEW_KEY:
1015  mEditor->addNewKey();
1016 
1017  // Refresh canvas
1018  drawCanvas(frameNumber, mCanvas.rect());
1019  break;
1020  default:
1021  break;
1022  }
1023 }
1024 
1025 void ScribbleArea::paintEvent(QPaintEvent* event)
1026 {
1027  if (!currentTool()->isActive())
1028  {
1029  // --- we retrieve the canvas from the cache; we create it if it doesn't exist
1030  const int currentFrame = mEditor->currentFrame();
1031  const int frameNumber = mEditor->layers()->lastFrameAtFrame(currentFrame);
1032 
1033  if (frameNumber < 0)
1034  {
1035  drawCanvas(currentFrame, event->rect());
1036  }
1037  else
1038  {
1039  auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned>(frameNumber));
1040 
1041  if (cacheKeyIter == mPixmapCacheKeys.end() || !QPixmapCache::find(cacheKeyIter.value(), &mCanvas))
1042  {
1043  drawCanvas(mEditor->currentFrame(), event->rect());
1044  mPixmapCacheKeys[static_cast<unsigned>(currentFrame)] = QPixmapCache::insert(mCanvas);
1045  //qDebug() << "Repaint canvas!";
1046  }
1047  else
1048  {
1049  // Simply use the cached canvas from PixmapCache
1050  }
1051  }
1052  }
1053  else
1054  {
1055  prepCanvas(mEditor->currentFrame(), event->rect());
1056  prepOverlays();
1057  mCanvasPainter.paintCached();
1058  }
1059 
1060  if (currentTool()->type() == MOVE)
1061  {
1062  Layer* layer = mEditor->layers()->currentLayer();
1063  Q_CHECK_PTR(layer);
1064  if (layer->type() == Layer::VECTOR)
1065  {
1066  VectorImage* vectorImage = currentVectorImage(layer);
1067  if (vectorImage != nullptr)
1068  {
1069  vectorImage->setModified(true);
1070  }
1071  }
1072  }
1073 
1074  QPainter painter(this);
1075 
1076  // paints the canvas
1077  painter.setWorldMatrixEnabled(false);
1078  painter.drawPixmap(QPoint(0, 0), mCanvas);
1079 
1080  Layer* layer = mEditor->layers()->currentLayer();
1081 
1082  if (!editor()->playback()->isPlaying()) // we don't need to display the following when the animation is playing
1083  {
1084  if (layer->type() == Layer::VECTOR)
1085  {
1086  VectorImage* vectorImage = currentVectorImage(layer);
1087  if (vectorImage != nullptr)
1088  {
1089  switch (currentTool()->type())
1090  {
1091  case SMUDGE:
1092  case HAND:
1093  {
1094  auto selectMan = mEditor->select();
1095  painter.save();
1096  painter.setWorldMatrixEnabled(false);
1097  painter.setRenderHint(QPainter::Antialiasing, false);
1098  // ----- paints the edited elements
1100  painter.setPen(pen2);
1101  QColor color;
1102  // ------------ vertices of the edited curves
1103  color = QColor(200, 200, 200);
1104  painter.setBrush(color);
1105  VectorSelection vectorSelection = selectMan->vectorSelection;
1106  for (int k = 0; k < vectorSelection.curve.size(); k++)
1107  {
1108  int curveNumber = vectorSelection.curve.at(k);
1109 
1110  for (int vertexNumber = -1; vertexNumber < vectorImage->getCurveSize(curveNumber); vertexNumber++)
1111  {
1112  QPointF vertexPoint = vectorImage->getVertex(curveNumber, vertexNumber);
1113  QRectF rectangle(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
1114  if (rect().contains(mEditor->view()->mapCanvasToScreen(vertexPoint).toPoint()))
1115  {
1116  painter.drawRect(rectangle);
1117  }
1118  }
1119  }
1120  // ------------ selected vertices of the edited curves
1121  color = QColor(100, 100, 255);
1122  painter.setBrush(color);
1123  for (int k = 0; k < vectorSelection.vertex.size(); k++)
1124  {
1125  VertexRef vertexRef = vectorSelection.vertex.at(k);
1126  QPointF vertexPoint = vectorImage->getVertex(vertexRef);
1127  QRectF rectangle0 = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
1128  painter.drawRect(rectangle0);
1129  }
1130  // ----- paints the closest vertices
1131  color = QColor(255, 0, 0);
1132  painter.setBrush(color);
1133  QList<VertexRef> closestVertices = selectMan->closestVertices();
1134  if (vectorSelection.curve.size() > 0)
1135  {
1136  for (int k = 0; k < closestVertices.size(); k++)
1137  {
1138  VertexRef vertexRef = closestVertices.at(k);
1139  QPointF vertexPoint = vectorImage->getVertex(vertexRef);
1140 
1141  QRectF rectangle = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
1142  painter.drawRect(rectangle);
1143  }
1144  }
1145  painter.restore();
1146  break;
1147  }
1148  default:
1149  {
1150  break;
1151  }
1152  } // end switch
1153  }
1154  }
1155 
1156  paintCanvasCursor(painter);
1157 
1158  mCanvasPainter.renderGrid(painter);
1159  mOverlayPainter.renderOverlays(painter, editor()->overlays()->getMoveMode());
1160 
1161  // paints the selection outline
1162  if (mEditor->select()->somethingSelected())
1163  {
1164  paintSelectionVisuals(painter);
1165  }
1166  }
1167 
1168  // outlines the frame of the viewport
1169 #ifdef _DEBUG
1170  painter.setWorldMatrixEnabled(false);
1171  painter.setPen(QPen(Qt::gray, 2));
1172  painter.setBrush(Qt::NoBrush);
1173  painter.drawRect(QRect(0, 0, width(), height()));
1174 #endif
1175 
1176  event->accept();
1177 }
1178 
1179 void ScribbleArea::paintSelectionVisuals(QPainter &painter)
1180 {
1181  Object* object = mEditor->object();
1182 
1183  auto selectMan = mEditor->select();
1184  selectMan->updatePolygons();
1185 
1186  if (selectMan->currentSelectionPolygonF().isEmpty()) { return; }
1187  if (selectMan->currentSelectionPolygonF().count() < 4) { return; }
1188 
1189  QPolygonF lastSelectionPolygon = editor()->view()->mapPolygonToScreen(selectMan->lastSelectionPolygonF());
1190  QPolygonF currentSelectionPolygon = selectMan->currentSelectionPolygonF();
1191  if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP)
1192  {
1193  currentSelectionPolygon = currentSelectionPolygon.toPolygon();
1194  }
1195  currentSelectionPolygon = editor()->view()->mapPolygonToScreen(currentSelectionPolygon);
1196 
1197  if (mOriginalPolygonF.isEmpty())
1198  {
1199  mOriginalPolygonF = selectMan->currentSelectionPolygonF();
1200  }
1201 
1202  TransformParameters params = { lastSelectionPolygon, currentSelectionPolygon };
1203  mSelectionPainter.paint(painter, object, mEditor->currentLayerIndex(),
1204  currentTool(), params, mOriginalPolygonF, selectMan->currentSelectionPolygonF());
1205  emit selectionUpdated();
1206 }
1207 
1208 BitmapImage* ScribbleArea::currentBitmapImage(Layer* layer) const
1209 {
1210  Q_ASSERT(layer->type() == Layer::BITMAP);
1211  auto bitmapLayer = static_cast<LayerBitmap*>(layer);
1212  return bitmapLayer->getLastBitmapImageAtFrame(mEditor->currentFrame());
1213 }
1214 
1215 VectorImage* ScribbleArea::currentVectorImage(Layer* layer) const
1216 {
1217  Q_ASSERT(layer->type() == Layer::VECTOR);
1218  auto vectorLayer = static_cast<LayerVector*>(layer);
1219  return vectorLayer->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
1220 }
1221 
1222 void ScribbleArea::prepCanvas(int frame, QRect rect)
1223 {
1224  Object* object = mEditor->object();
1225 
1227  o.bPrevOnionSkin = mPrefs->isOn(SETTING::PREV_ONION);
1228  o.bNextOnionSkin = mPrefs->isOn(SETTING::NEXT_ONION);
1229  o.bColorizePrevOnion = mPrefs->isOn(SETTING::ONION_RED);
1230  o.bColorizeNextOnion = mPrefs->isOn(SETTING::ONION_BLUE);
1231  o.nPrevOnionSkinCount = mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM);
1232  o.nNextOnionSkinCount = mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM);
1233  o.fOnionSkinMaxOpacity = mPrefs->getInt(SETTING::ONION_MAX_OPACITY);
1234  o.fOnionSkinMinOpacity = mPrefs->getInt(SETTING::ONION_MIN_OPACITY);
1235  o.bAntiAlias = mPrefs->isOn(SETTING::ANTIALIAS);
1236  o.bGrid = mPrefs->isOn(SETTING::GRID);
1237  o.nGridSizeW = mPrefs->getInt(SETTING::GRID_SIZE_W);
1238  o.nGridSizeH = mPrefs->getInt(SETTING::GRID_SIZE_H);
1239  o.bAxis = false;
1240  o.bThinLines = mPrefs->isOn(SETTING::INVISIBLE_LINES);
1241  o.bOutlines = mPrefs->isOn(SETTING::OUTLINES);
1242  o.eLayerVisibility = mLayerVisibility;
1243  o.fLayerVisibilityThreshold = mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD);
1244  o.bIsOnionAbsolute = (mPrefs->getString(SETTING::ONION_TYPE) == "absolute");
1245  o.scaling = mEditor->view()->scaling();
1246  o.onionWhilePlayback = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK);
1247  o.isPlaying = mEditor->playback()->isPlaying() ? true : false;
1248  o.cmBufferBlendMode = mEditor->tools()->currentTool()->type() == ToolType::ERASER ? QPainter::CompositionMode_DestinationOut : QPainter::CompositionMode_SourceOver;
1249  mCanvasPainter.setOptions(o);
1250 
1251  mCanvasPainter.setCanvas(&mCanvas);
1252 
1253  ViewManager* vm = mEditor->view();
1254  mCanvasPainter.setViewTransform(vm->getView(), vm->getViewInverse());
1255 
1256  mCanvasPainter.setPaintSettings(object, mEditor->layers()->currentLayerIndex(), frame, rect, mBufferImg);
1257 }
1258 
1259 void ScribbleArea::drawCanvas(int frame, QRect rect)
1260 {
1261  mCanvas.fill(Qt::transparent);
1262  mCanvas.setDevicePixelRatio(mDevicePixelRatio);
1263  prepCanvas(frame, rect);
1264  mCanvasPainter.paint();
1265  prepOverlays();
1266 }
1267 
1268 void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor color, qreal opacity, qreal offset)
1269 {
1270  if (offset < 0) { offset = 0; }
1271  if (offset > 100) { offset = 100; }
1272 
1273  int r = color.red();
1274  int g = color.green();
1275  int b = color.blue();
1276  qreal a = color.alphaF();
1277 
1278  int mainColorAlpha = qRound(a * 255 * opacity);
1279 
1280  // the more feather (offset), the more softness (opacity)
1281  int alphaAdded = qRound((mainColorAlpha * offset) / 100);
1282 
1283  gradient.setColorAt(0.0, QColor(r, g, b, mainColorAlpha - alphaAdded));
1284  gradient.setColorAt(1.0, QColor(r, g, b, 0));
1285  gradient.setColorAt(1.0 - (offset / 100.0), QColor(r, g, b, mainColorAlpha - alphaAdded));
1286 }
1287 
1288 void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA)
1289 {
1290  QRectF rectangle(thePoint.x() - 0.5 * brushWidth, thePoint.y() - 0.5 * brushWidth, brushWidth, brushWidth);
1291 
1292  mBufferImg->drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern),
1294 }
1295 
1296 void ScribbleArea::drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity)
1297 {
1298  drawBrush(thePoint, brushWidth, fixedBrushFeather, fillColor, opacity, true);
1299 }
1300 
1301 void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset, QColor fillColor, qreal opacity, bool usingFeather, bool useAA)
1302 {
1303  QRectF rectangle(thePoint.x() - 0.5 * brushWidth, thePoint.y() - 0.5 * brushWidth, brushWidth, brushWidth);
1304 
1305  if (usingFeather)
1306  {
1307  QRadialGradient radialGrad(thePoint, 0.5 * brushWidth);
1308  setGaussianGradient(radialGrad, fillColor, opacity, mOffset);
1309 
1310  mBufferImg->drawEllipse(rectangle, Qt::NoPen, radialGrad,
1312  }
1313  else
1314  {
1315  mBufferImg->drawEllipse(rectangle, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern),
1317  }
1318 }
1319 
1320 void ScribbleArea::flipSelection(bool flipVertical)
1321 {
1322  mEditor->select()->flipSelection(flipVertical);
1323  paintTransformedSelection();
1324 }
1325 
1326 void ScribbleArea::renderOverlays()
1327 {
1329 }
1330 
1331 void ScribbleArea::prepOverlays()
1332 {
1334 
1335  o.bCenter = mPrefs->isOn(SETTING::OVERLAY_CENTER);
1336  o.bThirds = mPrefs->isOn(SETTING::OVERLAY_THIRDS);
1337  o.bGoldenRatio = mPrefs->isOn(SETTING::OVERLAY_GOLDEN);
1338  o.bSafeArea = mPrefs->isOn(SETTING::OVERLAY_SAFE);
1339  o.bPerspective1 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE1);
1340  o.bPerspective2 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE2);
1341  o.bPerspective3 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE3);
1342  o.nOverlayAngle = mPrefs->getInt(SETTING::OVERLAY_ANGLE);
1343  o.bActionSafe = mPrefs->isOn(SETTING::ACTION_SAFE_ON);
1344  o.nActionSafe = mPrefs->getInt(SETTING::ACTION_SAFE);
1345  o.bShowSafeAreaHelperText = mPrefs->isOn(SETTING::OVERLAY_SAFE_HELPER_TEXT_ON);
1346  o.bTitleSafe = mPrefs->isOn(SETTING::TITLE_SAFE_ON);
1347  o.nTitleSafe = mPrefs->getInt(SETTING::TITLE_SAFE);
1348 
1349  o.mRect = getCameraRect(); // camera rect!
1350  o.mSinglePerspPoint = mEditor->overlays()->getSinglePerspPoint();
1351  o.mLeftPerspPoint = mEditor->overlays()->getLeftPerspPoint();
1352  o.mRightPerspPoint = mEditor->overlays()->getRightPerspPoint();
1353  o.mMiddlePerspPoint = mEditor->overlays()->getMiddlePerspPoint();
1354 
1355  mOverlayPainter.setOptions(o);
1356 
1357  ViewManager* vm = mEditor->view();
1358  mOverlayPainter.setViewTransform(vm->getView());
1359 }
1360 
1361 void ScribbleArea::blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_)
1362 {
1363  QRadialGradient radialGrad(thePoint_, 0.5 * brushWidth_);
1364  setGaussianGradient(radialGrad, QColor(255, 255, 255, 127), opacity_, mOffset_);
1365 
1366  QRectF srcRect(srcPoint_.x() - 0.5 * brushWidth_, srcPoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1367  QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1368 
1369  BitmapImage bmiSrcClip = bmiSource_->copy(srcRect.toAlignedRect());
1370  BitmapImage bmiTmpClip = bmiSrcClip; // TODO: find a shorter way
1371 
1372  bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
1373  bmiSrcClip.bounds().moveTo(trgRect.topLeft().toPoint());
1374  bmiTmpClip.paste(&bmiSrcClip, QPainter::CompositionMode_SourceIn);
1375  mBufferImg->paste(&bmiTmpClip);
1376 }
1377 
1378 void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_)
1379 {
1380  QPointF delta = (thePoint_ - srcPoint_); // increment vector
1381  QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1382 
1383  QRadialGradient radialGrad(thePoint_, 0.5 * brushWidth_);
1384  setGaussianGradient(radialGrad, QColor(255, 255, 255, 255), opacity_, mOffset_);
1385 
1386  // Create gradient brush
1387  BitmapImage bmiTmpClip;
1388  bmiTmpClip.drawRect(trgRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
1389 
1390  // Slide texture/pixels of the source image
1391  qreal factor, factorGrad;
1392 
1393  for (int yb = bmiTmpClip.top(); yb < bmiTmpClip.bottom(); yb++)
1394  {
1395  for (int xb = bmiTmpClip.left(); xb < bmiTmpClip.right(); xb++)
1396  {
1397  QColor color;
1398  color.setRgba(bmiTmpClip.pixel(xb, yb));
1399  factorGrad = color.alphaF(); // any from r g b a is ok
1400 
1401  int xa = xb - factorGrad * delta.x();
1402  int ya = yb - factorGrad * delta.y();
1403 
1404  color.setRgba(bmiSource_->pixel(xa, ya));
1405  factor = color.alphaF();
1406 
1407  if (factor > 0.0)
1408  {
1409  color.setRed(color.red() / factor);
1410  color.setGreen(color.green() / factor);
1411  color.setBlue(color.blue() / factor);
1412  color.setAlpha(255); // Premultiplied color
1413 
1414  color.setRed(color.red()*factorGrad);
1415  color.setGreen(color.green()*factorGrad);
1416  color.setBlue(color.blue()*factorGrad);
1417  color.setAlpha(255 * factorGrad); // Premultiplied color
1418 
1419  bmiTmpClip.setPixel(xb, yb, color.rgba());
1420  }
1421  else
1422  {
1423  bmiTmpClip.setPixel(xb, yb, qRgba(255, 255, 255, 0));
1424  }
1425  }
1426  }
1427  mBufferImg->paste(&bmiTmpClip);
1428 }
1429 
1430 void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA)
1431 {
1432  QRectF updateRect = mEditor->view()->mapCanvasToScreen(path.boundingRect().toRect()).adjusted(-1, -1, 1, 1);
1433 
1434  // Update region outside updateRect
1435  QRectF boundingRect = updateRect.adjusted(-width(), -height(), width(), height());
1436  mBufferImg->clear();
1437  mBufferImg->drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_SourceOver, useAA);
1438  update(boundingRect.toRect());
1439 
1440 }
1441 
1442 /************************************************************************************/
1443 // view handling
1444 
1445 QRect ScribbleArea::getCameraRect()
1446 {
1447  return mCanvasPainter.getCameraRect();
1448 }
1449 
1450 QPointF ScribbleArea::getCentralPoint()
1451 {
1452  return mEditor->view()->mapScreenToCanvas(QPointF(width() / 2, height() / 2));
1453 }
1454 
1455 void ScribbleArea::paintTransformedSelection()
1456 {
1457  Layer* layer = mEditor->layers()->currentLayer();
1458  if (layer == nullptr) { return; }
1459 
1460  auto selectMan = mEditor->select();
1461  if (selectMan->somethingSelected()) // there is something selected
1462  {
1463  if (layer->type() == Layer::BITMAP)
1464  {
1465  mCanvasPainter.setTransformedSelection(selectMan->mySelectionRect().toRect(), selectMan->selectionTransform());
1466  }
1467  else if (layer->type() == Layer::VECTOR)
1468  {
1469  // vector transformation
1470  VectorImage* vectorImage = currentVectorImage(layer);
1471  if (vectorImage == nullptr) { return; }
1472  vectorImage->setSelectionTransformation(selectMan->selectionTransform());
1473 
1474  }
1475  setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1476  }
1478 }
1479 
1480 void ScribbleArea::applySelectionChanges()
1481 {
1482  // we haven't applied our last modifications yet
1483  // therefore apply the transformed selection first.
1484  applyTransformedSelection();
1485 
1486  auto selectMan = mEditor->select();
1487 
1488  // make sure the current transformed selection is valid
1489  if (!selectMan->myTempTransformedSelectionRect().isValid())
1490  {
1491  const QRectF& normalizedRect = selectMan->myTempTransformedSelectionRect().normalized();
1492  selectMan->setTempTransformedSelectionRect(normalizedRect);
1493  }
1494  selectMan->setSelection(selectMan->myTempTransformedSelectionRect(), false);
1495  paintTransformedSelection();
1496 
1497  // Calculate the new transformation based on the new selection
1498  selectMan->calculateSelectionTransformation();
1499 
1500  // apply the transformed selection to make the selection modification absolute.
1501  applyTransformedSelection();
1502 }
1503 
1504 void ScribbleArea::applyTransformedSelection()
1505 {
1506  mCanvasPainter.ignoreTransformedSelection();
1507 
1508  Layer* layer = mEditor->layers()->currentLayer();
1509  if (layer == nullptr) { return; }
1510 
1511  auto selectMan = mEditor->select();
1512  if (selectMan->somethingSelected())
1513  {
1514  if (selectMan->mySelectionRect().isEmpty() || selectMan->selectionTransform().isIdentity()) { return; }
1515 
1516  if (layer->type() == Layer::BITMAP)
1517  {
1519  BitmapImage* bitmapImage = currentBitmapImage(layer);
1520  if (bitmapImage == nullptr) { return; }
1521  BitmapImage transformedImage = bitmapImage->transformed(selectMan->mySelectionRect().toRect(), selectMan->selectionTransform(), true);
1522 
1523  bitmapImage->clear(selectMan->mySelectionRect());
1524  bitmapImage->paste(&transformedImage, QPainter::CompositionMode_SourceOver);
1525  }
1526  else if (layer->type() == Layer::VECTOR)
1527  {
1528  // Unfortunately this doesn't work right currently so vector transforms
1529  // will always be applied on the previous keyframe when on an empty frame
1530  //handleDrawingOnEmptyFrame();
1531  VectorImage* vectorImage = currentVectorImage(layer);
1532  if (vectorImage == nullptr) { return; }
1533  vectorImage->applySelectionTransformation();
1534  selectMan->setSelection(selectMan->myTempTransformedSelectionRect(), false);
1535  }
1536 
1537  setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1538  }
1539 
1540  update();
1541 }
1542 
1543 void ScribbleArea::cancelTransformedSelection()
1544 {
1545  mCanvasPainter.ignoreTransformedSelection();
1546 
1547  auto selectMan = mEditor->select();
1548  if (selectMan->somethingSelected())
1549  {
1550  Layer* layer = mEditor->layers()->currentLayer();
1551  if (layer == nullptr) { return; }
1552 
1553  if (layer->type() == Layer::VECTOR)
1554  {
1555  VectorImage* vectorImage = currentVectorImage(layer);
1556  if (vectorImage != nullptr)
1557  {
1558  vectorImage->setSelectionTransformation(QTransform());
1559  }
1560  }
1561 
1562  mEditor->select()->setSelection(selectMan->mySelectionRect(), false);
1563 
1564  selectMan->resetSelectionProperties();
1565  mOriginalPolygonF = QPolygonF();
1566 
1567  setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1569  }
1570 }
1571 
1572 void ScribbleArea::displaySelectionProperties()
1573 {
1574  Layer* layer = mEditor->layers()->currentLayer();
1575  if (layer == nullptr) { return; }
1576  if (layer->type() == Layer::VECTOR)
1577  {
1578  VectorImage* vectorImage = currentVectorImage(layer);
1579  if (vectorImage == nullptr) { return; }
1580  //vectorImage->applySelectionTransformation();
1581  if (currentTool()->type() == MOVE)
1582  {
1583  int selectedCurve = vectorImage->getFirstSelectedCurve();
1584  if (selectedCurve != -1)
1585  {
1586  mEditor->tools()->setWidth(vectorImage->curve(selectedCurve).getWidth());
1587  mEditor->tools()->setFeather(vectorImage->curve(selectedCurve).getFeather());
1588  mEditor->tools()->setInvisibility(vectorImage->curve(selectedCurve).isInvisible());
1589  mEditor->tools()->setPressure(vectorImage->curve(selectedCurve).getVariableWidth());
1590  mEditor->color()->setColorNumber(vectorImage->curve(selectedCurve).getColorNumber());
1591  }
1592 
1593  int selectedArea = vectorImage->getFirstSelectedArea();
1594  if (selectedArea != -1)
1595  {
1596  mEditor->color()->setColorNumber(vectorImage->mArea[selectedArea].mColorNumber);
1597  }
1598  }
1599  }
1600 }
1601 
1602 void ScribbleArea::toggleThinLines()
1603 {
1604  bool previousValue = mPrefs->isOn(SETTING::INVISIBLE_LINES);
1605  setEffect(SETTING::INVISIBLE_LINES, !previousValue);
1606 }
1607 
1608 void ScribbleArea::toggleOutlines()
1609 {
1610  mIsSimplified = !mIsSimplified;
1611  setEffect(SETTING::OUTLINES, mIsSimplified);
1612 }
1613 
1614 void ScribbleArea::setLayerVisibility(LayerVisibility visibility)
1615 {
1616  mLayerVisibility = visibility;
1617  mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1618 
1620 }
1621 
1622 void ScribbleArea::increaseLayerVisibilityIndex()
1623 {
1624  ++mLayerVisibility;
1625  mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1626 
1628 }
1629 
1630 void ScribbleArea::decreaseLayerVisibilityIndex()
1631 {
1632  --mLayerVisibility;
1633  mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1634 
1636 }
1637 
1638 /************************************************************************************/
1639 // tool handling
1640 
1641 BaseTool* ScribbleArea::currentTool() const
1642 {
1643  return editor()->tools()->currentTool();
1644 }
1645 
1646 BaseTool* ScribbleArea::getTool(ToolType eToolType)
1647 {
1648  return editor()->tools()->getTool(eToolType);
1649 }
1650 
1651 void ScribbleArea::setCurrentTool(ToolType eToolMode)
1652 {
1653  Q_UNUSED(eToolMode)
1654 
1655  // change cursor
1656  setCursor(currentTool()->cursor());
1657  updateCanvasCursor();
1658 }
1659 
1660 void ScribbleArea::deleteSelection()
1661 {
1662  auto selectMan = mEditor->select();
1663  if (selectMan->somethingSelected()) // there is something selected
1664  {
1665  Layer* layer = mEditor->layers()->currentLayer();
1666  if (layer == nullptr) { return; }
1667 
1669 
1670  mEditor->backup(tr("Delete Selection", "Undo Step: clear the selection area."));
1671 
1672  selectMan->clearCurves();
1673  if (layer->type() == Layer::VECTOR)
1674  {
1675  VectorImage* vectorImage = currentVectorImage(layer);
1676  Q_CHECK_PTR(vectorImage);
1677  vectorImage->deleteSelection();
1678  }
1679  else if (layer->type() == Layer::BITMAP)
1680  {
1681  BitmapImage* bitmapImage = currentBitmapImage(layer);
1682  Q_CHECK_PTR(bitmapImage);
1683  bitmapImage->clear(selectMan->mySelectionRect());
1684  }
1685  setModified(mEditor->currentLayerIndex(), mEditor->currentFrame());
1686  }
1687 }
1688 
1689 void ScribbleArea::clearImage()
1690 {
1691  Layer* layer = mEditor->layers()->currentLayer();
1692  if (layer == nullptr) { return; }
1693 
1694  if (layer->type() == Layer::VECTOR)
1695  {
1696  mEditor->backup(tr("Clear Image", "Undo step text"));
1697 
1698  VectorImage* vectorImage = currentVectorImage(layer);
1699  if (vectorImage != nullptr)
1700  {
1701  vectorImage->clear();
1702  }
1703  mEditor->select()->clearCurves();
1704  mEditor->select()->clearVertices();
1705  }
1706  else if (layer->type() == Layer::BITMAP)
1707  {
1708  mEditor->backup(tr("Clear Image", "Undo step text"));
1709 
1710  BitmapImage* bitmapImage = currentBitmapImage(layer);
1711  if (bitmapImage == nullptr) return;
1712  bitmapImage->clear();
1713  }
1714  else
1715  {
1716  return; // skip updates when nothing changes
1717  }
1718  setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1719 }
1720 
1721 void ScribbleArea::paletteColorChanged(QColor color)
1722 {
1723  Q_UNUSED(color)
1724 
1725  for (int i = 0; i < mEditor->layers()->count(); i++)
1726  {
1727  Layer* layer = mEditor->layers()->getLayer(i);
1728  if (layer->type() == Layer::VECTOR)
1729  {
1730  VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getVectorImageAtFrame(mEditor->currentFrame());
1731  if (vectorImage != nullptr)
1732  {
1733  vectorImage->modification();
1734  }
1735  }
1736  }
1737 
1739 }
1740 
1741 void ScribbleArea::floodFillError(int errorType)
1742 {
1743  QString message, error;
1744  if (errorType == 1) { message = tr("There is a gap in your drawing (or maybe you have zoomed too much)."); }
1745  if (errorType == 2 || errorType == 3)
1746  {
1747  message = tr("Sorry! This doesn't always work."
1748  "Please try again (zoom a bit, click at another location... )<br>"
1749  "if it doesn't work, zoom a bit and check that your paths are connected by pressing F1.).");
1750  }
1751 
1752  if (errorType == 1) { error = tr("Out of bound.", "Bucket tool fill error message"); }
1753  if (errorType == 2) { error = tr("Could not find a closed path.", "Bucket tool fill error message"); }
1754  if (errorType == 3) { error = tr("Could not find the root index.", "Bucket tool fill error message"); }
1755  QMessageBox::warning(this, tr("Flood fill error"), tr("%1<br><br>Error: %2").arg(message, error), QMessageBox::Ok, QMessageBox::Ok);
1756  mEditor->deselectAll();
1757 }
void setInterval(int msec)
QRectF boundingRect() const const
static QPixmap canvasCursor(float brushWidth, float brushFeather, bool useFeather, float scalingFac, int windowWidth)
precision circular cursor: used for drawing a cursor within scribble area.
Definition: basetool.cpp:124
void onSelectionChanged()
Selection was changed, keep cache.
void setTransform(const QTransform &transform, bool combine)
ControlModifier
Qt::KeyboardModifiers modifiers() const const
QPolygon toPolygon() const const
WindowDeactivate
int getFirstSelectedCurve()
VectorImage::getFirstSelectedCurve.
QEvent::Type type() const const
Qt::KeyboardModifiers modifiers() const
Returns the modifier created by keyboard while a device was in use.
QRect toRect() const const
int width() const const
void setCursor(const QCursor &)
void invalidateOnionSkinsCacheAround(int frame)
invalidate onion skin cache around frame
QPixmap quickSizeCursor(qreal scalingFac)
precision circular cursor: used for drawing stroke size while adjusting
Definition: basetool.cpp:262
qreal alphaF() const const
void reset()
void setRenderHint(QPainter::RenderHint hint, bool on)
void onPlayStateChanged()
Playstate changed, invalidate relevant cache.
SolidLine
int getCurveSize(int curveNumber)
VectorImage::getCurveSize.
QList< int > dirtyFrames() const
Returns a list of dirty frame positions.
Definition: layer.h:162
void fill(const QColor &color)
void setRgba(QRgb rgba)
void remove(const QString &key)
void clear()
VectorImage::clear.
QPoint map(const QPoint &point) const const
void setColorAt(qreal position, const QColor &color)
Qt::MouseButtons buttons() const const
const T & at(int i) const const
void onFramesModified()
Multiple frames modified, invalidate cache for affected frames.
void save()
LeftButton
void onLayerChanged()
Layer changed, invalidate relevant cache.
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void setBlue(int blue)
void setAlpha(int alpha)
void setModified(int layerNumber, int frameNumber)
Set frame on layer to modified and invalidate current frame cache.
Qt::MouseButtons buttons() const const
bool isAutoRepeat() const const
void setRed(int red)
void clear()
QString tr(const char *sourceText, const char *disambiguation, int n)
RoundCap
void update()
int x() const const
int y() const const
int size() const const
int width() const const
QSize size() const const
const QRect & rect() const const
void timeout()
void drawRect(const QRectF &rectangle)
void invalidateAllCache()
Invalidate all cache.
void setCacheLimit(int n)
qreal length() const const
qreal x() const const
qreal y() const const
void setGreen(int green)
void ignore()
int getFirstSelectedArea()
VectorImage::getFirstSelectedArea.
void handleDrawingOnEmptyFrame()
Call this when starting to use a paint tool.
const QPointF & posF() const const
int red() const const
void setPen(const QColor &color)
QRectF normalized() const const
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
void setObjectName(const QString &name)
QPointF getVertex(int curveNumber, int vertexNumber)
VectorImage::getVertex.
QPixmap transformed(const QMatrix &matrix, Qt::TransformationMode mode) const const
Definition: layer.h:38
QPoint pos() const const
void setBrush(const QBrush &brush)
QRect translated(int dx, int dy) const const
void updateFrame(int frame)
Update frame.
QMap::iterator end()
qreal devicePixelRatioF() const const
void setSizePolicy(QSizePolicy)
void onScrubbed(int frameNumber)
Frame scrubbed, invalidate relevant cache.
QRect rect() const const
void clearDirtyFrames()
Clear the list of dirty keyframes.
Definition: layer.h:169
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
Qt::MouseButtons buttons() const
Returns Qt::MouseButtons()
int green() const const
int key() const const
RoundJoin
void invalidateCacheForDirtyFrames()
invalidate cache for dirty keyframes.
void stop()
void restore()
WA_StaticContents
void onFrameModified(int frameNumber)
Frame modified, invalidate cache for frame if any.
int blue() const const
void invalidateCacheForFrame(int frameNumber)
Invalidate cache for the given frame.
bool contains(const QRect &rectangle, bool proper) const const
void setSelectionTransformation(QTransform transform)
VectorImage::setSelectionTransformation.
bool isNull() const const
QPoint toPoint() const const
void onViewChanged()
View updated, invalidate relevant cache.
bool isEmpty() const const
void invalidateLayerPixmapCache()
Invalidate the layer pixmap cache.
void onObjectLoaded()
Object updated, invalidate all cache.
void setTabletTracking(bool enable)
void setWorldMatrixEnabled(bool enable)
QPixmap * find(const QString &key)
void setX(int x)
void setY(int y)
void setMouseTracking(bool enable)
Qt::MouseButton button() const
Returns Qt::MouseButton()
void updateCurrentFrame()
Update current frame.
void start(int msec)
void applySelectionTransformation()
VectorImage::applySelectionTransformation.
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
Definition: object.h:41
void onOnionSkinTypeChanged()
Onion skin type changed, all frames will be affected.
void flipSelection(bool flipVertical)
ScribbleArea::flipSelection flip selection along the X or Y axis.
virtual void resizeEvent(QResizeEvent *event)
transparent
bool insert(const QString &key, const QPixmap &pixmap)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setDevicePixelRatio(qreal scaleFactor)
QRect rect() const const
virtual bool event(QEvent *event) override
QString platformName()
QMap::iterator find(const Key &key)
void onCurrentFrameModified()
Current frame modified, invalidate current frame cache if any.
Key_Right
int height() const const
QRgb rgba() const const
void deleteSelection()
VectorImage::deleteSelection.
int remove(const Key &key)