[calligra/krita-testing-kazakov] krita: The first part of selection tool outline optimization is done

Dmitry Kazakov dimula73 at gmail.com
Sat Jun 1 15:54:55 UTC 2013


Git commit 5bf1b1c91ddf4b0d0e28ffa54b071ac4a4ac46ea by Dmitry Kazakov.
Committed on 01/06/2013 at 09:25.
Pushed by dkazakov into branch 'krita-testing-kazakov'.

The first part of selection tool outline optimization is done

There are at least two significant changes happened:

1) Every selection now has outlineCache() method which is supposed to
be updated asynchronously and can be in either valid or invalid state.

2) The initial outline for the selection in most of the cases should not
be calculated, because it i sknown by the tool itself.

3) The process of the "validness" controlling of the cache is done by
a transaction. If you create a non-selection transaction on a
selection paint device, the cache is cleared. If you are running a
selection transaction, you should care for the cache yourself. The
operations inside KisPixelSelection keep the cache consistent. If you
use any other operation, you should invalidate the cache
manually. This is, of course, not the ideal approach but, I guess,
something can be done to that in the future.

TODO: the actual asynchronous calculation of the selection outline

CCMAIL:kimageshop at kde.org
CCBUG:319535

M  +0    -1    krita/image/CMakeLists.txt
M  +0    -4    krita/image/commands_new/kis_node_move_command2.cpp
M  +7    -0    krita/image/kis_image.cc
M  +1    -0    krita/image/kis_image.h
M  +4    -0    krita/image/kis_node_graph_listener.cpp
M  +8    -0    krita/image/kis_node_graph_listener.h
M  +1    -1    krita/image/kis_paint_device.h
M  +93   -4    krita/image/kis_pixel_selection.cpp
M  +10   -0    krita/image/kis_pixel_selection.h
M  +29   -2    krita/image/kis_selection.cc
M  +3    -1    krita/image/kis_selection.h
M  +4    -0    krita/image/kis_selection_component.h
D  +0    -51   krita/image/kis_selection_transaction_data.cpp
D  +0    -53   krita/image/kis_selection_transaction_data.h
M  +4    -4    krita/image/kis_transaction.h
M  +73   -6    krita/image/kis_transaction_data.cpp
M  +5    -2    krita/image/kis_transaction_data.h
M  +4    -3    krita/image/kis_transform_visitor.h
M  +8    -0    krita/image/kis_transform_worker.cc
M  +5    -0    krita/image/kis_transform_worker.h
M  +98   -0    krita/image/tests/kis_pixel_selection_test.cpp
M  +3    -1    krita/image/tests/kis_pixel_selection_test.h
M  +1    -0    krita/plugins/extensions/colorrange/colorrange.cc
M  +1    -0    krita/plugins/extensions/colorrange/dlg_colorrange.cc
M  +5    -2    krita/plugins/extensions/imagesize/imagesize.cc
M  +3    -1    krita/plugins/tools/selectiontools/kis_tool_select_brush.cc
M  +1    -1    krita/plugins/tools/selectiontools/kis_tool_select_brush.h
M  +2    -0    krita/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
M  +4    -0    krita/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
M  +8    -2    krita/plugins/tools/selectiontools/kis_tool_select_magnetic.cc
M  +5    -0    krita/plugins/tools/selectiontools/kis_tool_select_outline.cc
M  +4    -1    krita/plugins/tools/selectiontools/kis_tool_select_path.cc
M  +5    -0    krita/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
M  +5    -0    krita/plugins/tools/selectiontools/kis_tool_select_rectangular.cc
M  +1    -0    krita/plugins/tools/selectiontools/kis_tool_select_similar.cc
M  +6    -10   krita/plugins/tools/tool_transform2/kis_tool_transform.cc
M  +7    -7    krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
M  +1    -3    krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
M  +1    -1    krita/ui/actions/kis_selection_action_factories.cpp
M  +27   -12   krita/ui/flake/kis_shape_selection.cpp
M  +5    -5    krita/ui/flake/kis_shape_selection.h
M  +3    -1    krita/ui/kis_mirror_visitor.cpp
M  +12   -5    krita/ui/kis_selection_decoration.cc
M  +1    -0    krita/ui/kis_selection_decoration.h
M  +1    -1    krita/ui/operations/kis_filter_selection_operation.cpp
M  +4    -5    krita/ui/tool/kis_selection_tool_helper.cpp

http://commits.kde.org/calligra/5bf1b1c91ddf4b0d0e28ffa54b071ac4a4ac46ea

diff --git a/krita/image/CMakeLists.txt b/krita/image/CMakeLists.txt
index cb91eac..ff8e6f3 100644
--- a/krita/image/CMakeLists.txt
+++ b/krita/image/CMakeLists.txt
@@ -184,7 +184,6 @@ set(kritaimage_LIB_SRCS
    kis_random_sub_accessor.cpp
    kis_selection.cc
    kis_selection_mask.cpp
-   kis_selection_transaction_data.cpp
    kis_serializable_configuration.cc
    kis_shared.cc
    kis_transaction_data.cpp
diff --git a/krita/image/commands_new/kis_node_move_command2.cpp b/krita/image/commands_new/kis_node_move_command2.cpp
index 85eca35..b2b5e09 100644
--- a/krita/image/commands_new/kis_node_move_command2.cpp
+++ b/krita/image/commands_new/kis_node_move_command2.cpp
@@ -62,8 +62,4 @@ void KisNodeMoveCommand2::moveTo(const QPoint& pos)
      */
     m_node->setX(pos.x());
     m_node->setY(pos.y());
-
-    if(m_undoAdapter && m_node->inherits("KisSelectionMask")) {
-        m_undoAdapter->emitSelectionChanged();
-    }
 }
diff --git a/krita/image/kis_image.cc b/krita/image/kis_image.cc
index ad6cceb..8ead72f 100644
--- a/krita/image/kis_image.cc
+++ b/krita/image/kis_image.cc
@@ -1500,6 +1500,13 @@ void KisImage::notifyProjectionUpdated(const QRect &rc)
     }
 }
 
