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