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;
125 const bool symmetricalMode = shiftModifierActive ^
m_d->currentArgs.meshSymmetricalHandles();
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;
136 auto index =
m_d->currentArgs.meshTransform()->hitTestNode(mousePos, grabRadius);
137 auto nodeIt =
m_d->currentArgs.meshTransform()->find(index);
139 if (nodeIt !=
m_d->currentArgs.meshTransform()->endControlPoints()) {
140 hoveredControl = index;
141 mode = shiftModifierActive && nodeIt.isBorderNode() && !nodeIt.isCornerNode() ?
148 auto index =
m_d->currentArgs.meshTransform()->hitTestSegment(mousePos, grabRadius, &localSegmentPos);
149 if (
m_d->currentArgs.meshTransform()->isIndexValid(index)) {
150 hoveredSegment = index;
156 auto index =
m_d->currentArgs.meshTransform()->hitTestPatch(mousePos, &localPatchPos);
157 if (
m_d->currentArgs.meshTransform()->isIndexValid(index)) {
158 hoveredPatch = index;
166 bool(hoveredSegment) +
167 bool(hoveredPatch)<= 1);
171 m_d->currentArgs.meshTransform()->endControlPoints();
173 if (hoveredControl) {
174 controlIt =
m_d->currentArgs.meshTransform()->find(*hoveredControl);
177 if (altModifierActive &&
179 hoveredControl->isNode() &&
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)) {
197 }
else if (!hoveredPatch) {
198 if (perspectiveModifierActive) {
200 }
else if (shiftModifierActive) {
208 if (mode !=
m_d->mode ||
209 hoveredControl !=
m_d->hoveredControl ||
210 hoveredSegment !=
m_d->hoveredSegment ||
211 hoveredPatch !=
m_d->hoveredPatch) {
213 m_d->hoveredControl = hoveredControl;
214 m_d->hoveredSegment = hoveredSegment;
215 m_d->hoveredPatch = hoveredPatch;
221 m_d->localPatchPosition = localPatchPos;
222 m_d->localSegmentPosition = localSegmentPos;
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;
231 m_d->hoveredHandleOffset = QPointF();
396 if (
m_d->hoveredControl) {
397 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredControl);
400 qreal resultParam = 0;
401 qreal resultDistance = std::numeric_limits<qreal>::max();
404 auto estimateSegment =
409 const QPoint &removedNodeOffset,
413 if (segment != mesh.endSegments()) {
421 resultSegment = segment;
422 resultRemovedNodeIndex = segment.firstNodeIndex() + removedNodeOffset;
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());
432 estimateSegment(it.topSegment(), QPoint(0, 2), pt, *
m_d->currentArgs.meshTransform());
433 estimateSegment(it.bottomSegment(), QPoint(0, 0), pt, *
m_d->currentArgs.meshTransform());
436 if (resultSegment !=
m_d->currentArgs.meshTransform()->endSegments()) {
438 const qreal
eps = 0.01;
441 m_d->currentArgs.meshTransform()->subdivideSegment(resultSegment.
segmentIndex(), proportion);
442 m_d->currentArgs.meshTransform()->removeColumnOrRow(resultRemovedNodeIndex, !resultSegment.
isHorizontal());
445 m_d->currentArgs.meshTransform()->removeColumnOrRow(
m_d->hoveredControl->nodeIndex, !resultSegment.
isHorizontal());
449 }
else if (
m_d->hoveredSegment) {
450 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
457 const qreal
eps = 0.01;
459 m_d->currentArgs.meshTransform()->subdivideSegment(it.segmentIndex(), proportion);
463 m_d->recalculateSignalCompressor.start();
483 m_d->mouseClickPos = pt;
485 QRectF selectionBounds;
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);
493 selectionBounds =
m_d->currentArgs.meshTransform()->dstBoundingRect();
496 m_d->initialRotationCenter = selectionBounds.center();
498 m_d->initialMeshState = *
m_d->currentArgs.meshTransform();
500 m_d->pointWasDragged =
false;
507 if (
m_d->selectedNodes.size() <= 1 ||
508 !
m_d->selectedNodes.contains(
m_d->hoveredControl->nodeIndex)) {
510 m_d->selectedNodes.clear();
511 m_d->selectedNodes <<
m_d->hoveredControl->nodeIndex;
516 m_d->selectedNodes.clear();
517 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredControl);
520 if (it.isTopBorder() || it.isBottomBorder()) {
521 for (
int i = 0; i <
m_d->currentArgs.meshTransform()->size().height(); i++) {
525 for (
int i = 0; i <
m_d->currentArgs.meshTransform()->size().width(); i++) {
533 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
545 if (
m_d->hoveredControl) {
546 if (!
m_d->selectedNodes.contains(
m_d->hoveredControl->nodeIndex)) {
547 m_d->selectedNodes.insert(
m_d->hoveredControl->nodeIndex);
549 m_d->selectedNodes.remove(
m_d->hoveredControl->nodeIndex);
551 }
else if (
m_d->hoveredSegment) {
552 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
555 if (!
m_d->selectedNodes.contains(it.firstNodeIndex()) ||
556 !
m_d->selectedNodes.contains(it.secondNodeIndex())) {
558 m_d->selectedNodes.insert(it.firstNodeIndex());
559 m_d->selectedNodes.insert(it.secondNodeIndex());
561 m_d->selectedNodes.remove(it.firstNodeIndex());
562 m_d->selectedNodes.remove(it.secondNodeIndex());
573 m_d->lastMousePos = pt;
579 Q_UNUSED(shiftModifierActive);
580 Q_UNUSED(altModifierActive);
590 KisSmartMoveMeshControlMode::MoveSymmetricLock :
591 KisSmartMoveMeshControlMode::MoveFree;
593 smartMoveControl(*
m_d->currentArgs.meshTransform(),
594 *
m_d->hoveredControl,
595 pt -
m_d->lastMousePos,
597 m_d->currentArgs.meshScaleHandles());
602 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
608 const QPointF offset = pt -
m_d->lastMousePos;
613 std::tie(offsetP1, offsetP2) =
619 KisSmartMoveMeshControlMode::MoveSymmetricLock :
620 KisSmartMoveMeshControlMode::MoveFree;
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());
631 mesh =
m_d->initialMeshState;
633 auto patchIt =
m_d->currentArgs.meshTransform()->
find(*
m_d->hoveredPatch);
635 QPointF offset = pt -
m_d->mouseClickPos;
640 const QPointF &offset) {
645 std::tie(offsetP1, offsetP2) =
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());
654 const QPointF center = patchIt->localToGlobal(QPointF(0.5, 0.5));
655 const qreal centerDistance =
kisDistance(
m_d->mouseClickPos, center);
658 qreal nearestSegmentSignificance = 0;
659 qreal nearestSegmentDistance = std::numeric_limits<qreal>::max();
660 qreal nearestSegmentDistanceSignificance = 0.0;
661 qreal nearestSegmentParam = 0.5;
665 &nearestSegmentSignificance,
666 &nearestSegmentDistance,
667 &nearestSegmentDistanceSignificance,
668 &nearestSegmentParam,
675 if (
distance < nearestSegmentDistance) {
678 qreal distanceSignificance =
679 centerDistance / (centerDistance +
distance);
681 if (distanceSignificance > 0.6) {
682 distanceSignificance = std::min(1.0, linearReshapeFunc(distanceSignificance, 0.6, 0.75, 0.6, 1.0));
685 const qreal directionSignificance =
686 1.0 - std::min(1.0, std::abs(proportion - 0.5) / 0.4);
690 nearestSegmentParam = param;
692 nearestSegmentDistanceSignificance = distanceSignificance;
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());
703 const qreal translationOffsetCoeff =
705 linearReshapeFunc(1.0 - nearestSegmentDistanceSignificance,
706 0.95, 0.75, 1.0, 0.0),
708 const QPointF translationOffset = translationOffsetCoeff * offset;
709 offset -= translationOffset;
711 QPointF segmentOffset;
713 if (nearestSegmentSignificance > 0) {
714 segmentOffset = nearestSegmentSignificance * offset;
715 offset -= segmentOffset;
723 nearestSegmentParam, 0.1);
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;
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());
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());
738 offsetSegment(nearestSegment, nearestSegmentParam, segmentOffset);
741 *
m_d->currentArgs.meshTransform() =
m_d->initialMeshState;
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);
752 m_d->currentArgs.meshTransform()->translate(offset);
755 const qreal scale = 1.0 - (pt -
m_d->lastMousePos).y() /
m_d->initialSelectionMaxDimension;
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());
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);
768 m_d->currentArgs.meshTransform()->transform(t);
772 const QPointF oldDirection =
m_d->lastMousePos -
m_d->initialRotationCenter;
773 const QPointF newDirection = pt -
m_d->initialRotationCenter;
777 R.rotateRadians(rotateAngle);
780 QTransform::fromTranslate(-
m_d->initialRotationCenter.x(), -
m_d->initialRotationCenter.y()) *
782 QTransform::fromTranslate(
m_d->initialRotationCenter.x(),
m_d->initialRotationCenter.y());
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);
789 m_d->currentArgs.meshTransform()->transform(t);
793 m_d->lastMousePos = pt;
794 m_d->recalculateSignalCompressor.start();
818 const QTransform resultThumbTransform = q->thumbToImageTransform() * scaleTransform;
820 const bool useFlakeOptimization = scale < 1.0 &&
823 const QTransform imageToThumb = this->imageToThumb(useFlakeOptimization);
829 paintingOffset = transaction.originalTopLeft();
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();
837 transformedImage = q->originalImage();
838 paintingTransform = resultThumbTransform;
843 QImage dstImage(dstImageRect.size(), transformedImage.format());
846 mesh.
transformMesh(origTLInFlake.toPoint(), transformedImage,
847 dstImageRect.topLeft(), &dstImage);
849 transformedImage = dstImage;
850 paintingOffset = dstImageRect.topLeft();
853 transformedImage = q->originalImage();
854 paintingOffset = imageToThumb.map(transaction.originalTopLeft());
855 paintingTransform = resultThumbTransform;
858 Q_EMIT q->requestCanvasUpdate();
859 Q_EMIT q->requestImageRecalculation();