+void KisImage::notifySelectionChanged()
+{
+    if (!m_d->disableUIUpdateSignals) {
+        m_d->legacyUndoAdapter->emitSelectionChanged();
+    }
+}
+
 void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect)
 {
     if (m_d->disableDirtyRequests) return;
diff --git a/krita/image/kis_image.h b/krita/image/kis_image.h
index 7988df5..0e3ca2d 100644
--- a/krita/image/kis_image.h
+++ b/krita/image/kis_image.h
@@ -88,6 +88,7 @@ public: // KisNodeGraphListener implementation
     void nodeHasBeenAdded(KisNode *parent, int index);
     void aboutToRemoveANode(KisNode *parent, int index);
     void nodeChanged(KisNode * node);
+    void notifySelectionChanged();
     void requestProjectionUpdate(KisNode *node, const QRect& rect);
 
 public: // KisProjectionUpdateListener implementation
diff --git a/krita/image/kis_node_graph_listener.cpp b/krita/image/kis_node_graph_listener.cpp
index fd28de5..3af45d3 100644
--- a/krita/image/kis_node_graph_listener.cpp
+++ b/krita/image/kis_node_graph_listener.cpp
@@ -74,6 +74,10 @@ void KisNodeGraphListener::nodeChanged(KisNode * /*node*/)
 {
 }
 
+void KisNodeGraphListener::notifySelectionChanged()
+{
+}
+
 void KisNodeGraphListener::requestProjectionUpdate(KisNode * /*node*/, const QRect& /*rect*/)
 {
 }
diff --git a/krita/image/kis_node_graph_listener.h b/krita/image/kis_node_graph_listener.h
index 418c302..a4d6ede 100644
--- a/krita/image/kis_node_graph_listener.h
+++ b/krita/image/kis_node_graph_listener.h
@@ -79,6 +79,14 @@ public:
     virtual void nodeChanged(KisNode * node);
 
     /**
+     * Inform the model that one of the selections in the graph is
+     * changed. The sender is not passed to the function (at least for
+     * now) because the UI should decide itself whether it needs to
+     * fetch new selection of not.
+     */
+    virtual void notifySelectionChanged();
+
+    /**
      * Inform the model that a node has been changed (setDirty)
      */
     virtual void requestProjectionUpdate(KisNode * node, const QRect& rect);
diff --git a/krita/image/kis_paint_device.h b/krita/image/kis_paint_device.h
index e9d15b5..ef7c47c 100644
--- a/krita/image/kis_paint_device.h
+++ b/krita/image/kis_paint_device.h
@@ -752,7 +752,7 @@ private:
      */
     QVector<qint32> channelSizes();
 
-private:
+protected:
     friend class KisSelectionTest;
     KisNodeWSP parentNode() const;
 
diff --git a/krita/image/kis_pixel_selection.cpp b/krita/image/kis_pixel_selection.cpp
index cf24784..91b9539 100644
--- a/krita/image/kis_pixel_selection.cpp
+++ b/krita/image/kis_pixel_selection.cpp
@@ -41,12 +41,15 @@
 #include <kis_iterator_ng.h>
 
 struct KisPixelSelection::Private {
+    QPainterPath outlineCache;
+    bool outlineCacheValid;
 };
 
 KisPixelSelection::KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds)
         : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), defaultBounds)
         , m_d(new Private)
 {
+    m_d->outlineCacheValid = true;
 }
 
 KisPixelSelection::KisPixelSelection(const KisPixelSelection& rhs)
@@ -77,10 +80,21 @@ const KoColorSpace *KisPixelSelection::compositionSourceColorSpace() const
 void KisPixelSelection::select(const QRect & rc, quint8 selectedness)
 {
     QRect r = rc.normalized();
-    if (r.width() > 0 && r.height() > 0) {
-        KisFillPainter painter(KisPaintDeviceSP(this));
-        const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
-        painter.fillRect(r, KoColor(Qt::white, cs), selectedness);
+    if (r.isEmpty()) return;
+
+    KisFillPainter painter(KisPaintDeviceSP(this));
+    const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
+    painter.fillRect(r, KoColor(Qt::white, cs), selectedness);
+
+    if (m_d->outlineCacheValid) {
+        QPainterPath path;
+        path.addRect(r);
+
+        if (selectedness != MIN_SELECTED) {
+            m_d->outlineCache += path;
+        } else {
+            m_d->outlineCache -= path;
+        }
     }
 }
 
@@ -123,6 +137,12 @@ void KisPixelSelection::addSelection(KisPixelSelectionSP selection)
         dst->nextRow();
         src->nextRow();
     }
+
+    m_d->outlineCacheValid &= selection->outlineCacheValid();
+
+    if (m_d->outlineCacheValid) {
+        m_d->outlineCache += selection->outlineCache();
+    }
 }
 
 void KisPixelSelection::subtractSelection(KisPixelSelectionSP selection)
@@ -144,6 +164,12 @@ void KisPixelSelection::subtractSelection(KisPixelSelectionSP selection)
         dst->nextRow();
         src->nextRow();
     }
+
+    m_d->outlineCacheValid &= selection->outlineCacheValid();
+
+    if (m_d->outlineCacheValid) {
+        m_d->outlineCache -= selection->outlineCache();
+    }
 }
 
 void KisPixelSelection::intersectSelection(KisPixelSelectionSP selection)
@@ -160,6 +186,12 @@ void KisPixelSelection::intersectSelection(KisPixelSelectionSP selection)
         dst->nextRow();
         src->nextRow();
     }
+
+    m_d->outlineCacheValid &= selection->outlineCacheValid();
+
+    if (m_d->outlineCacheValid) {
+        m_d->outlineCache &= selection->outlineCache();
+    }
 }
 
 void KisPixelSelection::clear(const QRect & r)
@@ -171,6 +203,13 @@ void KisPixelSelection::clear(const QRect & r)
     } else {
         KisPaintDevice::clear(r);
     }
+
+    if (m_d->outlineCacheValid) {
+        QPainterPath path;
+        path.addRect(r);
+
+        m_d->outlineCache -= path;
+    }
 }
 
 void KisPixelSelection::clear()
@@ -178,6 +217,9 @@ void KisPixelSelection::clear()
     quint8 defPixel = MIN_SELECTED;
     setDefaultPixel(&defPixel);
     KisPaintDevice::clear();
+
+    m_d->outlineCacheValid = true;
+    m_d->outlineCache = QPainterPath();
 }
 
 void KisPixelSelection::invert()
@@ -194,6 +236,13 @@ void KisPixelSelection::invert()
     }
     quint8 defPixel = MAX_SELECTED - *defaultPixel();
     setDefaultPixel(&defPixel);
+
+    if (m_d->outlineCacheValid) {
+        QPainterPath path;
+        path.addRect(defaultBounds()->bounds());
+
+        m_d->outlineCache = path - m_d->outlineCache;
+    }
 }
 
 bool KisPixelSelection::isTotallyUnselected(const QRect & r) const
@@ -236,11 +285,51 @@ QVector<QPolygon> KisPixelSelection::outline() const
     return generator.outline(this, xOffset, yOffset, width, height);
 }
 
+QPainterPath KisPixelSelection::outlineCache() const
+{
+    return m_d->outlineCache;
+}
+
+void KisPixelSelection::setOutlineCache(const QPainterPath &cache)
+{
+    m_d->outlineCache = cache;
+    m_d->outlineCacheValid = true;
+}
+
+bool KisPixelSelection::outlineCacheValid() const
+{
+    return m_d->outlineCacheValid;
+}
+
+void KisPixelSelection::invalidateOutlineCache()
+{
+    m_d->outlineCacheValid = false;
+}
+
+void KisPixelSelection::recalculateOutlineCache()
+{
+    foreach (const QPolygon &polygon, outline()) {
+        m_d->outlineCache.addPolygon(polygon);
+        m_d->outlineCacheValid = true;
+    }
+}
+
 void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection)
 {
     renderToProjection(projection, selectedExactRect());
 }
 
+void KisPixelSelection::notifySelectionChanged()
+{
+    KisNodeSP parentNode;
+    if (!(parentNode = this->parentNode())) return;
+
+    KisNodeGraphListener *listener;
+    if (!(listener = parentNode->graphListener())) return;
+
+    listener->notifySelectionChanged();
+}
+
 void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& rc)
 {
     QRect updateRect = rc & selectedExactRect();
diff --git a/krita/image/kis_pixel_selection.h b/krita/image/kis_pixel_selection.h
index 5d18e8d..74a1faf 100644
--- a/krita/image/kis_pixel_selection.h
+++ b/krita/image/kis_pixel_selection.h
@@ -103,6 +103,16 @@ public:
      */
     QVector<QPolygon> outline() const;
 
+
+    QPainterPath outlineCache() const;
+    bool outlineCacheValid() const;
+    void recalculateOutlineCache();
+
+    void setOutlineCache(const QPainterPath &cache);
+    void invalidateOutlineCache();
+
+    void notifySelectionChanged();
+
     virtual void renderToProjection(KisPaintDeviceSP projection);
     virtual void renderToProjection(KisPaintDeviceSP projection, const QRect& r);
 
diff --git a/krita/image/kis_selection.cc b/krita/image/kis_selection.cc
index b9d9551..e3eb39c 100644
--- a/krita/image/kis_selection.cc
+++ b/krita/image/kis_selection.cc
@@ -145,9 +145,36 @@ bool KisSelection::hasShapeSelection() const
     return m_d->shapeSelection;
 }
 
