[graphics/krita/krita/4.3] /: Implement a new free patch deform algorithm

Dmitry Kazakov null at kde.org
Fri Jan 8 14:07:28 GMT 2021


Git commit 30a85ebbab3649c46ebf3ec7be68c5487374643c by Dmitry Kazakov.
Committed on 08/01/2021 at 14:06.
Pushed by dkazakov into branch 'krita/4.3'.

Implement a new free patch deform algorithm

The algorithm should also move the nodes as well. The patch
also implements a new Shift-mode:

Shift+drag-patch --- drags the patch without modifying the handles

The main idea of the algorithm is: it gest the offset budget from
the user's drag and splits it among the following moves:

* translate all four nodes if the patch
* offset the nearest segment
* translate the nearest node (or the two nodes of the nearest segment)

CC:kimageshop at kde.org

M  +21   -0    libs/global/KisBezierMesh.h
M  +10   -0    libs/global/kis_algebra_2d.h
M  +98   -11   plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp

https://invent.kde.org/graphics/krita/commit/30a85ebbab3649c46ebf3ec7be68c5487374643c

diff --git a/libs/global/KisBezierMesh.h b/libs/global/KisBezierMesh.h
index f1cd7c35d9..db9d4a4d15 100644
--- a/libs/global/KisBezierMesh.h
+++ b/libs/global/KisBezierMesh.h
@@ -618,6 +618,15 @@ private:
             return m_mesh->find(ControlPointIndex(secondNodeIndex(), Mesh::ControlType::Node));
         }
 
+        QPointF pointAtParam(qreal t) const {
+            return KisBezierUtils::bezierCurve(p0(), p1(), p2(), p3(), t);
+        }
+
+        qreal length() const {
+            const qreal eps = 1e-3;
+            return KisBezierUtils::curveLength(p0(), p1(), p2(), p3(), eps);
+        }
+
         int degree() const {
             return KisBezierUtils::bezierDegree(p0(), p1(), p2(), p3());
         }
@@ -985,6 +994,10 @@ public:
     control_point_const_iterator find(const ControlPointIndex &index) const { return find(*this, index); }
     control_point_const_iterator constFind(const ControlPointIndex &index) const { return find(*this, index); }
 
+    control_point_iterator find(const NodeIndex &index) { return find(*this, index); }
+    control_point_const_iterator find(const NodeIndex &index) const { return find(*this, index); }
+    control_point_const_iterator constFind(const NodeIndex &index) const { return find(*this, index); }
+
     segment_iterator find(const SegmentIndex &index) { return find(*this, index); }
     segment_const_iterator find(const SegmentIndex &index) const { return find(*this, index); }
     segment_const_iterator constFind(const SegmentIndex &index) const { return find(*this, index); }
@@ -1193,6 +1206,14 @@ private:
         return it.isValid() ? it : mesh.endControlPoints();
     }
 
+    template <class MeshType,
+              class IteratorType = control_point_iterator_impl<std::is_const<MeshType>::value>>
+    static
+    IteratorType find(MeshType &mesh, const NodeIndex &index) {
+        IteratorType it(&mesh, index.x(), index.y(), Mesh::ControlType::Node);
+        return it.isValid() ? it : mesh.endControlPoints();
+    }
+
     template <class MeshType,
               class IteratorType = segment_iterator_impl<std::is_const<MeshType>::value>>
     static
diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h
index 005caf32d6..9194a53cc1 100644
--- a/libs/global/kis_algebra_2d.h
+++ b/libs/global/kis_algebra_2d.h
@@ -690,6 +690,16 @@ std::pair<QPointF, QTransform> KRITAGLOBAL_EXPORT transformEllipse(const QPointF
 QPointF KRITAGLOBAL_EXPORT alignForZoom(const QPointF &pt, qreal zoom);
 
 
+/**
+ * Linearly reshape function \p x so that in range [x0, x1]
+ * it would cross points (x0, y0) and (x1, y1).
+ */
+template <typename T>
+inline T linearReshapeFunc(T x, T x0, T x1, T y0, T y1)
+{
+    return y0 + (y1 - y0) * (x - x0) / (x1 - x0);
+}
+
 }
 
 
