[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(¢er, 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(¢er);
- 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(¢er);
+
+ 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