From kde-kimageshop Fri Sep 26 11:56:58 2014 From: Dmitry Kazakov Date: Fri, 26 Sep 2014 11:56:58 +0000 To: kde-kimageshop Subject: [calligra/krita-chili-kazakov] krita: Added ability to select multiple points in Warp and Cage trans Message-Id: X-MARC-Message: https://marc.info/?l=kde-kimageshop&m=141173263718027 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@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 +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 -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 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 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 &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 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 KisWarpTransformStrategy::Private::getSelectedPoints(QPointF *center, bool limitToSelectedOnly) const +{ + QVector &points = currentArgs.refTransformedPoints(); + + QRectF boundingRect; + QVector selectedPoints; + if (limitToSelectedOnly || pointsInAction.size() > 1) { + foreach (int index, pointsInAction) { + selectedPoints << &points[index]; + KisAlgebra2D::accumulateBounds(points[index], &boundingRect); + } + } else { + QVector::iterator it = points.begin(); + QVector::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 selectedPoints = m_d->getSelectedPoints(¢er); - if (dist > handleRadiusSq) { - m_d->pointWasDragged = true; + QPointF diff = pt - m_d->lastMousePos; + + QVector::iterator it = selectedPoints.begin(); + QVector::iterator end = selectedPoints.end(); + for (; it != end; ++it) { + **it += diff; + } + } else if (m_d->mode == Private::OUTSIDE_POLYGON) { + QPointF center; + QVector 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::iterator it = selectedPoints.begin(); + QVector::iterator end = selectedPoints.end(); + for (; it != end; ++it) { + **it = t.map(**it); + } } + + m_d->lastMousePos = pt; m_d->recalculateTransformations(); emit requestCanvasUpdate(); } _______________________________________________ Krita mailing list kimageshop@kde.org https://mail.kde.org/mailman/listinfo/kimageshop