-QVector<QPolygon> KisSelection::outline() const
+bool KisSelection::outlineCacheValid() const
 {
-    return m_d->getProjection()->outline();
+    return !m_d->pixelSelection ||
+        m_d->pixelSelection->outlineCacheValid();
+}
+
+QPainterPath KisSelection::outlineCache() const
+{
+    QPainterPath outline;
+
+    if (m_d->pixelSelection && m_d->pixelSelection->outlineCacheValid()) {
+        outline += m_d->pixelSelection->outlineCache();
+    }
+
+    if (m_d->shapeSelection && m_d->shapeSelection->outlineCacheValid()) {
+        outline += m_d->shapeSelection->outlineCache();
+    }
+
+    return outline;
+}
+
+void KisSelection::recalculateOutlineCache()
+{
+    if (m_d->pixelSelection && !m_d->pixelSelection->outlineCacheValid()) {
+        m_d->pixelSelection->recalculateOutlineCache();
+    }
+
+    if (m_d->shapeSelection && !m_d->shapeSelection->outlineCacheValid()) {
+        m_d->shapeSelection->recalculateOutlineCache();
+    }
 }
 
 KisPixelSelectionSP KisSelection::pixelSelection() const
diff --git a/krita/image/kis_selection.h b/krita/image/kis_selection.h
index b0e88fa..f6ab03c 100644
--- a/krita/image/kis_selection.h
+++ b/krita/image/kis_selection.h
@@ -91,7 +91,9 @@ public:
     bool hasPixelSelection() const;
     bool hasShapeSelection() const;
 
-    QVector<QPolygon> outline() const;
+    bool outlineCacheValid() const;
+    QPainterPath outlineCache() const;
+    void recalculateOutlineCache();
 
     /**
      * return the pixel selection component of this selection or zero
diff --git a/krita/image/kis_selection_component.h b/krita/image/kis_selection_component.h
index 76c5cf4..4a9dcd2 100644
--- a/krita/image/kis_selection_component.h
+++ b/krita/image/kis_selection_component.h
@@ -47,6 +47,10 @@ public:
         Q_UNUSED(transform);
         return 0;
     }
+
+    virtual QPainterPath outlineCache() const = 0;
+    virtual bool outlineCacheValid() const = 0;
+    virtual void recalculateOutlineCache() = 0;
 };
 
 #endif
diff --git a/krita/image/kis_selection_transaction_data.cpp b/krita/image/kis_selection_transaction_data.cpp
deleted file mode 100644
index 3479136..0000000
--- a/krita/image/kis_selection_transaction_data.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- *  Copyright (c) 2002 Patrick Julien <freak at codepimps.org>
- *  Copyright (c) 2008 Sven Langkamp <sven.langkamp at gmail.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#include "kis_selection_transaction_data.h"
-
-#include "kis_selection.h"
-#include "kis_pixel_selection.h"
-#include "kis_undo_adapter.h"
-
-KisSelectionTransactionData::KisSelectionTransactionData(const QString& name, KisUndoAdapter *undoAdapter, KisSelectionSP selection, KUndo2Command* parent) :
-        KisTransactionData(name, selection->getOrCreatePixelSelection().data(), parent)
-        , m_undoAdapter(undoAdapter)
-        , m_selection(selection)
-{
-}
-
-KisSelectionTransactionData::~KisSelectionTransactionData()
-{
-}
-
-void KisSelectionTransactionData::redo()
-{
-    KisTransactionData::redo();
-    m_selection->updateProjection();
-
-    m_undoAdapter->emitSelectionChanged();
-}
-
-void KisSelectionTransactionData::undo()
-{
-    KisTransactionData::undo();
-    m_selection->updateProjection();
-
-    m_undoAdapter->emitSelectionChanged();
-}
diff --git a/krita/image/kis_selection_transaction_data.h b/krita/image/kis_selection_transaction_data.h
deleted file mode 100644
index 17ba0b8..0000000
--- a/krita/image/kis_selection_transaction_data.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- *  Copyright (c) 2005 C. Boemann <cbo at boemann.dk>
- *  Copyright (c) 2008 Sven Langkamp <sven.langkamp at gmail.com>
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-#ifndef KIS_SELECTION_TRANSACTION_DATA_H_
-#define KIS_SELECTION_TRANSACTION_DATA_H_
-
-#include "kis_transaction_data.h"
-
-#include "kis_selection.h"
-
-class KisUndoAdapter;
-
-/**
- * KisSelectionTransactionData records changes to the selection for the undo stack. There
- * are two selections in Krita: the global selection and the per-layer selection mask.
- * A particular action only works with one of these selections (in the future, we may
- * want to merge the global and local selection).
- *
- * KisSelectionTransactionData remembers which selection was changed.
- */
-class KRITAIMAGE_EXPORT KisSelectionTransactionData : public KisTransactionData
-{
-
-public:
-    KisSelectionTransactionData(const QString& name, KisUndoAdapter *undoAdapter, KisSelectionSP selection, KUndo2Command* parent = 0);
-    virtual ~KisSelectionTransactionData();
-
-public:
-    void redo();
-    void undo();
-
-private:
-    KisUndoAdapter *m_undoAdapter;
-    KisSelectionSP m_selection;
-};
-
-#endif /* KIS_SELECTION_TRANSACTION_DATA_H_ */
diff --git a/krita/image/kis_transaction.h b/krita/image/kis_transaction.h
index 60a24dc..7219217 100644
--- a/krita/image/kis_transaction.h
+++ b/krita/image/kis_transaction.h
@@ -25,8 +25,8 @@
 #include <krita_export.h>
 
 #include "kis_transaction_data.h"
-#include "kis_selection_transaction_data.h"
 #include "kis_paint_device.h"
+#include "kis_pixel_selection.h"
 
 #include "kis_undo_adapter.h"
 #include "kis_post_execution_undo_adapter.h"
