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 "pointerevent.h"
27#include "beziercurve.h"
28#include "object.h"
29#include "editor.h"
30#include "layerbitmap.h"
31#include "layervector.h"
32#include "layercamera.h"
33#include "bitmapimage.h"
34#include "vectorimage.h"
35#include "blitrect.h"
36#include "tile.h"
37
38#include "onionskinpainteroptions.h"
39
40#include "colormanager.h"
41#include "toolmanager.h"
42#include "layermanager.h"
43#include "playbackmanager.h"
44#include "viewmanager.h"
45#include "selectionmanager.h"
46#include "overlaymanager.h"
47
48ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent),
49 mCanvasPainter(mCanvas),
50 mCameraPainter(mCanvas)
51{
52 setObjectName("ScribbleArea");
53
54 // Qt::WA_StaticContents ensure that the widget contents are rooted to the top-left corner
55 // and don't change when the widget is resized.
56 setAttribute(Qt::WA_StaticContents);
57}
58
59ScribbleArea::~ScribbleArea()
60{
61}
62
63bool ScribbleArea::init()
64{
65 mPrefs = mEditor->preference();
66 mDoubleClickTimer = new QTimer(this);
67 mMouseFilterTimer = new QTimer(this);
68
69 connect(mPrefs, &PreferenceManager::optionChanged, this, &ScribbleArea::settingUpdated);
70 connect(mEditor->tools(), &ToolManager::toolPropertyChanged, this, &ScribbleArea::onToolPropertyUpdated);
71 connect(mEditor->tools(), &ToolManager::toolChanged, this, &ScribbleArea::onToolChanged);
72
73 connect(mDoubleClickTimer, &QTimer::timeout, this, &ScribbleArea::handleDoubleClick);
74 connect(mMouseFilterTimer, &QTimer::timeout, this, &ScribbleArea::tabletReleaseEventFired);
75
76 connect(mEditor->select(), &SelectionManager::selectionChanged, this, &ScribbleArea::onSelectionChanged);
77 connect(mEditor->select(), &SelectionManager::needDeleteSelection, this, &ScribbleArea::deleteSelection);
78
79 connect(&mTiledBuffer, &TiledBuffer::tileUpdated, this, &ScribbleArea::onTileUpdated);
80 connect(&mTiledBuffer, &TiledBuffer::tileCreated, this, &ScribbleArea::onTileCreated);
81
82 mDoubleClickTimer->setInterval(50);
83 mMouseFilterTimer->setInterval(50);
84
85 const int curveSmoothingLevel = mPrefs->getInt(SETTING::CURVE_SMOOTHING);
86 mCurveSmoothingLevel = curveSmoothingLevel / 20.0; // default value is 1.0
87
88 mMakeInvisible = false;
89
90 mLayerVisibility = static_cast<LayerVisibility>(mPrefs->getInt(SETTING::LAYER_VISIBILITY));
91
92 mDeltaFactor = mEditor->preference()->isOn(SETTING::INVERT_SCROLL_ZOOM_DIRECTION) ? -1 : 1;
93
94 setMouseTracking(true); // reacts to mouse move events, even if the button is not pressed
95#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
96 setTabletTracking(true); // tablet tracking first added in 5.9
97#endif
98
99 setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
100
101 QPixmapCache::setCacheLimit(100 * 1024); // unit is kb, so it's 100MB cache
102 mPixmapCacheKeys.clear();
103
104 return true;
105}
106
107void ScribbleArea::settingUpdated(SETTING setting)
108{
109 switch (setting)
110 {
111 case SETTING::CURVE_SMOOTHING:
112 setCurveSmoothing(mPrefs->getInt(SETTING::CURVE_SMOOTHING));
113 break;
114 case SETTING::TOOL_CURSOR:
115 updateToolCursor();
116 break;
117 case SETTING::ONION_PREV_FRAMES_NUM:
118 case SETTING::ONION_NEXT_FRAMES_NUM:
119 case SETTING::ONION_MIN_OPACITY:
120 case SETTING::ONION_MAX_OPACITY:
121 invalidateAllCache();
122 break;
123 case SETTING::ANTIALIAS:
124 case SETTING::GRID:
125 case SETTING::GRID_SIZE_W:
126 case SETTING::GRID_SIZE_H:
127 case SETTING::OVERLAY_CENTER:
128 case SETTING::OVERLAY_THIRDS:
129 case SETTING::OVERLAY_GOLDEN:
130 case SETTING::OVERLAY_SAFE:
131 case SETTING::OVERLAY_PERSPECTIVE1:
132 case SETTING::OVERLAY_PERSPECTIVE2:
133 case SETTING::OVERLAY_PERSPECTIVE3:
134 case SETTING::ACTION_SAFE_ON:
135 case SETTING::ACTION_SAFE:
136 case SETTING::TITLE_SAFE_ON:
137 case SETTING::TITLE_SAFE:
138 case SETTING::OVERLAY_SAFE_HELPER_TEXT_ON:
139 case SETTING::PREV_ONION:
140 case SETTING::NEXT_ONION:
141 case SETTING::ONION_BLUE:
142 case SETTING::ONION_RED:
143 case SETTING::INVISIBLE_LINES:
144 case SETTING::OUTLINES:
145 case SETTING::ONION_TYPE:
146 case SETTING::ONION_WHILE_PLAYBACK:
147 invalidateAllCache();
148 break;
149 case SETTING::ONION_MUTLIPLE_LAYERS:
150 invalidateAllCache();
151 break;
152 case SETTING::LAYER_VISIBILITY_THRESHOLD:
153 case SETTING::LAYER_VISIBILITY:
154 setLayerVisibility(static_cast<LayerVisibility>(mPrefs->getInt(SETTING::LAYER_VISIBILITY)));
155 break;
156 case SETTING::INVERT_SCROLL_ZOOM_DIRECTION:
157 mDeltaFactor = mEditor->preference()->isOn(SETTING::INVERT_SCROLL_ZOOM_DIRECTION) ? -1 : 1;
158 break;
159 default:
160 break;
161 }
162
163}
164
165void ScribbleArea::updateToolCursor()
166{
167 setCursor(currentTool()->cursor());
168}
169
170void ScribbleArea::setCurveSmoothing(int newSmoothingLevel)
171{
172 mCurveSmoothingLevel = newSmoothingLevel / 20.0;
173 invalidatePainterCaches();
174}
175
176void ScribbleArea::setEffect(SETTING e, bool isOn)
177{
178 mPrefs->set(e, isOn);
179 invalidatePainterCaches();
180}
181
182/************************************************************************************/
183// update methods
184
185void ScribbleArea::onTileUpdated(TiledBuffer* tiledBuffer, Tile* tile)
186{
187 Q_UNUSED(tiledBuffer);
188 const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds()));
189 update(mappedRect.toAlignedRect());
190}
191
192void ScribbleArea::onTileCreated(TiledBuffer* tiledBuffer, Tile* tile)
193{
194 Q_UNUSED(tiledBuffer)
195 const QRectF& mappedRect = mEditor->view()->getView().mapRect(QRectF(tile->bounds()));
196 update(mappedRect.toAlignedRect());
197}
198
199void ScribbleArea::updateFrame()
200{
201 if (currentTool()->isActive() && currentTool()->isDrawingTool()) {
202 return;
203 }
204
205 update();
206}
207
208void ScribbleArea::invalidateCacheForDirtyFrames()
209{
210 Layer* currentLayer = mEditor->layers()->currentLayer();
211 for (int pos : currentLayer->dirtyFrames()) {
212
213 invalidateCacheForFrame(pos);
214 invalidateOnionSkinsCacheAround(pos);
215 }
216 currentLayer->clearDirtyFrames();
217}
218
219void ScribbleArea::invalidateOnionSkinsCacheAround(int frameNumber)
220{
221 if (frameNumber < 0) { return; }
222
223 bool isOnionAbsolute = mPrefs->getString(SETTING::ONION_TYPE) == "absolute";
224 Layer *layer = mEditor->layers()->currentLayer(0);
225
226 // The current layer can be null if updateFrame is triggered when creating a new project
227 if (!layer) return;
228
229 if (mPrefs->isOn(SETTING::PREV_ONION))
230 {
231 int onionFrameNumber = frameNumber;
232 if (isOnionAbsolute)
233 {
234 onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber + 1, true);
235 }
236
237 for(int i = 1; i <= mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM); i++)
238 {
239 onionFrameNumber = layer->getPreviousFrameNumber(onionFrameNumber, isOnionAbsolute);
240 if (onionFrameNumber < 0) break;
241
242 invalidateCacheForFrame(onionFrameNumber);
243 }
244 }
245
246 if (mPrefs->isOn(SETTING::NEXT_ONION))
247 {
248 int onionFrameNumber = frameNumber;
249
250 for(int i = 1; i <= mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM); i++)
251 {
252 onionFrameNumber = layer->getNextFrameNumber(onionFrameNumber, isOnionAbsolute);
253 if (onionFrameNumber < 0) break;
254
255 invalidateCacheForFrame(onionFrameNumber);
256 }
257 }
258}
259
260void ScribbleArea::invalidateAllCache()
261{
262 if (currentTool()->isDrawingTool() && currentTool()->isActive()) { return; }
263
264 QPixmapCache::clear();
265 mPixmapCacheKeys.clear();
266 invalidatePainterCaches();
267 mEditor->layers()->currentLayer()->clearDirtyFrames();
268
269 updateFrame();
270}
271
272void ScribbleArea::invalidateCacheForFrame(int frameNumber)
273{
274 auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned int>(frameNumber));
275 if (cacheKeyIter != mPixmapCacheKeys.end())
276 {
277 QPixmapCache::remove(cacheKeyIter.value());
278 unsigned int key = cacheKeyIter.key();
279 mPixmapCacheKeys.remove(key);
280 }
281}
282
283void ScribbleArea::invalidatePainterCaches()
284{
285 mCameraPainter.resetCache();
286 mCanvasPainter.resetLayerCache();
287 updateFrame();
288}
289
290void ScribbleArea::onToolPropertyUpdated(ToolType, ToolPropertyType type)
291{
292 switch (type)
293 {
294 case ToolPropertyType::CAMERAPATH:
295 onFrameModified(mEditor->currentFrame());
296 break;
297 default:
298 break;
299 }
300}
301
302void ScribbleArea::onToolChanged(ToolType)
303{
304 int frame = mEditor->currentFrame();
305 prepOverlays(frame);
306 prepCameraPainter(frame);
307 invalidateCacheForFrame(frame);
308 updateFrame();
309}
310
311
312void ScribbleArea::onPlayStateChanged()
313{
314 int currentFrame = mEditor->currentFrame();
315 if (mPrefs->isOn(SETTING::PREV_ONION) ||
316 mPrefs->isOn(SETTING::NEXT_ONION)) {
317 invalidatePainterCaches();
318 }
319
320 prepOverlays(currentFrame);
321 prepCameraPainter(currentFrame);
322 invalidateCacheForFrame(currentFrame);
323 updateFrame();
324}
325
326void ScribbleArea::onScrubbed(int frameNumber)
327{
328 Q_UNUSED(frameNumber)
329 invalidatePainterCaches();
330 updateFrame();
331}
332
333void ScribbleArea::onFramesModified()
334{
335 invalidateCacheForDirtyFrames();
336 if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) {
337 invalidatePainterCaches();
338 }
339 updateFrame();
340}
341
342void ScribbleArea::onFrameModified(int frameNumber)
343{
344 if (mPrefs->isOn(SETTING::PREV_ONION) || mPrefs->isOn(SETTING::NEXT_ONION)) {
345 invalidateOnionSkinsCacheAround(frameNumber);
346 invalidatePainterCaches();
347 }
348 invalidateCacheForFrame(frameNumber);
349 updateFrame();
350}
351
352void ScribbleArea::onViewChanged()
353{
354 invalidateAllCache();
355}
356
357void ScribbleArea::onLayerChanged()
358{
359 invalidateAllCache();
360}
361
362void ScribbleArea::onSelectionChanged()
363{
364 int currentFrame = mEditor->currentFrame();
365 invalidateCacheForFrame(currentFrame);
366 updateFrame();
367}
368
369void ScribbleArea::onOnionSkinTypeChanged()
370{
371 invalidateAllCache();
372}
373
374void ScribbleArea::onObjectLoaded()
375{
376 invalidateAllCache();
377}
378
379bool ScribbleArea::event(QEvent *event)
380{
381 bool processed = false;
382 if (event->type() == QEvent::WindowDeactivate)
383 {
384 editor()->tools()->clearTemporaryTool();
385 processed = true;
386 } else if (event->type() == QEvent::Enter)
387 {
388 processed = currentTool()->enterEvent(static_cast<QEnterEvent*>(event)) || processed;
389 } else if (event->type() == QEvent::Leave)
390 {
391 processed = currentTool()->leaveEvent(event) || processed;
392 }
393
394 return QWidget::event(event) || processed;
395}
396
397/************************************************************************/
398/* key event handlers */
399/************************************************************************/
400
401void ScribbleArea::keyPressEvent(QKeyEvent *event)
402{
403 // Don't handle this event on auto repeat
404 if (event->isAutoRepeat()) { return; }
405
406 if (isPointerInUse()) { return; } // prevents shortcuts calls while drawing
407
408 if (currentTool()->keyPressEvent(event))
409 {
410 return; // has been handled by tool
411 }
412
413 // --- fixed control key shortcuts ---
414 if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier) &&
415 editor()->tools()->setTemporaryTool(ERASER, {}, event->modifiers()))
416 {
417 return;
418 }
419
420 // ---- fixed normal keys ----
421
422 auto selectMan = mEditor->select();
423 bool isSomethingSelected = selectMan->somethingSelected();
424 if (isSomethingSelected)
425 {
426 keyEventForSelection(event);
427 }
428 else
429 {
430 keyEvent(event);
431 }
432}
433
434void ScribbleArea::keyEventForSelection(QKeyEvent* event)
435{
436 auto selectMan = mEditor->select();
437 switch (event->key())
438 {
439 case Qt::Key_Right:
440 selectMan->translate(QPointF(1, 0));
441 selectMan->calculateSelectionTransformation();
442 emit mEditor->frameModified(mEditor->currentFrame());
443 return;
444 case Qt::Key_Left:
445 selectMan->translate(QPointF(-1, 0));
446 selectMan->calculateSelectionTransformation();
447 emit mEditor->frameModified(mEditor->currentFrame());
448 return;
449 case Qt::Key_Up:
450 selectMan->translate(QPointF(0, -1));
451 selectMan->calculateSelectionTransformation();
452 emit mEditor->frameModified(mEditor->currentFrame());
453 return;
454 case Qt::Key_Down:
455 selectMan->translate(QPointF(0, 1));
456 selectMan->calculateSelectionTransformation();
457 emit mEditor->frameModified(mEditor->currentFrame());
458 return;
459 case Qt::Key_Return:
460 applyTransformedSelection();
461 mEditor->deselectAll();
462 return;
463 case Qt::Key_Escape:
464 cancelTransformedSelection();
465 mEditor->deselectAll();
466 return;
467 case Qt::Key_Backspace:
468 deleteSelection();
469 mEditor->deselectAll();
470 return;
471 case Qt::Key_Space:
472 if (editor()->tools()->setTemporaryTool(HAND, Qt::Key_Space, Qt::NoModifier))
473 {
474 return;
475 }
476 break;
477 default:
478 break;
479 }
480 event->ignore();
481}
482
483void ScribbleArea::keyEvent(QKeyEvent* event)
484{
485 switch (event->key())
486 {
487 case Qt::Key_Right:
488 mEditor->scrubForward();
489 break;
490 case Qt::Key_Left:
491 mEditor->scrubBackward();
492 break;
493 case Qt::Key_Up:
494 mEditor->layers()->gotoNextLayer();
495 break;
496 case Qt::Key_Down:
497 mEditor->layers()->gotoPreviouslayer();
498 break;
499 case Qt::Key_Space:
500 if(editor()->tools()->setTemporaryTool(HAND, Qt::Key_Space, Qt::NoModifier))
501 {
502 return;
503 }
504 break;
505 default:
506 break;
507 }
508 event->ignore();
509}
510
511void ScribbleArea::keyReleaseEvent(QKeyEvent *event)
512{
513 // Don't handle this event on auto repeat
514 //
515 if (event->isAutoRepeat()) {
516 return;
517 }
518
519 if (event->key() == 0)
520 {
521 editor()->tools()->tryClearTemporaryTool(Qt::Key_unknown);
522 }
523 else
524 {
525 editor()->tools()->tryClearTemporaryTool(static_cast<Qt::Key>(event->key()));
526 }
527
528 if (isPointerInUse()) { return; }
529
530 if (currentTool()->keyReleaseEvent(event))
531 {
532 // has been handled by tool
533 return;
534 }
535}
536
537/************************************************************************************/
538// mouse and tablet event handlers
539void ScribbleArea::wheelEvent(QWheelEvent* event)
540{
541 // Don't change view if the tool is in use
542 if (isPointerInUse()) return;
543
544 static const bool isX11 = QGuiApplication::platformName() == "xcb";
545 const QPoint pixels = event->pixelDelta();
546 const QPoint angle = event->angleDelta();
547#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
548 const QPointF offset = mEditor->view()->mapScreenToCanvas(event->position());
549#else
550 const QPointF offset = mEditor->view()->mapScreenToCanvas(event->posF());
551#endif
552
553 const qreal currentScale = mEditor->view()->scaling();
554
555 // From the pixelDelta documentation: On X11 this value is driver-specific and unreliable, use angleDelta() instead
556 int delta = 0;
557 if (!isX11 && !pixels.isNull())
558 {
559 delta = pixels.y();
560 }
561 else if (!angle.isNull()) // Wheel based delta
562 {
563 delta = angle.y();
564 }
565
566 if (delta != 0) {
567 const qreal newScale = currentScale * std::pow(100, (delta * mDeltaFactor) / (12.0 * 120));
568 mEditor->view()->scaleAtOffset(newScale, offset);
569 }
570 event->accept();
571}
572
573void ScribbleArea::tabletEvent(QTabletEvent *e)
574{
575 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->posF()));
576
577#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
578 if (event.pointerType() == QPointingDevice::PointerType::Eraser)
579#else
580 if (event.pointerType() == QTabletEvent::Eraser)
581#endif
582 {
583 editor()->tools()->tabletSwitchToEraser();
584 }
585 else
586 {
587 editor()->tools()->tabletRestorePrevTool();
588 }
589
590 if (event.eventType() == PointerEvent::Press)
591 {
592 event.accept();
593 if (mIsFirstClick)
594 {
595 mIsFirstClick = false;
596 mDoubleClickTimer->start();
597 pointerPressEvent(&event);
598 }
599 else
600 {
601 qreal distance = QLineF(e->posF(), mTabletPressPos).length();
602 mTabletPressPos = e->posF();
603
604 if (mDoubleClickMillis <= DOUBLE_CLICK_THRESHOLD && distance < 5.0) {
605 currentTool()->pointerDoubleClickEvent(&event);
606 }
607 else
608 {
609 // in case we handled the event as double click but really should have handled it as single click.
610 pointerPressEvent(&event);
611 }
612 }
613 mTabletInUse = event.isAccepted();
614 }
615 else if (event.eventType() == PointerEvent::Move)
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 }
632
633#if defined Q_OS_LINUX
634 // Generate mouse events on linux to work around bug where the
635 // widget will not receive mouseEnter/mouseLeave
636 // events and the cursor will not update correctly.
637 // See https://codereview.qt-project.org/c/qt/qtbase/+/255384
638 // Scribblearea should not do anything with the mouse event when mTabletInUse is true.
639 event.ignore();
640#else
641 // Always accept so that mouse events are not generated (theoretically)
642 // Unfortunately Windows sometimes generates the events anyway
643 // As long as mTabletInUse is true, mouse events *should* be ignored even when
644 // the are generated
645 event.accept();
646#endif
647}
648
649void ScribbleArea::pointerPressEvent(PointerEvent* event)
650{
651 bool isCameraLayer = mEditor->layers()->currentLayer()->type() == Layer::CAMERA;
652 if ((currentTool()->type() != HAND || isCameraLayer) && (event->button() != Qt::RightButton) && (event->button() != Qt::MiddleButton || isCameraLayer))
653 {
654 Layer* layer = mEditor->layers()->currentLayer();
655 if (!layer->visible())
656 {
657 event->ignore();
658 // This needs to be async so that mTabletInUse is set to false before
659 // further events are created (modal dialogs do not currently block tablet events)
660 QTimer::singleShot(0, this, &ScribbleArea::showLayerNotVisibleWarning);
661 return;
662 }
663 }
664
665 if (event->buttons() & (Qt::MiddleButton | Qt::RightButton) &&
666 editor()->tools()->setTemporaryTool(HAND, event->buttons()))
667 {
668 currentTool()->pointerPressEvent(event);
669 } else if (event->button() == Qt::LeftButton)
670 {
671 currentTool()->pointerPressEvent(event);
672 }
673}
674
675void ScribbleArea::pointerMoveEvent(PointerEvent* event)
676{
677 currentTool()->pointerMoveEvent(event);
678}
679
680void ScribbleArea::pointerReleaseEvent(PointerEvent* event)
681{
682 currentTool()->pointerReleaseEvent(event);
683
684 editor()->tools()->tryClearTemporaryTool(event->button());
685}
686
687void ScribbleArea::handleDoubleClick()
688{
689 mDoubleClickMillis += 100;
690
691 if (mDoubleClickMillis >= DOUBLE_CLICK_THRESHOLD)
692 {
693 mDoubleClickMillis = 0;
694 mIsFirstClick = true;
695 mDoubleClickTimer->stop();
696 }
697}
698
699void ScribbleArea::tabletReleaseEventFired()
700{
701 // Under certain circumstances, a mouse press event will fire after a tablet release event.
702 // This causes unexpected behaviors for some tools, e.g., the bucket tool.
703 // The problem only seems to occur on Windows and only when tapping.
704 // Prior to this fix, the event queue would look like this:
705 // e.g.: TabletPress -> TabletRelease -> MousePress
706 // The following will filter mouse events created after a tablet release event.
707 mTabletReleaseMillisAgo += 50;
708
709 if (mTabletReleaseMillisAgo >= MOUSE_FILTER_THRESHOLD) {
710 mTabletReleaseMillisAgo = 0;
711 mMouseFilterTimer->stop();
712 }
713}
714
715void ScribbleArea::mousePressEvent(QMouseEvent* e)
716{
717 if (mTabletInUse || (mMouseFilterTimer->isActive() && mTabletReleaseMillisAgo < MOUSE_FILTER_THRESHOLD))
718 {
719 e->ignore();
720 return;
721 }
722
723 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
724 pointerPressEvent(&event);
725 mMouseInUse = event.isAccepted();
726}
727
728void ScribbleArea::mouseMoveEvent(QMouseEvent* e)
729{
730 if (mTabletInUse || (mMouseFilterTimer->isActive() && mTabletReleaseMillisAgo < MOUSE_FILTER_THRESHOLD)) { e->ignore(); return; }
731
732 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
733 pointerMoveEvent(&event);
734}
735
736void ScribbleArea::mouseReleaseEvent(QMouseEvent* e)
737{
738 if (mTabletInUse || (mMouseFilterTimer->isActive() && mTabletReleaseMillisAgo < MOUSE_FILTER_THRESHOLD)) { e->ignore(); return; }
739
740 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
741 pointerReleaseEvent(&event);
742 mMouseInUse = (e->buttons() & Qt::RightButton) || (e->buttons() & Qt::LeftButton);
743}
744
745void ScribbleArea::mouseDoubleClickEvent(QMouseEvent* e)
746{
747 if (mTabletInUse) { e->ignore(); return; }
748
749 PointerEvent event(e, mEditor->view()->mapScreenToCanvas(e->localPos()));
750 currentTool()->pointerDoubleClickEvent(&event);
751}
752
753void ScribbleArea::resizeEvent(QResizeEvent* event)
754{
755 QWidget::resizeEvent(event);
756 mDevicePixelRatio = devicePixelRatioF();
757 mCanvas = QPixmap(QSizeF(size() * mDevicePixelRatio).toSize());
758 mCanvas.setDevicePixelRatio(mDevicePixelRatio);
759 mEditor->view()->setCanvasSize(size());
760
761 invalidateCacheForFrame(mEditor->currentFrame());
762 invalidatePainterCaches();
763 mCanvasPainter.reset();
764 mCameraPainter.reset();
765}
766
767void ScribbleArea::showLayerNotVisibleWarning()
768{
769 QMessageBox::warning(this, tr("Warning"),
770 tr("You are trying to modify a hidden layer! Please select another layer (or make the current layer visible)."),
771 QMessageBox::Ok,
772 QMessageBox::Ok);
773}
774
775void ScribbleArea::paintBitmapBuffer()
776{
777 LayerBitmap* layer = static_cast<LayerBitmap*>(mEditor->layers()->currentLayer());
778 Q_ASSERT(layer);
779 Q_ASSERT(layer->type() == Layer::BITMAP);
780
781 int frameNumber = mEditor->currentFrame();
782
783 // If there is no keyframe at or before the current position,
784 // just return (since we have nothing to paint on).
785 if (layer->getLastKeyFrameAtPosition(frameNumber) == nullptr)
786 {
787 updateFrame();
788 return;
789 }
790
791 BitmapImage* targetImage = currentBitmapImage(layer);
792 if (targetImage != nullptr)
793 {
794 QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver;
795 switch (currentTool()->type())
796 {
797 case ERASER:
798 cm = QPainter::CompositionMode_DestinationOut;
799 break;
800 case BRUSH:
801 case PEN:
802 case PENCIL:
803 if (currentTool()->properties.preserveAlpha)
804 {
805 cm = QPainter::CompositionMode_SourceOver;
806 }
807 break;
808 default: //nothing
809 break;
810 }
811 targetImage->paste(&mTiledBuffer, cm);
812 }
813
814 QRect rect = mEditor->view()->mapCanvasToScreen(mTiledBuffer.bounds()).toRect();
815
816 update(rect);
817
818 layer->setModified(frameNumber, true);
819 mTiledBuffer.clear();
820}
821
822void ScribbleArea::clearDrawingBuffer()
823{
824 mTiledBuffer.clear();
825}
826
827void ScribbleArea::handleDrawingOnEmptyFrame()
828{
829 auto layer = mEditor->layers()->currentLayer();
830
831 if (!layer || !layer->isPaintable())
832 {
833 return;
834 }
835
836 if (currentTool()->type() == ERASER) {
837 return;
838 }
839
840 int frameNumber = mEditor->currentFrame();
841 if (layer->getKeyFrameAt(frameNumber)) { return; }
842
843 // Drawing on an empty frame; take action based on preference.
844 int action = mPrefs->getInt(SETTING::DRAW_ON_EMPTY_FRAME_ACTION);
845 auto previousKeyFrame = layer->getLastKeyFrameAtPosition(frameNumber);
846 switch (action)
847 {
848 case KEEP_DRAWING_ON_PREVIOUS_KEY:
849 {
850 if (previousKeyFrame == nullptr) {
851 mEditor->addNewKey();
852 } else {
853 onFrameModified(previousKeyFrame->pos());
854 }
855 break;
856 }
857 case DUPLICATE_PREVIOUS_KEY:
858 {
859 if (previousKeyFrame != nullptr) {
860 KeyFrame* dupKey = previousKeyFrame->clone();
861 layer->addKeyFrame(frameNumber, dupKey);
862 mEditor->scrubTo(frameNumber);
863 break;
864 }
865 }
866 // if the previous keyframe doesn't exist,
867 // an empty keyframe needs to be created, so
868 // fallthrough
869 case CREATE_NEW_KEY:
870 mEditor->addNewKey();
871
872 // Refresh canvas
873 drawCanvas(frameNumber, mCanvas.rect());
874 update();
875 break;
876 default:
877 break;
878 }
879}
880
881void ScribbleArea::paintEvent(QPaintEvent* event)
882{
883 int currentFrame = mEditor->currentFrame();
884 if (!currentTool()->isActive())
885 {
886 // --- we retrieve the canvas from the cache; we create it if it doesn't exist
887 const int frameNumber = mEditor->layers()->lastFrameAtFrame(currentFrame);
888
889 if (frameNumber < 0)
890 {
891 drawCanvas(currentFrame, event->rect());
892 }
893 else
894 {
895 auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned>(frameNumber));
896
897 if (cacheKeyIter == mPixmapCacheKeys.end() || !QPixmapCache::find(cacheKeyIter.value(), &mCanvas))
898 {
899 drawCanvas(currentFrame, event->rect());
900 mPixmapCacheKeys[static_cast<unsigned>(currentFrame)] = QPixmapCache::insert(mCanvas);
901 //qDebug() << "Repaint canvas!";
902 }
903 else
904 {
905 // Simply use the cached canvas from PixmapCache
906 }
907 }
908 }
909 else
910 {
911 prepCanvas(currentFrame);
912 prepCameraPainter(currentFrame);
913 prepOverlays(currentFrame);
914
915 mCanvasPainter.paintCached(event->rect());
916 mCameraPainter.paintCached(event->rect());
917 }
918
919 if (currentTool()->type() == MOVE)
920 {
921 Layer* layer = mEditor->layers()->currentLayer();
922 Q_CHECK_PTR(layer);
923 if (layer->type() == Layer::VECTOR)
924 {
925 VectorImage* vectorImage = currentVectorImage(layer);
926 if (vectorImage != nullptr)
927 {
928 vectorImage->setModified(true);
929 }
930 }
931 }
932
933 QPainter painter(this);
934
935 // paints the canvas
936 painter.setWorldMatrixEnabled(false);
937
938 // In other places we use the blitRect to paint the buffer pixmap, however
939 // the main pixmap which needs to be scaled accordingly to DPI, which is not accounted for when using the event rect
940 // instead we can set a clipRect to avoid the area being updated needlessly
941 painter.setClipRect(event->rect());
942 painter.drawPixmap(QPointF(), mCanvas);
943
944 currentTool()->paint(painter, event->rect());
945
946 if (!editor()->playback()->isPlaying()) // we don't need to display the following when the animation is playing
947 {
948 Layer* layer = mEditor->layers()->currentLayer();
949 if (layer->type() == Layer::VECTOR)
950 {
951 VectorImage* vectorImage = currentVectorImage(layer);
952 if (vectorImage != nullptr)
953 {
954 switch (currentTool()->type())
955 {
956 case SMUDGE:
957 case HAND:
958 {
959 auto selectMan = mEditor->select();
960 painter.save();
961 painter.setWorldMatrixEnabled(false);
962 painter.setRenderHint(QPainter::Antialiasing, false);
963 // ----- paints the edited elements
964 QPen pen2(Qt::black, 0.5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
965 painter.setPen(pen2);
966 QColor color;
967 // ------------ vertices of the edited curves
968 color = QColor(200, 200, 200);
969 painter.setBrush(color);
970 VectorSelection vectorSelection = selectMan->vectorSelection;
971 for (int k = 0; k < vectorSelection.curve.size(); k++)
972 {
973 int curveNumber = vectorSelection.curve.at(k);
974
975 for (int vertexNumber = -1; vertexNumber < vectorImage->getCurveSize(curveNumber); vertexNumber++)
976 {
977 QPointF vertexPoint = vectorImage->getVertex(curveNumber, vertexNumber);
978 QRectF rectangle(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
979 if (rect().contains(mEditor->view()->mapCanvasToScreen(vertexPoint).toPoint()))
980 {
981 painter.drawRect(rectangle);
982 }
983 }
984 }
985 // ------------ selected vertices of the edited curves
986 color = QColor(100, 100, 255);
987 painter.setBrush(color);
988 for (int k = 0; k < vectorSelection.vertex.size(); k++)
989 {
990 VertexRef vertexRef = vectorSelection.vertex.at(k);
991 QPointF vertexPoint = vectorImage->getVertex(vertexRef);
992 QRectF rectangle0 = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
993 painter.drawRect(rectangle0);
994 }
995 // ----- paints the closest vertices
996 color = QColor(255, 0, 0);
997 painter.setBrush(color);
998 QList<VertexRef> closestVertices = selectMan->closestVertices();
999 if (vectorSelection.curve.size() > 0)
1000 {
1001 for (int k = 0; k < closestVertices.size(); k++)
1002 {
1003 VertexRef vertexRef = closestVertices.at(k);
1004 QPointF vertexPoint = vectorImage->getVertex(vertexRef);
1005
1006 QRectF rectangle = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
1007 painter.drawRect(rectangle);
1008 }
1009 }
1010 painter.restore();
1011 break;
1012 }
1013 default:
1014 {
1015 break;
1016 }
1017 } // end switch
1018 }
1019 }
1020
1021 mOverlayPainter.paint(painter, rect());
1022
1023 // paints the selection outline
1024 if (mEditor->select()->somethingSelected())
1025 {
1026 paintSelectionVisuals(painter);
1027 }
1028 }
1029
1030 // outlines the frame of the viewport
1031#ifdef _DEBUG
1032 painter.setWorldMatrixEnabled(false);
1033 painter.setPen(QPen(Qt::gray, 2));
1034 painter.setBrush(Qt::NoBrush);
1035 painter.drawRect(QRect(0, 0, width(), height()));
1036#endif
1037
1038 event->accept();
1039}
1040
1041void ScribbleArea::paintSelectionVisuals(QPainter &painter)
1042{
1043 Object* object = mEditor->object();
1044
1045 auto selectMan = mEditor->select();
1046
1047 QRectF currentSelectionRect = selectMan->mySelectionRect();
1048
1049 if (currentSelectionRect.isEmpty()) { return; }
1050
1051 TransformParameters params = { currentSelectionRect, editor()->view()->getView(), selectMan->selectionTransform() };
1052 mSelectionPainter.paint(painter, object, mEditor->currentLayerIndex(), currentTool(), 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(), 0);
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()->currentTool()->properties.useAA;
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:70
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:905
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:326
ScribbleArea::onToolPropertyUpdated
void onToolPropertyUpdated(ToolType, ToolPropertyType)
Tool property updated, invalidate cache and frame if needed.
Definition: scribblearea.cpp:290
ScribbleArea::onViewChanged
void onViewChanged()
View updated, invalidate relevant cache.
Definition: scribblearea.cpp:352
ScribbleArea::onObjectLoaded
void onObjectLoaded()
Object updated, invalidate all cache.
Definition: scribblearea.cpp:374
ScribbleArea::invalidateCacheForDirtyFrames
void invalidateCacheForDirtyFrames()
invalidate cache for dirty keyframes.
Definition: scribblearea.cpp:208
ScribbleArea::updateFrame
void updateFrame()
Update frame.
Definition: scribblearea.cpp:199
ScribbleArea::handleDrawingOnEmptyFrame
void handleDrawingOnEmptyFrame()
Call this when starting to use a paint tool.
Definition: scribblearea.cpp:827
ScribbleArea::invalidatePainterCaches
void invalidatePainterCaches()
Invalidate the layer pixmap and camera painter caches.
Definition: scribblearea.cpp:283
ScribbleArea::invalidateOnionSkinsCacheAround
void invalidateOnionSkinsCacheAround(int frame)
invalidate onion skin cache around frame
Definition: scribblearea.cpp:219
ScribbleArea::onSelectionChanged
void onSelectionChanged()
Selection was changed, keep cache.
Definition: scribblearea.cpp:362
ScribbleArea::onOnionSkinTypeChanged
void onOnionSkinTypeChanged()
Onion skin type changed, all frames will be affected.
Definition: scribblearea.cpp:369
ScribbleArea::onFramesModified
void onFramesModified()
Multiple frames modified, invalidate cache for affected frames.
Definition: scribblearea.cpp:333
ScribbleArea::onToolChanged
void onToolChanged(ToolType)
Tool changed, invalidate cache and frame if needed.
Definition: scribblearea.cpp:302
ScribbleArea::onPlayStateChanged
void onPlayStateChanged()
Playstate changed, invalidate relevant cache.
Definition: scribblearea.cpp:312
ScribbleArea::invalidateAllCache
void invalidateAllCache()
Invalidate all cache.
Definition: scribblearea.cpp:260
ScribbleArea::invalidateCacheForFrame
void invalidateCacheForFrame(int frameNumber)
Invalidate cache for the given frame.
Definition: scribblearea.cpp:272
ScribbleArea::onLayerChanged
void onLayerChanged()
Layer changed, invalidate relevant cache.
Definition: scribblearea.cpp:357
ScribbleArea::onFrameModified
void onFrameModified(int frameNumber)
Frame modified, invalidate cache for frame if any.
Definition: scribblearea.cpp:342
SelectionManager
Definition: selectionmanager.h:33
SelectionManager::flipSelection
void flipSelection(bool flipVertical)
ScribbleArea::flipSelection flip selection along the X or Y axis.
Definition: selectionmanager.cpp:328
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
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::isEmpty
bool isEmpty() const const
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::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::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:29
Generated on Mon Dec 15 2025 03:41:35 for Pencil2D by doxygen 1.9.6 based on revision 9bfef078cfa681fa5250352bbcb5a69281765ae9