Krita Source Code Documentation
Loading...
Searching...
No Matches
KisToolBasicBrushBase.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2022 Deif Lou <ginoba@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include <QScreen>
8
9#include <KoPointerEvent.h>
10#include <KoShapeController.h>
11#include <KoViewConverter.h>
12#include <KisViewManager.h>
13#include <KoCanvasBase.h>
14#include <kis_icon.h>
15#include <kis_canvas2.h>
16#include <kis_cubic_curve.h>
17#include <kis_config.h>
18#include <kis_config_notifier.h>
19#include <kis_image_config.h>
21#include <kis_tool_utils.h>
23
25
27 : KisToolShape(canvas, cursor)
28 , m_type(type)
29 , m_previewColor(0, 255, 0, 128)
30 , m_displayRenderer(canvas->displayRendererInterface())
31{
33 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings()));
34 connect(m_displayRenderer, SIGNAL(displayConfigurationChanged), this, SLOT(updatePreviewColor()));
37}
38
41
53
61
63{
64 NodePaintAbility paintability = nodePaintAbility();
65 if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability == KisToolPaint::CLONE || paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE)) || (m_type == SELECT && !selectionEditable())) {
66
67 if (paintability == KisToolPaint::CLONE){
68 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
69 QString message = i18n("This tool cannot paint on clone layers. Please select a paint or vector layer or mask.");
70 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
71 }
72
73 if (paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE) {
74 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
75 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace");
76 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
77 }
78
79 event->ignore();
80 return;
81 }
82
84
85 beginShape();
86
87 const QPointF position = convertToPixelCoord(event);
88 const qreal pressure = pressureToCurve(event->pressure());
89 const qreal radius = pressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
90 m_path = QPainterPath(position);
91 m_path.setFillRule(Qt::WindingFill);
92 m_path.addEllipse(position, radius, radius);
93
94 m_lastPosition = position;
95 m_lastPressure = pressure;
96
97 update(m_path.boundingRect());
98}
99
101{
103
104 const QPointF position = convertToPixelCoord(event);
105 const qreal pressure = pressureToCurve(event->pressure());
106 const qreal brushRadius = currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
107 const QPainterPath segment = generateSegment(m_lastPosition, m_lastPressure * brushRadius, position, pressure * brushRadius);
108 m_path.addPath(segment);
109
110 m_lastPosition = position;
111 m_lastPressure = pressure;
112
113 requestUpdateOutline(event->point, event);
114 update(segment.boundingRect());
115}
116
127
138
149
164
166{
167 if (action != ChangeSize && action != ChangeSizeSnap) {
169 return;
170 }
171
172 QPointF lastWidgetPosition = convertDocumentToWidget(m_changeSizeLastDocumentPoint);
173 QPointF actualWidgetPosition = convertDocumentToWidget(event->point);
174
175 QPointF offset = actualWidgetPosition - lastWidgetPosition;
176
177 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
178 KIS_ASSERT(canvas2);
179 QRect screenRect = QGuiApplication::primaryScreen()->availableVirtualGeometry();
180
181 qreal scaleX = 0;
182 qreal scaleY = 0;
183 canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY);
184
185 const qreal maxBrushSize = KisImageConfig(true).maxBrushSize();
186 const qreal effectiveMaxDragSize = 0.5 * screenRect.width();
187 const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX);
188
189 const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize;
190 const qreal sizeDiff = scaleCoeff * offset.x() ;
191
192 if (qAbs(sizeDiff) > 0.01) {
193 KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
194
195 qreal newSize = m_changeSizeLastPaintOpSize + sizeDiff;
196
197 if (action == ChangeSizeSnap) {
198 newSize = qMax(qRound(newSize), 1);
199 }
200
201 newSize = qBound(0.01, newSize, maxBrushSize);
202
203 settings->setPaintOpSize(newSize);
204
206
207 m_changeSizeLastDocumentPoint = event->point;
209 }
210}
211
224
225void KisToolBasicBrushBase::update(const QRectF &strokeSegmentRect)
226{
227 QRectF segmentRect;
228 QRectF outlineRect;
229 // Segment rect
230 if (mode() == KisTool::PAINT_MODE) {
231 if (strokeSegmentRect.isValid()) {
232 segmentRect = kisGrowRect(strokeSegmentRect, feedbackLineWidth);
233 }
234 }
235 // Outline rect
238 const qreal radius =
240 ? currentPaintOpPreset()->settings()->paintOpSize() / 2.0
241 : m_lastPressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
242 outlineRect =
244 QRectF(m_lastPosition - QPointF(radius, radius), m_lastPosition + QPointF(radius, radius)),
246 );
247 }
248 // Update
249 if (segmentRect.isValid() && outlineRect.isValid()) {
250 updateCanvasPixelRect(segmentRect.united(outlineRect));
251 } else if (segmentRect.isValid()) {
252 updateCanvasPixelRect(segmentRect);
253 } else if (outlineRect.isValid()) {
254 updateCanvasPixelRect(outlineRect);
255 }
256}
257
259 const KoPointerEvent *event,
261{
262 Q_UNUSED(documentPos);
263 Q_UNUSED(event);
264
265 if (!outlineMode.isVisible) {
266 return {};
267 }
268 const qreal radius =
269 mode() != KisTool::PAINT_MODE || outlineMode.forceFullSize
270 ? currentPaintOpPreset()->settings()->paintOpSize() / 2.0
271 : m_lastPressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
272 QPainterPath outline;
273 outline.addEllipse(m_lastPosition, radius, radius);
274 return outline;
275}
276
277void KisToolBasicBrushBase::paint(QPainter &gc, const KoViewConverter &converter)
278{
279 if (mode() == KisTool::PAINT_MODE) {
280 gc.fillPath(pixelToView(m_path), m_previewColor);
281 }
282 KisToolShape::paint(gc, converter);
283}
284
285void KisToolBasicBrushBase::activate(const QSet<KoShape*> &shapes)
286{
287 m_lastPressure = 1.0;
288
290}
291
293{
294 KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
295 KIS_ASSERT_RECOVER_RETURN(kisCanvas);
296 kisCanvas->updateCanvas();
297
299}
300
302{
303 m_previewColor = color;
304}
305
341
347
352
353QPainterPath KisToolBasicBrushBase::generateSegment(const QPointF &point1, qreal radius1, const QPointF &point2, qreal radius2) const
354{
355 const QPointF &p1 = radius1 < radius2 ? point2 : point1;
356 const QPointF &p2 = radius1 < radius2 ? point1 : point2;
357 const qreal &r1 = radius1 < radius2 ? radius2 : radius1;
358 const qreal &r2 = radius1 < radius2 ? radius1 : radius2;
359 const QPointF deltaP1P2 = p2 - p1;
360 const qreal deltaR1R2 = r1 - r2;
361 QPointF tangentPointP11, tangentPointP12, tangentPointP21, tangentPointP22;
362
363 if (qFuzzyIsNull(deltaR1R2)) {
364 // Same radius case
365 const qreal deltaP1P2Length = std::sqrt(deltaP1P2.x() * deltaP1P2.x() + deltaP1P2.y() * deltaP1P2.y());
366 const QPointF deltaP1P2Normalized = deltaP1P2 / deltaP1P2Length;
367 tangentPointP11 = p1 + QPointF(deltaP1P2Normalized.y(), -deltaP1P2Normalized.x()) * r1;
368 tangentPointP12 = p1 + QPointF(-deltaP1P2Normalized.y(), deltaP1P2Normalized.x()) * r1;
369 tangentPointP21 = p2 + QPointF(deltaP1P2Normalized.y(), -deltaP1P2Normalized.x()) * r2;
370 tangentPointP22 = p2 + QPointF(-deltaP1P2Normalized.y(), deltaP1P2Normalized.x()) * r2;
371 } else {
372 // General case
373 const QPointF tangentIntersectionPoint(
374 (p2.x() * r1 - p1.x() * r2) / deltaR1R2,
375 (p2.y() * r1 - p1.y() * r2) / deltaR1R2
376 );
377 auto f = [](qreal t1, qreal t2, qreal t3, qreal t4, qreal sign) -> qreal
378 {
379 return (t1 + sign * t2) / t3 + t4;
380 };
381 {
382 const qreal r1Squared = r1 * r1;
383 const QPointF deltaP1TangentIntersectionPoint = tangentIntersectionPoint - p1;
384 const qreal deltaP1TangentIntersectionPointLengthSquared =
385 deltaP1TangentIntersectionPoint.x() * deltaP1TangentIntersectionPoint.x() +
386 deltaP1TangentIntersectionPoint.y() * deltaP1TangentIntersectionPoint.y();
387 const QPointF t11 = r1Squared * deltaP1TangentIntersectionPoint;
388 const QPointF t12 = r1 * deltaP1TangentIntersectionPoint * std::sqrt(deltaP1TangentIntersectionPointLengthSquared - r1Squared);
389 tangentPointP11 = QPointF(
390 f(t11.x(), t12.y(), deltaP1TangentIntersectionPointLengthSquared, p1.x(), 1.0),
391 f(t11.y(), t12.x(), deltaP1TangentIntersectionPointLengthSquared, p1.y(), -1.0)
392 );
393 tangentPointP12 = QPointF(
394 f(t11.x(), t12.y(), deltaP1TangentIntersectionPointLengthSquared, p1.x(), -1.0),
395 f(t11.y(), t12.x(), deltaP1TangentIntersectionPointLengthSquared, p1.y(), 1.0)
396 );
397 }
398 {
399 const qreal r2Squared = r2 * r2;
400 const QPointF deltaP2TangentIntersectionPoint = tangentIntersectionPoint - p2;
401 const qreal deltaP2TangentIntersectionPointLengthSquared =
402 deltaP2TangentIntersectionPoint.x() * deltaP2TangentIntersectionPoint.x() +
403 deltaP2TangentIntersectionPoint.y() * deltaP2TangentIntersectionPoint.y();
404 const QPointF t11 = r2Squared * deltaP2TangentIntersectionPoint;
405 const QPointF t12 = r2 * deltaP2TangentIntersectionPoint * std::sqrt(deltaP2TangentIntersectionPointLengthSquared - r2Squared);
406 tangentPointP21 = QPointF(
407 f(t11.x(), t12.y(), deltaP2TangentIntersectionPointLengthSquared, p2.x(), 1.0),
408 f(t11.y(), t12.x(), deltaP2TangentIntersectionPointLengthSquared, p2.y(), -1.0)
409 );
410 tangentPointP22 = QPointF(
411 f(t11.x(), t12.y(), deltaP2TangentIntersectionPointLengthSquared, p2.x(), -1.0),
412 f(t11.y(), t12.x(), deltaP2TangentIntersectionPointLengthSquared, p2.y(), 1.0)
413 );
414 }
415 }
416
417 QPainterPath path;
418 path.setFillRule(Qt::WindingFill);
419 path.moveTo(tangentPointP11);
420 path.lineTo(tangentPointP21);
421 path.lineTo(tangentPointP22);
422 path.lineTo(tangentPointP12);
423 path.closeSubpath();
424 path.addEllipse(point2, radius2, radius2);
425 return path;
426}
QPointF r2
QPointF r1
QPointF p2
QPointF p1
KisCoordinatesConverter * coordinatesConverter
void updateCanvas(const QRectF &rc) override
KisViewManager * viewManager() const
static KisConfigNotifier * instance()
QString pressureTabletCurve(bool defaultValue=false) const
CursorStyle newCursorStyle(bool defaultValue=false) const
OutlineStyle newOutlineStyle(bool defaultValue=false) const
bool forceAlwaysFullSizedOutline(bool defaultValue=false) const
bool showOutlineWhilePainting(bool defaultValue=false) const
void imageScale(qreal *scaleX, qreal *scaleY) const
static QCursor pixelBlackCursor()
Definition kis_cursor.cc:44
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
int maxBrushSize(bool defaultValue=false) const
void update(const QRectF &strokeSegmentRect)
void continueAlternateAction(KoPointerEvent *event, AlternateAction action) override
qreal pressureToCurve(qreal pressure)
KisOptimizedBrushOutline getOutlinePath(const QPointF &documentPos, const KoPointerEvent *event, KisPaintOpSettings::OutlineMode outlineMode) override
void beginAlternateAction(KoPointerEvent *event, AlternateAction action) override
void endPrimaryAction(KoPointerEvent *event) override
virtual void finishStroke(const QPainterPath &stroke)=0
QPainterPath generateSegment(const QPointF &point1, qreal radius1, const QPointF &point2, qreal radius2) const
void deactivateAlternateAction(AlternateAction action) override
KisToolBasicBrushBase(KoCanvasBase *canvas, ToolType type, const QCursor &cursor=KisCursor::loadWithSize("tool_outline_selection_cursor.svg", 32, 32, 5, 5))
static constexpr int levelOfPressureResolution
void activate(const QSet< KoShape * > &shapes) override
void mouseMoveEvent(KoPointerEvent *event) override
KoColorDisplayRendererInterface * m_displayRenderer
void setPreviewColor(const QColor &color)
void paint(QPainter &gc, const KoViewConverter &converter) override
void endAlternateAction(KoPointerEvent *event, AlternateAction action) override
QVector< qreal > m_pressureSamples
void beginPrimaryAction(KoPointerEvent *event) override
void continuePrimaryAction(KoPointerEvent *event) override
void activateAlternateAction(AlternateAction action) override
static constexpr int feedbackLineWidth
void setSupportOutline(bool supportOutline)
virtual void requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event)
void setOutlineVisible(bool visible)
void setMode(ToolMode mode) override
virtual void beginShape()
virtual void endShape()
void activate(const QSet< KoShape * > &shapes) override
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
virtual QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
qreal pressure() const
QPointF point
The point in document coordinates.
void useCursor(const QCursor &cursor)
virtual void mouseMoveEvent(KoPointerEvent *event)=0
virtual void deactivate()
QAction * action(const QString &name) const
virtual void paint(QPainter &painter, const KoViewConverter &converter)=0
static bool qFuzzyIsNull(half h)
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
@ OUTLINE_NONE
Definition kis_global.h:54
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
@ 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
#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)
const QVector< qreal > floatTransfer(int size=256) const
static qreal interpolateLinear(qreal normalizedValue, const QVector< qreal > &transfer)
QPointF convertToPixelCoord(KoPointerEvent *e)
Definition kis_tool.cc:189
virtual ToolMode mode() const
Definition kis_tool.cc:407
void updateCanvasPixelRect(const QRectF &pixelRect)
Update the canvas for the given rectangle in image pixel coordinates.
Definition kis_tool.cc:322
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
virtual void continueAlternateAction(KoPointerEvent *event, AlternateAction action)
Definition kis_tool.cc:477
KisPaintOpPresetSP currentPaintOpPreset()
Definition kis_tool.cc:359
QPointF pixelToView(const QPoint &pixelCoord) const
Definition kis_tool.cc:269
virtual void activateAlternateAction(AlternateAction action)
Definition kis_tool.cc:456
virtual void deactivateAlternateAction(AlternateAction action)
Definition kis_tool.cc:461
virtual void endAlternateAction(KoPointerEvent *event, AlternateAction action)
Definition kis_tool.cc:483
@ GESTURE_MODE
Definition kis_tool.h:303
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
bool selectionEditable()
Checks checks if the selection is editable, only applies to local selection as global selection is al...
Definition kis_tool.cc:685
QPointF convertDocumentToWidget(const QPointF &pt)
Definition kis_tool.cc:181
virtual void beginAlternateAction(KoPointerEvent *event, AlternateAction action)
Definition kis_tool.cc:466
AlternateAction
Definition kis_tool.h:134
@ 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
@ UNPAINTABLE
Definition kis_tool.h:152
static KoColorSpaceRegistry * instance()
KisCanvas2 * canvas