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 break;
875 default:
876 break;
877 }
878}
879
880void ScribbleArea::paintEvent(QPaintEvent* event)
881{
882 int currentFrame = mEditor->currentFrame();
883 if (!currentTool()->isActive())
884 {
885 // --- we retrieve the canvas from the cache; we create it if it doesn't exist
886 const int frameNumber = mEditor->layers()->lastFrameAtFrame(currentFrame);
887
888 if (frameNumber < 0)
889 {
890 drawCanvas(currentFrame, event->rect());
891 }
892 else
893 {
894 auto cacheKeyIter = mPixmapCacheKeys.find(static_cast<unsigned>(frameNumber));
895
896 if (cacheKeyIter == mPixmapCacheKeys.end() || !QPixmapCache::find(cacheKeyIter.value(), &mCanvas))
897 {
898 drawCanvas(currentFrame, event->rect());
899 mPixmapCacheKeys[static_cast<unsigned>(currentFrame)] = QPixmapCache::insert(mCanvas);
900 //qDebug() << "Repaint canvas!";
901 }
902 else
903 {
904 // Simply use the cached canvas from PixmapCache
905 }
906 }
907 }
908 else
909 {
910 prepCanvas(currentFrame);
911 prepCameraPainter(currentFrame);
912 prepOverlays(currentFrame);
913
914 mCanvasPainter.paintCached(event->rect());
915 mCameraPainter.paintCached(event->rect());
916 }
917
918 if (currentTool()->type() == MOVE)
919 {
920 Layer* layer = mEditor->layers()->currentLayer();
921 Q_CHECK_PTR(layer);
922 if (layer->type() == Layer::VECTOR)
923 {
924 VectorImage* vectorImage = currentVectorImage(layer);
925 if (vectorImage != nullptr)
926 {
927 vectorImage->setModified(true);
928 }
929 }
930 }
931
932 QPainter painter(this);
933
934 // paints the canvas
935 painter.setWorldMatrixEnabled(false);
936
937 // In other places we use the blitRect to paint the buffer pixmap, however
938 // the main pixmap which needs to be scaled accordingly to DPI, which is not accounted for when using the event rect
939 // instead we can set a clipRect to avoid the area being updated needlessly
940 painter.setClipRect(event->rect());
941 painter.drawPixmap(QPointF(), mCanvas);
942
943 currentTool()->paint(painter, event->rect());
944
945 if (!editor()->playback()->isPlaying()) // we don't need to display the following when the animation is playing
946 {
947 Layer* layer = mEditor->layers()->currentLayer();
948 if (layer->type() == Layer::VECTOR)
949 {
950 VectorImage* vectorImage = currentVectorImage(layer);
951 if (vectorImage != nullptr)
952 {
953 switch (currentTool()->type())
954 {
955 case SMUDGE:
956 case HAND:
957 {
958 auto selectMan = mEditor->select();
959 painter.save();
960 painter.setWorldMatrixEnabled(false);
961 painter.setRenderHint(QPainter::Antialiasing, false);
962 // ----- paints the edited elements
963 QPen pen2(Qt::black, 0.5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
964 painter.setPen(pen2);
965 QColor color;
966 // ------------ vertices of the edited curves
967 color = QColor(200, 200, 200);
968 painter.setBrush(color);
969 VectorSelection vectorSelection = selectMan->vectorSelection;
970 for (int k = 0; k < vectorSelection.curve.size(); k++)
971 {
972 int curveNumber = vectorSelection.curve.at(k);
973
974 for (int vertexNumber = -1; vertexNumber < vectorImage->getCurveSize(curveNumber); vertexNumber++)
975 {
976 QPointF vertexPoint = vectorImage->getVertex(curveNumber, vertexNumber);
977 QRectF rectangle(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
978 if (rect().contains(mEditor->view()->mapCanvasToScreen(vertexPoint).toPoint()))
979 {
980 painter.drawRect(rectangle);
981 }
982 }
983 }
984 // ------------ selected vertices of the edited curves
985 color = QColor(100, 100, 255);
986 painter.setBrush(color);
987 for (int k = 0; k < vectorSelection.vertex.size(); k++)
988 {
989 VertexRef vertexRef = vectorSelection.vertex.at(k);
990 QPointF vertexPoint = vectorImage->getVertex(vertexRef);
991 QRectF rectangle0 = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
992 painter.drawRect(rectangle0);
993 }
994 // ----- paints the closest vertices
995 color = QColor(255, 0, 0);
996 painter.setBrush(color);
997 QList<VertexRef> closestVertices = selectMan->closestVertices();
998 if (vectorSelection.curve.size() > 0)
999 {
1000 for (int k = 0; k < closestVertices.size(); k++)
1001 {
1002 VertexRef vertexRef = closestVertices.at(k);
1003 QPointF vertexPoint = vectorImage->getVertex(vertexRef);
1004
1005 QRectF rectangle = QRectF(mEditor->view()->mapCanvasToScreen(vertexPoint) - QPointF(3.0, 3.0), QSizeF(7, 7));
1006 painter.drawRect(rectangle);
1007 }
1008 }
1009 painter.restore();
1010 break;
1011 }
1012 default:
1013 {
1014 break;
1015 }
1016 } // end switch
1017 }
1018 }
1019
1020 mOverlayPainter.paint(painter, rect());
1021
1022 // paints the selection outline
1023 if (mEditor->select()->somethingSelected())
1024 {
1025 paintSelectionVisuals(painter);
1026 }
1027 }
1028
1029 // outlines the frame of the viewport
1030#ifdef _DEBUG
1031 painter.setWorldMatrixEnabled(false);
1032 painter.setPen(QPen(Qt::gray, 2));
1033 painter.setBrush(Qt::NoBrush);
1034 painter.drawRect(QRect(0, 0, width(), height()));
1035#endif
1036
1037 event->accept();
1038}
1039
1040void ScribbleArea::paintSelectionVisuals(QPainter &painter)
1041{
1042 Object* object = mEditor->object();
1043
1044 auto selectMan = mEditor->select();
1045
1046 QRectF currentSelectionRect = selectMan->mySelectionRect();
1047
1048 if (currentSelectionRect.isEmpty()) { return; }
1049
1050 TransformParameters params = { currentSelectionRect, editor()->view()->getView(), selectMan->selectionTransform() };
1051 mSelectionPainter.paint(painter, object, mEditor->currentLayerIndex(), currentTool(), params);
1052 emit selectionUpdated();
1053}
1054
1055BitmapImage* ScribbleArea::currentBitmapImage(Layer* layer) const
1056{
1057 Q_ASSERT(layer->type() == Layer::BITMAP);
1058 auto bitmapLayer = static_cast<LayerBitmap*>(layer);
1059 return bitmapLayer->getLastBitmapImageAtFrame(mEditor->currentFrame());
1060}
1061
1062VectorImage* ScribbleArea::currentVectorImage(Layer* layer) const
1063{
1064 Q_ASSERT(layer->type() == Layer::VECTOR);
1065 auto vectorLayer = static_cast<LayerVector*>(layer);
1066 return vectorLayer->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
1067}
1068
1069void ScribbleArea::prepCameraPainter(int frame)
1070{
1071 Object* object = mEditor->object();
1072
1073 mCameraPainter.preparePainter(object,
1074 mEditor->currentLayerIndex(),
1075 frame,
1076 mEditor->view()->getView(),
1077 mEditor->playback()->isPlaying(),
1078 mLayerVisibility,
1079 mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD),
1080 mEditor->view()->getScaleInversed());
1081
1082 OnionSkinPainterOptions onionSkinOptions;
1083 onionSkinOptions.enabledWhilePlaying = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK);
1084 onionSkinOptions.isPlaying = mEditor->playback()->isPlaying();
1085 onionSkinOptions.isAbsolute = (mPrefs->getString(SETTING::ONION_TYPE) == "absolute");
1086 onionSkinOptions.skinPrevFrames = mPrefs->isOn(SETTING::PREV_ONION);
1087 onionSkinOptions.skinNextFrames = mPrefs->isOn(SETTING::NEXT_ONION);
1088 onionSkinOptions.colorizePrevFrames = mPrefs->isOn(SETTING::ONION_RED);
1089 onionSkinOptions.colorizeNextFrames = mPrefs->isOn(SETTING::ONION_BLUE);
1090 onionSkinOptions.framesToSkinPrev = mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM);
1091 onionSkinOptions.framesToSkinNext = mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM);
1092 onionSkinOptions.maxOpacity = mPrefs->getInt(SETTING::ONION_MAX_OPACITY);
1093 onionSkinOptions.minOpacity = mPrefs->getInt(SETTING::ONION_MIN_OPACITY);
1094
1095 mCameraPainter.setOnionSkinPainterOptions(onionSkinOptions);
1096}
1097
1098void ScribbleArea::prepCanvas(int frame)
1099{
1100 Object* object = mEditor->object();
1101
1102 CanvasPainterOptions o;
1103 o.bOnionSkinMultiLayer = mPrefs->isOn(SETTING::ONION_MUTLIPLE_LAYERS);
1104 o.bAntiAlias = mPrefs->isOn(SETTING::ANTIALIAS);
1105 o.bThinLines = mPrefs->isOn(SETTING::INVISIBLE_LINES);
1106 o.bOutlines = mPrefs->isOn(SETTING::OUTLINES);
1107 o.eLayerVisibility = mLayerVisibility;
1108 o.fLayerVisibilityThreshold = mPrefs->getFloat(SETTING::LAYER_VISIBILITY_THRESHOLD);
1109 o.scaling = mEditor->view()->scaling();
1110 o.cmBufferBlendMode = mEditor->tools()->currentTool()->type() == ToolType::ERASER ? QPainter::CompositionMode_DestinationOut : QPainter::CompositionMode_SourceOver;
1111
1112 OnionSkinPainterOptions onionSkinOptions;
1113 onionSkinOptions.enabledWhilePlaying = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK);
1114 onionSkinOptions.isPlaying = mEditor->playback()->isPlaying();
1115 onionSkinOptions.isAbsolute = (mPrefs->getString(SETTING::ONION_TYPE) == "absolute");
1116 onionSkinOptions.skinPrevFrames = mPrefs->isOn(SETTING::PREV_ONION);
1117 onionSkinOptions.skinNextFrames = mPrefs->isOn(SETTING::NEXT_ONION);
1118 onionSkinOptions.colorizePrevFrames = mPrefs->isOn(SETTING::ONION_RED);
1119 onionSkinOptions.colorizeNextFrames = mPrefs->isOn(SETTING::ONION_BLUE);
1120 onionSkinOptions.framesToSkinPrev = mPrefs->getInt(SETTING::ONION_PREV_FRAMES_NUM);
1121 onionSkinOptions.framesToSkinNext = mPrefs->getInt(SETTING::ONION_NEXT_FRAMES_NUM);
1122 onionSkinOptions.maxOpacity = mPrefs->getInt(SETTING::ONION_MAX_OPACITY);
1123 onionSkinOptions.minOpacity = mPrefs->getInt(SETTING::ONION_MIN_OPACITY);
1124
1125 mCanvasPainter.setOnionSkinOptions(onionSkinOptions);
1126 mCanvasPainter.setOptions(o);
1127
1128 ViewManager* vm = mEditor->view();
1129 SelectionManager* sm = mEditor->select();
1130 mCanvasPainter.setViewTransform(vm->getView(), vm->getViewInverse());
1131 mCanvasPainter.setTransformedSelection(sm->mySelectionRect().toRect(), sm->selectionTransform());
1132
1133 mCanvasPainter.setPaintSettings(object, mEditor->layers()->currentLayerIndex(), frame, &mTiledBuffer);
1134}
1135
1136void ScribbleArea::drawCanvas(int frame, QRect rect)
1137{
1138 prepCanvas(frame);
1139 prepCameraPainter(frame);
1140 prepOverlays(frame);
1141 mCanvasPainter.paint(rect);
1142 mCameraPainter.paint(rect);
1143}
1144
1145void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor color, qreal opacity, qreal offset)
1146{
1147 if (offset < 0) { offset = 0; }
1148 if (offset > 100) { offset = 100; }
1149
1150 int r = color.red();
1151 int g = color.green();
1152 int b = color.blue();
1153 qreal a = color.alphaF();
1154
1155 int mainColorAlpha = qRound(a * 255 * opacity);
1156
1157 // the more feather (offset), the more softness (opacity)
1158 int alphaAdded = qRound((mainColorAlpha * offset) / 100);
1159
1160 gradient.setColorAt(0.0, QColor(r, g, b, mainColorAlpha - alphaAdded));
1161 gradient.setColorAt(1.0, QColor(r, g, b, 0));
1162 gradient.setColorAt(1.0 - (offset / 100.0), QColor(r, g, b, mainColorAlpha - alphaAdded));
1163}
1164
1165void ScribbleArea::drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm)
1166{
1167 mTiledBuffer.drawPath(mEditor->view()->mapScreenToCanvas(path), pen, brush, cm, mPrefs->isOn(SETTING::ANTIALIAS));
1168}
1169
1170void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColor, bool useAA)
1171{
1172 // We use Source as opposed to SourceOver here to avoid the dabs being added on top of each other
1173 mTiledBuffer.drawBrush(thePoint, brushWidth, Qt::NoPen, QBrush(fillColor, Qt::SolidPattern), QPainter::CompositionMode_Source, useAA);
1174}
1175
1176void ScribbleArea::drawPencil(QPointF thePoint, qreal brushWidth, qreal fixedBrushFeather, QColor fillColor, qreal opacity)
1177{
1178 drawBrush(thePoint, brushWidth, fixedBrushFeather, fillColor, QPainter::CompositionMode_SourceOver, opacity, true);
1179}
1180
1181void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset, QColor fillColor, QPainter::CompositionMode compMode, qreal opacity, bool usingFeather, bool useAA)
1182{
1183 QBrush brush;
1184 if (usingFeather)
1185 {
1186 QRadialGradient radialGrad(thePoint, 0.5 * brushWidth);
1187 setGaussianGradient(radialGrad, fillColor, opacity, mOffset);
1188 brush = radialGrad;
1189 }
1190 else
1191 {
1192 brush = QBrush(fillColor, Qt::SolidPattern);
1193 }
1194 mTiledBuffer.drawBrush(thePoint, brushWidth, Qt::NoPen, brush, compMode, useAA);
1195}
1196
1197void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA)
1198{
1199 BlitRect blitRect;
1200
1201 // In order to clear what was previously dirty, we need to include the previous buffer bound
1202 // this ensures that we won't see stroke artifacts
1203 blitRect.extend(mEditor->view()->mapCanvasToScreen(mTiledBuffer.bounds()).toRect());
1204
1205 QRect updateRect = mEditor->view()->mapCanvasToScreen(path.boundingRect()).toRect();
1206 // Now extend with the new path bounds mapped to the local coordinate
1207 blitRect.extend(updateRect);
1208
1209 mTiledBuffer.clear();
1210 mTiledBuffer.drawPath(path, pen, Qt::NoBrush, QPainter::CompositionMode_SourceOver, useAA);
1211
1212 // And update only the affected area
1213 update(blitRect.adjusted(-1, -1, 1, 1));
1214}
1215
1216void ScribbleArea::endStroke()
1217{
1218 if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP) {
1219 paintBitmapBuffer();
1220 }
1221
1222 onFrameModified(mEditor->currentFrame());
1223}
1224
1225void ScribbleArea::flipSelection(bool flipVertical)
1226{
1227 mEditor->select()->flipSelection(flipVertical);
1228}
1229
1230void ScribbleArea::prepOverlays(int frame)
1231{
1232 OverlayPainterOptions o;
1233
1234 o.bGrid = mPrefs->isOn(SETTING::GRID);
1235 o.nGridSizeW = mPrefs->getInt(SETTING::GRID_SIZE_W);
1236 o.nGridSizeH = mPrefs->getInt(SETTING::GRID_SIZE_H);
1237 o.bCenter = mPrefs->isOn(SETTING::OVERLAY_CENTER);
1238 o.bThirds = mPrefs->isOn(SETTING::OVERLAY_THIRDS);
1239 o.bGoldenRatio = mPrefs->isOn(SETTING::OVERLAY_GOLDEN);
1240 o.bSafeArea = mPrefs->isOn(SETTING::OVERLAY_SAFE);
1241 o.bPerspective1 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE1);
1242 o.bPerspective2 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE2);
1243 o.bPerspective3 = mPrefs->isOn(SETTING::OVERLAY_PERSPECTIVE3);
1244 o.nOverlayAngle = mPrefs->getInt(SETTING::OVERLAY_ANGLE);
1245 o.bActionSafe = mPrefs->isOn(SETTING::ACTION_SAFE_ON);
1246 o.nActionSafe = mPrefs->getInt(SETTING::ACTION_SAFE);
1247 o.bShowSafeAreaHelperText = mPrefs->isOn(SETTING::OVERLAY_SAFE_HELPER_TEXT_ON);
1248 o.bTitleSafe = mPrefs->isOn(SETTING::TITLE_SAFE_ON);
1249 o.nTitleSafe = mPrefs->getInt(SETTING::TITLE_SAFE);
1250 o.nOverlayAngle = mPrefs->getInt(SETTING::OVERLAY_ANGLE);
1251 o.bShowHandle = mEditor->tools()->currentTool()->type() == MOVE && mEditor->layers()->currentLayer()->type() != Layer::CAMERA;
1252
1253 o.mSinglePerspPoint = mEditor->overlays()->getSinglePerspectivePoint();
1254 o.mLeftPerspPoint = mEditor->overlays()->getLeftPerspectivePoint();
1255 o.mRightPerspPoint = mEditor->overlays()->getRightPerspectivePoint();
1256 o.mMiddlePerspPoint = mEditor->overlays()->getMiddlePerspectivePoint();
1257
1258 o.nFrameIndex = frame;
1259
1260 mOverlayPainter.setOptions(o);
1261 mOverlayPainter.preparePainter(mEditor->layers()->getCameraLayerBelow(mEditor->currentLayerIndex()), palette());
1262
1263 ViewManager* vm = mEditor->view();
1264 mOverlayPainter.setViewTransform(vm->getView());
1265}
1266
1267void ScribbleArea::blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_)
1268{
1269 QRadialGradient radialGrad(thePoint_, 0.5 * brushWidth_);
1270 setGaussianGradient(radialGrad, QColor(255, 255, 255, 127), opacity_, mOffset_);
1271
1272 QRectF srcRect(srcPoint_.x() - 0.5 * brushWidth_, srcPoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1273 QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1274
1275 BitmapImage bmiSrcClip = bmiSource_->copy(srcRect.toAlignedRect());
1276 BitmapImage bmiTmpClip = bmiSrcClip; // TODO: find a shorter way
1277
1278 bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, true);
1279 bmiSrcClip.bounds().moveTo(trgRect.topLeft().toPoint());
1280 bmiTmpClip.paste(&bmiSrcClip, QPainter::CompositionMode_SourceIn);
1281 mTiledBuffer.drawImage(*bmiTmpClip.image(), bmiTmpClip.bounds(), QPainter::CompositionMode_SourceOver, mPrefs->isOn(SETTING::ANTIALIAS));
1282}
1283
1284void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal mOffset_, qreal opacity_)
1285{
1286 QPointF delta = (thePoint_ - srcPoint_); // increment vector
1287 QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
1288
1289 QRadialGradient radialGrad(thePoint_, 0.5 * brushWidth_);
1290 setGaussianGradient(radialGrad, QColor(255, 255, 255, 255), opacity_, mOffset_);
1291
1292 // Create gradient brush
1293 BitmapImage bmiTmpClip;
1294 bmiTmpClip.drawRect(trgRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
1295
1296 // Slide texture/pixels of the source image
1297 qreal factor, factorGrad;
1298
1299 for (int yb = bmiTmpClip.top(); yb < bmiTmpClip.bottom(); yb++)
1300 {
1301 for (int xb = bmiTmpClip.left(); xb < bmiTmpClip.right(); xb++)
1302 {
1303 QColor color;
1304 color.setRgba(bmiTmpClip.pixel(xb, yb));
1305 factorGrad = color.alphaF(); // any from r g b a is ok
1306
1307 int xa = xb - factorGrad * delta.x();
1308 int ya = yb - factorGrad * delta.y();
1309
1310 color.setRgba(bmiSource_->pixel(xa, ya));
1311 factor = color.alphaF();
1312
1313 if (factor > 0.0)
1314 {
1315 color.setRed(color.red() / factor);
1316 color.setGreen(color.green() / factor);
1317 color.setBlue(color.blue() / factor);
1318 color.setAlpha(255); // Premultiplied color
1319
1320 color.setRed(color.red()*factorGrad);
1321 color.setGreen(color.green()*factorGrad);
1322 color.setBlue(color.blue()*factorGrad);
1323 color.setAlpha(255 * factorGrad); // Premultiplied color
1324
1325 bmiTmpClip.setPixel(xb, yb, color.rgba());
1326 }
1327 else
1328 {
1329 bmiTmpClip.setPixel(xb, yb, qRgba(255, 255, 255, 0));
1330 }
1331 }
1332 }
1333 mTiledBuffer.drawImage(*bmiTmpClip.image(), bmiTmpClip.bounds(), QPainter::CompositionMode_SourceOver, mPrefs->isOn(SETTING::ANTIALIAS));
1334}
1335
1336/************************************************************************************/
1337// view handling
1338
1339QPointF ScribbleArea::getCentralPoint()
1340{
1341 return mEditor->view()->mapScreenToCanvas(QPointF(width() / 2, height() / 2));
1342}
1343
1344void ScribbleArea::applyTransformedSelection()
1345{
1346 mCanvasPainter.ignoreTransformedSelection();
1347
1348 Layer* layer = mEditor->layers()->currentLayer();
1349
1350 bool useAA = mEditor->tools()->currentTool()->properties.useAA;
1351
1352 if (layer == nullptr) { return; }
1353
1354 auto selectMan = mEditor->select();
1355 if (selectMan->somethingSelected())
1356 {
1357 if (selectMan->mySelectionRect().isEmpty() || selectMan->selectionTransform().isIdentity()) { return; }
1358
1359 if (layer->type() == Layer::BITMAP)
1360 {
1361 handleDrawingOnEmptyFrame();
1362 BitmapImage* bitmapImage = currentBitmapImage(layer);
1363 if (bitmapImage == nullptr) { return; }
1364 BitmapImage transformedImage = bitmapImage->transformed(selectMan->mySelectionRect().toRect(), selectMan->selectionTransform(), useAA);
1365
1366
1367 bitmapImage->clear(selectMan->mySelectionRect());
1368 bitmapImage->paste(&transformedImage, QPainter::CompositionMode_SourceOver);
1369 }
1370 else if (layer->type() == Layer::VECTOR)
1371 {
1372 // Unfortunately this doesn't work right currently so vector transforms
1373 // will always be applied on the previous keyframe when on an empty frame
1374 //handleDrawingOnEmptyFrame();
1375 VectorImage* vectorImage = currentVectorImage(layer);
1376 if (vectorImage == nullptr) { return; }
1377
1378 vectorImage->applySelectionTransformation();
1379 }
1380
1381 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1382 }
1383
1384 updateFrame();
1385}
1386
1387void ScribbleArea::cancelTransformedSelection()
1388{
1389 mCanvasPainter.ignoreTransformedSelection();
1390
1391 auto selectMan = mEditor->select();
1392 if (selectMan->somethingSelected())
1393 {
1394 Layer* layer = mEditor->layers()->currentLayer();
1395 if (layer == nullptr) { return; }
1396
1397 if (layer->type() == Layer::VECTOR)
1398 {
1399 VectorImage* vectorImage = currentVectorImage(layer);
1400 if (vectorImage != nullptr)
1401 {
1402 vectorImage->setSelectionTransformation(QTransform());
1403 }
1404 }
1405
1406 mEditor->select()->setSelection(selectMan->mySelectionRect(), false);
1407
1408 selectMan->resetSelectionProperties();
1409 mOriginalPolygonF = QPolygonF();
1410
1411 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1412 updateFrame();
1413 }
1414}
1415
1416void ScribbleArea::toggleThinLines()
1417{
1418 bool previousValue = mPrefs->isOn(SETTING::INVISIBLE_LINES);
1419 setEffect(SETTING::INVISIBLE_LINES, !previousValue);
1420}
1421
1422void ScribbleArea::setLayerVisibility(LayerVisibility visibility)
1423{
1424 mLayerVisibility = visibility;
1425 mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1426
1427 invalidateAllCache();
1428}
1429
1430void ScribbleArea::increaseLayerVisibilityIndex()
1431{
1432 ++mLayerVisibility;
1433 mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1434
1435 invalidateAllCache();
1436}
1437
1438void ScribbleArea::decreaseLayerVisibilityIndex()
1439{
1440 --mLayerVisibility;
1441 mPrefs->set(SETTING::LAYER_VISIBILITY, static_cast<int>(mLayerVisibility));
1442
1443 invalidateAllCache();
1444}
1445
1446/************************************************************************************/
1447// tool handling
1448
1449BaseTool* ScribbleArea::currentTool() const
1450{
1451 return editor()->tools()->currentTool();
1452}
1453
1454void ScribbleArea::deleteSelection()
1455{
1456 auto selectMan = mEditor->select();
1457 if (selectMan->somethingSelected()) // there is something selected
1458 {
1459 Layer* layer = mEditor->layers()->currentLayer();
1460 if (layer == nullptr) { return; }
1461
1462 handleDrawingOnEmptyFrame();
1463
1464 mEditor->backup(tr("Delete Selection", "Undo Step: clear the selection area."));
1465
1466 selectMan->clearCurves();
1467 if (layer->type() == Layer::VECTOR)
1468 {
1469 VectorImage* vectorImage = currentVectorImage(layer);
1470 Q_CHECK_PTR(vectorImage);
1471 vectorImage->deleteSelection();
1472 }
1473 else if (layer->type() == Layer::BITMAP)
1474 {
1475 BitmapImage* bitmapImage = currentBitmapImage(layer);
1476 Q_CHECK_PTR(bitmapImage);
1477 bitmapImage->clear(selectMan->mySelectionRect());
1478 }
1479 mEditor->setModified(mEditor->currentLayerIndex(), mEditor->currentFrame());
1480 }
1481}
1482
1483void ScribbleArea::clearImage()
1484{
1485 Layer* layer = mEditor->layers()->currentLayer();
1486 if (layer == nullptr) { return; }
1487
1488 if (layer->type() == Layer::VECTOR)
1489 {
1490 mEditor->backup(tr("Clear Image", "Undo step text"));
1491
1492 VectorImage* vectorImage = currentVectorImage(layer);
1493 if (vectorImage != nullptr)
1494 {
1495 vectorImage->clear();
1496 }
1497 mEditor->select()->clearCurves();
1498 mEditor->select()->clearVertices();
1499 }
1500 else if (layer->type() == Layer::BITMAP)
1501 {
1502 mEditor->backup(tr("Clear Image", "Undo step text"));
1503
1504 BitmapImage* bitmapImage = currentBitmapImage(layer);
1505 if (bitmapImage == nullptr) return;
1506 bitmapImage->clear();
1507 }
1508 else
1509 {
1510 return; // skip updates when nothing changes
1511 }
1512 mEditor->setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
1513}
1514
1515void ScribbleArea::paletteColorChanged(QColor color)
1516{
1517 Q_UNUSED(color)
1518
1519 for (int i = 0; i < mEditor->layers()->count(); i++)
1520 {
1521 Layer* layer = mEditor->layers()->getLayer(i);
1522 if (layer->type() == Layer::VECTOR)
1523 {
1524 VectorImage* vectorImage = static_cast<LayerVector*>(layer)->getVectorImageAtFrame(mEditor->currentFrame());
1525 if (vectorImage != nullptr)
1526 {
1527 vectorImage->modification();
1528 }
1529 }
1530 }
1531
1532 invalidateAllCache();
1533}
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:903
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:49
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 Thu May 8 2025 04:47:53 for Pencil2D by doxygen 1.9.6 based on revision 4513250b1d5b1a3676ec0e67b06b7a885ceaae39