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>
22
24
26 : KisToolShape(canvas, cursor)
27 , m_type(type)
28 , m_previewColor(0, 255, 0, 128)
29{
31 connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings()));
33}
34
37
49
57
59{
60 NodePaintAbility paintability = nodePaintAbility();
61 if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability == KisToolPaint::CLONE || paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE)) || (m_type == SELECT && !selectionEditable())) {
62
63 if (paintability == KisToolPaint::CLONE){
64 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
65 QString message = i18n("This tool cannot paint on clone layers. Please select a paint or vector layer or mask.");
66 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
67 }
68
69 if (paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE) {
70 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
71 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace");
72 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
73 }
74
75 event->ignore();
76 return;
77 }
78
80
81 beginShape();
82
83 const QPointF position = convertToPixelCoord(event);
84 const qreal pressure = pressureToCurve(event->pressure());
85 const qreal radius = pressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
86 m_path = QPainterPath(position);
87 m_path.setFillRule(Qt::WindingFill);
88 m_path.addEllipse(position, radius, radius);
89
90 m_lastPosition = position;
91 m_lastPressure = pressure;
92
93 update(m_path.boundingRect());
94}
95
97{
99
100 const QPointF position = convertToPixelCoord(event);
101 const qreal pressure = pressureToCurve(event->pressure());
102 const qreal brushRadius = currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
103 const QPainterPath segment = generateSegment(m_lastPosition, m_lastPressure * brushRadius, position, pressure * brushRadius);
104 m_path.addPath(segment);
105
106 m_lastPosition = position;
107 m_lastPressure = pressure;
108
109 requestUpdateOutline(event->point, event);
110 update(segment.boundingRect());
111}
112
123
134
145
160
162{
163 if (action != ChangeSize && action != ChangeSizeSnap) {
165 return;
166 }
167
168 QPointF lastWidgetPosition = convertDocumentToWidget(m_changeSizeLastDocumentPoint);
169 QPointF actualWidgetPosition = convertDocumentToWidget(event->point);
170
171 QPointF offset = actualWidgetPosition - lastWidgetPosition;
172
173 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
174 KIS_ASSERT(canvas2);
175 QRect screenRect = QGuiApplication::primaryScreen()->availableVirtualGeometry();
176
177 qreal scaleX = 0;
178 qreal scaleY = 0;
179 canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY);
180
181 const qreal maxBrushSize = KisImageConfig(true).maxBrushSize();
182 const qreal effectiveMaxDragSize = 0.5 * screenRect.width();
183 const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX);
184
185 const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize;
186 const qreal sizeDiff = scaleCoeff * offset.x() ;
187
188 if (qAbs(sizeDiff) > 0.01) {
189 KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
190
191 qreal newSize = m_changeSizeLastPaintOpSize + sizeDiff;
192
193 if (action == ChangeSizeSnap) {
194 newSize = qMax(qRound(newSize), 1);
195 }
196
197 newSize = qBound(0.01, newSize, maxBrushSize);
198
199 settings->setPaintOpSize(newSize);
200
202
203 m_changeSizeLastDocumentPoint = event->point;
205 }
206}
207
220
221void KisToolBasicBrushBase::update(const QRectF &strokeSegmentRect)
222{
223 QRectF segmentRect;
224 QRectF outlineRect;
225 // Segment rect
226 if (mode() == KisTool::PAINT_MODE) {
227 if (strokeSegmentRect.isValid()) {
228 segmentRect = kisGrowRect(strokeSegmentRect, feedbackLineWidth);
229 }
230 }
231 // Outline rect
234 const qreal radius =
236 ? currentPaintOpPreset()->settings()->paintOpSize() / 2.0
237 : m_lastPressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
238 outlineRect =
240 QRectF(m_lastPosition - QPointF(radius, radius), m_lastPosition + QPointF(radius, radius)),
242 );
243 }
244 // Update
245 if (segmentRect.isValid() && outlineRect.isValid()) {
246 updateCanvasPixelRect(segmentRect.united(outlineRect));
247 } else if (segmentRect.isValid()) {
248 updateCanvasPixelRect(segmentRect);
249 } else if (outlineRect.isValid()) {
250 updateCanvasPixelRect(outlineRect);
251 }
252}
253
255 const KoPointerEvent *event,
257{
258 Q_UNUSED(documentPos);
259 Q_UNUSED(event);
260
261 if (!outlineMode.isVisible) {
262 return {};
263 }
264 const qreal radius =
265 mode() != KisTool::PAINT_MODE || outlineMode.forceFullSize
266 ? currentPaintOpPreset()->settings()->paintOpSize() / 2.0
267 : m_lastPressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
268 QPainterPath outline;
269 outline.addEllipse(m_lastPosition, radius, radius);
270 return outline;
271}
272
273void KisToolBasicBrushBase::paint(QPainter &gc, const KoViewConverter &converter)
274{
275 if (mode() == KisTool::PAINT_MODE) {
276 gc.fillPath(pixelToView(m_path), m_previewColor);
277 }
278 KisToolShape::paint(gc, converter);
279}
280
281void KisToolBasicBrushBase::activate(const QSet<KoShape*> &shapes)
282{
283 m_lastPressure = 1.0;
284
286}
287
289{
290 KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
291 KIS_ASSERT_RECOVER_RETURN(kisCanvas);
292 kisCanvas->updateCanvas();
293
295}
296
298{
299 m_previewColor = color;
300}
301
337
342
343QPainterPath KisToolBasicBrushBase::generateSegment(const QPointF &point1, qreal radius1, const QPointF &point2, qreal radius2) const
344{
345 const QPointF &p1 = radius1 < radius2 ? point2 : point1;
346 const QPointF &p2 = radius1 < radius2 ? point1 : point2;
347 const qreal &r1 = radius1 < radius2 ? radius2 : radius1;
348 const qreal &r2 = radius1 < radius2 ? radius1 : radius2;
349 const QPointF deltaP1P2 = p2 - p1;
350 const qreal deltaR1R2 = r1 - r2;
351 QPointF tangentPointP11, tangentPointP12, tangentPointP21, tangentPointP22;
352
353 if (qFuzzyIsNull(deltaR1R2)) {
354 // Same radius case
355 const qreal deltaP1P2Length = std::sqrt(deltaP1P2.x() * deltaP1P2.x() + deltaP1P2.y() * deltaP1P2.y());
356 const QPointF deltaP1P2Normalized = deltaP1P2 / deltaP1P2Length;
357 tangentPointP11 = p1 + QPointF(deltaP1P2Normalized.y(), -deltaP1P2Normalized.x()) * r1;
358 tangentPointP12 = p1 + QPointF(-deltaP1P2Normalized.y(), deltaP1P2Normalized.x()) * r1;
359 tangentPointP21 = p2 + QPointF(deltaP1P2Normalized.y(), -deltaP1P2Normalized.x()) * r2;
360 tangentPointP22 = p2 + QPointF(-deltaP1P2Normalized.y(), deltaP1P2Normalized.x()) * r2;
361 } else {
362 // General case
363 const QPointF tangentIntersectionPoint(
364 (p2.x() * r1 - p1.x() * r2) / deltaR1R2,
365 (p2.y() * r1 - p1.y() * r2) / deltaR1R2
366 );
367 auto f = [](qreal t1, qreal t2, qreal t3, qreal t4, qreal sign) -> qreal
368 {
369 return (t1 + sign * t2) / t3 + t4;
370 };
371 {
372 const qreal r1Squared = r1 * r1;
373 const QPointF deltaP1TangentIntersectionPoint = tangentIntersectionPoint - p1;
374 const qreal deltaP1TangentIntersectionPointLengthSquared =
375 deltaP1TangentIntersectionPoint.x() * deltaP1TangentIntersectionPoint.x() +
376 deltaP1TangentIntersectionPoint.y() * deltaP1TangentIntersectionPoint.y();
377 const QPointF t11 = r1Squared * deltaP1TangentIntersectionPoint;
378 const QPointF t12 = r1 * deltaP1TangentIntersectionPoint * std::sqrt(deltaP1TangentIntersectionPointLengthSquared - r1Squared);
379 tangentPointP11 = QPointF(
380 f(t11.x(), t12.y(), deltaP1TangentIntersectionPointLengthSquared, p1.x(), 1.0),
381 f(t11.y(), t12.x(), deltaP1TangentIntersectionPointLengthSquared, p1.y(), -1.0)
382 );
383 tangentPointP12 = QPointF(
384 f(t11.x(), t12.y(), deltaP1TangentIntersectionPointLengthSquared, p1.x(), -1.0),
385 f(t11.y(), t12.x(), deltaP1TangentIntersectionPointLengthSquared, p1.y(), 1.0)
386 );
387 }
388 {
389 const qreal r2Squared = r2 * r2;
390 const QPointF deltaP2TangentIntersectionPoint = tangentIntersectionPoint - p2;
391 const qreal deltaP2TangentIntersectionPointLengthSquared =
392 deltaP2TangentIntersectionPoint.x() * deltaP2TangentIntersectionPoint.x() +
393 deltaP2TangentIntersectionPoint.y() * deltaP2TangentIntersectionPoint.y();
394 const QPointF t11 = r2Squared * deltaP2TangentIntersectionPoint;
395 const QPointF t12 = r2 * deltaP2TangentIntersectionPoint * std::sqrt(deltaP2TangentIntersectionPointLengthSquared - r2Squared);
396 tangentPointP21 = QPointF(
397 f(t11.x(), t12.y(), deltaP2TangentIntersectionPointLengthSquared, p2.x(), 1.0),
398 f(t11.y(), t12.x(), deltaP2TangentIntersectionPointLengthSquared, p2.y(), -1.0)
399 );
400 tangentPointP22 = QPointF(
401 f(t11.x(), t12.y(), deltaP2TangentIntersectionPointLengthSquared, p2.x(), -1.0),
402 f(t11.y(), t12.x(), deltaP2TangentIntersectionPointLengthSquared, p2.y(), 1.0)
403 );
404 }
405 }
406
407 QPainterPath path;
408 path.setFillRule(Qt::WindingFill);
409 path.moveTo(tangentPointP11);
410 path.lineTo(tangentPointP21);
411 path.lineTo(tangentPointP22);
412 path.lineTo(tangentPointP12);
413 path.closeSubpath();
414 path.addEllipse(point2, radius2, radius2);
415 return path;
416}
QPointF r2
QPointF r1
QPointF p2
QPointF p1
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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)
KisToolBasicBrushBase(KoCanvasBase *canvas, ToolType type, const QCursor &cursor=KisCursor::load("tool_outline_selection_cursor.png", 5, 5))
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
static constexpr int levelOfPressureResolution
void activate(const QSet< KoShape * > &shapes) override
void mouseMoveEvent(KoPointerEvent *event) override
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
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:696
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
KisCanvas2 * canvas