[calligra/krita-chili-kazakov] krita: Added ability to select multiple points in Warp and Cage transforms

Dmitry Kazakov dimula73 at gmail.com
Fri Sep 26 11:56:58 UTC 2014


Git commit 2274f351e65d2134fda004a74da3042615fecfe8 by Dmitry Kazakov.
Committed on 26/09/2014 at 11:40.
Pushed by dkazakov into branch 'krita-chili-kazakov'.

Added ability to select multiple points in Warp and Cage transforms

Press Ctrl to select multiple points.

Move --- drag from the inside of the polygon
Rotate --- drag from the outside of the polygon

CCMAIL:kimageshop at kde.org

M  +8    -0    krita/image/kis_algebra_2d.cpp
M  +37   -0    krita/image/kis_algebra_2d.h
M  +1    -1    krita/image/kis_cage_transform_worker.cpp
M  +0    -20   krita/image/kis_global.h
M  +20   -0    krita/image/tests/kis_cage_transform_worker_test.cpp
M  +1    -0    krita/image/tests/kis_cage_transform_worker_test.h
M  +169  -27   krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp

http://commits.kde.org/calligra/2274f351e65d2134fda004a74da3042615fecfe8

diff --git a/krita/image/kis_algebra_2d.cpp b/krita/image/kis_algebra_2d.cpp
index d123e9a..121969b 100644
--- a/krita/image/kis_algebra_2d.cpp
+++ b/krita/image/kis_algebra_2d.cpp
@@ -77,4 +77,12 @@ QPointF KRITAIMAGE_EXPORT transformAsBase(const QPointF &pt, const QPointF &base
     return result;
 }
 
+qreal KRITAIMAGE_EXPORT angleBetweenVectors(const QPointF &v1, const QPointF &v2)
+{
+    qreal a1 = std::atan2(v1.y(), v1.x());
+    qreal a2 = std::atan2(v2.y(), v2.x());
+
+    return a2 - a1;
+}
+
 }
diff --git a/krita/image/kis_algebra_2d.h b/krita/image/kis_algebra_2d.h
index e8c95e2..e208076 100644
--- a/krita/image/kis_algebra_2d.h
+++ b/krita/image/kis_algebra_2d.h
@@ -129,6 +129,43 @@ void KRITAIMAGE_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int poly
  **/
 QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2);
 
+qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2);
+
+namespace Private {
+    inline void resetEmptyRectangle(const QPoint &pt, QRect *rc) {
+        *rc = QRect(pt, QSize(1, 1));
+    }
+
+    inline void resetEmptyRectangle(const QPointF &pt, QRectF *rc) {
+        static qreal eps = 1e-10;
+        *rc = QRectF(pt, QSizeF(eps, eps));
+    }
+}
+
+template <class Point, class Rect>
+inline void accumulateBounds(const Point &pt, Rect *bounds)
+{
+    if (bounds->isEmpty()) {
+        Private::resetEmptyRectangle(pt, bounds);
+    }
+
+    if (pt.x() > bounds->right()) {
+        bounds->setRight(pt.x());
+    }
+
+    if (pt.x() < bounds->left()) {
+        bounds->setLeft(pt.x());
+    }
+
+    if (pt.y() > bounds->bottom()) {
+        bounds->setBottom(pt.y());
+    }
+
+    if (pt.y() < bounds->top()) {
+        bounds->setTop(pt.y());
+    }
+}
+
 }
 
 #endif /* __KIS_ALGEBRA_2D_H */
diff --git a/krita/image/kis_cage_transform_worker.cpp b/krita/image/kis_cage_transform_worker.cpp
index d533835..680df15 100644
--- a/krita/image/kis_cage_transform_worker.cpp
+++ b/krita/image/kis_cage_transform_worker.cpp
@@ -521,7 +521,7 @@ QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset)
 
     QRectF dstBounds;
     foreach (const QPointF &pt, transformedPoints) {
-        kisAccumulateBounds(pt, &dstBounds);
+        KisAlgebra2D::accumulateBounds(pt, &dstBounds);
     }
 
     const QRectF srcBounds(m_d->srcImageOffset, m_d->srcImage.size());
diff --git a/krita/image/kis_global.h b/krita/image/kis_global.h
index 0dc3aae..7060919 100644
--- a/krita/image/kis_global.h
+++ b/krita/image/kis_global.h
@@ -199,25 +199,5 @@ inline QRect kisEnsureInRect(QRect rc, const QRect &bounds)
     return rc;
 }
 
