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