File indexing completed on 2024-12-22 04:13:00
0001 /* 0002 * kis_tool_freehand.cc - part of Krita 0003 * 0004 * SPDX-FileCopyrightText: 2003-2007 Boudewijn Rempt <boud@valdyas.org> 0005 * SPDX-FileCopyrightText: 2004 Bart Coppens <kde@bartcoppens.be> 0006 * SPDX-FileCopyrightText: 2007, 2008, 2010 Cyrille Berger <cberger@cberger.net> 0007 * SPDX-FileCopyrightText: 2009 Lukáš Tvrdý <lukast.dev@gmail.com> 0008 * 0009 * SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "kis_tool_freehand.h" 0013 #include <QPainter> 0014 #include <QRect> 0015 #include <QThreadPool> 0016 #include <QApplication> 0017 #include <QDesktopWidget> 0018 #include <QScreen> 0019 0020 #include <Eigen/Core> 0021 0022 #include <kis_icon.h> 0023 #include <KoPointerEvent.h> 0024 #include <KoViewConverter.h> 0025 #include <KoCanvasController.h> 0026 0027 //pop up palette 0028 #include <kis_canvas_resource_provider.h> 0029 0030 // Krita/image 0031 #include <kis_layer.h> 0032 #include <kis_paint_layer.h> 0033 #include <kis_painter.h> 0034 #include <brushengine/kis_paintop.h> 0035 #include <kis_selection.h> 0036 #include <brushengine/kis_paintop_preset.h> 0037 #include <brushengine/KisOptimizedBrushOutline.h> 0038 0039 0040 // Krita/ui 0041 #include "kis_abstract_perspective_grid.h" 0042 #include "kis_config.h" 0043 #include "kis_image_config.h" 0044 #include "canvas/kis_canvas2.h" 0045 #include "kis_cursor.h" 0046 #include <KisViewManager.h> 0047 #include <kis_painting_assistants_decoration.h> 0048 #include "kis_painting_information_builder.h" 0049 #include "kis_tool_freehand_helper.h" 0050 #include "strokes/freehand_stroke.h" 0051 #include "kis_tool_utils.h" 0052 0053 using namespace std::placeholders; // For _1 placeholder 0054 0055 0056 KisToolFreehand::KisToolFreehand(KoCanvasBase * canvas, const QCursor & cursor, const KUndo2MagicString &transactionText) 0057 : KisToolPaint(canvas, cursor), 0058 m_brushResizeCompressor(200, std::bind(&KisToolFreehand::slotDoResizeBrush, this, _1)) 0059 { 0060 0061 setSupportOutline(true); 0062 setMaskSyntheticEvents(KisConfig(true).disableTouchOnCanvas()); // Disallow mouse events from finger presses unless enabled 0063 0064 m_infoBuilder = new KisToolFreehandPaintingInformationBuilder(this); 0065 m_helper = new KisToolFreehandHelper(m_infoBuilder, canvas->resourceManager(), transactionText); 0066 0067 connect(m_helper, SIGNAL(requestExplicitUpdateOutline()), SLOT(explicitUpdateOutline())); 0068 0069 connect(qobject_cast<KisCanvas2*>(canvas)->viewManager(), SIGNAL(brushOutlineToggled()), SLOT(explicitUpdateOutline())); 0070 0071 KisCanvasResourceProvider *provider = qobject_cast<KisCanvas2*>(canvas)->viewManager()->canvasResourceProvider(); 0072 0073 connect(provider, SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(explicitUpdateOutline())); 0074 connect(provider, SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(resetCursorStyle())); 0075 connect(provider, SIGNAL(sigPaintOpPresetChanged(KisPaintOpPresetSP)), SLOT(explicitUpdateOutline())); 0076 connect(provider, SIGNAL(sigPaintOpPresetChanged(KisPaintOpPresetSP)), SLOT(resetCursorStyle())); 0077 } 0078 0079 KisToolFreehand::~KisToolFreehand() 0080 { 0081 delete m_helper; 0082 delete m_infoBuilder; 0083 } 0084 0085 void KisToolFreehand::mouseMoveEvent(KoPointerEvent *event) 0086 { 0087 KisToolPaint::mouseMoveEvent(event); 0088 m_helper->cursorMoved(convertToPixelCoord(event)); 0089 } 0090 0091 KisSmoothingOptionsSP KisToolFreehand::smoothingOptions() const 0092 { 0093 return m_helper->smoothingOptions(); 0094 } 0095 0096 void KisToolFreehand::resetCursorStyle() 0097 { 0098 KisConfig cfg(true); 0099 0100 bool useSeparateEraserCursor = cfg.separateEraserCursor() && isEraser(); 0101 0102 switch (useSeparateEraserCursor ? cfg.eraserCursorStyle() : cfg.newCursorStyle()) { 0103 case CURSOR_STYLE_NO_CURSOR: 0104 useCursor(KisCursor::blankCursor()); 0105 break; 0106 case CURSOR_STYLE_POINTER: 0107 useCursor(KisCursor::arrowCursor()); 0108 break; 0109 case CURSOR_STYLE_SMALL_ROUND: 0110 useCursor(KisCursor::roundCursor()); 0111 break; 0112 case CURSOR_STYLE_CROSSHAIR: 0113 useCursor(KisCursor::crossCursor()); 0114 break; 0115 case CURSOR_STYLE_TRIANGLE_RIGHTHANDED: 0116 useCursor(KisCursor::triangleRightHandedCursor()); 0117 break; 0118 case CURSOR_STYLE_TRIANGLE_LEFTHANDED: 0119 useCursor(KisCursor::triangleLeftHandedCursor()); 0120 break; 0121 case CURSOR_STYLE_BLACK_PIXEL: 0122 useCursor(KisCursor::pixelBlackCursor()); 0123 break; 0124 case CURSOR_STYLE_WHITE_PIXEL: 0125 useCursor(KisCursor::pixelWhiteCursor()); 0126 break; 0127 case CURSOR_STYLE_ERASER: 0128 useCursor(KisCursor::eraserCursor()); 0129 break; 0130 case CURSOR_STYLE_TOOLICON: 0131 default: 0132 KisToolPaint::resetCursorStyle(); 0133 break; 0134 } 0135 } 0136 0137 KisPaintingInformationBuilder* KisToolFreehand::paintingInformationBuilder() const 0138 { 0139 return m_infoBuilder; 0140 } 0141 0142 void KisToolFreehand::resetHelper(KisToolFreehandHelper *helper) 0143 { 0144 delete m_helper; 0145 m_helper = helper; 0146 } 0147 0148 bool KisToolFreehand::supportsPaintingAssistants() const 0149 { 0150 return true; 0151 } 0152 0153 int KisToolFreehand::flags() const 0154 { 0155 return KisTool::FLAG_USES_CUSTOM_COMPOSITEOP|KisTool::FLAG_USES_CUSTOM_PRESET 0156 |KisTool::FLAG_USES_CUSTOM_SIZE; 0157 } 0158 0159 void KisToolFreehand::activate(const QSet<KoShape*> &shapes) 0160 { 0161 KisToolPaint::activate(shapes); 0162 } 0163 0164 void KisToolFreehand::deactivate() 0165 { 0166 if (mode() == PAINT_MODE) { 0167 endStroke(); 0168 setMode(KisTool::HOVER_MODE); 0169 } 0170 KisToolPaint::deactivate(); 0171 } 0172 0173 void KisToolFreehand::initStroke(KoPointerEvent *event) 0174 { 0175 m_helper->initPaint(event, 0176 convertToPixelCoord(event), 0177 image(), 0178 currentNode(), 0179 image().data()); 0180 } 0181 0182 void KisToolFreehand::doStroke(KoPointerEvent *event) 0183 { 0184 m_helper->paintEvent(event); 0185 } 0186 0187 void KisToolFreehand::endStroke() 0188 { 0189 m_helper->endPaint(); 0190 bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mouseReleaseEvent(); 0191 Q_UNUSED(paintOpIgnoredEvent); 0192 } 0193 0194 bool KisToolFreehand::primaryActionSupportsHiResEvents() const 0195 { 0196 return true; 0197 } 0198 0199 void KisToolFreehand::beginPrimaryAction(KoPointerEvent *event) 0200 { 0201 // FIXME: workaround for the Duplicate Op 0202 trySampleByPaintOp(event, SampleFgImage); 0203 0204 requestUpdateOutline(event->point, event); 0205 0206 NodePaintAbility paintability = nodePaintAbility(); 0207 // XXX: move this to KisTool and make it work properly for clone layers: for clone layers, the shape paint tools don't work either 0208 if (!nodeEditable() || paintability != PAINT) { 0209 if (paintability == KisToolPaint::VECTOR || paintability == KisToolPaint::CLONE){ 0210 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); 0211 QString message = i18n("The brush tool cannot paint on this layer. Please select a paint layer or mask."); 0212 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); 0213 } 0214 else if (paintability == MYPAINTBRUSH_UNPAINTABLE) { 0215 KisCanvas2 * kiscanvas = static_cast<KisCanvas2*>(canvas()); 0216 QString message = i18n("The MyPaint Brush Engine is not available for this colorspace"); 0217 kiscanvas->viewManager()->showFloatingMessage(message, koIcon("object-locked")); 0218 } 0219 event->ignore(); 0220 0221 return; 0222 } 0223 0224 KIS_SAFE_ASSERT_RECOVER_RETURN(!m_helper->isRunning()); 0225 0226 setMode(KisTool::PAINT_MODE); 0227 0228 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas()); 0229 if (canvas2) { 0230 canvas2->viewManager()->disableControls(); 0231 } 0232 0233 initStroke(event); 0234 } 0235 0236 void KisToolFreehand::continuePrimaryAction(KoPointerEvent *event) 0237 { 0238 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0239 0240 requestUpdateOutline(event->point, event); 0241 0242 /** 0243 * Actual painting 0244 */ 0245 doStroke(event); 0246 } 0247 0248 void KisToolFreehand::endPrimaryAction(KoPointerEvent *event) 0249 { 0250 Q_UNUSED(event); 0251 CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); 0252 0253 endStroke(); 0254 0255 if (m_assistant && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) { 0256 static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->endStroke(); 0257 } 0258 0259 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas()); 0260 if (canvas2) { 0261 canvas2->viewManager()->enableControls(); 0262 } 0263 0264 setMode(KisTool::HOVER_MODE); 0265 } 0266 0267 bool KisToolFreehand::trySampleByPaintOp(KoPointerEvent *event, AlternateAction action) 0268 { 0269 if (action != SampleFgNode && action != SampleFgImage) return false; 0270 0271 /** 0272 * FIXME: we need some better way to implement modifiers 0273 * for a paintop level. This method is used in DuplicateOp only! 0274 */ 0275 QPointF pos = adjustPosition(event->point, event->point); 0276 qreal perspective = calculatePerspective(pos); 0277 if (!currentPaintOpPreset()) { 0278 return false; 0279 } 0280 KisPaintInformation info(convertToPixelCoord(event->point), 0281 m_infoBuilder->pressureToCurve(event->pressure()), 0282 event->xTilt(), event->yTilt(), 0283 event->rotation(), 0284 event->tangentialPressure(), 0285 perspective, 0, 0); 0286 info.setRandomSource(new KisRandomSource()); 0287 info.setPerStrokeRandomSource(new KisPerStrokeRandomSource()); 0288 0289 bool paintOpIgnoredEvent = currentPaintOpPreset()->settings()->mousePressEvent(info, 0290 event->modifiers(), 0291 currentNode()); 0292 // DuplicateOP during the sampling of new source point (origin) 0293 // is the only paintop that returns "false" here 0294 return !paintOpIgnoredEvent; 0295 } 0296 0297 void KisToolFreehand::activateAlternateAction(AlternateAction action) 0298 { 0299 if (action != ChangeSize && action != ChangeSizeSnap) { 0300 KisToolPaint::activateAlternateAction(action); 0301 return; 0302 } 0303 0304 useCursor(KisCursor::blankCursor()); 0305 setOutlineVisible(true); 0306 } 0307 0308 void KisToolFreehand::deactivateAlternateAction(AlternateAction action) 0309 { 0310 if (action != ChangeSize && action != ChangeSizeSnap) { 0311 KisToolPaint::deactivateAlternateAction(action); 0312 return; 0313 } 0314 0315 resetCursorStyle(); 0316 setOutlineVisible(false); 0317 } 0318 0319 void KisToolFreehand::beginAlternateAction(KoPointerEvent *event, AlternateAction action) 0320 { 0321 if (trySampleByPaintOp(event, action)) { 0322 m_paintopBasedSamplingInAction = true; 0323 return; 0324 } 0325 0326 if (action != ChangeSize && action != ChangeSizeSnap) { 0327 KisToolPaint::beginAlternateAction(event, action); 0328 return; 0329 } 0330 0331 setMode(GESTURE_MODE); 0332 m_initialGestureDocPoint = event->point; 0333 m_initialGestureGlobalPoint = QCursor::pos(); 0334 0335 m_lastDocumentPoint = event->point; 0336 m_lastPaintOpSize = currentPaintOpPreset()->settings()->paintOpSize(); 0337 } 0338 0339 void KisToolFreehand::continueAlternateAction(KoPointerEvent *event, AlternateAction action) 0340 { 0341 if (trySampleByPaintOp(event, action) || m_paintopBasedSamplingInAction) return; 0342 0343 if (action != ChangeSize && action != ChangeSizeSnap) { 0344 KisToolPaint::continueAlternateAction(event, action); 0345 return; 0346 } 0347 0348 QPointF lastWidgetPosition = convertDocumentToWidget(m_lastDocumentPoint); 0349 QPointF actualWidgetPosition = convertDocumentToWidget(event->point); 0350 0351 QPointF offset = actualWidgetPosition - lastWidgetPosition; 0352 0353 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2 *>(canvas()); 0354 KIS_SAFE_ASSERT_RECOVER_RETURN(canvas2); 0355 QRect screenRect = QGuiApplication::primaryScreen()->availableVirtualGeometry(); 0356 0357 qreal scaleX = 0; 0358 qreal scaleY = 0; 0359 canvas2->coordinatesConverter()->imageScale(&scaleX, &scaleY); 0360 0361 const qreal maxBrushSize = KisImageConfig(true).maxBrushSize(); 0362 const qreal effectiveMaxDragSize = 0.5 * screenRect.width(); 0363 const qreal effectiveMaxBrushSize = qMin(maxBrushSize, effectiveMaxDragSize / scaleX); 0364 0365 const qreal scaleCoeff = effectiveMaxBrushSize / effectiveMaxDragSize; 0366 const qreal sizeDiff = scaleCoeff * offset.x() ; 0367 0368 if (qAbs(sizeDiff) > 0.01) { 0369 KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); 0370 0371 qreal newSize = m_lastPaintOpSize + sizeDiff; 0372 0373 if (action == ChangeSizeSnap) { 0374 newSize = qMax(qRound(newSize), 1); 0375 } 0376 0377 newSize = qBound(0.01, newSize, maxBrushSize); 0378 0379 settings->setPaintOpSize(newSize); 0380 0381 requestUpdateOutline(m_initialGestureDocPoint, 0); 0382 //m_brushResizeCompressor.start(newSize); 0383 0384 m_lastDocumentPoint = event->point; 0385 m_lastPaintOpSize = newSize; 0386 } 0387 } 0388 0389 void KisToolFreehand::endAlternateAction(KoPointerEvent *event, AlternateAction action) 0390 { 0391 if (trySampleByPaintOp(event, action) || m_paintopBasedSamplingInAction) { 0392 m_paintopBasedSamplingInAction = false; 0393 return; 0394 } 0395 0396 if (action != ChangeSize && action != ChangeSizeSnap) { 0397 KisToolPaint::endAlternateAction(event, action); 0398 return; 0399 } 0400 0401 KisToolUtils::setCursorPos(m_initialGestureGlobalPoint); 0402 requestUpdateOutline(m_initialGestureDocPoint, 0); 0403 0404 setMode(HOVER_MODE); 0405 } 0406 0407 bool KisToolFreehand::wantsAutoScroll() const 0408 { 0409 return false; 0410 } 0411 0412 void KisToolFreehand::setAssistant(bool assistant) 0413 { 0414 m_assistant = assistant; 0415 } 0416 0417 void KisToolFreehand::setOnlyOneAssistantSnap(bool assistant) 0418 { 0419 m_only_one_assistant = assistant; 0420 } 0421 0422 void KisToolFreehand::setSnapEraser(bool assistant) 0423 { 0424 m_eraser_snapping = assistant; 0425 } 0426 0427 void KisToolFreehand::slotDoResizeBrush(qreal newSize) 0428 { 0429 KisPaintOpSettingsSP settings = currentPaintOpPreset()->settings(); 0430 0431 settings->setPaintOpSize(newSize); 0432 requestUpdateOutline(m_initialGestureDocPoint, 0); 0433 0434 } 0435 0436 QPointF KisToolFreehand::adjustPosition(const QPointF& point, const QPointF& strokeBegin) 0437 { 0438 if (m_assistant && static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()) { 0439 KisCanvas2* c = static_cast<KisCanvas2*>(canvas()); 0440 c->paintingAssistantsDecoration()->setOnlyOneAssistantSnap(m_only_one_assistant); 0441 c->paintingAssistantsDecoration()->setEraserSnap(m_eraser_snapping); 0442 QPointF ap = c->paintingAssistantsDecoration()->adjustPosition(point, strokeBegin); 0443 QPointF fp = (1.0 - m_magnetism) * point + m_magnetism * ap; 0444 // Report the final position back to the assistant so the guides 0445 // can follow the brush 0446 c->paintingAssistantsDecoration()->setAdjustedBrushPosition(fp); 0447 return fp; 0448 } 0449 return point; 0450 } 0451 0452 qreal KisToolFreehand::calculatePerspective(const QPointF &documentPoint) 0453 { 0454 qreal perspective = 1.0; 0455 Q_FOREACH (const KisPaintingAssistantSP assistant, static_cast<KisCanvas2*>(canvas())->paintingAssistantsDecoration()->assistants()) { 0456 QPointer<KisAbstractPerspectiveGrid> grid = dynamic_cast<KisAbstractPerspectiveGrid*>(assistant.data()); 0457 if (grid && grid->isActive() && grid->contains(documentPoint)) { 0458 perspective = grid->distance(documentPoint); 0459 break; 0460 } 0461 } 0462 return perspective; 0463 } 0464 0465 void KisToolFreehand::explicitUpdateOutline() 0466 { 0467 requestUpdateOutline(m_outlineDocPoint, 0); 0468 } 0469 0470 KisOptimizedBrushOutline KisToolFreehand::getOutlinePath(const QPointF &documentPos, 0471 const KoPointerEvent *event, 0472 KisPaintOpSettings::OutlineMode outlineMode) 0473 { 0474 if (currentPaintOpPreset()) 0475 return m_helper->paintOpOutline(convertToPixelCoord(documentPos), 0476 event, 0477 currentPaintOpPreset()->settings(), 0478 outlineMode); 0479 else 0480 return KisOptimizedBrushOutline(); 0481 } 0482 0483