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