@@ -35,7 +35,7 @@ class KisTransaction
 {
 public:
     KisTransaction(const QString& name, KisPaintDeviceSP device, KUndo2Command* parent = 0) {
-        m_transactionData = new KisTransactionData(name, device, parent);
+        m_transactionData = new KisTransactionData(name, device, true, parent);
     }
 
     virtual ~KisTransaction() {
@@ -117,9 +117,9 @@ protected:
 class KisSelectionTransaction : public KisTransaction
 {
 public:
-    KisSelectionTransaction(const QString& name, KisUndoAdapter *undoAdapter, KisSelectionSP selection, KUndo2Command* parent = 0)
+    KisSelectionTransaction(const QString& name, KisPixelSelectionSP pixelSelection, KUndo2Command* parent = 0)
     {
-        m_transactionData = new KisSelectionTransactionData(name, undoAdapter, selection, parent);
+        m_transactionData = new KisTransactionData(name, pixelSelection, false, parent);
     }
 };
 
diff --git a/krita/image/kis_transaction_data.cpp b/krita/image/kis_transaction_data.cpp
index fbc3280..9da324e 100644
--- a/krita/image/kis_transaction_data.cpp
+++ b/krita/image/kis_transaction_data.cpp
@@ -18,6 +18,7 @@
 
 #include "kis_transaction_data.h"
 
+#include "kis_pixel_selection.h"
 #include "kis_paint_device.h"
 #include "kis_datamanager.h"
 
@@ -43,11 +44,20 @@ public:
     bool transactionFinished;
     QPoint oldOffset;
     QPoint newOffset;
+
+    bool savedOutlineCacheValid;
+    QPainterPath savedOutlineCache;
 };
 
-KisTransactionData::KisTransactionData(const QString& name, KisPaintDeviceSP device, KUndo2Command* parent)
-        : KUndo2Command(name, parent)
-        , m_d(new Private())
+KisTransactionData::KisTransactionData(const QString& name, KisPaintDeviceSP device, bool resetSelectionOutlineCache, KUndo2Command* parent)
+    : KUndo2Command(name, parent)
+    , m_d(new Private())
+{
+    init(device);
+    saveSelectionOutlineCache(resetSelectionOutlineCache);
+}
+
+void KisTransactionData::init(KisPaintDeviceSP device)
 {
     m_d->device = device;
     DEBUG_ACTION("Transaction started");
@@ -93,14 +103,27 @@ void KisTransactionData::startUpdates()
     m_d->device->setDirty(rc);
 }
 
+void KisTransactionData::possiblyNotifySelectionChanged()
+{
+    KisPixelSelectionSP pixelSelection =
+        dynamic_cast<KisPixelSelection*>(m_d->device.data());
+
+    if (pixelSelection) {
+        pixelSelection->notifySelectionChanged();
+    }
+}
+
 void KisTransactionData::redo()
 {
     //KUndo2QStack calls redo(), so the first call needs to be blocked
     if (m_d->firstRedo) {
         m_d->firstRedo = false;
+        possiblyNotifySelectionChanged();
         return;
     }
 
+    restoreSelectionOutlineCache();
+
     DEBUG_ACTION("Redo()");
 
     Q_ASSERT(m_d->memento);
@@ -111,6 +134,7 @@ void KisTransactionData::redo()
     }
 
     startUpdates();
+    possiblyNotifySelectionChanged();
 }
 
 void KisTransactionData::undo()
@@ -124,11 +148,54 @@ void KisTransactionData::undo()
         m_d->device->move(m_d->oldOffset);
     }
 
+    restoreSelectionOutlineCache();
+
     startUpdates();
+    possiblyNotifySelectionChanged();
 }
 
-void KisTransactionData::undoNoUpdate()
+void KisTransactionData::saveSelectionOutlineCache(bool invalidateCache)
 {
-    Q_ASSERT(m_d->memento);
-    m_d->device->dataManager()->rollback(m_d->memento);
+    m_d->savedOutlineCacheValid = false;
+
+    KisPixelSelectionSP pixelSelection =
+        dynamic_cast<KisPixelSelection*>(m_d->device.data());
+
+    if (pixelSelection) {
+        m_d->savedOutlineCacheValid = pixelSelection->outlineCacheValid();
+        if (m_d->savedOutlineCacheValid) {
+            m_d->savedOutlineCache = pixelSelection->outlineCache();
+
+            if (invalidateCache) {
+                pixelSelection->invalidateOutlineCache();
+            }
+        }
+    }
+}
+
+void KisTransactionData::restoreSelectionOutlineCache()
+{
+    KisPixelSelectionSP pixelSelection =
+        dynamic_cast<KisPixelSelection*>(m_d->device.data());
+
+    if (pixelSelection) {
+        bool savedOutlineCacheValid;
+        QPainterPath savedOutlineCache;
+
+        savedOutlineCacheValid = pixelSelection->outlineCacheValid();
+        if (savedOutlineCacheValid) {
+            savedOutlineCache = pixelSelection->outlineCache();
+        }
+
+        if (m_d->savedOutlineCacheValid) {
+            pixelSelection->setOutlineCache(m_d->savedOutlineCache);
+        } else {
+            pixelSelection->invalidateOutlineCache();
+        }
+
+        m_d->savedOutlineCacheValid = savedOutlineCacheValid;
+        if (m_d->savedOutlineCacheValid) {
+            m_d->savedOutlineCache = savedOutlineCache;
+        }
+    }
 }