-template <class Point, class Rect>
-inline void kisAccumulateBounds(const Point &pt, Rect *bounds)
-{
-    if (pt.x() > bounds->right()) {
-        bounds->setRight(pt.x());
-    }
-
-    if (pt.x() < bounds->left()) {
-        bounds->setLeft(pt.x());
-    }
-
-    if (pt.y() > bounds->bottom()) {
-        bounds->setBottom(pt.y());
-    }
-
-    if (pt.y() < bounds->top()) {
-        bounds->setTop(pt.y());
-    }
-}
-
 #endif // KISGLOBAL_H_
 
diff --git a/krita/image/tests/kis_cage_transform_worker_test.cpp b/krita/image/tests/kis_cage_transform_worker_test.cpp
index 9892400..b3ffeae 100644
--- a/krita/image/tests/kis_cage_transform_worker_test.cpp
+++ b/krita/image/tests/kis_cage_transform_worker_test.cpp
@@ -250,5 +250,25 @@ void KisCageTransformWorkerTest::testTransformAsBase()
     QCOMPARE(result, QPointF(-2.0, 0.0));
 }
 
+void KisCageTransformWorkerTest::testAngleBetweenVectors()
+{
+    QPointF b1(1.0, 0.0);
+    QPointF b2(2.0, 0.0);
+    qreal result;
+
+    b1 = QPointF(1.0, 0.0);
+    b2 = QPointF(0.0, 1.0);
+    result = KisAlgebra2D::angleBetweenVectors(b1, b2);
+    QCOMPARE(result, M_PI_2);
+
+    b1 = QPointF(1.0, 0.0);
+    b2 = QPointF(std::sqrt(0.5), std::sqrt(0.5));
+    result = KisAlgebra2D::angleBetweenVectors(b1, b2);
+    QCOMPARE(result, M_PI / 4);
+
+    QTransform t;
+    t.rotateRadians(M_PI / 4);
+    QCOMPARE(t.map(b1), b2);
+}
 
 QTEST_KDEMAIN(KisCageTransformWorkerTest, GUI)
diff --git a/krita/image/tests/kis_cage_transform_worker_test.h b/krita/image/tests/kis_cage_transform_worker_test.h
index fdaebf9..07fd6d9 100644
--- a/krita/image/tests/kis_cage_transform_worker_test.h
+++ b/krita/image/tests/kis_cage_transform_worker_test.h
@@ -36,6 +36,7 @@ private slots:
     void testUnityGreenCoordinates();
 
     void testTransformAsBase();
+    void testAngleBetweenVectors();
 };
 
 #endif /* __KIS_CAGE_TRANSFORM_WORKER_TEST_H */
diff --git a/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp b/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
index 605e336..d01c6c3 100644
--- a/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_warp_transform_strategy.cpp
@@ -27,6 +27,7 @@
 #include "krita_utils.h"
 #include "kis_cursor.h"
 #include "kis_transform_utils.h"
+#include "kis_algebra_2d.h"
 
 
 struct KisWarpTransformStrategy::Private
@@ -39,6 +40,7 @@ struct KisWarpTransformStrategy::Private
           converter(_converter),
           currentArgs(_currentArgs),
           transaction(_transaction),
+          lastNumPoints(0),
           drawConnectionLines(true),
           drawOrigPoints(true),
           drawTransfPoints(true),
@@ -68,9 +70,20 @@ struct KisWarpTransformStrategy::Private
 
     QImage transformedImage;
 
-    bool cursorOverPoint;
     int pointIndexUnderCursor;
 
+    enum Mode {
+        OVER_POINT = 0,
+        MULTIPLE_POINT_SELECTION,
+        INSIDE_POLYGON,
+        OUTSIDE_POLYGON,
+        NOTHING
+    };
+    Mode mode;
+
+    QVector<int> pointsInAction;
+    int lastNumPoints;
+
     bool drawConnectionLines;
     bool drawOrigPoints;
     bool drawTransfPoints;
@@ -79,10 +92,13 @@ struct KisWarpTransformStrategy::Private
     QPointF pointPosOnClick;
     bool pointWasDragged;
 
+    QPointF lastMousePos;
+
     void recalculateTransformations();
     inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
 
     bool shouldCloseTheCage() const;