diff --git a/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
index e192f63777..8bfe4f66bc 100644
--- a/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
+++ b/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
@@ -60,6 +60,7 @@ struct KisMeshTransformStrategy::Private
         OVER_SEGMENT,
         OVER_SEGMENT_SYMMETRIC,
         OVER_PATCH,
+        OVER_PATCH_LOCKED,
         SPLIT_SEGMENT,
         MULTIPLE_POINT_SELECTION,
         MOVE_MODE,
@@ -165,7 +166,7 @@ void KisMeshTransformStrategy::setTransformFunction(const QPointF &mousePos, boo
         auto index = m_d->currentArgs.meshTransform()->hitTestPatch(mousePos, &localPatchPos);
         if (m_d->currentArgs.meshTransform()->isIndexValid(index)) {
             hoveredPatch = index;
-            mode = !shiftModifierActive ? Private::OVER_PATCH : Private::MOVE_MODE;
+            mode = !shiftModifierActive ? Private::OVER_PATCH : Private::OVER_PATCH_LOCKED;
         }
     }
 
@@ -330,6 +331,7 @@ QCursor KisMeshTransformStrategy::getCurrentCursor() const
     case Private::OVER_POINT_SYMMETRIC:
     case Private::OVER_SEGMENT_SYMMETRIC:
     case Private::OVER_PATCH:
+    case Private::OVER_PATCH_LOCKED:
         cursor = KisCursor::meshCursorLocked();
         break;
     case Private::SPLIT_SEGMENT: {
@@ -523,7 +525,7 @@ bool KisMeshTransformStrategy::beginPrimaryAction(const QPointF &pt)
 
         retval = true;
 
-    } else if (m_d->mode == Private::OVER_PATCH) {
+    } else if (m_d->mode == Private::OVER_PATCH || m_d->mode == Private::OVER_PATCH_LOCKED) {
         retval = true;
 
     } else if (m_d->mode == Private::SPLIT_SEGMENT) {
@@ -609,36 +611,121 @@ void KisMeshTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shi
         smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, mode);
         smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, mode);
 
-    } else if (m_d->mode == Private::OVER_PATCH) {
+    } else if (m_d->mode == Private::OVER_PATCH || m_d->mode == Private::OVER_PATCH_LOCKED) {
         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredPatch);
 
-        *m_d->currentArgs.meshTransform() = m_d->initialMeshState;
+        using KisAlgebra2D::linearReshapeFunc;
+        using Mesh = KisBezierTransformMesh;
+
+        KisBezierTransformMesh &mesh = *m_d->currentArgs.meshTransform();
+        mesh = m_d->initialMeshState;
 
         auto patchIt = m_d->currentArgs.meshTransform()->find(*m_d->hoveredPatch);
 
-        const QPointF offset = pt - m_d->mouseClickPos;
+        QPointF offset = pt - m_d->mouseClickPos;
 
         auto offsetSegment =
             [this] (KisBezierTransformMesh::segment_iterator it,
                     qreal t,
-                    qreal distance,
                     const QPointF &offset) {
 
             QPointF offsetP1;
             QPointF offsetP2;
 
             std::tie(offsetP1, offsetP2) =
-                KisBezierUtils::offsetSegment(t, (1.0 - distance) * offset);
+                KisBezierUtils::offsetSegment(t, offset);
 
 
             smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, KisSmartMoveMeshControlMode::MoveSymmetricLock);
             smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, KisSmartMoveMeshControlMode::MoveSymmetricLock);
         };
 
