Commit 552638b8 authored by Dmitry Kazakov's avatar Dmitry 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
parent 87fbfaf9
...@@ -77,4 +77,12 @@ QPointF KRITAIMAGE_EXPORT transformAsBase(const QPointF &pt, const QPointF &base ...@@ -77,4 +77,12 @@ QPointF KRITAIMAGE_EXPORT transformAsBase(const QPointF &pt, const QPointF &base
return result; 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;
}
} }
...@@ -129,6 +129,43 @@ void KRITAIMAGE_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int poly ...@@ -129,6 +129,43 @@ void KRITAIMAGE_EXPORT adjustIfOnPolygonBoundary(const QPolygonF &poly, int poly
**/ **/
QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2); 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 */ #endif /* __KIS_ALGEBRA_2D_H */
...@@ -521,7 +521,7 @@ QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset) ...@@ -521,7 +521,7 @@ QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset)
QRectF dstBounds; QRectF dstBounds;
foreach (const QPointF &pt, transformedPoints) { foreach (const QPointF &pt, transformedPoints) {
kisAccumulateBounds(pt, &dstBounds); KisAlgebra2D::accumulateBounds(pt, &dstBounds);
} }
const QRectF srcBounds(m_d->srcImageOffset, m_d->srcImage.size()); const QRectF srcBounds(m_d->srcImageOffset, m_d->srcImage.size());
......
...@@ -199,25 +199,5 @@ inline QRect kisEnsureInRect(QRect rc, const QRect &bounds) ...@@ -199,25 +199,5 @@ inline QRect kisEnsureInRect(QRect rc, const QRect &bounds)
return rc; 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_ #endif // KISGLOBAL_H_
...@@ -250,5 +250,25 @@ void KisCageTransformWorkerTest::testTransformAsBase() ...@@ -250,5 +250,25 @@ void KisCageTransformWorkerTest::testTransformAsBase()
QCOMPARE(result, QPointF(-2.0, 0.0)); 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) QTEST_KDEMAIN(KisCageTransformWorkerTest, GUI)
...@@ -36,6 +36,7 @@ private slots: ...@@ -36,6 +36,7 @@ private slots:
void testUnityGreenCoordinates(); void testUnityGreenCoordinates();
void testTransformAsBase(); void testTransformAsBase();
void testAngleBetweenVectors();
}; };
#endif /* __KIS_CAGE_TRANSFORM_WORKER_TEST_H */ #endif /* __KIS_CAGE_TRANSFORM_WORKER_TEST_H */
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "krita_utils.h" #include "krita_utils.h"
#include "kis_cursor.h" #include "kis_cursor.h"
#include "kis_transform_utils.h" #include "kis_transform_utils.h"
#include "kis_algebra_2d.h"
struct KisWarpTransformStrategy::Private struct KisWarpTransformStrategy::Private
...@@ -39,6 +40,7 @@ struct KisWarpTransformStrategy::Private ...@@ -39,6 +40,7 @@ struct KisWarpTransformStrategy::Private
converter(_converter), converter(_converter),
currentArgs(_currentArgs), currentArgs(_currentArgs),
transaction(_transaction), transaction(_transaction),
lastNumPoints(0),
drawConnectionLines(true), drawConnectionLines(true),
drawOrigPoints(true), drawOrigPoints(true),
drawTransfPoints(true), drawTransfPoints(true),
...@@ -68,9 +70,20 @@ struct KisWarpTransformStrategy::Private ...@@ -68,9 +70,20 @@ struct KisWarpTransformStrategy::Private
QImage transformedImage; QImage transformedImage;
bool cursorOverPoint;
int pointIndexUnderCursor; 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 drawConnectionLines;
bool drawOrigPoints; bool drawOrigPoints;
bool drawTransfPoints; bool drawTransfPoints;
...@@ -79,10 +92,13 @@ struct KisWarpTransformStrategy::Private ...@@ -79,10 +92,13 @@ struct KisWarpTransformStrategy::Private
QPointF pointPosOnClick; QPointF pointPosOnClick;
bool pointWasDragged; bool pointWasDragged;
QPointF lastMousePos;
void recalculateTransformations(); void recalculateTransformations();
inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization); inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
bool shouldCloseTheCage() const; bool shouldCloseTheCage() const;
QVector<QPointF*> getSelectedPoints(QPointF *center, bool limitToSelectedOnly = false) const;
}; };
KisWarpTransformStrategy::KisWarpTransformStrategy(const KisCoordinatesConverter *converter, KisWarpTransformStrategy::KisWarpTransformStrategy(const KisCoordinatesConverter *converter,
...@@ -102,26 +118,55 @@ void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos, boo ...@@ -102,26 +118,55 @@ void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos, boo
double handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter)); double handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
m_d->cursorOverPoint = false; bool cursorOverPoint = false;
m_d->pointIndexUnderCursor = -1; m_d->pointIndexUnderCursor = -1;
const QVector<QPointF> &points = m_d->currentArgs.transfPoints(); const QVector<QPointF> &points = m_d->currentArgs.transfPoints();
for (int i = 0; i < points.size(); ++i) { for (int i = 0; i < points.size(); ++i) {
if (kisSquareDistance(mousePos, points[i]) <= handleRadiusSq) { if (kisSquareDistance(mousePos, points[i]) <= handleRadiusSq) {
m_d->cursorOverPoint = true; cursorOverPoint = true;
m_d->pointIndexUnderCursor = i; m_d->pointIndexUnderCursor = i;
break; 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 QCursor KisWarpTransformStrategy::getCurrentCursor() const
{ {
if (m_d->cursorOverPoint) { QCursor cursor;
return KisCursor::pointingHandCursor();
} else { switch (m_d->mode) {
return KisCursor::arrowCursor(); 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, void KisWarpTransformStrategy::overrideDrawingItems(bool drawConnectionLines,
...@@ -217,6 +262,19 @@ void KisWarpTransformStrategy::paint(QPainter &gc) ...@@ -217,6 +262,19 @@ void KisWarpTransformStrategy::paint(QPainter &gc)
gc.setPen(mainPen); gc.setPen(mainPen);
gc.drawEllipse(handleRect1.translated(m_d->currentArgs.transfPoints()[i])); 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) { if (m_d->drawOrigPoints) {
...@@ -249,6 +307,10 @@ void KisWarpTransformStrategy::paint(QPainter &gc) ...@@ -249,6 +307,10 @@ void KisWarpTransformStrategy::paint(QPainter &gc)
void KisWarpTransformStrategy::externalConfigChanged() void KisWarpTransformStrategy::externalConfigChanged()
{ {
if (m_d->lastNumPoints != m_d->currentArgs.transfPoints().size()) {
m_d->pointsInAction.clear();
}
m_d->recalculateTransformations(); m_d->recalculateTransformations();
} }
...@@ -257,8 +319,13 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt) ...@@ -257,8 +319,13 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
const bool isEditingPoints = m_d->transaction.editWarpPoints(); const bool isEditingPoints = m_d->transaction.editWarpPoints();
bool retval = false; 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; retval = true;
} else if (isEditingPoints) { } else if (isEditingPoints) {
QPointF newPos = m_d->clipOriginalPointsPosition ? QPointF newPos = m_d->clipOriginalPointsPosition ?
KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) : KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
...@@ -267,7 +334,7 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt) ...@@ -267,7 +334,7 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
m_d->currentArgs.refOriginalPoints().append(newPos); m_d->currentArgs.refOriginalPoints().append(newPos);
m_d->currentArgs.refTransformedPoints().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->pointIndexUnderCursor = m_d->currentArgs.origPoints().size() - 1;
m_d->recalculateTransformations(); m_d->recalculateTransformations();
...@@ -276,43 +343,118 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt) ...@@ -276,43 +343,118 @@ bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
retval = true; retval = true;
} }
if (m_d->cursorOverPoint) { if (m_d->mode == Private::OVER_POINT) {
m_d->pointPosOnClick = m_d->pointPosOnClick =
m_d->currentArgs.transfPoints()[m_d->pointIndexUnderCursor]; m_d->currentArgs.transfPoints()[m_d->pointIndexUnderCursor];
m_d->pointWasDragged = false; 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; 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) void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt, bool specialModifierActve)
{ {
Q_UNUSED(specialModifierActve); Q_UNUSED(specialModifierActve);
// toplevel code switches to HOVER mode if nothing is selected // 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)); if (dist > handleRadiusSq) {
qreal dist = m_d->pointWasDragged = true;
kisSquareDistance( }
m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor), } else if (m_d->mode == Private::INSIDE_POLYGON) {
m_d->pointPosOnClick); QPointF center;
QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
if (dist > handleRadiusSq) { QPointF diff = pt - m_d->lastMousePos;
m_d->pointWasDragged = true;
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(); m_d->recalculateTransformations();
emit requestCanvasUpdate(); emit requestCanvasUpdate();
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment