Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • interface
scribblearea.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU 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#include <QTimer>
25
26#include "basetool.h"
27#include "transformtool.h"
28#include "pointerevent.h"
29#include "beziercurve.h"
30#include "object.h"
31#include "editor.h"
32#include "layerbitmap.h"
33#include "layervector.h"
34#include "layercamera.h"
35#include "bitmapimage.h"
36#include "vectorimage.h"
37#include "blitrect.h"
38#include "tile.h"
39
40#include "onionskinpainteroptions.h"
41
42#include "colormanager.h"
43#include "toolmanager.h"
44#include "layermanager.h"
45#include "playbackmanager.h"
46#include "viewmanager.h"
47#include "selectionmanager.h"
48#include "overlaymanager.h"
49
50ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent),
51 mCanvasPainter(mCanvas),
52 mCameraPainter(mCanvas)
53{
54 setObjectName("ScribbleArea");
55
56 // Qt::WA_StaticContents ensure that the widget contents are rooted to the top-left corner
57 // and don't change when the widget is resized.
58 setAttribute(Qt::WA_StaticContents);
59}
60
61ScribbleArea::~ScribbleArea()
62{
63}
64
65bool ScribbleArea::init()
66{
67 mPrefs = mEditor->preference();
68 mDoubleClickTimer = new QTimer(this);
69 mMouseFilterTimer = new QTimer(this);
70
71 connect(mPrefs, &PreferenceManager::optionChanged, this, &ScribbleArea::settingUpdated);
72 connect(mEditor->tools(), &ToolManager::toolChanged, this, &ScribbleArea::onToolChanged);
73
74 connect(mDoubleClickTimer, &QTimer::timeout, this, &ScribbleArea::handleDoubleClick);
75 connect(mMouseFilterTimer, &QTimer::timeout, this, &ScribbleArea::tabletReleaseEventFired);
76
77 connect(mEditor->select(), &SelectionManager::selectionChanged, this, &ScribbleArea::onSelectionChanged);
78 connect(mEditor->select(), &SelectionManager::needDeleteSelection, this, &ScribbleArea::deleteSelection);
79
80 connect(&mTiledBuffer, &TiledBuffer::tileUpdated, this, &ScribbleArea::onTileUpdated);
81 connect(&mTiledBuffer, &TiledBuffer::tileCreated, this, &ScribbleArea::onTileCreated);
82
83 mDoubleClickTimer->setInterval(50);
84 mMouseFilterTimer->setInterval(50);
85
86 const int curveSmoothingLevel = mPrefs->getInt(SETTING::CURVE_SMOOTHING);
87 mCurveSmoothingLevel = curveSmoothingLevel / 20.0; // default value is 1.0
88
89 mMakeInvisible = false;
90
91 mLayerVisibility = static_cast<LayerVisibility>(mPrefs->getInt(SETTING::LAYER_VISIBILITY));
92
93 mDeltaFactor = mEditor->preference()->isOn(SETTING::INVERT_SCROLL_ZOOM_DIRECTION) ? -1 : 1;
94
95 setMouseTracking(true); // reacts to mouse move events, even if the button is not pressed
96#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
97 setTabletTracking(true); // tablet tracking first added in 5.9
98#endif
99
100 setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
101
102 QPixmapCache::setCacheLimit(100 * 1024); // unit is kb, so it's 100MB cache
103 mPixmapCacheKeys.clear();
104
105 return true;
106}
107
108void ScribbleArea::settingUpdated(SETTING setting)
109{
110 switch (setting)
111 {
112 case SETTING::CURVE_SMOOTHING:
113 setCurveSmoothing(mPrefs->getInt(SETTING::CURVE_SMOOTHING));
114 break;
115 case SETTING::TOOL_CURSOR:
116 updateToolCursor();
117 break;
118 case SETTING::ONION_PREV_FRAMES_NUM:
119 case SETTING::ONION_NEXT_FRAMES_NUM:
120 case SETTING::ONION_MIN_OPACITY:
121 case SETTING::ONION_MAX_OPACITY:
122 invalidateAllCache();
123 break;
124 case SETTING::ANTIALIAS:
125 case SETTING::GRID:
126 case SETTING::GRID_SIZE_W:
127 case SETTING::GRID_SIZE_H:
128 case SETTING::OVERLAY_CENTER:
129 case SETTING::OVERLAY_THIRDS:
130 case SETTING::OVERLAY_GOLDEN:
131 case SETTING::OVERLAY_SAFE:
132 case SETTING::OVERLAY_PERSPECTIVE1:
133 case SETTING::OVERLAY_PERSPECTIVE2:
134 case SETTING::OVERLAY_PERSPECTIVE3:
135 case SETTING::ACTION_SAFE_ON:
136 case SETTING::ACTION_SAFE:
137 case SETTING::TITLE_SAFE_ON:
138 case SETTING::TITLE_SAFE:
139 case SETTING::OVERLAY_SAFE_HELPER_TEXT_ON:
140 case SETTING::PREV_ONION:
141 case SETTING::NEXT_ONION:
142 case SETTING::ONION_BLUE:
143 case SETTING::ONION_RED:
144 case SETTING::INVISIBLE_LINES:
145 case SETTING::OUTLINES:
146 case SETTING::ONION_TYPE:
147 case SETTING::ONION_WHILE_PLAYBACK:
148 invalidateAllCache();
149 break;
150 case SETTING::ONION_MUTLIPLE_LAYERS:
151 invalidateAllCache();
152 break;
153 case SETTING::LAYER_VISIBILITY_THRESHOLD:
154 case SETTING::LAYER_VISIBILITY:
155 setLayerVisibility(static_cast<LayerVisibility>(mPrefs->getInt(SETTING::LAYER_VISIBILITY)));
156 break;
157 case SETTING::INVERT_SCROLL_ZOOM_DIRECTION:
158 mDeltaFactor = mEditor->preference()->isOn(SETTING::INVERT_SCROLL_ZOOM_DIRECTION) ? -1 : 1;
159 break;
160 default:
161 break;
162 }
163
164}
165
166void ScribbleArea::updateToolCursor()
167{
168 setCursor(currentTool()->cursor());
169}
170
171void ScribbleArea::setCurveSmoothing(int newSmoothingLevel)
172{
173 mCurveSmoothingLevel = newSmoothingLevel / 20.0;
174 invalidatePainterCaches();
175}
176
177void ScribbleArea::setEffect(SETTING e, bool isOn)
178{
179 mPrefs->set(e, isOn);
180 invalidatePainterCaches();
181}
182
183/************************************************************************************/
184// update methods
185
186void ScribbleArea::onTileUpdated(TiledBuffer* tiledBuffer, Tile* tile)
187{
188 Q_UNUSED(tiledBuffer);
189 const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds()));
190 update(mappedRect.toAlignedRect());
191}
192
193void ScribbleArea::onTileCreated(TiledBuffer* tiledBuffer, Tile* tile)
194{
195 Q_UNUSED(tiledBuffer)
196 const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds()));
197 update(mappedRect.toAlignedRect());
198}
199
200void ScribbleArea::updateFrame()
201{
202 if (currentTool()->isActive() && currentTool()->isDrawingTool()) {
203 return;
204 }
205
206 update();
207}
208
209void ScribbleArea::invalidateCacheForDirtyFrames()
210{
211 Layer* currentLayer = mEditor->layers()->currentLayer();
212 for (int pos : currentLayer->dirtyFrames()) {
213
214 invalidateCacheForFrame(pos);
215 invalidateOnionSkinsCacheAround(pos);
216 }
217 currentLayer->clearDirtyFrames();
218}
219
220void ScribbleArea::invalidateOnionSkinsCacheAround(int frameNumber)
221{
222 if (frameNumber < 0) { return; }
223
224 bool isOnionAbsolute = mPrefs->getString(SETTING::ONION_TYPE) == "absolute";
225 Layer *layer = mEditor->layers()->currentLayer(0);
226
227 // The current layer can be null if updateFrame is triggered when creating a new project
228 if (!layer) return;
229
230 if (mPrefs->isOn(SETTING::PREV_ONION))
231 {
232 int onionFrameNumber = frameNumber;
233 if (isOnionAbsolute)
234 {
235 onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber + 1, true);
236 }
237
238 for(int i = 1; i <= mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM); i++)
239 {
240 onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isOnionAbsolute);
241 if (onionFrameNumber < 0) break;
242
243 invalidateCacheForFrame(onionFrameNumber);
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->getNextFrameNumber(onionFrameNumber, isOnionAbsolute);
254 if (onionFrameNumber < 0) break;
255
256 invalidateCacheForFrame(onionFrameNumber);
257 }
258 }
259}
260
261void ScribbleArea::invalidateAllCache()
262{
263 if (currentTool()->isDrawingTool() && currentTool()->isActive()) { return; }
264
265 QPixmapCache::clear();
266 mPixmapCacheKeys.clear();
267 invalidatePainterCaches();
268 mEditor->layers()->currentLayer()->clearDirtyFrames();
269
270 updateFrame();
271}
272
273void ScribbleArea::invalidateCacheForFrame(int frameNumber)
274{
275 auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned int>(frameNumber));
276 if (cacheKeyIter != mPixmapCacheKeys.end())
277 {
278 QPixmapCache::remove(cacheKeyIter.value());
279 unsigned int key = cacheKeyIter.key();
280 mPixmapCacheKeys.remove(key);
281 }
282}
283
284void ScribbleArea::invalidatePainterCaches()
285{
286 mCameraPainter.resetCache();
287 mCanvasPainter.resetLayerCache();
288 updateFrame();
289}
290
291void ScribbleArea::onToolChanged(ToolType)
292{
293 int frame = mEditor->currentFrame();
294 prepOverlays(frame);
295 prepCameraPainter(frame);
296 invalidateCacheForFrame(frame);
297 updateFrame();
298}
299
300
301void ScribbleArea::onPlayStateChanged()
302{
303 int currentFrame = mEditor->currentFrame();
304 if (mPrefs->isOn(SETTING::PREV_ONION) ||
305 mPrefs->isOn(SETTING::NEXT_ONION)) {
306 invalidatePainterCaches();
307 }
308
309 prepOverlays(currentFrame);
310 prepCameraPainter(currentFrame);
311 invalidateCacheForFrame(currentFrame);
312 updateFrame();
313}
314
315void ScribbleArea::onScrubbed(int frameNumber)
316{
317 Q_UNUSED(frameNumber)
318 invalidatePainterCaches();
319 updateFrame();
320}
321
322void ScribbleArea::onFramesModified()
323{
324 invalidateCacheForDirtyFrames();
325 if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) {
326 invalidatePainterCaches();
327 }
328 updateFrame();
329}
330
331void ScribbleArea::onFrameModified(int frameNumber)
332{
333 if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) {
334 invalidateOnionSkinsCacheAround(frameNumber);
335 invalidatePainterCaches();
336 }
337 invalidateCacheForFrame(frameNumber);
338 updateFrame();
339}
340
341void ScribbleArea::onViewChanged()
342{
343 invalidateAllCache();
344}
345
346void ScribbleArea::onLayerChanged()
347{
348 invalidateAllCache();
349}
350
351void ScribbleArea::onSelectionChanged()
352{
353 int currentFrame = mEditor->currentFrame();
354 invalidateCacheForFrame(currentFrame);
355 updateFrame();
356}
357
358void ScribbleArea::onOnionSkinTypeChanged()
359{
360 invalidateAllCache();
361}
362
363void ScribbleArea::onObjectLoaded()
364{
365 invalidateAllCache();
366}
367
368bool ScribbleArea::event(QEvent *event)
369{
370 bool processed = false;
371 if (event->type() == QEvent::WindowDeactivate)
372 {
373 editor()->tools()->clearTemporaryTool();
374 processed = true;
375 } else if (event->type() == QEvent::Enter)
376 {
377 emit requestFocus(this);
378
379 processed = currentTool()->enterEvent(static_cast<QEnterEvent*>(event)) || processed;
380 } else if (event->type() == QEvent::Leave)
381 {
382 processed = currentTool()->leaveEvent(event) || processed;
383 }
384
385 return QWidget::event(event) || processed;
386}
387
388/************************************************************************/
389/* key event handlers */
390/************************************************************************/
391
392void ScribbleArea::keyPressEvent(QKeyEvent *event)
393{
394 // Don't handle this event on auto repeat
395 if (event->isAutoRepeat()) { return; }
396
397 if (isPointerInUse()) { return; } // prevents shortcuts calls while drawing
398
399 if (currentTool()->keyPressEvent(event))
400 {
401 return; // has been handled by tool
402 }
403
404 // --- fixed control key shortcuts ---
405 if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier) &&
406 editor()->tools()->setTemporaryTool(ERASER, {}, event->modifiers()))
407 {
408 return;
409 }
410
411 // ---- fixed normal keys ----
412
413 auto selectMan = mEditor->select();
414 bool isSomethingSelected = selectMan->somethingSelected();
415 if (isSomethingSelected)
416 {
417 keyEventForSelection(event);
418 }
419 else
420 {
421 keyEvent(event);
422 }
423}
424
425void ScribbleArea::keyEventForSelection(QKeyEvent* event)
426{
427 auto selectMan = mEditor->select();
428 switch (event->key())
429 {
430 case Qt::Key_Right:
431 selectMan->translate(QPointF(1, 0));
432 selectMan->calculateSelectionTransformation();
433 emit mEditor->frameModified(mEditor->currentFrame());
434 return;
435 case Qt::Key_Left:
436 selectMan->translate(QPointF(-1, 0));
437 selectMan->calculateSelectionTransformation();
438 emit mEditor->frameModified(mEditor->currentFrame());
439 return;
440 case Qt::Key_Up:
441 selectMan->translate(QPointF(0, -1));
442 selectMan->calculateSelectionTransformation();
443 emit mEditor->frameModified(mEditor->currentFrame());
444 return;
445 case Qt::Key_Down:
446 selectMan->translate(QPointF(0, 1));
447 selectMan->calculateSelectionTransformation();
448 emit mEditor->frameModified(mEditor->currentFrame());
449 return;
450 case Qt::Key_Return:
451 applyTransformedSelection();
452 mEditor->deselectAll();
453 return;
454 case Qt::Key_Escape:
455 cancelTransformedSelection();
456 mEditor->deselectAll();
457 return;
458 case Qt::Key_Backspace:
459 deleteSelection();
460 mEditor->deselectAll();
461 return;
462 case Qt::Key_Space:
463 if (editor()->tools()->setTemporaryTool(HAND, Qt::Key_Space, Qt::NoModifier))
464 {
465 return;
466 }
467 break;
468 default:
469 break;
470 }
471 event->ignore();
472}
473
474void ScribbleArea::keyEvent(QKeyEvent* event)
475{
476 switch (event->key())
477 {
478 case Qt::Key_Right:
479 mEditor->scrubForward();
480 break;
481 case Qt::Key_Left:
482 mEditor->scrubBackward();
483 break;
484 case Qt::Key_Up:
485 mEditor->layers()->gotoNextLayer();
486 break;
487 case Qt::Key_Down:
488 mEditor->layers()->gotoPreviouslayer();
489 break;
490 case Qt::Key_Space:
491 if(editor()->tools()->setTemporaryTool(HAND, Qt::Key_Space, Qt::NoModifier))
492 {
493 return;
494 }
495 break;
496 default:
497 break;
498 }
499 event->ignore();
500}
501
502void ScribbleArea::keyReleaseEvent(QKeyEvent *event)
503{
504 // Don't handle this event on auto repeat
505 //
506 if (event->isAutoRepeat()) {
507 return;
508 }
509
510 if (event->key() == 0)
511 {
512 editor()->tools()->tryClearTemporaryTool(Qt::Key_unknown);
513 }
514 else
515 {
516 editor()->tools()->tryClearTemporaryTool(static_cast<Qt::Key>(event->key()));
517 }
518
519 if (isPointerInUse()) { return; }
520
521 if (currentTool()->keyReleaseEvent(event))
522 {
523 // has been handled by tool
524 return;
525 }
526}
527
528/************************************************************************************/
529// mouse and tablet event handlers
530void ScribbleArea::wheelEvent(QWheelEvent* event)
531{
532 // Don't change view if the tool is in use
533 if (isPointerInUse()) return;
534
535 static const bool isX11 = QGuiApplication::platformName() == "xcb";
536 const QPoint pixels = event->pixelDelta();
537 const QPoint angle = event->angleDelta();
538#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
539 const QPointF offset = mEditor->view()->mapScreenToCanvas(event->position());
540#else
541 const QPointF offset = mEditor->view()->mapScreenToCanvas(event->posF());
542#endif
543
544 const qreal currentScale = mEditor->view()->scaling();
545
546 // From the pixelDelta documentation: On X11 this value is driver-specific and unreliable, use angleDelta() instead
547 int delta = 0;
548 if (!isX11 && !pixels.isNull())
549 {
550 delta = pixels.y();
551 }
552 else if (!angle.isNull()) // Wheel based delta
553 {
554 delta = angle.y();
555 }
556
557 if (delta != 0) {
558 const qreal newScale = currentScale * std::pow(100, (delta * mDeltaFactor) / (12.0 * 120));
559 mEditor->view()->scaleAtOffset(newScale, offset);
560 }
561 event->accept();
562}
563
564void ScribbleArea::tabletEvent(QTabletEvent *e)
565{
566 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->posF()));
567
568#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
569 if (event.pointerType() == QPointingDevice::PointerType::Eraser)
570#else
571 if (event.pointerType() == QTabletEvent::Eraser)
572#endif
573 {
574 editor()->tools()->tabletSwitchToEraser();
575 }
576 else
577 {
578 editor()->tools()->tabletRestorePrevTool();
579 }
580
581 if (event.eventType() == PointerEvent::Press)
582 {
583 event.accept();
584
585 if (!hasFocus()) {
586 setFocus();
587 }
588
589 if (mIsFirstClick)
590 {
591 mIsFirstClick = false;
592 mDoubleClickTimer->start();
593 pointerPressEvent(&event);
594 }
595 else
596 {
597 qreal distance = QLineF(e->posF(), mTabletPressPos).length();
598 mTabletPressPos = e->posF();
599
600 if (mDoubleClickMillis <= DOUBLE_CLICK_THRESHOLD && distance < 5.0) {
601 currentTool()->pointerDoubleClickEvent(&event);
602 }
603 else
604 {
605 // in case we handled the event as double click but really should have handled it as single click.
606 pointerPressEvent(&event);
607 }
608 }
609 mTabletInUse = event.isAccepted();
610 }
611 else if (event.eventType() == PointerEvent::Move)
612 {
613 if (!mTabletHasEntered && !hasFocus()) {
614 emit requestFocus(this);
615 mTabletHasEntered = true;
616 }
617 if (!(event.buttons() & (Qt::LeftButton | Qt::RightButton)) || mTabletInUse)
618 {
619 pointerMoveEvent(&event);
620 }
621 }
622 else if (event.eventType() == PointerEvent::Release)
623 {
624 mTabletReleaseMillisAgo = 0;
625 mMouseFilterTimer->start();
626 if (mTabletInUse)
627 {
628 pointerReleaseEvent(&event);
629 mTabletInUse = false;
630 }
631 mTabletHasEntered = false;
632 }
633
634#if defined Q_OS_LINUX
635 // Generate mouse events on linux to work around bug where the
636 // widget will not receive mouseEnter/mouseLeave
637 // events and the cursor will not update correctly.
638 // See https://codereview.qt-project.org/c/qt/qtbase/+/255384
639 // Scribblearea should not do anything with the mouse event when mTabletInUse is true.
640 event.ignore();
641#else
642 // Always accept so that mouse events are not generated (theoretically)
643 // Unfortunately Windows sometimes generates the events anyway
644 // As long as mTabletInUse is true, mouse events *should* be ignored even when
645 // the are generated
646 event.accept();
647#endif
648}
649
650void ScribbleArea::pointerPressEvent(PointerEvent* event)
651{
652 bool isCameraLayer = mEditor->layers()->currentLayer()->type() == Layer::CAMERA;
653 if ((currentTool()->type() != HAND || isCameraLayer) && (event->button() != Qt::RightButton) && (event->button() != Qt::MiddleButton || isCameraLayer))
654 {
655 Layer* layer = mEditor->layers()->currentLayer();
656 if (!layer->visible())
657 {
658 event->ignore();
659 // This needs to be async so that mTabletInUse is set to false before
660 // further events are created (modal dialogs do not currently block tablet events)
661 QTimer::singleShot(0, this, &ScribbleArea::showLayerNotVisibleWarning);
662 return;
663 }
664 }
665
666 if (event->buttons() & (Qt::MiddleButton | Qt::RightButton) &&
667 editor()->tools()->setTemporaryTool(HAND, event->buttons()))
668 {
669 currentTool()->pointerPressEvent(event);
670 } else if (event->button() == Qt::LeftButton)
671 {
672 currentTool()->pointerPressEvent(event);
673 }
674}
675
676void ScribbleArea::pointerMoveEvent(PointerEvent* event)
677{
678 currentTool()->pointerMoveEvent(event);
679}
680
681void ScribbleArea::pointerReleaseEvent(PointerEvent* event)
682{
683 currentTool()->pointerReleaseEvent(event);
684
685 editor()->tools()->tryClearTemporaryTool(event->button());
686}
687
688void ScribbleArea::handleDoubleClick()
689{
690 mDoubleClickMillis += 100;
691
692 if (mDoubleClickMillis >= DOUBLE_CLICK_THRESHOLD)
693 {
694 mDoubleClickMillis = 0;
695 mIsFirstClick = true;
696 mDoubleClickTimer->stop();
697 }
698}
699
700void ScribbleArea::tabletReleaseEventFired()
701{
702 // Under certain circumstances, a mouse press event will fire after a tablet release event.
703 // This causes unexpected behaviors for some tools, e.g., the bucket tool.
704 // The problem only seems to occur on Windows and only when tapping.
705 // Prior to this fix, the event queue would look like this:
706 // e.g.: TabletPress -> TabletRelease -> MousePress
707 // The following will filter mouse events created after a tablet release event.
708 mTabletReleaseMillisAgo += 50;
709
710 if (mTabletReleaseMillisAgo >= MOUSE_FILTER_THRESHOLD) {
711 mTabletReleaseMillisAgo = 0;
712 mMouseFilterTimer->stop();
713 }
714}
715
716void ScribbleArea::mousePressEvent(QMouseEvent* e)
717{
718 if (mTabletInUse || (mMouseFilterTimer->isActive() && mTabletReleaseMillisAgo < MOUSE_FILTER_THRESHOLD))
719 {
720 e->ignore();
721 return;
722 }
723
724 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
725 pointerPressEvent(&event);
726 mMouseInUse = event.isAccepted();
727}
728
729void ScribbleArea::mouseMoveEvent(QMouseEvent* e)
730{
731 if (mTabletInUse || (mMouseFilterTimer->isActive() && mTabletReleaseMillisAgo < MOUSE_FILTER_THRESHOLD)) { e->ignore(); return; }
732
733 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
734 pointerMoveEvent(&event);
735}
736
737void ScribbleArea::mouseReleaseEvent(QMouseEvent* e)
738{
739 if (mTabletInUse || (mMouseFilterTimer->isActive() && mTabletReleaseMillisAgo < MOUSE_FILTER_THRESHOLD)) { e->ignore(); return; }
740
741 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
742 pointerReleaseEvent(&event);
743 mMouseInUse = (e->buttons() & Qt::RightButton) || (e->buttons() & Qt::LeftButton);
744}
745
746void ScribbleArea::mouseDoubleClickEvent(QMouseEvent* e)
747{
748 if (mTabletInUse) { e->ignore(); return; }
749
750 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
751 currentTool()->pointerDoubleClickEvent(&event);
752}
753
754void ScribbleArea::resizeEvent(QResizeEvent* event)
755{
756 QWidget::resizeEvent(event);
757 mDevicePixelRatio = devicePixelRatioF();
758 mCanvas = QPixmap(QSizeF(size() * mDevicePixelRatio).toSize());
759 mCanvas.setDevicePixelRatio(mDevicePixelRatio);
760 mEditor->view()->setCanvasSize(size());
761
762 invalidateCacheForFrame(mEditor->currentFrame());
763 invalidatePainterCaches();
764 mCanvasPainter.reset();
765 mCameraPainter.reset();
766}
767
768void ScribbleArea::showLayerNotVisibleWarning()
769{
770 QMessageBox::warning(this, tr("Warning"),
771 tr("You are trying to modify a hidden layer! Please select another layer (or make the current layer visible)."),
772 QMessageBox::Ok,
773 QMessageBox::Ok);
774}
775
776void ScribbleArea::paintBitmapBuffer()
777{
778 LayerBitmap* layer = static_cast<LayerBitmap*>(mEditor->layers()->currentLayer());
779 Q_ASSERT(layer);
780 Q_ASSERT(layer->type() == Layer::BITMAP);
781
782 int frameNumber = mEditor->currentFrame();
783
784 // If there is no keyframe at or before the current position,
785 // just return (since we have nothing to paint on).
786 if (layer->getLastKeyFrameAtPosition(frameNumber) == nullptr)
787 {
788 updateFrame();
789 return;
790 }
791
792 BitmapImage* targetImage = currentBitmapImage(layer);
793 if (targetImage != nullptr)
794 {
795 QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver;
796 switch (currentTool()->type())
797 {
798 case ERASER:
799 cm = QPainter::CompositionMode_DestinationOut;
800 break;
801 case BRUSH:
802 case PEN:
803 case PENCIL:
804 break;
805 default: //nothing
806 break;
807 }
808 targetImage->paste(&mTiledBuffer, cm);
809 }
810
811 QRect rect = mEditor->view()->mapCanvasToScreen(mTiledBuffer.bounds()).toRect();
812
813 update(rect);
814
815 layer->setModified(frameNumber, true);
816 mTiledBuffer.clear();
817}
818
819void ScribbleArea::clearDrawingBuffer()
820{
821 mTiledBuffer.clear();
822}
823
824void ScribbleArea::handleDrawingOnEmptyFrame()
825{
826 auto layer = mEditor->layers()->currentLayer();
827
828 if (!layer || !layer->isPaintable())
829 {
830 return;
831 }
832
833 if (currentTool()->type() == ERASER) {
834 return;
835 }
836
837 int frameNumber = mEditor->currentFrame();
838 if (layer->getKeyFrameAt(frameNumber)) { return; }
839
840 // Drawing on an empty frame; take action based on preference.
841 int action = mPrefs->getInt(SETTING::DRAW_ON_EMPTY_FRAME_ACTION);
842 auto previousKeyFrame = layer->getLastKeyFrameAtPosition(frameNumber);
843 switch (action)
844 {
845 case KEEP_DRAWING_ON_PREVIOUS_KEY:
846 {
847 if (previousKeyFrame == nullptr) {
848 mEditor->addNewKey();
849 } else {
850 onFrameModified(previousKeyFrame->pos());
851 }
852 break;
853 }
854 case DUPLICATE_PREVIOUS_KEY:
855 {
856 if (previousKeyFrame != nullptr) {
857 KeyFrame* dupKey = previousKeyFrame->clone();
858 layer->addKeyFrame(frameNumber, dupKey);
859 mEditor->scrubTo(frameNumber);
860 break;
861 }
862 }
863 // if the previous keyframe doesn't exist,
864 // an empty keyframe needs to be created, so
865 // fallthrough
866 case CREATE_NEW_KEY:
867 mEditor->addNewKey();
868
869 // Refresh canvas
870 drawCanvas(frameNumber, mCanvas.rect());
871 update();
872 break;
873 default:
874 break;
875 }
876}
877
878void ScribbleArea::paintEvent(QPaintEvent* event)
879{
880 int currentFrame = mEditor->currentFrame();
881 if (!currentTool()->isActive())
882 {
883 // --- we retrieve the canvas from the cache; we create it if it doesn't exist
884 const int frameNumber = mEditor->layers()->lastFrameAtFrame(currentFrame);
885
886 if (frameNumber < 0)
887 {
888 drawCanvas(currentFrame, event->rect());
889 }
890 else
891 {
892 auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned>(frameNumber));
893
894 if (cacheKeyIter == mPixmapCacheKeys.end() || !QPixmapCache::find(cacheKeyIter.value(), &mCanvas))
895 {
896 drawCanvas(currentFrame, event->rect());
897 mPixmapCacheKeys[static_cast<unsigned>(currentFrame)] = QPixmapCache::insert(mCanvas);
898 //qDebug() << "Repaint canvas!";
899 }
900 else
901 {
902 // Simply use the cached canvas from PixmapCache
903 }
904 }
905 }
906 else
907 {
908 prepCanvas(currentFrame);
909 prepCameraPainter(currentFrame);
910 prepOverlays(currentFrame);
911
912 mCanvasPainter.paintCached(event->rect());
913 mCameraPainter.paintCached(event->rect());
914 }
915
916 if (currentTool()->type() == MOVE)
917 {
918 Layer* layer = mEditor->layers()->currentLayer();
919 Q_CHECK_PTR(layer);
920 if (layer->type() == Layer::VECTOR)
921 {
922 VectorImage* vectorImage = currentVectorImage(layer);
923 if (vectorImage != nullptr)
924 {
925 vectorImage->setModified(true);
926 }
927 }
928 }
929
930 QPainter painter(this);
931
932 // paints the canvas
933 painter.setWorldMatrixEnabled(false);
934
935 // In other places we use the blitRect to paint the buffer pixmap, however
936 // the main pixmap which needs to be scaled accordingly to DPI, which is not accounted for when using the event rect
937 // instead we can set a clipRect to avoid the area being updated needlessly
938 painter.setClipRect(event->rect());
939 painter.drawPixmap(QPointF(), mCanvas);
940
941 currentTool()->paint(painter, event->rect());
942
943 if (!editor()->playback()->isPlaying()) // we don't need to display the following when the animation is playing
944 {
945 Layer* layer = mEditor->layers()->currentLayer();
946 if (layer->type() == Layer::VECTOR)
947 {
948 VectorImage* vectorImage = currentVectorImage(layer);
949 if (vectorImage != nullptr)
950 {
951 switch (currentTool()->type())
952 {
953 case SMUDGE:
954 case HAND:
955 {
956 auto selectMan = mEditor->select();
957 painter.save();
958 painter.setWorldMatrixEnabled(false);
959 painter.setRenderHint(QPainter::Antialiasing, false);
960 // ----- paints the edited elements
961 QPen pen2(Qt::black, 0.5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
962 painter.setPen(pen2);
963 QColor color;
964 // ------------ vertices of the edited curves
965 color = QColor(200, 200, 200);
966 painter.setBrush(color);
967 VectorSelection vectorSelection = selectMan->vectorSelection;
968 for (int k = 0; k < vectorSelection.curve.size(); k++)
969 {
970 int curveNumber = vectorSelection.curve.at(k);
971
972 for (int vertexNumber = -1; vertexNumber < vectorImage->getCurveSize(curveNumber); vertexNumber++)
973 {
974 QPointF vertexPoint = vectorImage->getVertex(curveNumber, vertexNumber);
975 QRectF rectangle(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
976 if (rect().contains(mEditor->view()->mapCanvasToScreen(vertexPoint).toPoint()))
977 {
978 painter.drawRect(rectangle);
979 }
980 }
981 }
982 // ------------ selected vertices of the edited curves
983 color = QColor(100, 100, 255);
984 painter.setBrush(color);
985 for (int k = 0; k < vectorSelection.vertex.size(); k++)
986 {
987 VertexRef vertexRef = vectorSelection.vertex.at(k);
988 QPointF vertexPoint = vectorImage->getVertex(vertexRef);
989 QRectF rectangle0 = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
990 painter.drawRect(rectangle0);
991 }
992 // ----- paints the closest vertices
993 color = QColor(255, 0, 0);
994 painter.setBrush(color);
995 QList<VertexRef> closestVertices = selectMan->closestVertices();
996 if (vectorSelection.curve.size() > 0)
997 {
998 for (int k = 0; k < closestVertices.size(); k++)
999 {
1000 VertexRef vertexRef = closestVertices.at(k);
1001 QPointF vertexPoint = vectorImage->getVertex(vertexRef);
1002
1003 QRectF rectangle = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
1004 painter.drawRect(rectangle);
1005 }
1006 }
1007 painter.restore();
1008 break;
1009 }
1010 default:
1011 {
1012 break;
1013 }
1014 } // end switch
1015 }
1016 }
1017
1018 mOverlayPainter.paint(painter, rect());
1019
1020 // paints the selection outline
1021 if (mEditor->select()->somethingSelected())
1022 {
1023 paintSelectionVisuals(painter);
1024 }
1025 }
1026
1027 // outlines the frame of the viewport
1028#ifdef _DEBUG
1029 painter.setWorldMatrixEnabled(false);
1030 painter.setPen(QPen(Qt::gray, 2));
1031 painter.setBrush(Qt::NoBrush);
1032 painter.drawRect(QRect(0, 0, width(), height()));
1033#endif
1034
1035 event->accept();
1036}
1037
1038void ScribbleArea::paintSelectionVisuals(QPainter &painter)
1039{
1040 Object* object = mEditor->object();
1041
1042 auto selectMan = mEditor->select();
1043
1044 QRectF currentSelectionRect = selectMan->mySelectionRect();
1045
1046 TransformParameters params = { currentSelectionRect, editor()->view()->getView(), selectMan->selectionTransform() };
1047
1048 mSelectionPainter.paint(painter,
1049 object,
1050 mEditor->currentLayerIndex(),
1051 static_cast<const TransformTool*>(editor()->tools()->getTool(SELECT))->transformSettings(),
1052 params);
1053 emit selectionUpdated();
1054}
1055
1056BitmapImage* ScribbleArea::currentBitmapImage(Layer* layer) const
1057{
1058 Q_ASSERT(layer->type() == Layer::BITMAP);
1059 auto bitmapLayer = static_cast<LayerBitmap*>(layer);
1060 return bitmapLayer->getLastBitmapImageAtFrame(mEditor->currentFrame());
1061}
1062
1063VectorImage* ScribbleArea::currentVectorImage(Layer* layer) const
1064{
1065 Q_ASSERT(layer->type() == Layer::VECTOR);
1066 auto vectorLayer = static_cast<LayerVector*>(layer);
1067 return vectorLayer->getLastVectorImageAtFrame(mEditor->currentFrame());
1068}
1069
1070void ScribbleArea::prepCameraPainter(int frame)
1071{
1072 Object* object = mEditor->object();
1073
1074 mCameraPainter.preparePainter(object,
1075 mEditor->currentLayerIndex(),
1076 frame,
1077 mEditor->view()->getView(),
1078 mEditor->playback()->isPlaying(),
1079 mLayerVisibility,
1080 mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD),
1081 mEditor->view()->getScaleInversed());
1082
1083 OnionSkinPainterOptions onionSkinOptions;
1084 onionSkinOptions.enabledWhilePlaying = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK);
1085 onionSkinOptions.isPlaying = mEditor->playback()->isPlaying();
1086 onionSkinOptions.isAbsolute = (mPrefs->getString(SETTING::ONION_TYPE) == "absolute");
1087 onionSkinOptions.skinPrevFrames = mPrefs->isOn(SETTING::PREV_ONION);
1088 onionSkinOptions.skinNextFrames = mPrefs->isOn(SETTING::NEXT_ONION);
1089 onionSkinOptions.colorizePrevFrames = mPrefs->isOn(SETTING::ONION_RED);
1090 onionSkinOptions.colorizeNextFrames = mPrefs->isOn(SETTING::ONION_BLUE);
1091 onionSkinOptions.framesToSkinPrev = mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM);
1092 onionSkinOptions.framesToSkinNext = mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM);
1093 onionSkinOptions.maxOpacity = mPrefs->getInt(SETTING::ONION_MAX_OPACITY);
1094 onionSkinOptions.minOpacity = mPrefs->getInt(SETTING::ONION_MIN_OPACITY);
1095
1096 mCameraPainter.setOnionSkinPainterOptions(onionSkinOptions);
1097}
1098
1099void ScribbleArea::prepCanvas(int frame)
1100{
1101 Object* object = mEditor->object();
1102
1103 CanvasPainterOptions o;
1104 o.bOnionSkinMultiLayer = mPrefs->isOn(SETTING::ONION_MUTLIPLE_LAYERS);
1105 o.bAntiAlias = mPrefs->isOn(SETTING::ANTIALIAS);
1106 o.bThinLines = mPrefs->isOn(SETTING::INVISIBLE_LINES);
1107 o.bOutlines = mPrefs->isOn(SETTING::OUTLINES);
1108 o.eLayerVisibility = mLayerVisibility;
1109 o.fLayerVisibilityThreshold = mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD);
1110 o.scaling = mEditor->view()->scaling();
1111 o.cmBufferBlendMode = mEditor->tools()->currentTool()->type() == ToolType::ERASER ? QPainter::CompositionMode_DestinationOut : QPainter::CompositionMode_SourceOver;
1112
1113 OnionSkinPainterOptions onionSkinOptions;
1114 onionSkinOptions.enabledWhilePlaying = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK);
1115 onionSkinOptions.isPlaying = mEditor->playback()->isPlaying();
1116 onionSkinOptions.isAbsolute = (mPrefs->getString(SETTING::ONION_TYPE) == "absolute");
1117 onionSkinOptions.skinPrevFrames = mPrefs->isOn(SETTING::PREV_ONION);
1118 onionSkinOptions.skinNextFrames = mPrefs->isOn(SETTING::NEXT_ONION);
1119 onionSkinOptions.colorizePrevFrames = mPrefs->isOn(SETTING::ONION_RED);
1120 onionSkinOptions.colorizeNextFrames = mPrefs->isOn(SETTING::ONION_BLUE);
1121 onionSkinOptions.framesToSkinPrev = mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM);
1122 onionSkinOptions.framesToSkinNext = mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM);
1123 onionSkinOptions.maxOpacity = mPrefs->getInt(SETTING::ONION_MAX_OPACITY);
1124 onionSkinOptions.minOpacity = mPrefs->getInt(SETTING::ONION_MIN_OPACITY);
1125
1126 mCanvasPainter.setOnionSkinOptions(onionSkinOptions);
1127 mCanvasPainter.setOptions(o);
1128
1129 ViewManager* vm = mEditor->view();
1130 SelectionManager* sm = mEditor->select();
1131 mCanvasPainter.setViewTransform(vm->getView(), vm->getViewInverse());
1132 mCanvasPainter.setTransformedSelection(sm->mySelectionRect().toRect(), sm->selectionTransform());
1133
1134 mCanvasPainter.setPaintSettings(object, mEditor->layers()->currentLayerIndex(), frame, &mTiledBuffer);
1135}
1136
1137void ScribbleArea::drawCanvas(int frame, QRect rect)
1138{
1139 prepCanvas(frame);
1140 prepCameraPainter(frame);
1141 prepOverlays(frame);
1142 mCanvasPainter.paint(rect);
1143 mCameraPainter.paint(rect);
1144}
1145
1146void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor color, qreal opacity, qreal offset)
1147{
1148 if (offset < 0) { offset = 0; }
1149 if (offset > 100) { offset = 100; }
1150
1151 int r = color.red();
1152 int g = color.green();
1153 int b = color.blue();
1154 qreal a = color.alphaF();
1155
1156 int mainColorAlpha = qRound(a * 255 * opacity);
1157
1158 // the more feather (offset), the more softness (opacity)
1159 int alphaAdded = qRound((mainColorAlpha * offset) / 100);
1160
1161 gradient.setColorAt(0.0, QColor(r, g, b, mainColorAlpha - alphaAdded));
1162 gradient.setColorAt(1.0, QColor(r, g, b, 0));
1163 gradient.setColorAt(1.0 - (offset / 100.0), QColor(r, g, b, mainColorAlpha - alphaAdded));
1164}
1165
1166void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm)
1167{
1168 mTiledBuffer.drawPath(mEditor->view()->mapScreenToCanvas(path), pen, brush, cm, mPrefs->isOn(SETTING::ANTIALIAS));
1169}
1170
1171void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA)
1172{
1173 // We use Source as opposed to SourceOver here to avoid the dabs being added on top of each other
1174 mTiledBuffer.drawBrush(thePoint, brushWidth, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_Source, useAA);
1175}
1176
1177void ScribbleArea::drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity)
1178{
1179 drawBrush(thePoint, brushWidth, fixedBrushFeather, fillColor, QPainter::CompositionMode_SourceOver, opacity, true);
1180}
1181
1182void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset, QColor fillColor, QPainter::CompositionMode compMode, qreal opacity, bool usingFeather, bool useAA)
1183{
1184 QBrush brush;
1185 if (usingFeather)
1186 {
1187 QRadialGradient radialGrad(thePoint, 0.5 * brushWidth);
1188 setGaussianGradient(radialGrad, fillColor, opacity, mOffset);
1189 brush = radialGrad;
1190 }
1191 else
1192 {
1193 brush = QBrush(fillColor, Qt::SolidPattern);
1194 }
1195 mTiledBuffer.drawBrush(thePoint, brushWidth, Qt::NoPen, brush, compMode, useAA);
1196}
1197
1198void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA)
1199{
1200 BlitRect blitRect;
1201
1202 // In order to clear what was previously dirty, we need to include the previous buffer bound
1203 // this ensures that we won't see stroke artifacts
1204 blitRect.extend(mEditor->view()->mapCanvasToScreen(mTiledBuffer.bounds()).toRect());
1205
1206 QRect updateRect = mEditor->view()->mapCanvasToScreen(path.boundingRect()).toRect();
1207 // Now extend with the new path bounds mapped to the local coordinate
1208 blitRect.extend(updateRect);
1209
1210 mTiledBuffer.clear();
1211 mTiledBuffer.drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_SourceOver, useAA);
1212
1213 // And update only the affected area
1214 update(blitRect.adjusted(-1, -1, 1, 1));
1215}
1216
1217void ScribbleArea::endStroke()
1218{
1219 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP) {
1220 paintBitmapBuffer();
1221 }
1222
1223 onFrameModified(mEditor->currentFrame());
1224}
1225
1226void ScribbleArea::flipSelection(bool flipVertical)
1227{
1228 mEditor->select()->flipSelection(flipVertical);
1229}
1230
1231void ScribbleArea::prepOverlays(int frame)
1232{
1233 OverlayPainterOptions o;
1234
1235 o.bGrid = mPrefs->isOn(SETTING::GRID);
1236 o.nGridSizeW = mPrefs->getInt(SETTING::GRID_SIZE_W);
1237 o.nGridSizeH = mPrefs->getInt(SETTING::GRID_SIZE_H);
1238 o.bCenter = mPrefs->isOn(SETTING::OVERLAY_CENTER);
1239 o.bThirds = mPrefs->isOn(SETTING::OVERLAY_THIRDS);
1240 o.bGoldenRatio = mPrefs->isOn(SETTING::OVERLAY_GOLDEN);
1241 o.bSafeArea = mPrefs->isOn(SETTING::OVERLAY_SAFE);
1242 o.bPerspective1 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE1);
1243 o.bPerspective2 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE2);
1244 o.bPerspective3 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE3);
1245 o.nOverlayAngle = mPrefs->getInt(SETTING::OVERLAY_ANGLE);
1246 o.bActionSafe = mPrefs->isOn(SETTING::ACTION_SAFE_ON);
1247 o.nActionSafe = mPrefs->getInt(SETTING::ACTION_SAFE);
1248 o.bShowSafeAreaHelperText = mPrefs->isOn(SETTING::OVERLAY_SAFE_HELPER_TEXT_ON);
1249 o.bTitleSafe = mPrefs->isOn(SETTING::TITLE_SAFE_ON);
1250 o.nTitleSafe = mPrefs->getInt(SETTING::TITLE_SAFE);
1251 o.nOverlayAngle = mPrefs->getInt(SETTING::OVERLAY_ANGLE);
1252 o.bShowHandle = mEditor->tools()->currentTool()->type() == MOVE && mEditor->layers()->currentLayer()->type() != Layer::CAMERA;
1253
1254 o.mSinglePerspPoint = mEditor->overlays()->getSinglePerspectivePoint();
1255 o.mLeftPerspPoint = mEditor->overlays()->getLeftPerspectivePoint();
1256 o.mRightPerspPoint = mEditor->overlays()->getRightPerspectivePoint();
1257 o.mMiddlePerspPoint = mEditor->overlays()->getMiddlePerspectivePoint();
1258
1259 o.nFrameIndex = frame;
1260
1261 mOverlayPainter.setOptions(o);
1262 mOverlayPainter.preparePainter(mEditor->layers()->getCameraLayerBelow(mEditor->currentLayerIndex()), palette());
1263
1264 ViewManager* vm = mEditor->view();
1265 mOverlayPainter.setViewTransform(vm->getView());
1266}
1267
1268void ScribbleArea::blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_)
1269{
1270 QRadialGradient radialGrad(thePoint_, 0.5 * brushWidth_);
1271 setGaussianGradient(radialGrad, QColor(255, 255, 255, 127), opacity_, mOffset_);
1272
1273 QRectF srcRect(srcPoint_.x() - 0.5 * brushWidth_, srcPoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1274 QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1275
1276 BitmapImage bmiSrcClip = bmiSource_->copy(srcRect.toAlignedRect());
1277 BitmapImage bmiTmpClip = bmiSrcClip; // TODO: find a shorter way
1278
1279 bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, true);
1280 bmiSrcClip.bounds().moveTo(trgRect.topLeft().toPoint());
1281 bmiTmpClip.paste(&bmiSrcClip, QPainter::CompositionMode_SourceIn);
1282 mTiledBuffer.drawImage(*bmiTmpClip.image(), bmiTmpClip.bounds(), QPainter::CompositionMode_SourceOver, mPrefs->isOn(SETTING::ANTIALIAS));
1283}
1284
1285void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_)
1286{
1287 QPointF delta = (thePoint_ - srcPoint_); // increment vector
1288 QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1289
1290 QRadialGradient radialGrad(thePoint_, 0.5 * brushWidth_);
1291 setGaussianGradient(radialGrad, QColor(255, 255, 255, 255), opacity_, mOffset_);
1292
1293 // Create gradient brush
1294 BitmapImage bmiTmpClip;
1295 bmiTmpClip.drawRect(trgRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
1296
1297 // Slide texture/pixels of the source image
1298 qreal factor, factorGrad;
1299
1300 for (int yb = bmiTmpClip.top(); yb < bmiTmpClip.bottom(); yb++)
1301 {
1302 for (int xb = bmiTmpClip.left(); xb < bmiTmpClip.right(); xb++)
1303 {
1304 QColor color;
1305 color.setRgba(bmiTmpClip.pixel(xb, yb));
1306 factorGrad = color.alphaF(); // any from r g b a is ok
1307
1308 int xa = xb - factorGrad * delta.x();
1309 int ya = yb - factorGrad * delta.y();
1310
1311 color.setRgba(bmiSource_->pixel(xa, ya));
1312 factor = color.alphaF();
1313
1314 if (factor > 0.0)
1315 {
1316 color.setRed(color.red() / factor);
1317 color.setGreen(color.green() / factor);
1318 color.setBlue(color.blue() / factor);
1319 color.setAlpha(255); // Premultiplied color
1320
1321 color.setRed(color.red()*factorGrad);
1322 color.setGreen(color.green()*factorGrad);
1323 color.setBlue(color.blue()*factorGrad);
1324 color.setAlpha(255 * factorGrad); // Premultiplied color
1325
1326 bmiTmpClip.setPixel(xb, yb, color.rgba());
1327 }
1328 else
1329 {
1330 bmiTmpClip.setPixel(xb, yb, qRgba(255, 255, 255, 0));
1331 }
1332 }
1333 }
1334 mTiledBuffer.drawImage(*bmiTmpClip.image(), bmiTmpClip.bounds(), QPainter::CompositionMode_SourceOver, mPrefs->isOn(SETTING::ANTIALIAS));
1335}
1336
1337/************************************************************************************/
1338// view handling
1339
1340QPointF ScribbleArea::getCentralPoint()
1341{
1342 return mEditor->view()->mapScreenToCanvas(QPointF(width() / 2, height() / 2));
1343}
1344
1345void ScribbleArea::applyTransformedSelection()
1346{
1347 mCanvasPainter.ignoreTransformedSelection();
1348
1349 Layer* layer = mEditor->layers()->currentLayer();
1350
1351 bool useAA = mEditor->tools()->getTool(ToolType::MOVE)->toolProperties().getInfo(TransformToolProperties::ANTI_ALIASING_ENABLED).boolValue();
1352
1353 if (layer == nullptr) { return; }
1354
1355 auto selectMan = mEditor->select();
1356 if (selectMan->somethingSelected())
1357 {
1358 if (selectMan->mySelectionRect().isEmpty() || selectMan->selectionTransform().isIdentity()) { return; }
1359
1360 if (layer->type() == Layer::BITMAP)
1361 {
1362 handleDrawingOnEmptyFrame();
1363 BitmapImage* bitmapImage = currentBitmapImage(layer);
1364 if (bitmapImage == nullptr) { return; }
1365 BitmapImage transformedImage = bitmapImage->transformed(selectMan->mySelectionRect().toRect(), selectMan->selectionTransform(), useAA);
1366
1367
1368 bitmapImage->clear(selectMan->mySelectionRect());
1369 bitmapImage->paste(&transformedImage, QPainter::CompositionMode_SourceOver);
1370 }
1371 else if (layer->type() == Layer::VECTOR)
1372 {
1373 // Unfortunately this doesn't work right currently so vector transforms
1374 // will always be applied on the previous keyframe when on an empty frame
1375 //handleDrawingOnEmptyFrame();
1376 VectorImage* vectorImage = currentVectorImage(layer);
1377 if (vectorImage == nullptr) { return; }
1378
1379 vectorImage->applySelectionTransformation();
1380 }
1381
1382 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1383 }
1384
1385 updateFrame();
1386}
1387
1388void ScribbleArea::cancelTransformedSelection()
1389{
1390 mCanvasPainter.ignoreTransformedSelection();
1391
1392 auto selectMan = mEditor->select();
1393 if (selectMan->somethingSelected())
1394 {
1395 Layer* layer = mEditor->layers()->currentLayer();
1396 if (layer == nullptr) { return; }
1397
1398 if (layer->type() == Layer::VECTOR)
1399 {
1400 VectorImage* vectorImage = currentVectorImage(layer);
1401 if (vectorImage != nullptr)
1402 {
1403 vectorImage->setSelectionTransformation(QTransform());
1404 }
1405 }
1406
1407 mEditor->select()->setSelection(selectMan->mySelectionRect(), false);
1408
1409 selectMan->resetSelectionProperties();
1410 mOriginalPolygonF = QPolygonF();
1411
1412 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1413 updateFrame();
1414 }
1415}
1416
1417void ScribbleArea::toggleThinLines()
1418{
1419 bool previousValue = mPrefs->isOn(SETTING::INVISIBLE_LINES);
1420 setEffect(SETTING::INVISIBLE_LINES, !previousValue);
1421}
1422
1423void ScribbleArea::setLayerVisibility(LayerVisibility visibility)
1424{
1425 mLayerVisibility = visibility;
1426 mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1427
1428 invalidateAllCache();
1429}
1430
1431void ScribbleArea::increaseLayerVisibilityIndex()
1432{
1433 ++mLayerVisibility;
1434 mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1435
1436 invalidateAllCache();
1437}
1438
1439void ScribbleArea::decreaseLayerVisibilityIndex()
1440{
1441 --mLayerVisibility;
1442 mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1443
1444 invalidateAllCache();
1445}
1446
1447/************************************************************************************/
1448// tool handling
1449
1450BaseTool* ScribbleArea::currentTool() const
1451{
1452 return editor()->tools()->currentTool();
1453}
1454
1455void ScribbleArea::deleteSelection()
1456{
1457 auto selectMan = mEditor->select();
1458 if (selectMan->somethingSelected()) // there is something selected
1459 {
1460 Layer* layer = mEditor->layers()->currentLayer();
1461 if (layer == nullptr) { return; }
1462
1463 handleDrawingOnEmptyFrame();
1464
1465 mEditor->backup(tr("Delete Selection", "Undo Step: clear the selection area."));
1466
1467 selectMan->clearCurves();
1468 if (layer->type() == Layer::VECTOR)
1469 {
1470 VectorImage* vectorImage = currentVectorImage(layer);
1471 Q_CHECK_PTR(vectorImage);
1472 vectorImage->deleteSelection();
1473 }
1474 else if (layer->type() == Layer::BITMAP)
1475 {
1476 BitmapImage* bitmapImage = currentBitmapImage(layer);
1477 Q_CHECK_PTR(bitmapImage);
1478 bitmapImage->clear(selectMan->mySelectionRect());
1479 }
1480 mEditor->setModified(mEditor->currentLayerIndex(), mEditor->currentFrame());
1481 }
1482}
1483
1484void ScribbleArea::clearImage()
1485{
1486 Layer* layer = mEditor->layers()->currentLayer();
1487 if (layer == nullptr) { return; }
1488
1489 if (layer->type() == Layer::VECTOR)
1490 {
1491 mEditor->backup(tr("Clear Image", "Undo step text"));
1492
1493 VectorImage* vectorImage = currentVectorImage(layer);
1494 if (vectorImage != nullptr)
1495 {
1496 vectorImage->clear();
1497 }
1498 mEditor->select()->clearCurves();
1499 mEditor->select()->clearVertices();
1500 }
1501 else if (layer->type() == Layer::BITMAP)
1502 {
1503 mEditor->backup(tr("Clear Image", "Undo step text"));
1504
1505 BitmapImage* bitmapImage = currentBitmapImage(layer);
1506 if (bitmapImage == nullptr) return;
1507 bitmapImage->clear();
1508 }
1509 else
1510 {
1511 return; // skip updates when nothing changes
1512 }
1513 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1514}
1515
1516void ScribbleArea::paletteColorChanged(QColor color)
1517{
1518 Q_UNUSED(color)
1519
1520 for (int i = 0; i < mEditor->layers()->count(); i++)
1521 {
1522 Layer* layer = mEditor->layers()->getLayer(i);
1523 if (layer->type() == Layer::VECTOR)
1524 {
1525 VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getVectorImageAtFrame(mEditor->currentFrame());
1526 if (vectorImage != nullptr)
1527 {
1528 vectorImage->modification();
1529 }
1530 }
1531 }
1532
1533 invalidateAllCache();
1534}
BaseTool
Definition: basetool.h:47
BitmapImage
Definition: bitmapimage.h:28
BlitRect
Definition: blitrect.h:25
Editor::addNewKey
KeyFrame * addNewKey()
Attempts to create a new keyframe at the current frame and layer.
Definition: editor.cpp:899
Editor::frameModified
void frameModified(int frameNumber)
This should be emitted after modifying the frame content.
KeyFrame
Definition: keyframe.h:30
LayerBitmap
Definition: layerbitmap.h:26
Layer
Definition: layer.h:33
Layer::dirtyFrames
QList< int > dirtyFrames() const
Returns a list of dirty frame positions.
Definition: layer.h:166
Layer::addKeyFrame
virtual bool addKeyFrame(int position, KeyFrame *pKeyFrame)
Adds a keyframe at the given position, unless one already exists.
Definition: layer.cpp:191
Layer::clearDirtyFrames
void clearDirtyFrames()
Clear the list of dirty keyframes.
Definition: layer.h:173
LayerVector
Definition: layervector.h:26
Object
Definition: object.h:42
PointerEvent
Definition: pointerevent.h:8
PointerEvent::button
Qt::MouseButton button() const
Returns Qt::MouseButton()
Definition: pointerevent.cpp:38
PointerEvent::buttons
Qt::MouseButtons buttons() const
Returns Qt::MouseButtons()
Definition: pointerevent.cpp:54
ScribbleArea::onScrubbed
void onScrubbed(int frameNumber)
Frame scrubbed, invalidate relevant cache.
Definition: scribblearea.cpp:315
ScribbleArea::onViewChanged
void onViewChanged()
View updated, invalidate relevant cache.
Definition: scribblearea.cpp:341
ScribbleArea::onObjectLoaded
void onObjectLoaded()
Object updated, invalidate all cache.
Definition: scribblearea.cpp:363
ScribbleArea::invalidateCacheForDirtyFrames
void invalidateCacheForDirtyFrames()
invalidate cache for dirty keyframes.
Definition: scribblearea.cpp:209
ScribbleArea::updateFrame
void updateFrame()
Update frame.
Definition: scribblearea.cpp:200
ScribbleArea::handleDrawingOnEmptyFrame
void handleDrawingOnEmptyFrame()
Call this when starting to use a paint tool.
Definition: scribblearea.cpp:824
ScribbleArea::invalidatePainterCaches
void invalidatePainterCaches()
Invalidate the layer pixmap and camera painter caches.
Definition: scribblearea.cpp:284
ScribbleArea::invalidateOnionSkinsCacheAround
void invalidateOnionSkinsCacheAround(int frame)
invalidate onion skin cache around frame
Definition: scribblearea.cpp:220
ScribbleArea::onSelectionChanged
void onSelectionChanged()
Selection was changed, keep cache.
Definition: scribblearea.cpp:351
ScribbleArea::onOnionSkinTypeChanged
void onOnionSkinTypeChanged()
Onion skin type changed, all frames will be affected.
Definition: scribblearea.cpp:358
ScribbleArea::onFramesModified
void onFramesModified()
Multiple frames modified, invalidate cache for affected frames.
Definition: scribblearea.cpp:322
ScribbleArea::onToolChanged
void onToolChanged(ToolType)
Tool changed, invalidate cache and frame if needed.
Definition: scribblearea.cpp:291
ScribbleArea::onPlayStateChanged
void onPlayStateChanged()
Playstate changed, invalidate relevant cache.
Definition: scribblearea.cpp:301
ScribbleArea::invalidateAllCache
void invalidateAllCache()
Invalidate all cache.
Definition: scribblearea.cpp:261
ScribbleArea::invalidateCacheForFrame
void invalidateCacheForFrame(int frameNumber)
Invalidate cache for the given frame.
Definition: scribblearea.cpp:273
ScribbleArea::onLayerChanged
void onLayerChanged()
Layer changed, invalidate relevant cache.
Definition: scribblearea.cpp:346
ScribbleArea::onFrameModified
void onFrameModified(int frameNumber)
Frame modified, invalidate cache for frame if any.
Definition: scribblearea.cpp:331
SelectionManager
The SelectionManager class acts as the "Brain" of the selection system.
Definition: selectionmanager.h:51
SelectionManager::flipSelection
void flipSelection(bool flipVertical)
ScribbleArea::flipSelection flip selection along the X or Y axis.
Definition: selectionmanager.cpp:328
SelectionManager::setSelection
void setSelection(QRectF rect, bool roundPixels=false)
Defines the selection area.
Definition: selectionmanager.cpp:273
Tile
Definition: tile.h:24
TiledBuffer
Definition: tiledbuffer.h:50
TiledBuffer::drawPath
void drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
Draws a path with the specified parameters to the tiled buffer.
Definition: tiledbuffer.cpp:128
TiledBuffer::clear
void clear()
Clears the content of the tiled buffer.
Definition: tiledbuffer.cpp:161
TiledBuffer::drawImage
void drawImage(const QImage &image, const QRect &imageBounds, QPainter::CompositionMode cm, bool antialiasing)
Draws a image with the specified parameters to the tiled buffer.
Definition: tiledbuffer.cpp:99
TiledBuffer::drawBrush
void drawBrush(QPointF point, qreal brushWidth, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
Draws a brush with the specified parameters to the tiled buffer.
Definition: tiledbuffer.cpp:51
TransformTool
Definition: transformtool.h:23
VectorImage
Definition: vectorimage.h:32
VectorImage::getVertex
QPointF getVertex(int curveNumber, int vertexNumber)
VectorImage::getVertex.
Definition: vectorimage.cpp:1566
VectorImage::clear
void clear()
VectorImage::clear.
Definition: vectorimage.cpp:1249
VectorImage::applySelectionTransformation
void applySelectionTransformation()
VectorImage::applySelectionTransformation.
Definition: vectorimage.cpp:1274
VectorImage::setSelectionTransformation
void setSelectionTransformation(QTransform transform)
VectorImage::setSelectionTransformation.
Definition: vectorimage.cpp:897
VectorImage::getCurveSize
int getCurveSize(int curveNumber)
VectorImage::getCurveSize.
Definition: vectorimage.cpp:1704
VectorImage::deleteSelection
void deleteSelection()
VectorImage::deleteSelection.
Definition: vectorimage.cpp:906
VectorSelection
Definition: vectorselection.h:26
VertexRef
Definition: vertexref.h:22
ViewManager
Definition: viewmanager.h:26
ViewManager::getScaleInversed
qreal getScaleInversed() const
Definition: viewmanager.cpp:111
QBrush
QColor
QColor::alphaF
qreal alphaF() const const
QColor::blue
int blue() const const
QColor::green
int green() const const
QColor::red
int red() const const
QColor::rgba
QRgb rgba() const const
QColor::setAlpha
void setAlpha(int alpha)
QColor::setBlue
void setBlue(int blue)
QColor::setGreen
void setGreen(int green)
QColor::setRed
void setRed(int red)
QColor::setRgba
void setRgba(QRgb rgba)
QEnterEvent
QEvent
QEvent::WindowDeactivate
WindowDeactivate
QEvent::ignore
void ignore()
QEvent::type
QEvent::Type type() const const
QGradient
QGradient::setColorAt
void setColorAt(qreal position, const QColor &color)
QGuiApplication::platformName
platformName
QKeyEvent
QKeyEvent::isAutoRepeat
bool isAutoRepeat() const const
QKeyEvent::key
int key() const const
QKeyEvent::modifiers
Qt::KeyboardModifiers modifiers() const const
QLineF
QLineF::length
qreal length() const const
QList
QList::at
const T & at(int i) const const
QList::size
int size() const const
QMap::clear
void clear()
QMap::end
QMap::iterator end()
QMap::find
QMap::iterator find(const Key &key)
QMap::remove
int remove(const Key &key)
QMessageBox::Ok
Ok
QMessageBox::warning
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
QMouseEvent
QMouseEvent::buttons
Qt::MouseButtons buttons() const const
QMouseEvent::localPos
const QPointF & localPos() const const
QObject::connect
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::tr
QString tr(const char *sourceText, const char *disambiguation, int n)
QPaintDevice::devicePixelRatioF
qreal devicePixelRatioF() const const
QPainter
QPainter::CompositionMode
CompositionMode
QPainter::Antialiasing
Antialiasing
QPainterPath
QPainterPath::boundingRect
QRectF boundingRect() const const
QPaintEvent
QPaintEvent::rect
const QRect & rect() const const
QPen
QPixmap
QPixmap::rect
QRect rect() const const
QPixmap::setDevicePixelRatio
void setDevicePixelRatio(qreal scaleFactor)
QPixmapCache::find
QPixmap * find(const QString &key)
QPixmapCache::clear
void clear()
QPixmapCache::insert
bool insert(const QString &key, const QPixmap &pixmap)
QPixmapCache::remove
void remove(const QString &key)
QPixmapCache::setCacheLimit
void setCacheLimit(int n)
QPoint
QPoint::isNull
bool isNull() const const
QPoint::y
int y() const const
QPointF
QPointF::toPoint
QPoint toPoint() const const
QPointF::x
qreal x() const const
QPointF::y
qreal y() const const
QPolygonF
QRadialGradient
QRect
QRect::adjusted
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
QRect::moveTo
void moveTo(int x, int y)
QRectF
QRectF::toAlignedRect
QRect toAlignedRect() const const
QRectF::toRect
QRect toRect() const const
QResizeEvent
QSizeF
QSizePolicy
QSizePolicy::MinimumExpanding
MinimumExpanding
Qt::NoBrush
NoBrush
Qt::black
black
Qt::Key_Right
Key_Right
Qt::ControlModifier
ControlModifier
Qt::LeftButton
LeftButton
Qt::RoundCap
RoundCap
Qt::RoundJoin
RoundJoin
Qt::SolidLine
SolidLine
Qt::WA_StaticContents
WA_StaticContents
QTabletEvent
QTabletEvent::Eraser
Eraser
QTabletEvent::posF
const QPointF & posF() const const
QTimer
QTimer::setInterval
void setInterval(int msec)
QTimer::isActive
bool isActive() const const
QTimer::singleShot
singleShot
QTimer::start
void start(int msec)
QTimer::stop
void stop()
QTimer::timeout
void timeout()
QTransform
QTransform::mapRect
QRect mapRect(const QRect &rectangle) const const
QWheelEvent::posF
const QPointF & posF() const const
QWheelEvent
QWheelEvent::buttons
Qt::MouseButtons buttons() const const
QWheelEvent::position
QPointF position() const const
QWidget
QWidget::setCursor
void setCursor(const QCursor &)
QWidget::event
virtual bool event(QEvent *event) override
QWidget::hasFocus
bool hasFocus() const const
QWidget::height
height
QWidget::setMouseTracking
void setMouseTracking(bool enable)
QWidget::palette
palette
QWidget::pos
pos
QWidget::rect
rect
QWidget::resizeEvent
virtual void resizeEvent(QResizeEvent *event)
QWidget::setFocus
void setFocus()
QWidget::size
size
QWidget::setSizePolicy
void setSizePolicy(QSizePolicy)
QWidget::setTabletTracking
void setTabletTracking(bool enable)
QWidget::update
void update()
QWidget::width
width
CanvasPainterOptions
Definition: canvaspainter.h:40
OnionSkinPainterOptions
Definition: onionskinpainteroptions.h:20
OverlayPainterOptions
Definition: overlaypainter.h:12
TransformParameters
Definition: selectionpainter.h:30
Generated on Mon May 4 2026 07:50:47 for Pencil2D by doxygen 1.9.6 based on revision 3ed50cdd696e72315cedf30508d3572536c3876e