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 QPointF tangent1, QPointF tangent2)
377{
378 if (tangent1.isNull() || tangent2.isNull()) return;
379
380 const qreal maxSanePoint = 1e6;
381
382 QPointF controlTarget1;
383 QPointF controlTarget2;
384
385 // Shows the direction in which control points go
386 QPointF controlDirection1 = pi1.pos() + tangent1;
387 QPointF controlDirection2 = pi2.pos() - tangent2;
388
389 // Lines in the direction of the control points
390 QLineF line1(pi1.pos(), controlDirection1);
391 QLineF line2(pi2.pos(), controlDirection2);
392
393 // Lines to check whether the control points lay on the opposite
394 // side of the line
395 QLineF line3(controlDirection1, controlDirection2);
396 QLineF line4(pi1.pos(), pi2.pos());
397
398 QPointF intersection;
399 if (line3.intersects(line4, &intersection) == QLineF::BoundedIntersection) {
400 qreal controlLength = line4.length() / 2;
401
402 line1.setLength(controlLength);
403 line2.setLength(controlLength);
404
405 controlTarget1 = line1.p2();
406 controlTarget2 = line2.p2();
407 } else {
408 QLineF::IntersectType type = line1.intersects(line2, &intersection);
409
410 if (type == QLineF::NoIntersection ||
411 intersection.manhattanLength() > maxSanePoint) {
412
413 intersection = 0.5 * (pi1.pos() + pi2.pos());
414// dbgKrita << "WARNING: there is no intersection point "
415// << "in the basic smoothing algorithms";
416 }
417
418 controlTarget1 = intersection;
419 controlTarget2 = intersection;
420 }
421
422 // shows how near to the controlTarget the value raises
423 qreal coeff = 0.8;
424
425 qreal velocity1 = QLineF(QPointF(), tangent1).length();
426 qreal velocity2 = QLineF(QPointF(), tangent2).length();
427
428 if (velocity1 == 0.0 || velocity2 == 0.0) {
429 velocity1 = 1e-6;
430 velocity2 = 1e-6;
431 warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2);
432 }
433
434 qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1);
435
436 // the controls should not differ more than 50%
437 similarity = qMax(similarity, qreal(0.5));
438
439 // when the controls are symmetric, their size should be smaller
440 // to avoid corner-like curves
441 coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
442
443 Q_ASSERT(coeff > 0);
444
445
446 QPointF control1;
447 QPointF control2;
448
449 if (velocity1 > velocity2) {
450 control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
451 coeff *= similarity;
452 control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
453 } else {
454 control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
455 coeff *= similarity;
456 control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
457 }
458
460 control1,
461 control2,
462 pi2);
463}
464
466{
467 qreal zoomingCoeff = 1.0;
468
471
472 if ((smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) ^
473 smoothingOptions->useScalableDistance()) {
474
475 zoomingCoeff = 1.0 / resources->effectiveZoom();
476 }
477
478 return zoomingCoeff * ((1.0 - speed) * smoothingOptions->smoothnessDistanceMax() + speed * smoothingOptions->smoothnessDistanceMin());
479}
480
490
491
493{
514 KisPaintInformation lastUsedPaintInformation;
515
517 && (m_d->smoothingOptions->smoothnessDistanceMin() > 0.0
518 || m_d->smoothingOptions->smoothnessDistanceMax() > 0.0)) {
519
520 { // initialize current distance
521 QPointF prevPos;
522
523 if (!m_d->history.isEmpty()) {
524 const KisPaintInformation &prevPi = m_d->history.last();
525 prevPos = prevPi.pos();
526 } else {
527 prevPos = m_d->previousPaintInformation.pos();
528 }
529
530 qreal currentDistance = QVector2D(info.pos() - prevPos).length();
531 m_d->distanceHistory.append(currentDistance);
532 }
533
534 m_d->history.append(info);
535
536 qreal x = 0.0;
537 qreal y = 0.0;
538
539 if (m_d->history.size() > 3) {
540 const qreal sigma = m_d->effectiveSmoothnessDistance(m_d->previousPaintInformation.drawingSpeed()) / 3.0; // '3.0' for (3 * sigma) range
541
542 qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
543 qreal gaussianWeight2 = sigma * sigma;
544 qreal distanceSum = 0.0;
545 qreal scaleSum = 0.0;
546 qreal pressure = 0.0;
547 qreal baseRate = 0.0;
548
549 Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size());
550
551 for (int i = m_d->history.size() - 1; i >= 0; i--) {
552 qreal rate = 0.0;
553
554 const KisPaintInformation nextInfo = m_d->history.at(i);
555 double distance = m_d->distanceHistory.at(i);
556 Q_ASSERT(distance >= 0.0);
557
558 qreal pressureGrad = 0.0;
559 if (i < m_d->history.size() - 1) {
560 pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();
561
562 const qreal tailAggressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness();
563
564 if (pressureGrad > 0.0 ) {
565 pressureGrad *= tailAggressiveness * (1.0 - nextInfo.pressure());
566 distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
567 }
568 }
569
570 if (gaussianWeight2 != 0.0) {
571 distanceSum += distance;
572 rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2));
573 }
574
575 if (m_d->history.size() - i == 1) {
576 baseRate = rate;
577 } else if (baseRate / rate > 100) {
578 break;
579 }
580
581 scaleSum += rate;
582 x += rate * nextInfo.pos().x();
583 y += rate * nextInfo.pos().y();
584
585 if (m_d->smoothingOptions->smoothPressure()) {
586 pressure += rate * nextInfo.pressure();
587 }
588 }
589
590 if (scaleSum != 0.0) {
591 x /= scaleSum;
592 y /= scaleSum;
593
594 if (m_d->smoothingOptions->smoothPressure()) {
595 pressure /= scaleSum;
596 }
597 }
598
599 if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
600 info.setPos(QPointF(x, y));
601 if (m_d->smoothingOptions->smoothPressure()) {
602 info.setPressure(pressure);
603 }
604 m_d->history.last() = info;
605 }
606 }
607 }
608
611 {
612 // Now paint between the coordinates, using the bezier curve interpolation
613 if (!m_d->haveTangent) {
614 m_d->haveTangent = true;
616 (info.pos() - m_d->previousPaintInformation.pos()) /
617 qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime());
618 } else {
619 QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
620 qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime());
621
622 if (newTangent.isNull() || m_d->previousTangent.isNull())
623 {
625 } else {
627 m_d->previousTangent, newTangent);
628 }
629
630 m_d->previousTangent = newTangent;
631 }
633
634 // Enable stroke timeout only when not airbrushing.
635 if (!m_d->airbrushingTimer.isActive()) {
636 m_d->strokeTimeoutTimer.start(100);
637 }
638 }
639
640 else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
642 }
643
645 QPoint currentPixelPos = Private::toPixelPos(info.pos());
646
647 // First pixel is always perfect.
648 if (!m_d->hasLastDrawnPixel) {
649 Private::setPaintInfoPixelPos(info, currentPixelPos);
650 paintLine(info, info);
651 m_d->lastDrawnPixel = currentPixelPos;
652 m_d->hasLastDrawnPixel = true;
653 m_d->hasTentativePixel = false;
655
656 } else {
657 // Check if we need to flush a currently stashed tentative pixel.
658 bool lastTentative = m_d->isTentativePixel(currentPixelPos);
659 if (m_d->hasTentativePixel && !lastTentative) {
662 m_d->hasTentativePixel = false;
665 lastTentative = m_d->isTentativePixel(currentPixelPos);
666 }
667
668 // If this pixel is too close to the previously drawn one, we mark
669 // it as tentative and wait for further movements to make sure we
670 // don't put it down in a place that generates a jag. If there is
671 // already a tentative pixel, it will be clobbered.
672 if (lastTentative) {
673 m_d->hasTentativePixel = true;
674 m_d->tentativePixel = currentPixelPos;
676 } else {
677 Private::setPaintInfoPixelPos(info, currentPixelPos);
679 m_d->lastDrawnPixel = currentPixelPos;
681 // Don't need to fiddle with hasLastTentativePixel here, it can
682 // only ever be false here due to the previous condition.
683 }
684 }
685 }
686
687 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
690 // Paint here so we don't have to rely on the timer
691 // This is just a tricky source for a relatively stable 7ms "timer"
693 }
694 } else {
696 }
697
698 if(m_d->airbrushingTimer.isActive()) {
699 m_d->airbrushingTimer.start();
700 }
701}
702
704{
705 if (!m_d->hasPaintAtLeastOnce) {
707 } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
708 finishStroke();
709 }
710 m_d->strokeTimeoutTimer.stop();
711
712 if(m_d->airbrushingTimer.isActive()) {
713 m_d->airbrushingTimer.stop();
714 }
715
716 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
718 }
719
722 }
723
730 m_d->strokeInfos.clear();
731
732 // last update to complete rendering if there is still something pending
735
737 m_d->strokeId.clear();
739}
740
742{
743 if (!m_d->strokeId) return;
744
745 m_d->strokeTimeoutTimer.stop();
746
747 if (m_d->airbrushingTimer.isActive()) {
748 m_d->airbrushingTimer.stop();
749 }
750
753 }
754
755 if (m_d->stabilizerPollTimer.isActive()) {
756 m_d->stabilizerPollTimer.stop();
757 }
758
761 }
762
763 // see a comment in endPaint()
764 m_d->strokeInfos.clear();
765
767 m_d->strokeId.clear();
768
769}
770
772{
773 return m_d->strokeTime.elapsed();
774}
775
777{
778 m_d->usingStabilizer = true;
779 // FIXME: Ugly hack, this is no a "distance" in any way
780 int sampleSize = qRound(m_d->effectiveSmoothnessDistance(firstPaintInfo.drawingSpeed()));
781 sampleSize = qMax(3, sampleSize);
782
783 // Fill the deque with the current value repeated until filling the sample
784 m_d->stabilizerDeque.clear();
785 for (int i = sampleSize; i > 0; i--) {
786 m_d->stabilizerDeque.enqueue(firstPaintInfo);
787 }
788
789 // Poll and draw regularly
790 KisConfig cfg(true);
791 int stabilizerSampleSize = cfg.stabilizerSampleSize();
792 m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize);
793 m_d->stabilizerPollTimer.start();
794
795 bool delayedPaintEnabled = cfg.stabilizerDelayedPaint();
796 if (delayedPaintEnabled) {
797 m_d->stabilizerDelayedPaintHelper.start(firstPaintInfo);
798 }
799
801 m_d->stabilizedSampler.addEvent(firstPaintInfo);
802}
803
805KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
806 const KisPaintInformation &lastPaintInfo)
807{
808 KisPaintInformation result(lastPaintInfo.pos(),
809 lastPaintInfo.pressure(),
810 lastPaintInfo.xTilt(),
811 lastPaintInfo.yTilt(),
812 lastPaintInfo.rotation(),
813 lastPaintInfo.tangentialPressure(),
814 lastPaintInfo.perspective(),
816 lastPaintInfo.drawingSpeed());
817
818 result.setCanvasRotation(lastPaintInfo.canvasRotation());
819 result.setCanvasMirroredH(lastPaintInfo.canvasMirroredH());
820 result.setCanvasMirroredV(lastPaintInfo.canvasMirroredV());
821
822 if (queue.size() > 1) {
823 QQueue<KisPaintInformation>::const_iterator it = queue.constBegin();
824 QQueue<KisPaintInformation>::const_iterator end = queue.constEnd();
825
829 it++;
830 int i = 2;
831
832 if (m_d->smoothingOptions->stabilizeSensors()) {
833 while (it != end) {
834 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
835 result.KisPaintInformation::mixOtherWithoutTime(k, *it);
836 it++;
837 i++;
838 }
839 } else{
840 while (it != end) {
841 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
842 result.KisPaintInformation::mixOtherOnlyPosition(k, *it);
843 it++;
844 i++;
845 }
846 }
847 }
848
849 return result;
850}
851
853{
856 std::tie(it, end) = m_d->stabilizedSampler.range();
857 QVector<KisPaintInformation> delayedPaintTodoItems;
858
859 for (; it != end; ++it) {
860 KisPaintInformation sampledInfo = *it;
861
862 bool canPaint = true;
863
864 if (m_d->smoothingOptions->useDelayDistance()) {
865 const qreal R = m_d->smoothingOptions->delayDistance() /
867
868 QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos();
869 qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
870
871 if (!(dx > R)) {
873 sampledInfo.setPos(m_d->previousPaintInformation.pos());
874 }
875 else {
876 canPaint = false;
877 }
878 }
879 }
880
881 if (canPaint) {
883
885 delayedPaintTodoItems.append(newInfo);
886 } else {
888 }
889 m_d->previousPaintInformation = newInfo;
890
891 // Push the new entry through the queue
892 m_d->stabilizerDeque.dequeue();
893 m_d->stabilizerDeque.enqueue(sampledInfo);
894 } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
895 QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
896 QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
897
898 while (it != end) {
900 ++it;
901 }
902 }
903 }
904
906
908 m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems);
909 } else {
911 }
912}
913
915{
916 // Stop the timer
917 m_d->stabilizerPollTimer.stop();
918
919 // Finish the line
920 if (m_d->smoothingOptions->finishStabilizedCurve()) {
921 // Process all the existing events first
923
924 // Draw the finish line with pending events and a time override
927 }
928
931 }
932 m_d->usingStabilizer = false;
933}
934
936{
937 if (!isRunning()) {
938 return;
939 }
940 KisSmoothingOptions::SmoothingType currentSmoothingType =
941 m_d->smoothingOptions->smoothingType();
943 && (currentSmoothingType != KisSmoothingOptions::STABILIZER)) {
945 } else if (!m_d->usingStabilizer
946 && (currentSmoothingType == KisSmoothingOptions::STABILIZER)) {
948 }
949}
950
966
968{
969 // Check that the stroke hasn't ended.
970 if (!m_d->strokeInfos.isEmpty()) {
971
972 // Add a new painting update at a point identical to the previous one, except for the time
973 // and speed information.
975 KisPaintInformation nextPaint(prevPaint.pos(),
976 prevPaint.pressure(),
977 prevPaint.xTilt(),
978 prevPaint.yTilt(),
979 prevPaint.rotation(),
980 prevPaint.tangentialPressure(),
981 prevPaint.perspective(),
983 0.0);
984 nextPaint.setCanvasRotation(prevPaint.canvasRotation());
985 nextPaint.setCanvasMirroredH(prevPaint.canvasMirroredH());
986 nextPaint.setCanvasMirroredV(prevPaint.canvasMirroredV());
987 paint(nextPaint);
988 }
989}
990
992{
994 return qMax(1, qFloor(realInterval));
995}
996
1001
1006
1008 const KisPaintInformation &pi)
1009{
1010 m_d->hasPaintAtLeastOnce = true;
1012 new FreehandStrokeStrategy::Data(strokeInfoId, pi));
1013
1014}
1015
1017 const KisPaintInformation &pi1,
1018 const KisPaintInformation &pi2)
1019{
1020 m_d->hasPaintAtLeastOnce = true;
1022 new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2));
1023
1024}
1025
1027 const KisPaintInformation &pi1,
1028 const QPointF &control1,
1029 const QPointF &control2,
1030 const KisPaintInformation &pi2)
1031{
1032
1033#ifdef DEBUG_BEZIER_CURVES
1036
1037 tpi1 = pi1;
1038 tpi2 = pi2;
1039
1040 tpi1.setPressure(0.3);
1041 tpi2.setPressure(0.3);
1042
1043 paintLine(tpi1, tpi2);
1044
1045 tpi1.setPressure(0.6);
1046 tpi2.setPressure(0.3);
1047
1048 tpi1.setPos(pi1.pos());
1049 tpi2.setPos(control1);
1050 paintLine(tpi1, tpi2);
1051
1052 tpi1.setPos(pi2.pos());
1053 tpi2.setPos(control2);
1054 paintLine(tpi1, tpi2);
1055#endif
1056
1057 m_d->hasPaintAtLeastOnce = true;
1059 new FreehandStrokeStrategy::Data(strokeInfoId,
1060 pi1, control1, control2, pi2));
1061
1062}
1063
1065 const KisDistanceInformation &startDist)
1066{
1067 strokeInfos << new KisFreehandStrokeInfo(startDist);
1068}
1069
1071{
1072 paintAt(0, pi);
1073}
1074
1076 const KisPaintInformation &pi2)
1077{
1078 paintLine(0, pi1, pi2);
1079}
1080
1082 const QPointF &control1,
1083 const QPointF &control2,
1084 const KisPaintInformation &pi2)
1085{
1086 paintBezierCurve(0, pi1, control1, control2, pi2);
1087}
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 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)
bool isTouchEvent() const
#define bounds(x, a, b)
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
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)