diff --git a/krita/image/kis_transaction_data.h b/krita/image/kis_transaction_data.h
index 473427e..a3e3f82 100644
--- a/krita/image/kis_transaction_data.h
+++ b/krita/image/kis_transaction_data.h
@@ -36,7 +36,7 @@
 class KRITAIMAGE_EXPORT KisTransactionData : public KUndo2Command
 {
 public:
-    KisTransactionData(const QString& name, KisPaintDeviceSP device, KUndo2Command* parent = 0);
+    KisTransactionData(const QString& name, KisPaintDeviceSP device, bool resetSelectionOutlineCache, KUndo2Command* parent);
     virtual ~KisTransactionData();
 
 public:
@@ -46,10 +46,13 @@ public:
     virtual void endTransaction();
 
 protected:
-    virtual void undoNoUpdate();
+    virtual void saveSelectionOutlineCache(bool invalidateCache);
+    virtual void restoreSelectionOutlineCache();
 
 private:
+    void init(KisPaintDeviceSP device);
     void startUpdates();
+    void possiblyNotifySelectionChanged();
 
 private:
     class Private;
diff --git a/krita/image/kis_transform_visitor.h b/krita/image/kis_transform_visitor.h
index 0b825c7..da1149c 100644
--- a/krita/image/kis_transform_visitor.h
+++ b/krita/image/kis_transform_visitor.h
@@ -167,11 +167,12 @@ private:
     void transformMask(KisMask* mask) {
         KisSelectionSP selection = mask->selection();
         if(selection->hasPixelSelection() && !m_scaleOnlyShapes) {
-            KisSelectionTransaction transaction(QString(), m_image->undoAdapter(), selection);
+            KisPixelSelectionSP pixelSelection = selection->getOrCreatePixelSelection().data();
 
-            KisPaintDeviceSP dev = selection->getOrCreatePixelSelection().data();
-            KisTransformWorker tw(dev, m_sx, m_sy, 0.0, 0.0, 0.0, 0.0, m_angle, m_tx, m_ty, m_progress, m_filter);
+            KisSelectionTransaction transaction(QString(), pixelSelection);
+            KisTransformWorker tw(pixelSelection, m_sx, m_sy, 0.0, 0.0, 0.0, 0.0, m_angle, m_tx, m_ty, m_progress, m_filter);
             tw.run();
+            tw.transformPixelSelectionOutline(pixelSelection);
 
             transaction.commit(m_image->undoAdapter());
         }
diff --git a/krita/image/kis_transform_worker.cc b/krita/image/kis_transform_worker.cc
index a522cfa..b1f0e7e 100644
--- a/krita/image/kis_transform_worker.cc
+++ b/krita/image/kis_transform_worker.cc
@@ -81,6 +81,14 @@ QTransform KisTransformWorker::transform() const
     return TS.inverted() * S * TS * SC * R * T;
 }
 
+void KisTransformWorker::transformPixelSelectionOutline(KisPixelSelectionSP pixelSelection) const
+{
+    if (pixelSelection->outlineCacheValid()) {
+        QPainterPath outlineCache = pixelSelection->outlineCache();
+        pixelSelection->setOutlineCache(transform().map(outlineCache));
+    }
+}
+
 QRect rotateWithTf(int rotation, KisPaintDeviceSP dev,
                    QRect boundRect,
                    KoUpdaterPtr progressUpdater,
diff --git a/krita/image/kis_transform_worker.h b/krita/image/kis_transform_worker.h
index fc5a19a..30909a14 100644
--- a/krita/image/kis_transform_worker.h
+++ b/krita/image/kis_transform_worker.h
@@ -109,6 +109,11 @@ public:
      */
     QTransform transform() const;
 
+    /**
+     * Transforms the outline of the pixel selection (if it is valid)
+     */
+    void transformPixelSelectionOutline(KisPixelSelectionSP pixelSelection) const;
+
 private:
     // XXX (BSAR): Why didn't we use the shared-pointer versions of the paint device classes?
     // CBR: because the template functions used within don't work if it's not true pointers
diff --git a/krita/image/tests/kis_pixel_selection_test.cpp b/krita/image/tests/kis_pixel_selection_test.cpp
index 8830d2d..1bae066 100644
--- a/krita/image/tests/kis_pixel_selection_test.cpp
+++ b/krita/image/tests/kis_pixel_selection_test.cpp
@@ -34,6 +34,7 @@
 #include "testutil.h"
 #include "kis_fill_painter.h"
 #include "kis_transaction.h"
+#include "kis_surrogate_undo_adapter.h"
 #include "commands/kis_selection_commands.h"
 
 
@@ -280,6 +281,103 @@ void KisPixelSelectionTest::testCrossColorSpacePainting()
     QCOMPARE(psel1->selectedExactRect(), r0 | r3);
 }
 
+void KisPixelSelectionTest::testOutlineCache()
+{
+    KisPixelSelectionSP psel1 = new KisPixelSelection();
+    KisPixelSelectionSP psel2 = new KisPixelSelection();
+
+    QVERIFY(psel1->outlineCacheValid());
+    QVERIFY(psel2->outlineCacheValid());
+
+    psel1->select(QRect(10,10,90,90), 100);
+    QVERIFY(psel1->outlineCacheValid());
+    QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+
+    psel2->select(QRect(20,20,100,100), 200);
+    QVERIFY(psel2->outlineCacheValid());
+    QCOMPARE(psel2->outlineCache().boundingRect(), QRectF(20,20,100,100));
+
+    psel1->applySelection(psel2, SELECTION_ADD);
+    QVERIFY(psel1->outlineCacheValid());
+    QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,110,110));
+
+    psel1->applySelection(psel2, SELECTION_INTERSECT);
+    QVERIFY(psel1->outlineCacheValid());
+    QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(20,20,100,100));
+
+    psel2->invalidateOutlineCache();
+    QVERIFY(!psel2->outlineCacheValid());
+
+    psel1->applySelection(psel2, SELECTION_SUBTRACT);
+    QVERIFY(!psel1->outlineCacheValid());
+
+    psel1->clear();
+    QVERIFY(psel1->outlineCacheValid());
+}
+
+void KisPixelSelectionTest::testOutlineCacheTransactions()
+{
+    KisSurrogateUndoAdapter undoAdapter;
+    KisPixelSelectionSP psel1 = new KisPixelSelection();
+
+    QVERIFY(psel1->outlineCacheValid());
+
+    psel1->clear();
+    psel1->select(QRect(10,10,90,90), 100);
+    QVERIFY(psel1->outlineCacheValid());
+    QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+
+    {
+        KisTransaction t("", psel1);
+        t.end();
+        QVERIFY(!psel1->outlineCacheValid());
+    }
+
+    psel1->clear();
+    psel1->select(QRect(10,10,90,90), 100);
+    QVERIFY(psel1->outlineCacheValid());
+    QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+
+    {
+        KisTransaction t("", psel1);
+        t.revert();
+        QVERIFY(psel1->outlineCacheValid());
+        QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+    }
+
+    psel1->clear();
+    psel1->select(QRect(10,10,90,90), 100);
+    QVERIFY(psel1->outlineCacheValid());
+    QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+
+    {
+        KisSelectionTransaction t("", psel1);
+
+        QVERIFY(psel1->outlineCacheValid());
+        QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+
+        psel1->select(QRect(10,10,200,200));
+
+        QVERIFY(psel1->outlineCacheValid());
+        QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,200,200));
+
+        t.commit(&undoAdapter);
+
+        QVERIFY(psel1->outlineCacheValid());
+        QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,200,200));
+
+        undoAdapter.undo();
+
+        QVERIFY(psel1->outlineCacheValid());
+        QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,90,90));
+
+        undoAdapter.redo();
+
+        QVERIFY(psel1->outlineCacheValid());
+        QCOMPARE(psel1->outlineCache().boundingRect(), QRectF(10,10,200,200));
+    }
+}
+
 QTEST_KDEMAIN(KisPixelSelectionTest, NoGUI)
 #include "kis_pixel_selection_test.moc"
 
diff --git a/krita/image/tests/kis_pixel_selection_test.h b/krita/image/tests/kis_pixel_selection_test.h
index a7c9358..be04851 100644
--- a/krita/image/tests/kis_pixel_selection_test.h
+++ b/krita/image/tests/kis_pixel_selection_test.h
@@ -41,8 +41,10 @@ private slots:
     void testExactRectWithImage();
     void testUndo();
     void testInvertWithImage();
-
     void testCrossColorSpacePainting();
+    void testOutlineCache();
+
+    void testOutlineCacheTransactions();
 };
 
 #endif
diff --git a/krita/plugins/extensions/colorrange/colorrange.cc b/krita/plugins/extensions/colorrange/colorrange.cc
index 7c09809..b8d07bf 100644
--- a/krita/plugins/extensions/colorrange/colorrange.cc
+++ b/krita/plugins/extensions/colorrange/colorrange.cc
@@ -107,6 +107,7 @@ void ColorRange::selectOpaque()
         selIter->nextRow();
     }
 
+    tmpSel->invalidateOutlineCache();
     helper.selectPixelSelection(tmpSel, SELECTION_ADD);
 }
 
diff --git a/krita/plugins/extensions/colorrange/dlg_colorrange.cc b/krita/plugins/extensions/colorrange/dlg_colorrange.cc
index 935bd48..14534ca 100644
--- a/krita/plugins/extensions/colorrange/dlg_colorrange.cc
+++ b/krita/plugins/extensions/colorrange/dlg_colorrange.cc
@@ -311,6 +311,7 @@ void DlgColorRange::slotSelectClicked()
         selIter->nextRow();
     }
 
+    selection->pixelSelection()->invalidateOutlineCache();
     KisSelectionToolHelper helper(m_view->canvasBase(), i18n("Color Range Selection"));
     helper.selectPixelSelection(selection->pixelSelection(), m_mode);
 
