File indexing completed on 2024-12-22 04:13:00

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_tool_freehand_helper.h"
0008 
0009 #include <QTimer>
0010 #include <QElapsedTimer>
0011 #include <QQueue>
0012 
0013 #include <klocalizedstring.h>
0014 
0015 #include <KoPointerEvent.h>
0016 #include <KoCanvasResourceProvider.h>
0017 
0018 #include "kis_algebra_2d.h"
0019 #include "kis_distance_information.h"
0020 #include "kis_painting_information_builder.h"
0021 #include "kis_image.h"
0022 #include "kis_painter.h"
0023 #include <brushengine/kis_paintop_preset.h>
0024 #include <brushengine/kis_paintop_utils.h>
0025 
0026 #include "kis_update_time_monitor.h"
0027 #include "kis_stabilized_events_sampler.h"
0028 #include "KisStabilizerDelayedPaintHelper.h"
0029 #include "kis_config.h"
0030 
0031 #include "kis_random_source.h"
0032 #include "KisPerStrokeRandomSource.h"
0033 
0034 #include "strokes/freehand_stroke.h"
0035 #include "strokes/KisFreehandStrokeInfo.h"
0036 #include "KisAsynchronousStrokeUpdateHelper.h"
0037 #include "kis_canvas_resource_provider.h"
0038 #include <KisOptimizedBrushOutline.h>
0039 
0040 #include <math.h>
0041 
0042 //#define DEBUG_BEZIER_CURVES
0043 
0044 // Factor by which to scale the airbrush timer's interval, relative to the actual airbrushing rate.
0045 // Setting this less than 1 makes the timer-generated pseudo-events happen faster than the desired
0046 // airbrush rate, which can improve responsiveness.
0047 const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5;
0048 
0049 // The amount of time, in milliseconds, to allow between updates of the spacing information. Only
0050 // used when spacing updates between dabs are enabled.
0051 const qreal SPACING_UPDATE_INTERVAL = 50.0;
0052 
0053 // The amount of time, in milliseconds, to allow between updates of the timing information. Only
0054 // used when airbrushing.
0055 const qreal TIMING_UPDATE_INTERVAL = 50.0;
0056 
0057 struct KisToolFreehandHelper::Private
0058 {
0059     KoCanvasResourceProvider *resourceManager;
0060     KisPaintingInformationBuilder *infoBuilder;
0061     KisStrokesFacade *strokesFacade;
0062     KisAsynchronousStrokeUpdateHelper asyncUpdateHelper;
0063 
0064     KUndo2MagicString transactionText;
0065 
0066     bool haveTangent;
0067     QPointF previousTangent;
0068 
0069     bool hasPaintAtLeastOnce;
0070 
0071     QElapsedTimer strokeTime;
0072     QTimer strokeTimeoutTimer;
0073 
0074     QVector<KisFreehandStrokeInfo*> strokeInfos;
0075     KisResourcesSnapshotSP resources;
0076     KisStrokeId strokeId;
0077 
0078     KisPaintInformation previousPaintInformation;
0079     KisPaintInformation olderPaintInformation;
0080 
0081     KisSmoothingOptionsSP smoothingOptions;
0082 
0083     // fake random sources for hovering outline *only*
0084     KisRandomSourceSP fakeDabRandomSource;
0085     KisPerStrokeRandomSourceSP fakeStrokeRandomSource;
0086 
0087     // Timer used to generate paint updates periodically even without input events. This is only
0088     // used for paintops that depend on timely updates even when the cursor is not moving, e.g. for
0089     // airbrushing effects.
0090     QTimer airbrushingTimer;
0091 
0092     QList<KisPaintInformation> history;
0093     QList<qreal> distanceHistory;
0094 
0095     // Keeps track of past cursor positions. This is used to determine the drawing angle when
0096     // drawing the brush outline or starting a stroke.
0097     KisPaintOpUtils::PositionHistory lastCursorPos;
0098 
0099     // Stabilizer data
0100     bool usingStabilizer;
0101     QQueue<KisPaintInformation> stabilizerDeque;
0102     QTimer stabilizerPollTimer;
0103     KisStabilizedEventsSampler stabilizedSampler;
0104     KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper;
0105 
0106     qreal effectiveSmoothnessDistance() const;
0107 };
0108 
0109 
0110 KisToolFreehandHelper::KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder,
0111                                              KoCanvasResourceProvider *resourceManager,
0112                                              const KUndo2MagicString &transactionText,
0113                                              KisSmoothingOptions *smoothingOptions)
0114     : m_d(new Private())
0115 {
0116     m_d->resourceManager = resourceManager;
0117     m_d->infoBuilder = infoBuilder;
0118     m_d->transactionText = transactionText;
0119     m_d->smoothingOptions = KisSmoothingOptionsSP(
0120                 smoothingOptions ? smoothingOptions : new KisSmoothingOptions());
0121 
0122     m_d->fakeDabRandomSource = new KisRandomSource();
0123     m_d->fakeStrokeRandomSource = new KisPerStrokeRandomSource();
0124 
0125     m_d->strokeTimeoutTimer.setSingleShot(true);
0126     connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
0127     connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
0128     connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
0129     connect(m_d->smoothingOptions.data(), SIGNAL(sigSmoothingTypeChanged()), SLOT(slotSmoothingTypeChanged()));
0130 
0131     m_d->stabilizerDelayedPaintHelper.setPaintLineCallback(
0132                 [this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) {
0133                     paintLine(pi1, pi2);
0134                 });
0135     m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback(
0136                 [this]() {
0137                     emit requestExplicitUpdateOutline();
0138                 });
0139 }
0140 
0141 KisToolFreehandHelper::~KisToolFreehandHelper()
0142 {
0143     delete m_d;
0144 }
0145 
0146 void KisToolFreehandHelper::setSmoothness(KisSmoothingOptionsSP smoothingOptions)
0147 {
0148     m_d->smoothingOptions = smoothingOptions;
0149 }
0150 
0151 KisSmoothingOptionsSP KisToolFreehandHelper::smoothingOptions() const
0152 {
0153     return m_d->smoothingOptions;
0154 }
0155 
0156 KisOptimizedBrushOutline KisToolFreehandHelper::paintOpOutline(const QPointF &savedCursorPos,
0157                                                                const KoPointerEvent *event,
0158                                                                const KisPaintOpSettingsSP globalSettings,
0159                                                                KisPaintOpSettings::OutlineMode mode) const
0160 {
0161     KisPaintOpSettingsSP settings = globalSettings;
0162     QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos, currentZoom());
0163     qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0);
0164     KisDistanceInformation distanceInfo(prevPoint, startAngle);
0165 
0166     KisPaintInformation info;
0167 
0168     if (!m_d->strokeInfos.isEmpty()) {
0169         settings = m_d->resources->currentPaintOpPreset()->settings();
0170         if (m_d->stabilizerDelayedPaintHelper.running() &&
0171                 m_d->stabilizerDelayedPaintHelper.hasLastPaintInformation()) {
0172             info = m_d->stabilizerDelayedPaintHelper.lastPaintInformation();
0173         } else {
0174             info = m_d->previousPaintInformation;
0175         }
0176 
0177         /**
0178          * When LoD mode is active it may happen that the helper has
0179          * already started a stroke, but it painted noting, because
0180          * all the work is being calculated by the scaled-down LodN
0181          * stroke. So at first we try to fetch the data from the lodN
0182          * stroke ("buddy") and then check if there is at least
0183          * something has been painted with this distance information
0184          * object.
0185          */
0186         KisDistanceInformation *buddyDistance =
0187             m_d->strokeInfos.first()->buddyDragDistance();
0188 
0189         if (buddyDistance) {
0190             /**
0191              * Tiny hack alert: here we fetch the distance information
0192              * directly from the LodN stroke. Ideally, we should
0193              * upscale its data, but here we just override it with our
0194              * local copy of the coordinates.
0195              */
0196             distanceInfo = *buddyDistance;
0197             distanceInfo.overrideLastValues(prevPoint, startAngle);
0198 
0199         } else if (m_d->strokeInfos.first()->dragDistance->isStarted()) {
0200             distanceInfo = *m_d->strokeInfos.first()->dragDistance;
0201         }
0202     } else {
0203         info = m_d->infoBuilder->hover(savedCursorPos, event, !m_d->strokeInfos.isEmpty());
0204     }
0205 
0206     KisPaintInformation::DistanceInformationRegistrar registrar =
0207         info.registerDistanceInformation(&distanceInfo);
0208 
0209     info.setRandomSource(m_d->fakeDabRandomSource);
0210     info.setPerStrokeRandomSource(m_d->fakeStrokeRandomSource);
0211 
0212     KisOptimizedBrushOutline outline = settings->brushOutline(info, mode, currentPhysicalZoom());
0213 
0214     if (m_d->resources &&
0215         m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER &&
0216         m_d->smoothingOptions->useDelayDistance()) {
0217 
0218         const qreal R = m_d->smoothingOptions->delayDistance() /
0219             m_d->resources->effectiveZoom();
0220 
0221         outline.addEllipse(info.pos(), R, R);
0222     }
0223 
0224     return outline;
0225 }
0226 
0227 void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos)
0228 {
0229     m_d->lastCursorPos.pushThroughHistory(cursorPos, currentZoom());
0230 }
0231 
0232 void KisToolFreehandHelper::initPaint(KoPointerEvent *event,
0233                                       const QPointF &pixelCoords,
0234                                       KisImageWSP image, KisNodeSP currentNode,
0235                                       KisStrokesFacade *strokesFacade,
0236                                       KisNodeSP overrideNode,
0237                                       KisDefaultBoundsBaseSP bounds)
0238 {
0239     QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords, currentZoom());
0240     m_d->strokeTime.start();
0241     KisPaintInformation pi =
0242         m_d->infoBuilder->startStroke(event, elapsedStrokeTime(), m_d->resourceManager);
0243     qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0);
0244 
0245     initPaintImpl(startAngle,
0246                   pi,
0247                   m_d->resourceManager,
0248                   image,
0249                   currentNode,
0250                   strokesFacade,
0251                   overrideNode,
0252                   bounds);
0253 }
0254 
0255 bool KisToolFreehandHelper::isRunning() const
0256 {
0257     return m_d->strokeId;
0258 }
0259 
0260 void KisToolFreehandHelper::initPaintImpl(qreal startAngle,
0261                                           const KisPaintInformation &pi,
0262                                           KoCanvasResourceProvider *resourceManager,
0263                                           KisImageWSP image,
0264                                           KisNodeSP currentNode,
0265                                           KisStrokesFacade *strokesFacade,
0266                                           KisNodeSP overrideNode,
0267                                           KisDefaultBoundsBaseSP bounds)
0268 {
0269     m_d->strokesFacade = strokesFacade;
0270 
0271     m_d->haveTangent = false;
0272     m_d->previousTangent = QPointF();
0273 
0274     m_d->hasPaintAtLeastOnce = false;
0275 
0276     m_d->previousPaintInformation = pi;
0277 
0278     m_d->resources = new KisResourcesSnapshot(image,
0279                                               currentNode,
0280                                               resourceManager,
0281                                               bounds);
0282     if(overrideNode) {
0283         m_d->resources->setCurrentNode(overrideNode);
0284     }
0285 
0286     const bool airbrushing = m_d->resources->needsAirbrushing();
0287     const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates();
0288 
0289     KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(),
0290                                       startAngle,
0291                                       useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME,
0292                                       airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME,
0293                                       0);
0294     KisDistanceInformation startDist = startDistInfo.makeDistInfo();
0295 
0296     createPainters(m_d->strokeInfos,
0297                    startDist);
0298 
0299     KisStrokeStrategy *stroke =
0300         new FreehandStrokeStrategy(m_d->resources,
0301                                    m_d->strokeInfos,
0302                                    m_d->transactionText,
0303                                    FreehandStrokeStrategy::SupportsContinuedInterstrokeData |
0304                                    FreehandStrokeStrategy::SupportsTimedMergeId);
0305 
0306     m_d->strokeId = m_d->strokesFacade->startStroke(stroke);
0307 
0308     m_d->history.clear();
0309     m_d->distanceHistory.clear();
0310 
0311     if (airbrushing) {
0312         m_d->airbrushingTimer.setInterval(computeAirbrushTimerInterval());
0313         m_d->airbrushingTimer.start();
0314     } else if (m_d->resources->presetNeedsAsynchronousUpdates()) {
0315         m_d->asyncUpdateHelper.startUpdateStream(m_d->strokesFacade, m_d->strokeId);
0316     }
0317 
0318     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
0319         stabilizerStart(m_d->previousPaintInformation);
0320     }
0321 
0322     // If airbrushing, paint an initial dab immediately. This is a workaround for an issue where
0323     // some paintops (Dyna, Particle, Sketch) might never initialize their spacing/timing
0324     // information until paintAt is called.
0325     if (airbrushing) {
0326         paintAt(pi);
0327     }
0328 }
0329 
0330 KoCanvasResourceProvider *KisToolFreehandHelper::resourceManager() const
0331 {
0332     return m_d->resourceManager;
0333 }
0334 
0335 void KisToolFreehandHelper::paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2,
0336                                                QPointF tangent1, QPointF tangent2)
0337 {
0338     if (tangent1.isNull() || tangent2.isNull()) return;
0339 
0340     const qreal maxSanePoint = 1e6;
0341 
0342     QPointF controlTarget1;
0343     QPointF controlTarget2;
0344 
0345     // Shows the direction in which control points go
0346     QPointF controlDirection1 = pi1.pos() + tangent1;
0347     QPointF controlDirection2 = pi2.pos() - tangent2;
0348 
0349     // Lines in the direction of the control points
0350     QLineF line1(pi1.pos(), controlDirection1);
0351     QLineF line2(pi2.pos(), controlDirection2);
0352 
0353     // Lines to check whether the control points lay on the opposite
0354     // side of the line
0355     QLineF line3(controlDirection1, controlDirection2);
0356     QLineF line4(pi1.pos(), pi2.pos());
0357 
0358     QPointF intersection;
0359     if (line3.intersect(line4, &intersection) == QLineF::BoundedIntersection) {
0360         qreal controlLength = line4.length() / 2;
0361 
0362         line1.setLength(controlLength);
0363         line2.setLength(controlLength);
0364 
0365         controlTarget1 = line1.p2();
0366         controlTarget2 = line2.p2();
0367     } else {
0368         QLineF::IntersectType type = line1.intersect(line2, &intersection);
0369 
0370         if (type == QLineF::NoIntersection ||
0371             intersection.manhattanLength() > maxSanePoint) {
0372 
0373             intersection = 0.5 * (pi1.pos() + pi2.pos());
0374 //            dbgKrita << "WARNING: there is no intersection point "
0375 //                     << "in the basic smoothing algorithms";
0376         }
0377 
0378         controlTarget1 = intersection;
0379         controlTarget2 = intersection;
0380     }
0381 
0382     // shows how near to the controlTarget the value raises
0383     qreal coeff = 0.8;
0384 
0385     qreal velocity1 = QLineF(QPointF(), tangent1).length();
0386     qreal velocity2 = QLineF(QPointF(), tangent2).length();
0387 
0388     if (velocity1 == 0.0 || velocity2 == 0.0) {
0389         velocity1 = 1e-6;
0390         velocity2 = 1e-6;
0391         warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2);
0392     }
0393 
0394     qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1);
0395 
0396     // the controls should not differ more than 50%
0397     similarity = qMax(similarity, qreal(0.5));
0398 
0399     // when the controls are symmetric, their size should be smaller
0400     // to avoid corner-like curves
0401     coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
0402 
0403     Q_ASSERT(coeff > 0);
0404 
0405 
0406     QPointF control1;
0407     QPointF control2;
0408 
0409     if (velocity1 > velocity2) {
0410         control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
0411         coeff *= similarity;
0412         control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
0413     } else {
0414         control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
0415         coeff *= similarity;
0416         control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
0417     }
0418 
0419     paintBezierCurve(pi1,
0420                      control1,
0421                      control2,
0422                      pi2);
0423 }
0424 
0425 qreal KisToolFreehandHelper::Private::effectiveSmoothnessDistance() const
0426 {
0427     qreal zoomingCoeff = 1.0;
0428 
0429     /// stabilizer has inverted meaning of the "scalable distance", because
0430     /// it measures "samples", but not "distance"
0431 
0432     if ((smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) ^
0433          smoothingOptions->useScalableDistance()) {
0434 
0435         zoomingCoeff = 1.0 / resources->effectiveZoom();
0436     }
0437 
0438     return smoothingOptions->smoothnessDistance() * zoomingCoeff;
0439 }
0440 
0441 void KisToolFreehandHelper::paintEvent(KoPointerEvent *event)
0442 {
0443     KisPaintInformation info =
0444             m_d->infoBuilder->continueStroke(event,
0445                                              elapsedStrokeTime());
0446     KisUpdateTimeMonitor::instance()->reportMouseMove(info.pos());
0447 
0448     paint(info);
0449 }
0450 
0451 void KisToolFreehandHelper::paint(KisPaintInformation &info)
0452 {
0453     /**
0454      * Smooth the coordinates out using the history and the
0455      * distance. This is a heavily modified version of an algo used in
0456      * Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and
0457      * https://w.atwiki.jp/sigetch_2007/pages/17.html.  The main
0458      * differences are:
0459      *
0460      * 1) It uses 'distance' instead of 'velocity', since time
0461      *    measurements are too unstable in realworld environment
0462      *
0463      * 2) There is no 'Quality' parameter, since the number of samples
0464      *    is calculated automatically
0465      *
0466      * 3) 'Tail Aggressiveness' is used for controlling the end of the
0467      *    stroke
0468      *
0469      * 4) The formula is a little bit different: 'Distance' parameter
0470      *    stands for $3 \Sigma$
0471      */
0472     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING
0473         && m_d->smoothingOptions->smoothnessDistance() > 0.0) {
0474 
0475         { // initialize current distance
0476             QPointF prevPos;
0477 
0478             if (!m_d->history.isEmpty()) {
0479                 const KisPaintInformation &prevPi = m_d->history.last();
0480                 prevPos = prevPi.pos();
0481             } else {
0482                 prevPos = m_d->previousPaintInformation.pos();
0483             }
0484 
0485             qreal currentDistance = QVector2D(info.pos() - prevPos).length();
0486             m_d->distanceHistory.append(currentDistance);
0487         }
0488 
0489         m_d->history.append(info);
0490 
0491         qreal x = 0.0;
0492         qreal y = 0.0;
0493 
0494         if (m_d->history.size() > 3) {
0495             const qreal sigma = m_d->effectiveSmoothnessDistance() / 3.0; // '3.0' for (3 * sigma) range
0496 
0497             qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
0498             qreal gaussianWeight2 = sigma * sigma;
0499             qreal distanceSum = 0.0;
0500             qreal scaleSum = 0.0;
0501             qreal pressure = 0.0;
0502             qreal baseRate = 0.0;
0503 
0504             Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size());
0505 
0506             for (int i = m_d->history.size() - 1; i >= 0; i--) {
0507                 qreal rate = 0.0;
0508 
0509                 const KisPaintInformation nextInfo = m_d->history.at(i);
0510                 double distance = m_d->distanceHistory.at(i);
0511                 Q_ASSERT(distance >= 0.0);
0512 
0513                 qreal pressureGrad = 0.0;
0514                 if (i < m_d->history.size() - 1) {
0515                     pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();
0516 
0517                     const qreal tailAggressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness();
0518 
0519                     if (pressureGrad > 0.0 ) {
0520                         pressureGrad *= tailAggressiveness * (1.0 - nextInfo.pressure());
0521                         distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
0522                     }
0523                 }
0524 
0525                 if (gaussianWeight2 != 0.0) {
0526                     distanceSum += distance;
0527                     rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2));
0528                 }
0529 
0530                 if (m_d->history.size() - i == 1) {
0531                     baseRate = rate;
0532                 } else if (baseRate / rate > 100) {
0533                     break;
0534                 }
0535 
0536                 scaleSum += rate;
0537                 x += rate * nextInfo.pos().x();
0538                 y += rate * nextInfo.pos().y();
0539 
0540                 if (m_d->smoothingOptions->smoothPressure()) {
0541                     pressure += rate * nextInfo.pressure();
0542                 }
0543             }
0544 
0545             if (scaleSum != 0.0) {
0546                 x /= scaleSum;
0547                 y /= scaleSum;
0548 
0549                 if (m_d->smoothingOptions->smoothPressure()) {
0550                     pressure /= scaleSum;
0551                 }
0552             }
0553 
0554             if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
0555                 info.setPos(QPointF(x, y));
0556                 if (m_d->smoothingOptions->smoothPressure()) {
0557                     info.setPressure(pressure);
0558                 }
0559                 m_d->history.last() = info;
0560             }
0561         }
0562     }
0563 
0564     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING
0565         || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING)
0566     {
0567         // Now paint between the coordinates, using the bezier curve interpolation
0568         if (!m_d->haveTangent) {
0569             m_d->haveTangent = true;
0570             m_d->previousTangent =
0571                 (info.pos() - m_d->previousPaintInformation.pos()) /
0572                 qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime());
0573         } else {
0574             QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
0575                 qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime());
0576 
0577             if (newTangent.isNull() || m_d->previousTangent.isNull())
0578             {
0579                 paintLine(m_d->previousPaintInformation, info);
0580             } else {
0581                 paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation,
0582                                    m_d->previousTangent, newTangent);
0583             }
0584 
0585             m_d->previousTangent = newTangent;
0586         }
0587         m_d->olderPaintInformation = m_d->previousPaintInformation;
0588 
0589         // Enable stroke timeout only when not airbrushing.
0590         if (!m_d->airbrushingTimer.isActive()) {
0591             m_d->strokeTimeoutTimer.start(100);
0592         }
0593     }
0594     else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
0595         paintLine(m_d->previousPaintInformation, info);
0596     }
0597 
0598     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
0599         m_d->stabilizedSampler.addEvent(info);
0600         if (m_d->stabilizerDelayedPaintHelper.running()) {
0601             // Paint here so we don't have to rely on the timer
0602             // This is just a tricky source for a relatively stable 7ms "timer"
0603             m_d->stabilizerDelayedPaintHelper.paintSome();
0604         }
0605     } else {
0606         m_d->previousPaintInformation = info;
0607     }
0608 
0609     if(m_d->airbrushingTimer.isActive()) {
0610         m_d->airbrushingTimer.start();
0611     }
0612 }
0613 
0614 void KisToolFreehandHelper::endPaint()
0615 {
0616     if (!m_d->hasPaintAtLeastOnce) {
0617         paintAt(m_d->previousPaintInformation);
0618     } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
0619         finishStroke();
0620     }
0621     m_d->strokeTimeoutTimer.stop();
0622 
0623     if(m_d->airbrushingTimer.isActive()) {
0624         m_d->airbrushingTimer.stop();
0625     }
0626 
0627     if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
0628         stabilizerEnd();
0629     }
0630 
0631     if (m_d->asyncUpdateHelper.isActive()) {
0632         m_d->asyncUpdateHelper.endUpdateStream();
0633     }
0634 
0635     /**
0636      * There might be some timer events still pending, so
0637      * we should cancel them. Use this flag for the purpose.
0638      * Please note that we are not in MT here, so no mutex
0639      * is needed
0640      */
0641     m_d->strokeInfos.clear();
0642 
0643     // last update to complete rendering if there is still something pending
0644     m_d->strokesFacade->addJob(m_d->strokeId,
0645        new KisAsynchronousStrokeUpdateHelper::UpdateData(true));
0646 
0647     m_d->strokesFacade->endStroke(m_d->strokeId);
0648     m_d->strokeId.clear();
0649     m_d->infoBuilder->reset();
0650 }
0651 
0652 void KisToolFreehandHelper::cancelPaint()
0653 {
0654     if (!m_d->strokeId) return;
0655 
0656     m_d->strokeTimeoutTimer.stop();
0657 
0658     if (m_d->airbrushingTimer.isActive()) {
0659         m_d->airbrushingTimer.stop();
0660     }
0661 
0662     if (m_d->asyncUpdateHelper.isActive()) {
0663         m_d->asyncUpdateHelper.cancelUpdateStream();
0664     }
0665 
0666     if (m_d->stabilizerPollTimer.isActive()) {
0667         m_d->stabilizerPollTimer.stop();
0668     }
0669 
0670     if (m_d->stabilizerDelayedPaintHelper.running()) {
0671         m_d->stabilizerDelayedPaintHelper.cancel();
0672     }
0673 
0674     // see a comment in endPaint()
0675     m_d->strokeInfos.clear();
0676 
0677     m_d->strokesFacade->cancelStroke(m_d->strokeId);
0678     m_d->strokeId.clear();
0679 
0680 }
0681 
0682 int KisToolFreehandHelper::elapsedStrokeTime() const
0683 {
0684     return m_d->strokeTime.elapsed();
0685 }
0686 
0687 void KisToolFreehandHelper::stabilizerStart(KisPaintInformation firstPaintInfo)
0688 {
0689     m_d->usingStabilizer = true;
0690     // FIXME: Ugly hack, this is no a "distance" in any way
0691     int sampleSize = qRound(m_d->effectiveSmoothnessDistance());
0692     sampleSize = qMax(3, sampleSize);
0693 
0694     // Fill the deque with the current value repeated until filling the sample
0695     m_d->stabilizerDeque.clear();
0696     for (int i = sampleSize; i > 0; i--) {
0697         m_d->stabilizerDeque.enqueue(firstPaintInfo);
0698     }
0699 
0700     // Poll and draw regularly
0701     KisConfig cfg(true);
0702     int stabilizerSampleSize = cfg.stabilizerSampleSize();
0703     m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize);
0704     m_d->stabilizerPollTimer.start();
0705 
0706     bool delayedPaintEnabled = cfg.stabilizerDelayedPaint();
0707     if (delayedPaintEnabled) {
0708         m_d->stabilizerDelayedPaintHelper.start(firstPaintInfo);
0709     }
0710 
0711     m_d->stabilizedSampler.clear();
0712     m_d->stabilizedSampler.addEvent(firstPaintInfo);
0713 }
0714 
0715 KisPaintInformation
0716 KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
0717                                               const KisPaintInformation &lastPaintInfo)
0718 {
0719     KisPaintInformation result(lastPaintInfo.pos(),
0720                                lastPaintInfo.pressure(),
0721                                lastPaintInfo.xTilt(),
0722                                lastPaintInfo.yTilt(),
0723                                lastPaintInfo.rotation(),
0724                                lastPaintInfo.tangentialPressure(),
0725                                lastPaintInfo.perspective(),
0726                                elapsedStrokeTime(),
0727                                lastPaintInfo.drawingSpeed());
0728 
0729     result.setCanvasRotation(lastPaintInfo.canvasRotation());
0730     result.setCanvasMirroredH(lastPaintInfo.canvasMirroredH());
0731     result.setCanvasMirroredV(lastPaintInfo.canvasMirroredV());
0732 
0733     if (queue.size() > 1) {
0734         QQueue<KisPaintInformation>::const_iterator it = queue.constBegin();
0735         QQueue<KisPaintInformation>::const_iterator end = queue.constEnd();
0736 
0737         /**
0738          * The first point is going to be overridden by lastPaintInfo, skip it.
0739          */
0740         it++;
0741         int i = 2;
0742 
0743         if (m_d->smoothingOptions->stabilizeSensors()) {
0744             while (it != end) {
0745                 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
0746                 result.KisPaintInformation::mixOtherWithoutTime(k, *it);
0747                 it++;
0748                 i++;
0749             }
0750         } else{
0751             while (it != end) {
0752                 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
0753                 result.KisPaintInformation::mixOtherOnlyPosition(k, *it);
0754                 it++;
0755                 i++;
0756             }
0757         }
0758     }
0759 
0760     return result;
0761 }
0762 
0763 void KisToolFreehandHelper::stabilizerPollAndPaint()
0764 {
0765     KisStabilizedEventsSampler::iterator it;
0766     KisStabilizedEventsSampler::iterator end;
0767     std::tie(it, end) = m_d->stabilizedSampler.range();
0768     QVector<KisPaintInformation> delayedPaintTodoItems;
0769 
0770     for (; it != end; ++it) {
0771         KisPaintInformation sampledInfo = *it;
0772 
0773         bool canPaint = true;
0774 
0775         if (m_d->smoothingOptions->useDelayDistance()) {
0776             const qreal R = m_d->smoothingOptions->delayDistance() /
0777                     m_d->resources->effectiveZoom();
0778 
0779             QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos();
0780             qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
0781 
0782             if (!(dx > R)) {
0783                 if (m_d->resources->needsAirbrushing()) {
0784                     sampledInfo.setPos(m_d->previousPaintInformation.pos());
0785                 }
0786                 else {
0787                     canPaint = false;
0788                 }
0789             }
0790         }
0791 
0792         if (canPaint) {
0793             KisPaintInformation newInfo = getStabilizedPaintInfo(m_d->stabilizerDeque, sampledInfo);
0794 
0795             if (m_d->stabilizerDelayedPaintHelper.running()) {
0796                 delayedPaintTodoItems.append(newInfo);
0797             } else {
0798                 paintLine(m_d->previousPaintInformation, newInfo);
0799             }
0800             m_d->previousPaintInformation = newInfo;
0801 
0802             // Push the new entry through the queue
0803             m_d->stabilizerDeque.dequeue();
0804             m_d->stabilizerDeque.enqueue(sampledInfo);
0805         } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
0806 
0807             QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
0808             QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
0809 
0810             while (it != end) {
0811                 *it = m_d->previousPaintInformation;
0812                 ++it;
0813             }
0814         }
0815     }
0816 
0817     m_d->stabilizedSampler.clear();
0818 
0819     if (m_d->stabilizerDelayedPaintHelper.running()) {
0820         m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems);
0821     } else {
0822         emit requestExplicitUpdateOutline();
0823     }
0824 }
0825 
0826 void KisToolFreehandHelper::stabilizerEnd()
0827 {
0828     // Stop the timer
0829     m_d->stabilizerPollTimer.stop();
0830 
0831     // Finish the line
0832     if (m_d->smoothingOptions->finishStabilizedCurve()) {
0833         // Process all the existing events first
0834         stabilizerPollAndPaint();
0835 
0836         // Draw the finish line with pending events and a time override
0837         m_d->stabilizedSampler.addFinishingEvent(m_d->stabilizerDeque.size());
0838         stabilizerPollAndPaint();
0839     }
0840 
0841     if (m_d->stabilizerDelayedPaintHelper.running()) {
0842         m_d->stabilizerDelayedPaintHelper.end();
0843     }
0844     m_d->usingStabilizer = false;
0845 }
0846 
0847 void KisToolFreehandHelper::slotSmoothingTypeChanged()
0848 {
0849     if (!isRunning()) {
0850         return;
0851     }
0852     KisSmoothingOptions::SmoothingType currentSmoothingType =
0853             m_d->smoothingOptions->smoothingType();
0854     if (m_d->usingStabilizer
0855             && (currentSmoothingType != KisSmoothingOptions::STABILIZER)) {
0856         stabilizerEnd();
0857     } else if (!m_d->usingStabilizer
0858             && (currentSmoothingType == KisSmoothingOptions::STABILIZER)) {
0859         stabilizerStart(m_d->previousPaintInformation);
0860     }
0861 }
0862 
0863 void KisToolFreehandHelper::finishStroke()
0864 {
0865     if (m_d->haveTangent) {
0866         m_d->haveTangent = false;
0867 
0868         QPointF newTangent = (m_d->previousPaintInformation.pos() - m_d->olderPaintInformation.pos()) /
0869             (m_d->previousPaintInformation.currentTime() - m_d->olderPaintInformation.currentTime());
0870 
0871         paintBezierSegment(m_d->olderPaintInformation,
0872                            m_d->previousPaintInformation,
0873                            m_d->previousTangent,
0874                            newTangent);
0875     }
0876 }
0877 
0878 void KisToolFreehandHelper::doAirbrushing()
0879 {
0880     // Check that the stroke hasn't ended.
0881     if (!m_d->strokeInfos.isEmpty()) {
0882 
0883         // Add a new painting update at a point identical to the previous one, except for the time
0884         // and speed information.
0885         const KisPaintInformation &prevPaint = m_d->previousPaintInformation;
0886         KisPaintInformation nextPaint(prevPaint.pos(),
0887                                       prevPaint.pressure(),
0888                                       prevPaint.xTilt(),
0889                                       prevPaint.yTilt(),
0890                                       prevPaint.rotation(),
0891                                       prevPaint.tangentialPressure(),
0892                                       prevPaint.perspective(),
0893                                       elapsedStrokeTime(),
0894                                       0.0);
0895         nextPaint.setCanvasRotation(prevPaint.canvasRotation());
0896         nextPaint.setCanvasMirroredH(prevPaint.canvasMirroredH());
0897         nextPaint.setCanvasMirroredV(prevPaint.canvasMirroredV());
0898         paint(nextPaint);
0899     }
0900 }
0901 
0902 int KisToolFreehandHelper::computeAirbrushTimerInterval() const
0903 {
0904     qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR;
0905     return qMax(1, qFloor(realInterval));
0906 }
0907 
0908 qreal KisToolFreehandHelper::currentZoom() const
0909 {
0910     return m_d->resourceManager ? m_d->resourceManager->resource(KoCanvasResource::EffectiveZoom).toReal() : 1.0;
0911 }
0912 
0913 qreal KisToolFreehandHelper::currentPhysicalZoom() const
0914 {
0915     return m_d->resourceManager ? m_d->resourceManager->resource(KoCanvasResource::EffectivePhysicalZoom).toReal() : 1.0;
0916 }
0917 
0918 void KisToolFreehandHelper::paintAt(int strokeInfoId,
0919                                     const KisPaintInformation &pi)
0920 {
0921     m_d->hasPaintAtLeastOnce = true;
0922     m_d->strokesFacade->addJob(m_d->strokeId,
0923                                new FreehandStrokeStrategy::Data(strokeInfoId, pi));
0924 
0925 }
0926 
0927 void KisToolFreehandHelper::paintLine(int strokeInfoId,
0928                                       const KisPaintInformation &pi1,
0929                                       const KisPaintInformation &pi2)
0930 {
0931     m_d->hasPaintAtLeastOnce = true;
0932     m_d->strokesFacade->addJob(m_d->strokeId,
0933                                new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2));
0934 
0935 }
0936 
0937 void KisToolFreehandHelper::paintBezierCurve(int strokeInfoId,
0938                                              const KisPaintInformation &pi1,
0939                                              const QPointF &control1,
0940                                              const QPointF &control2,
0941                                              const KisPaintInformation &pi2)
0942 {
0943 #ifdef DEBUG_BEZIER_CURVES
0944     KisPaintInformation tpi1;
0945     KisPaintInformation tpi2;
0946 
0947     tpi1 = pi1;
0948     tpi2 = pi2;
0949 
0950     tpi1.setPressure(0.3);
0951     tpi2.setPressure(0.3);
0952 
0953     paintLine(tpi1, tpi2);
0954 
0955     tpi1.setPressure(0.6);
0956     tpi2.setPressure(0.3);
0957 
0958     tpi1.setPos(pi1.pos());
0959     tpi2.setPos(control1);
0960     paintLine(tpi1, tpi2);
0961 
0962     tpi1.setPos(pi2.pos());
0963     tpi2.setPos(control2);
0964     paintLine(tpi1, tpi2);
0965 #endif
0966 
0967     m_d->hasPaintAtLeastOnce = true;
0968     m_d->strokesFacade->addJob(m_d->strokeId,
0969                                new FreehandStrokeStrategy::Data(strokeInfoId,
0970                                                                 pi1, control1, control2, pi2));
0971 
0972 }
0973 
0974 void KisToolFreehandHelper::createPainters(QVector<KisFreehandStrokeInfo*> &strokeInfos,
0975                                            const KisDistanceInformation &startDist)
0976 {
0977     strokeInfos << new KisFreehandStrokeInfo(startDist);
0978 }
0979 
0980 void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi)
0981 {
0982     paintAt(0, pi);
0983 }
0984 
0985 void KisToolFreehandHelper::paintLine(const KisPaintInformation &pi1,
0986                                       const KisPaintInformation &pi2)
0987 {
0988     paintLine(0, pi1, pi2);
0989 }
0990 
0991 void KisToolFreehandHelper::paintBezierCurve(const KisPaintInformation &pi1,
0992                                              const QPointF &control1,
0993                                              const QPointF &control2,
0994                                              const KisPaintInformation &pi2)
0995 {
0996     paintBezierCurve(0, pi1, control1, control2, pi2);
0997 }