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