diff --git a/krita/plugins/extensions/imagesize/imagesize.cc b/krita/plugins/extensions/imagesize/imagesize.cc
index 5489855..854589e 100644
--- a/krita/plugins/extensions/imagesize/imagesize.cc
+++ b/krita/plugins/extensions/imagesize/imagesize.cc
@@ -178,17 +178,20 @@ void ImageSize::slotSelectionScale()
     QPointer<KoUpdater> u = pu->startSubtask();
 
     if (dlgSize->exec() == QDialog::Accepted) {
-        KisSelectionTransaction transaction(i18n("Scale Selection"), image->undoAdapter(), selection);
+        KisPixelSelectionSP pixelSelection = selection->getOrCreatePixelSelection().data();
+        KisSelectionTransaction transaction(i18n("Scale Selection"), pixelSelection);
 
         qint32 w = dlgSize->width();
         qint32 h = dlgSize->height();
-        KisTransformWorker worker(selection->getOrCreatePixelSelection(),
+        KisTransformWorker worker(pixelSelection,
                                   (double)w / ((double)(rc.width())),
                                   (double)h / ((double)(rc.height())),
                                   0, 0, 0.0, 0.0, 0.0, 0, 0, u,
                                   dlgSize->filterType()
                                  );
         worker.run();
+        worker.transformPixelSelectionOutline(pixelSelection);
+
         transaction.commit(image->undoAdapter());
         layer->setDirty();
         pu->deleteLater();
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_brush.cc b/krita/plugins/tools/selectiontools/kis_tool_select_brush.cc
index abb1357..81db9ef 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_brush.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_brush.cc
@@ -185,7 +185,7 @@ void KisToolSelectBrush::slotSetBrushSize(int size)
     m_brushRadius = ((qreal) size)/2.0;
 }
 
-void KisToolSelectBrush::applyToSelection(const QPainterPath &selection) {
+void KisToolSelectBrush::applyToSelection(QPainterPath selection) {
     KisCanvas2 * kisCanvas = dynamic_cast<KisCanvas2*>(canvas());
     Q_ASSERT(kisCanvas);
     if (!kisCanvas)
@@ -205,7 +205,9 @@ void KisToolSelectBrush::applyToSelection(const QPainterPath &selection) {
         painter.setFillStyle(KisPainter::FillStyleForegroundColor);
         painter.setStrokeStyle(KisPainter::StrokeStyleNone);
 
+        selection.closeSubpath();
         painter.fillPainterPath(selection);
+        tmpSel->setOutlineCache(selection);
 
         helper.selectPixelSelection(tmpSel, selectionAction());
 
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_brush.h b/krita/plugins/tools/selectiontools/kis_tool_select_brush.h
index fd66d3a..cab3a94 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_brush.h
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_brush.h
@@ -51,7 +51,7 @@ public slots:
     void slotSetBrushSize(int size);
 
 protected:
-    void applyToSelection(const QPainterPath& selection);
+    void applyToSelection(QPainterPath selection);
     void resetSelection();
     void addPoint(const QPointF& point);
     void addGap(const QPointF& start, const QPointF& end);
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_contiguous.cc b/krita/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
index fa1858c..f9f38df 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_contiguous.cc
@@ -103,6 +103,8 @@ void KisToolSelectContiguous::mousePressEvent(KoPointerEvent *event)
             QApplication::restoreOverrideCursor();
             return;
         }
+
+        selection->pixelSelection()->invalidateOutlineCache();
         KisSelectionToolHelper helper(kisCanvas, i18n("Contiguous Area Selection"));
         helper.selectPixelSelection(selection->pixelSelection(), selectionAction());
 
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_elliptical.cc b/krita/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
index 388c6ac..e8cbbb0 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_elliptical.cc
@@ -82,6 +82,10 @@ void KisToolSelectElliptical::finishEllipse(const QRectF &rect)
 
         painter.paintEllipse(rect);
 
+        QPainterPath cache;
+        cache.addEllipse(rect);
+        tmpSel->setOutlineCache(cache);
+
         helper.selectPixelSelection(tmpSel, m_widgetHelper.selectionAction());
     } else {
         QRectF ptRect = convertToPt(rect);
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_magnetic.cc b/krita/plugins/tools/selectiontools/kis_tool_select_magnetic.cc
index 8d69ce1..3f990a3 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_magnetic.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_magnetic.cc
@@ -478,7 +478,7 @@ void KisToolSelectMagnetic::LocalTool::addPathShape(KoPathShape* pathShape)
     KisSelectionToolHelper helper(kisCanvas, i18n("Path Selection"));
 
 
-    KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
+    KisPixelSelectionSP tmpSel = new KisPixelSelection();
 
     KisPainter painter(tmpSel);
     painter.setBounds(m_selectingTool->currentImage()->bounds());
@@ -488,7 +488,13 @@ void KisToolSelectMagnetic::LocalTool::addPathShape(KoPathShape* pathShape)
     painter.setOpacity(OPACITY_OPAQUE_U8);
     painter.setCompositeOp(tmpSel->colorSpace()->compositeOp(COMPOSITE_OVER));
 
-    painter.paintPolygon(QPolygonF(m_detectedBorder));
+    QPolygonF path = QPolygonF(m_detectedBorder);
+    painter.paintPolygon(path);
+
+    QPainterPath cache;
+    cache.addPolygon(path);
+    cache.closeSubpath();
+    tmpSel.setOutlineCache(cache);
 
     helper.selectPixelSelection(tmpSel, m_selectingTool->m_selectAction);
 
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_outline.cc b/krita/plugins/tools/selectiontools/kis_tool_select_outline.cc
index 2baed27..4a42fdb 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_outline.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_outline.cc
@@ -128,6 +128,11 @@ void KisToolSelectOutline::mouseReleaseEvent(KoPointerEvent *event)
 
                 painter.paintPolygon(m_points);
 
+                QPainterPath cache;
+                cache.addPolygon(m_points);
+                cache.closeSubpath();
+                tmpSel->setOutlineCache(cache);
+
                 helper.selectPixelSelection(tmpSel, selectionAction());
             } else {
 
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_path.cc b/krita/plugins/tools/selectiontools/kis_tool_select_path.cc
index 09b7af8..7913a71 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_path.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_path.cc
@@ -104,7 +104,10 @@ void KisToolSelectPath::LocalTool::addPathShape(KoPathShape* pathShape)
         QTransform matrix;
         matrix.scale(image->xRes(), image->yRes());
         matrix.translate(pathShape->position().x(), pathShape->position().y());
-        painter.fillPainterPath(matrix.map(pathShape->outline()));
+
+        QPainterPath path = matrix.map(pathShape->outline());
+        painter.fillPainterPath(path);
+        tmpSel->setOutlineCache(path);
 
         helper.selectPixelSelection(tmpSel, m_selectionTool->selectionAction());
 
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_polygonal.cc b/krita/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
index f37abac..6f0a93f 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_polygonal.cc
@@ -82,6 +82,11 @@ void KisToolSelectPolygonal::finishPolyline(const QVector<QPointF> &points)
 
         painter.paintPolygon(points);
 
+        QPainterPath cache;
+        cache.addPolygon(points);
+        cache.closeSubpath();
+        tmpSel->setOutlineCache(cache);
+
         helper.selectPixelSelection(tmpSel, m_widgetHelper.selectionAction());
     } else {
         KoPathShape* path = new KoPathShape();
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_rectangular.cc b/krita/plugins/tools/selectiontools/kis_tool_select_rectangular.cc
index 281cee6..f886d6c 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_rectangular.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_rectangular.cc
@@ -81,6 +81,11 @@ void KisToolSelectRectangular::finishRect(const QRectF& rect)
         if (rc.isValid()) {
             KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
             tmpSel->select(rc);
+
+            QPainterPath cache;
+            cache.addRect(rc);
+            tmpSel->setOutlineCache(cache);
+
             helper.selectPixelSelection(tmpSel, m_widgetHelper.selectionAction());
         }
     } else {
diff --git a/krita/plugins/tools/selectiontools/kis_tool_select_similar.cc b/krita/plugins/tools/selectiontools/kis_tool_select_similar.cc
index f72a0b7..e277527 100644
--- a/krita/plugins/tools/selectiontools/kis_tool_select_similar.cc
+++ b/krita/plugins/tools/selectiontools/kis_tool_select_similar.cc
@@ -112,6 +112,7 @@ void KisToolSelectSimilar::mousePressEvent(KoPointerEvent *event)
         KisPixelSelectionSP tmpSel = KisPixelSelectionSP(new KisPixelSelection());
         selectByColor(dev, tmpSel, c.data(), m_fuzziness);
 
+        tmpSel->invalidateOutlineCache();
         KisSelectionToolHelper helper(kisCanvas, i18n("Similar Selection"));
         helper.selectPixelSelection(tmpSel, selectionAction());
 
diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform.cc b/krita/plugins/tools/tool_transform2/kis_tool_transform.cc
index 91e8e50..9a17fdb 100644
--- a/krita/plugins/tools/tool_transform2/kis_tool_transform.cc
+++ b/krita/plugins/tools/tool_transform2/kis_tool_transform.cc
@@ -1927,23 +1927,19 @@ void KisToolTransform::updateSelectionPath()
 {
     m_selectionPath = QPainterPath();
 
-    QVector<QPolygon> selectionOutline;
+    QPainterPath selectionOutline;
     KisSelectionSP selection = currentSelection();
 
-    if (selection) {
-        selectionOutline = selection->outline();
+    if (selection && selection->outlineCacheValid()) {
+        selectionOutline = selection->outlineCache();
     } else {
-        selectionOutline << m_selectedPortionCache->exactBounds();
+        selectionOutline.addRect(m_selectedPortionCache->exactBounds());
     }
 
     const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
     QTransform i2f = converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
 
-    foreach(const QPolygon &polygon, selectionOutline) {
-        QPolygon p = i2f.map(polygon);
-
-        m_selectionPath.addPolygon(p);
-    }
+    m_selectionPath = i2f.map(selectionOutline);
 }
 
 void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice)
@@ -2053,7 +2049,7 @@ void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode)
             !currentNode->paintDevice();
     }
 
-    TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, currentSelection(), image()->postExecutionUndoAdapter(), image()->undoAdapter());
+    TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode, currentSelection(), image()->postExecutionUndoAdapter());
     KisPaintDeviceSP previewDevice = strategy->previewDevice();
 
     KisSelectionSP selection = currentSelection();
