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
80
82 QPointF waitingPixel;
85
87
88 // fake random sources for hovering outline *only*
91
92 // Timer used to generate paint updates periodically even without input events. This is only
93 // used for paintops that depend on timely updates even when the cursor is not moving, e.g. for
94 // airbrushing effects.
96
99
100 // Keeps track of past cursor positions. This is used to determine the drawing angle when
101 // drawing the brush outline or starting a stroke.
103
104 // Stabilizer data
106 QQueue<KisPaintInformation> stabilizerDeque;
110
111 qreal effectiveSmoothnessDistance(qreal speed) const;
113
114
116 KoCanvasResourceProvider *resourceManager,
117 const KUndo2MagicString &transactionText,
118 KisSmoothingOptions *smoothingOptions)
119 : m_d(new Private())
121 m_d->resourceManager = resourceManager;
122 m_d->infoBuilder = infoBuilder;
123 m_d->transactionText = transactionText;
124 m_d->smoothingOptions = KisSmoothingOptionsSP(
125 smoothingOptions ? smoothingOptions : new KisSmoothingOptions());
126
127 m_d->fakeDabRandomSource = new KisRandomSource();
128 m_d->fakeStrokeRandomSource = new KisPerStrokeRandomSource();
129
130 m_d->strokeTimeoutTimer.setSingleShot(true);
131 connect(&m_d->strokeTimeoutTimer, SIGNAL(timeout()), SLOT(finishStroke()));
132 connect(&m_d->airbrushingTimer, SIGNAL(timeout()), SLOT(doAirbrushing()));
133 connect(&m_d->stabilizerPollTimer, SIGNAL(timeout()), SLOT(stabilizerPollAndPaint()));
134 connect(m_d->smoothingOptions.data(), SIGNAL(sigSmoothingTypeChanged()), SLOT(slotSmoothingTypeChanged()));
135
136 m_d->stabilizerDelayedPaintHelper.setPaintLineCallback(
137 [this](const KisPaintInformation &pi1, const KisPaintInformation &pi2) {
138 paintLine(pi1, pi2);
139 });
140 m_d->stabilizerDelayedPaintHelper.setUpdateOutlineCallback(
141 [this]() {
142 Q_EMIT requestExplicitUpdateOutline();
143 });
144}
145
150
155
160
162 const KoPointerEvent *event,
163 const KisPaintOpSettingsSP globalSettings,
165{
166 KisPaintOpSettingsSP settings = globalSettings;
167 QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(savedCursorPos, currentZoom());
168 qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, savedCursorPos, 0);
169 KisDistanceInformation distanceInfo(prevPoint, startAngle);
170
172
173 if (!m_d->strokeInfos.isEmpty()) {
174 settings = m_d->resources->currentPaintOpPreset()->settings();
178 } else {
180 }
181
191 KisDistanceInformation *buddyDistance =
192 m_d->strokeInfos.first()->buddyDragDistance();
193
194 if (buddyDistance) {
201 distanceInfo = *buddyDistance;
202 distanceInfo.overrideLastValues(prevPoint, startAngle);
203
204 } else if (m_d->strokeInfos.first()->dragDistance->isStarted()) {
205 distanceInfo = *m_d->strokeInfos.first()->dragDistance;
206 }
207 } else {
208 info = m_d->infoBuilder->hover(savedCursorPos, event, !m_d->strokeInfos.isEmpty());
209 }
210
212 info.registerDistanceInformation(&distanceInfo);
213
216
217 KisOptimizedBrushOutline outline = settings->brushOutline(info, mode, currentPhysicalZoom());
218
219 if (m_d->resources &&
221 m_d->smoothingOptions->useDelayDistance()) {
222
223 const qreal R = m_d->smoothingOptions->delayDistance() /
225
226 outline.addEllipse(info.pos(), R, R);
227 }
228
229 return outline;
230}
231
232void KisToolFreehandHelper::cursorMoved(const QPointF &cursorPos)
233{
235}
236
238 const QPointF &pixelCoords,
239 KisImageWSP image, KisNodeSP currentNode,
240 KisStrokesFacade *strokesFacade,
241 KisNodeSP overrideNode,
243{
244 // When using touch drawing, we only get coordinates when the finger is
245 // actually pressed down, never when the finger is in the air. That means we
246 // have to clear the distance and speed information in this case, otherwise
247 // strokes start in a state depending on where the last stroke left off,
248 // which is useless. Resetting these at least makes it predictable. This
249 // can't sensibly be done in endPaint since there can be stylus or mouse
250 // events in the meantime, whose distance and speed is also unrelated.
251 if (event->isTouchEvent()) {
252 m_d->lastCursorPos.reset(pixelCoords);
254 }
255
256 QPointF prevPoint = m_d->lastCursorPos.pushThroughHistory(pixelCoords, currentZoom());
257 m_d->strokeTime.start();
260 qreal startAngle = KisAlgebra2D::directionBetweenPoints(prevPoint, pixelCoords, 0.0);
261
262 initPaintImpl(startAngle,
263 pi,
265 image,
266 currentNode,
267 strokesFacade,
268 overrideNode,
269 bounds);
270}
271
273{
274 return bool(m_d->strokeId);
275}
276
278 const KisPaintInformation &pi,
279 KoCanvasResourceProvider *resourceManager,
280 KisImageWSP image,
281 KisNodeSP currentNode,
282 KisStrokesFacade *strokesFacade,
283 KisNodeSP overrideNode,
285{
286 m_d->strokesFacade = strokesFacade;
287
288 m_d->haveTangent = false;
289 m_d->previousTangent = QPointF();
290
291 m_d->hasPaintAtLeastOnce = false;
292
294
296 currentNode,
298 bounds);
299 if(overrideNode) {
300 m_d->resources->setCurrentNode(overrideNode);
301 }
302
303 const bool airbrushing = m_d->resources->needsAirbrushing();
304 const bool useSpacingUpdates = m_d->resources->needsSpacingUpdates();
305
307 startAngle,
308 useSpacingUpdates ? SPACING_UPDATE_INTERVAL : LONG_TIME,
309 airbrushing ? TIMING_UPDATE_INTERVAL : LONG_TIME,
310 0);
311 KisDistanceInformation startDist = startDistInfo.makeDistInfo();
312
314 startDist);
315
316 KisStrokeStrategy *stroke =
322
324
325 m_d->history.clear();
326 m_d->distanceHistory.clear();
327 m_d->lastDrawnPixel = QPointF(-1.0, -1.0);
328 m_d->hasLastDrawnPixel = false;
330
331 if (airbrushing) {
333 m_d->airbrushingTimer.start();
336 }
337
338 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
340 }
341
342 // If airbrushing, paint an initial dab immediately. This is a workaround for an issue where
343 // some paintops (Dyna, Particle, Sketch) might never initialize their spacing/timing
344 // information until paintAt is called.
345 if (airbrushing) {
346 paintAt(pi);
347 }
348}
349
354
356 QPointF tangent1, QPointF tangent2)
357{
358 if (tangent1.isNull() || tangent2.isNull()) return;
359
360 const qreal maxSanePoint = 1e6;
361
362 QPointF controlTarget1;
363 QPointF controlTarget2;
364
365 // Shows the direction in which control points go
366 QPointF controlDirection1 = pi1.pos() + tangent1;
367 QPointF controlDirection2 = pi2.pos() - tangent2;
368
369 // Lines in the direction of the control points
370 QLineF line1(pi1.pos(), controlDirection1);
371 QLineF line2(pi2.pos(), controlDirection2);
372
373 // Lines to check whether the control points lay on the opposite
374 // side of the line
375 QLineF line3(controlDirection1, controlDirection2);
376 QLineF line4(pi1.pos(), pi2.pos());
377
378 QPointF intersection;
379 if (line3.intersects(line4, &intersection) == QLineF::BoundedIntersection) {
380 qreal controlLength = line4.length() / 2;
381
382 line1.setLength(controlLength);
383 line2.setLength(controlLength);
384
385 controlTarget1 = line1.p2();
386 controlTarget2 = line2.p2();
387 } else {
388 QLineF::IntersectType type = line1.intersects(line2, &intersection);
389
390 if (type == QLineF::NoIntersection ||
391 intersection.manhattanLength() > maxSanePoint) {
392
393 intersection = 0.5 * (pi1.pos() + pi2.pos());
394// dbgKrita << "WARNING: there is no intersection point "
395// << "in the basic smoothing algorithms";
396 }
397
398 controlTarget1 = intersection;
399 controlTarget2 = intersection;
400 }
401
402 // shows how near to the controlTarget the value raises
403 qreal coeff = 0.8;
404
405 qreal velocity1 = QLineF(QPointF(), tangent1).length();
406 qreal velocity2 = QLineF(QPointF(), tangent2).length();
407
408 if (velocity1 == 0.0 || velocity2 == 0.0) {
409 velocity1 = 1e-6;
410 velocity2 = 1e-6;
411 warnKrita << "WARNING: Basic Smoothing: Velocity is Zero! Please report a bug:" << ppVar(velocity1) << ppVar(velocity2);
412 }
413
414 qreal similarity = qMin(velocity1/velocity2, velocity2/velocity1);
415
416 // the controls should not differ more than 50%
417 similarity = qMax(similarity, qreal(0.5));
418
419 // when the controls are symmetric, their size should be smaller
420 // to avoid corner-like curves
421 coeff *= 1 - qMax(qreal(0.0), similarity - qreal(0.8));
422
423 Q_ASSERT(coeff > 0);
424
425
426 QPointF control1;
427 QPointF control2;
428
429 if (velocity1 > velocity2) {
430 control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
431 coeff *= similarity;
432 control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
433 } else {
434 control2 = pi2.pos() * (1.0 - coeff) + coeff * controlTarget2;
435 coeff *= similarity;
436 control1 = pi1.pos() * (1.0 - coeff) + coeff * controlTarget1;
437 }
438
440 control1,
441 control2,
442 pi2);
443}
444
446{
447 qreal zoomingCoeff = 1.0;
448
451
452 if ((smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) ^
453 smoothingOptions->useScalableDistance()) {
454
455 zoomingCoeff = 1.0 / resources->effectiveZoom();
456 }
457
458 return zoomingCoeff * ((1.0 - speed) * smoothingOptions->smoothnessDistanceMax() + speed * smoothingOptions->smoothnessDistanceMin());
459}
460
470
471
473{
494 KisPaintInformation lastUsedPaintInformation;
495 QPointF currentPixelPos = info.pos();
496 const float PIXEL_DISTANCE_THRESHOLD = 1.7;
497
498
500 && (m_d->smoothingOptions->smoothnessDistanceMin() > 0.0
501 || m_d->smoothingOptions->smoothnessDistanceMax() > 0.0)) {
502
503 { // initialize current distance
504 QPointF prevPos;
505
506 if (!m_d->history.isEmpty()) {
507 const KisPaintInformation &prevPi = m_d->history.last();
508 prevPos = prevPi.pos();
509 } else {
510 prevPos = m_d->previousPaintInformation.pos();
511 }
512
513 qreal currentDistance = QVector2D(info.pos() - prevPos).length();
514 m_d->distanceHistory.append(currentDistance);
515 }
516
517 m_d->history.append(info);
518
519 qreal x = 0.0;
520 qreal y = 0.0;
521
522 if (m_d->history.size() > 3) {
523 const qreal sigma = m_d->effectiveSmoothnessDistance(m_d->previousPaintInformation.drawingSpeed()) / 3.0; // '3.0' for (3 * sigma) range
524
525 qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma);
526 qreal gaussianWeight2 = sigma * sigma;
527 qreal distanceSum = 0.0;
528 qreal scaleSum = 0.0;
529 qreal pressure = 0.0;
530 qreal baseRate = 0.0;
531
532 Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size());
533
534 for (int i = m_d->history.size() - 1; i >= 0; i--) {
535 qreal rate = 0.0;
536
537 const KisPaintInformation nextInfo = m_d->history.at(i);
538 double distance = m_d->distanceHistory.at(i);
539 Q_ASSERT(distance >= 0.0);
540
541 qreal pressureGrad = 0.0;
542 if (i < m_d->history.size() - 1) {
543 pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure();
544
545 const qreal tailAggressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness();
546
547 if (pressureGrad > 0.0 ) {
548 pressureGrad *= tailAggressiveness * (1.0 - nextInfo.pressure());
549 distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region
550 }
551 }
552
553 if (gaussianWeight2 != 0.0) {
554 distanceSum += distance;
555 rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2));
556 }
557
558 if (m_d->history.size() - i == 1) {
559 baseRate = rate;
560 } else if (baseRate / rate > 100) {
561 break;
562 }
563
564 scaleSum += rate;
565 x += rate * nextInfo.pos().x();
566 y += rate * nextInfo.pos().y();
567
568 if (m_d->smoothingOptions->smoothPressure()) {
569 pressure += rate * nextInfo.pressure();
570 }
571 }
572
573 if (scaleSum != 0.0) {
574 x /= scaleSum;
575 y /= scaleSum;
576
577 if (m_d->smoothingOptions->smoothPressure()) {
578 pressure /= scaleSum;
579 }
580 }
581
582 if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) {
583 info.setPos(QPointF(x, y));
584 if (m_d->smoothingOptions->smoothPressure()) {
585 info.setPressure(pressure);
586 }
587 m_d->history.last() = info;
588 }
589 }
590 }
591
594 {
595 // Now paint between the coordinates, using the bezier curve interpolation
596 if (!m_d->haveTangent) {
597 m_d->haveTangent = true;
599 (info.pos() - m_d->previousPaintInformation.pos()) /
600 qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime());
601 } else {
602 QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) /
603 qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime());
604
605 if (newTangent.isNull() || m_d->previousTangent.isNull())
606 {
608 } else {
610 m_d->previousTangent, newTangent);
611 }
612
613 m_d->previousTangent = newTangent;
614 }
616
617 // Enable stroke timeout only when not airbrushing.
618 if (!m_d->airbrushingTimer.isActive()) {
619 m_d->strokeTimeoutTimer.start(100);
620 }
621 }
622
623 else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING){
625 }
626
628 // initial case: No last drawn pixel or waiting pixel
629 if(!m_d->hasLastDrawnPixel) {
630 currentPixelPos = info.pos();
631 m_d->waitingPixel = currentPixelPos;
632
635 m_d->hasLastDrawnPixel = true;
637 } else {
638 if (abs(currentPixelPos.x() - m_d->lastDrawnPixel.x()) > PIXEL_DISTANCE_THRESHOLD || abs(currentPixelPos.y() - m_d->lastDrawnPixel.y()) > PIXEL_DISTANCE_THRESHOLD) {
640 // current pixel is too far, draw the waiting pixel
642 m_d->pixelInLineCount += 2;
644 m_d->waitingPixel = currentPixelPos;
645 }
646 // check axis, if the currentpixel is in the same axis as the lastdrawnpixel, we can draw waiting pixel
647 if (m_d->pixelInLineCount >= 1 && (currentPixelPos.x() == m_d->lastDrawnPixel.x() || currentPixelPos.y() == m_d->lastDrawnPixel.y())) {
652 }
653 else{
654 //otherwise just change update waiting pixel without drawing it, this is the scenario where we just skip a corner and therefore we also reset pixelInLineCount
655 m_d->waitingPixel = currentPixelPos;
657 }
658 // Enable stroke timeout only when not airbrushing.
659 if (!m_d->airbrushingTimer.isActive()) {
660 m_d->strokeTimeoutTimer.start(100);
661 }
662 }
663 }
664
665 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
668 // Paint here so we don't have to rely on the timer
669 // This is just a tricky source for a relatively stable 7ms "timer"
671 }
672 } else {
674 }
675
676 if(m_d->airbrushingTimer.isActive()) {
677 m_d->airbrushingTimer.start();
678 }
679}
680
682{
683 if (!m_d->hasPaintAtLeastOnce) {
685 } else if (m_d->smoothingOptions->smoothingType() != KisSmoothingOptions::NO_SMOOTHING) {
686 finishStroke();
687 }
688 m_d->strokeTimeoutTimer.stop();
689
690 if(m_d->airbrushingTimer.isActive()) {
691 m_d->airbrushingTimer.stop();
692 }
693
694 if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) {
696 }
697
700 }
701
708 m_d->strokeInfos.clear();
709
710 // last update to complete rendering if there is still something pending
713
715 m_d->strokeId.clear();
717}
718
720{
721 if (!m_d->strokeId) return;
722
723 m_d->strokeTimeoutTimer.stop();
724
725 if (m_d->airbrushingTimer.isActive()) {
726 m_d->airbrushingTimer.stop();
727 }
728
731 }
732
733 if (m_d->stabilizerPollTimer.isActive()) {
734 m_d->stabilizerPollTimer.stop();
735 }
736
739 }
740
741 // see a comment in endPaint()
742 m_d->strokeInfos.clear();
743
745 m_d->strokeId.clear();
746
747}
748
750{
751 return m_d->strokeTime.elapsed();
752}
753
755{
756 m_d->usingStabilizer = true;
757 // FIXME: Ugly hack, this is no a "distance" in any way
758 int sampleSize = qRound(m_d->effectiveSmoothnessDistance(firstPaintInfo.drawingSpeed()));
759 sampleSize = qMax(3, sampleSize);
760
761 // Fill the deque with the current value repeated until filling the sample
762 m_d->stabilizerDeque.clear();
763 for (int i = sampleSize; i > 0; i--) {
764 m_d->stabilizerDeque.enqueue(firstPaintInfo);
765 }
766
767 // Poll and draw regularly
768 KisConfig cfg(true);
769 int stabilizerSampleSize = cfg.stabilizerSampleSize();
770 m_d->stabilizerPollTimer.setInterval(stabilizerSampleSize);
771 m_d->stabilizerPollTimer.start();
772
773 bool delayedPaintEnabled = cfg.stabilizerDelayedPaint();
774 if (delayedPaintEnabled) {
775 m_d->stabilizerDelayedPaintHelper.start(firstPaintInfo);
776 }
777
779 m_d->stabilizedSampler.addEvent(firstPaintInfo);
780}
781
783KisToolFreehandHelper::getStabilizedPaintInfo(const QQueue<KisPaintInformation> &queue,
784 const KisPaintInformation &lastPaintInfo)
785{
786 KisPaintInformation result(lastPaintInfo.pos(),
787 lastPaintInfo.pressure(),
788 lastPaintInfo.xTilt(),
789 lastPaintInfo.yTilt(),
790 lastPaintInfo.rotation(),
791 lastPaintInfo.tangentialPressure(),
792 lastPaintInfo.perspective(),
794 lastPaintInfo.drawingSpeed());
795
796 result.setCanvasRotation(lastPaintInfo.canvasRotation());
797 result.setCanvasMirroredH(lastPaintInfo.canvasMirroredH());
798 result.setCanvasMirroredV(lastPaintInfo.canvasMirroredV());
799
800 if (queue.size() > 1) {
801 QQueue<KisPaintInformation>::const_iterator it = queue.constBegin();
802 QQueue<KisPaintInformation>::const_iterator end = queue.constEnd();
803
807 it++;
808 int i = 2;
809
810 if (m_d->smoothingOptions->stabilizeSensors()) {
811 while (it != end) {
812 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
813 result.KisPaintInformation::mixOtherWithoutTime(k, *it);
814 it++;
815 i++;
816 }
817 } else{
818 while (it != end) {
819 qreal k = qreal(i - 1) / i; // coeff for uniform averaging
820 result.KisPaintInformation::mixOtherOnlyPosition(k, *it);
821 it++;
822 i++;
823 }
824 }
825 }
826
827 return result;
828}
829
831{
834 std::tie(it, end) = m_d->stabilizedSampler.range();
835 QVector<KisPaintInformation> delayedPaintTodoItems;
836
837 for (; it != end; ++it) {
838 KisPaintInformation sampledInfo = *it;
839
840 bool canPaint = true;
841
842 if (m_d->smoothingOptions->useDelayDistance()) {
843 const qreal R = m_d->smoothingOptions->delayDistance() /
845
846 QPointF diff = sampledInfo.pos() - m_d->previousPaintInformation.pos();
847 qreal dx = sqrt(pow2(diff.x()) + pow2(diff.y()));
848
849 if (!(dx > R)) {
851 sampledInfo.setPos(m_d->previousPaintInformation.pos());
852 }
853 else {
854 canPaint = false;
855 }
856 }
857 }
858
859 if (canPaint) {
861
863 delayedPaintTodoItems.append(newInfo);
864 } else {
866 }
867 m_d->previousPaintInformation = newInfo;
868
869 // Push the new entry through the queue
870 m_d->stabilizerDeque.dequeue();
871 m_d->stabilizerDeque.enqueue(sampledInfo);
872 } else if (m_d->stabilizerDeque.head().pos() != m_d->previousPaintInformation.pos()) {
873 QQueue<KisPaintInformation>::iterator it = m_d->stabilizerDeque.begin();
874 QQueue<KisPaintInformation>::iterator end = m_d->stabilizerDeque.end();
875
876 while (it != end) {
878 ++it;
879 }
880 }
881 }
882
884
886 m_d->stabilizerDelayedPaintHelper.update(delayedPaintTodoItems);
887 } else {
889 }
890}
891
893{
894 // Stop the timer
895 m_d->stabilizerPollTimer.stop();
896
897 // Finish the line
898 if (m_d->smoothingOptions->finishStabilizedCurve()) {
899 // Process all the existing events first
901
902 // Draw the finish line with pending events and a time override
905 }
906
909 }
910 m_d->usingStabilizer = false;
911}
912
914{
915 if (!isRunning()) {
916 return;
917 }
918 KisSmoothingOptions::SmoothingType currentSmoothingType =
919 m_d->smoothingOptions->smoothingType();
921 && (currentSmoothingType != KisSmoothingOptions::STABILIZER)) {
923 } else if (!m_d->usingStabilizer
924 && (currentSmoothingType == KisSmoothingOptions::STABILIZER)) {
926 }
927}
928
944
946{
947 // Check that the stroke hasn't ended.
948 if (!m_d->strokeInfos.isEmpty()) {
949
950 // Add a new painting update at a point identical to the previous one, except for the time
951 // and speed information.
953 KisPaintInformation nextPaint(prevPaint.pos(),
954 prevPaint.pressure(),
955 prevPaint.xTilt(),
956 prevPaint.yTilt(),
957 prevPaint.rotation(),
958 prevPaint.tangentialPressure(),
959 prevPaint.perspective(),
961 0.0);
962 nextPaint.setCanvasRotation(prevPaint.canvasRotation());
963 nextPaint.setCanvasMirroredH(prevPaint.canvasMirroredH());
964 nextPaint.setCanvasMirroredV(prevPaint.canvasMirroredV());
965 paint(nextPaint);
966 }
967}
968
970{
972 return qMax(1, qFloor(realInterval));
973}
974
979
984
985void KisToolFreehandHelper::paintAt(int strokeInfoId,
986 const KisPaintInformation &pi)
987{
988 m_d->hasPaintAtLeastOnce = true;
990 new FreehandStrokeStrategy::Data(strokeInfoId, pi));
991
992}
993
995 const KisPaintInformation &pi1,
996 const KisPaintInformation &pi2)
997{
998 m_d->hasPaintAtLeastOnce = true;
1000 new FreehandStrokeStrategy::Data(strokeInfoId, pi1, pi2));
1001
1002}
1003
1005 const KisPaintInformation &pi1,
1006 const QPointF &control1,
1007 const QPointF &control2,
1008 const KisPaintInformation &pi2)
1009{
1010
1011#ifdef DEBUG_BEZIER_CURVES
1014
1015 tpi1 = pi1;
1016 tpi2 = pi2;
1017
1018 tpi1.setPressure(0.3);
1019 tpi2.setPressure(0.3);
1020
1021 paintLine(tpi1, tpi2);
1022
1023 tpi1.setPressure(0.6);
1024 tpi2.setPressure(0.3);
1025
1026 tpi1.setPos(pi1.pos());
1027 tpi2.setPos(control1);
1028 paintLine(tpi1, tpi2);
1029
1030 tpi1.setPos(pi2.pos());
1031 tpi2.setPos(control2);
1032 paintLine(tpi1, tpi2);
1033#endif
1034
1035 m_d->hasPaintAtLeastOnce = true;
1037 new FreehandStrokeStrategy::Data(strokeInfoId,
1038 pi1, control1, control2, pi2));
1039
1040}
1041
1043 const KisDistanceInformation &startDist)
1044{
1045 strokeInfos << new KisFreehandStrokeInfo(startDist);
1046}
1047
1049{
1050 paintAt(0, pi);
1051}
1052
1054 const KisPaintInformation &pi2)
1055{
1056 paintLine(0, pi1, pi2);
1057}
1058
1060 const QPointF &control1,
1061 const QPointF &control2,
1062 const KisPaintInformation &pi2)
1063{
1064 paintBezierCurve(0, pi1, control1, control2, pi2);
1065}
Eigen::Matrix< double, 4, 2 > R
qreal distance(const QPointF &p1, const QPointF &p2)
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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 update(const QVector< KisPaintInformation > &newPaintInfos)
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)
QList< KisPaintInformation > history
KisStabilizedEventsSampler stabilizedSampler
QQueue< KisPaintInformation > stabilizerDeque
KisPaintOpUtils::PositionHistory lastCursorPos
KisPerStrokeRandomSourceSP fakeStrokeRandomSource
KoCanvasResourceProvider * resourceManager
QVector< KisFreehandStrokeInfo * > strokeInfos
KisAsynchronousStrokeUpdateHelper asyncUpdateHelper
KisStabilizerDelayedPaintHelper stabilizerDelayedPaintHelper
qreal effectiveSmoothnessDistance(qreal speed) const
KisPaintingInformationBuilder * infoBuilder
static KisUpdateTimeMonitor * instance()
void reportMouseMove(const QPointF &pos)