File indexing completed on 2024-05-26 04:34:37

0001 /*
0002  *  kis_tool_dyna.cpp - part of Krita
0003  *
0004  *  SPDX-FileCopyrightText: 2009-2011 Lukáš Tvrdý <LukasT.dev@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_tool_dyna.h"
0010 
0011 #include <QCheckBox>
0012 #include <QDoubleSpinBox>
0013 #include <QLabel>
0014 
0015 #include <klocalizedstring.h>
0016 #include <ksharedconfig.h>
0017 
0018 #include "KoPointerEvent.h"
0019 #include "kundo2magicstring.h"
0020 
0021 #include "kis_cursor.h"
0022 #include <kis_slider_spin_box.h>
0023 #include <KisAngleSelector.h>
0024 
0025 
0026 #define MAXIMUM_SMOOTHNESS 1000
0027 #define MAXIMUM_MAGNETISM 1000
0028 
0029 #define MIN_MASS 1.0
0030 #define MAX_MASS 160.0
0031 #define MIN_DRAG 0.0
0032 #define MAX_DRAG 0.5
0033 #define MIN_ACC 0.000001
0034 #define MIN_VEL 0.000001
0035 
0036 
0037 KisToolDyna::KisToolDyna(KoCanvasBase * canvas)
0038         : KisToolFreehand(canvas, KisCursor::load("tool_freehand_cursor.xpm", 2, 2), kundo2_i18n("Dynamic Brush Stroke"))
0039 {
0040     setObjectName("tool_dyna");
0041     initDyna();
0042 }
0043 
0044 
0045 void KisToolDyna::initDyna()
0046 {
0047     /* dynadraw init */
0048     m_curmass = 0.5;
0049     m_curdrag = 0.15;
0050     m_mouse.fixedangle = false;
0051     m_width = 1.5;
0052     m_xangle = 0.60;
0053     m_yangle = 0.20;
0054     m_widthRange = 0.05;
0055 }
0056 
0057 
0058 KisToolDyna::~KisToolDyna()
0059 {
0060 }
0061 
0062 void KisToolDyna::resetCursorStyle()
0063 {
0064     KisToolFreehand::resetCursorStyle();
0065 
0066     overrideCursorIfNotEditable();
0067 }
0068 
0069 void KisToolDyna::activate(const QSet<KoShape*> &shapes)
0070 {
0071     KisToolPaint::activate(shapes);
0072     m_configGroup =  KSharedConfig::openConfig()->group(toolId());
0073 }
0074 
0075 void KisToolDyna::initStroke(KoPointerEvent *event)
0076 {
0077     QRectF imageSize = QRectF(QPointF(0.0,0.0),currentImage()->size());
0078     QRectF documentSize = currentImage()->pixelToDocument(imageSize);
0079     m_surfaceWidth = documentSize.width();
0080     m_surfaceHeight = documentSize.height();
0081     setMousePosition(event->point);
0082     m_mouse.init(m_mousePos.x(), m_mousePos.y());
0083 
0084     KisToolFreehand::initStroke(event);
0085 }
0086 
0087 void KisToolDyna::beginPrimaryAction(KoPointerEvent *event)
0088 {
0089     setMousePosition(event->point);
0090     m_mouse.init(m_mousePos.x(), m_mousePos.y());
0091     m_odelx = m_mousePos.x();
0092     m_odely = m_mousePos.y();
0093 
0094     KisToolFreehand::beginPrimaryAction(event);
0095 }
0096 
0097 void KisToolDyna::continuePrimaryAction(KoPointerEvent *event)
0098 {
0099     setMousePosition(event->point);
0100 
0101     if (applyFilter(m_mousePos.x(), m_mousePos.y())) {
0102         KoPointerEvent newEvent = filterEvent(event);
0103         KisToolFreehand::continuePrimaryAction(&newEvent);
0104     }
0105 }
0106 
0107 // dyna algorithm
0108 int KisToolDyna::applyFilter(qreal mx, qreal my)
0109 {
0110     /* calculate mass and drag */
0111     qreal mass = flerp(MIN_MASS, MAX_MASS, m_curmass);
0112     qreal drag = flerp(MIN_DRAG, MAX_DRAG, m_curdrag * m_curdrag);
0113 
0114     /* calculate force and acceleration */
0115     qreal fx = mx - m_mouse.curx;
0116     qreal fy = my - m_mouse.cury;
0117 
0118     m_mouse.acc = sqrt(fx * fx + fy * fy);
0119 
0120     if (m_mouse.acc < MIN_ACC) {
0121         return 0;
0122     }
0123 
0124     m_mouse.accx = fx / mass;
0125     m_mouse.accy = fy / mass;
0126 
0127     /* calculate new velocity */
0128     m_mouse.velx += m_mouse.accx;
0129     m_mouse.vely += m_mouse.accy;
0130     m_mouse.vel = sqrt(m_mouse.velx * m_mouse.velx + m_mouse.vely * m_mouse.vely);
0131     m_mouse.angx = -m_mouse.vely;
0132     m_mouse.angy = m_mouse.velx;
0133     if (m_mouse.vel < MIN_VEL) {
0134         return 0;
0135     }
0136 
0137     /* calculate angle of drawing tool */
0138     if (m_mouse.fixedangle) {
0139         m_mouse.angx = m_xangle;
0140         m_mouse.angy = m_yangle;
0141     } else {
0142         m_mouse.angx /= m_mouse.vel;
0143         m_mouse.angy /= m_mouse.vel;
0144     }
0145 
0146     m_mouse.velx = m_mouse.velx * (1.0 - drag);
0147     m_mouse.vely = m_mouse.vely * (1.0 - drag);
0148 
0149     m_mouse.lastx = m_mouse.curx;
0150     m_mouse.lasty = m_mouse.cury;
0151     m_mouse.curx = m_mouse.curx + m_mouse.velx;
0152     m_mouse.cury = m_mouse.cury + m_mouse.vely;
0153 
0154     return 1;
0155 }
0156 
0157 
0158 KoPointerEvent KisToolDyna::filterEvent(KoPointerEvent* event)
0159 {
0160     qreal wid = m_widthRange - m_mouse.vel;
0161 
0162     wid = wid * m_width;
0163 
0164     if (wid < 0.00001) {
0165         wid = 0.00001;
0166     }
0167 
0168     qreal delx = m_mouse.angx * wid;
0169     qreal dely = m_mouse.angy * wid;
0170 
0171     qreal px = m_mouse.lastx;
0172     qreal py = m_mouse.lasty;
0173     qreal nx = m_mouse.curx;
0174     qreal ny = m_mouse.cury;
0175 
0176     QPointF prev(px , py);         // previous position
0177     QPointF now(nx , ny);           // new position
0178 
0179     QPointF prevr(px + m_odelx , py + m_odely);
0180     QPointF prevl(px - m_odelx , py - m_odely);
0181 
0182     QPointF nowl(nx - delx , ny - dely);
0183     QPointF nowr(nx + delx , ny + dely);
0184 
0185     // transform coords from float points into image points
0186     prev.rx() *= m_surfaceWidth;
0187     prevr.rx() *= m_surfaceWidth;
0188     prevl.rx() *= m_surfaceWidth;
0189     now.rx()  *= m_surfaceWidth;
0190     nowl.rx() *= m_surfaceWidth;
0191     nowr.rx() *= m_surfaceWidth;
0192 
0193     prev.ry() *= m_surfaceHeight;
0194     prevr.ry() *= m_surfaceHeight;
0195     prevl.ry() *= m_surfaceHeight;
0196     now.ry()  *= m_surfaceHeight;
0197     nowl.ry() *= m_surfaceHeight;
0198     nowr.ry() *= m_surfaceHeight;
0199 
0200 #if 0
0201 
0202     qreal xTilt, yTilt;
0203     qreal m_rotation;
0204     qreal m_tangentialPressure;
0205 
0206     // some funny debugging
0207     dbgPlugins << "m_mouse.vel: " << m_mouse.vel;
0208     dbgPlugins << "m_mouse.velx: " << m_mouse.velx;
0209     dbgPlugins << "m_mouse.vely: " << m_mouse.vely;
0210     dbgPlugins << "m_mouse.accx: " << m_mouse.accx;
0211     dbgPlugins << "m_mouse.accy: " << m_mouse.accy;
0212 
0213 
0214     dbgPlugins << "fixed: " << m_mouse.fixedangle;
0215     dbgPlugins << "drag: " << m_curdrag;
0216     dbgPlugins << "mass: " << m_curmass;
0217     dbgPlugins << "xAngle: " << m_xangle;
0218     dbgPlugins << "yAngle: " << m_yangle;
0219 
0220 #endif
0221 
0222     m_odelx = delx;
0223     m_odely = dely;
0224 
0225     // how to change pressure in the KoPointerEvent???
0226     return KoPointerEvent(event,now);
0227 }
0228 
0229 
0230 void KisToolDyna::slotSetDrag(qreal drag)
0231 {
0232     m_curdrag = drag;
0233     m_configGroup.writeEntry("dragAmount", drag);
0234 }
0235 
0236 
0237 void KisToolDyna::slotSetMass(qreal mass)
0238 {
0239     m_curmass = mass;
0240     m_configGroup.writeEntry("massAmount", mass);
0241 }
0242 
0243 
0244 void KisToolDyna::slotSetDynaWidth(double width)
0245 {
0246     m_width = width;
0247     m_configGroup.writeEntry("initWidth", width);
0248 }
0249 
0250 
0251 void KisToolDyna::slotSetWidthRange(double widthRange)
0252 {
0253     m_widthRange = widthRange;
0254     m_configGroup.writeEntry("initWidthRange", widthRange);
0255 }
0256 
0257 
0258 void KisToolDyna::slotSetFixedAngle(bool fixedAngle)
0259 {
0260     m_mouse.fixedangle = fixedAngle;
0261     m_angleSelector->setEnabled(fixedAngle);
0262     m_configGroup.writeEntry("useFixedAngle", fixedAngle);
0263 }
0264 
0265 QWidget * KisToolDyna::createOptionWidget()
0266 {
0267 
0268     QWidget * optionsWidget = KisToolFreehand::createOptionWidget();
0269     optionsWidget->setObjectName(toolId() + " option widget");
0270 
0271     m_optionLayout = new QGridLayout();
0272 
0273     m_optionLayout->setMargin(0);
0274     m_optionLayout->setSpacing(2);
0275     KisToolFreehand::addOptionWidgetLayout(m_optionLayout);
0276 
0277     QLabel* massLbl = new QLabel(i18n("Mass:"), optionsWidget);
0278     m_massSPBox = new KisDoubleSliderSpinBox(optionsWidget);
0279     m_massSPBox->setRange(0.0,1.0,2);
0280     m_massSPBox->setSingleStep(0.01);
0281     connect(m_massSPBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetMass(qreal)));
0282     KisToolFreehand::addOptionWidgetOption(m_massSPBox,massLbl);
0283 
0284     QLabel* dragLbl = new QLabel(i18n("Drag:"), optionsWidget);
0285     m_dragSPBox = new KisDoubleSliderSpinBox(optionsWidget);
0286     m_dragSPBox->setRange(0.0,1.0,2);
0287     m_dragSPBox->setSingleStep(0.01);
0288     connect(m_dragSPBox, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetDrag(qreal)));
0289     KisToolFreehand::addOptionWidgetOption(m_dragSPBox,dragLbl);
0290 
0291     //NOTE: so far unused, waiting for the changes to propagate rotation/pressure to freehand tool
0292     // fixed angle might be for 2.4, but the later one for 2.5
0293     m_chkFixedAngle = new QCheckBox(i18n("Fixed angle:"), optionsWidget);
0294     m_chkFixedAngle->setEnabled(false);
0295     connect(m_chkFixedAngle, SIGNAL(toggled(bool)), this, SLOT(slotSetFixedAngle(bool)));
0296 
0297     m_angleSelector = new KisAngleSelector(optionsWidget);
0298     m_angleSelector->setDecimals(0);
0299     m_angleSelector->setFlipOptionsMode(KisAngleSelector::FlipOptionsMode_MenuButton);
0300     m_angleSelector->setIncreasingDirection(KisAngleGauge::IncreasingDirection_Clockwise);
0301     m_angleSelector->setEnabled(false);
0302     connect(m_angleSelector, SIGNAL(angleChanged(qreal)), this, SLOT(slotSetAngle(qreal)));
0303 
0304     KisToolFreehand::addOptionWidgetOption(m_angleSelector,m_chkFixedAngle);
0305 
0306     // read settings in from config
0307     m_massSPBox->setValue(m_configGroup.readEntry("massAmount", 0.01));
0308     m_dragSPBox->setValue(m_configGroup.readEntry("dragAmount", .98));
0309     m_chkFixedAngle->setChecked((bool)m_configGroup.readEntry("useFixedAngle", false));
0310     m_angleSelector->setAngle(m_configGroup.readEntry("angleAmount", 20));
0311 
0312 
0313 #if 0
0314     QLabel* initWidthLbl = new QLabel(i18n("Initial width:"), optionWidget);
0315     m_initWidthSPBox = new QDoubleSpinBox(optionWidget);   
0316     connect(m_initWidthSPBox, SIGNAL(valueChanged(double)), this, SLOT(slotSetDynaWidth(double)));
0317     KisToolFreehand::addOptionWidgetOption(m_initWidthSPBox,initWidthLbl);
0318 
0319     QLabel* widthRangeLbl = new QLabel(i18n("Width range:"), optionWidget);
0320     m_widthRangeSPBox = new QDoubleSpinBox(optionWidget);
0321     connect(m_widthRangeSPBox, SIGNAL(valueChanged(double)), this, SLOT(slotSetWidthRange(double)));
0322     //KisToolFreehand::addOptionWidgetOption(m_widthRangeSPBox,widthRangeLbl);
0323 
0324     m_initWidthSPBox->setValue(m_configGroup.readEntry("initWidth", 10));
0325     m_widthRangeSPBox->setValue(m_configGroup.readEntry("initWidthRange", 20));
0326 
0327 
0328 #endif
0329 
0330     return optionsWidget;
0331 }
0332 
0333 void KisToolDyna::slotSetAngle(qreal angle)
0334 {
0335     m_xangle = cos(angle * M_PI/180.0);
0336     m_yangle = sin(angle * M_PI/180.0);
0337 
0338     m_configGroup.writeEntry("angleAmount", angle);
0339 }
0340 
0341