-        offsetSegment(patchIt.segmentP(), m_d->localPatchPosition.x(), m_d->localPatchPosition.y(), offset);
-        offsetSegment(patchIt.segmentQ(), m_d->localPatchPosition.x(), 1.0 - m_d->localPatchPosition.y(), offset);
-        offsetSegment(patchIt.segmentR(), m_d->localPatchPosition.y(), m_d->localPatchPosition.x(), offset);
-        offsetSegment(patchIt.segmentS(), m_d->localPatchPosition.y(), 1.0 - m_d->localPatchPosition.x(), offset);
+
+        const QPointF center = patchIt->localToGlobal(QPointF(0.5, 0.5));
+        const qreal centerDistance = kisDistance(m_d->mouseClickPos, center);
+
+        KisBezierTransformMesh::segment_iterator nearestSegment = mesh.endSegments();
+        qreal nearestSegmentSignificance = 0;
+        qreal nearestSegmentDistance = std::numeric_limits<qreal>::max();
+        qreal nearestSegmentDistanceSignificance = 0.0;
+        qreal nearestSegmentParam = 0.5;
+
+        auto testSegment =
+                [&nearestSegment,
+                 &nearestSegmentSignificance,
+                 &nearestSegmentDistance,
+                 &nearestSegmentDistanceSignificance,
+                 &nearestSegmentParam,
+                 centerDistance,
+                 this] (KisBezierTransformMesh::segment_iterator it, qreal param) {
+
+            const QPointF movedPoint = KisBezierUtils::bezierCurve(it.p0(), it.p1(), it.p2(), it.p3(), param);
+            const qreal distance = kisDistance(m_d->mouseClickPos, movedPoint);
+
+            if (distance < nearestSegmentDistance) {
+                const qreal proportion = KisBezierUtils::curveProportionByParam(it.p0(), it.p1(), it.p2(), it.p3(), param, 0.1);
+
+                qreal distanceSignificance =
+                    centerDistance / (centerDistance + distance);
+
+                if (distanceSignificance > 0.6) {
+                    distanceSignificance = std::min(1.0, linearReshapeFunc(distanceSignificance, 0.6, 0.75, 0.6, 1.0));
+                }
+
+                const qreal directionSignificance =
+                    1.0 - std::min(1.0, std::abs(proportion - 0.5) / 0.4);
+
+                nearestSegmentDistance = distance;
+                nearestSegment = it;
+                nearestSegmentParam = param;
+                nearestSegmentSignificance = m_d->mode != Private::OVER_PATCH_LOCKED ? distanceSignificance * directionSignificance : 0;
+                nearestSegmentDistanceSignificance = distanceSignificance;
+            }
+        };
+
+        testSegment(patchIt.segmentP(), m_d->localPatchPosition.x());
+        testSegment(patchIt.segmentQ(), m_d->localPatchPosition.x());
+        testSegment(patchIt.segmentR(), m_d->localPatchPosition.y());
+        testSegment(patchIt.segmentS(), m_d->localPatchPosition.y());
+
+        KIS_SAFE_ASSERT_RECOVER_RETURN(nearestSegment != mesh.endSegments());
+
+        const qreal translationOffsetCoeff =
+            qBound(0.0,
+                   linearReshapeFunc(1.0 - nearestSegmentDistanceSignificance,
+                                     0.95, 0.75, 1.0, 0.0),
+                   1.0);
+        const QPointF translationOffset = translationOffsetCoeff * offset;
+        offset -= translationOffset;
+
+        QPointF segmentOffset;
+
+        if (nearestSegmentSignificance > 0) {
+            segmentOffset = nearestSegmentSignificance * offset;
+            offset -= segmentOffset;
+        }
+
+        const qreal alpha =
+            1.0 - KisBezierUtils::curveProportionByParam(nearestSegment.p0(),
+                                                         nearestSegment.p1(),
+                                                         nearestSegment.p2(),
+                                                         nearestSegment.p3(),
+                                                         nearestSegmentParam, 0.1);
+
+        const qreal coeffN1 =
+            alpha > 0.5 ? std::max(0.0, linearReshapeFunc(alpha, 0.6, 0.75, 1.0, 0.0)) : 1.0;
+        const qreal coeffN0 =
+            alpha < 0.5 ? std::max(0.0, linearReshapeFunc(alpha, 0.25, 0.4, 0.0, 1.0)) : 1.0;
+
+        nearestSegment.itP0().node().translate(offset * coeffN0);
+        nearestSegment.itP3().node().translate(offset * coeffN1);
+
+        patchIt.nodeTopLeft().node().translate(translationOffset);
+        patchIt.nodeTopRight().node().translate(translationOffset);
+        patchIt.nodeBottomLeft().node().translate(translationOffset);
+        patchIt.nodeBottomRight().node().translate(translationOffset);
+
+        offsetSegment(nearestSegment, nearestSegmentParam, segmentOffset);
 
     } else if (m_d->mode == Private::SPLIT_SEGMENT) {
         *m_d->currentArgs.meshTransform() = m_d->initialMeshState;



More information about the kimageshop mailing list