[graphics/krita/krita/4.4.2] /: Implement a new free patch deform algorithm
Dmitry Kazakov
null at kde.org
Mon Jan 11 17:14:01 GMT 2021
Git commit ccc0bc69013141526b3b0cf85773795df9bfebb3 by Dmitry Kazakov.
Committed on 11/01/2021 at 17:13.
Pushed by dkazakov into branch 'krita/4.4.2'.
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/ccc0bc69013141526b3b0cf85773795df9bfebb3
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