Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_freehand_helper.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QTimer>
10#include <QElapsedTimer>
11#include <QQueue>
12
13#include <klocalizedstring.h>
14
15#include <KoPointerEvent.h>
17
18#include "kis_algebra_2d.h"
21#include "kis_image.h"
22#include "kis_painter.h"
25
29#include "kis_config.h"
30
31#include "kis_random_source.h"
33
39
40#include <math.h>
41
42//#define DEBUG_BEZIER_CURVES
43
44// Factor by which to scale the airbrush timer's interval, relative to the actual airbrushing rate.
45// Setting this less than 1 makes the timer-generated pseudo-events happen faster than the desired
46// airbrush rate, which can improve responsiveness.
47const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5;
48
49// The amount of time, in milliseconds, to allow between updates of the spacing information. Only
50// used when spacing updates between dabs are enabled.
51const qreal SPACING_UPDATE_INTERVAL = 50.0;
52
53// The amount of time, in milliseconds, to allow between updates of the timing information. Only
54// used when airbrushing.
55const qreal TIMING_UPDATE_INTERVAL = 50.0;
56
58{
63
65
68
70
71 QElapsedTimer strokeTime;
73
77
79 // Used only by basic, weighted and pixel smoothing.
81 // Used only by pixel smoothing.
83
88
90
91 // fake random sources for hovering outline *only*
94
95 // Timer used to generate paint updates periodically even without input events. This is only
96 // used for paintops that depend on timely updates even when the cursor is not moving, e.g. for
97 // airbrushing effects.
99
102
103 // Keeps track of past cursor positions. This is used to determine the drawing angle when
104 // drawing the brush outline or starting a stroke.
107 // Stabilizer data
109 QQueue<KisPaintInformation> stabilizerDeque;
113
114 qreal effectiveSmoothnessDistance(qreal speed) const;
115
116 bool isTentativePixel(const QPoint &currentPixelPos) const
117 {
118 return abs(currentPixelPos.x() - lastDrawnPixel.x()) <= 1 && abs(currentPixelPos.y() - lastDrawnPixel.y()) <= 1;
119 }
121 static QPoint toPixelPos(const QPointF &pos)
123 // Don't replace this with QPointF::toPoint, that rounds! Flooring is
124 // the correct operation here.
125 return QPoint(qFloor(pos.x()), qFloor(pos.y()));
126 }
127
128 static void setPaintInfoPixelPos(KisPaintInformation &info, const QPoint &pixelPos)
129 {
130 // Offset the position to the center of the pixel.
131 info.setPos(QPointF(qreal(pixelPos.x()) + 0.5, qreal(pixelPos.y()) + 0.5));
132 }
133};
134
135
137 KoCanvasResourceProvider *resourceManager,
138 const KUndo2MagicString &transactionText,
139 KisSmoothingOptions *smoothingOptions)
140 : m_d(new Private())
141{
143 m_d->infoBuilder = infoBuilder;
144 m_d->transactionText = transactionText;
147
150
151 m_d->strokeTimeoutTimer.setSingleShot(true);
152 connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
153 connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
154 connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
155 connect(m_d->smoothingOptions.data(), SIGNAL(sigSmoothingTypeChanged()), SLOT(slotSmoothingTypeChanged()));
156
158 [this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) {
159 paintLine(pi1, pi2);
160 });
162 [this]() {
164 });
165}
166
171
176
181
183 const KoPointerEvent *event,
184 const KisPaintOpSettingsSP globalSettings,
186{
187 KisPaintOpSettingsSP settings = globalSettings;
188 QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos, currentZoom());
189 qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0);
190 KisDistanceInformation distanceInfo(prevPoint, startAngle);
191
193
194 if (!m_d->strokeInfos.isEmpty()) {
195 settings = m_d->resources->currentPaintOpPreset()->settings();
199 } else {
201 }
202
212 KisDistanceInformation *buddyDistance =
213 m_d->strokeInfos.first()->buddyDragDistance();
214
215 if (buddyDistance) {
222 distanceInfo = *buddyDistance;
223 distanceInfo.overrideLastValues(prevPoint, startAngle);
224
225 } else if (m_d->strokeInfos.first()->dragDistance->isStarted()) {
226 distanceInfo = *m_d->strokeInfos.first()->dragDistance;
227 }
228 } else {
229 info = m_d->infoBuilder->hover(savedCursorPos, event, !m_d->strokeInfos.isEmpty());
230 }
231
233 info.registerDistanceInformation(&distanceInfo);
234
237
238 KisOptimizedBrushOutline outline = settings->brushOutline(info, mode, currentPhysicalZoom());
239
240 if (m_d->resources &&
242 m_d->smoothingOptions->useDelayDistance()) {
243
244 const qreal R = m_d->smoothingOptions->delayDistance() /
246
247 outline.addEllipse(info.pos(), R, R);
248 }
249
250 return outline;
251}
252
253void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos)
254{
256}
257
259 const QPointF &pixelCoords,
260 KisImageWSP image, KisNodeSP currentNode,
261 KisStrokesFacade *strokesFacade,
262 KisNodeSP overrideNode,
264{
265 // When using touch drawing, we only get coordinates when the finger is
266 // actually pressed down, never when the finger is in the air. That means we
267 // have to clear the distance and speed information in this case, otherwise
268 // strokes start in a state depending on where the last stroke left off,
269 // which is useless. Resetting these at least makes it predictable. This
270 // can't sensibly be done in endPaint since there can be stylus or mouse
271 // events in the meantime, whose distance and speed is also unrelated.
272 if (event->isTouchEvent()) {
273 m_d->lastCursorPos.reset(pixelCoords);
275 }
276
277 QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords, currentZoom());
278 m_d->strokeTime.start();
281 qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0);
282
283 initPaintImpl(startAngle,
284 pi,
286 image,
287 currentNode,
288 strokesFacade,
289 overrideNode,
290 bounds);
291}
292
294{
295 return bool(m_d->strokeId);
296}
297
299 const KisPaintInformation &pi,
300 KoCanvasResourceProvider *resourceManager,
301 KisImageWSP image,
302 KisNodeSP currentNode,
303 KisStrokesFacade *strokesFacade,
304 KisNodeSP overrideNode,
306{
307 m_d->strokesFacade = strokesFacade;
308
309 m_d->haveTangent = false;
310 m_d->previousTangent = QPointF();
311
312 m_d->hasPaintAtLeastOnce = false;
313
315
317 currentNode,
319 bounds);
320 if(overrideNode) {
321 m_d->resources->setCurrentNode(overrideNode);
322 }
323
324 const bool airbrushing = m_d->resources->needsAirbrushing();
325 const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates();
326
328 startAngle,
329 useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME,
330 airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME,
331 0);
332 KisDistanceInformation startDist = startDistInfo.makeDistInfo();
333
335 startDist);
336
337 KisStrokeStrategy *stroke =
343
345
346 m_d->history.clear();
347 m_d->distanceHistory.clear();
348 m_d->hasLastDrawnPixel = false;
349 m_d->hasTentativePixel = false;
350
351 if (airbrushing) {
353 m_d->airbrushingTimer.start();
356 }
357
358 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
360 }
361
362 // If airbrushing, paint an initial dab immediately. This is a workaround for an issue where
363 // some paintops (Dyna, Particle, Sketch) might never initialize their spacing/timing
364 // information until paintAt is called.
365 if (airbrushing) {
366 paintAt(pi);
367 }
368}
369
374
376{
377 // Store on the flake-level resource provider (the only provider this
378 // helper holds), keyed by a Krita-specific resource id.
379 if (KoCanvasResourceProvider *manager = resourceManager()) {
381 QVariant::fromValue(pixelPos));
382 }
383}
384
386 QPointF tangent1, QPointF tangent2)
387{
388 if (tangent1.isNull() || tangent2.isNull()) return;
389
390 const qreal maxSanePoint = 1e6;
391
392 QPointF controlTarget1;
393 QPointF controlTarget2;
394
395 // Shows the direction in which control points go
396 QPointF controlDirection1 = pi1.pos() + tangent1;
397 QPointF controlDirection2 = pi2.pos() - tangent2;
398
399 // Lines in the direction of the control points
400 QLineF line1(pi1.pos(), controlDirection1);
401 QLineF line2(pi2.pos(), controlDirection2);
402
403 // Lines to check whether the control points lay on the opposite
404 // side of the line
405 QLineF line3(controlDirection1, controlDirection2);
406 QLineF line4(pi1.pos(), pi2.pos());
407
408 QPointF intersection;
409 if (line3.intersects(line4, &intersection) == QLineF::BoundedIntersection) {
410 qreal controlLength = line4.length() / 2;
411
412 line1.setLength(controlLength);
413 line2.setLength(controlLength);
414
415 controlTarget1 = line1.p2();
416 controlTarget2 = line2.p2();
417 } else {
418 QLineF::IntersectType type = line1.intersects(line2, &intersection);
419
420 if (type == QLineF::NoIntersection ||
421 intersection.manhattanLength() > maxSanePoint) {
422
423 intersection = 0.5 * (pi1.pos() + pi2.pos());
424// dbgKrita << "WARNING: there is no intersection point "
425// << "in the basic smoothing algorithms";
426 }
427
428 controlTarget1 = intersection;
429 controlTarget2 = intersection;
430 }
431
432 // shows how near to the controlTarget the value raises
433 qreal coeff = 0.8;
434
435 qreal velocity1 = QLineF(QPointF(), tangent1).length();
436 qreal velocity2 = QLineF(QPointF(), tangent2).length();
437
438 if (velocity1 == 0.0 || velocity2 == 0.0) {
439 velocity1 = 1e-6;
440 velocity2 = 1e-6;
441 warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2);
442 }
443
444 qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1);
445
446 // the controls should not differ more than 50%
447 similarity = qMax(similarity, qreal(0.5));
448
449 // when the controls are symmetric, their size should be smaller
450 // to avoid corner-like curves
451 coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
452
453 Q_ASSERT(coeff > 0);
454
455
456 QPointF control1;
457 QPointF control2;
458
459 if (velocity1 > velocity2) {
460 control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
461 coeff *= similarity;
462 control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
463 } else {
464 control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
465 coeff *= similarity;
466 control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
467 }
468
470 control1,
471 control2,
472 pi2);
473}
474
476{
477 qreal zoomingCoeff = 1.0;
478
481
482 if ((smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) ^
483 smoothingOptions->useScalableDistance()) {
484
485 zoomingCoeff = 1.0 / resources->effectiveZoom();
486 }
487
488 return zoomingCoeff * ((1.0 - speed) * smoothingOptions->smoothnessDistanceMax() + speed * smoothingOptions->smoothnessDistanceMin());
489}
490
500
501
503{
524 KisPaintInformation lastUsedPaintInformation;
525
527 && (m_d->smoothingOptions->smoothnessDistanceMin() > 0.0
528 || m_d->smoothingOptions->smoothnessDistanceMax() > 0.0)) {
529
530 { // initialize current distance
531 QPointF prevPos;
532
533 if (!m_d->history.isEmpty()) {
534 const KisPaintInformation &prevPi = m_d->history.last();
535 prevPos = prevPi.pos();
536 } else {
537 prevPos = m_d->previousPaintInformation.pos();
538 }
539
540 qreal currentDistance = QVector2D(info.pos() - prevPos).length();
541 m_d->distanceHistory.append(currentDistance);
542 }
543
544 m_d->history.append(info);
545
546 qreal x = 0.0;
547 qreal y = 0.0;
548
549 if (m_d->history.size() > 3) {
550 const qreal sigma = m_d->effectiveSmoothnessDistance(m_d->previousPaintInformation.drawingSpeed()) / 3.0; // '3.0' for (3 * sigma) range
551
552 qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
553 qreal gaussianWeight2 = sigma * sigma;
554 qreal distanceSum = 0.0;
555 qreal scaleSum = 0.0;
556 qreal pressure = 0.0;
557 qreal baseRate = 0.0;
558
559 Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size());
560
561 for (int i = m_d->history.size() - 1; i >= 0; i--) {
562 qreal rate = 0.0;
563
564 const KisPaintInformation nextInfo = m_d->history.at(i);
565 double distance = m_d->distanceHistory.at(i);
566 Q_ASSERT(distance >= 0.0);
567
568 qreal pressureGrad = 0.0;
569 if (i < m_d->history.size() - 1) {
570 pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();
571
572 const qreal tailAggressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness();
573
574 if (pressureGrad > 0.0 ) {
575 pressureGrad *= tailAggressiveness * (1.0 - nextInfo.pressure());
576 distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
577 }
578 }
579
580 if (gaussianWeight2 != 0.0) {
581 distanceSum += distance;
582 rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2));
583 }
584
585 if (m_d->history.size() - i == 1) {
586 baseRate = rate;
587 } else if (baseRate / rate > 100) {
588 break;
589 }
590
591 scaleSum += rate;
592 x += rate * nextInfo.pos().x();
593 y += rate * nextInfo.pos().y();
594
595 if (m_d->smoothingOptions->smoothPressure()) {
596 pressure += rate * nextInfo.pressure();
597 }
598 }
599
600 if (scaleSum != 0.0) {
601 x /= scaleSum;
602 y /= scaleSum;
603
604 if (m_d->smoothingOptions->smoothPressure()) {
605 pressure /= scaleSum;
606 }
607 }
608
609 if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
610 info.setPos(QPointF(x, y));
611 if (m_d->smoothingOptions->smoothPressure()) {
612 info.setPressure(pressure);
613 }
614 m_d->history.last() = info;
615 }
616 }
617 }
618
621 {
622 // Now paint between the coordinates, using the bezier curve interpolation
623 if (!m_d->haveTangent) {
624 m_d->haveTangent = true;
626 (info.pos() - m_d->previousPaintInformation.pos()) /
627 qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime());
628 } else {
629 QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
630 qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime());
631
632 if (newTangent.isNull() || m_d->previousTangent.isNull())
633 {
635 } else {
637 m_d->previousTangent, newTangent);
638 }
639
640 m_d->previousTangent = newTangent;
641 }
643
644 // Enable stroke timeout only when not airbrushing.
645 if (!m_d->airbrushingTimer.isActive()) {
646 m_d->strokeTimeoutTimer.start(100);
647 }
648 }
649
650 else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
652 }
653
655 QPoint currentPixelPos = Private::toPixelPos(info.pos());
656
657 // First pixel is always perfect.
658 if (!m_d->hasLastDrawnPixel) {
659 Private::setPaintInfoPixelPos(info, currentPixelPos);
660 paintLine(info, info);
661 m_d->lastDrawnPixel = currentPixelPos;
662 m_d->hasLastDrawnPixel = true;
663 m_d->hasTentativePixel = false;
665
666 } else {
667 // Check if we need to flush a currently stashed tentative pixel.
668 bool lastTentative = m_d->isTentativePixel(currentPixelPos);
669 if (m_d->hasTentativePixel && !lastTentative) {
672 m_d->hasTentativePixel = false;
675 lastTentative = m_d->isTentativePixel(currentPixelPos);
676 }
677
678 // If this pixel is too close to the previously drawn one, we mark
679 // it as tentative and wait for further movements to make sure we
680 // don't put it down in a place that generates a jag. If there is
681 // already a tentative pixel, it will be clobbered.
682 if (lastTentative) {
683 m_d->hasTentativePixel = true;
684 m_d->tentativePixel = currentPixelPos;
686 } else {
687 Private::setPaintInfoPixelPos(info, currentPixelPos);
689 m_d->lastDrawnPixel = currentPixelPos;
691 // Don't need to fiddle with hasLastTentativePixel here, it can
692 // only ever be false here due to the previous condition.
693 }
694 }
695 }
696
697 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
700 // Paint here so we don't have to rely on the timer
701 // This is just a tricky source for a relatively stable 7ms "timer"
703 }
704 } else {
706 }
707
708 if(m_d->airbrushingTimer.isActive()) {
709 m_d->airbrushingTimer.start();
710 }
711}
712
714{
715 if (!m_d->hasPaintAtLeastOnce) {
717 } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
718 finishStroke();
719 }
720 m_d->strokeTimeoutTimer.stop();
721
722 if(m_d->airbrushingTimer.isActive()) {
723 m_d->airbrushingTimer.stop();
724 }
725
726 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
728 }
729
732 }
733
740 m_d->strokeInfos.clear();
741
742 // last update to complete rendering if there is still something pending
745
747 m_d->strokeId.clear();
749}
750
752{
753 if (!m_d->strokeId) return;
754
755 m_d->strokeTimeoutTimer.stop();
756
757 if (m_d->airbrushingTimer.isActive()) {
758 m_d->airbrushingTimer.stop();
759 }
760
763 }
764
765 if (m_d->stabilizerPollTimer.isActive()) {
766 m_d->stabilizerPollTimer.stop();
767 }
768
771 }
772
773 // see a comment in endPaint()
774 m_d->strokeInfos.clear();
775
777 m_d->strokeId.clear();
778
779}
780
782{
783 return m_d->strokeTime.elapsed();
784}
785
787{
788 m_d->usingStabilizer = true;
789 // FIXME: Ugly hack, this is no a "distance" in any way
790 int sampleSize = qRound(m_d->effectiveSmoothnessDistance(firstPaintInfo.drawingSpeed()));
791 sampleSize = qMax(3, sampleSize);
792
793 // Fill the deque with the current value repeated until filling the sample
794 m_d->stabilizerDeque.clear();
795 for (int i = sampleSize; i > 0; i--) {
796 m_d->stabilizerDeque.enqueue(firstPaintInfo);
797 }
798
799 // Poll and draw regularly
800 KisConfig cfg(true);
801 int stabilizerSampleSize = cfg.stabilizerSampleSize();
802 m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize);
803 m_d->stabilizerPollTimer.start();
804
805 bool delayedPaintEnabled = cfg.stabilizerDelayedPaint();
806 if (delayedPaintEnabled) {
807 m_d->stabilizerDelayedPaintHelper.start(firstPaintInfo);
808 }
809
811 m_d->stabilizedSampler.addEvent(firstPaintInfo);
812}
813
815KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
816 const KisPaintInformation &lastPaintInfo)
817{
818 KisPaintInformation result(lastPaintInfo.pos(),
819 lastPaintInfo.pressure(),
820 lastPaintInfo.xTilt(),
821 lastPaintInfo.yTilt(),
822 lastPaintInfo.rotation(),
823 lastPaintInfo.tangentialPressure(),
824 lastPaintInfo.perspective(),
826 lastPaintInfo.drawingSpeed());
827
828 result.setCanvasRotation(lastPaintInfo.canvasRotation());
829 result.setCanvasMirroredH(lastPaintInfo.canvasMirroredH());
830 result.setCanvasMirroredV(lastPaintInfo.canvasMirroredV());
831
832 if (queue.size() > 1) {
833 QQueue<KisPaintInformation>::const_iterator it = queue.constBegin();
834 QQueue<KisPaintInformation>::const_iterator end = queue.constEnd();
835
839 it++;
840 int i = 2;
841
842 if (m_d->smoothingOptions->stabilizeSensors()) {
843 while (it != end) {
844 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
845 result.KisPaintInformation::mixOtherWithoutTime(k, *it);
846 it++;
847 i++;
848 }
849 } else{
850 while (it != end) {
851 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
852 result.KisPaintInformation::mixOtherOnlyPosition(k, *it);
853 it++;
854 i++;
855 }
856 }
857 }
858
859 return result;
860}
861
863{
866 std::tie(it, end) = m_d->stabilizedSampler.range();
867 QVector<KisPaintInformation> delayedPaintTodoItems;
868
869 for (; it != end; ++it) {
870 KisPaintInformation sampledInfo = *it;
871
872 bool canPaint = true;
873
874 if (m_d->smoothingOptions->useDelayDistance()) {
875 const qreal R = m_d->smoothingOptions->delayDistance() /
877
878 QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos();
879 qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
880
881 if (!(dx > R)) {
883 sampledInfo.setPos(m_d->previousPaintInformation.pos());
884 }
885 else {
886 canPaint = false;
887 }
888 }
889 }
890
891 if (canPaint) {
893
895 delayedPaintTodoItems.append(newInfo);
896 } else {
898 }
899 m_d->previousPaintInformation = newInfo;
900
901 // Push the new entry through the queue
902 m_d->stabilizerDeque.dequeue();
903 m_d->stabilizerDeque.enqueue(sampledInfo);
904 } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
905 QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
906 QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
907
908 while (it != end) {
910 ++it;
911 }
912 }
913 }
914
916
918 m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems);
919 } else {
921 }
922}
923
925{
926 // Stop the timer
927 m_d->stabilizerPollTimer.stop();
928
929 // Finish the line
930 if (m_d->smoothingOptions->finishStabilizedCurve()) {
931 // Process all the existing events first
933
934 // Draw the finish line with pending events and a time override
937 }
938
941 }
942 m_d->usingStabilizer = false;
943}
944
946{
947 if (!isRunning()) {
948 return;
949 }
950 KisSmoothingOptions::SmoothingType currentSmoothingType =
951 m_d->smoothingOptions->smoothingType();
953 && (currentSmoothingType != KisSmoothingOptions::STABILIZER)) {
955 } else if (!m_d->usingStabilizer
956 && (currentSmoothingType == KisSmoothingOptions::STABILIZER)) {
958 }
959}
960
976
978{
979 // Check that the stroke hasn't ended.
980 if (!m_d->strokeInfos.isEmpty()) {
981
982 // Add a new painting update at a point identical to the previous one, except for the time
983 // and speed information.
985 KisPaintInformation nextPaint(prevPaint.pos(),
986 prevPaint.pressure(),
987 prevPaint.xTilt(),
988 prevPaint.yTilt(),
989 prevPaint.rotation(),
990 prevPaint.tangentialPressure(),
991 prevPaint.perspective(),
993 0.0);
994 nextPaint.setCanvasRotation(prevPaint.canvasRotation());
995 nextPaint.setCanvasMirroredH(prevPaint.canvasMirroredH());
996 nextPaint.setCanvasMirroredV(prevPaint.canvasMirroredV());
997 paint(nextPaint);
998 }
999}
1000
1002{
1003 qreal realInterval = m_d->resources->airbrushingInterval() * AIRBRUSH_INTERVAL_FACTOR;
1004 return qMax(1, qFloor(realInterval));
1005}
1006
1011
1016
1018 const KisPaintInformation &pi)
1019{
1021 m_d->hasPaintAtLeastOnce = true;
1023 new FreehandStrokeStrategy::Data(strokeInfoId, pi));
1024
1025}
1026
1028 const KisPaintInformation &pi1,
1029 const KisPaintInformation &pi2)
1030{
1032 m_d->hasPaintAtLeastOnce = true;
1034 new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2));
1035
1036}
1037
1039 const KisPaintInformation &pi1,
1040 const QPointF &control1,
1041 const QPointF &control2,
1042 const KisPaintInformation &pi2)
1043{
1045
1046#ifdef DEBUG_BEZIER_CURVES
1049
1050 tpi1 = pi1;
1051 tpi2 = pi2;
1052
1053 tpi1.setPressure(0.3);
1054 tpi2.setPressure(0.3);
1055
1056 paintLine(tpi1, tpi2);
1057
1058 tpi1.setPressure(0.6);
1059 tpi2.setPressure(0.3);
1060
1061 tpi1.setPos(pi1.pos());
1062 tpi2.setPos(control1);
1063 paintLine(tpi1, tpi2);
1064
1065 tpi1.setPos(pi2.pos());
1066 tpi2.setPos(control2);
1067 paintLine(tpi1, tpi2);
1068#endif
1069
1070 m_d->hasPaintAtLeastOnce = true;
1072 new FreehandStrokeStrategy::Data(strokeInfoId,
1073 pi1, control1, control2, pi2));
1074
1075}
1076
1078 const KisDistanceInformation &startDist)
1079{
1080 strokeInfos << new KisFreehandStrokeInfo(startDist);
1081}
1082
1084{
1085 paintAt(0, pi);
1086}
1087
1089 const KisPaintInformation &pi2)
1090{
1091 paintLine(0, pi1, pi2);
1092}
1093
1095 const QPointF &control1,
1096 const QPointF &control2,
1097 const KisPaintInformation &pi2)
1098{
1099 paintBezierCurve(0, pi1, control1, control2, pi2);
1100}
Eigen::Matrix< double, 4, 2 > R
qreal distance(const QPointF &p1, const QPointF &p2)
void startUpdateStream(KisStrokesFacade *strokesFacade, KisStrokeId strokeId)
int stabilizerSampleSize(bool defaultValue=false) const
bool stabilizerDelayedPaint(bool defaultValue=false) const
void addEllipse(const QPointF &center, qreal rx, qreal ry)
void setRandomSource(KisRandomSourceSP value)
void setPos(const QPointF &p)
qreal perspective() const
reciprocal of distance on the perspective grid
const QPointF & pos() const
void setCanvasMirroredV(bool value)
void setCanvasMirroredH(bool value)
qreal xTilt() const
The tilt of the pen on the horizontal axis (from 0.0 to 1.0)
void setPressure(qreal p)
Set the pressure.
DistanceInformationRegistrar registerDistanceInformation(KisDistanceInformation *distance)
void setCanvasRotation(qreal rotation)
qreal tangentialPressure() const
tangential pressure (i.e., rate for an airbrush device)
qreal currentTime() const
Number of ms since the beginning of the stroke.
void setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value)
qreal yTilt() const
The tilt of the pen on the vertical axis (from 0.0 to 1.0)
qreal rotation() const
rotation as given by the tablet event
qreal pressure() const
The pressure of the value (from 0.0 to 1.0)
QPointF pushThroughHistory(const QPointF &pt, qreal zoom)
KisPaintInformation continueStroke(KoPointerEvent *event, int timeElapsed)
KisPaintInformation startStroke(KoPointerEvent *event, int timeElapsed, const KoCanvasResourceProvider *manager)
KisPaintInformation hover(const QPointF &imagePoint, const KoPointerEvent *event, bool isStrokeStarted)
The KisResourcesSnapshot class takes a snapshot of the various resources like colors and settings use...
void setCurrentNode(KisNodeSP node)
KisPaintOpPresetSP currentPaintOpPreset() const
void addEvent(const KisPaintInformation &pi)
std::pair< iterator, iterator > range() const
void setUpdateOutlineCallback(std::function< void()> requestUpdateOutline)
void update(const QVector< KisPaintInformation > &newPaintInfos)
void setPaintLineCallback(std::function< void(const KisPaintInformation &, const KisPaintInformation &)> paintLine)
void start(const KisPaintInformation &firstPaintInfo)
virtual KisStrokeId startStroke(KisStrokeStrategy *strokeStrategy)=0
virtual void endStroke(KisStrokeId id)=0
virtual void addJob(KisStrokeId id, KisStrokeJobData *data)=0
virtual bool cancelStroke(KisStrokeId id)=0
KisSmoothingOptionsSP smoothingOptions() const
void setSmoothness(KisSmoothingOptionsSP smoothingOptions)
KisToolFreehandHelper(KisPaintingInformationBuilder *infoBuilder, KoCanvasResourceProvider *resourceManager, const KUndo2MagicString &transactionText=KUndo2MagicString(), KisSmoothingOptions *smoothingOptions=0)
void initPaintImpl(qreal startAngle, const KisPaintInformation &pi, KoCanvasResourceProvider *resourceManager, KisImageWSP image, KisNodeSP node, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode=0, KisDefaultBoundsBaseSP bounds=0)
void cursorMoved(const QPointF &cursorPos)
KisPaintInformation getStabilizedPaintInfo(const QQueue< KisPaintInformation > &queue, const KisPaintInformation &lastPaintInfo)
KoCanvasResourceProvider * resourceManager() const
void stabilizerStart(KisPaintInformation firstPaintInfo)
void paint(KisPaintInformation &info)
void paintEvent(KoPointerEvent *event)
void paintBezierCurve(int strokeInfoId, const KisPaintInformation &pi1, const QPointF &control1, const QPointF &control2, const KisPaintInformation &pi2)
KisOptimizedBrushOutline paintOpOutline(const QPointF &savedCursorPos, const KoPointerEvent *event, const KisPaintOpSettingsSP globalSettings, KisPaintOpSettings::OutlineMode mode) const
void updateCurrentBrushPosition(const QPointF &pixelPos)
void paintBezierSegment(KisPaintInformation pi1, KisPaintInformation pi2, QPointF tangent1, QPointF tangent2)
void paintLine(int strokeInfoId, const KisPaintInformation &pi1, const KisPaintInformation &pi2)
void initPaint(KoPointerEvent *event, const QPointF &pixelCoords, KisImageWSP image, KisNodeSP currentNode, KisStrokesFacade *strokesFacade, KisNodeSP overrideNode=0, KisDefaultBoundsBaseSP bounds=0)
void paintAt(int strokeInfoId, const KisPaintInformation &pi)
void requestExplicitUpdateOutline()
virtual void createPainters(QVector< KisFreehandStrokeInfo * > &strokeInfos, const KisDistanceInformation &startDist)
void setResource(int key, const QVariant &value)
bool isTouchEvent() const
#define bounds(x, a, b)
#define warnKrita
Definition kis_debug.h:94
#define ppVar(var)
Definition kis_debug.h:163
T pow2(const T &x)
Definition kis_global.h:166
#define M_PI
Definition kis_global.h:111
QSharedPointer< KisSmoothingOptions > KisSmoothingOptionsSP
const qreal LONG_TIME
const qreal TIMING_UPDATE_INTERVAL
const qreal AIRBRUSH_INTERVAL_FACTOR
const qreal SPACING_UPDATE_INTERVAL
qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
@ EffectiveZoom
-Used only by painting tools for non-displaying purposes
@ EffectivePhysicalZoom
-Used by tool for displaying purposes
void overrideLastValues(const QPointF &lastPosition, qreal lastAngle)
KisStabilizedEventsSampler stabilizedSampler
static void setPaintInfoPixelPos(KisPaintInformation &info, const QPoint &pixelPos)
bool isTentativePixel(const QPoint &currentPixelPos) const
QQueue< KisPaintInformation > stabilizerDeque
KisPaintOpUtils::PositionHistory lastCursorPos
KisPerStrokeRandomSourceSP fakeStrokeRandomSource
KoCanvasResourceProvider * resourceManager
QVector< KisFreehandStrokeInfo * > strokeInfos
static QPoint toPixelPos(const QPointF &pos)
KisAsynchronousStrokeUpdateHelper asyncUpdateHelper
KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper
qreal effectiveSmoothnessDistance(qreal speed) const
KisPaintingInformationBuilder * infoBuilder
static KisUpdateTimeMonitor * instance()
void reportMouseMove(const QPointF &pos)