[krita/kazakov/svg-loading] /: Implement global scaling mode

Dmitry Kazakov null at kde.org
Fri Dec 30 10:02:46 UTC 2016


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 at 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, const 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 = 1e-10;
+
+    const qreal diff = x2 - x1;
+    const qreal expDiff = expX2 - expX1;
+
+    return qAbs(diff) > eps ? expDiff / diff : 1.0;
+}
+
+void findMinMaxPoints(const QPolygonF &poly, int *minPoint, int *maxPoint, std::function<qreal(const QPointF&)> dimension)
+{
+    KIS_ASSERT_RECOVER_RETURN(minPoint);
+    KIS_ASSERT_RECOVER_RETURN(maxPoint);
+
+    qreal minValue = dimension(poly[*minPoint]);
+    qreal maxValue = dimension(poly[*maxPoint]);
+
+    for (int i = 0; i < poly.size(); i++) {
+        const qreal value = dimension(poly[i]);
+
+        if (value < minValue) {
+            *minPoint = i;
+            minValue = value;
+        }
+
+        if (value > maxValue) {
+            *maxPoint = i;
+            maxValue = value;
+        }
+    }
+}
+
+}
+
 void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY,
                           const QPointF &absoluteStillPoint,
+                          bool useGlobalMode,
                           bool usePostScaling, const QTransform &postScalingCoveringTransform)
 {
     QPointF localStillPoint = shape->absoluteTransformation(0).inverted().map(absoluteStillPoint);
@@ -90,12 +128,93 @@ void KoFlake::resizeShape(KoShape *shape, qreal scaleX, qreal scaleY,
 
     if (usePostScaling) {
         const QTransform scale = 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 =
+                    shape->absoluteTransformation(0) *
+                    scale *
+                    shape->absoluteTransformation(0).inverted() *
+                    shape->transformation();
+
+            shape->setTransformation(uniformGlobalTransform);
+        }
     } else {
         using namespace KisAlgebra2D;
 
+        if (useGlobalMode) {
+            const QTransform scale = QTransform::fromScale(scaleX, scaleY);
+            const QTransform uniformGlobalTransform =
+                    shape->absoluteTransformation(0) *
+                    scale *
+                    shape->absoluteTransformation(0).inverted();
+
+            const QRectF rect = shape->outlineRect();
+
+            /**
+             * The basic idea of such global scaling:
+             *
+             * 1) We choose two the most distant points of the original outline rect
+             * 2) Calculate their expected position if transformed using `uniformGlobalTransform`
+             * 3) NOTE1: we do not transform the entire shape using `uniformGlobalTransform`,
+             *           because it will cause massive shearing. We transform 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 equivalent changes on
+             *           both globalScaleX and globalScaleY at the same time. We can guarantee
+             *           only one of them. Therefore we select the most "important" axis and
+             *           guarantee scael along it. The scale along the other direction is not
+             *           controlled.
+             * 5) After we have the two most distant points, we can just calculate the scale
+             *    by dividing difference between their expected and original positions. This
+             *    formula can be derived from equation:
+             *
+             *    localPoint_i * ScaleMatrix = localPoint_i * UniformGlobalTransform = expectedPoint_i
+             */
+
+            // choose the most significant scale direction
+            qreal scaleXDeviation = qAbs(1.0 - scaleX);
+            qreal scaleYDeviation = qAbs(1.0 - scaleY);
+
+            std::function<qreal(const QPointF&)> dimension;
+
+            if (scaleXDeviation > scaleYDeviation) {
+                dimension = [] (const QPointF &pt) {
+                    return pt.x();
+                };
+
+            } else {
+                dimension = [] (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 = shape->absoluteTransformation(0).map(localPoints);
+
+            int minPointIndex = 0;
+            int maxPointIndex = 2;
+
+            findMinMaxPoints(globalPoints, &minPointIndex, &maxPointIndex, dimension);
+
+            // calculate the scale using the extremum points
+            const QPointF minPoint = localPoints[minPointIndex];
+            const QPointF maxPoint = localPoints[maxPointIndex];
+
+            const QPointF minPointExpected = uniformGlobalTransform.map(minPoint);
+            const QPointF maxPointExpected = uniformGlobalTransform.map(maxPoint);
+
+            scaleX = getScaleByPointsPair(minPoint.x(), maxPoint.x(),
+                                          minPointExpected.x(), maxPointExpected.x());
+            scaleY = getScaleByPointsPair(minPoint.y(), maxPoint.y(),
+                                          minPointExpected.y(), maxPointExpected.y());
+        }
+
         const QSizeF oldSize(shape->size());
         const QSizeF newSize(oldSize.width() * qAbs(scaleX), oldSize.height() * 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 QSizeF &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 QTransform &postScalingCoveringTransform);
 }
 
 #endif
diff --git a/libs/flake/commands/KoShapeResizeCommand.cpp b/libs/flake/commands/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<KoShape*> &shapes,
                                            qreal scaleX, qreal scaleY,
                                            const QPointF &absoluteStillPoint,
+                                           bool useGLobalMode,
                                            bool usePostScaling,
                                            const QTransform &postScalingCoveringTransform,
                                            KUndo2Command *parent)
@@ -48,6 +50,7 @@ KoShapeResizeCommand::KoShapeResizeCommand(const QList<KoShape*> &shapes,
     m_d->scaleX = scaleX;
     m_d->scaleY = scaleY;
     m_d->absoluteStillPoint = absoluteStillPoint;
+    m_d->useGlobalMode = useGLobalMode;
     m_d->usePostScaling = usePostScaling;
     m_d->postScalingCoveringTransform = 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/commands/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 KUndo2Command
 public:
     KoShapeResizeCommand(const QList<KoShape*> &shapes,
                          qreal scaleX, qreal scaleY,
-                         const QPointF &absoluteStillPoint,
+                         const QPointF &absoluteStillPoint, bool useGLobalMode,
                          bool usePostScaling, const QTransform &postScalingCoveringTransform,
                          KUndo2Command *parent = 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 normalizeGroup)
 
 
     const QPointF stillPoint = group->absolutePosition(KoFlake::BottomRightCorner);
-    KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, true, QTransform());
+    KoFlake::resizeShape(group, 1.2, 1.4, stillPoint, false, true, QTransform());
 
     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 = 1e-6;
+
     const bool useGlobalSize = chkGlobalCoordinates->isChecked();
     const KoFlake::AnchorPosition anchor = positionSelector->value();
 
@@ -293,7 +295,7 @@ void DefaultToolWidget::slotRepositionShapes()
     const QPointF newPosition(positionXSpinBox->value(), positionYSpinBox->value());
     const QPointF diff = newPosition - oldPosition;
 
-    if (diff.manhattanLength() < 1e-6) return;
+    if (diff.manhattanLength() < eps) return;
 
     QList<QPointF> oldPositions;
     QList<QPointF> newPositions;
@@ -312,6 +314,8 @@ void DefaultToolWidget::slotRepositionShapes()
 
 void DefaultToolWidget::slotResizeShapes()
 {
+    static const qreal eps = 1e-4;
+
     const bool useGlobalSize = chkGlobalCoordinates->isChecked();
     const KoFlake::AnchorPosition anchor = positionSelector->value();
 
@@ -327,7 +331,7 @@ void DefaultToolWidget::slotResizeShapes()
     const qreal scaleX = newSize.width() / oldSize.width();
     const qreal scaleY = 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 =
         shapes.size() > 1 || chkUniformScaling->isChecked();
@@ -335,6 +339,7 @@ void DefaultToolWidget::slotResizeShapes()
     KUndo2Command *cmd = new KoShapeResizeCommand(shapes,
                                                   scaleX, scaleY,
                                                   bounds.topLeft(),
+                                                  useGlobalSize,
                                                   usePostScaling,
                                                   selection->transformation());
     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 &stillPoint, qreal zoomX, qreal
         shape->setSize(m_initialSizes[i]);
         KoFlake::resizeShape(shape, zoomX, zoomY,
                              stillPoint,
+                             false,
                              usePostScaling, m_postScalingCoveringTransform);
 
         shape->update();



More information about the kimageshop mailing list