[graphics/krita] /: Implement "Scale handles proportionally" feature for the mesh transform

Dmitry Kazakov null at kde.org
Fri Jan 8 17:46:49 GMT 2021


Git commit a7220ca0e5c91ec4dfb6cf3f890fc852c5ac0472 by Dmitry Kazakov.
Committed on 08/01/2021 at 17:46.
Pushed by dkazakov into branch 'master'.

Implement "Scale handles proportionally" feature for the mesh transform

When the user moves a node, the handles of the neighbouring nodes may
overlap. To avoid that we should shrink the size of the handles when
the distance between two nodes decreases.

The patch implements an option for the mesh transform tool that
activates such scaling (disabled by default).

This feature cannot go into 4.4.2 because of the string freeze.

CC:kimageshop at kde.org

# Conflicts:
#	plugins/tools/tool_transform2/tool_transform_args.h

M  +39   -1    libs/global/KisBezierMesh.h
M  +12   -11   plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
M  +10   -0    plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
M  +1    -1    plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
M  +15   -0    plugins/tools/tool_transform2/tool_transform_args.cc
M  +4    -0    plugins/tools/tool_transform2/tool_transform_args.h
M  +8    -1    plugins/tools/tool_transform2/wdg_tool_transform.ui

https://invent.kde.org/graphics/krita/commit/a7220ca0e5c91ec4dfb6cf3f890fc852c5ac0472

diff --git a/libs/global/KisBezierMesh.h b/libs/global/KisBezierMesh.h
index db9d4a4d15..600413a6df 100644
--- a/libs/global/KisBezierMesh.h
+++ b/libs/global/KisBezierMesh.h
@@ -1434,16 +1434,54 @@ template<typename NodeArg, typename PatchArg>
 void smartMoveControl(Mesh<NodeArg, PatchArg> &mesh,
                       typename Mesh<NodeArg, PatchArg>::ControlPointIndex index,
                       const QPointF &move,
-                      SmartMoveMeshControlMode mode)
+                      SmartMoveMeshControlMode mode,
+                      bool scaleNodeMoves)
 {
     using ControlType = typename Mesh<NodeArg, PatchArg>::ControlType;
     using ControlPointIndex = typename Mesh<NodeArg, PatchArg>::ControlPointIndex;
+    using ControlPointIterator = typename Mesh<NodeArg, PatchArg>::control_point_iterator;
+    using SegmentIterator = typename Mesh<NodeArg, PatchArg>::segment_iterator;
 
     auto it = mesh.find(index);
     KIS_SAFE_ASSERT_RECOVER_RETURN(it != mesh.endControlPoints());
 
     if (it.isNode()) {
+        auto preAdjustSegment = [] (Mesh<NodeArg, PatchArg> &mesh,
+                                    SegmentIterator it,
+                                    const QPointF &normalizedOffset) {
+
+            if (it == mesh.endSegments()) return;
+
+            const QPointF base1 = it.p3() - it.p0();
+            const QPointF base2 = it.p3() - it.p0() - normalizedOffset;
+
+            {
+                const QPointF control = it.p1() - it.p0();
+                const qreal dist0 = KisAlgebra2D::norm(base1);
+                const qreal dist1 = KisAlgebra2D::dotProduct(base2, base1) / dist0;
+                const qreal coeff = dist1 / dist0;
+
+                it.p1() = it.p0() + coeff * (control);
+            }
+            {
+                const QPointF control = it.p2() - it.p3();
+                const qreal dist0 = KisAlgebra2D::norm(base1);
+                const qreal dist1 = KisAlgebra2D::dotProduct(base2, base1) / dist0;
+                const qreal coeff = dist1 / dist0;
+
+                it.p2() = it.p3() + coeff * (control);
+            }
+        };
+
+        if (scaleNodeMoves) {
+            preAdjustSegment(mesh, it.topSegment(), -move);
+            preAdjustSegment(mesh, it.leftSegment(), -move);
+            preAdjustSegment(mesh, it.bottomSegment(), move);
+            preAdjustSegment(mesh, it.rightSegment(), move);
+        }
+
         it.node().translate(move);
+
     } else {
         const QPointF newPos = *it + move;
 
diff --git a/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp b/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
index a65b776080..26b783672d 100644
--- a/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
+++ b/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp
@@ -571,7 +571,8 @@ void KisMeshTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shi
         smartMoveControl(*m_d->currentArgs.meshTransform(),
                          *m_d->hoveredControl,
                          pt - m_d->lastMousePos,
-                         mode);
+                         mode,
+                         m_d->currentArgs.meshScaleHandles());
 
     } else if (m_d->mode == Private::OVER_SEGMENT || m_d->mode == Private::OVER_SEGMENT_SYMMETRIC) {
         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredSegment);
