File indexing completed on 2025-01-26 04:05:51

0001 /*
0002  *  SPDX-FileCopyrightText: 2007, 2010 Cyrille Berger <cberger@cberger.net>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include <brushengine/kis_paint_information.h>
0008 
0009 #include <QDomElement>
0010 #include <boost/optional.hpp>
0011 
0012 #include "kis_paintop.h"
0013 #include "kis_algebra_2d.h"
0014 #include "kis_lod_transform.h"
0015 #include "kis_spacing_information.h"
0016 
0017 #include <kis_dom_utils.h>
0018 
0019 struct KisPaintInformation::Private {
0020     Private(const QPointF & pos_,
0021             qreal pressure_,
0022             qreal xTilt_, qreal yTilt_,
0023             qreal rotation_,
0024             qreal tangentialPressure_,
0025             qreal perspective_,
0026             qreal time_,
0027             qreal speed_,
0028             bool isHoveringMode_)
0029         :
0030         pos(pos_),
0031         pressure(pressure_),
0032         xTilt(xTilt_),
0033         yTilt(yTilt_),
0034         rotation(rotation_),
0035         tangentialPressure(tangentialPressure_),
0036         perspective(perspective_),
0037         time(time_),
0038         speed(speed_),
0039         isHoveringMode(isHoveringMode_),
0040         randomSource(0),
0041         perStrokeRandomSource(0),
0042         levelOfDetail(0)
0043     {
0044     }
0045 
0046 
0047 
0048     ~Private() {
0049         KIS_ASSERT_RECOVER_NOOP(!sanityIsRegistered);
0050     }
0051     Private(const Private &rhs) {
0052         copy(rhs);
0053     }
0054     Private& operator=(const Private &rhs) {
0055         copy(rhs);
0056         return *this;
0057     }
0058 
0059     void copy(const Private &rhs) {
0060         pos = rhs.pos;
0061         pressure = rhs.pressure;
0062         xTilt = rhs.xTilt;
0063         yTilt = rhs.yTilt;
0064         rotation = rhs.rotation;
0065         tangentialPressure = rhs.tangentialPressure;
0066         perspective = rhs.perspective;
0067         time = rhs.time;
0068         speed = rhs.speed;
0069         isHoveringMode = rhs.isHoveringMode;
0070         randomSource = rhs.randomSource;
0071         perStrokeRandomSource = rhs.perStrokeRandomSource;
0072         sanityIsRegistered = false; // HINT: we do not copy registration mark!
0073         directionHistoryInfo = rhs.directionHistoryInfo;
0074         canvasRotation = rhs.canvasRotation;
0075         canvasMirroredH = rhs.canvasMirroredH;
0076         canvasMirroredV = rhs.canvasMirroredV;
0077 
0078         if (rhs.drawingAngleOverride) {
0079             drawingAngleOverride = *rhs.drawingAngleOverride;
0080         }
0081 
0082         levelOfDetail = rhs.levelOfDetail;
0083     }
0084 
0085 
0086     QPointF pos;
0087     qreal pressure;
0088     qreal xTilt;
0089     qreal yTilt;
0090     qreal rotation;
0091     qreal tangentialPressure;
0092     qreal perspective;
0093     qreal time;
0094     qreal speed;
0095     bool isHoveringMode;
0096     KisRandomSourceSP randomSource;
0097     KisPerStrokeRandomSourceSP perStrokeRandomSource;
0098     qreal canvasRotation {0};
0099     bool canvasMirroredH {false};
0100     bool canvasMirroredV {false};
0101 
0102     boost::optional<qreal> drawingAngleOverride;
0103     bool sanityIsRegistered = false;
0104 
0105     struct DirectionHistoryInfo {
0106         DirectionHistoryInfo() {}
0107         DirectionHistoryInfo(qreal _totalDistance,
0108                              int _currentDabSeqNo,
0109                              qreal _lastAngle,
0110                              QPointF _lastPosition,
0111                              qreal _lastMaxPressure,
0112                              boost::optional<qreal> _lockedDrawingAngle)
0113             : totalStrokeLength(_totalDistance),
0114               currentDabSeqNo(_currentDabSeqNo),
0115               lastAngle(_lastAngle),
0116               lastPosition(_lastPosition),
0117               lastMaxPressure(_lastMaxPressure),
0118               lockedDrawingAngle(_lockedDrawingAngle)
0119         {
0120         }
0121 
0122         qreal totalStrokeLength = 0.0;
0123         int currentDabSeqNo = 0;
0124         qreal lastAngle = 0.0;
0125         QPointF lastPosition;
0126         qreal lastMaxPressure = 0.0;
0127         boost::optional<qreal> lockedDrawingAngle;
0128     };
0129     boost::optional<DirectionHistoryInfo> directionHistoryInfo;
0130 
0131     int levelOfDetail;
0132 
0133     void registerDistanceInfo(KisDistanceInformation *di) {
0134         directionHistoryInfo = DirectionHistoryInfo(di->scalarDistanceApprox(),
0135                                                     di->currentDabSeqNo(),
0136                                                     di->lastDrawingAngle(),
0137                                                     di->lastPosition(),
0138                                                     di->maxPressure(),
0139                                                     di->lockedDrawingAngleOptional());
0140 
0141 
0142         KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityIsRegistered);
0143         sanityIsRegistered = true;
0144     }
0145 
0146     void unregisterDistanceInfo() {
0147         sanityIsRegistered = false;
0148     }
0149 };
0150 
0151 KisPaintInformation::DistanceInformationRegistrar::
0152 DistanceInformationRegistrar(KisPaintInformation *_p, KisDistanceInformation *distanceInfo)
0153     : p(_p)
0154 {
0155     p->d->registerDistanceInfo(distanceInfo);
0156 }
0157 
0158 KisPaintInformation::DistanceInformationRegistrar::DistanceInformationRegistrar(KisPaintInformation::DistanceInformationRegistrar &&rhs)
0159     : p(0)
0160 {
0161     std::swap(p, rhs.p);
0162 }
0163 
0164 KisPaintInformation::DistanceInformationRegistrar::
0165 ~DistanceInformationRegistrar()
0166 {
0167     if (p) {
0168         p->d->unregisterDistanceInfo();
0169     }
0170 }
0171 
0172 KisPaintInformation::KisPaintInformation(const QPointF & pos,
0173                                          qreal pressure,
0174                                          qreal xTilt, qreal yTilt,
0175                                          qreal rotation,
0176                                          qreal tangentialPressure,
0177                                          qreal perspective,
0178                                          qreal time,
0179                                          qreal speed)
0180     : d(new Private(pos,
0181                     pressure,
0182                     xTilt, yTilt,
0183                     rotation,
0184                     tangentialPressure,
0185                     perspective,
0186                     time,
0187                     speed,
0188                     false))
0189 {
0190 }
0191 
0192 KisPaintInformation::KisPaintInformation(const QPointF & pos,
0193                                          qreal pressure,
0194                                          qreal xTilt,
0195                                          qreal yTilt,
0196                                          qreal rotation)
0197     : d(new Private(pos,
0198                     pressure,
0199                     xTilt, yTilt,
0200                     rotation,
0201                     0.0,
0202                     1.0,
0203                     0.0,
0204                     0.0,
0205                     false))
0206 {
0207 
0208 }
0209 
0210 KisPaintInformation::KisPaintInformation(const QPointF &pos,
0211                                          qreal pressure)
0212     : d(new Private(pos,
0213                     pressure,
0214                     0.0, 0.0,
0215                     0.0,
0216                     0.0,
0217                     1.0,
0218                     0.0,
0219                     0.0,
0220                     false))
0221 {
0222 }
0223 
0224 KisPaintInformation::KisPaintInformation(const KisPaintInformation& rhs)
0225     : d(new Private(*rhs.d))
0226 {
0227 }
0228 
0229 void KisPaintInformation::operator=(const KisPaintInformation & rhs)
0230 {
0231     *d = *rhs.d;
0232 }
0233 
0234 KisPaintInformation::~KisPaintInformation()
0235 {
0236     delete d;
0237 }
0238 
0239 bool KisPaintInformation::isHoveringMode() const
0240 {
0241     return d->isHoveringMode;
0242 }
0243 
0244 
0245 KisPaintInformation
0246 KisPaintInformation::createHoveringModeInfo(const QPointF &pos,
0247         qreal pressure,
0248         qreal xTilt, qreal yTilt,
0249         qreal rotation,
0250         qreal tangentialPressure,
0251         qreal perspective,
0252         qreal speed,
0253         qreal canvasrotation,
0254         bool canvasMirroredH,
0255         bool canvasMirroredV)
0256 {
0257     KisPaintInformation info(pos,
0258                              pressure,
0259                              xTilt, yTilt,
0260                              rotation,
0261                              tangentialPressure,
0262                              perspective, 0, speed);
0263     info.d->isHoveringMode = true;
0264     info.d->canvasRotation = canvasrotation;
0265     info.d->canvasMirroredH = canvasMirroredH;
0266     info.d->canvasMirroredV = canvasMirroredV;
0267     return info;
0268 }
0269 
0270 
0271 qreal KisPaintInformation::canvasRotation() const
0272 {
0273     return d->canvasRotation;
0274 }
0275 
0276 void KisPaintInformation::setCanvasRotation(qreal rotation)
0277 {
0278     d->canvasRotation = normalizeAngleDegrees(rotation);
0279 }
0280 
0281 bool KisPaintInformation::canvasMirroredH() const
0282 {
0283     return d->canvasMirroredH;
0284 }
0285 
0286 void KisPaintInformation::setCanvasMirroredH(bool value)
0287 {
0288     d->canvasMirroredH = value;
0289 }
0290 
0291 bool KisPaintInformation::canvasMirroredV() const
0292 {
0293     return d->canvasMirroredV;
0294 }
0295 
0296 void KisPaintInformation::setCanvasMirroredV(bool value)
0297 {
0298     d->canvasMirroredV = value;
0299 }
0300 
0301 void KisPaintInformation::toXML(QDomDocument&, QDomElement& e) const
0302 {
0303     // hovering mode infos are not supposed to be saved
0304     KIS_ASSERT_RECOVER_NOOP(!d->isHoveringMode);
0305 
0306     e.setAttribute("pointX", QString::number(pos().x(), 'g', 15));
0307     e.setAttribute("pointY", QString::number(pos().y(), 'g', 15));
0308     e.setAttribute("pressure", QString::number(pressure(), 'g', 15));
0309     e.setAttribute("xTilt", QString::number(xTilt(), 'g', 15));
0310     e.setAttribute("yTilt", QString::number(yTilt(), 'g', 15));
0311     e.setAttribute("rotation", QString::number(rotation(), 'g', 15));
0312     e.setAttribute("tangentialPressure", QString::number(tangentialPressure(), 'g', 15));
0313     e.setAttribute("perspective", QString::number(perspective(), 'g', 15));
0314     e.setAttribute("time", QString::number(d->time, 'g', 15));
0315     e.setAttribute("speed", QString::number(d->speed, 'g', 15));
0316 }
0317 
0318 KisPaintInformation KisPaintInformation::fromXML(const QDomElement& e)
0319 {
0320     qreal pointX = qreal(KisDomUtils::toDouble(e.attribute("pointX", "0.0")));
0321     qreal pointY = qreal(KisDomUtils::toDouble(e.attribute("pointY", "0.0")));
0322     qreal pressure = qreal(KisDomUtils::toDouble(e.attribute("pressure", "0.0")));
0323     qreal rotation = qreal(KisDomUtils::toDouble(e.attribute("rotation", "0.0")));
0324     qreal tangentialPressure = qreal(KisDomUtils::toDouble(e.attribute("tangentialPressure", "0.0")));
0325     qreal perspective = qreal(KisDomUtils::toDouble(e.attribute("perspective", "0.0")));
0326     qreal xTilt = qreal(KisDomUtils::toDouble(e.attribute("xTilt", "0.0")));
0327     qreal yTilt = qreal(KisDomUtils::toDouble(e.attribute("yTilt", "0.0")));
0328     qreal time = KisDomUtils::toDouble(e.attribute("time", "0"));
0329     qreal speed = KisDomUtils::toDouble(e.attribute("speed", "0"));
0330 
0331     return KisPaintInformation(QPointF(pointX, pointY), pressure, xTilt, yTilt,
0332                                rotation, tangentialPressure, perspective, time, speed);
0333 }
0334 
0335 const QPointF& KisPaintInformation::pos() const
0336 {
0337     return d->pos;
0338 }
0339 
0340 void KisPaintInformation::setPos(const QPointF& p)
0341 {
0342     d->pos = p;
0343 }
0344 
0345 qreal KisPaintInformation::pressure() const
0346 {
0347     return d->pressure;
0348 }
0349 
0350 void KisPaintInformation::setPressure(qreal p)
0351 {
0352     d->pressure = p;
0353 }
0354 
0355 qreal KisPaintInformation::xTilt() const
0356 {
0357     return d->xTilt;
0358 }
0359 
0360 qreal KisPaintInformation::yTilt() const
0361 {
0362     return d->yTilt;
0363 }
0364 
0365 void KisPaintInformation::overrideDrawingAngle(qreal angle)
0366 {
0367     d->drawingAngleOverride = angle;
0368 }
0369 
0370 qreal KisPaintInformation::drawingAngleSafe(const KisDistanceInformation &distance) const
0371 {
0372     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!d->directionHistoryInfo, 0.0);
0373     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(distance.hasLastDabInformation(), 0.0);
0374     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!d->drawingAngleOverride, 0.0);
0375 
0376     return KisAlgebra2D::directionBetweenPoints(distance.lastPosition(),
0377                                                 pos(),
0378                                                 distance.lastDrawingAngle());
0379 
0380 }
0381 
0382 KisPaintInformation::DistanceInformationRegistrar
0383 KisPaintInformation::registerDistanceInformation(KisDistanceInformation *distance)
0384 {
0385     return DistanceInformationRegistrar(this, distance);
0386 }
0387 
0388 qreal KisPaintInformation::drawingAngle(bool considerLockedAngle) const
0389 {
0390     if (d->drawingAngleOverride) return *d->drawingAngleOverride;
0391 
0392     if (!d->directionHistoryInfo) {
0393         warnKrita << "KisPaintInformation::drawingAngleSafe()" << "DirectionHistoryInfo object is not available";
0394         return 0.0;
0395     }
0396 
0397     if (considerLockedAngle &&
0398         d->directionHistoryInfo->lockedDrawingAngle) {
0399 
0400         return *d->directionHistoryInfo->lockedDrawingAngle;
0401     }
0402 
0403     // If the start and end positions are the same, we can't compute an angle. In that case, use the
0404     // provided default.
0405     return KisAlgebra2D::directionBetweenPoints(d->directionHistoryInfo->lastPosition,
0406                                                 pos(),
0407                                                 d->directionHistoryInfo->lastAngle);
0408 }
0409 
0410 QPointF KisPaintInformation::drawingDirectionVector() const
0411 {
0412     const qreal angle = drawingAngle(false);
0413     return QPointF(cos(angle), sin(angle));
0414 }
0415 
0416 qreal KisPaintInformation::drawingDistance() const
0417 {
0418     if (!d->directionHistoryInfo) {
0419         warnKrita << "KisPaintInformation::drawingDistance()" << "DirectionHistoryInfo object is not available";
0420         return 1.0;
0421     }
0422 
0423     QVector2D diff(pos() - d->directionHistoryInfo->lastPosition);
0424     qreal length = diff.length();
0425 
0426     if (d->levelOfDetail) {
0427         length *= KisLodTransform::lodToInvScale(d->levelOfDetail);
0428     }
0429 
0430     return length;
0431 }
0432 
0433 qreal KisPaintInformation::maxPressure() const
0434 {
0435     if (!d->directionHistoryInfo) {
0436         warnKrita << "KisPaintInformation::maxPressure()" << "DirectionHistoryInfo object is not available";
0437         return d->pressure;
0438     }
0439 
0440     return qMax(d->directionHistoryInfo->lastMaxPressure, d->pressure);
0441 }
0442 
0443 qreal KisPaintInformation::drawingSpeed() const
0444 {
0445     return d->speed;
0446 }
0447 
0448 void KisPaintInformation::setCurrentTime(qreal time) const {
0449 
0450     d->time =  time;
0451 }
0452 
0453 qreal KisPaintInformation::rotation() const
0454 {
0455     return d->rotation;
0456 }
0457 
0458 qreal KisPaintInformation::tangentialPressure() const
0459 {
0460     return d->tangentialPressure;
0461 }
0462 
0463 qreal KisPaintInformation::perspective() const
0464 {
0465     return d->perspective;
0466 }
0467 
0468 qreal KisPaintInformation::currentTime() const
0469 {
0470     return d->time;
0471 }
0472 
0473 int KisPaintInformation::currentDabSeqNo() const
0474 {
0475     if (!d->directionHistoryInfo) {
0476         warnKrita << "KisPaintInformation::currentDabSeqNo()" << "DirectionHistoryInfo object is not available";
0477         return 0;
0478     }
0479 
0480     return d->directionHistoryInfo->currentDabSeqNo;
0481 }
0482 
0483 qreal KisPaintInformation::totalStrokeLength() const
0484 {
0485     if (!d->directionHistoryInfo) {
0486         warnKrita << "KisPaintInformation::totalStrokeLength()" << "DirectionHistoryInfo object is not available";
0487         return 0;
0488     }
0489 
0490     return d->directionHistoryInfo->totalStrokeLength;
0491 }
0492 
0493 KisRandomSourceSP KisPaintInformation::randomSource() const
0494 {
0495     if (!d->randomSource) {
0496         qWarning() << "Accessing uninitialized random source!";
0497         qDebug().noquote() << kisBacktrace();
0498         d->randomSource = new KisRandomSource();
0499     }
0500 
0501     return d->randomSource;
0502 }
0503 
0504 void KisPaintInformation::setRandomSource(KisRandomSourceSP value)
0505 {
0506     d->randomSource = value;
0507 }
0508 
0509 KisPerStrokeRandomSourceSP KisPaintInformation::perStrokeRandomSource() const
0510 {
0511     if (!d->perStrokeRandomSource) {
0512         qWarning() << "Accessing uninitialized per stroke random source!";
0513         d->perStrokeRandomSource = new KisPerStrokeRandomSource();
0514     }
0515 
0516     return d->perStrokeRandomSource;
0517 }
0518 
0519 void KisPaintInformation::setPerStrokeRandomSource(KisPerStrokeRandomSourceSP value)
0520 {
0521     d->perStrokeRandomSource = value;
0522 }
0523 
0524 void KisPaintInformation::setLevelOfDetail(int levelOfDetail)
0525 {
0526     d->levelOfDetail = levelOfDetail;
0527 }
0528 
0529 QDebug operator<<(QDebug dbg, const KisPaintInformation &info)
0530 {
0531 #ifdef NDEBUG
0532     Q_UNUSED(info);
0533 #else
0534     dbg.nospace() << "Position: " << info.pos();
0535     dbg.nospace() << ", Pressure: " << info.pressure();
0536     dbg.nospace() << ", X Tilt: " << info.xTilt();
0537     dbg.nospace() << ", Y Tilt: " << info.yTilt();
0538     dbg.nospace() << ", Rotation: " << info.rotation();
0539     dbg.nospace() << ", Tangential Pressure: " << info.tangentialPressure();
0540     dbg.nospace() << ", Perspective: " << info.perspective();
0541     dbg.nospace() << ", Drawing Angle: " << info.drawingAngle();
0542     dbg.nospace() << ", Drawing Speed: " << info.drawingSpeed();
0543     dbg.nospace() << ", Drawing Distance: " << info.drawingDistance();
0544     dbg.nospace() << ", Time: " << info.currentTime();
0545 #endif
0546     return dbg.space();
0547 }
0548 
0549 KisPaintInformation KisPaintInformation::mixOnlyPosition(qreal t, const KisPaintInformation& mixedPi, const KisPaintInformation& basePi)
0550 {
0551     QPointF pt = (1 - t) * mixedPi.pos() + t * basePi.pos();
0552     return mixImpl(pt, t, mixedPi, basePi, true, false);
0553 }
0554 
0555 KisPaintInformation KisPaintInformation::mix(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
0556 {
0557     QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
0558     return mix(pt, t, pi1, pi2);
0559 }
0560 
0561 KisPaintInformation KisPaintInformation::mix(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
0562 {
0563     return mixImpl(p, t, pi1, pi2, false, true);
0564 }
0565 
0566 KisPaintInformation KisPaintInformation::mixWithoutTime(qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
0567 {
0568     QPointF pt = (1 - t) * pi1.pos() + t * pi2.pos();
0569     return mixWithoutTime(pt, t, pi1, pi2);
0570 }
0571 
0572 KisPaintInformation KisPaintInformation::mixWithoutTime(const QPointF& p, qreal t, const KisPaintInformation& pi1, const KisPaintInformation& pi2)
0573 {
0574     return mixImpl(p, t, pi1, pi2, false, false);
0575 }
0576 
0577 void KisPaintInformation::mixOtherOnlyPosition(qreal t, const KisPaintInformation& other)
0578 {
0579     QPointF pt = (1 - t) * other.pos() + t * this->pos();
0580     this->mixOtherImpl(pt, t, other, true, false);
0581 }
0582 
0583 void KisPaintInformation::mixOtherWithoutTime(qreal t, const KisPaintInformation& other)
0584 {
0585     QPointF pt = (1 - t) * other.pos() + t * this->pos();
0586     this->mixOtherImpl(pt, t, other, false, false);
0587 }
0588 
0589 KisPaintInformation KisPaintInformation::mixImpl(const QPointF &p, qreal t, const KisPaintInformation &pi1, const KisPaintInformation &pi2, bool posOnly, bool mixTime)
0590 {
0591     KisPaintInformation result(pi2);
0592     result.mixOtherImpl(p, t, pi1, posOnly, mixTime);
0593     return result;
0594 }
0595 
0596 void KisPaintInformation::mixOtherImpl(const QPointF &p, qreal t, const KisPaintInformation &other, bool posOnly, bool mixTime)
0597 {
0598     if (posOnly) {
0599         this->d->pos = p;
0600         this->d->isHoveringMode = false;
0601         this->d->levelOfDetail = 0;
0602         return;
0603     }
0604     else {
0605         qreal pressure = (1 - t) * other.pressure() + t * this->pressure();
0606         qreal xTilt = (1 - t) * other.xTilt() + t * this->xTilt();
0607         qreal yTilt = (1 - t) * other.yTilt() + t * this->yTilt();
0608 
0609         qreal rotation = other.rotation();
0610 
0611         if (other.rotation() != this->rotation()) {
0612             qreal a1 = kisDegreesToRadians(other.rotation());
0613             qreal a2 = kisDegreesToRadians(this->rotation());
0614             qreal distance = shortestAngularDistance(a2, a1);
0615 
0616             rotation = kisRadiansToDegrees(incrementInDirection(a1, t * distance, a2));
0617         }
0618 
0619         qreal tangentialPressure = (1 - t) * other.tangentialPressure() + t * this->tangentialPressure();
0620         qreal perspective = (1 - t) * other.perspective() + t * this->perspective();
0621         qreal time = mixTime ? ((1 - t) * other.currentTime() + t * this->currentTime()) : this->currentTime();
0622         qreal speed = (1 - t) * other.drawingSpeed() + t * this->drawingSpeed();
0623 
0624         KIS_ASSERT_RECOVER_NOOP(other.isHoveringMode() == this->isHoveringMode());
0625         *(this->d) = Private(p, pressure, xTilt, yTilt, rotation, tangentialPressure, perspective, time, speed, other.isHoveringMode());
0626         this->d->canvasRotation = other.d->canvasRotation;
0627         this->d->canvasMirroredH = other.d->canvasMirroredH;
0628         this->d->canvasMirroredV = other.d->canvasMirroredV;
0629         this->d->randomSource = other.d->randomSource;
0630         this->d->perStrokeRandomSource = other.d->perStrokeRandomSource;
0631         // this->d->isHoveringMode = other.isHoveringMode();
0632         this->d->levelOfDetail = other.d->levelOfDetail;
0633     }
0634 }
0635 
0636 qreal KisPaintInformation::tiltDirection(const KisPaintInformation& info, bool normalize)
0637 {
0638     qreal xTilt = info.xTilt();
0639     qreal yTilt = info.yTilt();
0640     // radians -PI, PI
0641     qreal tiltDirection = atan2(-xTilt, yTilt);
0642     // if normalize is true map to 0.0..1.0
0643     return normalize ? (tiltDirection / (2 * M_PI) + 0.5) : tiltDirection;
0644 }
0645 
0646 qreal KisPaintInformation::tiltElevation(const KisPaintInformation& info, qreal maxTiltX, qreal maxTiltY, bool normalize)
0647 {
0648     qreal xTilt = qBound(qreal(-1.0), info.xTilt() / maxTiltX , qreal(1.0));
0649     qreal yTilt = qBound(qreal(-1.0), info.yTilt() / maxTiltY , qreal(1.0));
0650 
0651     qreal e;
0652     if (fabs(xTilt) > fabs(yTilt)) {
0653         e = sqrt(qreal(1.0) + yTilt * yTilt);
0654     } else {
0655         e = sqrt(qreal(1.0) + xTilt * xTilt);
0656     }
0657 
0658     qreal cosAlpha    = sqrt(xTilt * xTilt + yTilt * yTilt) / e;
0659     qreal tiltElevation = acos(cosAlpha); // in radians in [0, 0.5 * PI]
0660 
0661     // mapping to 0.0..1.0 if normalize is true
0662     return normalize ? (tiltElevation / (M_PI * qreal(0.5))) : tiltElevation;
0663 }