diff --git a/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp b/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
index c2bdee7..7801823 100644
--- a/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
+++ b/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
@@ -34,11 +34,9 @@
 
 TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode,
                                                  KisSelectionSP selection,
-                                                 KisPostExecutionUndoAdapter *undoAdapter,
-                                                 KisUndoAdapter *legacyUndoAdapter)
+                                                 KisPostExecutionUndoAdapter *undoAdapter)
     : KisStrokeStrategyUndoCommandBased(i18n("Transform Stroke"), false, undoAdapter),
-      m_selection(selection),
-      m_legacyUndoAdapter(legacyUndoAdapter)
+      m_selection(selection)
 {
     if (rootNode->childCount() || !rootNode->paintDevice()) {
         m_previewDevice = createDeviceCache(rootNode->projection());
@@ -138,7 +136,11 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
             m_selection->flatten();
             Q_ASSERT(m_selection->pixelSelection());
 
-            KisSelectionTransaction transaction("Transform Selection", m_legacyUndoAdapter, m_selection);
+            /**
+             * We use usual transaction here, because we cannot calsulate
+             * transformation for perspective and warp workers.
+             */
+            KisTransaction transaction("Transform Selection", m_selection->pixelSelection());
 
             KisProcessingVisitor::ProgressHelper helper(td->node);
             transformDevice(td->config,
@@ -148,8 +150,6 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
             runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
                               KisStrokeJobData::CONCURRENT,
                               KisStrokeJobData::NORMAL);
-
-            m_legacyUndoAdapter->emitSelectionChanged();
         }
     } else if (csd) {
         KisPaintDeviceSP device = csd->node->paintDevice();
diff --git a/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h b/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
index 5db6a9d..308336b 100644
--- a/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
+++ b/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
@@ -69,8 +69,7 @@ public:
 public:
     TransformStrokeStrategy(KisNodeSP rootNode,
                             KisSelectionSP selection,
-                            KisPostExecutionUndoAdapter *undoAdapter,
-                            KisUndoAdapter *legacyUndoAdapter);
+                            KisPostExecutionUndoAdapter *undoAdapter);
 
     ~TransformStrokeStrategy();
 
@@ -102,7 +101,6 @@ private:
 
 private:
     KisSelectionSP m_selection;
-    KisUndoAdapter *m_legacyUndoAdapter;
 
     QMutex m_devicesCacheMutex;
     QHash<KisPaintDevice*, KisPaintDeviceSP> m_devicesCacheHash;
diff --git a/krita/ui/actions/kis_selection_action_factories.cpp b/krita/ui/actions/kis_selection_action_factories.cpp
index 4f72850..150b84f 100644
--- a/krita/ui/actions/kis_selection_action_factories.cpp
+++ b/krita/ui/actions/kis_selection_action_factories.cpp
@@ -108,7 +108,7 @@ void KisSelectAllActionFactory::run(KisView2 *view)
         KisImageSP m_image;
         KUndo2Command* paint() {
             KisSelectionSP selection = m_image->globalSelection();
-            KisSelectionTransaction transaction(QString(), m_image->undoAdapter(), selection);
+            KisSelectionTransaction transaction(QString(), selection->getOrCreatePixelSelection());
             selection->getOrCreatePixelSelection()->select(m_image->bounds());
             return transaction.endAndTake();
         }
diff --git a/krita/ui/flake/kis_shape_selection.cpp b/krita/ui/flake/kis_shape_selection.cpp
index 6c34177..344bd76 100644
--- a/krita/ui/flake/kis_shape_selection.cpp
+++ b/krita/ui/flake/kis_shape_selection.cpp
@@ -292,22 +292,37 @@ bool KisShapeSelection::loadSelection(KoStore* store)
 
 }
 
-QPainterPath KisShapeSelection::selectionOutline()
+QPainterPath KisShapeSelection::outlineCache() const
 {
+    // TODO: remove this lazyness and do it more efficiently
     if (m_dirty) {
-        QList<KoShape*> shapesList = shapes();
-
-        QPainterPath outline;
-        foreach(KoShape * shape, shapesList) {
-            QTransform shapeMatrix = shape->absoluteTransformation(0);
-            outline = outline.united(shapeMatrix.map(shape->outline()));
-        }
-        m_outline = outline;
-        m_dirty = false;
+        const_cast<KisShapeSelection*>(this)->recalculateOutlineCache();
     }
+
     return m_outline;
 }
 
+bool KisShapeSelection::outlineCacheValid() const
+{
+    // TODO: remove the lazyness
+    return true;
+}
+
+void KisShapeSelection::recalculateOutlineCache()
+{
+    if (!m_dirty) return;
+
+    QList<KoShape*> shapesList = shapes();
+
+    QPainterPath outline;
+    foreach(KoShape * shape, shapesList) {
+        QTransform shapeMatrix = shape->absoluteTransformation(0);
+        outline = outline.united(shapeMatrix.map(shape->outline()));
+    }
+    m_outline = outline;
+    m_dirty = false;
+}
+
 void KisShapeSelection::paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &)
 {
     Q_UNUSED(painter);
@@ -321,7 +336,7 @@ void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection)
     QTransform resolutionMatrix;
     resolutionMatrix.scale(m_image->xRes(), m_image->yRes());
 
-    QRectF boundingRect = resolutionMatrix.mapRect(selectionOutline().boundingRect());
+    QRectF boundingRect = resolutionMatrix.mapRect(outlineCache().boundingRect());
     renderSelection(projection, boundingRect.toAlignedRect());
 }
 