@@ -596,8 +597,8 @@ void KisMeshTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shi
             KisSmartMoveMeshControlMode::MoveSymmetricLock :
             KisSmartMoveMeshControlMode::MoveFree;
 
-        smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, mode);
-        smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, mode);
+        smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, mode, m_d->currentArgs.meshScaleHandles());
+        smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, mode, m_d->currentArgs.meshScaleHandles());
 
     } else if (m_d->mode == Private::OVER_PATCH || m_d->mode == Private::OVER_PATCH_LOCKED) {
         KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredPatch);
@@ -624,8 +625,8 @@ void KisMeshTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shi
                 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);
+            smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
+            smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
         };
 
 
@@ -705,13 +706,13 @@ void KisMeshTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shi
         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);
+        smartMoveControl(*m_d->currentArgs.meshTransform(), nearestSegment.itP0().controlIndex(), offset * coeffN0, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
+        smartMoveControl(*m_d->currentArgs.meshTransform(), nearestSegment.itP3().controlIndex(), offset * coeffN1, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
 
-        patchIt.nodeTopLeft().node().translate(translationOffset);
-        patchIt.nodeTopRight().node().translate(translationOffset);
-        patchIt.nodeBottomLeft().node().translate(translationOffset);
-        patchIt.nodeBottomRight().node().translate(translationOffset);
+        smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeTopLeft().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
+        smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeTopRight().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
+        smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeBottomLeft().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
+        smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeBottomRight().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
 
         offsetSegment(nearestSegment, nearestSegmentParam, segmentOffset);
 
diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
index 31e7115787..a69c2d32de 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
+++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
@@ -287,6 +287,7 @@ KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionP
 
     connect(chkShowControlPoints, SIGNAL(toggled(bool)), this, SLOT(slotMeshShowHandlesChanged()));
     connect(chkSymmetricalHandles, SIGNAL(toggled(bool)), this, SLOT(slotMeshSymmetricalHandlesChanged()));
+    connect(chkScaleHandles, SIGNAL(toggled(bool)), this, SLOT(slotMeshScaleHandlesChanged()));
     connect(intNumColumns, SIGNAL(valueChanged(int)), this, SLOT(slotMeshSizeChanged()));
     connect(intNumRows, SIGNAL(valueChanged(int)), this, SLOT(slotMeshSizeChanged()));
 
@@ -635,6 +636,7 @@ void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config)
         intNumRows->setValue(config.meshTransform()->size().height() - 1);
         chkShowControlPoints->setChecked(config.meshShowHandles());
         chkSymmetricalHandles->setChecked(config.meshSymmetricalHandles());
+        chkScaleHandles->setChecked(config.meshScaleHandles());
     }
 
     unblockUiSlots();
@@ -1300,3 +1302,11 @@ void KisToolTransformConfigWidget::slotMeshSymmetricalHandlesChanged()
     config->setMeshSymmetricalHandles(this->chkSymmetricalHandles->isChecked());
     notifyConfigChanged();
 }
+
+void KisToolTransformConfigWidget::slotMeshScaleHandlesChanged()
+{
+    if (m_uiSlotsBlocked) return;
+    ToolTransformArgs *config = m_transaction->currentConfig();
+    config->setMeshScaleHandles(this->chkScaleHandles->isChecked());
+    notifyConfigChanged();
+}
diff --git a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
index 72b5854b49..fec6ea2d6e 100644
--- a/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
+++ b/plugins/tools/tool_transform2/kis_tool_transform_config_widget.h
@@ -109,7 +109,7 @@ public Q_SLOTS:
     void slotMeshSizeChanged();
     void slotMeshShowHandlesChanged();
     void slotMeshSymmetricalHandlesChanged();
