diff --git a/libs/image/brushengine/kis_paintop.h b/libs/image/brushengine/kis_paintop.h --- a/libs/image/brushengine/kis_paintop.h +++ b/libs/image/brushengine/kis_paintop.h @@ -58,6 +58,14 @@ void paintAt(const KisPaintInformation& info, KisDistanceInformation *currentDistance); /** + * Updates the spacing in currentDistance based on the provided information. Note that the + * spacing is updated automatically in the paintAt method, so there is no need to call this + * method if paintAt has just been called. + */ + void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) + const; + + /** * Draw a line between pos1 and pos2 using the currently set brush and color. * If savedDist is less than zero, the brush is painted at pos1 before being * painted along the line using the spacing setting. @@ -100,10 +108,15 @@ protected: friend class KisPaintInformation; /** - * The implementation of painting of a dab + * The implementation of painting of a dab and updating spacing */ virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0; + /** + * Implementation of a spacing update + */ + virtual KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const = 0; + KisFixedPaintDeviceSP cachedDab(); KisFixedPaintDeviceSP cachedDab(const KoColorSpace *cs); diff --git a/libs/image/brushengine/kis_paintop.cc b/libs/image/brushengine/kis_paintop.cc --- a/libs/image/brushengine/kis_paintop.cc +++ b/libs/image/brushengine/kis_paintop.cc @@ -160,6 +160,20 @@ pi.paintAt(*this, currentDistance); } +void KisPaintOp::updateSpacing(const KisPaintInformation &info, + KisDistanceInformation ¤tDistance) const +{ + KisPaintInformation pi(info); + KisSpacingInformation spacingInfo; + { + KisPaintInformation::DistanceInformationRegistrar r + = pi.registerDistanceInformation(¤tDistance); + spacingInfo = updateSpacingImpl(pi); + } + + currentDistance.setSpacing(spacingInfo); +} + KisPainter* KisPaintOp::painter() const { return d->painter; diff --git a/libs/image/brushengine/kis_paintop_utils.h b/libs/image/brushengine/kis_paintop_utils.h --- a/libs/image/brushengine/kis_paintop_utils.h +++ b/libs/image/brushengine/kis_paintop_utils.h @@ -21,6 +21,7 @@ #include "kis_global.h" #include "kis_paint_information.h" +#include "kis_distance_information.h" namespace KisPaintOpUtils { @@ -88,6 +89,11 @@ */ pi.paintAt(op, currentDistance); } + + // Perform a spacing update between dabs if appropriate. + if (currentDistance->needsSpacingUpdate()) { + op.updateSpacing(pi2, *currentDistance); + } } /** @@ -189,8 +195,11 @@ spacing *= extraScale; + qreal scaledInterval = rateExtraScale <= 0.0 ? LONG_TIME : + timedSpacingInterval / rateExtraScale; + return KisSpacingInformation(distanceSpacingEnabled, spacing, rotation, axesFlipped, - timedSpacingEnabled, timedSpacingInterval / rateExtraScale); + timedSpacingEnabled, scaledInterval); } } diff --git a/libs/image/kis_distance_information.h b/libs/image/kis_distance_information.h --- a/libs/image/kis_distance_information.h +++ b/libs/image/kis_distance_information.h @@ -22,10 +22,19 @@ #include #include +#include +#include #include "kritaimage_export.h" class KisPaintInformation; +class KisDistanceInformation; +/** + * A time in milliseconds that is assumed to be longer than any stroke (or other paint operation) + * will ever last. This is used instead of infinity to avoid potential errors. The value is + * approximately ten years. + */ +const qreal LONG_TIME = 320000000000.0; /** * This structure contains information about the desired spacing @@ -134,13 +143,13 @@ } /** - * @return The desired maximum amount of time between dabs, in milliseconds. Returns a time of - * approximately 1 year if time-based spacing is disabled. + * @return The desired maximum amount of time between dabs, in milliseconds. Returns LONG_TIME + * if time-based spacing is disabled. */ inline qreal timedSpacingInterval() const { return isTimedSpacingEnabled() ? m_timedSpacingInterval : - 32000000000.0; + LONG_TIME; } inline bool isIsotropic() const { @@ -174,13 +183,78 @@ }; /** + * Represents some information that can be used to initialize a KisDistanceInformation object. The + * main purpose of this class is to allow serialization of KisDistanceInformation initial settings + * to XML. + */ +class KRITAIMAGE_EXPORT KisDistanceInitInfo { + +public: + + /** + * Creates a KisDistanceInitInfo with no initial last dab information, and spacing update + * interval set to LONG_TIME. + */ + explicit KisDistanceInitInfo(); + + /** + * Creates a KisDistanceInitInfo with no initial last dab information, and the specified spacing + * update interval. + */ + explicit KisDistanceInitInfo(qreal spacingUpdateInterval); + + /** + * Creates a KisDistanceInitInfo with the specified last dab information, and spacing update + * interval set to LONG_TIME. + */ + explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); + + /** + * Creates a KisDistanceInitInfo with the specified last dab information and spacing update + * interval. + */ + explicit KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, + qreal spacingUpdateInterval); + + bool operator==(const KisDistanceInitInfo &other) const; + + bool operator!=(const KisDistanceInitInfo &other) const; + + /** + * Constructs a KisDistanceInformation with initial settings based on this object. + */ + KisDistanceInformation makeDistInfo(); + + void toXML(QDomDocument &doc, QDomElement &elt) const; + + static KisDistanceInitInfo fromXML(const QDomElement &elt); + +private: + // Indicates whether lastPosition, lastTime, and lastAngle are valid or not. + bool m_hasLastInfo; + + QPointF m_lastPosition; + qreal m_lastTime; + qreal m_lastAngle; + + qreal m_spacingUpdateInterval; +}; + +/** * This structure is used as return value of paintLine to contain * information that is needed to be passed for the next call. */ class KRITAIMAGE_EXPORT KisDistanceInformation { public: KisDistanceInformation(); + KisDistanceInformation(qreal spacingUpdateInterval); KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle); + /** + * @param spacingUpdateInterval The amount of time allowed between spacing updates, in + * milliseconds. Only used when timed spacing is enabled. + */ + KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle, + qreal spacingUpdateInterval); KisDistanceInformation(const KisDistanceInformation &rhs); KisDistanceInformation(const KisDistanceInformation &rhs, int levelOfDetail); KisDistanceInformation& operator=(const KisDistanceInformation &rhs); @@ -188,6 +262,13 @@ ~KisDistanceInformation(); const KisSpacingInformation& currentSpacing() const; + void setSpacing(const KisSpacingInformation &spacing); + /** + * Returns true if this KisDistanceInformation should have its spacing information updated + * immediately (regardless of whether a dab is ready to be painted). + */ + bool needsSpacingUpdate() const; + bool hasLastDabInformation() const; QPointF lastPosition() const; qreal lastTime() const; diff --git a/libs/image/kis_distance_information.cpp b/libs/image/kis_distance_information.cpp --- a/libs/image/kis_distance_information.cpp +++ b/libs/image/kis_distance_information.cpp @@ -24,6 +24,7 @@ #include #include #include "kis_algebra_2d.h" +#include "kis_dom_utils.h" #include "kis_lod_transform.h" @@ -33,12 +34,13 @@ const qreal MIN_TIMED_INTERVAL = 0.5; // Largest allowed interval when timed spacing is enabled, in milliseconds. -const qreal MAX_TIMED_INTERVAL = 1000.0; +const qreal MAX_TIMED_INTERVAL = LONG_TIME; struct Q_DECL_HIDDEN KisDistanceInformation::Private { Private() : accumDistance(), accumTime(0.0), + spacingUpdateInterval(LONG_TIME), lastDabInfoValid(false), lastPaintInfoValid(false), lockedDrawingAngle(0.0), @@ -48,6 +50,7 @@ QPointF accumDistance; qreal accumTime; KisSpacingInformation spacing; + qreal spacingUpdateInterval; QPointF lastPosition; qreal lastTime; @@ -62,11 +65,127 @@ qreal totalDistance; }; +KisDistanceInitInfo::KisDistanceInitInfo() + : m_hasLastInfo(false) + , m_lastPosition() + , m_lastTime(0.0) + , m_lastAngle(0.0) + , m_spacingUpdateInterval(LONG_TIME) +{ +} + +KisDistanceInitInfo::KisDistanceInitInfo(qreal spacingUpdateInterval) + : m_hasLastInfo(false) + , m_lastPosition() + , m_lastTime(0.0) + , m_lastAngle(0.0) + , m_spacingUpdateInterval(spacingUpdateInterval) +{ +} + +KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, + qreal lastAngle) + : m_hasLastInfo(true) + , m_lastPosition(lastPosition) + , m_lastTime(lastTime) + , m_lastAngle(lastAngle) + , m_spacingUpdateInterval(LONG_TIME) +{ +} + +KisDistanceInitInfo::KisDistanceInitInfo(const QPointF &lastPosition, qreal lastTime, + qreal lastAngle, qreal spacingUpdateInterval) + : m_hasLastInfo(true) + , m_lastPosition(lastPosition) + , m_lastTime(lastTime) + , m_lastAngle(lastAngle) + , m_spacingUpdateInterval(spacingUpdateInterval) +{ +} + +bool KisDistanceInitInfo::operator==(const KisDistanceInitInfo &other) const +{ + if (m_spacingUpdateInterval != other.m_spacingUpdateInterval + || m_hasLastInfo != other.m_hasLastInfo) + { + return false; + } + if (m_hasLastInfo) { + if (m_lastPosition != other.m_lastPosition || m_lastTime != other.m_lastTime + || m_lastAngle != other.m_lastAngle) + { + return false; + } + } + + return true; +} + +bool KisDistanceInitInfo::operator!=(const KisDistanceInitInfo &other) const +{ + return !(*this == other); +} + +KisDistanceInformation KisDistanceInitInfo::makeDistInfo() +{ + if (m_hasLastInfo) { + return KisDistanceInformation(m_lastPosition, m_lastTime, m_lastAngle, + m_spacingUpdateInterval); + } + else { + return KisDistanceInformation(m_spacingUpdateInterval); + } +} + +void KisDistanceInitInfo::toXML(QDomDocument &doc, QDomElement &elt) const +{ + elt.setAttribute("spacingUpdateInterval", QString::number(m_spacingUpdateInterval, 'g', 15)); + if (m_hasLastInfo) { + QDomElement lastInfoElt = doc.createElement("LastInfo"); + lastInfoElt.setAttribute("lastPosX", QString::number(m_lastPosition.x(), 'g', 15)); + lastInfoElt.setAttribute("lastPosY", QString::number(m_lastPosition.y(), 'g', 15)); + lastInfoElt.setAttribute("lastTime", QString::number(m_lastTime, 'g', 15)); + lastInfoElt.setAttribute("lastAngle", QString::number(m_lastAngle, 'g', 15)); + elt.appendChild(lastInfoElt); + } +} + +KisDistanceInitInfo KisDistanceInitInfo::fromXML(const QDomElement &elt) +{ + const qreal spacingUpdateInterval = qreal(KisDomUtils::toDouble(elt.attribute("spacingUpdateInterval", + QString::number(LONG_TIME, 'g', 15)))); + const QDomElement lastInfoElt = elt.firstChildElement("LastInfo"); + const bool hasLastInfo = !lastInfoElt.isNull(); + + if (hasLastInfo) { + const qreal lastPosX = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosX", + "0.0"))); + const qreal lastPosY = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastPosY", + "0.0"))); + const qreal lastTime = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastTime", + "0.0"))); + const qreal lastAngle = qreal(KisDomUtils::toDouble(lastInfoElt.attribute("lastAngle", + "0.0"))); + + return KisDistanceInitInfo(QPointF(lastPosX, lastPosY), lastTime, lastAngle, + spacingUpdateInterval); + } + else { + return KisDistanceInitInfo(spacingUpdateInterval); + } +} + KisDistanceInformation::KisDistanceInformation() : m_d(new Private) { } +KisDistanceInformation::KisDistanceInformation(qreal spacingUpdateInterval) + : m_d(new Private) +{ + m_d->spacingUpdateInterval = spacingUpdateInterval; +} + KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition, qreal lastTime, qreal lastAngle) @@ -79,6 +198,15 @@ m_d->lastDabInfoValid = true; } +KisDistanceInformation::KisDistanceInformation(const QPointF &lastPosition, + qreal lastTime, + qreal lastAngle, + qreal spacingUpdateInterval) + : KisDistanceInformation(lastPosition, lastTime, lastAngle) +{ + m_d->spacingUpdateInterval = spacingUpdateInterval; +} + KisDistanceInformation::KisDistanceInformation(const KisDistanceInformation &rhs) : m_d(new Private(*rhs.m_d)) { @@ -123,6 +251,17 @@ return m_d->spacing; } +void KisDistanceInformation::setSpacing(const KisSpacingInformation &spacing) +{ + m_d->spacing = spacing; +} + +bool KisDistanceInformation::needsSpacingUpdate() const +{ + // Only require spacing updates between dabs if timed spacing is enabled. + return m_d->spacing.isTimedSpacingEnabled() && m_d->accumTime >= m_d->spacingUpdateInterval; +} + bool KisDistanceInformation::hasLastDabInformation() const { return m_d->lastDabInfoValid; @@ -218,7 +357,13 @@ qreal t; - if (nextPointDistance <= dragVecLength) { + // nextPointDistance can sometimes be negative if the spacing info has been modified since the + // last interpolation attempt. In that case, have a point painted immediately. + if (nextPointDistance <= 0.0) { + resetAccumulators(); + return 0.0; + } + else if (nextPointDistance <= dragVecLength) { t = nextPointDistance / dragVecLength; resetAccumulators(); } else { @@ -242,6 +387,16 @@ qreal x = m_d->accumDistance.x(); qreal y = m_d->accumDistance.y(); + qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1; + + // If the distance accumulator is already past the spacing ellipse, have a point painted + // immediately. This can happen if the spacing info has been modified since the last + // interpolation attempt. + if (gamma >= 0.0) { + resetAccumulators(); + return 0.0; + } + static const qreal eps = 2e-3; // < 0.2 deg qreal currentRotation = m_d->spacing.rotation(); @@ -264,8 +419,6 @@ qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev); qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev; - qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1; - qreal D_4 = pow2(beta) - alpha * gamma; qreal t = -1.0; @@ -297,9 +450,9 @@ qreal timedSpacingInterval = qBound(MIN_TIMED_INTERVAL, m_d->spacing.timedSpacingInterval(), MAX_TIMED_INTERVAL); qreal nextPointInterval = timedSpacingInterval - m_d->accumTime; - - // Note: nextPointInterval SHOULD always be positive, but I wasn't sure if floating point - // roundoff error might make it nonpositive in some cases, so I included this check. + + // nextPointInterval can sometimes be negative if the spacing info has been modified since the + // last interpolation attempt. In that case, have a point painted immediately. if (nextPointInterval <= 0.0) { resetAccumulators(); return 0.0; diff --git a/libs/image/recorder/kis_recorded_path_paint_action.h b/libs/image/recorder/kis_recorded_path_paint_action.h --- a/libs/image/recorder/kis_recorded_path_paint_action.h +++ b/libs/image/recorder/kis_recorded_path_paint_action.h @@ -25,6 +25,7 @@ class KisPaintInformation; class KisPainter; +class KisDistanceInitInfo; #include @@ -36,13 +37,22 @@ public: + /** + * @param startDist - Provides initial information related to distance and spacing, which can + * have an effect on how the path is painted. + */ KisRecordedPathPaintAction(const KisNodeQueryPath& path, - const KisPaintOpPresetSP paintOpPreset); + const KisPaintOpPresetSP paintOpPreset, + const KisDistanceInitInfo& startDistInfo); KisRecordedPathPaintAction(const KisRecordedPathPaintAction&); ~KisRecordedPathPaintAction() override; + KisDistanceInitInfo getInitDistInfo() const; + + void setInitDistInfo(const KisDistanceInitInfo &startDistInfo); + void addPoint(const KisPaintInformation& info); void addLine(const KisPaintInformation& point1, const KisPaintInformation& point2); void addPolyLine(const QList& points); diff --git a/libs/image/recorder/kis_recorded_path_paint_action.cpp b/libs/image/recorder/kis_recorded_path_paint_action.cpp --- a/libs/image/recorder/kis_recorded_path_paint_action.cpp +++ b/libs/image/recorder/kis_recorded_path_paint_action.cpp @@ -53,14 +53,22 @@ QPointF control2; KisPaintInformation point2; }; + + Private(const KisDistanceInitInfo &startDistInfo) : + startDistInfo(startDistInfo) {} + QList curveSlices; + + // Information about distance and spacing at the start of the action. + KisDistanceInitInfo startDistInfo; }; KisRecordedPathPaintAction::KisRecordedPathPaintAction( const KisNodeQueryPath& path, - const KisPaintOpPresetSP preset) + const KisPaintOpPresetSP preset, + const KisDistanceInitInfo& startDistInfo) : KisRecordedPaintAction("PathPaintAction", i18n("Path"), path, preset) - , d(new Private) + , d(new Private(startDistInfo)) { } @@ -74,6 +82,16 @@ delete d; } +KisDistanceInitInfo KisRecordedPathPaintAction::getInitDistInfo() const +{ + return d->startDistInfo; +} + +void KisRecordedPathPaintAction::setInitDistInfo(const KisDistanceInitInfo &startDistInfo) +{ + d->startDistInfo = startDistInfo; +} + void KisRecordedPathPaintAction::addPoint(const KisPaintInformation& info) { Private::BezierCurveSlice slice; @@ -120,7 +138,8 @@ { dbgImage << "play path paint action with " << d->curveSlices.size() << " slices"; if (d->curveSlices.size() <= 0) return; - KisDistanceInformation savedDist; + + KisDistanceInformation savedDist = d->startDistInfo.makeDistInfo(); Q_FOREACH (const Private::BezierCurveSlice &slice, d->curveSlices) { @@ -196,6 +215,10 @@ } } elt.appendChild(waypointsElt); + + QDomElement initDistElt = doc.createElement("StartDistInfo"); + d->startDistInfo.toXML(doc, initDistElt); + elt.appendChild(initDistElt); } KisRecordedAction* KisRecordedPathPaintAction::clone() const @@ -221,7 +244,8 @@ // Decode pressets KisPaintOpPresetSP paintOpPreset = paintOpPresetFromXML(elt); - KisRecordedPathPaintAction* rplpa = new KisRecordedPathPaintAction(pathnode, paintOpPreset); + KisRecordedPathPaintAction* rplpa = new KisRecordedPathPaintAction(pathnode, paintOpPreset, + KisDistanceInitInfo()); setupPaintAction(rplpa, elt, context); @@ -254,6 +278,14 @@ } else { dbgImage << "Warning: no found"; } + + QDomElement initDistInfoElt = elt.firstChildElement("StartDistInfo"); + if (!initDistInfoElt.isNull()) { + rplpa->setInitDistInfo(KisDistanceInitInfo::fromXML(initDistInfoElt)); + } else { + dbgImage << "Warning: no found"; + } + return rplpa; } diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -94,6 +94,7 @@ kis_node_graph_listener_test.cpp kis_node_visitor_test.cpp kis_paint_information_test.cpp + kis_distance_information_test.cpp kis_paintop_test.cpp kis_pattern_test.cpp kis_recorded_action_factory_registry_test.cpp diff --git a/libs/image/tests/kis_distance_information_test.h b/libs/image/tests/kis_distance_information_test.h new file mode 100644 --- /dev/null +++ b/libs/image/tests/kis_distance_information_test.h @@ -0,0 +1,32 @@ +#ifndef KIS_DISTANCE_INFORMATION_TEST_H +#define KIS_DISTANCE_INFORMATION_TEST_H + +#include + +class KisPaintInformation; +class KisDistanceInformation; + +class KisDistanceInformationTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testInitInfo(); + void testInterpolation(); + +private: + void testInitInfoEquality() const; + void testInitInfoXMLClone() const; + + /** + * Performs one interpolation using the specified KisDistanceInformation and checks the results. + * @param interpFactor The interpolation factor that the KisDistanceInformation is expected to + * return. + * @param needSpacingUpdate Indicates whether the KisDistanceInformation is expected to need a + * spacing update after the interpolation. + */ + void testInterpolationImpl(const KisPaintInformation &p1, const KisPaintInformation &p2, + KisDistanceInformation &dist, qreal interpFactor, + bool needSpacingUpdate, qreal interpTolerance) const; +}; + +#endif // KIS_DISTANCE_INFORMATION_TEST_H diff --git a/libs/image/tests/kis_distance_information_test.cpp b/libs/image/tests/kis_distance_information_test.cpp new file mode 100644 --- /dev/null +++ b/libs/image/tests/kis_distance_information_test.cpp @@ -0,0 +1,159 @@ +#include "kis_distance_information_test.h" + +#include +#include +#include +#include + +#include "kis_algebra_2d.h" +#include "kis_distance_information.h" +#include "kis_paint_information.h" + +void KisDistanceInformationTest::testInitInfo() +{ + // Test equality checking operators. + testInitInfoEquality(); + + // Test XML cloning. + testInitInfoXMLClone(); +} + +void KisDistanceInformationTest::testInterpolation() +{ + // Set up a scenario for interpolation. + + QPointF startPos; + QPointF endPos(100.0, -50.0); + qreal dist = KisAlgebra2D::norm(endPos - startPos); + + qreal startTime = 0.0; + qreal endTime = 1000.0; + qreal interval = endTime - startTime; + + KisPaintInformation p1(startPos, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, startTime, 0.0); + KisPaintInformation p2(endPos, 1.0, 0.0, 0.0, 5.0, 0.0, 1.0, endTime, 0.0); + + // Test interpolation with various spacing settings. + + static const qreal interpTolerance = 0.000001; + + KisDistanceInformation dist1; + dist1.setSpacing(KisSpacingInformation(dist/10.0)); + testInterpolationImpl(p1, p2, dist1, 1.0/10.0, false, interpTolerance); + + KisDistanceInformation dist2(interval/2.0); + dist2.setSpacing(KisSpacingInformation(dist*2.0, interval*1.5)); + testInterpolationImpl(p1, p2, dist2, -1.0, true, interpTolerance); + + KisDistanceInformation dist3(interval*1.1); + dist3.setSpacing(KisSpacingInformation(dist/40.0)); + testInterpolationImpl(p1, p2, dist3, 1.0/40.0, false, interpTolerance); + + KisDistanceInformation dist4; + dist4.setSpacing(KisSpacingInformation(false, 1.0, false, 1.0)); + testInterpolationImpl(p1, p2, dist4, -1.0, false, interpTolerance); + + KisDistanceInformation dist5; + dist5.setSpacing(KisSpacingInformation(false, 1.0, true, interval/20.0)); + testInterpolationImpl(p1, p2, dist5, 1.0/20.0, false, interpTolerance); + + KisDistanceInformation dist6; + dist6.setSpacing(KisSpacingInformation(true, dist/10.0, true, interval/15.0)); + testInterpolationImpl(p1, p2, dist6, 1.0/15.0, false, interpTolerance); + + KisDistanceInformation dist7; + dist7.setSpacing(KisSpacingInformation(true, dist/15.0, true, interval/10.0)); + testInterpolationImpl(p1, p2, dist7, 1.0/15.0, false, interpTolerance); + + KisDistanceInformation dist8; + dist8.setSpacing(KisSpacingInformation(true, dist * 2.0, true, interval * 1.5)); + testInterpolationImpl(p1, p2, dist8, -1.0, false, interpTolerance); + + KisDistanceInformation dist9; + qreal a = 50.0; + qreal b = 25.0; + dist9.setSpacing(KisSpacingInformation(QPointF(a * 2.0, b * 2.0), 0.0, false)); + // Compute the expected interpolation factor; we are using anisotropic spacing here. + qreal angle = KisAlgebra2D::directionBetweenPoints(startPos, endPos, 0.0); + qreal cosTermSqrt = qCos(angle) / a; + qreal sinTermSqrt = qSin(angle) / b; + qreal spacingDist = 2.0 / qSqrt(cosTermSqrt * cosTermSqrt + sinTermSqrt * sinTermSqrt); + qreal expectedInterp = spacingDist / dist; + testInterpolationImpl(p1, p2, dist9, expectedInterp, false, interpTolerance); +} + +void KisDistanceInformationTest::testInitInfoEquality() const +{ + KisDistanceInitInfo info1; + KisDistanceInitInfo info2; + QVERIFY(info1 == info2); + QVERIFY(!(info1 != info2)); + + KisDistanceInitInfo info3(0.1); + KisDistanceInitInfo info4(0.1); + QVERIFY(info3 == info4); + QVERIFY(!(info3 != info4)); + + KisDistanceInitInfo info5(QPointF(1.1, -10.7), 100.0, 3.3); + KisDistanceInitInfo info6(QPointF(1.1, -10.7), 100.0, 3.3); + QVERIFY(info5 == info6); + QVERIFY(!(info5 != info6)); + + KisDistanceInitInfo info7(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1); + KisDistanceInitInfo info8(QPointF(-12.3, 24.0), 104.0, 5.0, 20.1); + QVERIFY(info7 == info8); + QVERIFY(!(info7 != info8)); + + QVERIFY(info1 != info3); + QVERIFY(info1 != info5); + QVERIFY(info1 != info7); + QVERIFY(info3 != info5); + QVERIFY(info3 != info7); + QVERIFY(info5 != info7); +} + +void KisDistanceInformationTest::testInitInfoXMLClone() const +{ + // Note: Numeric values used here must be values that get serialized to XML exactly (e.g. small + // integers). Otherwise, roundoff error in serialization may cause a failure. + + KisDistanceInitInfo info1; + QDomDocument doc; + QDomElement elt1 = doc.createElement("Test1"); + info1.toXML(doc, elt1); + KisDistanceInitInfo clone1 = KisDistanceInitInfo::fromXML(elt1); + QVERIFY(clone1 == info1); + + KisDistanceInitInfo info2(40.0); + QDomElement elt2 = doc.createElement("Test2"); + info2.toXML(doc, elt2); + KisDistanceInitInfo clone2 = KisDistanceInitInfo::fromXML(elt2); + QVERIFY(clone2 == info2); + + KisDistanceInitInfo info3(QPointF(-8.0, -5.0), 0.0, 60.0); + QDomElement elt3 = doc.createElement("Test3"); + info3.toXML(doc, elt3); + KisDistanceInitInfo clone3 = KisDistanceInitInfo::fromXML(elt3); + QVERIFY(clone3 == info3); + + KisDistanceInitInfo info4(QPointF(0.0, 9.0), 10.0, 6.0, 1.0); + QDomElement elt4 = doc.createElement("Test4"); + info4.toXML(doc, elt4); + KisDistanceInitInfo clone4 = KisDistanceInitInfo::fromXML(elt4); + QVERIFY(clone4 == info4); +} + +void KisDistanceInformationTest::testInterpolationImpl(const KisPaintInformation &p1, + const KisPaintInformation &p2, + KisDistanceInformation &dist, + qreal interpFactor, + bool needSpacingUpdate, + qreal interpTolerance) const +{ + qreal actualInterpFactor = dist.getNextPointPosition(p1.pos(), p2.pos(), p1.currentTime(), + p2.currentTime()); + QVERIFY(qAbs(interpFactor - actualInterpFactor) <= interpTolerance); + QCOMPARE(dist.needsSpacingUpdate(), needSpacingUpdate); +} + +QTEST_MAIN(KisDistanceInformationTest) diff --git a/libs/image/tests/kis_paintop_test.cpp b/libs/image/tests/kis_paintop_test.cpp --- a/libs/image/tests/kis_paintop_test.cpp +++ b/libs/image/tests/kis_paintop_test.cpp @@ -31,10 +31,17 @@ : KisPaintOp(gc) { } +protected: + KisSpacingInformation paintAt(const KisPaintInformation&) override { return KisSpacingInformation(0.0); } + KisSpacingInformation updateSpacingImpl(const KisPaintInformation&) const override + { + return KisSpacingInformation(0.0); + } + }; void KisPaintopTest::testCreation() diff --git a/libs/ui/tool/kis_recording_adapter.h b/libs/ui/tool/kis_recording_adapter.h --- a/libs/ui/tool/kis_recording_adapter.h +++ b/libs/ui/tool/kis_recording_adapter.h @@ -24,6 +24,7 @@ class KisRecordedPathPaintAction; class KisPaintInformation; +class KisDistanceInitInfo; class KisRecordingAdapter @@ -32,7 +33,8 @@ KisRecordingAdapter(); ~KisRecordingAdapter(); - void startStroke(KisImageWSP image, KisResourcesSnapshotSP resources); + void startStroke(KisImageWSP image, KisResourcesSnapshotSP resources, + const KisDistanceInitInfo &startDist); void endStroke(); void addPoint(const KisPaintInformation &pi); diff --git a/libs/ui/tool/kis_recording_adapter.cpp b/libs/ui/tool/kis_recording_adapter.cpp --- a/libs/ui/tool/kis_recording_adapter.cpp +++ b/libs/ui/tool/kis_recording_adapter.cpp @@ -36,14 +36,15 @@ { } -void KisRecordingAdapter::startStroke(KisImageWSP image, KisResourcesSnapshotSP resources) +void KisRecordingAdapter::startStroke(KisImageWSP image, KisResourcesSnapshotSP resources, + const KisDistanceInitInfo &startDistInfo) { Q_ASSERT(!m_pathPaintAction); Q_ASSERT(!m_image); m_image = image; m_pathPaintAction = new KisRecordedPathPaintAction( - KisNodeQueryPath::absolutePath(resources->currentNode()), 0); + KisNodeQueryPath::absolutePath(resources->currentNode()), 0, startDistInfo); resources->setupPaintAction(m_pathPaintAction); } diff --git a/libs/ui/tool/kis_tool_freehand_helper.h b/libs/ui/tool/kis_tool_freehand_helper.h --- a/libs/ui/tool/kis_tool_freehand_helper.h +++ b/libs/ui/tool/kis_tool_freehand_helper.h @@ -109,9 +109,7 @@ protected: virtual void createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle); + const KisDistanceInformation &startDist); // lo-level methods for painting primitives diff --git a/libs/ui/tool/kis_tool_freehand_helper.cpp b/libs/ui/tool/kis_tool_freehand_helper.cpp --- a/libs/ui/tool/kis_tool_freehand_helper.cpp +++ b/libs/ui/tool/kis_tool_freehand_helper.cpp @@ -50,6 +50,10 @@ // airbrush rate, which can improve responsiveness. const qreal AIRBRUSH_INTERVAL_FACTOR = 0.5; +// The amount of time, in milliseconds, to allow between updates of the spacing information. Only +// used when airbrushing. +const qreal SPACING_UPDATE_INTERVAL = 50.0; + struct KisToolFreehandHelper::Private { KisPaintingInformationBuilder *infoBuilder; @@ -266,10 +270,14 @@ m_d->previousPaintInformation = pi; + KisDistanceInitInfo startDistInfo(m_d->previousPaintInformation.pos(), + m_d->previousPaintInformation.currentTime(), + startAngle, + SPACING_UPDATE_INTERVAL); + KisDistanceInformation startDist = startDistInfo.makeDistInfo(); + createPainters(m_d->painterInfos, - m_d->previousPaintInformation.pos(), - m_d->previousPaintInformation.currentTime(), - startAngle); + startDist); m_d->resources = new KisResourcesSnapshot(image, currentNode, @@ -281,7 +289,7 @@ } if(m_d->recordingAdapter) { - m_d->recordingAdapter->startStroke(image, m_d->resources); + m_d->recordingAdapter->startStroke(image, m_d->resources, startDistInfo); } KisStrokeStrategy *stroke = @@ -916,11 +924,9 @@ } void KisToolFreehandHelper::createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle) + const KisDistanceInformation &startDist) { - painterInfos << new PainterInfo(lastPosition, lastTime, lastAngle); + painterInfos << new PainterInfo(startDist); } void KisToolFreehandHelper::paintAt(const KisPaintInformation &pi) diff --git a/libs/ui/tool/kis_tool_multihand_helper.h b/libs/ui/tool/kis_tool_multihand_helper.h --- a/libs/ui/tool/kis_tool_multihand_helper.h +++ b/libs/ui/tool/kis_tool_multihand_helper.h @@ -36,9 +36,7 @@ protected: void createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle) override; + const KisDistanceInformation &startDist) override; void paintAt(const KisPaintInformation &pi) override; diff --git a/libs/ui/tool/kis_tool_multihand_helper.cpp b/libs/ui/tool/kis_tool_multihand_helper.cpp --- a/libs/ui/tool/kis_tool_multihand_helper.cpp +++ b/libs/ui/tool/kis_tool_multihand_helper.cpp @@ -47,12 +47,10 @@ } void KisToolMultihandHelper::createPainters(QVector &painterInfos, - const QPointF &lastPosition, - int lastTime, - qreal lastAngle) + const KisDistanceInformation &startDist) { for (int i = 0; i < d->transformations.size(); i++) { - painterInfos << new PainterInfo(lastPosition, lastTime, lastAngle); + painterInfos << new PainterInfo(startDist); } } diff --git a/libs/ui/tool/kis_tool_shape.cc b/libs/ui/tool/kis_tool_shape.cc --- a/libs/ui/tool/kis_tool_shape.cc +++ b/libs/ui/tool/kis_tool_shape.cc @@ -202,7 +202,8 @@ // Recorde the paint action KisRecordedPathPaintAction bezierCurvePaintAction( KisNodeQueryPath::absolutePath(node), - preset ); + preset, + KisDistanceInitInfo()); bezierCurvePaintAction.setPaintColor(currentFgColor()); QPointF lastPoint, nextPoint; int elementCount = mapedOutline.elementCount(); diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h --- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h +++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.h @@ -39,7 +39,7 @@ class KRITAUI_EXPORT PainterInfo { public: PainterInfo(); - PainterInfo(const QPointF &lastPosition, int lastTime, qreal lastAngle); + PainterInfo(const KisDistanceInformation &startDist); PainterInfo(PainterInfo *rhs, int levelOfDetail); ~PainterInfo(); diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp --- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp @@ -38,10 +38,9 @@ { } -KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(const QPointF &lastPosition, int lastTime, - qreal lastAngle) +KisPainterBasedStrokeStrategy::PainterInfo::PainterInfo(const KisDistanceInformation &startDist) : painter(new KisPainter()), - dragDistance(new KisDistanceInformation(lastPosition, lastTime, lastAngle)), + dragDistance(new KisDistanceInformation(startDist)), m_parentPainterInfo(0), m_childPainterInfo(0) { diff --git a/plugins/paintops/chalk/kis_chalk_paintop.h b/plugins/paintops/chalk/kis_chalk_paintop.h --- a/plugins/paintops/chalk/kis_chalk_paintop.h +++ b/plugins/paintops/chalk/kis_chalk_paintop.h @@ -38,8 +38,12 @@ KisChalkPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisChalkPaintOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_dab; ChalkBrush * m_chalkBrush; diff --git a/plugins/paintops/chalk/kis_chalk_paintop.cpp b/plugins/paintops/chalk/kis_chalk_paintop.cpp --- a/plugins/paintops/chalk/kis_chalk_paintop.cpp +++ b/plugins/paintops/chalk/kis_chalk_paintop.cpp @@ -92,6 +92,11 @@ painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisChalkPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, 1.0, false, 1.0, 1.0, &m_airbrushOption, nullptr, &m_rateOption, info); } diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.h b/plugins/paintops/colorsmudge/kis_colorsmudgeop.h --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.h +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.h @@ -46,8 +46,11 @@ KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisColorSmudgeOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: // Sets the m_maskDab _and m_maskDabRect void updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint); diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp @@ -285,3 +285,10 @@ return spacingInfo; } + +KisSpacingInformation KisColorSmudgeOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + const qreal rotation = m_rotationOption.apply(info); + return effectiveSpacing(scale, rotation, m_spacingOption, info); +} diff --git a/plugins/paintops/curvebrush/kis_curve_paintop.h b/plugins/paintops/curvebrush/kis_curve_paintop.h --- a/plugins/paintops/curvebrush/kis_curve_paintop.h +++ b/plugins/paintops/curvebrush/kis_curve_paintop.h @@ -37,9 +37,13 @@ KisCurvePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisCurvePaintOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void paintLine(KisPaintDeviceSP dab, const KisPaintInformation &pi1, const KisPaintInformation &pi2); diff --git a/plugins/paintops/curvebrush/kis_curve_paintop.cpp b/plugins/paintops/curvebrush/kis_curve_paintop.cpp --- a/plugins/paintops/curvebrush/kis_curve_paintop.cpp +++ b/plugins/paintops/curvebrush/kis_curve_paintop.cpp @@ -52,11 +52,15 @@ KisSpacingInformation KisCurvePaintOp::paintAt(const KisPaintInformation& info) { + return updateSpacingImpl(info); +} + +KisSpacingInformation KisCurvePaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ Q_UNUSED(info); return KisSpacingInformation(1.0); } - void KisCurvePaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.h b/plugins/paintops/defaultpaintops/brush/kis_brushop.h --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.h +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.h @@ -53,9 +53,13 @@ KisBrushOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisBrushOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisColorSource *m_colorSource; KisAirbrushOption m_airbrushOption; diff --git a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp --- a/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp +++ b/plugins/paintops/defaultpaintops/brush/kis_brushop.cpp @@ -171,6 +171,14 @@ info); } +KisSpacingInformation KisBrushOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + qreal rotation = m_rotationOption.apply(info); + return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, + info); +} + void KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) { if (m_sharpnessOption.isChecked() && m_brush && (m_brush->width() == 1) && (m_brush->height() == 1)) { diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.h @@ -50,8 +50,11 @@ KisDuplicateOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisDuplicateOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: qreal minimizeEnergy(const qreal* m, qreal* sol, int w, int h); diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop.cpp @@ -287,3 +287,8 @@ return effectiveSpacing(scale); } + +KisSpacingInformation KisDuplicateOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + return effectiveSpacing(m_sizeOption.apply(info)); +} diff --git a/plugins/paintops/deform/kis_deform_paintop.h b/plugins/paintops/deform/kis_deform_paintop.h --- a/plugins/paintops/deform/kis_deform_paintop.h +++ b/plugins/paintops/deform/kis_deform_paintop.h @@ -42,8 +42,11 @@ KisDeformPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisDeformPaintOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_dab; diff --git a/plugins/paintops/deform/kis_deform_paintop.cpp b/plugins/paintops/deform/kis_deform_paintop.cpp --- a/plugins/paintops/deform/kis_deform_paintop.cpp +++ b/plugins/paintops/deform/kis_deform_paintop.cpp @@ -135,7 +135,7 @@ // this happens for the first dab of the move mode, we need more information for being able to move if (!mask) { - return KisSpacingInformation(m_spacing); + return updateSpacingImpl(info); } quint8 origOpacity = m_opacityOption.apply(painter(), info); @@ -143,11 +143,14 @@ painter()->renderMirrorMask(QRect(QPoint(x, y), QSize(mask->bounds().width() , mask->bounds().height())), dab, mask); painter()->setOpacity(origOpacity); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisDeformPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, m_spacing, false, 1.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); } - - diff --git a/plugins/paintops/dynadraw/kis_dyna_paintop.h b/plugins/paintops/dynadraw/kis_dyna_paintop.h --- a/plugins/paintops/dynadraw/kis_dyna_paintop.h +++ b/plugins/paintops/dynadraw/kis_dyna_paintop.h @@ -39,13 +39,17 @@ KisDynaPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisDynaPaintOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; virtual bool incremental() const { return true; } +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); diff --git a/plugins/paintops/dynadraw/kis_dyna_paintop.cpp b/plugins/paintops/dynadraw/kis_dyna_paintop.cpp --- a/plugins/paintops/dynadraw/kis_dyna_paintop.cpp +++ b/plugins/paintops/dynadraw/kis_dyna_paintop.cpp @@ -126,6 +126,11 @@ KisSpacingInformation KisDynaPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisDynaPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); diff --git a/plugins/paintops/experiment/kis_experiment_paintop.h b/plugins/paintops/experiment/kis_experiment_paintop.h --- a/plugins/paintops/experiment/kis_experiment_paintop.h +++ b/plugins/paintops/experiment/kis_experiment_paintop.h @@ -38,8 +38,12 @@ ~KisExperimentPaintOp() override; void paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) override; + +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void paintRegion(const QRegion &changedRegion); QPointF speedCorrectedPosition(const KisPaintInformation& pi1, diff --git a/plugins/paintops/experiment/kis_experiment_paintop.cpp b/plugins/paintops/experiment/kis_experiment_paintop.cpp --- a/plugins/paintops/experiment/kis_experiment_paintop.cpp +++ b/plugins/paintops/experiment/kis_experiment_paintop.cpp @@ -245,6 +245,11 @@ KisSpacingInformation KisExperimentPaintOp::paintAt(const KisPaintInformation& info) { + return updateSpacingImpl(info); +} + +KisSpacingInformation KisExperimentPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ Q_UNUSED(info); return KisSpacingInformation(1.0); } diff --git a/plugins/paintops/filterop/kis_filterop.h b/plugins/paintops/filterop/kis_filterop.h --- a/plugins/paintops/filterop/kis_filterop.h +++ b/plugins/paintops/filterop/kis_filterop.h @@ -40,8 +40,12 @@ KisFilterOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisFilterOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisPaintDeviceSP m_tmpDevice; diff --git a/plugins/paintops/filterop/kis_filterop.cpp b/plugins/paintops/filterop/kis_filterop.cpp --- a/plugins/paintops/filterop/kis_filterop.cpp +++ b/plugins/paintops/filterop/kis_filterop.cpp @@ -140,3 +140,10 @@ return effectiveSpacing(scale, rotation, info); } + +KisSpacingInformation KisFilterOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + const qreal rotation = m_rotationOption.apply(info); + return effectiveSpacing(scale, rotation, info); +} diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.h b/plugins/paintops/gridbrush/kis_grid_paintop.h --- a/plugins/paintops/gridbrush/kis_grid_paintop.h +++ b/plugins/paintops/gridbrush/kis_grid_paintop.h @@ -57,8 +57,14 @@ KisGridPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisGridPaintOp() override; +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(qreal lodScale) const; + private: KisGridPaintOpSettingsSP m_settings; KisImageWSP m_image; diff --git a/plugins/paintops/gridbrush/kis_grid_paintop.cpp b/plugins/paintops/gridbrush/kis_grid_paintop.cpp --- a/plugins/paintops/gridbrush/kis_grid_paintop.cpp +++ b/plugins/paintops/gridbrush/kis_grid_paintop.cpp @@ -237,7 +237,18 @@ m_total += msec; m_count++; #endif - return KisSpacingInformation(m_spacing * additionalScale); + return computeSpacing(additionalScale); +} + +KisSpacingInformation KisGridPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + Q_UNUSED(info); + return computeSpacing(KisLodTransform::lodToScale(painter()->device())); +} + +KisSpacingInformation KisGridPaintOp::computeSpacing(qreal lodScale) const +{ + return KisSpacingInformation(m_spacing * lodScale); } void KisGridProperties::readOptionSetting(const KisPropertiesConfigurationSP setting) diff --git a/plugins/paintops/hairy/kis_hairy_paintop.h b/plugins/paintops/hairy/kis_hairy_paintop.h --- a/plugins/paintops/hairy/kis_hairy_paintop.h +++ b/plugins/paintops/hairy/kis_hairy_paintop.h @@ -40,9 +40,13 @@ public: KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisHairyProperties m_properties; diff --git a/plugins/paintops/hairy/kis_hairy_paintop.cpp b/plugins/paintops/hairy/kis_hairy_paintop.cpp --- a/plugins/paintops/hairy/kis_hairy_paintop.cpp +++ b/plugins/paintops/hairy/kis_hairy_paintop.cpp @@ -103,6 +103,11 @@ KisSpacingInformation KisHairyPaintOp::paintAt(const KisPaintInformation& info) { + return updateSpacingImpl(info); +} + +KisSpacingInformation KisHairyPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ Q_UNUSED(info); return KisSpacingInformation(0.5); } diff --git a/plugins/paintops/hatching/kis_hatching_paintop.h b/plugins/paintops/hatching/kis_hatching_paintop.h --- a/plugins/paintops/hatching/kis_hatching_paintop.h +++ b/plugins/paintops/hatching/kis_hatching_paintop.h @@ -47,18 +47,21 @@ ~KisHatchingPaintOp() override; /** - * Paint a hatched dab around the mouse cursor according to - * sensor settings and user preferences. - */ - KisSpacingInformation paintAt(const KisPaintInformation& info) override; - - /** * Returns a number between -90 and 90, and corresponds to the * angle that results from adding angle 'spin' to 'm_settings->angle', * corrected to coincide with the way the GUI operates. */ double spinAngle(double spin); +protected: + /** + * Paint a hatched dab around the mouse cursor according to + * sensor settings and user preferences. + */ + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: KisHatchingPaintOpSettingsSP m_settings; KisImageWSP m_image; diff --git a/plugins/paintops/hatching/kis_hatching_paintop.cpp b/plugins/paintops/hatching/kis_hatching_paintop.cpp --- a/plugins/paintops/hatching/kis_hatching_paintop.cpp +++ b/plugins/paintops/hatching/kis_hatching_paintop.cpp @@ -184,6 +184,12 @@ return effectiveSpacing(scale); } +KisSpacingInformation KisHatchingPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal scale = KisLodTransform::lodToScale(painter()->device()) * m_sizeOption.apply(info); + return effectiveSpacing(scale); +} + double KisHatchingPaintOp::spinAngle(double spin) { double tempangle = m_settings->angle + spin; diff --git a/plugins/paintops/particle/kis_particle_paintop.h b/plugins/paintops/particle/kis_particle_paintop.h --- a/plugins/paintops/particle/kis_particle_paintop.h +++ b/plugins/paintops/particle/kis_particle_paintop.h @@ -38,9 +38,13 @@ KisParticlePaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisParticlePaintOp() override; - KisSpacingInformation paintAt(const KisPaintInformation& info) override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2); diff --git a/plugins/paintops/particle/kis_particle_paintop.cpp b/plugins/paintops/particle/kis_particle_paintop.cpp --- a/plugins/paintops/particle/kis_particle_paintop.cpp +++ b/plugins/paintops/particle/kis_particle_paintop.cpp @@ -71,6 +71,11 @@ KisSpacingInformation KisParticlePaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisParticlePaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.h b/plugins/paintops/roundmarker/kis_roundmarkerop.h --- a/plugins/paintops/roundmarker/kis_roundmarkerop.h +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.h @@ -37,8 +37,15 @@ KisRoundMarkerOp(KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisRoundMarkerOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal diameter) const; + private: bool m_firstRun; KisImageSP m_image; diff --git a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp --- a/plugins/paintops/roundmarker/kis_roundmarkerop.cpp +++ b/plugins/paintops/roundmarker/kis_roundmarkerop.cpp @@ -76,7 +76,6 @@ const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); const qreal scale = m_sizeOption.apply(info) * lodScale; const qreal rotation = 0; // TODO - const bool axesFlipped = false; // TODO const qreal diameter = m_markerOption.diameter * scale; qreal radius = 0.5 * diameter; @@ -126,20 +125,7 @@ //m_lastPaintPos = newCenterPos; - qreal extraSpacingScale = 1.0; - if (m_spacingOption.isChecked()) { - extraSpacingScale = m_spacingOption.apply(info); - } - - KisSpacingInformation spacingInfo = - KisPaintOpUtils::effectiveSpacing(diameter, diameter, - extraSpacingScale, 1.0, true, true, rotation, axesFlipped, - m_markerOption.spacing, - m_markerOption.use_auto_spacing, - m_markerOption.auto_spacing_coeff, - false, - 0.0, - lodScale); + KisSpacingInformation spacingInfo = computeSpacing(info, diameter); if (m_firstRun) { m_firstRun = false; @@ -149,3 +135,30 @@ return spacingInfo; } + +KisSpacingInformation KisRoundMarkerOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); + const qreal diameter = m_markerOption.diameter * m_sizeOption.apply(info) * lodScale; + + return computeSpacing(info, diameter); +} + +KisSpacingInformation KisRoundMarkerOp::computeSpacing(const KisPaintInformation &info, + qreal diameter) const +{ + const qreal rotation = 0; // TODO + const bool axesFlipped = false; // TODO + + qreal extraSpacingScale = 1.0; + if (m_spacingOption.isChecked()) { + extraSpacingScale = m_spacingOption.apply(info); + } + + return KisPaintOpUtils::effectiveSpacing(diameter, diameter, + extraSpacingScale, 1.0, true, true, rotation, + axesFlipped, m_markerOption.spacing, + m_markerOption.use_auto_spacing, + m_markerOption.auto_spacing_coeff, false, 0.0, + KisLodTransform::lodToScale(painter()->device())); +} diff --git a/plugins/paintops/sketch/kis_sketch_paintop.h b/plugins/paintops/sketch/kis_sketch_paintop.h --- a/plugins/paintops/sketch/kis_sketch_paintop.h +++ b/plugins/paintops/sketch/kis_sketch_paintop.h @@ -47,8 +47,12 @@ ~KisSketchPaintOp() override; void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; + +protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + private: // pixel buffer KisPaintDeviceSP m_dab; diff --git a/plugins/paintops/sketch/kis_sketch_paintop.cpp b/plugins/paintops/sketch/kis_sketch_paintop.cpp --- a/plugins/paintops/sketch/kis_sketch_paintop.cpp +++ b/plugins/paintops/sketch/kis_sketch_paintop.cpp @@ -309,6 +309,11 @@ KisSpacingInformation KisSketchPaintOp::paintAt(const KisPaintInformation& info) { doPaintLine(info, info); + return updateSpacingImpl(info); +} + +KisSpacingInformation KisSketchPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0, KisLodTransform::lodToScale(painter()->device()), &m_airbrushOption, nullptr, &m_rateOption, info); diff --git a/plugins/paintops/spray/kis_spray_paintop.h b/plugins/paintops/spray/kis_spray_paintop.h --- a/plugins/paintops/spray/kis_spray_paintop.h +++ b/plugins/paintops/spray/kis_spray_paintop.h @@ -42,8 +42,15 @@ KisSprayPaintOp(const KisPaintOpSettingsSP settings, KisPainter * painter, KisNodeSP node, KisImageSP image); ~KisSprayPaintOp() override; +protected: + KisSpacingInformation paintAt(const KisPaintInformation& info) override; + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal lodScale) const; + private: KisShapeProperties m_shapeProperties; KisSprayProperties m_properties; diff --git a/plugins/paintops/spray/kis_spray_paintop.cpp b/plugins/paintops/spray/kis_spray_paintop.cpp --- a/plugins/paintops/spray/kis_spray_paintop.cpp +++ b/plugins/paintops/spray/kis_spray_paintop.cpp @@ -115,14 +115,14 @@ // Spray Brush is capable of working with zero scale, // so no additional checks for 'zero'ness are needed const qreal scale = m_sizeOption.apply(info); - const qreal additionalScale = KisLodTransform::lodToScale(painter()->device()); + const qreal lodScale = KisLodTransform::lodToScale(painter()->device()); m_sprayBrush.paint(m_dab, m_node->paintDevice(), info, rotation, - scale, additionalScale, + scale, lodScale, painter()->paintColor(), painter()->backgroundColor()); @@ -131,8 +131,18 @@ painter()->renderMirrorMask(rc, m_dab); painter()->setOpacity(origOpacity); + return computeSpacing(info, lodScale); +} + +KisSpacingInformation KisSprayPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + return computeSpacing(info, KisLodTransform::lodToScale(painter()->device())); +} + +KisSpacingInformation KisSprayPaintOp::computeSpacing(const KisPaintInformation &info, + qreal lodScale) const +{ return KisPaintOpPluginUtils::effectiveSpacing(1.0, 1.0, true, 0.0, false, - m_spacing * additionalScale, false, 1.0, - KisLodTransform::lodToScale(painter()->device()), + m_spacing * lodScale, false, 1.0, lodScale, &m_airbrushOption, nullptr, &m_rateOption, info); } diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.h @@ -48,9 +48,18 @@ KisTangentNormalPaintOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image); ~KisTangentNormalPaintOp() override; + void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; + +protected: /*paint the dabs*/ KisSpacingInformation paintAt(const KisPaintInformation& info) override; - void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override; + + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; + +private: + KisSpacingInformation computeSpacing(const KisPaintInformation &info, qreal scale, + qreal rotation) const; + private: //private functions// KisPressureSizeOption m_sizeOption; diff --git a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp --- a/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp +++ b/plugins/paintops/tangentnormal/kis_tangent_normal_paintop.cpp @@ -167,6 +167,19 @@ painter()->setOpacity(oldOpacity); painter()->setCompositeOp(oldCompositeOpId); + return computeSpacing(info, scale, rotation); +} + +KisSpacingInformation KisTangentNormalPaintOp::updateSpacingImpl(const KisPaintInformation &info) const +{ + qreal scale = m_sizeOption.apply(info) * KisLodTransform::lodToScale(painter()->device()); + qreal rotation = m_rotationOption.apply(info); + return computeSpacing(info, scale, rotation); +} + +KisSpacingInformation KisTangentNormalPaintOp::computeSpacing(const KisPaintInformation &info, + qreal scale, qreal rotation) const +{ return effectiveSpacing(scale, rotation, &m_airbrushOption, &m_spacingOption, &m_rateOption, info); } diff --git a/plugins/tools/basictools/kis_tool_line_helper.cpp b/plugins/tools/basictools/kis_tool_line_helper.cpp --- a/plugins/tools/basictools/kis_tool_line_helper.cpp +++ b/plugins/tools/basictools/kis_tool_line_helper.cpp @@ -111,7 +111,8 @@ { if (!m_d->enabled) return; - // Ignore the elapsed stroke time. + // Ignore the elapsed stroke time, so that the line tool will behave as if the whole stroke is + // drawn at once. This should prevent any possible spurious dabs caused by airbrushing features. KisPaintInformation pi = m_d->infoBuilder->continueStroke(event, 0); diff --git a/plugins/tools/tool_polygon/kis_tool_polygon.cc b/plugins/tools/tool_polygon/kis_tool_polygon.cc --- a/plugins/tools/tool_polygon/kis_tool_polygon.cc +++ b/plugins/tools/tool_polygon/kis_tool_polygon.cc @@ -59,7 +59,9 @@ if (!blockUntilOperationsFinished()) return; if (image()) { - KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset()); + KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), + currentPaintOpPreset(), + KisDistanceInitInfo()); setupPaintAction(&linePaintAction); linePaintAction.addPolyLine(points.toList()); linePaintAction.addLine(KisPaintInformation(points.last()), KisPaintInformation(points.first())); diff --git a/plugins/tools/tool_polyline/kis_tool_polyline.cc b/plugins/tools/tool_polyline/kis_tool_polyline.cc --- a/plugins/tools/tool_polyline/kis_tool_polyline.cc +++ b/plugins/tools/tool_polyline/kis_tool_polyline.cc @@ -64,7 +64,9 @@ if (!blockUntilOperationsFinished()) return; if (image()) { - KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), currentPaintOpPreset()); + KisRecordedPathPaintAction linePaintAction(KisNodeQueryPath::absolutePath(currentNode()), + currentPaintOpPreset(), + KisDistanceInitInfo()); setupPaintAction(&linePaintAction); linePaintAction.addPolyLine(points.toList()); image()->actionRecorder()->addAction(linePaintAction); diff --git a/plugins/tools/tool_transform2/kis_liquify_paintop.h b/plugins/tools/tool_transform2/kis_liquify_paintop.h --- a/plugins/tools/tool_transform2/kis_liquify_paintop.h +++ b/plugins/tools/tool_transform2/kis_liquify_paintop.h @@ -24,6 +24,7 @@ class KisLiquifyTransformWorker; class KisPaintInformation; class KisSpacingInformation; +class KisDistanceInformation; class KisLiquifyProperties; class QPainterPath; @@ -37,8 +38,20 @@ KisSpacingInformation paintAt(const KisPaintInformation &pi); + /** + * Updates the spacing in currentDistance based on the provided information. + */ + void updateSpacing(const KisPaintInformation &info, KisDistanceInformation ¤tDistance) + const; + static QPainterPath brushOutline(const KisLiquifyProperties &props, const KisPaintInformation &info); +protected: + KisSpacingInformation updateSpacingImpl(const KisPaintInformation &pi) const; + +private: + qreal computeSize(const KisPaintInformation &pi) const; + private: struct Private; const QScopedPointer m_d; diff --git a/plugins/tools/tool_transform2/kis_liquify_paintop.cpp b/plugins/tools/tool_transform2/kis_liquify_paintop.cpp --- a/plugins/tools/tool_transform2/kis_liquify_paintop.cpp +++ b/plugins/tools/tool_transform2/kis_liquify_paintop.cpp @@ -105,13 +105,25 @@ return outline; } +// TODO: Reduce code duplication between KisLiquifyPaintop and KisPaintOp. It might be possible to +// make them both subclasses of some more general base class. +void KisLiquifyPaintop::updateSpacing(const KisPaintInformation &info, + KisDistanceInformation ¤tDistance) const +{ + KisPaintInformation pi(info); + KisSpacingInformation spacingInfo; + { + KisPaintInformation::DistanceInformationRegistrar r + = pi.registerDistanceInformation(¤tDistance); + spacingInfo = updateSpacingImpl(pi); + } + + currentDistance.setSpacing(spacingInfo); +} + KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi) { - static const qreal sizeToSigmaCoeff = 1.0 / 3.0; - const qreal size = sizeToSigmaCoeff * - (m_d->props.sizeHasPressure() ? - pi.pressure() * m_d->props.size(): - m_d->props.size()); + const qreal size = computeSize(pi); const qreal spacing = m_d->props.spacing() * size; @@ -164,3 +176,17 @@ return KisSpacingInformation(spacing); } + +KisSpacingInformation KisLiquifyPaintop::updateSpacingImpl(const KisPaintInformation &pi) const +{ + return KisSpacingInformation(m_d->props.spacing() * computeSize(pi)); +} + +qreal KisLiquifyPaintop::computeSize(const KisPaintInformation &pi) const +{ + static const qreal sizeToSigmaCoeff = 1.0 / 3.0; + return sizeToSigmaCoeff * + (m_d->props.sizeHasPressure() ? + pi.pressure() * m_d->props.size(): + m_d->props.size()); +}