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