Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_paint_information.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007, 2010 Cyrille Berger <cberger@cberger.net>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QDomElement>
10#include <boost/optional.hpp>
11
12#include "kis_paintop.h"
13#include "kis_algebra_2d.h"
14#include "kis_lod_transform.h"
16
17#include <kis_dom_utils.h>
18
20 Private(const QPointF & pos_,
21 qreal pressure_,
22 qreal xTilt_, qreal yTilt_,
23 qreal rotation_,
24 qreal tangentialPressure_,
25 qreal perspective_,
26 qreal time_,
27 qreal speed_,
28 bool isHoveringMode_)
29 :
30 pos(pos_),
31 pressure(pressure_),
32 xTilt(xTilt_),
33 yTilt(yTilt_),
34 rotation(rotation_),
35 tangentialPressure(tangentialPressure_),
36 perspective(perspective_),
37 time(time_),
38 speed(speed_),
39 isHoveringMode(isHoveringMode_),
40 randomSource(0),
43 {
44 }
45
46
47
51 Private(const Private &rhs) {
52 copy(rhs);
53 }
54 Private& operator=(const Private &rhs) {
55 copy(rhs);
56 return *this;
57 }
58
85
86
87 QPointF pos;
88 qreal pressure;
89 qreal xTilt;
90 qreal yTilt;
91 qreal rotation;
94 qreal time;
95 qreal speed;
99 qreal canvasRotation {0};
100 bool canvasMirroredH {false};
101 bool canvasMirroredV {false};
102 qreal tiltDirectionOffset {0}; // [0.0, 360.0) degrees
103
104 boost::optional<qreal> drawingAngleOverride;
105 bool sanityIsRegistered = false;
106
109 DirectionHistoryInfo(qreal _totalDistance,
110 int _currentDabSeqNo,
111 qreal _lastAngle,
112 QPointF _lastPosition,
113 qreal _lastMaxPressure,
114 boost::optional<qreal> _lockedDrawingAngle)
115 : totalStrokeLength(_totalDistance),
116 currentDabSeqNo(_currentDabSeqNo),
117 lastAngle(_lastAngle),
118 lastPosition(_lastPosition),
119 lastMaxPressure(_lastMaxPressure),
120 lockedDrawingAngle(_lockedDrawingAngle)
121 {
122 }
123
124 qreal totalStrokeLength = 0.0;
126 qreal lastAngle = 0.0;
128 qreal lastMaxPressure = 0.0;
129 boost::optional<qreal> lockedDrawingAngle;
130 };
131 boost::optional<DirectionHistoryInfo> directionHistoryInfo;
132
134
147
149 sanityIsRegistered = false;
150 }
151};
152
159
165
168{
169 if (p) {
170 p->d->unregisterDistanceInfo();
171 }
172}
173
175 qreal pressure,
176 qreal xTilt, qreal yTilt,
177 qreal rotation,
178 qreal tangentialPressure,
179 qreal perspective,
180 qreal time,
181 qreal speed)
182 : d(new Private(pos,
183 pressure,
184 xTilt, yTilt,
185 rotation,
188 time,
189 speed,
190 false))
191{
192}
193
195 qreal pressure,
196 qreal xTilt,
197 qreal yTilt,
198 qreal rotation)
199 : d(new Private(pos,
200 pressure,
201 xTilt, yTilt,
202 rotation,
203 0.0,
204 1.0,
205 0.0,
206 0.0,
207 false))
208{
209
210}
211
213 qreal pressure)
214 : d(new Private(pos,
215 pressure,
216 0.0, 0.0,
217 0.0,
218 0.0,
219 1.0,
220 0.0,
221 0.0,
222 false))
223{
224}
225
230
232{
233 *d = *rhs.d;
234}
235
240
242{
243 return d->isHoveringMode;
244}
245
246
249 qreal pressure,
250 qreal xTilt, qreal yTilt,
251 qreal rotation,
252 qreal tangentialPressure,
253 qreal perspective,
254 qreal speed,
255 qreal canvasrotation,
256 bool canvasMirroredH,
257 bool canvasMirroredV,
258 qreal tiltDirectionOffset)
259{
261 pressure,
262 xTilt, yTilt,
263 rotation,
265 perspective, 0, speed);
266 info.d->isHoveringMode = true;
267 info.d->canvasRotation = canvasrotation;
271 return info;
272}
273
274
276{
277 return d->canvasRotation;
278}
279
284
286{
287 return d->canvasMirroredH;
288}
289
294
296{
297 return d->canvasMirroredV;
298}
299
304
309
314
315void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
316{
317 // hovering mode infos are not supposed to be saved
319
320 e.setAttribute("pointX", QString::number(pos().x(), 'g', 15));
321 e.setAttribute("pointY", QString::number(pos().y(), 'g', 15));
322 e.setAttribute("pressure", QString::number(pressure(), 'g', 15));
323 e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15));
324 e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15));
325 e.setAttribute("rotation", QString::number(rotation(), 'g', 15));
326 e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
327 e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
328 e.setAttribute("time", QString::number(d->time, 'g', 15));
329 e.setAttribute("speed", QString::number(d->speed, 'g', 15));
330}
331
333{
334 qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0")));
335 qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0")));
336 qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0")));
337 qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0")));
338 qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0")));
339 qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0")));
340 qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0")));
341 qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0")));
342 qreal time = KisDomUtils::toDouble(e.attribute("time", "0"));
343 qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0"));
344
345 return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
347}
348
349const QPointF& KisPaintInformation::pos() const
350{
351 return d->pos;
352}
353
354void KisPaintInformation::setPos(const QPointF& p)
355{
356 d->pos = p;
357}
358
360{
361 return d->pressure;
362}
363
365{
366 d->pressure = p;
367}
368
370{
371 return d->xTilt;
372}
373
375{
376 return d->yTilt;
377}
378
380{
381 d->drawingAngleOverride = angle;
382}
383
395
401
402qreal KisPaintInformation::drawingAngle(bool considerLockedAngle) const
403{
405
406 if (!d->directionHistoryInfo) {
407 warnKrita << "KisPaintInformation::drawingAngleSafe()" << "DirectionHistoryInfo object is not available";
408 return 0.0;
409 }
410
411 if (considerLockedAngle &&
412 d->directionHistoryInfo->lockedDrawingAngle) {
413
414 return *d->directionHistoryInfo->lockedDrawingAngle;
415 }
416
417 // If the start and end positions are the same, we can't compute an angle. In that case, use the
418 // provided default.
420 pos(),
421 d->directionHistoryInfo->lastAngle);
422}
423
425{
426 const qreal angle = drawingAngle(false);
427 return QPointF(cos(angle), sin(angle));
428}
429
431{
432 if (!d->directionHistoryInfo) {
433 warnKrita << "KisPaintInformation::drawingDistance()" << "DirectionHistoryInfo object is not available";
434 return 1.0;
435 }
436
437 QVector2D diff(pos() - d->directionHistoryInfo->lastPosition);
438 qreal length = diff.length();
439
440 if (d->levelOfDetail) {
442 }
443
444 return length;
445}
446
448{
449 if (!d->directionHistoryInfo) {
450 warnKrita << "KisPaintInformation::maxPressure()" << "DirectionHistoryInfo object is not available";
451 return d->pressure;
452 }
453
454 return qMax(d->directionHistoryInfo->lastMaxPressure, d->pressure);
455}
456
458{
459 return d->speed;
460}
461
463
464 d->time = time;
465}
466
468{
469 return d->rotation;
470}
471
476
478{
479 return d->perspective;
480}
481
483{
484 return d->time;
485}
486
488{
489 if (!d->directionHistoryInfo) {
490 warnKrita << "KisPaintInformation::currentDabSeqNo()" << "DirectionHistoryInfo object is not available";
491 return 0;
492 }
493
494 return d->directionHistoryInfo->currentDabSeqNo;
495}
496
498{
499 if (!d->directionHistoryInfo) {
500 warnKrita << "KisPaintInformation::totalStrokeLength()" << "DirectionHistoryInfo object is not available";
501 return 0;
502 }
503
504 return d->directionHistoryInfo->totalStrokeLength;
505}
506
508{
509 if (!d->randomSource) {
510 qWarning() << "Accessing uninitialized random source!";
511 qDebug().noquote() << kisBacktrace();
513 }
514
515 return d->randomSource;
516}
517
522
524{
525 if (!d->perStrokeRandomSource) {
526 qWarning() << "Accessing uninitialized per stroke random source!";
528 }
529
530 return d->perStrokeRandomSource;
531}
532
537
539{
540 d->levelOfDetail = levelOfDetail;
541}
542
543QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
544{
545#ifdef NDEBUG
546 Q_UNUSED(info);
547#else
548 dbg.nospace() << "Position: " << info.pos();
549 dbg.nospace() << ", Pressure: " << info.pressure();
550 dbg.nospace() << ", X Tilt: " << info.xTilt();
551 dbg.nospace() << ", Y Tilt: " << info.yTilt();
552 dbg.nospace() << ", Rotation: " << info.rotation();
553 dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure();
554 dbg.nospace() << ", Perspective: " << info.perspective();
555 dbg.nospace() << ", Drawing Angle: " << info.drawingAngle();
556 dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed();
557 dbg.nospace() << ", Drawing Distance: " << info.drawingDistance();
558 dbg.nospace() << ", Time: " << info.currentTime();
559#endif
560 return dbg.space();
561}
562
564{
565 QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos();
566 return mixImpl(pt, t, mixedPi, basePi, true, false);
567}
568
570{
571 QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
572 return mix(pt, t, pi1, pi2);
573}
574
576{
577 return mixImpl(p, t, pi1, pi2, false, true);
578}
579
581{
582 QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
583 return mixWithoutTime(pt, t, pi1, pi2);
584}
585
587{
588 return mixImpl(p, t, pi1, pi2, false, false);
589}
590
592{
593 QPointF pt = (1 - t) * other.pos() + t * this->pos();
594 this->mixOtherImpl(pt, t, other, true, false);
595}
596
598{
599 QPointF pt = (1 - t) * other.pos() + t * this->pos();
600 this->mixOtherImpl(pt, t, other, false, false);
601}
602
603KisPaintInformation KisPaintInformation::mixImpl(const QPointF &p, qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2, bool posOnly, bool mixTime)
604{
605 KisPaintInformation result(pi2);
606 result.mixOtherImpl(p, t, pi1, posOnly, mixTime);
607 return result;
608}
609
610void KisPaintInformation::mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime)
611{
612 if (posOnly) {
613 this->d->pos = p;
614 this->d->isHoveringMode = false;
615 this->d->levelOfDetail = 0;
616 return;
617 }
618 else {
619 qreal pressure = (1 - t) * other.pressure() + t * this->pressure();
620 qreal xTilt = (1 - t) * other.xTilt() + t * this->xTilt();
621 qreal yTilt = (1 - t) * other.yTilt() + t * this->yTilt();
622
623 qreal rotation = other.rotation();
624
625 if (other.rotation() != this->rotation()) {
626 qreal a1 = kisDegreesToRadians(other.rotation());
627 qreal a2 = kisDegreesToRadians(this->rotation());
628 qreal distance = shortestAngularDistance(a2, a1);
629
631 }
632
633 qreal tangentialPressure = (1 - t) * other.tangentialPressure() + t * this->tangentialPressure();
634 qreal perspective = (1 - t) * other.perspective() + t * this->perspective();
635 qreal time = mixTime ? ((1 - t) * other.currentTime() + t * this->currentTime()) : this->currentTime();
636 qreal speed = (1 - t) * other.drawingSpeed() + t * this->drawingSpeed();
637
638 KIS_ASSERT_RECOVER_NOOP(other.isHoveringMode() == this->isHoveringMode());
639 *(this->d) = Private(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, other.isHoveringMode());
640 this->d->canvasRotation = other.d->canvasRotation;
641 this->d->canvasMirroredH = other.d->canvasMirroredH;
642 this->d->canvasMirroredV = other.d->canvasMirroredV;
643 this->d->randomSource = other.d->randomSource;
645 // this->d->isHoveringMode = other.isHoveringMode();
646 this->d->levelOfDetail = other.d->levelOfDetail;
648 }
649}
650
652{
653 qreal xTilt = info.xTilt();
654 qreal yTilt = info.yTilt();
655
656 // radians -PI, PI
657 qreal tiltDirection = 0.0;
658
669 } else {
670 tiltDirection = atan2(-xTilt, yTilt);
671 }
672
673 if (!qFuzzyIsNull(info.d->tiltDirectionOffset)) {
675 // ensure we stay in the -PI, PI range
676 if (tiltDirection < -M_PI) {
677 tiltDirection += 2 * M_PI;
678 } else if (tiltDirection > M_PI) {
679 tiltDirection -= 2 * M_PI;
680 }
681 }
682
683 // if normalize is true map to 0.0..1.0
684 return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection;
685}
686
687qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize)
688{
689 qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0));
690 qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0));
691
692 qreal e;
693 if (fabs(xTilt) > fabs(yTilt)) {
694 e = sqrt(qreal(1.0) + yTilt * yTilt);
695 } else {
696 e = sqrt(qreal(1.0) + xTilt * xTilt);
697 }
698
699 qreal cosAlpha = sqrt(xTilt * xTilt + yTilt * yTilt) / e;
700 qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI]
701
702 // mapping to 0.0..1.0 if normalize is true
703 return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation;
704}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
const Params2D p
qreal distance(const QPointF &p1, const QPointF &p2)
static qreal lodToInvScale(int levelOfDetail)
DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo)
void mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime)
void setRandomSource(KisRandomSourceSP value)
void mixOtherOnlyPosition(qreal t, const KisPaintInformation &other)
void setPos(const QPointF &p)
qreal perspective() const
reciprocal of distance on the perspective grid
void setTiltDirectionOffset(qreal angle)
KisRandomSourceSP randomSource() const
void overrideDrawingAngle(qreal angle)
XXX !!! :-| Please add dox!
void mixOtherWithoutTime(qreal t, const KisPaintInformation &other)
const QPointF & pos() const
qreal drawingAngleSafe(const KisDistanceInformation &distance) const
XXX !!! :-| Please add dox!
void setCanvasMirroredV(bool value)
static KisPaintInformation mixOnlyPosition(qreal t, const KisPaintInformation &mixedPi, const KisPaintInformation &basePi)
(1-t) * p1 + t * p2
static KisPaintInformation fromXML(const QDomElement &)
void setCurrentTime(qreal time) const
void setCanvasMirroredH(bool value)
KisPerStrokeRandomSourceSP perStrokeRandomSource() const
static qreal tiltDirection(const KisPaintInformation &info, bool normalize=true)
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)
static qreal tiltElevation(const KisPaintInformation &info, qreal maxTiltX=60.0, qreal maxTiltY=60.0, bool normalize=true)
static KisPaintInformation createHoveringModeInfo(const QPointF &pos, qreal pressure=PRESSURE_DEFAULT, qreal xTilt=0.0, qreal yTilt=0.0, qreal rotation=0.0, qreal tangentialPressure=0.0, qreal perspective=1.0, qreal speed=0.0, qreal canvasrotation=0.0, bool canvasMirroredH=false, bool canvasMirroredV=false, qreal tiltDirectionOffset=0.0)
void setCanvasRotation(qreal rotation)
void setLevelOfDetail(int levelOfDetail)
static KisPaintInformation mix(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2)
void toXML(QDomDocument &, QDomElement &) const
static KisPaintInformation mixImpl(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2, bool posOnly, bool mixTime)
static KisPaintInformation mixWithoutTime(const QPointF &p, qreal t, const KisPaintInformation &p1, const KisPaintInformation &p2)
QPointF drawingDirectionVector() const
KisPaintInformation(const QPointF &pos, qreal pressure, qreal xTilt, qreal yTilt, qreal rotation, qreal tangentialPressure, qreal perspective, qreal time, qreal speed)
qreal totalStrokeLength() const
The length of the stroke before painting the current dab.
void operator=(const KisPaintInformation &rhs)
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
int currentDabSeqNo() const
Number of dabs painted since the beginning of the stroke.
qreal pressure() const
The pressure of the value (from 0.0 to 1.0)
qreal drawingAngle(bool considerLockedAngle=false) const
static bool qFuzzyIsNull(half h)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
QString kisBacktrace()
Definition kis_debug.cpp:51
#define warnKrita
Definition kis_debug.h:87
qreal incrementInDirection(qreal a, qreal inc, qreal direction)
Definition kis_global.h:147
T kisRadiansToDegrees(T radians)
Definition kis_global.h:181
std::enable_if< std::is_floating_point< T >::value, T >::type normalizeAngleDegrees(T a)
Definition kis_global.h:132
qreal shortestAngularDistance(qreal a, qreal b)
Definition kis_global.h:140
T kisDegreesToRadians(T degrees)
Definition kis_global.h:176
#define M_PI
Definition kis_global.h:111
QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
qreal directionBetweenPoints(const QPointF &p1, const QPointF &p2, qreal defaultAngle)
double toDouble(const QString &str, bool *ok=nullptr)
boost::optional< qreal > lockedDrawingAngleOptional
DirectionHistoryInfo(qreal _totalDistance, int _currentDabSeqNo, qreal _lastAngle, QPointF _lastPosition, qreal _lastMaxPressure, boost::optional< qreal > _lockedDrawingAngle)
KisPerStrokeRandomSourceSP perStrokeRandomSource
Private(const QPointF &pos_, qreal pressure_, qreal xTilt_, qreal yTilt_, qreal rotation_, qreal tangentialPressure_, qreal perspective_, qreal time_, qreal speed_, bool isHoveringMode_)
boost::optional< qreal > drawingAngleOverride
void registerDistanceInfo(KisDistanceInformation *di)
boost::optional< DirectionHistoryInfo > directionHistoryInfo
Private & operator=(const Private &rhs)