File indexing completed on 2024-05-19 04:26:11

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 KisDistanceInitInfo &KisDistanceInitInfo::operator=(const KisDistanceInitInfo &rhs)
0173 {
0174     *m_d = *rhs.m_d;
0175     return *this;
0176 }
0177 
0178 KisDistanceInformation KisDistanceInitInfo::makeDistInfo()
0179 {
0180     if (m_d->hasLastInfo) {
0181         return KisDistanceInformation(m_d->lastPosition, m_d->lastAngle,
0182                                       m_d->spacingUpdateInterval, m_d->timingUpdateInterval,
0183                                       m_d->currentDabSeqNo);
0184     }
0185     else {
0186         return KisDistanceInformation(m_d->spacingUpdateInterval, m_d->timingUpdateInterval, m_d->currentDabSeqNo);
0187     }
0188 }
0189 
0190 void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const
0191 {
0192     elt.setAttribute("spacingUpdateInterval", QString::number(m_d->spacingUpdateInterval, 'g', 15));
0193     elt.setAttribute("timingUpdateInterval", QString::number(m_d->timingUpdateInterval, 'g', 15));
0194     elt.setAttribute("currentDabSeqNo", QString::number(m_d->currentDabSeqNo));
0195     if (m_d->hasLastInfo) {
0196         QDomElement lastInfoElt = doc.createElement("LastInfo");
0197         lastInfoElt.setAttribute("lastPosX", QString::number(m_d->lastPosition.x(), 'g', 15));
0198         lastInfoElt.setAttribute("lastPosY", QString::number(m_d->lastPosition.y(), 'g', 15));
0199         lastInfoElt.setAttribute("lastAngle", QString::number(m_d->lastAngle, 'g', 15));
0200         elt.appendChild(lastInfoElt);
0201     }
0202 }
0203 
0204 KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt)
0205 {
0206     const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval",
0207                                                                                   QString::number(LONG_TIME, 'g', 15))));
0208     const qreal timingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("timingUpdateInterval",
0209                                                                                   QString::number(LONG_TIME, 'g', 15))));
0210     const qreal currentDabSeqNo = KisDomUtils::toInt(elt.attribute("currentDabSeqNo", "0"));
0211 
0212     const QDomElement lastInfoElt = elt.firstChildElement("LastInfo");
0213     const bool hasLastInfo = !lastInfoElt.isNull();
0214 
0215     if (hasLastInfo) {
0216         const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX",
0217                                                                                  "0.0")));
0218         const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY",
0219                                                                                  "0.0")));
0220         const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle",
0221                                                                                   "0.0")));
0222 
0223         return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastAngle,
0224                                    spacingUpdateInterval, timingUpdateInterval,
0225                                    currentDabSeqNo);
0226     }
0227     else {
0228         return KisDistanceInitInfo(spacingUpdateInterval, timingUpdateInterval,
0229                                    currentDabSeqNo);
0230     }
0231 }
0232 
0233 KisDistanceInformation::KisDistanceInformation()
0234     : m_d(new Private)
0235 {
0236 }
0237 
0238 KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval,
0239                                                qreal timingUpdateInterval,
0240                                                int currentDabSeqNo)
0241     : m_d(new Private)
0242 {
0243     m_d->spacingUpdateInterval = spacingUpdateInterval;
0244     m_d->timingUpdateInterval = timingUpdateInterval;
0245     m_d->currentDabSeqNo = currentDabSeqNo;
0246 }
0247 
0248 KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
0249                                                qreal lastAngle)
0250     : m_d(new Private)
0251 {
0252     m_d->lastPosition = lastPosition;
0253     m_d->lastAngle = lastAngle;
0254 
0255     m_d->lastDabInfoValid = true;
0256 }
0257 
0258 KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition,
0259                                                qreal lastAngle,
0260                                                qreal spacingUpdateInterval,
0261                                                qreal timingUpdateInterval,
0262                                                int currentDabSeqNo)
0263     : KisDistanceInformation(lastPosition, lastAngle)
0264 {
0265     m_d->spacingUpdateInterval = spacingUpdateInterval;
0266     m_d->timingUpdateInterval = timingUpdateInterval;
0267     m_d->currentDabSeqNo = currentDabSeqNo;
0268 }
0269 
0270 KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs)
0271     : m_d(new Private(*rhs.m_d))
0272 {
0273 
0274 }
0275 
0276 KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail)
0277     : m_d(new Private(*rhs.m_d))
0278 {
0279     KIS_ASSERT_RECOVER_NOOP(!m_d->lastPaintInfoValid &&
0280                             "The distance information "
0281                             "should be cloned before the "
0282                             "actual painting is started");
0283 
0284     m_d->levelOfDetail = levelOfDetail;
0285 
0286     KisLodTransform t(levelOfDetail);
0287     m_d->lastPosition = t.map(m_d->lastPosition);
0288 }
0289 
0290 KisDistanceInformation& KisDistanceInformation::operator=(const KisDistanceInformation &rhs)
0291 {
0292     *m_d = *rhs.m_d;
0293     return *this;
0294 }
0295 
0296 void KisDistanceInformation::overrideLastValues(const QPointF &lastPosition,
0297                                                 qreal lastAngle)
0298 {
0299     m_d->lastPosition = lastPosition;
0300     m_d->lastAngle = lastAngle;
0301 
0302     m_d->lastDabInfoValid = true;
0303 }
0304 
0305 KisDistanceInformation::~KisDistanceInformation()
0306 {
0307     delete m_d;
0308 }
0309 
0310 const KisSpacingInformation& KisDistanceInformation::currentSpacing() const
0311 {
0312     return m_d->spacing;
0313 }
0314 
0315 void KisDistanceInformation::updateSpacing(const KisSpacingInformation &spacing)
0316 {
0317     m_d->spacing = spacing;
0318     m_d->timeSinceSpacingUpdate = 0.0;
0319 }
0320 
0321 bool KisDistanceInformation::needsSpacingUpdate() const
0322 {
0323     return m_d->timeSinceSpacingUpdate >= m_d->spacingUpdateInterval;
0324 }
0325 
0326 const KisTimingInformation &KisDistanceInformation::currentTiming() const
0327 {
0328     return m_d->timing;
0329 }
0330 
0331 void KisDistanceInformation::updateTiming(const KisTimingInformation &timing)
0332 {
0333     m_d->timing = timing;
0334     m_d->timeSinceTimingUpdate = 0.0;
0335 }
0336 
0337 bool KisDistanceInformation::needsTimingUpdate() const
0338 {
0339     return m_d->timeSinceTimingUpdate >= m_d->timingUpdateInterval;
0340 }
0341 
0342 bool KisDistanceInformation::hasLastDabInformation() const
0343 {
0344     return m_d->lastDabInfoValid;
0345 }
0346 
0347 QPointF KisDistanceInformation::lastPosition() const
0348 {
0349     return m_d->lastPosition;
0350 }
0351 
0352 qreal KisDistanceInformation::lastDrawingAngle() const
0353 {
0354     return m_d->lastAngle;
0355 }
0356 
0357 bool KisDistanceInformation::hasLastPaintInformation() const
0358 {
0359     return m_d->lastPaintInfoValid;
0360 }
0361 
0362 const KisPaintInformation& KisDistanceInformation::lastPaintInformation() const
0363 {
0364     return m_d->lastPaintInformation;
0365 }
0366 
0367 int KisDistanceInformation::currentDabSeqNo() const
0368 {
0369     return m_d->currentDabSeqNo;
0370 }
0371 
0372 qreal KisDistanceInformation::maxPressure() const
0373 {
0374     return m_d->lastMaxPressure;
0375 }
0376 
0377 bool KisDistanceInformation::isStarted() const
0378 {
0379     return m_d->lastPaintInfoValid;
0380 }
0381 
0382 void KisDistanceInformation::registerPaintedDab(const KisPaintInformation &info,
0383                                                 const KisSpacingInformation &spacing,
0384                                                 const KisTimingInformation &timing)
0385 {
0386     m_d->totalDistance +=
0387         KisAlgebra2D::norm(info.pos() - m_d->lastPosition) *
0388         KisLodTransform::lodToInvScale(m_d->levelOfDetail);
0389 
0390     m_d->lastPaintInformation = info;
0391     m_d->lastPaintInfoValid = true;
0392 
0393     m_d->lastAngle = info.drawingAngle(false);
0394     m_d->lastPosition = info.pos();
0395     m_d->lastDabInfoValid = true;
0396 
0397     m_d->spacing = spacing;
0398     m_d->timing = timing;
0399 
0400     m_d->currentDabSeqNo++;
0401 
0402     m_d->lastMaxPressure = qMax(info.pressure(), m_d->lastMaxPressure);
0403 }
0404 
0405 qreal KisDistanceInformation::getNextPointPosition(const QPointF &start,
0406                                                    const QPointF &end,
0407                                                    qreal startTime,
0408                                                    qreal endTime)
0409 {
0410     // Compute interpolation factor based on distance.
0411     qreal distanceFactor = -1.0;
0412     if (m_d->spacing.isDistanceSpacingEnabled()) {
0413         distanceFactor = m_d->spacing.isIsotropic() ?
0414             getNextPointPositionIsotropic(start, end) :
0415             getNextPointPositionAnisotropic(start, end);
0416     }
0417 
0418     // Compute interpolation factor based on time.
0419     qreal timeFactor = -1.0;
0420     if (m_d->timing.isTimedSpacingEnabled()) {
0421         timeFactor = getNextPointPositionTimed(startTime, endTime);
0422     }
0423 
0424     // Return the distance-based or time-based factor, whichever is smallest.
0425     qreal t = -1.0;
0426     if (distanceFactor < 0.0) {
0427         t = timeFactor;
0428     } else if (timeFactor < 0.0) {
0429         t = distanceFactor;
0430     } else {
0431         t = qMin(distanceFactor, timeFactor);
0432     }
0433 
0434     // If we aren't ready to paint a dab, accumulate time for the spacing/timing updates that might
0435     // be needed between dabs.
0436     if (t < 0.0) {
0437         m_d->timeSinceSpacingUpdate += endTime - startTime;
0438         m_d->timeSinceTimingUpdate += endTime - startTime;
0439     }
0440 
0441     // If we are ready to paint a dab, reset the accumulated time for spacing/timing updates.
0442     else {
0443         m_d->timeSinceSpacingUpdate = 0.0;
0444         m_d->timeSinceTimingUpdate = 0.0;
0445     }
0446 
0447     return t;
0448 }
0449 
0450 qreal KisDistanceInformation::getSpacingInterval() const
0451 {
0452     return m_d->spacingUpdateInterval;
0453 }
0454 
0455 qreal KisDistanceInformation::getTimingUpdateInterval() const
0456 {
0457     return m_d->timingUpdateInterval;
0458 }
0459 
0460 qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start,
0461                                                             const QPointF &end)
0462 {
0463     qreal distance = m_d->accumDistance.x();
0464     qreal spacing = qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
0465 
0466     if (start == end) {
0467         return -1;
0468     }
0469 
0470     qreal dragVecLength = QVector2D(end - start).length();
0471     qreal nextPointDistance = spacing - distance;
0472 
0473     qreal t;
0474 
0475     // nextPointDistance can sometimes be negative if the spacing info has been modified since the
0476     // last interpolation attempt. In that case, have a point painted immediately.
0477     if (nextPointDistance <= 0.0) {
0478         resetAccumulators();
0479         t = 0.0;
0480     }
0481     else if (nextPointDistance <= dragVecLength) {
0482         t = nextPointDistance / dragVecLength;
0483         resetAccumulators();
0484     } else {
0485         t = -1;
0486         m_d->accumDistance.rx() += dragVecLength;
0487     }
0488 
0489     return t;
0490 }
0491 
0492 qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start,
0493                                                               const QPointF &end)
0494 {
0495     if (start == end) {
0496         return -1;
0497     }
0498 
0499     qreal a_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().x());
0500     qreal b_rev = 1.0 / qMax(MIN_DISTANCE_SPACING, m_d->spacing.distanceSpacing().y());
0501 
0502     qreal x = m_d->accumDistance.x();
0503     qreal y = m_d->accumDistance.y();
0504 
0505     qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1;
0506 
0507     // If the distance accumulator is already past the spacing ellipse, have a point painted
0508     // immediately. This can happen if the spacing info has been modified since the last
0509     // interpolation attempt.
0510     if (gamma >= 0.0) {
0511         resetAccumulators();
0512         return 0.0;
0513     }
0514 
0515     static const qreal eps = 2e-3; // < 0.2 deg
0516 
0517     qreal currentRotation = m_d->spacing.rotation();
0518     if (m_d->spacing.coordinateSystemFlipped()) {
0519         currentRotation = 2 * M_PI - currentRotation;
0520     }
0521 
0522     QPointF diff = end - start;
0523 
0524     if (currentRotation > eps) {
0525         QTransform rot;
0526         // since the ellipse is symmetrical, the sign
0527         // of rotation doesn't matter
0528         rot.rotateRadians(currentRotation);
0529         diff = rot.map(diff);
0530     }
0531 
0532     qreal dx = qAbs(diff.x());
0533     qreal dy = qAbs(diff.y());
0534 
0535     qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev);
0536     qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev;
0537     qreal D_4 = pow2(beta) - alpha * gamma;
0538 
0539     qreal t = -1.0;
0540 
0541     if (D_4 >= 0) {
0542         qreal k = (-beta + qSqrt(D_4)) / alpha;
0543 
0544         if (k >= 0.0 && k <= 1.0) {
0545             t = k;
0546             resetAccumulators();
0547         } else {
0548             m_d->accumDistance += KisAlgebra2D::abs(diff);
0549         }
0550     } else {
0551         warnKrita << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened.";
0552     }
0553 
0554     return t;
0555 }
0556 
0557 qreal KisDistanceInformation::getNextPointPositionTimed(qreal startTime,
0558                                                         qreal endTime)
0559 {
0560     // If start time is not before end time, do not interpolate.
0561     if (!(startTime < endTime)) {
0562         return -1.0;
0563     }
0564 
0565     qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->timing.timedSpacingInterval(),
0566                                         MAX_TIMED_INTERVAL);
0567     qreal nextPointInterval = timedSpacingInterval - m_d->accumTime;
0568     
0569     qreal t = -1.0;
0570 
0571     // nextPointInterval can sometimes be negative if the spacing info has been modified since the
0572     // last interpolation attempt. In that case, have a point painted immediately.
0573     if (nextPointInterval <= 0.0) {
0574         resetAccumulators();
0575         t = 0.0;
0576     }
0577     else if (nextPointInterval <= endTime - startTime) {
0578         resetAccumulators();
0579         t = nextPointInterval / (endTime - startTime);
0580     }
0581     else {
0582         m_d->accumTime += endTime - startTime;
0583         t = -1.0;
0584     }
0585     
0586     return t;
0587 }
0588 
0589 void KisDistanceInformation::resetAccumulators()
0590 {
0591     m_d->accumDistance = QPointF();
0592     m_d->accumTime = 0.0;
0593 }
0594 
0595 boost::optional<qreal> KisDistanceInformation::lockedDrawingAngleOptional() const
0596 {
0597     return m_d->lockedDrawingAngleOptional;
0598 }
0599 
0600 void KisDistanceInformation::lockCurrentDrawingAngle(const KisPaintInformation &info) const
0601 {
0602     qreal newAngle = info.drawingAngle(false);
0603 
0604     if (m_d->lockedDrawingAngleOptional) {
0605         newAngle = *m_d->lockedDrawingAngleOptional;
0606     }
0607 
0608     m_d->lockedDrawingAngleOptional = newAngle;
0609 }
0610 
0611 
0612 qreal KisDistanceInformation::scalarDistanceApprox() const
0613 {
0614     return m_d->totalDistance;
0615 }
0616