Warning, file /graphics/krita/plugins/tools/tool_enclose_and_fill/subtools/KisToolBasicBrushBase.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  SPDX-FileCopyrightText: 2022 Deif Lou <ginoba@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include <QScreen>
0008 
0009 #include <KoPointerEvent.h>
0010 #include <KoShapeController.h>
0011 #include <KoViewConverter.h>
0012 #include <KisViewManager.h>
0013 #include <KoCanvasBase.h>
0014 #include <kis_icon.h>
0015 #include <kis_canvas2.h>
0016 #include <kis_cubic_curve.h>
0017 #include <kis_config.h>
0018 #include <kis_config_notifier.h>
0019 #include <kis_image_config.h>
0020 #include <brushengine/kis_paintop_preset.h>
0021 #include <kis_tool_utils.h>
0022 
0023 #include "KisToolBasicBrushBase.h"
0024 
0025 KisToolBasicBrushBase::KisToolBasicBrushBase(KoCanvasBase * canvas, ToolType type, const QCursor & cursor)
0026     : KisToolShape(canvas, cursor)
0027     , m_type(type)
0028     , m_previewColor(0, 255, 0, 128)
0029 {
0030     setSupportOutline(true);
0031     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(updateSettings()));
0032     updateSettings();
0033 }
0034 
0035 KisToolBasicBrushBase::~KisToolBasicBrushBase()
0036 {}
0037 
0038 void KisToolBasicBrushBase::updateSettings()
0039 {
0040     KisConfig cfg(true);
0041     // Pressure curve
0042     KisCubicCurve curve(cfg.pressureTabletCurve());
0043     m_pressureSamples = curve.floatTransfer(levelOfPressureResolution + 1);
0044     // Outline options
0045     m_outlineStyle = cfg.newOutlineStyle();
0046     m_showOutlineWhilePainting = cfg.showOutlineWhilePainting();
0047     m_forceAlwaysFullSizedOutline = cfg.forceAlwaysFullSizedOutline();
0048 }
0049 
0050 void KisToolBasicBrushBase::mouseMoveEvent(KoPointerEvent *event)
0051 {
0052     if (mode() == KisTool::HOVER_MODE) {
0053         m_lastPosition = convertToPixelCoord(event);
0054     }
0055     KisToolShape::mouseMoveEvent(event);
0056 }
0057 
0058 void KisToolBasicBrushBase::beginPrimaryAction(KoPointerEvent *event)
0059 {
0060     NodePaintAbility paintability = nodePaintAbility();
0061     if ((m_type == PAINT && (!nodeEditable() || paintability == UNPAINTABLE || paintability  == KisToolPaint::CLONE || paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE)) || (m_type == SELECT && !selectionEditable())) {
0062 
0063         if (paintability == KisToolPaint::CLONE){
0064             KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
0065             QString message = i18n("This tool cannot paint on clone layers.  Please select a paint or vector layer or mask.");
0066             kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
0067         }
0068 
0069         if (paintability == KisToolPaint::MYPAINTBRUSH_UNPAINTABLE) {
0070             KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas());
0071             QString message = i18n("The MyPaint Brush Engine is not available for this colorspace");
0072             kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
0073         }
0074 
0075         event->ignore();
0076         return;
0077     }
0078 
0079     setMode(KisTool::PAINT_MODE);
0080 
0081     beginShape();
0082 
0083     const QPointF position = convertToPixelCoord(event);
0084     const qreal pressure = pressureToCurve(event->pressure());
0085     const qreal radius = pressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
0086     m_path = QPainterPath(position);
0087     m_path.setFillRule(Qt::WindingFill);
0088     m_path.addEllipse(position, radius, radius);
0089 
0090     m_lastPosition = position;
0091     m_lastPressure = pressure;
0092 
0093     update(m_path.boundingRect());
0094 }
0095 
0096 void KisToolBasicBrushBase::continuePrimaryAction(KoPointerEvent *event)
0097 {
0098     CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
0099 
0100     const QPointF position = convertToPixelCoord(event);
0101     const qreal pressure = pressureToCurve(event->pressure());
0102     const qreal brushRadius = currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
0103     const QPainterPath segment = generateSegment(m_lastPosition, m_lastPressure * brushRadius, position, pressure * brushRadius);
0104     m_path.addPath(segment);
0105 
0106     m_lastPosition = position;
0107     m_lastPressure = pressure;
0108 
0109     requestUpdateOutline(event->point, event);
0110     update(segment.boundingRect());
0111 }
0112 
0113 void KisToolBasicBrushBase::endPrimaryAction(KoPointerEvent *event)
0114 {
0115     Q_UNUSED(event);
0116 
0117     CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
0118     setMode(KisTool::HOVER_MODE);
0119 
0120     endShape();
0121     finishStroke(m_path);
0122 }
0123 
0124 void KisToolBasicBrushBase::activateAlternateAction(AlternateAction action)
0125 {
0126     if (action != ChangeSize && action != ChangeSizeSnap) {
0127         KisToolShape::activateAlternateAction(action);
0128         return;
0129     }
0130 
0131     useCursor(KisCursor::blankCursor());
0132     setOutlineVisible(true);
0133 }
0134 
0135 void KisToolBasicBrushBase::deactivateAlternateAction(AlternateAction action)
0136 {
0137     if (action != ChangeSize && action != ChangeSizeSnap) {
0138         KisToolShape::deactivateAlternateAction(action);
0139         return;
0140     }
0141 
0142     resetCursorStyle();
0143     setOutlineVisible(false);
0144 }
0145 
0146 void KisToolBasicBrushBase::beginAlternateAction(KoPointerEvent *event, AlternateAction action)
0147 {
0148     if (action != ChangeSize && action != ChangeSizeSnap) {
0149         KisToolShape::beginAlternateAction(event, action);
0150         return;
0151     }
0152 
0153     setMode(GESTURE_MODE);
0154     m_changeSizeInitialGestureDocPoint = event->point;
0155     m_changeSizeInitialGestureGlobalPoint = QCursor::pos();
0156 
0157     m_changeSizeLastDocumentPoint = event->point;
0158     m_changeSizeLastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize();
0159 }
0160 
0161 void KisToolBasicBrushBase::continueAlternateAction(KoPointerEvent *event, AlternateAction action)
0162 {
0163     if (action != ChangeSize && action != ChangeSizeSnap) {
0164         KisToolShape::continueAlternateAction(event, action);
0165         return;
0166     }
0167 
0168     QPointF lastWidgetPosition = convertDocumentToWidget(m_changeSizeLastDocumentPoint);
0169     QPointF actualWidgetPosition = convertDocumentToWidget(event->point);
0170 
0171     QPointF offset = actualWidgetPosition - lastWidgetPosition;
0172 
0173     KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas());
0174     KIS_ASSERT(canvas2);
0175     QRect screenRect = QGuiApplication::primaryScreen()->availableVirtualGeometry();
0176 
0177     qreal scaleX = 0;
0178     qreal scaleY = 0;
0179     canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY);
0180 
0181     const qreal maxBrushSize = KisImageConfig(true).maxBrushSize();
0182     const qreal effectiveMaxDragSize = 0.5 * screenRect.width();
0183     const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX);
0184 
0185     const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize;
0186     const qreal sizeDiff = scaleCoeff * offset.x() ;
0187 
0188     if (qAbs(sizeDiff) > 0.01) {
0189         KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings();
0190 
0191         qreal newSize = m_changeSizeLastPaintOpSize + sizeDiff;
0192 
0193         if (action == ChangeSizeSnap) {
0194             newSize = qMax(qRound(newSize), 1);
0195         }
0196 
0197         newSize = qBound(0.01, newSize, maxBrushSize);
0198 
0199         settings->setPaintOpSize(newSize);
0200 
0201         requestUpdateOutline(m_changeSizeInitialGestureDocPoint, 0);
0202 
0203         m_changeSizeLastDocumentPoint = event->point;
0204         m_changeSizeLastPaintOpSize = newSize;
0205     }
0206 }
0207 
0208 void KisToolBasicBrushBase::endAlternateAction(KoPointerEvent *event, AlternateAction action)
0209 {
0210     if (action != ChangeSize && action != ChangeSizeSnap) {
0211         KisToolShape::endAlternateAction(event, action);
0212         return;
0213     }
0214 
0215     KisToolUtils::setCursorPos(m_changeSizeInitialGestureGlobalPoint);
0216     requestUpdateOutline(m_changeSizeInitialGestureDocPoint, 0);
0217 
0218     setMode(HOVER_MODE);
0219 }
0220 
0221 void KisToolBasicBrushBase::update(const QRectF &strokeSegmentRect)
0222 {
0223     QRectF segmentRect;
0224     QRectF outlineRect;
0225     // Segment rect
0226     if (mode() == KisTool::PAINT_MODE) {
0227         if (strokeSegmentRect.isValid()) {
0228             segmentRect = kisGrowRect(strokeSegmentRect, feedbackLineWidth);
0229         }
0230     }
0231     // Outline rect
0232     if (m_outlineStyle != OUTLINE_NONE &&
0233         (mode() != KisTool::PAINT_MODE || m_showOutlineWhilePainting)) {
0234         const qreal radius =
0235             m_forceAlwaysFullSizedOutline
0236             ? currentPaintOpPreset()->settings()->paintOpSize() / 2.0
0237             : m_lastPressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
0238         outlineRect = 
0239             kisGrowRect(
0240                 QRectF(m_lastPosition - QPointF(radius, radius), m_lastPosition + QPointF(radius, radius)),
0241                 feedbackLineWidth
0242             );
0243     }
0244     // Update
0245     if (segmentRect.isValid() && outlineRect.isValid()) {
0246         updateCanvasPixelRect(segmentRect.united(outlineRect));
0247     } else if (segmentRect.isValid()) {
0248         updateCanvasPixelRect(segmentRect);
0249     } else if (outlineRect.isValid()) {
0250         updateCanvasPixelRect(outlineRect);
0251     }
0252 }
0253 
0254 KisOptimizedBrushOutline KisToolBasicBrushBase::getOutlinePath(const QPointF &documentPos,
0255                                                                const KoPointerEvent *event,
0256                                                                KisPaintOpSettings::OutlineMode outlineMode)
0257 {
0258     Q_UNUSED(documentPos);
0259     Q_UNUSED(event);
0260 
0261     if (!outlineMode.isVisible) {
0262         return {};
0263     }
0264     const qreal radius =
0265         mode() != KisTool::PAINT_MODE || outlineMode.forceFullSize
0266         ? currentPaintOpPreset()->settings()->paintOpSize() / 2.0
0267         : m_lastPressure * currentPaintOpPreset()->settings()->paintOpSize() / 2.0;
0268     QPainterPath outline;
0269     outline.addEllipse(m_lastPosition, radius, radius);
0270     return outline;
0271 }
0272 
0273 void KisToolBasicBrushBase::paint(QPainter &gc, const KoViewConverter &converter)
0274 {
0275     if (mode() == KisTool::PAINT_MODE) {
0276         gc.fillPath(pixelToView(m_path), m_previewColor);
0277     }
0278     KisToolShape::paint(gc, converter);
0279 }
0280 
0281 void KisToolBasicBrushBase::activate(const QSet<KoShape*> &shapes)
0282 {
0283     m_lastPressure = 1.0;
0284     
0285     KisToolShape::activate(shapes);
0286 }
0287 
0288 void KisToolBasicBrushBase::deactivate()
0289 {
0290     KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
0291     KIS_ASSERT_RECOVER_RETURN(kisCanvas);
0292     kisCanvas->updateCanvas();
0293 
0294     KisToolShape::deactivate();
0295 }
0296 
0297 void KisToolBasicBrushBase::setPreviewColor(const QColor &color)
0298 {
0299     m_previewColor = color;
0300 }
0301 
0302 void KisToolBasicBrushBase::resetCursorStyle()
0303 {
0304     KisConfig cfg(true);
0305 
0306     switch (cfg.newCursorStyle()) {
0307     case CURSOR_STYLE_NO_CURSOR:
0308         useCursor(KisCursor::blankCursor());
0309         break;
0310     case CURSOR_STYLE_POINTER:
0311         useCursor(KisCursor::arrowCursor());
0312         break;
0313     case CURSOR_STYLE_SMALL_ROUND:
0314         useCursor(KisCursor::roundCursor());
0315         break;
0316     case CURSOR_STYLE_CROSSHAIR:
0317         useCursor(KisCursor::crossCursor());
0318         break;
0319     case CURSOR_STYLE_TRIANGLE_RIGHTHANDED:
0320         useCursor(KisCursor::triangleRightHandedCursor());
0321         break;
0322     case CURSOR_STYLE_TRIANGLE_LEFTHANDED:
0323         useCursor(KisCursor::triangleLeftHandedCursor());
0324         break;
0325     case CURSOR_STYLE_BLACK_PIXEL:
0326         useCursor(KisCursor::pixelBlackCursor());
0327         break;
0328     case CURSOR_STYLE_WHITE_PIXEL:
0329         useCursor(KisCursor::pixelWhiteCursor());
0330         break;
0331     case CURSOR_STYLE_TOOLICON:
0332     default:
0333         KisToolPaint::resetCursorStyle();
0334         break;
0335     }
0336 }
0337 
0338 qreal KisToolBasicBrushBase::pressureToCurve(qreal pressure)
0339 {
0340     return KisCubicCurve::interpolateLinear(pressure, m_pressureSamples);
0341 }
0342 
0343 QPainterPath KisToolBasicBrushBase::generateSegment(const QPointF &point1, qreal radius1, const QPointF &point2, qreal radius2) const
0344 {
0345     const QPointF &p1 = radius1 < radius2 ? point2 : point1;
0346     const QPointF &p2 = radius1 < radius2 ? point1 : point2;
0347     const qreal &r1 = radius1 < radius2 ? radius2 : radius1;
0348     const qreal &r2 = radius1 < radius2 ? radius1 : radius2;
0349     const QPointF deltaP1P2 = p2 - p1;
0350     const qreal deltaR1R2 = r1 - r2;
0351     QPointF tangentPointP11, tangentPointP12, tangentPointP21, tangentPointP22;
0352 
0353     if (qFuzzyIsNull(deltaR1R2)) {
0354         // Same radius case
0355         const qreal deltaP1P2Length = std::sqrt(deltaP1P2.x() * deltaP1P2.x() + deltaP1P2.y() * deltaP1P2.y());
0356         const QPointF deltaP1P2Normalized = deltaP1P2 / deltaP1P2Length;
0357         tangentPointP11 = p1 + QPointF(deltaP1P2Normalized.y(), -deltaP1P2Normalized.x()) * r1;
0358         tangentPointP12 = p1 + QPointF(-deltaP1P2Normalized.y(), deltaP1P2Normalized.x()) * r1;
0359         tangentPointP21 = p2 + QPointF(deltaP1P2Normalized.y(), -deltaP1P2Normalized.x()) * r2;
0360         tangentPointP22 = p2 + QPointF(-deltaP1P2Normalized.y(), deltaP1P2Normalized.x()) * r2;
0361     } else {
0362         // General case
0363         const QPointF tangentIntersectionPoint(
0364             (p2.x() * r1 - p1.x() * r2) / deltaR1R2,
0365             (p2.y() * r1 - p1.y() * r2) / deltaR1R2
0366         );
0367         auto f = [](qreal t1, qreal t2, qreal t3, qreal t4, qreal sign) -> qreal
0368         {
0369             return (t1 + sign * t2) / t3 + t4;
0370         };
0371         {
0372             const qreal r1Squared = r1 * r1;
0373             const QPointF deltaP1TangentIntersectionPoint = tangentIntersectionPoint - p1;
0374             const qreal deltaP1TangentIntersectionPointLengthSquared = 
0375                 deltaP1TangentIntersectionPoint.x() * deltaP1TangentIntersectionPoint.x() +
0376                 deltaP1TangentIntersectionPoint.y() * deltaP1TangentIntersectionPoint.y();
0377             const QPointF t11 = r1Squared * deltaP1TangentIntersectionPoint;
0378             const QPointF t12 = r1 * deltaP1TangentIntersectionPoint * std::sqrt(deltaP1TangentIntersectionPointLengthSquared - r1Squared);
0379             tangentPointP11 = QPointF(
0380                 f(t11.x(), t12.y(), deltaP1TangentIntersectionPointLengthSquared, p1.x(), 1.0),
0381                 f(t11.y(), t12.x(), deltaP1TangentIntersectionPointLengthSquared, p1.y(), -1.0)
0382             );
0383             tangentPointP12 = QPointF(
0384                 f(t11.x(), t12.y(), deltaP1TangentIntersectionPointLengthSquared, p1.x(), -1.0),
0385                 f(t11.y(), t12.x(), deltaP1TangentIntersectionPointLengthSquared, p1.y(), 1.0)
0386             );
0387         }
0388         {
0389             const qreal r2Squared = r2 * r2;
0390             const QPointF deltaP2TangentIntersectionPoint = tangentIntersectionPoint - p2;
0391             const qreal deltaP2TangentIntersectionPointLengthSquared = 
0392                 deltaP2TangentIntersectionPoint.x() * deltaP2TangentIntersectionPoint.x() +
0393                 deltaP2TangentIntersectionPoint.y() * deltaP2TangentIntersectionPoint.y();
0394             const QPointF t11 = r2Squared * deltaP2TangentIntersectionPoint;
0395             const QPointF t12 = r2 * deltaP2TangentIntersectionPoint * std::sqrt(deltaP2TangentIntersectionPointLengthSquared - r2Squared);
0396             tangentPointP21 = QPointF(
0397                 f(t11.x(), t12.y(), deltaP2TangentIntersectionPointLengthSquared, p2.x(), 1.0),
0398                 f(t11.y(), t12.x(), deltaP2TangentIntersectionPointLengthSquared, p2.y(), -1.0)
0399             );
0400             tangentPointP22 = QPointF(
0401                 f(t11.x(), t12.y(), deltaP2TangentIntersectionPointLengthSquared, p2.x(), -1.0),
0402                 f(t11.y(), t12.x(), deltaP2TangentIntersectionPointLengthSquared, p2.y(), 1.0)
0403             );
0404         }
0405     }
0406 
0407     QPainterPath path;
0408     path.setFillRule(Qt::WindingFill);
0409     path.moveTo(tangentPointP11);
0410     path.lineTo(tangentPointP21);
0411     path.lineTo(tangentPointP22);
0412     path.lineTo(tangentPointP12);
0413     path.closeSubpath();
0414     path.addEllipse(point2, radius2, radius2);
0415     return path;
0416 }