-
+    void slotMeshScaleHandlesChanged();
 private:
 
     void blockNotifications();
diff --git a/plugins/tools/tool_transform2/tool_transform_args.cc b/plugins/tools/tool_transform2/tool_transform_args.cc
index ea9e3742c1..2f8d3ecdae 100644
--- a/plugins/tools/tool_transform2/tool_transform_args.cc
+++ b/plugins/tools/tool_transform2/tool_transform_args.cc
@@ -27,6 +27,7 @@ ToolTransformArgs::ToolTransformArgs()
     m_transformAroundRotationCenter = configGroup.readEntry("transformAroundRotationCenter", "0").toInt();
     m_meshShowHandles = configGroup.readEntry("meshShowHandles", true);
     m_meshSymmetricalHandles = configGroup.readEntry("meshSymmetricalHandles", true);
+    m_meshScaleHandles = configGroup.readEntry("meshScaleHandles", false);
 }
 
 void ToolTransformArgs::setFilterId(const QString &id) {
@@ -80,10 +81,24 @@ void ToolTransformArgs::init(const ToolTransformArgs& args)
     m_meshTransform = args.m_meshTransform;
     m_meshShowHandles = args.m_meshShowHandles;
     m_meshSymmetricalHandles = args.m_meshSymmetricalHandles;
+    m_meshScaleHandles = args.m_meshScaleHandles;
 
     m_continuedTransformation.reset(args.m_continuedTransformation ? new ToolTransformArgs(*args.m_continuedTransformation) : 0);
 }
 
+bool ToolTransformArgs::meshScaleHandles() const
+{
+    return m_meshScaleHandles;
+}
+
+void ToolTransformArgs::setMeshScaleHandles(bool meshScaleHandles)
+{
+    m_meshScaleHandles = meshScaleHandles;
+
+    KConfigGroup configGroup =  KSharedConfig::openConfig()->group("KisToolTransform");
+    configGroup.writeEntry("meshScaleHandles", meshScaleHandles);
+}
+
 void ToolTransformArgs::clear()
 {
     m_origPoints.clear();
diff --git a/plugins/tools/tool_transform2/tool_transform_args.h b/plugins/tools/tool_transform2/tool_transform_args.h
index d62e73f0a8..aaebf4195a 100644
--- a/plugins/tools/tool_transform2/tool_transform_args.h
+++ b/plugins/tools/tool_transform2/tool_transform_args.h
@@ -307,6 +307,9 @@ public:
     bool meshSymmetricalHandles() const;
     void setMeshSymmetricalHandles(bool meshSymmetricalHandles);
 
+    bool meshScaleHandles() const;
+    void setMeshScaleHandles(bool meshScaleHandles);
+
 private:
     void clear();
     void init(const ToolTransformArgs& args);
@@ -353,6 +356,7 @@ private:
     KisBezierTransformMesh m_meshTransform;
     bool m_meshShowHandles = true;
     bool m_meshSymmetricalHandles = true;
+    bool m_meshScaleHandles = false;
 
     /**
      * When we continue a transformation, m_continuedTransformation
diff --git a/plugins/tools/tool_transform2/wdg_tool_transform.ui b/plugins/tools/tool_transform2/wdg_tool_transform.ui
index bc33787137..9d4c527b10 100644
--- a/plugins/tools/tool_transform2/wdg_tool_transform.ui
+++ b/plugins/tools/tool_transform2/wdg_tool_transform.ui
@@ -2197,6 +2197,13 @@
          </property>
         </widget>
        </item>
+       <item>
+        <widget class="QCheckBox" name="chkScaleHandles">
+         <property name="text">
+          <string>Scale handles proportionally</string>
+         </property>
+        </widget>
+       </item>
        <item>
         <spacer name="verticalSpacer_4">
          <property name="orientation">
@@ -2313,8 +2320,8 @@
  <resources/>
  <connections/>
  <buttongroups>
-  <buttongroup name="freeTransformRadioGroup"/>
   <buttongroup name="cageTransformButtonGroup"/>
   <buttongroup name="buttonGroup"/>
+  <buttongroup name="freeTransformRadioGroup"/>
  </buttongroups>
 </ui>
\ No newline at end of file


More information about the kimageshop mailing list