File indexing completed on 2024-05-12 15:58:14

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <kis_distance_information.h>
0009 #include <brushengine/kis_paint_information.h>
0010 #include "kis_spacing_information.h"
0011 #include "kis_timing_information.h"
0012 #include "kis_debug.h"
0013 #include <QtCore/qmath.h>
0014 #include <QVector2D>
0015 #include <QTransform>
0016 #include "kis_algebra_2d.h"
0017 #include "kis_dom_utils.h"
0018 
0019 #include "kis_lod_transform.h"
0020 
0021 const qreal MIN_DISTANCE_SPACING = 0.5;
0022 
0023 // Smallest allowed interval when timed spacing is enabled, in milliseconds.
0024 const qreal MIN_TIMED_INTERVAL = 0.5;
0025 
0026 // Largest allowed interval when timed spacing is enabled, in milliseconds.
0027 const qreal MAX_TIMED_INTERVAL = LONG_TIME;
0028 
0029 struct Q_DECL_HIDDEN KisDistanceInformation::Private {
0030     Private() :
0031         accumDistance(),
0032         accumTime(0.0),
0033         spacingUpdateInterval(LONG_TIME),
0034         timeSinceSpacingUpdate(0.0),
0035         timingUpdateInterval(LONG_TIME),
0036         timeSinceTimingUpdate(0.0),
0037         lastAngle(0.0),
0038         lastDabInfoValid(false),
0039         lastPaintInfoValid(false),
0040         totalDistance(0.0),
0041         currentDabSeqNo(0),
0042         levelOfDetail(0)
0043     {
0044     }
0045 
0046     // Accumulators of time/distance passed since the last painted dab
0047     QPointF accumDistance;
0048     qreal accumTime;
0049 
0050     KisSpacingInformation spacing;
0051     qreal spacingUpdateInterval;
0052     // Accumulator of time passed since the last spacing update
0053     qreal timeSinceSpacingUpdate;
0054 
0055     KisTimingInformation timing;
0056     qreal timingUpdateInterval;
0057     // Accumulator of time passed since the last timing update
0058     qreal timeSinceTimingUpdate;
0059 
0060     // Information about the last position considered (not necessarily a painted dab)
0061     QPointF lastPosition;
0062     qreal lastAngle;
0063     bool lastDabInfoValid;
0064 
0065     // Information about the last painted dab
0066     KisPaintInformation lastPaintInformation;
0067     bool lastPaintInfoValid;
0068 
0069     qreal totalDistance;
0070     boost::optional<qreal> lockedDrawingAngleOptional;
0071 
0072     int currentDabSeqNo;
0073     int levelOfDetail;
0074 
0075     qreal lastMaxPressure = 0.0;
0076 };
0077 
0078 struct Q_DECL_HIDDEN KisDistanceInitInfo::Private {
0079     Private() :
0080         hasLastInfo(false),
0081         lastPosition(),
0082         lastAngle(0.0),
0083         spacingUpdateInterval(LONG_TIME),
0084         timingUpdateInterval(LONG_TIME),
0085         currentDabSeqNo(0)
0086     {
0087     }
0088 
0089 
0090     // Indicates whether lastPosition,  and lastAngle are valid or not.
0091     bool hasLastInfo;
0092 
0093     QPointF lastPosition;
0094     qreal lastAngle;
0095 
0096     qreal spacingUpdateInterval;
0097     qreal timingUpdateInterval;
0098 
0099     int currentDabSeqNo;
0100 };
0101 
0102 KisDistanceInitInfo::KisDistanceInitInfo()
0103     : m_d(new Private)
0104 {
0105 }
0106 
0107 KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval, qreal timingUpdateInterval, int currentDabSeqNo)
0108     : m_d(new Private)
0109 {
0110     m_d->spacingUpdateInterval = spacingUpdateInterval;
0111     m_d->timingUpdateInterval = timingUpdateInterval;
0112     m_d->currentDabSeqNo = currentDabSeqNo;
0113 }
0114 
0115 KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition,
0116                                          qreal lastAngle, int currentDabSeqNo)
0117     : m_d(new Private)
0118 {
0119     m_d->hasLastInfo = true;
0120     m_d->lastPosition = lastPosition;
0121     m_d->lastAngle = lastAngle;
0122     m_d->currentDabSeqNo = currentDabSeqNo;
0123 }
0124 
0125 KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition,
0126                                          qreal lastAngle, qreal spacingUpdateInterval,
0127                                          qreal timingUpdateInterval,
0128                                          int currentDabSeqNo)
0129     : m_d(new Private)
0130 {
0131     m_d->hasLastInfo = true;
0132     m_d->lastPosition = lastPosition;
0133     m_d->lastAngle = lastAngle;
0134     m_d->spacingUpdateInterval = spacingUpdateInterval;
0135     m_d->timingUpdateInterval = timingUpdateInterval;
0136     m_d->currentDabSeqNo = currentDabSeqNo;
0137 }
0138 
0139 KisDistanceInitInfo::KisDistanceInitInfo(const KisDistanceInitInfo &rhs)
0140     : m_d(new Private(*rhs.m_d))
0141 {
0142 }
0143 
0144 KisDistanceInitInfo::~KisDistanceInitInfo()
0145 {
0146     delete m_d;
0147 }
0148 
0149 bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const
0150 {
0151     if (m_d->spacingUpdateInterval != other.m_d->spacingUpdateInterval
0152         || m_d->timingUpdateInterval != other.m_d->timingUpdateInterval
0153         || m_d->hasLastInfo != other.m_d->hasLastInfo)
0154     {
0155         return false;
0156     }
0157     if (m_d->hasLastInfo) {
0158         if (m_d->lastPosition != other.m_d->lastPosition
0159             || m_d->lastAngle != other.m_d->lastAngle)
0160         {
0161             return false;
0162         }
0163     }
0164 
0165     if (m_d->currentDabSeqNo != other.m_d->currentDabSeqNo) {
0166         return false;
0167     }
0168 
0169     return true;
0170 }
0171 
0172 bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const
0173 {
0174     return !(*this == other);
0175 }
0176 
0177 KisDistanceInitInfo &KisDistanceInitInfo::operator=(const KisDistanceInitInfo &rhs)
0178 {
0179     *m_d = *rhs.m_d;
0180     return *this;
0181 }
0182 
0183 KisDistanceInformation KisDistanceInitInfo::makeDistInfo()
0184 {
0185     if (m_d->hasLastInfo) {
0186         return KisDistanceInformation(m_d->lastPosition, m_d->lastAngle,
0187                                       m_d->spacingUpdateInterval, m_d->timingUpdateInterval,
0188                                       m_d->currentDabSeqNo);
0189     }
0190     else {
0191         return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval, m_d->currentDabSeqNo);
0192     }
0193 }
0194 
0195 void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const
0196 {
0197     elt.setAttribute("spacingUpdateInterval", QString::number(m_d->spacingUpdateInterval, 'g', 15));
0198     elt.setAttribute("timingUpdateInterval", QString::number(m_d->timingUpdateInterval, 'g', 15));
0199     elt.setAttribute("currentDabSeqNo", QString::number(m_d->currentDabSeqNo));
0200     if (m_d->hasLastInfo) {
0201         QDomElement lastInfoElt = doc.createElement("LastInfo");
0202         lastInfoElt.setAttribute("lastPosX", QString::number(m_d->lastPosition.x(), 'g', 15));
0203         lastInfoElt.setAttribute("lastPosY", QString::number(m_d->lastPosition.y(), 'g', 15));
0204         lastInfoElt.setAttribute("lastAngle", QString::number(m_d->lastAngle, 'g', 15));
0205         elt.appendChild(lastInfoElt);
0206     }
0207 }
0208 
0209 KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt)
0210 {
0211     const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval",
0212                                                                                   QString::number(LONG_TIME, 'g', 15))));
0213     const qreal timingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("timingUpdateInterval",
0214                                                                                   QString::number(LONG_TIME, 'g', 15))));
0215     const qreal currentDabSeqNo = KisDomUtils::toInt(elt.attribute("currentDabSeqNo", "0"));
0216 
0217     const QDomElement lastInfoElt = elt.firstChildElement("LastInfo");
0218     const bool hasLastInfo = !lastInfoElt.isNull();
0219 
0220     if (hasLastInfo) {
0221         const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX",
0222                                                                                  "0.0")));
0223         const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY",
0224                                                                                  "0.0")));
0225         const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle",
0226                                                                                   "0.0")));
0227 
0228         return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastAngle,
0229                                    spacingUpdateInterval, timingUpdateInterval,
0230                                    currentDabSeqNo);
0231     }
0232     else {
0233         return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval,
0234                                    currentDabSeqNo);
0235     }
0236 }
0237 
0238 KisDistanceInformation::KisDistanceInformation()
0239     : m_d(new Private)
0240 {
0241 }
0242 
0243 KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval,
0244                                                qreal timingUpdateInterval,
0245                                                int currentDabSeqNo)
0246     : m_d(new Private)
0247 {
0248     m_d->spacingUpdateInterval = spacingUpdateInterval;
0249     m_d->timingUpdateInterval = timingUpdateInterval;
0250     m_d->currentDabSeqNo = currentDabSeqNo;
0251 }
0252 
0253 KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
0254                                                qreal lastAngle)
0255     : m_d(new Private)
0256 {
0257     m_d->lastPosition = lastPosition;
0258     m_d->lastAngle = lastAngle;
0259 
0260     m_d->lastDabInfoValid = true;
0261 }
0262 
0263 KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
0264                                                qreal lastAngle,
0265                                                qreal spacingUpdateInterval,
0266                                                qreal timingUpdateInterval,
0267                                                int currentDabSeqNo)
0268     : KisDistanceInformation(lastPosition, lastAngle)
0269 {
0270     m_d->spacingUpdateInterval = spacingUpdateInterval;
0271     m_d->timingUpdateInterval = timingUpdateInterval;
0272     m_d->currentDabSeqNo = currentDabSeqNo;
0273 }
0274 
0275 KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs)
0276     : m_d(new Private(*rhs.m_d))
0277 {
0278 
0279 }
0280 
0281 KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail)
0282     : m_d(new Private(*rhs.m_d))
0283 {
0284     KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid &&
0285                             "The distance information "
0286                             "should be cloned before the "
0287                             "actual painting is started");
0288 
0289     m_d->levelOfDetail = levelOfDetail;
0290 
0291     KisLodTransform t(levelOfDetail);
0292     m_d->lastPosition = t.map(m_d->lastPosition);
0293 }
0294 
0295 KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs)
0296 {
0297     *m_d = *rhs.m_d;
0298     return *this;
0299 }
0300 
0301 void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition,
0302                                                 qreal lastAngle)
0303 {
0304     m_d->lastPosition = lastPosition;
0305     m_d->lastAngle = lastAngle;
0306 
0307     m_d->lastDabInfoValid = true;
0308 }
0309 
0310 KisDistanceInformation::~KisDistanceInformation()
0311 {
0312     delete m_d;
0313 }
0314 
0315 const KisSpacingInformation& KisDistanceInformation::currentSpacing() const
0316 {
0317     return m_d->spacing;
0318 }
0319 
0320 void KisDistanceInformation::updateSpacing(const KisSpacingInformation &spacing)
0321 {
0322     m_d->spacing = spacing;
0323     m_d->timeSinceSpacingUpdate = 0.0;
0324 }
0325 
0326 bool KisDistanceInformation::needsSpacingUpdate() const
0327 {
0328     return m_d->timeSinceSpacingUpdate >= m_d->spacingUpdateInterval;
0329 }
0330 
0331 const KisTimingInformation &KisDistanceInformation::currentTiming() const
0332 {
0333     return m_d->timing;
0334 }
0335 
0336 void KisDistanceInformation::updateTiming(const KisTimingInformation &timing)
0337 {
0338     m_d->timing = timing;
0339     m_d->timeSinceTimingUpdate = 0.0;
0340 }
0341 
0342 bool KisDistanceInformation::needsTimingUpdate() const
0343 {
0344     return m_d->timeSinceTimingUpdate >= m_d->timingUpdateInterval;
0345 }
0346 
0347 bool KisDistanceInformation::hasLastDabInformation() const
0348 {
0349     return m_d->lastDabInfoValid;
0350 }
0351 
0352 QPointF KisDistanceInformation::lastPosition() const
0353 {
0354     return m_d->lastPosition;
0355 }
0356 
0357 qreal KisDistanceInformation::lastDrawingAngle() const
0358 {
0359     return m_d->lastAngle;
0360 }
0361 
0362 bool KisDistanceInformation::hasLastPaintInformation() const
0363 {
0364     return m_d->lastPaintInfoValid;
0365 }
0366 
0367 const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const
0368 {
0369     return m_d->lastPaintInformation;
0370 }
0371 
0372 int KisDistanceInformation::currentDabSeqNo() const
0373 {
0374     return m_d->currentDabSeqNo;
0375 }
0376 
0377 qreal KisDistanceInformation::maxPressure() const
0378 {
0379     return m_d->lastMaxPressure;
0380 }
0381 
0382 bool KisDistanceInformation::isStarted() const
0383 {
0384     return m_d->lastPaintInfoValid;
0385 }
0386 
0387 void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info,
0388                                                 const KisSpacingInformation &spacing,
0389                                                 const KisTimingInformation &timing)
0390 {
0391     m_d->totalDistance +=
0392         KisAlgebra2D::norm(info.pos() - m_d->lastPosition) *
0393         KisLodTransform::lodToInvScale(m_d->levelOfDetail);
0394 
0395     m_d->lastPaintInformation = info;
0396     m_d->lastPaintInfoValid = true;
0397 
0398     m_d->lastAngle = info.drawingAngle(false);
0399     m_d->lastPosition = info.pos();
0400     m_d->lastDabInfoValid = true;
0401 
0402     m_d->spacing = spacing;
0403     m_d->timing = timing;
0404 
0405     m_d->currentDabSeqNo++;
0406 
0407     m_d->lastMaxPressure = qMax(info.pressure(), m_d->lastMaxPressure);
0408 }
0409 
0410 qreal KisDistanceInformation::getNextPointPosition(const QPointF &start,
0411                                                    const QPointF &end,
0412                                                    qreal startTime,
0413                                                    qreal endTime)
0414 {
0415     // Compute interpolation factor based on distance.
0416     qreal distanceFactor = -1.0;
0417     if (m_d->spacing.isDistanceSpacingEnabled()) {
0418         distanceFactor = m_d->spacing.isIsotropic() ?
0419             getNextPointPositionIsotropic(start, end) :
0420             getNextPointPositionAnisotropic(start, end);
0421     }
0422 
0423     // Compute interpolation factor based on time.
0424     qreal timeFactor = -1.0;
0425     if (m_d->timing.isTimedSpacingEnabled()) {
0426         timeFactor = getNextPointPositionTimed(startTime, endTime);
0427     }
0428 
0429     // Return the distance-based or time-based factor, whichever is smallest.
0430     qreal t = -1.0;
0431     if (distanceFactor < 0.0) {
0432         t = timeFactor;
0433     } else if (timeFactor < 0.0) {
0434         t = distanceFactor;
0435     } else {
0436         t = qMin(distanceFactor, timeFactor);
0437     }
0438 
0439     // If we aren't ready to paint a dab, accumulate time for the spacing/timing updates that might
0440     // be needed between dabs.
0441     if (t < 0.0) {
0442         m_d->timeSinceSpacingUpdate += endTime - startTime;
0443         m_d->timeSinceTimingUpdate += endTime - startTime;
0444     }
0445 
0446     // If we are ready to paint a dab, reset the accumulated time for spacing/timing updates.
0447     else {
0448         m_d->timeSinceSpacingUpdate = 0.0;
0449         m_d->timeSinceTimingUpdate = 0.0;
0450     }
0451 
0452     return t;
0453 }
0454 
0455 qreal KisDistanceInformation::getSpacingInterval() const
0456 {
0457     return m_d->spacingUpdateInterval;
0458 }
0459 
0460 qreal KisDistanceInformation::getTimingUpdateInterval() const
0461 {
0462     return m_d->timingUpdateInterval;
0463 }
0464 
0465 qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start,
0466                                                             const QPointF &end)
0467 {
0468     qreal distance = m_d->accumDistance.x();
0469     qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
0470 
0471     if (start == end) {
0472         return -1;
0473     }
0474 
0475     qreal dragVecLength = QVector2D(end - start).length();
0476     qreal nextPointDistance = spacing - distance;
0477 
0478     qreal t;
0479 
0480     // nextPointDistance can sometimes be negative if the spacing info has been modified since the
0481     // last interpolation attempt. In that case, have a point painted immediately.
0482     if (nextPointDistance <= 0.0) {
0483         resetAccumulators();
0484         t = 0.0;
0485     }
0486     else if (nextPointDistance <= dragVecLength) {
0487         t = nextPointDistance / dragVecLength;
0488         resetAccumulators();
0489     } else {
0490         t = -1;
0491         m_d->accumDistance.rx() += dragVecLength;
0492     }
0493 
0494     return t;
0495 }
0496 
0497 qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start,
0498                                                               const QPointF &end)
0499 {
0500     if (start == end) {
0501         return -1;
0502     }
0503 
0504     qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
0505     qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y());
0506 
0507     qreal x = m_d->accumDistance.x();
0508     qreal y = m_d->accumDistance.y();
0509 
0510     qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1;
0511 
0512     // If the distance accumulator is already past the spacing ellipse, have a point painted
0513     // immediately. This can happen if the spacing info has been modified since the last
0514     // interpolation attempt.
0515     if (gamma >= 0.0) {
0516         resetAccumulators();
0517         return 0.0;
0518     }
0519 
0520     static const qreal eps = 2e-3; // < 0.2 deg
0521 
0522     qreal currentRotation = m_d->spacing.rotation();
0523     if (m_d->spacing.coordinateSystemFlipped()) {
0524         currentRotation = 2 * M_PI - currentRotation;
0525     }
0526 
0527     QPointF diff = end - start;
0528 
0529     if (currentRotation > eps) {
0530         QTransform rot;
0531         // since the ellipse is symmetrical, the sign
0532         // of rotation doesn't matter
0533         rot.rotateRadians(currentRotation);
0534         diff = rot.map(diff);
0535     }
0536 
0537     qreal dx = qAbs(diff.x());
0538     qreal dy = qAbs(diff.y());
0539 
0540     qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev);
0541     qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev;
0542     qreal D_4 = pow2(beta) - alpha * gamma;
0543 
0544     qreal t = -1.0;
0545 
0546     if (D_4 >= 0) {
0547         qreal k = (-beta + qSqrt(D_4)) / alpha;
0548 
0549         if (k >= 0.0 && k <= 1.0) {
0550             t = k;
0551             resetAccumulators();
0552         } else {
0553             m_d->accumDistance += KisAlgebra2D::abs(diff);
0554         }
0555     } else {
0556         warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened.";
0557     }
0558 
0559     return t;
0560 }
0561 
0562 qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime,
0563                                                         qreal endTime)
0564 {
0565     // If start time is not before end time, do not interpolate.
0566     if (!(startTime < endTime)) {
0567         return -1.0;
0568     }
0569 
0570     qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->timing.timedSpacingInterval(),
0571                                         MAX_TIMED_INTERVAL);
0572     qreal nextPointInterval = timedSpacingInterval - m_d->accumTime;
0573     
0574     qreal t = -1.0;
0575 
0576     // nextPointInterval can sometimes be negative if the spacing info has been modified since the
0577     // last interpolation attempt. In that case, have a point painted immediately.
0578     if (nextPointInterval <= 0.0) {
0579         resetAccumulators();
0580         t = 0.0;
0581     }
0582     else if (nextPointInterval <= endTime - startTime) {
0583         resetAccumulators();
0584         t = nextPointInterval / (endTime - startTime);
0585     }
0586     else {
0587         m_d->accumTime += endTime - startTime;
0588         t = -1.0;
0589     }
0590     
0591     return t;
0592 }
0593 
0594 void KisDistanceInformation::resetAccumulators()
0595 {
0596     m_d->accumDistance = QPointF();
0597     m_d->accumTime = 0.0;
0598 }
0599 
0600 boost::optional<qreal> KisDistanceInformation::lockedDrawingAngleOptional() const
0601 {
0602     return m_d->lockedDrawingAngleOptional;
0603 }
0604 
0605 void KisDistanceInformation::lockCurrentDrawingAngle(const KisPaintInformation &info) const
0606 {
0607     const qreal angle = info.drawingAngle(false);
0608 
0609     qreal newAngle = angle;
0610 
0611     if (m_d->lockedDrawingAngleOptional) {
0612         const qreal stabilizingCoeff = 20.0;
0613         const qreal dist = stabilizingCoeff * m_d->spacing.scalarApprox();
0614         const qreal alpha = qMax(0.0, dist - scalarDistanceApprox()) / dist;
0615 
0616         const qreal oldAngle = *m_d->lockedDrawingAngleOptional;
0617 
0618         if (shortestAngularDistance(oldAngle, newAngle) < M_PI / 6) {
0619             newAngle = (1.0 - alpha) * oldAngle + alpha * newAngle;
0620         } else {
0621             newAngle = oldAngle;
0622         }
0623     }
0624 
0625     m_d->lockedDrawingAngleOptional = newAngle;
0626 }
0627 
0628 
0629 qreal KisDistanceInformation::scalarDistanceApprox() const
0630 {
0631     return m_d->totalDistance;
0632 }
0633