Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_freehand.cc
Go to the documentation of this file.
1/*
2 * kis_tool_freehand.cc - part of Krita
3 *
4 * SPDX-FileCopyrightText: 2003-2007 Boudewijn Rempt <boud@valdyas.org>
5 * SPDX-FileCopyrightText: 2004 Bart Coppens <kde@bartcoppens.be>
6 * SPDX-FileCopyrightText: 2007, 2008, 2010 Cyrille Berger <cberger@cberger.net>
7 * SPDX-FileCopyrightText: 2009 Lukáš Tvrdý <lukast.dev@gmail.com>
8 * SPDX-FileCopyrightText: 2026 Ayanami Kaine <personal@ayanamikaine.com>
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 */
12
13#include "kis_tool_freehand.h"
14#include <optional>
15#include <QPainter>
16#include <QRect>
17#include <QThreadPool>
18#include <QApplication>
19#include <QScreen>
20
21#include <Eigen/Core>
22
23#include <kis_icon.h>
24#include <KoPointerEvent.h>
25#include <KoViewConverter.h>
26#include <KoCanvasController.h>
27
28//pop up palette
30
31// Krita/image
32#include <kis_layer.h>
33#include <kis_paint_layer.h>
34#include <kis_painter.h>
36#include <kis_selection.h>
39
40
41// Krita/ui
43#include "kis_config.h"
44#include "kis_config_notifier.h"
45#include "kis_image_config.h"
46#include "canvas/kis_canvas2.h"
47#include "kis_cursor.h"
48#include <KisViewManager.h>
53#include "kis_tool_utils.h"
54
55using namespace std::placeholders; // For _1 placeholder
56
57namespace {
58bool snapsTemporaryStraightRuler(const KisTemporaryPaintConstraint &constraint,
59 Qt::KeyboardModifiers modifiers)
60{
62 modifiers.testFlag(Qt::ShiftModifier);
63}
64
65bool hasLockedValidTemporaryStraightRuler(const std::optional<KisTemporaryPaintConstraint> &constraint)
66{
67 return constraint && constraint->isLocked() && constraint->hasValidLine();
68}
69}
70
71
72KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor,
73 const KUndo2MagicString &transactionText, bool useSavedSmoothing)
74 : KisToolPaint(canvas, cursor),
75 m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1))
76{
77
80 connect(KisConfigNotifier::instance(), SIGNAL(touchPaintingChanged()),
82
85 new KisSmoothingOptions(useSavedSmoothing));
86
87 connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline()));
88
89 connect(qobject_cast<KisCanvas2*>(canvas)->viewManager(), SIGNAL(brushOutlineToggled()), SLOT(explicitUpdateOutline()));
90
91 KisCanvasResourceProvider *provider = qobject_cast<KisCanvas2*>(canvas)->viewManager()->canvasResourceProvider();
92
93 connect(provider, SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(explicitUpdateOutline()));
94 connect(provider, SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(resetCursorStyle()));
95 connect(provider, SIGNAL(sigPaintOpPresetChanged(KisPaintOpPresetSP)), SLOT(explicitUpdateOutline()));
96 connect(provider, SIGNAL(sigPaintOpPresetChanged(KisPaintOpPresetSP)), SLOT(resetCursorStyle()));
97
98 KisPaintingAssistantsDecorationSP decoration = qobject_cast<KisCanvas2*>(canvas)->paintingAssistantsDecoration();
99 connect(decoration.data(), SIGNAL(temporaryConstraintAvailabilityChanged()), SLOT(explicitUpdateOutline()));
100 connect(decoration.data(), SIGNAL(temporaryConstraintAvailabilityChanged()), SLOT(resetCursorStyle()));
101}
102
104{
105 delete m_helper;
106 delete m_infoBuilder;
107}
108
122
127
129{
131 useCursor(KisCursor::loadWithSize("cursor-cross-full.svg", 31, 31));
132 return;
133 }
134
135 KisConfig cfg(true);
136
137 bool useSeparateEraserCursor = cfg.separateEraserCursor() && isEraser();
138
139 switch (useSeparateEraserCursor ? cfg.eraserCursorStyle() : cfg.newCursorStyle()) {
142 break;
145 break;
148 break;
151 break;
154 break;
157 break;
160 break;
163 break;
166 break;
168 default:
170 break;
171 }
172}
173
178
180{
181 delete m_helper;
182 m_helper = helper;
183}
184
186{
187 return true;
188}
189
195
196void KisToolFreehand::activate(const QSet<KoShape*> &shapes)
197{
199}
200
202{
203 if (mode() == PAINT_MODE) {
204 endStroke();
206 }
207 if (KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas())) {
208 if (canvas2->paintingAssistantsDecoration()) {
209 canvas2->paintingAssistantsDecoration()->setTemporaryConstraint(std::nullopt);
210 }
211 }
213}
214
216{
217 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas());
218 return canvas2 &&
219 canvas2->paintingAssistantsDecoration() &&
220 canvas2->paintingAssistantsDecoration()->temporaryConstraint().has_value();
221}
222
224{
225 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas());
226 if (!canvas2 ||
227 !canvas2->paintingAssistantsDecoration() ||
228 !hasLockedValidTemporaryStraightRuler(canvas2->paintingAssistantsDecoration()->temporaryConstraint())) {
229
230 return documentPoint;
231 }
232
233 return adjustPosition(documentPoint, documentPoint);
234}
235
236void KisToolFreehand::updateTemporaryStraightRulerPreview(const QPointF &documentPoint, Qt::KeyboardModifiers modifiers)
237{
238 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas());
239 if (!canvas2 || !canvas2->paintingAssistantsDecoration()) {
240 return;
241 }
242
243 std::optional<KisTemporaryPaintConstraint> constraint =
245
246 if (!constraint || constraint->isLocked()) {
247 return;
248 }
249
250 if (!constraint->hasLine()) {
251 constraint->beginLine(documentPoint);
252 } else {
253 constraint->updateLine(documentPoint, snapsTemporaryStraightRuler(*constraint, modifiers));
254 }
255
257}
258
259void KisToolFreehand::lockTemporaryStraightRuler(const QPointF &documentPoint, Qt::KeyboardModifiers modifiers)
260{
261 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas());
262 if (!canvas2 || !canvas2->paintingAssistantsDecoration()) {
263 return;
264 }
265
266 std::optional<KisTemporaryPaintConstraint> constraint =
268
269 if (!constraint) {
270 return;
271 }
272
273 if (!constraint->hasLine()) {
274 return;
275 }
276
277 if (!constraint->isLocked()) {
278 constraint->updateLine(documentPoint, snapsTemporaryStraightRuler(*constraint, modifiers));
279 }
280
281 if (constraint->hasValidLine()) {
282 constraint->setLocked(true);
283 } else {
284 const KisTemporaryPaintConstraint::InteractionMode interactionMode = constraint->interactionMode();
285 constraint = KisTemporaryPaintConstraint();
286 constraint->setInteractionMode(interactionMode);
287 }
288
290}
291
293{
294 m_helper->initPaint(event,
295 convertToPixelCoord(event),
296 image(),
297 currentNode(),
298 image().data());
299}
300
305
307{
309 bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mouseReleaseEvent();
310 Q_UNUSED(paintOpIgnoredEvent);
311}
312
314{
315 return true;
316}
317
319{
320 // FIXME: workaround for the Duplicate Op
322
324
326
327 NodePaintAbility paintability = nodePaintAbility();
328 // XXX: move this to KisTool and make it work properly for clone layers: for clone layers, the shape paint tools don't work either
329 if (!nodeEditable() || paintability != PAINT) {
330 if (paintability == KisToolPaint::VECTOR || paintability == KisToolPaint::CLONE){
331 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
332 QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask.");
333 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
334 }
335 else if (paintability == MYPAINTBRUSH_UNPAINTABLE) {
336 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
337 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace");
338 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
339 }
340 event->ignore();
341
342 return;
343 }
344
346
348
349 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
350 if (canvas2) {
351 canvas2->viewManager()->disableControls();
352 }
353
354 initStroke(event);
355}
356
368
370{
371 Q_UNUSED(event);
373
374 endStroke();
375
376 if (m_assistant && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) {
377 static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->endStroke();
378 }
379
380 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
381 if (canvas2) {
382 canvas2->viewManager()->enableControls();
383 }
384
386}
387
389{
390 if (action != SampleFgNode && action != SampleFgImage) return false;
391
396 QPointF pos = adjustPosition(event->point, event->point);
397 qreal perspective = calculatePerspective(pos);
398 if (!currentPaintOpPreset()) {
399 return false;
400 }
403 event->xTilt(), event->yTilt(),
404 event->rotation(),
405 event->tangentialPressure(),
406 perspective, 0, 0);
409
410 bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mousePressEvent(info,
411 event->modifiers(),
412 currentNode());
413 // DuplicateOP during the sampling of new source point (origin)
414 // is the only paintop that returns "false" here
415 return !paintOpIgnoredEvent;
416}
417
428
439
441{
442 if (trySampleByPaintOp(event, action)) {
444 return;
445 }
446
447 if (action != ChangeSize && action != ChangeSizeSnap) {
449 return;
450 }
451
453 m_initialGestureDocPoint = event->point;
454 m_initialGestureGlobalPoint = event->globalPos();
455
456 m_lastDocumentPoint = event->point;
457 m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize();
458
459 m_beginAlternateActionEvent = event->deepCopyEvent();
461}
462
464{
466
467 if (action != ChangeSize && action != ChangeSizeSnap) {
469 return;
470 }
471
472 QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint);
473 QPointF actualWidgetPosition = convertDocumentToWidget(event->point);
474
475 QPointF offset = actualWidgetPosition - lastWidgetPosition;
476
477 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
479 QRect screenRect = QGuiApplication::primaryScreen()->availableVirtualGeometry();
480
481 qreal scaleX = 0;
482 qreal scaleY = 0;
483 canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY);
484
485 const qreal maxBrushSize = KisImageConfig(true).maxBrushSize();
486 const qreal effectiveMaxDragSize = 0.5 * screenRect.width();
487 const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX);
488
489 const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize;
490 const qreal sizeDiff = scaleCoeff * offset.x() ;
491
492 if (qAbs(sizeDiff) > 0.01) {
493 KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
494
495 qreal newSize = m_lastPaintOpSize + sizeDiff;
496
497 if (action == ChangeSizeSnap) {
498 newSize = qMax(qRound(newSize), 1);
499 }
500
501 newSize = qBound(0.01, newSize, maxBrushSize);
502
503 settings->setPaintOpSize(newSize);
504
507 m_beginAlternateActionEvent.has_value() ? &m_beginAlternateActionEvent->event : nullptr);
508 //m_brushResizeCompressor.start(newSize);
509
510 m_lastDocumentPoint = event->point;
511 m_lastPaintOpSize = newSize;
512 }
513}
514
534
536{
537 return false;
538}
539
541{
542 m_assistant = assistant;
543}
544
546{
547 m_only_one_assistant = assistant;
548}
549
551{
552 m_eraser_snapping = assistant;
553}
554
556{
557 KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
558
559 settings->setPaintOpSize(newSize);
561
562}
563
564QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin)
565{
566 KisCanvas2* c = static_cast<KisCanvas2*>(canvas());
567 if (!c || !c->paintingAssistantsDecoration()) {
568 return point;
569 }
570
571 if (hasLockedValidTemporaryStraightRuler(c->paintingAssistantsDecoration()->temporaryConstraint())) {
573 QPointF ap = c->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin);
575 return ap;
576 }
577
578 if (m_assistant) {
581 QPointF ap = c->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin);
582 QPointF fp = (1.0 - m_magnetism) * point + m_magnetism * ap;
583 // Report the final position back to the assistant so the guides
584 // can follow the brush
586 return fp;
587 }
588 return point;
589}
590
591qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint)
592{
593 qreal perspective = 1.0;
594 Q_FOREACH (const KisPaintingAssistantSP assistant, static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->assistants()) {
595 QPointer<KisAbstractPerspectiveGrid> grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data());
596 if (grid && grid->isActive() && grid->contains(documentPoint)) {
597 perspective = grid->distance(documentPoint);
598 break;
599 }
600 }
601 return perspective;
602}
603
608
613
615 const KoPointerEvent *event,
617{
620 }
621
623 return m_helper->paintOpOutline(convertToPixelCoord(documentPos),
624 event,
625 currentPaintOpPreset()->settings(),
626 outlineMode);
627 else
629}
KisCoordinatesConverter * coordinatesConverter
KisViewManager * viewManager() const
KisPaintingAssistantsDecorationSP paintingAssistantsDecoration() const
static KisConfigNotifier * instance()
CursorStyle newCursorStyle(bool defaultValue=false) const
CursorStyle eraserCursorStyle(bool defaultValue=false) const
bool separateEraserCursor(bool defaultValue=false) const
void imageScale(qreal *scaleX, qreal *scaleY) const
static QCursor pixelBlackCursor()
Definition kis_cursor.cc:44
static QCursor loadWithSize(const QString &cursorName, int width, int height, int hotspotX=-1, int hotspotY=-1)
static QCursor blankCursor()
Definition kis_cursor.cc:89
static QCursor crossCursor()
Definition kis_cursor.cc:34
static QCursor triangleRightHandedCursor()
static QCursor arrowCursor()
Definition kis_cursor.cc:24
static QCursor triangleLeftHandedCursor()
static QCursor roundCursor()
Definition kis_cursor.cc:39
static QCursor pixelWhiteCursor()
Definition kis_cursor.cc:49
static QCursor eraserCursor()
int maxBrushSize(bool defaultValue=false) const
void setRandomSource(KisRandomSourceSP value)
void setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value)
std::optional< KisTemporaryPaintConstraint > temporaryConstraint() const
void setOnlyOneAssistantSnap(bool assistant)
sets whether we snap to only one assistant
void setTemporaryConstraint(std::optional< KisTemporaryPaintConstraint > constraint)
void setEraserSnap(bool assistant)
sets whether eraser brushes snap
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin)
KisSmoothingOptionsSP smoothingOptions() const
void cursorMoved(const QPointF &cursorPos)
void paintEvent(KoPointerEvent *event)
KisOptimizedBrushOutline paintOpOutline(const QPointF &savedCursorPos, const KoPointerEvent *event, const KisPaintOpSettingsSP globalSettings, KisPaintOpSettings::OutlineMode mode) const
void initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode=0, KisDefaultBoundsBaseSP bounds=0)
virtual void endStroke()
KisPaintingInformationBuilder * m_infoBuilder
void setSnapEraser(bool assistant)
void deactivate() override
KisOptimizedBrushOutline getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) override
void continuePrimaryAction(KoPointerEvent *event) override
KisToolFreehandHelper * m_helper
void mouseMoveEvent(KoPointerEvent *event) override
friend class KisToolFreehandPaintingInformationBuilder
void activateAlternateAction(AlternateAction action) override
QPoint m_initialGestureGlobalPoint
void updateTemporaryStraightRulerPreview(const QPointF &documentPoint, Qt::KeyboardModifiers modifiers)
void activate(const QSet< KoShape * > &shapes) override
KisToolFreehand(KoCanvasBase *canvas, const QCursor &cursor, const KUndo2MagicString &transactionText, bool useSavedSmoothing=true)
void setOnlyOneAssistantSnap(bool assistant)
bool wantsAutoScroll() const override
bool temporaryStraightRulerActive() const
void deactivateAlternateAction(AlternateAction action) override
~KisToolFreehand() override
void slotDoResizeBrush(qreal newSize)
bool primaryActionSupportsHiResEvents() const override
void setAssistant(bool assistant)
virtual void initStroke(KoPointerEvent *event)
void updateMaskSyntheticEventsFromTouch()
KisPaintingInformationBuilder * paintingInformationBuilder() const
void beginPrimaryAction(KoPointerEvent *event) override
virtual void doStroke(KoPointerEvent *event)
void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override
KisSmoothingOptionsSP smoothingOptions() const
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override
void resetHelper(KisToolFreehandHelper *helper)
QPointF m_initialGestureDocPoint
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override
QPointF temporaryStraightRulerOutlinePosition(const QPointF &documentPoint)
std::optional< KoPointerEventWrapper > m_beginAlternateActionEvent
void resetCursorStyle() override
void endPrimaryAction(KoPointerEvent *event) override
bool supportsPaintingAssistants() const override
int flags() const override
bool trySampleByPaintOp(KoPointerEvent *event, AlternateAction action)
qreal calculatePerspective(const QPointF &documentPoint)
void lockTemporaryStraightRuler(const QPointF &documentPoint, Qt::KeyboardModifiers modifiers)
QPointF adjustPosition(const QPointF &point, const QPointF &strokeBegin)
void setSupportOutline(bool supportOutline)
QPointF m_outlineDocPoint
virtual void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event)
void deactivate() override
void activate(const QSet< KoShape * > &shapes) override
void deactivateAlternateAction(AlternateAction action) override
void setOutlineVisible(bool visible)
void setMode(ToolMode mode) override
bool isEraser() const
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override
void activateAlternateAction(AlternateAction action) override
void mouseMoveEvent(KoPointerEvent *event) override
void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override
void enableControls()
disable and enable toolbar controls. used for disabling them during painting.
void showFloatingMessage(const QString &message, const QIcon &icon, int timeout=4500, KisFloatingMessage::Priority priority=KisFloatingMessage::Medium, int alignment=Qt::AlignCenter|Qt::TextWordWrap)
shows a floating message in the top right corner of the canvas
QPointer< KoCanvasResourceProvider > resourceManager
qreal pressure() const
qreal rotation() const
return the rotation (or a default value)
qreal yTilt() const
qreal tangentialPressure() const
Qt::KeyboardModifiers modifiers() const
QPointF point
The point in document coordinates.
qreal xTilt() const
void setMaskSyntheticEvents(bool value)
void useCursor(const QCursor &cursor)
QAction * action(const QString &name) const
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
@ CURSOR_STYLE_POINTER
Definition kis_global.h:65
@ CURSOR_STYLE_SMALL_ROUND
Definition kis_global.h:66
@ CURSOR_STYLE_CROSSHAIR
Definition kis_global.h:67
@ CURSOR_STYLE_TOOLICON
Definition kis_global.h:64
@ CURSOR_STYLE_TRIANGLE_RIGHTHANDED
Definition kis_global.h:68
@ CURSOR_STYLE_WHITE_PIXEL
Definition kis_global.h:71
@ CURSOR_STYLE_BLACK_PIXEL
Definition kis_global.h:70
@ CURSOR_STYLE_TRIANGLE_LEFTHANDED
Definition kis_global.h:69
@ CURSOR_STYLE_NO_CURSOR
Definition kis_global.h:63
@ CURSOR_STYLE_ERASER
Definition kis_global.h:72
#define koIcon(name)
Use these macros for icons without any issues.
Definition kis_icon.h:25
#define CHECK_MODE_SANITY_OR_RETURN(_mode)
Definition kis_tool.h:27
void KRITAUI_EXPORT setCursorPos(const QPoint &point)
QPointF convertToPixelCoord(KoPointerEvent *e)
Definition kis_tool.cc:189
virtual ToolMode mode() const
Definition kis_tool.cc:407
KisTool::NodePaintAbility nodePaintAbility()
Definition kis_tool.cc:539
virtual void resetCursorStyle()
Definition kis_tool.cc:613
bool nodeEditable()
Checks checks if the current node is editable.
Definition kis_tool.cc:651
KisPaintOpPresetSP currentPaintOpPreset()
Definition kis_tool.cc:359
KisNodeSP currentNode() const
Definition kis_tool.cc:370
@ FLAG_USES_CUSTOM_SIZE
Definition kis_tool.h:47
@ FLAG_USES_CUSTOM_COMPOSITEOP
Definition kis_tool.h:47
@ FLAG_USES_CUSTOM_PRESET
Definition kis_tool.h:47
KisImageWSP image() const
Definition kis_tool.cc:332
@ GESTURE_MODE
Definition kis_tool.h:303
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
QPointF convertDocumentToWidget(const QPointF &pt)
Definition kis_tool.cc:181
AlternateAction
Definition kis_tool.h:134
@ SampleFgImage
Definition kis_tool.h:139
@ SampleFgNode
Definition kis_tool.h:137
@ ChangeSizeSnap
Definition kis_tool.h:136
@ ChangeSize
Definition kis_tool.h:135
NodePaintAbility
Definition kis_tool.h:148
@ MYPAINTBRUSH_UNPAINTABLE
Definition kis_tool.h:153
KisCanvas2 * canvas