[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-kimageshop
Subject:    [krita/kazakov/svg-loading] /: Implement global scaling mode
From:       Dmitry Kazakov <null () kde ! org>
Date:       2016-12-30 10:02:46
Message-ID: E1cMu1O-00089h-8H () code ! kde ! org
[Download RAW message or body]

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, 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();


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic