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