+    QVector<QPointF*> getSelectedPoints(QPointF *center, bool limitToSelectedOnly = false) const;
 };
 
 KisWarpTransformStrategy::KisWarpTransformStrategy(const KisCoordinatesConverter *converter,
@@ -102,26 +118,55 @@ void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos, boo
 
     double handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
 
-    m_d->cursorOverPoint = false;
+    bool cursorOverPoint = false;
     m_d->pointIndexUnderCursor = -1;
 
     const QVector<QPointF> &points = m_d->currentArgs.transfPoints();
     for (int i = 0; i < points.size(); ++i) {
         if (kisSquareDistance(mousePos, points[i]) <= handleRadiusSq) {
-            m_d->cursorOverPoint = true;
+            cursorOverPoint = true;
             m_d->pointIndexUnderCursor = i;
             break;
         }
     }
+
+    if (cursorOverPoint) {
+        m_d->mode = perspectiveModifierActive &&
+            !m_d->transaction.editWarpPoints() ?
+            Private::MULTIPLE_POINT_SELECTION : Private::OVER_POINT;
+
+    } else if (!m_d->transaction.editWarpPoints()) {
+        QPolygonF polygon(m_d->currentArgs.transfPoints());
+        bool insidePolygon = polygon.boundingRect().contains(mousePos);
+        m_d->mode = insidePolygon ? Private::INSIDE_POLYGON : Private::OUTSIDE_POLYGON;
+    } else {
+        m_d->mode = Private::NOTHING;
+    }
 }
 
 QCursor KisWarpTransformStrategy::getCurrentCursor() const
 {
-    if (m_d->cursorOverPoint) {
-        return KisCursor::pointingHandCursor();
-    } else {
-        return KisCursor::arrowCursor();
+    QCursor cursor;
+
+    switch (m_d->mode) {
+    case Private::OVER_POINT:
+        cursor = KisCursor::pointingHandCursor();
+        break;
+    case Private::MULTIPLE_POINT_SELECTION:
+        cursor = KisCursor::crossCursor();
+        break;
+    case Private::INSIDE_POLYGON:
+        cursor = KisCursor::moveCursor();
+        break;
+    case Private::OUTSIDE_POLYGON:
+        cursor = KisCursor::rotateCursor();
+        break;
+    case Private::NOTHING:
+        cursor = KisCursor::arrowCursor();
+        break;
     }
+
+    return cursor;
 }
 
 void KisWarpTransformStrategy::overrideDrawingItems(bool drawConnectionLines,
@@ -217,6 +262,19 @@ void KisWarpTransformStrategy::paint(QPainter &gc)
                 gc.setPen(mainPen);
                 gc.drawEllipse(handleRect1.translated(m_d->currentArgs.transfPoints()[i]));
             }
+
+            QPointF center;
+            QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center, true);
+
+            QBrush selectionBrush = selectedPoints.size() > 1 ? Qt::red : Qt::black;
+
+            QBrush oldBrush = gc.brush();
+            gc.setBrush(selectionBrush);
+            foreach (const QPointF *pt, selectedPoints) {
+                gc.drawEllipse(handleRect1.translated(*pt));
+            }
+            gc.setBrush(oldBrush);
+
         }
 
         if (m_d->drawOrigPoints) {
@@ -249,6 +307,10 @@ void KisWarpTransformStrategy::paint(QPainter &gc)
 
 void KisWarpTransformStrategy::externalConfigChanged()
 {
+    if (m_d->lastNumPoints != m_d->currentArgs.transfPoints().size()) {
+        m_d->pointsInAction.clear();
+    }
+
     m_d->recalculateTransformations();
 }
 
@@ -257,8 +319,13 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
     const bool isEditingPoints = m_d->transaction.editWarpPoints();
     bool retval = false;
 
-    if (m_d->cursorOverPoint) {
+    if (m_d->mode == Private::OVER_POINT ||
+        m_d->mode == Private::MULTIPLE_POINT_SELECTION ||
+        m_d->mode == Private::INSIDE_POLYGON ||
+        m_d->mode == Private::OUTSIDE_POLYGON) {
+
         retval = true;
+
     } else if (isEditingPoints) {
         QPointF newPos = m_d->clipOriginalPointsPosition ?
             KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
@@ -267,7 +334,7 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
         m_d->currentArgs.refOriginalPoints().append(newPos);
         m_d->currentArgs.refTransformedPoints().append(newPos);
 
-        m_d->cursorOverPoint = true;
+        m_d->mode = Private::OVER_POINT;
         m_d->pointIndexUnderCursor = m_d->currentArgs.origPoints().size() - 1;
 
         m_d->recalculateTransformations();
@@ -276,43 +343,118 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
         retval = true;
     }
 
-    if (m_d->cursorOverPoint) {
+    if (m_d->mode == Private::OVER_POINT) {
         m_d->pointPosOnClick =
             m_d->currentArgs.transfPoints()[m_d->pointIndexUnderCursor];
         m_d->pointWasDragged = false;
+
+        m_d->pointsInAction.clear();
+        m_d->pointsInAction << m_d->pointIndexUnderCursor;
+        m_d->lastNumPoints = m_d->currentArgs.transfPoints().size();
+    } else if (m_d->mode == Private::MULTIPLE_POINT_SELECTION) {
+        m_d->pointsInAction << m_d->pointIndexUnderCursor;
+        m_d->lastNumPoints = m_d->currentArgs.transfPoints().size();
     }
 
+    m_d->lastMousePos = pt;
     return retval;
 }
 
+QVector<QPointF*> KisWarpTransformStrategy::Private::getSelectedPoints(QPointF *center, bool limitToSelectedOnly) const
+{
+    QVector<QPointF> &points = currentArgs.refTransformedPoints();
+
+    QRectF boundingRect;
+    QVector<QPointF*> selectedPoints;
+    if (limitToSelectedOnly || pointsInAction.size() > 1) {
+        foreach (int index, pointsInAction) {
+            selectedPoints << &points[index];
+            KisAlgebra2D::accumulateBounds(points[index], &boundingRect);
+        }
+    } else {
+        QVector<QPointF>::iterator it = points.begin();
+        QVector<QPointF>::iterator end = points.end();
+        for (; it != end; ++it) {
+            selectedPoints << &(*it);
+            KisAlgebra2D::accumulateBounds(*it, &boundingRect);
+        }
+    }
+
+    *center = boundingRect.center();
+    return selectedPoints;
+}
+
 void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt, bool specialModifierActve)
 {
     Q_UNUSED(specialModifierActve);
 
     // toplevel code switches to HOVER mode if nothing is selected
-    KIS_ASSERT_RECOVER_RETURN(m_d->pointIndexUnderCursor >= 0);
+    KIS_ASSERT_RECOVER_RETURN(m_d->mode == Private::INSIDE_POLYGON ||
+                              m_d->mode == Private::OUTSIDE_POLYGON||
+                              (m_d->mode == Private::OVER_POINT &&
+                               m_d->pointIndexUnderCursor >= 0 &&
+                               m_d->pointsInAction.size() == 1) ||
+                              (m_d->mode == Private::MULTIPLE_POINT_SELECTION &&
+                               m_d->pointIndexUnderCursor >= 0 &&
+                               m_d->pointsInAction.size() >= 1));
+
+    if (m_d->mode == Private::OVER_POINT) {
+        if (m_d->transaction.editWarpPoints()) {
+            QPointF newPos = m_d->clipOriginalPointsPosition ?
+                KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
+                pt;
+            m_d->currentArgs.origPoint(m_d->pointIndexUnderCursor) = newPos;
+            m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = newPos;
+        } else {
+            m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = pt;
+        }
 
-    if (m_d->transaction.editWarpPoints()) {
-        QPointF newPos = m_d->clipOriginalPointsPosition ?
-            KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
-            pt;
-        m_d->currentArgs.origPoint(m_d->pointIndexUnderCursor) = newPos;
-        m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = newPos;
-    } else {
-        m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = pt;
-    }
 
+        const qreal handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
+        qreal dist =
+            kisSquareDistance(
+                m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor),
+                m_d->pointPosOnClick);
 
-    const qreal handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
-    qreal dist =
-        kisSquareDistance(
-            m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor),
-            m_d->pointPosOnClick);
+        if (dist > handleRadiusSq) {
+            m_d->pointWasDragged = true;
+        }
+    } else if (m_d->mode == Private::INSIDE_POLYGON) {
+        QPointF center;
+        QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
 
-    if (dist > handleRadiusSq) {
-        m_d->pointWasDragged = true;
+        QPointF diff = pt - m_d->lastMousePos;
+
+        QVector<QPointF*>::iterator it = selectedPoints.begin();
+        QVector<QPointF*>::iterator end = selectedPoints.end();
+        for (; it != end; ++it) {
+            **it += diff;
+        }
+    } else if (m_d->mode == Private::OUTSIDE_POLYGON) {
+        QPointF center;
+        QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
+
+        QPointF oldDirection = m_d->lastMousePos - center;
+        QPointF newDirection = pt - center;
+
+        qreal rotateAngle = KisAlgebra2D::angleBetweenVectors(oldDirection, newDirection);
+        QTransform R;
+        R.rotateRadians(rotateAngle);
+
+        QTransform t =
+            QTransform::fromTranslate(-center.x(), -center.y()) *
+            R *
+            QTransform::fromTranslate(center.x(), center.y());
+
+        QVector<QPointF*>::iterator it = selectedPoints.begin();
+        QVector<QPointF*>::iterator end = selectedPoints.end();
+        for (; it != end; ++it) {
+            **it = t.map(**it);
+        }
     }
 
+
+    m_d->lastMousePos = pt;
     m_d->recalculateTransformations();
     emit requestCanvasUpdate();
 }


More information about the kimageshop mailing list