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;
126 const bool symmetricalMode = shiftModifierActive ^
m_d->currentArgs.meshSymmetricalHandles();
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;
137 auto index =
m_d->currentArgs.meshTransform()->hitTestNode(mousePos, grabRadius);
138 auto nodeIt =
m_d->currentArgs.meshTransform()->find(index);
140 if (nodeIt !=
m_d->currentArgs.meshTransform()->endControlPoints()) {
141 hoveredControl = index;
142 mode = shiftModifierActive && nodeIt.isBorderNode() && !nodeIt.isCornerNode() ?
149 auto index =
m_d->currentArgs.meshTransform()->hitTestSegment(mousePos, grabRadius, &localSegmentPos);
150 if (
m_d->currentArgs.meshTransform()->isIndexValid(index)) {
151 hoveredSegment = index;
157 auto index =
m_d->currentArgs.meshTransform()->hitTestPatch(mousePos, &localPatchPos);
158 if (
m_d->currentArgs.meshTransform()->isIndexValid(index)) {
159 hoveredPatch = index;
167 bool(hoveredSegment) +
168 bool(hoveredPatch)<= 1);
172 m_d->currentArgs.meshTransform()->endControlPoints();
174 if (hoveredControl) {
175 controlIt =
m_d->currentArgs.meshTransform()->find(*hoveredControl);
178 if (altModifierActive &&
180 hoveredControl->isNode() &&
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)) {
198 }
else if (!hoveredPatch) {
199 if (perspectiveModifierActive) {
201 }
else if (shiftModifierActive) {
209 if (mode !=
m_d->mode ||
210 hoveredControl !=
m_d->hoveredControl ||
211 hoveredSegment !=
m_d->hoveredSegment ||
212 hoveredPatch !=
m_d->hoveredPatch) {
214 m_d->hoveredControl = hoveredControl;
215 m_d->hoveredSegment = hoveredSegment;
216 m_d->hoveredPatch = hoveredPatch;
222 m_d->localPatchPosition = localPatchPos;
223 m_d->localSegmentPosition = localSegmentPos;
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;
232 m_d->hoveredHandleOffset = QPointF();
398 if (
m_d->hoveredControl) {
399 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredControl);
402 qreal resultParam = 0;
403 qreal resultDistance = std::numeric_limits<qreal>::max();
406 auto estimateSegment =
411 const QPoint &removedNodeOffset,
415 if (segment != mesh.endSegments()) {
423 resultSegment = segment;
424 resultRemovedNodeIndex = segment.firstNodeIndex() + removedNodeOffset;
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());
434 estimateSegment(it.topSegment(), QPoint(0, 2), pt, *
m_d->currentArgs.meshTransform());
435 estimateSegment(it.bottomSegment(), QPoint(0, 0), pt, *
m_d->currentArgs.meshTransform());
438 if (resultSegment !=
m_d->currentArgs.meshTransform()->endSegments()) {
440 const qreal
eps = 0.01;
443 m_d->currentArgs.meshTransform()->subdivideSegment(resultSegment.
segmentIndex(), proportion);
444 m_d->currentArgs.meshTransform()->removeColumnOrRow(resultRemovedNodeIndex, !resultSegment.
isHorizontal());
447 m_d->currentArgs.meshTransform()->removeColumnOrRow(
m_d->hoveredControl->nodeIndex, !resultSegment.
isHorizontal());
451 }
else if (
m_d->hoveredSegment) {
452 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
459 const qreal
eps = 0.01;
461 m_d->currentArgs.meshTransform()->subdivideSegment(it.segmentIndex(), proportion);
465 m_d->recalculateSignalCompressor.start();
485 m_d->mouseClickPos = pt;
487 QRectF selectionBounds;
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);
495 selectionBounds =
m_d->currentArgs.meshTransform()->dstBoundingRect();
498 m_d->initialRotationCenter = selectionBounds.center();
500 m_d->initialMeshState = *
m_d->currentArgs.meshTransform();
502 m_d->pointWasDragged =
false;
509 if (
m_d->selectedNodes.size() <= 1 ||
510 !
m_d->selectedNodes.contains(
m_d->hoveredControl->nodeIndex)) {
512 m_d->selectedNodes.clear();
513 m_d->selectedNodes <<
m_d->hoveredControl->nodeIndex;
518 m_d->selectedNodes.clear();
519 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredControl);
522 if (it.isTopBorder() || it.isBottomBorder()) {
523 for (
int i = 0; i <
m_d->currentArgs.meshTransform()->size().height(); i++) {
527 for (
int i = 0; i <
m_d->currentArgs.meshTransform()->size().width(); i++) {
535 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
547 if (
m_d->hoveredControl) {
548 if (!
m_d->selectedNodes.contains(
m_d->hoveredControl->nodeIndex)) {
549 m_d->selectedNodes.insert(
m_d->hoveredControl->nodeIndex);
551 m_d->selectedNodes.remove(
m_d->hoveredControl->nodeIndex);
553 }
else if (
m_d->hoveredSegment) {
554 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
557 if (!
m_d->selectedNodes.contains(it.firstNodeIndex()) ||
558 !
m_d->selectedNodes.contains(it.secondNodeIndex())) {
560 m_d->selectedNodes.insert(it.firstNodeIndex());
561 m_d->selectedNodes.insert(it.secondNodeIndex());
563 m_d->selectedNodes.remove(it.firstNodeIndex());
564 m_d->selectedNodes.remove(it.secondNodeIndex());
575 m_d->lastMousePos = pt;
581 Q_UNUSED(shiftModifierActive);
582 Q_UNUSED(altModifierActive);
592 KisSmartMoveMeshControlMode::MoveSymmetricLock :
593 KisSmartMoveMeshControlMode::MoveFree;
595 smartMoveControl(*
m_d->currentArgs.meshTransform(),
596 *
m_d->hoveredControl,
597 pt -
m_d->lastMousePos,
599 m_d->currentArgs.meshScaleHandles());
604 auto it =
m_d->currentArgs.meshTransform()->find(*
m_d->hoveredSegment);
610 const QPointF offset = pt -
m_d->lastMousePos;
615 std::tie(offsetP1, offsetP2) =
621 KisSmartMoveMeshControlMode::MoveSymmetricLock :
622 KisSmartMoveMeshControlMode::MoveFree;
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());
633 mesh =
m_d->initialMeshState;
635 auto patchIt =
m_d->currentArgs.meshTransform()->
find(*
m_d->hoveredPatch);
637 QPointF offset = pt -
m_d->mouseClickPos;
642 const QPointF &offset) {
647 std::tie(offsetP1, offsetP2) =
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());
656 const QPointF center = patchIt->localToGlobal(QPointF(0.5, 0.5));
657 const qreal centerDistance =
kisDistance(
m_d->mouseClickPos, center);
660 qreal nearestSegmentSignificance = 0;
661 qreal nearestSegmentDistance = std::numeric_limits<qreal>::max();
662 qreal nearestSegmentDistanceSignificance = 0.0;
663 qreal nearestSegmentParam = 0.5;
667 &nearestSegmentSignificance,
668 &nearestSegmentDistance,
669 &nearestSegmentDistanceSignificance,
670 &nearestSegmentParam,
677 if (
distance < nearestSegmentDistance) {
680 qreal distanceSignificance =
681 centerDistance / (centerDistance +
distance);
683 if (distanceSignificance > 0.6) {
684 distanceSignificance = std::min(1.0, linearReshapeFunc(distanceSignificance, 0.6, 0.75, 0.6, 1.0));
687 const qreal directionSignificance =
688 1.0 - std::min(1.0, std::abs(proportion - 0.5) / 0.4);
692 nearestSegmentParam = param;
694 nearestSegmentDistanceSignificance = distanceSignificance;
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());
705 const qreal translationOffsetCoeff =
707 linearReshapeFunc(1.0 - nearestSegmentDistanceSignificance,
708 0.95, 0.75, 1.0, 0.0),
710 const QPointF translationOffset = translationOffsetCoeff * offset;
711 offset -= translationOffset;
713 QPointF segmentOffset;
715 if (nearestSegmentSignificance > 0) {
716 segmentOffset = nearestSegmentSignificance * offset;
717 offset -= segmentOffset;
725 nearestSegmentParam, 0.1);
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;
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());
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());
740 offsetSegment(nearestSegment, nearestSegmentParam, segmentOffset);
743 *
m_d->currentArgs.meshTransform() =
m_d->initialMeshState;
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);
754 m_d->currentArgs.meshTransform()->translate(offset);
757 const qreal scale = 1.0 - (pt -
m_d->lastMousePos).y() /
m_d->initialSelectionMaxDimension;
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());
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);
770 m_d->currentArgs.meshTransform()->transform(t);
774 const QPointF oldDirection =
m_d->lastMousePos -
m_d->initialRotationCenter;
775 const QPointF newDirection = pt -
m_d->initialRotationCenter;
779 R.rotateRadians(rotateAngle);
782 QTransform::fromTranslate(-
m_d->initialRotationCenter.x(), -
m_d->initialRotationCenter.y()) *
784 QTransform::fromTranslate(
m_d->initialRotationCenter.x(),
m_d->initialRotationCenter.y());
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);
791 m_d->currentArgs.meshTransform()->transform(t);
795 m_d->lastMousePos = pt;
796 m_d->recalculateSignalCompressor.start();
820 const QTransform resultThumbTransform = q->thumbToImageTransform() * scaleTransform;
822 const bool useFlakeOptimization = scale < 1.0 &&
825 const QTransform imageToThumb = this->imageToThumb(useFlakeOptimization);
831 paintingOffset = transaction.originalTopLeft();
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();
839 transformedImage = q->originalImage();
840 paintingTransform = resultThumbTransform;
845 QImage dstImage(dstImageRect.size(), transformedImage.format());
848 mesh.
transformMesh(origTLInFlake.toPoint(), transformedImage,
849 dstImageRect.topLeft(), &dstImage);
851 transformedImage = dstImage;
852 paintingOffset = dstImageRect.topLeft();
855 transformedImage = q->originalImage();
856 paintingOffset = imageToThumb.map(transaction.originalTopLeft());
857 paintingTransform = resultThumbTransform;
860 Q_EMIT q->requestCanvasUpdate();
861 Q_EMIT q->requestImageRecalculation();