@@ -355,7 +370,7 @@ void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect
 
             maskPainter.fillRect(polygonMaskImage.rect(), QColor(OPACITY_TRANSPARENT_U8, OPACITY_TRANSPARENT_U8, OPACITY_TRANSPARENT_U8, 255));
             maskPainter.translate(-x, -y);
-            maskPainter.fillPath(resolutionMatrix.map(selectionOutline()), QColor(OPACITY_OPAQUE_U8, OPACITY_OPAQUE_U8, OPACITY_OPAQUE_U8, 255));
+            maskPainter.fillPath(resolutionMatrix.map(outlineCache()), QColor(OPACITY_OPAQUE_U8, OPACITY_OPAQUE_U8, OPACITY_OPAQUE_U8, 255));
             maskPainter.translate(x, y);
 
             qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH);
diff --git a/krita/ui/flake/kis_shape_selection.h b/krita/ui/flake/kis_shape_selection.h
index 8375566..0e715f0 100644
--- a/krita/ui/flake/kis_shape_selection.h
+++ b/krita/ui/flake/kis_shape_selection.h
@@ -71,7 +71,9 @@ public:
 
     virtual void setDirty();
 
-    virtual QPainterPath selectionOutline();
+    QPainterPath outlineCache() const;
+    bool outlineCacheValid() const;
+    void recalculateOutlineCache();
 
     KoShapeManager *shapeManager() const;
 
@@ -79,8 +81,6 @@ public:
     void moveY(qint32 y);
 
     KUndo2Command* transform(const QTransform &transform);
-
-
 protected:
 
     virtual void paintComponent(QPainter& painter, const KoViewConverter& converter, KoShapePaintingContext &paintcontext);
@@ -90,8 +90,8 @@ private:
     void renderSelection(KisPaintDeviceSP projection, const QRect& r);
 
     KisImageWSP m_image;
-    QPainterPath m_outline;
-    bool m_dirty;
+    mutable QPainterPath m_outline;
+    mutable bool m_dirty;
     KisImageViewConverter* m_converter;
     KisShapeSelectionCanvas* m_canvas;
     KisShapeSelectionModel* m_model;
diff --git a/krita/ui/kis_mirror_visitor.cpp b/krita/ui/kis_mirror_visitor.cpp
index 2fe06b2..0e658af 100644
--- a/krita/ui/kis_mirror_visitor.cpp
+++ b/krita/ui/kis_mirror_visitor.cpp
@@ -129,7 +129,9 @@ bool KisMirrorVisitor::mirrorMask(KisMask* mask)
         } else {
             name = i18n("Mirror Mask Y");
         }
-        KisSelectionTransaction transaction(name, m_image->undoAdapter(), selection);
+
+        // TODO: use a selection transaction to cache the outline
+        KisTransaction transaction(name, dev);
 
         QRect dirty;
         if (m_orientation == Qt::Horizontal) {
diff --git a/krita/ui/kis_selection_decoration.cc b/krita/ui/kis_selection_decoration.cc
index 09c15e8..ffd45d7 100644
--- a/krita/ui/kis_selection_decoration.cc
+++ b/krita/ui/kis_selection_decoration.cc
@@ -92,12 +92,15 @@ void KisSelectionDecoration::selectionChanged()
         m_outline.clear();
 
         if (selection) {
-            if (selection->hasPixelSelection() || selection->hasShapeSelection()) {
-                if (!m_timer->isActive())
+            if (selection->outlineCacheValid()) {
+                if (!m_timer->isActive()) {
                     m_timer->start(300);
+                }
+
+                m_outlinePath = selection->outlineCache();
+            } else {
+                m_outlinePath = QPainterPath();
             }
-            m_outline = selection->outline();
-            updateSimpleOutline();
         } else {
             m_timer->stop();
         }
@@ -130,6 +133,7 @@ void KisSelectionDecoration::selectionTimerEvent()
     }
 }
 
+// Let it stay here for now
 void KisSelectionDecoration::updateSimpleOutline()
 {
     m_simpleOutline.clear();
@@ -190,7 +194,10 @@ void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRe
 
         int i = 0;
         gc.setPen(pen);
-        if (0.5 * (scaleX + scaleY) < 3) {
+
+        if (!m_outlinePath.isEmpty()) {
+            gc.drawPath(transform.map(m_outlinePath));
+        } else if (0.5 * (scaleX + scaleY) < 3) {
             foreach(const QPolygon & polygon, m_simpleOutline) {
                 gc.drawPolygon(transform.map(polygon));
                 i++;
diff --git a/krita/ui/kis_selection_decoration.h b/krita/ui/kis_selection_decoration.h
index fc234b0..10c6d07 100644
--- a/krita/ui/kis_selection_decoration.h
+++ b/krita/ui/kis_selection_decoration.h
@@ -49,6 +49,7 @@ private:
 private:
     QVector<QPolygon> m_outline;
     QVector<QPolygon> m_simpleOutline;
+    QPainterPath m_outlinePath;
     QTimer* m_timer;
     int m_offset;
     QList<QBrush> m_brushes;
diff --git a/krita/ui/operations/kis_filter_selection_operation.cpp b/krita/ui/operations/kis_filter_selection_operation.cpp
index 97a90eb..c105935 100644
--- a/krita/ui/operations/kis_filter_selection_operation.cpp
+++ b/krita/ui/operations/kis_filter_selection_operation.cpp
@@ -43,8 +43,8 @@ void KisFilterSelectionOperation::runFilter(KisSelectionFilter* filter, KisView2
         KisSelectionFilter *m_filter;
 
         KUndo2Command* paint() {
-            KisSelectionTransaction transaction("", m_image->undoAdapter(), m_sel);
             KisPixelSelectionSP mergedSelection = m_sel->getOrCreatePixelSelection();
+            KisTransaction transaction("", mergedSelection);
             QRect processingRect = m_filter->changeRect(mergedSelection->selectedExactRect());
             m_filter->process(mergedSelection, processingRect);
             m_sel->setDirty(processingRect); // check if really needed
diff --git a/krita/ui/tool/kis_selection_tool_helper.cpp b/krita/ui/tool/kis_selection_tool_helper.cpp
index c0afe87..fb8b20a 100644
--- a/krita/ui/tool/kis_selection_tool_helper.cpp
+++ b/krita/ui/tool/kis_selection_tool_helper.cpp
@@ -63,9 +63,8 @@ void KisSelectionToolHelper::selectPixelSelection(KisPixelSelectionSP selection,
         undoAdapter->addCommand(new KisSetEmptyGlobalSelectionCommand(m_image));
     }
 
-    KisSelectionTransaction transaction(m_name, m_image->undoAdapter(), view->selection());
-
     KisPixelSelectionSP pixelSelection = view->selection()->getOrCreatePixelSelection();
+    KisSelectionTransaction transaction(m_name, pixelSelection);
 
     if (!hasSelection && action == SELECTION_SUBTRACT) {
         pixelSelection->invert();
@@ -103,9 +102,9 @@ void KisSelectionToolHelper::addSelectionShape(KoShape* shape)
         undoAdapter->addCommand(new KisSetEmptyGlobalSelectionCommand(m_image));
     }
 
-    KisSelectionSP selection = view->selection();
-    KisSelectionTransaction transaction(m_name, m_image->undoAdapter(), selection);
-
+    KisPixelSelectionSP pixelSelection = view->selection()->getOrCreatePixelSelection();
+    KisSelectionTransaction transaction(m_name, pixelSelection);
+    pixelSelection->clear();
     transaction.commit(undoAdapter);
 
     KUndo2Command *cmd = m_canvas->shapeController()->addShape(shape);



More information about the kimageshop mailing list