From kde-kimageshop Fri Dec 30 10:02:46 2016 From: Dmitry Kazakov Date: Fri, 30 Dec 2016 10:02:46 +0000 To: kde-kimageshop Subject: [krita/kazakov/svg-loading] /: Implement global scaling mode Message-Id: X-MARC-Message: https://marc.info/?l=kde-kimageshop&m=148309219620137 Git commit 22edd1672f0a1f77b2230aed98833d1f50b9b823 by Dmitry Kazakov. Committed on 30/12/2016 at 09:34. Pushed by dkazakov into branch 'kazakov/svg-loading'. Implement global scaling mode In Global Scaling Mode the spin boxes in the geometry box measure the size of your shape in *absolute* coordinates, that is in X,Y coordinates of your image. That way you can find out the bounding box of the heavily transformed shapes. Global Scaling Mode can also be combined with "non-uniform scaling" mode. It this case, changing width or height of the shapes will keep the geometry of the shape(!), that is will not add any shears to the rotated shapes. If you combine Global Scaling Mode and Uniform Scaling, then scaling rotated shapes will shear during the transformation. PS: I feel these options will need quite a bit of video documentation/explanation! CC:kimageshop@kde.org M +122 -3 libs/flake/KoFlake.cpp M +3 -1 libs/flake/KoFlake.h M +4 -0 libs/flake/commands/KoShapeResizeCommand.cpp M +1 -1 libs/flake/commands/KoShapeResizeCommand.h M +1 -1 libs/ui/tests/kis_shape_commands_test.cpp M +7 -2 plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp M +1 -0 plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp https://commits.kde.org/krita/22edd1672f0a1f77b2230aed98833d1f50b9b823 diff --git a/libs/flake/KoFlake.cpp b/libs/flake/KoFlake.cpp index d3593a1f245..d77c3c138e8 100644 --- a/libs/flake/KoFlake.cpp +++ b/libs/flake/KoFlake.cpp @@ -79,8 +79,46 @@ QPointF KoFlake::toAbsolute(const QPointF &relative, con= st QSizeF &size) #include "kis_debug.h" #include "kis_algebra_2d.h" = +namespace { + +qreal getScaleByPointsPair(qreal x1, qreal x2, qreal expX1, qreal expX2) +{ + static const qreal eps =3D 1e-10; + + const qreal diff =3D x2 - x1; + const qreal expDiff =3D expX2 - expX1; + + return qAbs(diff) > eps ? expDiff / diff : 1.0; +} + +void findMinMaxPoints(const QPolygonF &poly, int *minPoint, int *maxPoint,= std::function dimension) +{ + KIS_ASSERT_RECOVER_RETURN(minPoint); + KIS_ASSERT_RECOVER_RETURN(maxPoint); + + qreal minValue =3D dimension(poly[*minPoint]); + qreal maxValue =3D dimension(poly[*maxPoint]); + + for (int i =3D 0; i < poly.size(); i++) { + const qreal value =3D dimension(poly[i]); + + if (value < minValue) { + *minPoint =3D i; + minValue =3D value; + } + + if (value > maxValue) { + *maxPoint =3D i; + maxValue =3D value; + } + } +} + +} + void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY, const QPointF &absoluteStillPoint, + bool useGlobalMode, bool usePostScaling, const QTransform &postScali= ngCoveringTransform) { QPointF localStillPoint =3D shape->absoluteTransformation(0).inverted(= ).map(absoluteStillPoint); @@ -90,12 +128,93 @@ void KoFlake::resizeShape(KoShape *shape, qreal scaleX= , qreal scaleY, = if (usePostScaling) { const QTransform scale =3D QTransform::fromScale(scaleX, scaleY); - shape->setTransformation(shape->transformation() * - postScalingCoveringTransform.inverted() * - scale * postScalingCoveringTransform); + + if (!useGlobalMode) { + shape->setTransformation(shape->transformation() * + postScalingCoveringTransform.inverted= () * + scale * postScalingCoveringTransform); + } else { + const QTransform uniformGlobalTransform =3D + shape->absoluteTransformation(0) * + scale * + shape->absoluteTransformation(0).inverted() * + shape->transformation(); + + shape->setTransformation(uniformGlobalTransform); + } } else { using namespace KisAlgebra2D; = + if (useGlobalMode) { + const QTransform scale =3D QTransform::fromScale(scaleX, scale= Y); + const QTransform uniformGlobalTransform =3D + shape->absoluteTransformation(0) * + scale * + shape->absoluteTransformation(0).inverted(); + + const QRectF rect =3D shape->outlineRect(); + + /** + * The basic idea of such global scaling: + * + * 1) We choose two the most distant points of the original ou= tline rect + * 2) Calculate their expected position if transformed using `= uniformGlobalTransform` + * 3) NOTE1: we do not transform the entire shape using `unifo= rmGlobalTransform`, + * because it will cause massive shearing. We transf= orm only two points + * and adjust other points using dumb scaling. + * 4) NOTE2: given that `scale` transform is much more simpler= than + * `uniformGlobalTransform`, we cannot guarantee equ= ivalent changes on + * both globalScaleX and globalScaleY at the same ti= me. We can guarantee + * only one of them. Therefore we select the most "i= mportant" axis and + * guarantee scael along it. The scale along the oth= er direction is not + * controlled. + * 5) After we have the two most distant points, we can just c= alculate the scale + * by dividing difference between their expected and origin= al positions. This + * formula can be derived from equation: + * + * localPoint_i * ScaleMatrix =3D localPoint_i * UniformGlo= balTransform =3D expectedPoint_i + */ + + // choose the most significant scale direction + qreal scaleXDeviation =3D qAbs(1.0 - scaleX); + qreal scaleYDeviation =3D qAbs(1.0 - scaleY); + + std::function dimension; + + if (scaleXDeviation > scaleYDeviation) { + dimension =3D [] (const QPointF &pt) { + return pt.x(); + }; + + } else { + dimension =3D [] (const QPointF &pt) { + return pt.y(); + }; + } + + // find min and max points (in absolute coordinates), + // by default use top-left and bottom-right + QPolygonF localPoints(rect); + QPolygonF globalPoints =3D shape->absoluteTransformation(0).ma= p(localPoints); + + int minPointIndex =3D 0; + int maxPointIndex =3D 2; + + findMinMaxPoints(globalPoints, &minPointIndex, &maxPointIndex,= dimension); + + // calculate the scale using the extremum points + const QPointF minPoint =3D localPoints[minPointIndex]; + const QPointF maxPoint =3D localPoints[maxPointIndex]; + + const QPointF minPointExpected =3D uniformGlobalTransform.map(= minPoint); + const QPointF maxPointExpected =3D uniformGlobalTransform.map(= maxPoint); + + scaleX =3D getScaleByPointsPair(minPoint.x(), maxPoint.x(), + minPointExpected.x(), maxPointEx= pected.x()); + scaleY =3D getScaleByPointsPair(minPoint.y(), maxPoint.y(), + minPointExpected.y(), maxPointEx= pected.y()); + } + const QSizeF oldSize(shape->size()); const QSizeF newSize(oldSize.width() * qAbs(scaleX), oldSize.heigh= t() * qAbs(scaleY)); = diff --git a/libs/flake/KoFlake.h b/libs/flake/KoFlake.h index 7b57ff53959..5525f840554 100644 --- a/libs/flake/KoFlake.h +++ b/libs/flake/KoFlake.h @@ -129,7 +129,9 @@ namespace KoFlake KRITAFLAKE_EXPORT QPointF toAbsolute(const QPointF &relative, const QS= izeF &size); = KRITAFLAKE_EXPORT void resizeShape(KoShape *shape, qreal scaleX, qreal= scaleY, - const QPointF &absoluteStillPoint, = bool usePostScaling, const QTransform &postScalingCoveringTransform); + const QPointF &absoluteStillPoint, + bool useGlobalMode, + bool usePostScaling, const QTransfo= rm &postScalingCoveringTransform); } = #endif diff --git a/libs/flake/commands/KoShapeResizeCommand.cpp b/libs/flake/comm= ands/KoShapeResizeCommand.cpp index adffc934b1a..dd0e3948038 100644 --- a/libs/flake/commands/KoShapeResizeCommand.cpp +++ b/libs/flake/commands/KoShapeResizeCommand.cpp @@ -27,6 +27,7 @@ struct Q_DECL_HIDDEN KoShapeResizeCommand::Private qreal scaleX; qreal scaleY; QPointF absoluteStillPoint; + bool useGlobalMode; bool usePostScaling; QTransform postScalingCoveringTransform; = @@ -38,6 +39,7 @@ struct Q_DECL_HIDDEN KoShapeResizeCommand::Private KoShapeResizeCommand::KoShapeResizeCommand(const QList &shapes, qreal scaleX, qreal scaleY, const QPointF &absoluteStillPoi= nt, + bool useGLobalMode, bool usePostScaling, const QTransform &postScalingCo= veringTransform, KUndo2Command *parent) @@ -48,6 +50,7 @@ KoShapeResizeCommand::KoShapeResizeCommand(const QList &shapes, m_d->scaleX =3D scaleX; m_d->scaleY =3D scaleY; m_d->absoluteStillPoint =3D absoluteStillPoint; + m_d->useGlobalMode =3D useGLobalMode; m_d->usePostScaling =3D usePostScaling; m_d->postScalingCoveringTransform =3D postScalingCoveringTransform; = @@ -69,6 +72,7 @@ void KoShapeResizeCommand::redo() KoFlake::resizeShape(shape, m_d->scaleX, m_d->scaleY, m_d->absoluteStillPoint, + m_d->useGlobalMode, m_d->usePostScaling, m_d->postScalingCoveringTransform); = diff --git a/libs/flake/commands/KoShapeResizeCommand.h b/libs/flake/comman= ds/KoShapeResizeCommand.h index ff9a54ad99f..05adcb3a5d0 100644 --- a/libs/flake/commands/KoShapeResizeCommand.h +++ b/libs/flake/commands/KoShapeResizeCommand.h @@ -36,7 +36,7 @@ class KRITAFLAKE_EXPORT KoShapeResizeCommand : public KUn= do2Command public: KoShapeResizeCommand(const QList &shapes, qreal scaleX, qreal scaleY, - const QPointF &absoluteStillPoint, + const QPointF &absoluteStillPoint, bool useGLobal= Mode, bool usePostScaling, const QTransform &postScalin= gCoveringTransform, KUndo2Command *parent =3D 0); = diff --git a/libs/ui/tests/kis_shape_commands_test.cpp b/libs/ui/tests/kis_= shape_commands_test.cpp index a9d2cd0ed82..a6cbf151e2a 100644 --- a/libs/ui/tests/kis_shape_commands_test.cpp +++ b/libs/ui/tests/kis_shape_commands_test.cpp @@ -204,7 +204,7 @@ void KisShapeCommandsTest::testResizeShape(bool normali= zeGroup) = = const QPointF stillPoint =3D group->absolutePosition(KoFlake::BottomRi= ghtCorner); - KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, true, QTransform()); + KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, false, true, QTransf= orm()); = qDebug() << "After:"; qDebug() << ppVar(group->absolutePosition(KoFlake::TopLeftCorner)); diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp b/= plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp index 60f538d4988..668e9ce562d 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp @@ -280,6 +280,8 @@ void DefaultToolWidget::slotUpdatePositionBoxes() = void DefaultToolWidget::slotRepositionShapes() { + static const qreal eps =3D 1e-6; + const bool useGlobalSize =3D chkGlobalCoordinates->isChecked(); const KoFlake::AnchorPosition anchor =3D positionSelector->value(); = @@ -293,7 +295,7 @@ void DefaultToolWidget::slotRepositionShapes() const QPointF newPosition(positionXSpinBox->value(), positionYSpinBox-= >value()); const QPointF diff =3D newPosition - oldPosition; = - if (diff.manhattanLength() < 1e-6) return; + if (diff.manhattanLength() < eps) return; = QList oldPositions; QList newPositions; @@ -312,6 +314,8 @@ void DefaultToolWidget::slotRepositionShapes() = void DefaultToolWidget::slotResizeShapes() { + static const qreal eps =3D 1e-4; + const bool useGlobalSize =3D chkGlobalCoordinates->isChecked(); const KoFlake::AnchorPosition anchor =3D positionSelector->value(); = @@ -327,7 +331,7 @@ void DefaultToolWidget::slotResizeShapes() const qreal scaleX =3D newSize.width() / oldSize.width(); const qreal scaleY =3D newSize.height() / oldSize.height(); = - if (qAbs(scaleX - 1.0) < 1e-6 && qAbs(scaleY - 1.0) < 1e-6) return; + if (qAbs(scaleX - 1.0) < eps && qAbs(scaleY - 1.0) < eps) return; = const bool usePostScaling =3D shapes.size() > 1 || chkUniformScaling->isChecked(); @@ -335,6 +339,7 @@ void DefaultToolWidget::slotResizeShapes() KUndo2Command *cmd =3D new KoShapeResizeCommand(shapes, scaleX, scaleY, bounds.topLeft(), + useGlobalSize, usePostScaling, selection->transformatio= n()); m_tool->canvas()->addCommand(cmd); diff --git a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp = b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp index a01ed1ee377..aaaf759c26d 100644 --- a/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp +++ b/plugins/tools/defaulttool/defaulttool/ShapeResizeStrategy.cpp @@ -215,6 +215,7 @@ void ShapeResizeStrategy::resizeBy(const QPointF &still= Point, qreal zoomX, qreal shape->setSize(m_initialSizes[i]); KoFlake::resizeShape(shape, zoomX, zoomY, stillPoint, + false, usePostScaling, m_postScalingCoveringTransfor= m); = shape->update();