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 }