Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_warp_transform_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <algorithm>
10
11#include <QPointF>
12#include <QPainter>
13#include <QPainterPath>
14
16#include "tool_transform_args.h"
18#include "kis_painting_tweaks.h"
19#include "kis_cursor.h"
20#include "kis_transform_utils.h"
21#include "kis_algebra_2d.h"
24
25
26
28{
30 const KisCoordinatesConverter *_converter,
31 ToolTransformArgs &_currentArgs,
33 : q(_q),
34 converter(_converter),
35 currentArgs(_currentArgs),
36 transaction(_transaction),
38 {
39 }
40
42
44
46
51
54
55 QTransform handlesTransform;
56
58
60
62
72
75
76 bool drawConnectionLines {false}; // useful while developing
77 bool drawOrigPoints {false};
78 bool drawTransfPoints {true};
82 bool pointWasDragged {false};
83
84 QPointF lastMousePos;
85
86 // cage transform also uses this logic. This helps this class know what transform type we are using
89
91 inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
92
93 bool shouldCloseTheCage() const;
94 QVector<QPointF*> getSelectedPoints(QPointF *center, bool limitToSelectedOnly = false) const;
95};
96
98 KoSnapGuide *snapGuide,
99 ToolTransformArgs &currentArgs,
101 : KisSimplifiedActionPolicyStrategy(converter, snapGuide),
102 m_d(new Private(this, converter, currentArgs, transaction))
103{
104 connect(&m_d->recalculateSignalCompressor, SIGNAL(timeout()),
105 SLOT(recalculateTransformations()));
106}
107
111
112void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive)
113{
114 Q_UNUSED(shiftModifierActive);
115 Q_UNUSED(altModifierActive);
116
117 const double handleRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
118
119 bool cursorOverPoint = false;
120 m_d->pointIndexUnderCursor = -1;
121
123 handleChooser(mousePos, Private::NOTHING);
124
125 const QVector<QPointF> &points = m_d->currentArgs.transfPoints();
126 for (int i = 0; i < points.size(); ++i) {
127 if (handleChooser.addFunction(points[i],
128 handleRadius, Private::NOTHING)) {
129
130 cursorOverPoint = true;
131 m_d->pointIndexUnderCursor = i;
132 }
133 }
134
135 if (cursorOverPoint) {
136 m_d->mode = perspectiveModifierActive &&
137 !m_d->currentArgs.isEditingTransformPoints() ?
139
140 } else if (!m_d->currentArgs.isEditingTransformPoints()) {
141 QPolygonF polygon(m_d->currentArgs.transfPoints());
142 bool insidePolygon = polygon.boundingRect().contains(mousePos);
143 m_d->mode = insidePolygon ? Private::MOVE_MODE :
144 !perspectiveModifierActive ? Private::ROTATE_MODE :
146 } else {
147 m_d->mode = Private::NOTHING;
148 }
149}
150
152{
153 QCursor cursor;
154
155 switch (m_d->mode) {
158 break;
160 cursor = KisCursor::crossCursor();
161 break;
163 cursor = KisCursor::moveCursor();
164 break;
166 cursor = KisCursor::rotateCursor();
167 break;
169 cursor = KisCursor::sizeVerCursor();
170 break;
171 case Private::NOTHING:
172 cursor = KisCursor::arrowCursor();
173 break;
174 }
175
176 return cursor;
177}
178
180 bool drawOrigPoints,
181 bool drawTransfPoints)
182{
183 m_d->drawConnectionLines = drawConnectionLines;
184 m_d->drawOrigPoints = drawOrigPoints;
185 m_d->drawTransfPoints = drawTransfPoints;
186}
187
189{
190 m_d->closeOnStartPointClick = value;
191}
192
194{
195 m_d->clipOriginalPointsPosition = value;
196}
197
199 m_d->transformType = type;
200}
201
203 const QVector<QPointF> &origPoints,
204 const QVector<QPointF> &transfPoints,
205 bool isEditingPoints)
206{
207 Q_UNUSED(isEditingPoints);
208
209 QPen antsPen;
210 QPen outlinePen;
211
212 KisPaintingTweaks::initAntsPen(&antsPen, &outlinePen);
213 antsPen.setWidth(decorationThickness());
214 outlinePen.setWidth(decorationThickness());
215
216 const int numPoints = origPoints.size();
217
218 for (int i = 0; i < numPoints; ++i) {
219 gc.setPen(outlinePen);
220 gc.drawLine(transfPoints[i], origPoints[i]);
221 gc.setPen(antsPen);
222 gc.drawLine(transfPoints[i], origPoints[i]);
223 }
224}
225
227{
228 // Draw preview image
229
230 gc.save();
231
232 gc.setOpacity(m_d->transaction.basePreviewOpacity());
233 gc.setTransform(m_d->paintingTransform, true);
234 gc.drawImage(m_d->paintingOffset, m_d->transformedImage);
235
236 gc.restore();
237
238
239 gc.save();
240 gc.setTransform(m_d->handlesTransform, true);
241
242 if (m_d->drawConnectionLines) {
243 gc.setOpacity(0.5);
244
246 m_d->currentArgs.origPoints(),
247 m_d->currentArgs.transfPoints(),
248 m_d->currentArgs.isEditingTransformPoints());
249 }
250
251
252 QPen mainPen(Qt::black);
253 mainPen.setCosmetic(true);
254 mainPen.setWidth(decorationThickness());
255 QPen outlinePen(Qt::white);
256 outlinePen.setCosmetic(true);
257 outlinePen.setWidth(decorationThickness());
258
259 // draw handles
260 {
261 const int numPoints = m_d->currentArgs.origPoints().size();
262
263
264
265 qreal handlesExtraScale = KisTransformUtils::scaleFromAffineMatrix(m_d->handlesTransform);
266
267 qreal dstIn = 8 / handlesExtraScale;
268 qreal dstOut = 10 / handlesExtraScale;
269 qreal srcIn = 6 / handlesExtraScale;
270 qreal srcOut = 6 / handlesExtraScale;
271
272 QRectF handleRect1(-0.5 * dstIn, -0.5 * dstIn, dstIn, dstIn);
273 QRectF handleRect2(-0.5 * dstOut, -0.5 * dstOut, dstOut, dstOut);
274
275 if (m_d->drawTransfPoints) {
276 gc.setOpacity(1.0);
277
278 for (int i = 0; i < numPoints; ++i) {
279 gc.setPen(outlinePen);
280 gc.drawEllipse(handleRect2.translated(m_d->currentArgs.transfPoints()[i]));
281 gc.setPen(mainPen);
282 gc.drawEllipse(handleRect1.translated(m_d->currentArgs.transfPoints()[i]));
283 }
284
285 QPointF center;
286 QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center, true);
287
288 QBrush selectionBrush = selectedPoints.size() > 1 ? Qt::red : Qt::black;
289
290 QBrush oldBrush = gc.brush();
291 gc.setBrush(selectionBrush);
292 Q_FOREACH (const QPointF *pt, selectedPoints) {
293 gc.drawEllipse(handleRect1.translated(*pt));
294 }
295 gc.setBrush(oldBrush);
296
297 }
298
299 if (m_d->drawOrigPoints) {
300 QPainterPath inLine;
301 inLine.moveTo(-0.5 * srcIn, 0);
302 inLine.lineTo( 0.5 * srcIn, 0);
303 inLine.moveTo( 0, -0.5 * srcIn);
304 inLine.lineTo( 0, 0.5 * srcIn);
305
306 QPainterPath outLine;
307 outLine.moveTo(-0.5 * srcOut, -0.5 * srcOut);
308 outLine.lineTo( 0.5 * srcOut, -0.5 * srcOut);
309 outLine.lineTo( 0.5 * srcOut, 0.5 * srcOut);
310 outLine.lineTo(-0.5 * srcOut, 0.5 * srcOut);
311 outLine.lineTo(-0.5 * srcOut, -0.5 * srcOut);
312
313 gc.setOpacity(0.5);
314
315 for (int i = 0; i < numPoints; ++i) {
316 gc.setPen(outlinePen);
317 gc.drawPath(outLine.translated(m_d->currentArgs.origPoints()[i]));
318 gc.setPen(mainPen);
319 gc.drawPath(inLine.translated(m_d->currentArgs.origPoints()[i]));
320 }
321 }
322
323 }
324
325 // draw grid lines only if we are using the GRID mode. Also only use this logic for warp, not cage transforms
326 if (m_d->currentArgs.warpCalculation() == KisWarpTransformWorker::WarpCalculation::GRID &&
327 m_d->transformType == TransformType::WARP_TRANSFORM ) {
328
329 // see how many rows we have. we are only going to do lines up to 6 divisions/
330 // it is almost impossible to use with 6 even.
331 const int numPoints = m_d->currentArgs.origPoints().size();
332
333 // grid is always square, so get the square root to find # of rows
334 int rowsInWarp = sqrt(m_d->currentArgs.origPoints().size());
335
336
337 KisHandlePainterHelper handlePainter(&gc, 0.0, decorationThickness());
339
340 // draw horizontal lines
341 for (int i = 0; i < numPoints; i++) {
342 if (i != 0 && i % rowsInWarp == rowsInWarp -1) {
343 // skip line if it is the last in the row
344 } else {
345 handlePainter.drawConnectionLine(m_d->currentArgs.transfPoints()[i], m_d->currentArgs.transfPoints()[i+1] );
346 }
347 }
348
349 // draw vertical lines
350 for (int i = 0; i < numPoints; i++) {
351
352 if ( (numPoints - i - 1) < rowsInWarp ) {
353 // last row doesn't need to draw vertical lines
354 } else {
355 handlePainter.drawConnectionLine(m_d->currentArgs.transfPoints()[i], m_d->currentArgs.transfPoints()[i+rowsInWarp] );
356 }
357 }
358
359 } // end if statement
360
361 gc.restore();
362}
363
365{
366 if (m_d->lastNumPoints != m_d->currentArgs.transfPoints().size()) {
367 m_d->pointsInAction.clear();
368 }
369
370 m_d->recalculateTransformations();
371}
372
374{
375 const bool isEditingPoints = m_d->currentArgs.isEditingTransformPoints();
376 bool retval = false;
377
378 if (m_d->mode == Private::OVER_POINT ||
380 m_d->mode == Private::MOVE_MODE ||
381 m_d->mode == Private::ROTATE_MODE ||
382 m_d->mode == Private::SCALE_MODE) {
383
384 retval = true;
385
386 } else if (isEditingPoints) {
387 QPointF newPos = m_d->clipOriginalPointsPosition ?
388 KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
389 pt;
390
391 m_d->currentArgs.refOriginalPoints().append(newPos);
392 m_d->currentArgs.refTransformedPoints().append(newPos);
393
394 m_d->mode = Private::OVER_POINT;
395 m_d->pointIndexUnderCursor = m_d->currentArgs.origPoints().size() - 1;
396
397 m_d->recalculateSignalCompressor.start();
398
399 retval = true;
400 }
401
402 if (m_d->mode == Private::OVER_POINT) {
403 m_d->pointPosOnClick =
404 m_d->currentArgs.transfPoints()[m_d->pointIndexUnderCursor];
405 m_d->pointWasDragged = false;
406
407 m_d->pointsInAction.clear();
408 m_d->pointsInAction << m_d->pointIndexUnderCursor;
409 m_d->lastNumPoints = m_d->currentArgs.transfPoints().size();
410 } else if (m_d->mode == Private::MULTIPLE_POINT_SELECTION) {
412 std::find(m_d->pointsInAction.begin(),
413 m_d->pointsInAction.end(),
414 m_d->pointIndexUnderCursor);
415
416 if (it != m_d->pointsInAction.end()) {
417 m_d->pointsInAction.erase(it);
418 } else {
419 m_d->pointsInAction << m_d->pointIndexUnderCursor;
420 }
421
422 m_d->lastNumPoints = m_d->currentArgs.transfPoints().size();
423 }
424
425 m_d->lastMousePos = pt;
426 return retval;
427}
428
429QVector<QPointF*> KisWarpTransformStrategy::Private::getSelectedPoints(QPointF *center, bool limitToSelectedOnly) const
430{
432
433 QRectF boundingRect;
434 QVector<QPointF*> selectedPoints;
435 if (limitToSelectedOnly || pointsInAction.size() > 1) {
436 Q_FOREACH (int index, pointsInAction) {
437 selectedPoints << &points[index];
438 KisAlgebra2D::accumulateBounds(points[index], &boundingRect);
439 }
440 } else {
441 QVector<QPointF>::iterator it = points.begin();
442 QVector<QPointF>::iterator end = points.end();
443 for (; it != end; ++it) {
444 selectedPoints << &(*it);
445 KisAlgebra2D::accumulateBounds(*it, &boundingRect);
446 }
447 }
448
449 *center = boundingRect.center();
450 return selectedPoints;
451}
452
453void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shiftModifierActive, bool altModifierActive)
454{
455 Q_UNUSED(shiftModifierActive);
456 Q_UNUSED(altModifierActive);
457
458 // toplevel code switches to HOVER mode if nothing is selected
460 m_d->mode == Private::ROTATE_MODE ||
461 m_d->mode == Private::SCALE_MODE ||
462 (m_d->mode == Private::OVER_POINT &&
463 m_d->pointIndexUnderCursor >= 0 &&
464 m_d->pointsInAction.size() == 1) ||
466 m_d->pointIndexUnderCursor >= 0));
467
468 if (m_d->mode == Private::OVER_POINT) {
469 if (m_d->currentArgs.isEditingTransformPoints()) {
470 QPointF newPos = m_d->clipOriginalPointsPosition ?
471 KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect()) :
472 pt;
473 m_d->currentArgs.origPoint(m_d->pointIndexUnderCursor) = newPos;
474 m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = newPos;
475 } else {
476 m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = pt;
477 }
478
479
480 const qreal handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
481 qreal dist =
483 m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor),
484 m_d->pointPosOnClick);
485
486 if (dist > handleRadiusSq) {
487 m_d->pointWasDragged = true;
488 }
489 } else if (m_d->mode == Private::MOVE_MODE) {
490 QPointF center;
491 QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
492
493 QPointF diff = pt - m_d->lastMousePos;
494
495 QVector<QPointF*>::iterator it = selectedPoints.begin();
496 QVector<QPointF*>::iterator end = selectedPoints.end();
497 for (; it != end; ++it) {
498 **it += diff;
499 }
500 } else if (m_d->mode == Private::ROTATE_MODE) {
501 QPointF center;
502 QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
503
504 QPointF oldDirection = m_d->lastMousePos - center;
505 QPointF newDirection = pt - center;
506
507 qreal rotateAngle = KisAlgebra2D::angleBetweenVectors(oldDirection, newDirection);
508 QTransform R;
509 R.rotateRadians(rotateAngle);
510
511 QTransform t =
512 QTransform::fromTranslate(-center.x(), -center.y()) *
513 R *
514 QTransform::fromTranslate(center.x(), center.y());
515
516 QVector<QPointF*>::iterator it = selectedPoints.begin();
517 QVector<QPointF*>::iterator end = selectedPoints.end();
518 for (; it != end; ++it) {
519 **it = t.map(**it);
520 }
521 } else if (m_d->mode == Private::SCALE_MODE) {
522 QPointF center;
523 QVector<QPointF*> selectedPoints = m_d->getSelectedPoints(&center);
524
525 QPolygonF polygon(m_d->currentArgs.origPoints());
526 QSizeF maxSize = polygon.boundingRect().size();
527 qreal maxDimension = qMax(maxSize.width(), maxSize.height());
528
529 qreal scale = 1.0 - (pt - m_d->lastMousePos).y() / maxDimension;
530
531 QTransform t =
532 QTransform::fromTranslate(-center.x(), -center.y()) *
533 QTransform::fromScale(scale, scale) *
534 QTransform::fromTranslate(center.x(), center.y());
535
536 QVector<QPointF*>::iterator it = selectedPoints.begin();
537 QVector<QPointF*>::iterator end = selectedPoints.end();
538 for (; it != end; ++it) {
539 **it = t.map(**it);
540 }
541 }
542
543 m_d->lastMousePos = pt;
544 m_d->recalculateSignalCompressor.start();
545
546}
547
549{
550 return currentArgs.isEditingTransformPoints() &&
551 closeOnStartPointClick &&
552 pointIndexUnderCursor == 0 &&
553 currentArgs.origPoints().size() > 2 &&
554 !pointWasDragged;
555}
556
558{
559 return m_d->shouldCloseTheCage() ||
560 m_d->currentArgs.isEditingTransformPoints();
561}
562
564{
565 if (m_d->shouldCloseTheCage()) {
566 m_d->currentArgs.setEditingTransformPoints(false);
567 }
568
569 return true;
570}
571
572inline QPointF KisWarpTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization)
573{
574 return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt);
575}
576
578{
579 QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter);
580
581 QTransform resultThumbTransform = q->thumbToImageTransform() * scaleTransform;
582 qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultThumbTransform);
583 bool useFlakeOptimization = scale < 1.0 &&
584 !KisTransformUtils::thumbnailTooSmall(resultThumbTransform, q->originalImage().rect());
585
586 QVector<QPointF> thumbOrigPoints(currentArgs.numPoints());
587 QVector<QPointF> thumbTransfPoints(currentArgs.numPoints());
588
589 for (int i = 0; i < currentArgs.numPoints(); ++i) {
590 thumbOrigPoints[i] = imageToThumb(currentArgs.origPoints()[i], useFlakeOptimization);
591 thumbTransfPoints[i] = imageToThumb(currentArgs.transfPoints()[i], useFlakeOptimization);
592 }
593
594 paintingOffset = transaction.originalTopLeft();
595
596 if (!q->originalImage().isNull() && !currentArgs.isEditingTransformPoints()) {
597 QPointF origTLInFlake = imageToThumb(transaction.originalTopLeft(), useFlakeOptimization);
598
599 if (useFlakeOptimization) {
600 transformedImage = q->originalImage().transformed(resultThumbTransform);
601 paintingTransform = QTransform();
602 } else {
603 transformedImage = q->originalImage();
604 paintingTransform = resultThumbTransform;
605
606 }
607
608 transformedImage = q->calculateTransformedImage(currentArgs,
609 transformedImage,
610 thumbOrigPoints,
611 thumbTransfPoints,
612 origTLInFlake,
613 &paintingOffset);
614 } else {
615 transformedImage = q->originalImage();
616 paintingOffset = imageToThumb(transaction.originalTopLeft(), false);
617 paintingTransform = resultThumbTransform;
618 }
619
620 handlesTransform = scaleTransform;
621 Q_EMIT q->requestCanvasUpdate();
622 Q_EMIT q->requestImageRecalculation();
623}
624
626 const QImage &srcImage,
627 const QVector<QPointF> &origPoints,
628 const QVector<QPointF> &transfPoints,
629 const QPointF &srcOffset,
630 QPointF *dstOffset)
631{
633 currentArgs.warpType(),
634 origPoints, transfPoints,
635 currentArgs.alpha(),
636 srcImage,
637 srcOffset, dstOffset);
638}
639
640#include "moc_kis_warp_transform_strategy.cpp"
float value(const T *src, size_t ch)
Eigen::Matrix< double, 4, 2 > R
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static QCursor crossCursor()
Definition kis_cursor.cc:34
static QCursor rotateCursor()
static QCursor moveCursor()
static QCursor arrowCursor()
Definition kis_cursor.cc:24
static QCursor pointingHandCursor()
static QCursor sizeVerCursor()
Definition kis_cursor.cc:64
The KisHandlePainterHelper class is a special helper for painting handles around objects....
void drawConnectionLine(const QLineF &line)
void setHandleStyle(const KisHandleStyle &style)
static KisHandleStyle & primarySelection()
bool addFunction(const QPointF &pt, qreal radius, Function function)
static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
static qreal scaleFromAffineMatrix(const QTransform &t)
static QPointF clipInRect(QPointF p, QRectF r)
static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter)
static bool thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect)
void continuePrimaryAction(const QPointF &pt, bool shiftModifierActive, bool altModifierActive) override
virtual void drawConnectionLines(QPainter &gc, const QVector< QPointF > &origPoints, const QVector< QPointF > &transfPoints, bool isEditingPoints)
void setTransformType(TransformType type)
KisWarpTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs &currentArgs, TransformTransactionProperties &transaction)
virtual QImage calculateTransformedImage(ToolTransformArgs &currentArgs, const QImage &srcImage, const QVector< QPointF > &origPoints, const QVector< QPointF > &transfPoints, const QPointF &srcOffset, QPointF *dstOffset)
const QScopedPointer< Private > m_d
bool beginPrimaryAction(const QPointF &pt) override
void paint(QPainter &gc) override
void overrideDrawingItems(bool drawConnectionLines, bool drawOrigPoints, bool drawTransfPoints)
QCursor getCurrentCursor() const override
void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive) override
static QImage transformQImage(WarpType warpType, const QVector< QPointF > &origPoint, const QVector< QPointF > &transfPoint, qreal alpha, const QImage &srcImage, const QPointF &srcQImageOffset, QPointF *newOffset)
QVector< QPointF > & refTransformedPoints()
KisWarpTransformWorker::WarpType warpType() const
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
T pow2(const T &x)
Definition kis_global.h:166
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
void accumulateBounds(const Point &pt, Rect *bounds)
qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
void initAntsPen(QPen *antsPen, QPen *outlinePen, int antLength, int antSpace)
Private(KisWarpTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction)
TransformTransactionProperties & transaction
QVector< QPointF * > getSelectedPoints(QPointF *center, bool limitToSelectedOnly=false) const
QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization)
const KisCoordinatesConverter * converter
standard members ///