Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_mesh_transform_strategy.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
9
10#include <QPointF>
11#include <QPainter>
12#include <QPainterPath>
13
14#include "kis_painting_tweaks.h"
15#include "kis_cursor.h"
16
19#include "kis_transform_utils.h"
21
22
23uint qHash(const QPoint &value) {
24 return uint((0xffffffffffffffff - quint64(value.y())) ^ quint64(value.x()));
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
60
64
65 QSet<KisBezierTransformMesh::NodeIndex> selectedNodes;
66 boost::optional<KisBezierTransformMesh::SegmentIndex> hoveredSegment;
67 boost::optional<KisBezierTransformMesh::ControlPointIndex> hoveredControl;
68 boost::optional<KisBezierTransformMesh::PatchIndex> hoveredPatch;
72
74
78
79 bool pointWasDragged = false;
80 QPointF lastMousePos;
82
84
88
90 QTransform imageToThumb(bool useFlakeOptimization);
91};
92
93
95 KoSnapGuide *snapGuide,
96 ToolTransformArgs &currentArgs,
98 : KisSimplifiedActionPolicyStrategy(converter, snapGuide),
99 m_d(new Private(this, converter, currentArgs, transaction))
100{
101
102 connect(&m_d->recalculateSignalCompressor, SIGNAL(timeout()),
103 SLOT(recalculateTransformations()));
104
105 m_d->selectedNodes << KisBezierTransformMesh::NodeIndex(1, 1);
108}
109
113
114void KisMeshTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive)
115{
116 const qreal grabRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
117
118 boost::optional<KisBezierTransformMesh::SegmentIndex> hoveredSegment;
119 boost::optional<KisBezierTransformMesh::ControlPointIndex> hoveredControl;
120 boost::optional<KisBezierTransformMesh::PatchIndex> hoveredPatch;
122 QPointF localPatchPos;
123 qreal localSegmentPos = 0.0;
124
125 const bool symmetricalMode = shiftModifierActive ^ m_d->currentArgs.meshSymmetricalHandles();
126
127 if (m_d->currentArgs.meshShowHandles()) {
128 auto index = m_d->currentArgs.meshTransform()->hitTestControlPoint(mousePos, grabRadius);
129 if (m_d->currentArgs.meshTransform()->isIndexValid(index)) {
130 hoveredControl = index;
131 mode = symmetricalMode ? Private::OVER_POINT_SYMMETRIC : Private::OVER_POINT;
132 }
133 }
134
135 if (mode == Private::NOTHING) {
136 auto index = m_d->currentArgs.meshTransform()->hitTestNode(mousePos, grabRadius);
137 auto nodeIt = m_d->currentArgs.meshTransform()->find(index);
138
139 if (nodeIt != m_d->currentArgs.meshTransform()->endControlPoints()) {
140 hoveredControl = index;
141 mode = shiftModifierActive && nodeIt.isBorderNode() && !nodeIt.isCornerNode() ?
144 }
145 }
146
147 if (mode == Private::NOTHING) {
148 auto index = m_d->currentArgs.meshTransform()->hitTestSegment(mousePos, grabRadius, &localSegmentPos);
149 if (m_d->currentArgs.meshTransform()->isIndexValid(index)) {
150 hoveredSegment = index;
152 }
153 }
154
155 if (mode == Private::NOTHING) {
156 auto index = m_d->currentArgs.meshTransform()->hitTestPatch(mousePos, &localPatchPos);
157 if (m_d->currentArgs.meshTransform()->isIndexValid(index)) {
158 hoveredPatch = index;
159 mode = !shiftModifierActive ? Private::OVER_PATCH : Private::OVER_PATCH_LOCKED;
160 }
161 }
162
163
164 // verify that we have only one active selection at a time
165 KIS_SAFE_ASSERT_RECOVER_RETURN(bool(hoveredControl) +
166 bool(hoveredSegment) +
167 bool(hoveredPatch)<= 1);
168
169
171 m_d->currentArgs.meshTransform()->endControlPoints();
172
173 if (hoveredControl) {
174 controlIt = m_d->currentArgs.meshTransform()->find(*hoveredControl);
175 }
176
177 if (altModifierActive &&
178 ((hoveredControl &&
179 hoveredControl->isNode() &&
180 controlIt.isBorderNode() &&
181 !controlIt.isCornerNode()) ||
182 hoveredSegment)) {
183
185
186 } else {
187 if (hoveredControl || hoveredSegment) {
188 if (perspectiveModifierActive) {
190 } else if (hoveredControl &&
191 hoveredControl->isNode() &&
192 m_d->selectedNodes.size() > 1 &&
193 m_d->selectedNodes.contains(hoveredControl->nodeIndex)) {
194
195 mode = Private::MOVE_MODE;
196 }
197 } else if (!hoveredPatch) {
198 if (perspectiveModifierActive) {
199 mode = Private::SCALE_MODE;
200 } else if (shiftModifierActive) {
201 mode = Private::MOVE_MODE;
202 } else {
204 }
205 }
206 }
207
208 if (mode != m_d->mode ||
209 hoveredControl != m_d->hoveredControl ||
210 hoveredSegment != m_d->hoveredSegment ||
211 hoveredPatch != m_d->hoveredPatch) {
212
213 m_d->hoveredControl = hoveredControl;
214 m_d->hoveredSegment = hoveredSegment;
215 m_d->hoveredPatch = hoveredPatch;
216
217 m_d->mode = mode;
218 Q_EMIT requestCanvasUpdate();
219 }
220
221 m_d->localPatchPosition = localPatchPos;
222 m_d->localSegmentPosition = localSegmentPos;
223
224 if (hoveredControl) {
225 m_d->hoveredHandleOffset = *controlIt - mousePos;
226 } else if (hoveredSegment) {
228 m_d->currentArgs.meshTransform()->find(*hoveredSegment);
229 m_d->hoveredHandleOffset = segmentIt.pointAtParam(m_d->localSegmentPosition) - mousePos;
230 } else {
231 m_d->hoveredHandleOffset = QPointF();
232 }
233
235}
236
237QPointF KisMeshTransformStrategy::handleSnapPoint(const QPointF &imagePos)
238{
239 return imagePos + m_d->hoveredHandleOffset;
240}
241
243{
244 return true;
245}
246
248{
249 bool shouldUpdate = false;
250
251 const QSize currentMeshSize = m_d->currentArgs.meshTransform()->size();
252 if (currentMeshSize != m_d->lastMeshSize) {
253 m_d->selectedNodes.clear();
254 shouldUpdate = true;
255 }
256 m_d->lastMeshSize = currentMeshSize;
257
258 if (shouldUpdate) {
259 Q_EMIT requestCanvasUpdate();
260 }
261}
262
264{
265 gc.save();
266
267 gc.setOpacity(m_d->transaction.basePreviewOpacity());
268 gc.setTransform(m_d->paintingTransform, true);
269 gc.drawImage(m_d->paintingOffset, m_d->transformedImage);
270
271 gc.restore();
272
273 gc.save();
274 gc.setTransform(KisTransformUtils::imageToFlakeTransform(m_d->converter), true);
275
277
278 for (auto it = m_d->currentArgs.meshTransform()->beginSegments();
279 it != m_d->currentArgs.meshTransform()->endSegments();
280 ++it) {
281
282 if (m_d->hoveredSegment && it.segmentIndex() == *m_d->hoveredSegment) {
284 } else {
286 }
287
288 QPainterPath path;
289 path.moveTo(it.p0());
290 path.cubicTo(it.p1(), it.p2(), it.p3());
291
292 handlePainter.drawPath(path);
293 }
294
295 for (auto it = m_d->currentArgs.meshTransform()->beginControlPoints();
296 it != m_d->currentArgs.meshTransform()->endControlPoints();
297 ++it) {
298
299 if (!m_d->currentArgs.meshShowHandles() && !it.isNode()) {
300 KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->hoveredControl || *m_d->hoveredControl != it.controlIndex());
301
302 continue;
303 }
304
305
306 if (m_d->hoveredControl && *m_d->hoveredControl == it.controlIndex()) {
307
309
310 } else if (it.type() == KisBezierTransformMesh::ControlType::Node &&
311 m_d->selectedNodes.contains(it.nodeIndex())) {
312
314
315 } else {
317 }
318
320 handlePainter.drawHandleCircle(*it);
321 } else {
322 handlePainter.drawConnectionLine(it.node().node, *it);
323 handlePainter.drawHandleSmallCircle(*it);
324 }
325 }
326
327 gc.restore();
328}
329
331{
332 QCursor cursor;
333
334 switch (m_d->mode) {
338 cursor = KisCursor::meshCursorFree();
339 break;
346 break;
348 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->hoveredSegment || m_d->hoveredControl,
350
351 if (m_d->hoveredControl) {
352 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredControl);
353 cursor = it.isTopBorder() || it.isBottomBorder() ?
355
356 } else if (m_d->hoveredSegment) {
357 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredSegment);
358
359 const QRectF segmentRect(it.p0(), it.p3());
360 cursor = segmentRect.width() > segmentRect.height() ?
362 }
363
364 break;
365 }
367 cursor = KisCursor::crossCursor();
368 break;
370 cursor = KisCursor::moveCursor();
371 break;
373 cursor = KisCursor::rotateCursor();
374 break;
376 cursor = KisCursor::sizeVerCursor();
377 break;
378 case Private::NOTHING:
379 cursor = KisCursor::arrowCursor();
380 break;
381 }
382
383 return cursor;
384}
385
387{
389 m_d->recalculateTransformations();
390}
391
393{
394 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->hoveredSegment || m_d->hoveredControl, false);
395
396 if (m_d->hoveredControl) {
397 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredControl);
398
399 KisBezierTransformMesh::segment_iterator resultSegment = m_d->currentArgs.meshTransform()->endSegments();
400 qreal resultParam = 0;
401 qreal resultDistance = std::numeric_limits<qreal>::max();
402 KisBezierTransformMesh::NodeIndex resultRemovedNodeIndex;
403
404 auto estimateSegment =
405 [&resultParam,
406 &resultSegment,
407 &resultDistance,
408 &resultRemovedNodeIndex] (const KisBezierTransformMesh::segment_iterator &segment,
409 const QPoint &removedNodeOffset,
410 const QPointF &pt,
412 {
413 if (segment != mesh.endSegments()) {
414
415 qreal distance = 0.0;
416 qreal param = KisBezierUtils::nearestPoint({segment.p0(), segment.p1(), segment.p2(), segment.p3()}, pt, &distance);
417
418 if (distance < resultDistance) {
419 resultDistance = distance;
420 resultParam = param;
421 resultSegment = segment;
422 resultRemovedNodeIndex = segment.firstNodeIndex() + removedNodeOffset;
423 }
424 }
425 };
426
427
428 if (it.isTopBorder() || it.isBottomBorder()) {
429 estimateSegment(it.leftSegment(), QPoint(2, 0), pt, *m_d->currentArgs.meshTransform());
430 estimateSegment(it.rightSegment(), QPoint(0, 0), pt, *m_d->currentArgs.meshTransform());
431 } else {
432 estimateSegment(it.topSegment(), QPoint(0, 2), pt, *m_d->currentArgs.meshTransform());
433 estimateSegment(it.bottomSegment(), QPoint(0, 0), pt, *m_d->currentArgs.meshTransform());
434 }
435
436 if (resultSegment != m_d->currentArgs.meshTransform()->endSegments()) {
437 if (!shouldDeleteNode(resultDistance, resultParam)) {
438 const qreal eps = 0.01;
439 const qreal proportion = KisBezierUtils::curveProportionByParam(resultSegment.p0(), resultSegment.p1(), resultSegment.p2(), resultSegment.p3(), resultParam, eps);
440
441 m_d->currentArgs.meshTransform()->subdivideSegment(resultSegment.segmentIndex(), proportion);
442 m_d->currentArgs.meshTransform()->removeColumnOrRow(resultRemovedNodeIndex, !resultSegment.isHorizontal());
443
444 } else {
445 m_d->currentArgs.meshTransform()->removeColumnOrRow(m_d->hoveredControl->nodeIndex, !resultSegment.isHorizontal());
446 }
447 }
448
449 } else if (m_d->hoveredSegment) {
450 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredSegment);
451 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->currentArgs.meshTransform()->endSegments(), false);
452
453 qreal distance = 0;
454 const qreal t = KisBezierUtils::nearestPoint({it.p0(), it.p1(), it.p2(), it.p3()}, pt, &distance);
455
456 if (!shouldDeleteNode(distance, t)) {
457 const qreal eps = 0.01;
458 const qreal proportion = KisBezierUtils::curveProportionByParam(it.p0(), it.p1(), it.p2(), it.p3(), t, eps);
459 m_d->currentArgs.meshTransform()->subdivideSegment(it.segmentIndex(), proportion);
460 }
461 }
462
463 m_d->recalculateSignalCompressor.start();
464
465 return true;
466}
467
469{
470 const qreal grabRadius = KisTransformUtils::effectiveHandleGrabRadius(m_d->converter);
471 return
472 distance > 10 * grabRadius ||
473 qFuzzyCompare(param, 0.0) ||
474 qFuzzyCompare(param, 1.0);
475
476}
477
479{
480 // retval shows if the stroke may have a continuation
481 bool retval = false;
482
483 m_d->mouseClickPos = pt;
484
485 QRectF selectionBounds;
486
487 if (m_d->selectedNodes.size() > 1) {
488 for (auto it = m_d->selectedNodes.begin(); it != m_d->selectedNodes.end(); ++it) {
490 m_d->currentArgs.meshTransform()->node(*it).node, &selectionBounds);
491 }
492 } else {
493 selectionBounds = m_d->currentArgs.meshTransform()->dstBoundingRect();
494 }
495
496 m_d->initialRotationCenter = selectionBounds.center();
497 m_d->initialSelectionMaxDimension = KisAlgebra2D::maxDimension(selectionBounds);
498 m_d->initialMeshState = *m_d->currentArgs.meshTransform();
499
500 m_d->pointWasDragged = false;
501
502 if (m_d->mode == Private::OVER_NODE ||
503 m_d->mode == Private::OVER_POINT ||
505 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->hoveredControl, false);
506
507 if (m_d->selectedNodes.size() <= 1 ||
508 !m_d->selectedNodes.contains(m_d->hoveredControl->nodeIndex)) {
509
510 m_d->selectedNodes.clear();
511 m_d->selectedNodes << m_d->hoveredControl->nodeIndex;
512 }
513
514 retval = true;
515 } else if (m_d->mode == Private::OVER_NODE_WHOLE_LINE) {
516 m_d->selectedNodes.clear();
517 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredControl);
518 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->currentArgs.meshTransform()->endControlPoints(), false);
519
520 if (it.isTopBorder() || it.isBottomBorder()) {
521 for (int i = 0; i < m_d->currentArgs.meshTransform()->size().height(); i++) {
522 m_d->selectedNodes << KisBezierTransformMesh::NodeIndex(m_d->hoveredControl->nodeIndex.x(), i);
523 }
524 } else {
525 for (int i = 0; i < m_d->currentArgs.meshTransform()->size().width(); i++) {
526 m_d->selectedNodes << KisBezierTransformMesh::NodeIndex(i, m_d->hoveredControl->nodeIndex.y());
527 }
528 }
529 retval = true;
530 } else if (m_d->mode == Private::OVER_SEGMENT || m_d->mode == Private::OVER_SEGMENT_SYMMETRIC) {
531 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_d->hoveredSegment, false);
532
533 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredSegment);
534 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->currentArgs.meshTransform()->endSegments(), false);
535
536 retval = true;
537
538 } else if (m_d->mode == Private::OVER_PATCH || m_d->mode == Private::OVER_PATCH_LOCKED) {
539 retval = true;
540
541 } else if (m_d->mode == Private::SPLIT_SEGMENT) {
542 retval = splitHoveredSegment(pt);
543
544 } else if (m_d->mode == Private::MULTIPLE_POINT_SELECTION) {
545 if (m_d->hoveredControl) {
546 if (!m_d->selectedNodes.contains(m_d->hoveredControl->nodeIndex)) {
547 m_d->selectedNodes.insert(m_d->hoveredControl->nodeIndex);
548 } else {
549 m_d->selectedNodes.remove(m_d->hoveredControl->nodeIndex);
550 }
551 } else if (m_d->hoveredSegment) {
552 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredSegment);
553 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(it != m_d->currentArgs.meshTransform()->endSegments(), false);
554
555 if (!m_d->selectedNodes.contains(it.firstNodeIndex()) ||
556 !m_d->selectedNodes.contains(it.secondNodeIndex())) {
557
558 m_d->selectedNodes.insert(it.firstNodeIndex());
559 m_d->selectedNodes.insert(it.secondNodeIndex());
560 } else {
561 m_d->selectedNodes.remove(it.firstNodeIndex());
562 m_d->selectedNodes.remove(it.secondNodeIndex());
563 }
564 }
565 retval = false;
566 } else if (m_d->mode == Private::MOVE_MODE ||
567 m_d->mode == Private::SCALE_MODE ||
568 m_d->mode == Private::ROTATE_MODE) {
569
570 retval = true;
571 }
572
573 m_d->lastMousePos = pt;
574 return retval;
575}
576
577void KisMeshTransformStrategy::continuePrimaryAction(const QPointF &pt, bool shiftModifierActive, bool altModifierActive)
578{
579 Q_UNUSED(shiftModifierActive);
580 Q_UNUSED(altModifierActive);
581
582 if (m_d->mode == Private::OVER_POINT ||
584 m_d->mode == Private::OVER_NODE) {
585
586 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredControl);
587
590 KisSmartMoveMeshControlMode::MoveSymmetricLock :
591 KisSmartMoveMeshControlMode::MoveFree;
592
593 smartMoveControl(*m_d->currentArgs.meshTransform(),
594 *m_d->hoveredControl,
595 pt - m_d->lastMousePos,
596 mode,
597 m_d->currentArgs.meshScaleHandles());
598
599 } else if (m_d->mode == Private::OVER_SEGMENT || m_d->mode == Private::OVER_SEGMENT_SYMMETRIC) {
600 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredSegment);
601
602 auto it = m_d->currentArgs.meshTransform()->find(*m_d->hoveredSegment);
603
604 // TODO: recover special case for degree-2 curves. There is a special
605 // function for that in KisBezierUtils::interpolateQuadric(), but
606 // it seems like not working properly.
607
608 const QPointF offset = pt - m_d->lastMousePos;
609
610 QPointF offsetP1;
611 QPointF offsetP2;
612
613 std::tie(offsetP1, offsetP2) =
614 KisBezierUtils::offsetSegment(m_d->localSegmentPosition, offset);
615
616
619 KisSmartMoveMeshControlMode::MoveSymmetricLock :
620 KisSmartMoveMeshControlMode::MoveFree;
621
622 smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, mode, m_d->currentArgs.meshScaleHandles());
623 smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, mode, m_d->currentArgs.meshScaleHandles());
624
625 } else if (m_d->mode == Private::OVER_PATCH || m_d->mode == Private::OVER_PATCH_LOCKED) {
626 KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredPatch);
627
629
630 KisBezierTransformMesh &mesh = *m_d->currentArgs.meshTransform();
631 mesh = m_d->initialMeshState;
632
633 auto patchIt = m_d->currentArgs.meshTransform()->find(*m_d->hoveredPatch);
634
635 QPointF offset = pt - m_d->mouseClickPos;
636
637 auto offsetSegment =
639 qreal t,
640 const QPointF &offset) {
641
642 QPointF offsetP1;
643 QPointF offsetP2;
644
645 std::tie(offsetP1, offsetP2) =
647
648
649 smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().controlIndex(), offsetP1, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
650 smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().controlIndex(), offsetP2, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
651 };
652
653
654 const QPointF center = patchIt->localToGlobal(QPointF(0.5, 0.5));
655 const qreal centerDistance = kisDistance(m_d->mouseClickPos, center);
656
658 qreal nearestSegmentSignificance = 0;
659 qreal nearestSegmentDistance = std::numeric_limits<qreal>::max();
660 qreal nearestSegmentDistanceSignificance = 0.0;
661 qreal nearestSegmentParam = 0.5;
662
663 auto testSegment =
664 [&nearestSegment,
665 &nearestSegmentSignificance,
666 &nearestSegmentDistance,
667 &nearestSegmentDistanceSignificance,
668 &nearestSegmentParam,
669 centerDistance,
670 this] (KisBezierTransformMesh::segment_iterator it, qreal param) {
671
672 const QPointF movedPoint = KisBezierUtils::bezierCurve(it.p0(), it.p1(), it.p2(), it.p3(), param);
673 const qreal distance = kisDistance(m_d->mouseClickPos, movedPoint);
674
675 if (distance < nearestSegmentDistance) {
676 const qreal proportion = KisBezierUtils::curveProportionByParam(it.p0(), it.p1(), it.p2(), it.p3(), param, 0.1);
677
678 qreal distanceSignificance =
679 centerDistance / (centerDistance + distance);
680
681 if (distanceSignificance > 0.6) {
682 distanceSignificance = std::min(1.0, linearReshapeFunc(distanceSignificance, 0.6, 0.75, 0.6, 1.0));
683 }
684
685 const qreal directionSignificance =
686 1.0 - std::min(1.0, std::abs(proportion - 0.5) / 0.4);
687
688 nearestSegmentDistance = distance;
689 nearestSegment = it;
690 nearestSegmentParam = param;
691 nearestSegmentSignificance = m_d->mode != Private::OVER_PATCH_LOCKED ? distanceSignificance * directionSignificance : 0;
692 nearestSegmentDistanceSignificance = distanceSignificance;
693 }
694 };
695
696 testSegment(patchIt.segmentP(), m_d->localPatchPosition.x());
697 testSegment(patchIt.segmentQ(), m_d->localPatchPosition.x());
698 testSegment(patchIt.segmentR(), m_d->localPatchPosition.y());
699 testSegment(patchIt.segmentS(), m_d->localPatchPosition.y());
700
701 KIS_SAFE_ASSERT_RECOVER_RETURN(nearestSegment != mesh.endSegments());
702
703 const qreal translationOffsetCoeff =
704 qBound(0.0,
705 linearReshapeFunc(1.0 - nearestSegmentDistanceSignificance,
706 0.95, 0.75, 1.0, 0.0),
707 1.0);
708 const QPointF translationOffset = translationOffsetCoeff * offset;
709 offset -= translationOffset;
710
711 QPointF segmentOffset;
712
713 if (nearestSegmentSignificance > 0) {
714 segmentOffset = nearestSegmentSignificance * offset;
715 offset -= segmentOffset;
716 }
717
718 const qreal alpha =
719 1.0 - KisBezierUtils::curveProportionByParam(nearestSegment.p0(),
720 nearestSegment.p1(),
721 nearestSegment.p2(),
722 nearestSegment.p3(),
723 nearestSegmentParam, 0.1);
724
725 const qreal coeffN1 =
726 alpha > 0.5 ? std::max(0.0, linearReshapeFunc(alpha, 0.6, 0.75, 1.0, 0.0)) : 1.0;
727 const qreal coeffN0 =
728 alpha < 0.5 ? std::max(0.0, linearReshapeFunc(alpha, 0.25, 0.4, 0.0, 1.0)) : 1.0;
729
730 smartMoveControl(*m_d->currentArgs.meshTransform(), nearestSegment.itP0().controlIndex(), offset * coeffN0, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
731 smartMoveControl(*m_d->currentArgs.meshTransform(), nearestSegment.itP3().controlIndex(), offset * coeffN1, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
732
733 smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeTopLeft().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
734 smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeTopRight().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
735 smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeBottomLeft().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
736 smartMoveControl(*m_d->currentArgs.meshTransform(), patchIt.nodeBottomRight().controlIndex(), translationOffset, KisSmartMoveMeshControlMode::MoveSymmetricLock, m_d->currentArgs.meshScaleHandles());
737
738 offsetSegment(nearestSegment, nearestSegmentParam, segmentOffset);
739
740 } else if (m_d->mode == Private::SPLIT_SEGMENT) {
741 *m_d->currentArgs.meshTransform() = m_d->initialMeshState;
742 const bool sanitySplitResult = splitHoveredSegment(pt);
743 KIS_SAFE_ASSERT_RECOVER_NOOP(sanitySplitResult);
744
745 } else if (m_d->mode == Private::MOVE_MODE || m_d->mode == Private::OVER_NODE_WHOLE_LINE) {
746 const QPointF offset = pt - m_d->lastMousePos;
747 if (m_d->selectedNodes.size() > 1) {
748 for (auto it = m_d->selectedNodes.begin(); it != m_d->selectedNodes.end(); ++it) {
749 m_d->currentArgs.meshTransform()->node(*it).translate(offset);
750 }
751 } else {
752 m_d->currentArgs.meshTransform()->translate(offset);
753 }
754 } else if (m_d->mode == Private::SCALE_MODE) {
755 const qreal scale = 1.0 - (pt - m_d->lastMousePos).y() / m_d->initialSelectionMaxDimension;
756
757
758 const QTransform t =
759 QTransform::fromTranslate(-m_d->initialRotationCenter.x(), -m_d->initialRotationCenter.y()) *
760 QTransform::fromScale(scale, scale) *
761 QTransform::fromTranslate(m_d->initialRotationCenter.x(), m_d->initialRotationCenter.y());
762
763 if (m_d->selectedNodes.size() > 1) {
764 for (auto it = m_d->selectedNodes.begin(); it != m_d->selectedNodes.end(); ++it) {
765 m_d->currentArgs.meshTransform()->node(*it).transform(t);
766 }
767 } else {
768 m_d->currentArgs.meshTransform()->transform(t);
769 }
770
771 } else if (m_d->mode == Private::ROTATE_MODE) {
772 const QPointF oldDirection = m_d->lastMousePos - m_d->initialRotationCenter;
773 const QPointF newDirection = pt - m_d->initialRotationCenter;
774 const qreal rotateAngle = KisAlgebra2D::angleBetweenVectors(oldDirection, newDirection);
775
776 QTransform R;
777 R.rotateRadians(rotateAngle);
778
779 const QTransform t =
780 QTransform::fromTranslate(-m_d->initialRotationCenter.x(), -m_d->initialRotationCenter.y()) *
781 R *
782 QTransform::fromTranslate(m_d->initialRotationCenter.x(), m_d->initialRotationCenter.y());
783
784 if (m_d->selectedNodes.size() > 1) {
785 for (auto it = m_d->selectedNodes.begin(); it != m_d->selectedNodes.end(); ++it) {
786 m_d->currentArgs.meshTransform()->node(*it).transform(t);
787 }
788 } else {
789 m_d->currentArgs.meshTransform()->transform(t);
790 }
791 }
792
793 m_d->lastMousePos = pt;
794 m_d->recalculateSignalCompressor.start();
795}
796
801
803{
804 return m_d->mode == Private::SPLIT_SEGMENT;
805}
806
807QTransform KisMeshTransformStrategy::Private::imageToThumb(bool useFlakeOptimization)
808{
809 return useFlakeOptimization ?
811 q->thumbToImageTransform().inverted();
812}
813
815{
816 const QTransform scaleTransform = KisTransformUtils::imageToFlakeTransform(converter);
817
818 const QTransform resultThumbTransform = q->thumbToImageTransform() * scaleTransform;
819 const qreal scale = KisTransformUtils::scaleFromAffineMatrix(resultThumbTransform);
820 const bool useFlakeOptimization = scale < 1.0 &&
821 !KisTransformUtils::thumbnailTooSmall(resultThumbTransform, q->originalImage().rect());
822
823 const QTransform imageToThumb = this->imageToThumb(useFlakeOptimization);
824 KIS_SAFE_ASSERT_RECOVER_RETURN(imageToThumb.type() <= QTransform::TxScale);
825
826 KisBezierTransformMesh mesh(*currentArgs.meshTransform());
827 mesh.transformSrcAndDst(imageToThumb);
828
829 paintingOffset = transaction.originalTopLeft();
830
831 if (!q->originalImage().isNull()) {
832 const QPointF origTLInFlake = imageToThumb.map(transaction.originalTopLeft());
833 if (useFlakeOptimization) {
834 transformedImage = q->originalImage().transformed(resultThumbTransform);
835 paintingTransform = QTransform();
836 } else {
837 transformedImage = q->originalImage();
838 paintingTransform = resultThumbTransform;
839
840 }
841
842 const QRect dstImageRect = mesh.dstBoundingRect().toAlignedRect();
843 QImage dstImage(dstImageRect.size(), transformedImage.format());
844 dstImage.fill(0);
845
846 mesh.transformMesh(origTLInFlake.toPoint(), transformedImage,
847 dstImageRect.topLeft(), &dstImage);
848
849 transformedImage = dstImage;
850 paintingOffset = dstImageRect.topLeft();
851
852 } else {
853 transformedImage = q->originalImage();
854 paintingOffset = imageToThumb.map(transaction.originalTopLeft());
855 paintingTransform = resultThumbTransform;
856 }
857
858 Q_EMIT q->requestCanvasUpdate();
859 Q_EMIT q->requestImageRecalculation();
860}
861
862#include "moc_kis_mesh_transform_strategy.cpp"
float value(const T *src, size_t ch)
Eigen::Matrix< double, 4, 2 > R
qreal distance(const QPointF &p1, const QPointF &p2)
unsigned int uint
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
std::pair< NodeIndex, int > SegmentIndex
control_point_iterator find(const ControlPointIndex &index)
void transformSrcAndDst(const QTransform &t)
segment_iterator endSegments()
void transformMesh(const QPoint &srcQImageOffset, const QImage &srcImage, const QPoint &dstQImageOffset, QImage *dstImage) const
static QCursor splitVCursor()
Definition kis_cursor.cc:94
static QCursor crossCursor()
Definition kis_cursor.cc:34
static QCursor rotateCursor()
static QCursor moveCursor()
static QCursor arrowCursor()
Definition kis_cursor.cc:24
static QCursor meshCursorFree()
static QCursor meshCursorLocked()
static QCursor sizeVerCursor()
Definition kis_cursor.cc:64
static QCursor splitHCursor()
Definition kis_cursor.cc:99
The KisHandlePainterHelper class is a special helper for painting handles around objects....
void drawHandleSmallCircle(const QPointF &center)
void drawPath(const QPainterPath &path)
void drawConnectionLine(const QLineF &line)
void setHandleStyle(const KisHandleStyle &style)
void drawHandleCircle(const QPointF &center, qreal radius)
static KisHandleStyle & highlightedPrimaryHandlesWithSolidOutline()
static KisHandleStyle & highlightedPrimaryHandles()
static KisHandleStyle & selectedPrimaryHandles()
static KisHandleStyle & primarySelection()
void paint(QPainter &gc) override
KisMeshTransformStrategy(const KisCoordinatesConverter *converter, KoSnapGuide *snapGuide, ToolTransformArgs &currentArgs, TransformTransactionProperties &transaction)
QCursor getCurrentCursor() const override
bool beginPrimaryAction(const QPointF &pt) override
bool shouldDeleteNode(qreal distance, qreal param)
const QScopedPointer< Private > m_d
void continuePrimaryAction(const QPointF &pt, bool shiftModifierActive, bool altModifierActive) override
QPointF handleSnapPoint(const QPointF &imagePos) override
bool splitHoveredSegment(const QPointF &pt)
void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive, bool shiftModifierActive, bool altModifierActive) override
static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
static const int handleRadius
static qreal scaleFromAffineMatrix(const QTransform &t)
static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter)
static bool thumbnailTooSmall(const QTransform &resultThumbTransform, const QRect &originalImageRect)
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
const qreal eps
qreal kisDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:190
uint qHash(const QPoint &value)
auto maxDimension(Size size) -> decltype(size.width())
T linearReshapeFunc(T x, T x0, T x1, T y0, T y1)
void accumulateBounds(const Point &pt, Rect *bounds)
qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
qreal curveProportionByParam(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t, const qreal error)
std::pair< QPointF, QPointF > offsetSegment(qreal t, const QPointF &offset)
moves point t of the curve by offset offset
QPointF bezierCurve(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t)
qreal nearestPoint(const QList< QPointF > controlPoints, const QPointF &point, qreal *resultDistance, QPointF *resultPoint)
const KisCoordinatesConverter * converter
Private(KisMeshTransformStrategy *_q, const KisCoordinatesConverter *_converter, ToolTransformArgs &_currentArgs, TransformTransactionProperties &_transaction)
QSet< KisBezierTransformMesh::NodeIndex > selectedNodes
boost::optional< KisBezierTransformMesh::ControlPointIndex > hoveredControl
boost::optional< KisBezierTransformMesh::SegmentIndex > hoveredSegment
QTransform imageToThumb(bool useFlakeOptimization)
TransformTransactionProperties & transaction
boost::optional< KisBezierTransformMesh::PatchIndex > hoveredPatch