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

List:       kde-kimageshop
Subject:    [calligra/krita-chili-kazakov] krita: First ready-for-testing version of the Transform Mask function
From:       Dmitry Kazakov <dimula73 () gmail ! com>
Date:       2014-10-27 16:32:12
Message-ID: E1XinDI-0002Zy-DO () scm ! kde ! org
[Download RAW message or body]

Git commit 2d16e0611550095db4ad674c1008ffb2d0e0c2a6 by Dmitry Kazakov.
Committed on 27/10/2014 at 15:31.
Pushed by dkazakov into branch 'krita-chili-kazakov'.

First ready-for-testing version of the Transform Mask functionality

1) Just create a Transform Mask and edit it with usual Transform Tool
2) All the transformation modes are supported.
2.1) Affine transforms (Free and Perspective) have dynamic preview. That
     is you can paint on a layer and the mask will be updated dynamically
2.1) The other types are updated "statically", that is after 3 sec after
     the last change in the original layer.

Question for testers: isn't this 3 sec delay too long? Probably we should
                      make it shorter?

3) The transformation is written into the mask after you click Apply button.
4) The quality of the affine preview and the final transform (after 3 sec
   delay) differs a bit. That is expected.
5) You can also apply a transform mask onto a Clone Layer, which will
   allow you painting in 3D-perspective dynamically.

Please test it in krita-chili-kazakov branch :)

CCMAIL:kimageshop@kde.org

M  +3    -0    krita/image/CMakeLists.txt
M  +4    -4    krita/image/kis_async_merger.cpp
M  +20   -14   krita/image/kis_base_rects_walker.h
A  +48   -0    krita/image/kis_cached_paint_device.h     [License: GPL (v2+)]
M  +4    -1    krita/image/kis_filter_mask.cpp
M  +2    -1    krita/image/kis_filter_mask.h
M  +19   -5    krita/image/kis_layer.cc
M  +3    -2    krita/image/kis_layer.h
M  +16   -35   krita/image/kis_mask.cc
M  +3    -2    krita/image/kis_mask.h
M  +88   -17   krita/image/kis_perspectivetransform_worker.cpp
M  +16   -5    krita/image/kis_perspectivetransform_worker.h
A  +41   -0    krita/image/kis_recalculate_transform_mask_job.cpp     [License: GPL \
(v2+)] A  +38   -0    krita/image/kis_recalculate_transform_mask_job.h     [License: \
GPL (v2+)] A  +253  -0    krita/image/kis_transform_mask.cpp     [License: GPL (v2+)]
C  +27   -19   krita/image/kis_transform_mask.h [from: krita/image/kis_filter_mask.h \
- 062% similarity] A  +76   -0    krita/image/kis_transform_mask_params_interface.cpp \
[License: GPL (v2+)] A  +59   -0    krita/image/kis_transform_mask_params_interface.h \
[License: GPL (v2+)] A  +28   -0    krita/image/kis_transform_params.cpp     \
[License: GPL (v2+)] A  +30   -0    krita/image/kis_transform_params.h     [License: \
GPL (v2+)] M  +6    -1    krita/image/kis_transform_worker.cc
M  +1    -0    krita/image/kis_transform_worker.h
M  +4    -1    krita/image/kis_transparency_mask.cc
M  +2    -1    krita/image/kis_transparency_mask.h
M  +11   -3    krita/image/kis_types.h
M  +8    -0    krita/image/tests/CMakeLists.txt
M  +3    -2    krita/image/tests/kis_filter_mask_test.cpp
M  +1    -1    krita/image/tests/kis_mask_test.cpp
M  +3    -3    krita/image/tests/kis_paint_layer_test.cpp
A  +59   -0    krita/image/tests/kis_transform_mask_test.cpp     [License: GPL (v2+)]
A  +31   -0    krita/image/tests/kis_transform_mask_test.h     [License: GPL (v2+)]
M  +32   -0    krita/image/tests/kis_transform_worker_test.cpp
M  +2    -0    krita/image/tests/kis_transform_worker_test.h
M  +5    -3    krita/image/tests/kis_transparency_mask_test.cpp
M  +3    -0    krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
M  +1    -0    krita/plugins/tools/tool_transform2/CMakeLists.txt
M  +3    -3    krita/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp
M  +36   -3    krita/plugins/tools/tool_transform2/kis_tool_transform.cc
M  +2    -0    krita/plugins/tools/tool_transform2/kis_tool_transform.h
M  +2    -2    krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
 A  +72   -0    krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.cpp    \
[License: GPL (v2+)] A  +47   -0    \
krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.h     [License: GPL \
(v2+)] M  +104  -0    krita/plugins/tools/tool_transform2/kis_transform_utils.cpp
M  +11   -0    krita/plugins/tools/tool_transform2/kis_transform_utils.h
M  +85   -112  krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
 M  +1    -0    krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
 M  +1    -1    krita/plugins/tools/tool_transform2/transform_transaction_properties.h
 M  +2    -2    krita/sdk/tests/testutil.h
M  +14   -5    krita/ui/kis_mask_manager.cc
M  +2    -1    krita/ui/kis_mask_manager.h
M  +5    -0    krita/ui/kis_node_manager.cpp
M  +1    -1    krita/ui/widgets/kis_scratch_pad.cpp

http://commits.kde.org/calligra/2d16e0611550095db4ad674c1008ffb2d0e0c2a6

diff --git a/krita/image/CMakeLists.txt b/krita/image/CMakeLists.txt
index d84ace3..98f318f 100644
--- a/krita/image/CMakeLists.txt
+++ b/krita/image/CMakeLists.txt
@@ -132,6 +132,9 @@ set(kritaimage_LIB_SRCS
    kis_fill_painter.cc
    kis_filter_mask.cpp
    kis_filter_strategy.cc
+   kis_transform_mask.cpp
+   kis_transform_mask_params_interface.cpp
+   kis_recalculate_transform_mask_job.cpp
    kis_gradient_painter.cc
    kis_iterator_ng.cpp
    kis_async_merger.cpp
diff --git a/krita/image/kis_async_merger.cpp b/krita/image/kis_async_merger.cpp
index d2e4445..3211254 100644
--- a/krita/image/kis_async_merger.cpp
+++ b/krita/image/kis_async_merger.cpp
@@ -208,7 +208,7 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool \
notifyClones) {  m_currentProjection,
                                                      walker.cropRect());
             currentNode->accept(originalVisitor);
-            currentNode->updateProjection(applyRect);
+            currentNode->updateProjection(applyRect, \
KisMergeWalker::convertPositionToFilthy(item.m_position));  
             continue;
         }
@@ -224,18 +224,18 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, \
bool notifyClones) {  if(item.m_position & KisMergeWalker::N_FILTHY) {
             DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentNode, applyRect);
             currentNode->accept(originalVisitor);
-            currentNode->updateProjection(applyRect);
+            currentNode->updateProjection(applyRect, \
KisMergeWalker::convertPositionToFilthy(item.m_position));  }
         else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
             DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentNode, applyRect);
             if(dependOnLowerNodes(currentNode)) {
                 currentNode->accept(originalVisitor);
-                currentNode->updateProjection(applyRect);
+                currentNode->updateProjection(applyRect, \
KisMergeWalker::convertPositionToFilthy(item.m_position));  }
         }
         else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
             DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentNode, \
                applyRect);
-            currentNode->updateProjection(applyRect);
+            currentNode->updateProjection(applyRect, \
KisMergeWalker::convertPositionToFilthy(item.m_position));  }
         else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ {
             DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentNode, applyRect);
diff --git a/krita/image/kis_base_rects_walker.h \
b/krita/image/kis_base_rects_walker.h index 293f12e..d9c34f1 100644
--- a/krita/image/kis_base_rects_walker.h
+++ b/krita/image/kis_base_rects_walker.h
@@ -58,7 +58,20 @@ public:
     };
 
     #define GRAPH_POSITION_MASK     0x07
-    #define POSITION_TO_FILTHY_MASK 0xF8
+
+    static inline KisNode::PositionToFilthy convertPositionToFilthy(NodePosition \
position) { +        static const int positionToFilthyMask =
+            N_ABOVE_FILTHY |
+            N_FILTHY_PROJECTION |
+            N_FILTHY |
+            N_BELOW_FILTHY;
+
+        qint32 positionToFilthy = position & N_EXTRA ? N_FILTHY : position & \
positionToFilthyMask; +        // We do not use N_FILTHY_ORIGINAL yet, so...
+        Q_ASSERT(positionToFilthy);
+
+        return static_cast<KisNode::PositionToFilthy>(positionToFilthy);
+    }
 
     struct CloneNotification {
         CloneNotification() {}
@@ -189,13 +202,6 @@ protected:
     virtual void startTrip(KisNodeSP startWith) = 0;
 
 protected:
-    static inline KisNode::PositionToFilthy getPositionToFilthy(qint32 position) {
-        qint32 positionToFilthy = position & POSITION_TO_FILTHY_MASK;
-        // We do not use N_FILTHY_ORIGINAL yet, so...
-        Q_ASSERT(!(positionToFilthy & N_FILTHY_ORIGINAL));
-
-        return static_cast<KisNode::PositionToFilthy>(positionToFilthy);
-    }
 
     static inline qint32 getGraphPosition(qint32 position) {
         return position & GRAPH_POSITION_MASK;
@@ -270,7 +276,7 @@ protected:
         if(!isLayer(node)) return;
 
         QRect currentChangeRect = node->changeRect(m_resultChangeRect,
-                                                   getPositionToFilthy(position));
+                                                   \
convertPositionToFilthy(position));  currentChangeRect = \
cropThisRect(currentChangeRect);  
         if(!m_changeRectVaries)
@@ -279,7 +285,7 @@ protected:
         m_resultChangeRect = currentChangeRect;
 
         m_resultUncroppedChangeRect = node->changeRect(m_resultUncroppedChangeRect,
-                                                       \
getPositionToFilthy(position)); +                                                     \
convertPositionToFilthy(position));  registerCloneNotification(node, position);
     }
 
@@ -320,10 +326,10 @@ protected:
             //else /* Why push empty rect? */;
 
             m_resultAccessRect |= node->accessRect(m_lastNeedRect,
-                                                   getPositionToFilthy(position));
+                                                   \
convertPositionToFilthy(position));  
             m_lastNeedRect = node->needRect(m_lastNeedRect,
-                                            getPositionToFilthy(position));
+                                            convertPositionToFilthy(position));
             m_lastNeedRect = cropThisRect(m_lastNeedRect);
             m_childNeedRect = m_lastNeedRect;
         }
@@ -332,10 +338,10 @@ protected:
                 pushJob(node, position, m_lastNeedRect);
 
                 m_resultAccessRect |= node->accessRect(m_lastNeedRect,
-                                                       \
getPositionToFilthy(position)); +                                                     \
convertPositionToFilthy(position));  
                 m_lastNeedRect = node->needRect(m_lastNeedRect,
-                                                getPositionToFilthy(position));
+                                                convertPositionToFilthy(position));
                 m_lastNeedRect = cropThisRect(m_lastNeedRect);
             }
         }
diff --git a/krita/image/kis_cached_paint_device.h \
b/krita/image/kis_cached_paint_device.h new file mode 100644
index 0000000..8ee478e
--- /dev/null
+++ b/krita/image/kis_cached_paint_device.h
@@ -0,0 +1,48 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_CACHED_PAINT_DEVICE_H
+#define __KIS_CACHED_PAINT_DEVICE_H
+
+#include "tiles3/kis_lockless_stack.h"
+
+
+class KisCachedPaintDevice
+{
+public:
+    KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) {
+        KisPaintDeviceSP device;
+
+        if(!m_stack.pop(device)) {
+            device = new KisPaintDevice(prototype->colorSpace());
+        }
+
+        device->prepareClone(prototype);
+        return device;
+    }
+
+    void putDevice(KisPaintDeviceSP device) {
+        device->clear();
+        m_stack.push(device);
+    }
+
+private:
+    KisLocklessStack<KisPaintDeviceSP> m_stack;
+};
+
+#endif /* __KIS_CACHED_PAINT_DEVICE_H */
diff --git a/krita/image/kis_filter_mask.cpp b/krita/image/kis_filter_mask.cpp
index 1e43dec..a5e81f5 100644
--- a/krita/image/kis_filter_mask.cpp
+++ b/krita/image/kis_filter_mask.cpp
@@ -69,8 +69,11 @@ void KisFilterMask::setFilter(KisFilterConfiguration * \
filterConfig)  
 QRect KisFilterMask::decorateRect(KisPaintDeviceSP &src,
                                   KisPaintDeviceSP &dst,
-                                  const QRect & rc) const
+                                  const QRect & rc,
+                                  PositionToFilthy parentPos) const
 {
+    Q_UNUSED(parentPos);
+
     KisSafeFilterConfigurationSP filterConfig = filter();
 
     Q_ASSERT(nodeProgressProxy());
diff --git a/krita/image/kis_filter_mask.h b/krita/image/kis_filter_mask.h
index e3c093d..4095c7b 100644
--- a/krita/image/kis_filter_mask.h
+++ b/krita/image/kis_filter_mask.h
@@ -60,7 +60,8 @@ public:
 
     QRect decorateRect(KisPaintDeviceSP &src,
                        KisPaintDeviceSP &dst,
-                       const QRect & rc) const;
+                       const QRect & rc,
+                       PositionToFilthy parentPos) const;
 
     QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
     QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
diff --git a/krita/image/kis_layer.cc b/krita/image/kis_layer.cc
index 83aba8d..89e52cb 100644
--- a/krita/image/kis_layer.cc
+++ b/krita/image/kis_layer.cc
@@ -389,7 +389,8 @@ QRect KisLayer::masksNeedRect(const QList<KisEffectMaskSP> \
&masks,  
 QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
                            const KisPaintDeviceSP destination,
-                           const QRect &requestedRect) const
+                           const QRect &requestedRect,
+                           PositionToFilthy pos) const
 {
     Q_ASSERT(source);
     Q_ASSERT(destination);
@@ -434,7 +435,11 @@ QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
             }
 
             foreach(const KisEffectMaskSP& mask, masks) {
-                mask->apply(destination, applyRects.pop());
+                const QRect maskApplyRect = applyRects.pop();
+                const QRect maskNeedRect =
+                    applyRects.isEmpty() ? needRect : applyRects.top();
+
+                mask->apply(destination, maskApplyRect, maskNeedRect, pos);
             }
             Q_ASSERT(applyRects.isEmpty());
         } else {
@@ -445,10 +450,19 @@ QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
              */
 
             KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace());
+            tempDevice->prepareClone(source);
             copyOriginalToProjection(source, tempDevice, needRect);
 
+            QRect maskApplyRect = applyRects.pop();
+            QRect maskNeedRect = needRect;
+
             foreach(const KisEffectMaskSP& mask, masks) {
-                mask->apply(tempDevice, applyRects.pop());
+                mask->apply(tempDevice, maskApplyRect, maskNeedRect, pos);
+
+                if (!applyRects.isEmpty()) {
+                    maskNeedRect = maskApplyRect;
+                    maskApplyRect = applyRects.pop();
+                }
             }
             Q_ASSERT(applyRects.isEmpty());
 
@@ -461,7 +475,7 @@ QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
     return changeRect;
 }
 
-QRect KisLayer::updateProjection(const QRect& rect)
+QRect KisLayer::updateProjection(const QRect& rect, PositionToFilthy pos)
 {
     QRect updatedRect = rect;
     KisPaintDeviceSP originalDevice = original();
@@ -478,7 +492,7 @@ QRect KisLayer::updateProjection(const QRect& rect)
                 m_d->safeProjection.getDeviceLazy(originalDevice);
 
             updatedRect = applyMasks(originalDevice, projection,
-                                     updatedRect);
+                                     updatedRect, pos);
         }
     }
 
diff --git a/krita/image/kis_layer.h b/krita/image/kis_layer.h
index eb36e26..6ab0d52 100644
--- a/krita/image/kis_layer.h
+++ b/krita/image/kis_layer.h
@@ -83,7 +83,7 @@ public:
      * Ask the layer to assemble its data & apply all the effect masks
      * to it.
      */
-    virtual QRect updateProjection(const QRect& rect);
+    virtual QRect updateProjection(const QRect& rect, PositionToFilthy pos);
 
     virtual bool needProjection() const;
 
@@ -275,7 +275,8 @@ protected:
 
     QRect applyMasks(const KisPaintDeviceSP source,
                      const KisPaintDeviceSP destination,
-                     const QRect &requestedRect) const;
+                     const QRect &requestedRect,
+                     PositionToFilthy pos) const;
 
 private:
     struct Private;
diff --git a/krita/image/kis_mask.cc b/krita/image/kis_mask.cc
index 7145c75..3e7b7bc 100644
--- a/krita/image/kis_mask.cc
+++ b/krita/image/kis_mask.cc
@@ -38,35 +38,14 @@
 #include "kis_image.h"
 #include "kis_layer.h"
 
-#include "tiles3/kis_lockless_stack.h"
+#include "kis_cached_paint_device.h"
 
-struct KisMask::Private {
-    class CachedPaintDevice {
-    public:
-        KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) {
-            KisPaintDeviceSP device;
-
-            if(!m_stack.pop(device)) {
-                device = new KisPaintDevice(prototype->colorSpace());
-            }
-
-            device->prepareClone(prototype);
-            return device;
-        }
-
-        void putDevice(KisPaintDeviceSP device) {
-            device->clear();
-            m_stack.push(device);
-        }
-
-    private:
-        KisLocklessStack<KisPaintDeviceSP> m_stack;
-    };
 
+struct KisMask::Private {
     Private(KisMask *_q) : q(_q) {}
 
     mutable KisSelectionSP selection;
-    CachedPaintDevice paintDeviceCache;
+    KisCachedPaintDevice paintDeviceCache;
     KisMask *q;
 
     /**
@@ -231,30 +210,32 @@ void KisMask::select(const QRect & rc, quint8 selectedness)
 
 QRect KisMask::decorateRect(KisPaintDeviceSP &src,
                             KisPaintDeviceSP &dst,
-                            const QRect & rc) const
+                            const QRect & rc,
+                            PositionToFilthy parentPos) const
 {
     Q_UNUSED(src);
     Q_UNUSED(dst);
+    Q_UNUSED(parentPos);
     Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors");
     return rc;
 }
 
-void KisMask::apply(KisPaintDeviceSP projection, const QRect & rc) const
+void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect \
&needRect, PositionToFilthy parentPos) const  {
     if (selection()) {
 
-        m_d->selection->updateProjection(rc);
+        m_d->selection->updateProjection(applyRect);
 
-        if(!extent().intersects(rc))
+        if(!extent().intersects(applyRect))
             return;
 
         KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection);
 
-        QRect updatedRect = decorateRect(projection, cacheDevice, rc);
+        QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, \
parentPos);  
         KisPainter gc(projection);
-        gc.setCompositeOp(compositeOp());
-        gc.setOpacity(opacity());
+        // masks don't have any compositioning
+        gc.setCompositeOp(COMPOSITE_COPY);
         gc.setSelection(m_d->selection);
         gc.bitBlt(updatedRect.topLeft(), cacheDevice, updatedRect);
 
@@ -262,11 +243,11 @@ void KisMask::apply(KisPaintDeviceSP projection, const QRect & \
rc) const  
     } else {
         KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection);
-        cacheDevice->makeCloneFromRough(projection, rc);
-        projection->clear(rc);
 
-        // FIXME: how about opacity and compositeOp?
-        decorateRect(cacheDevice, projection, rc);
+        cacheDevice->makeCloneFromRough(projection, needRect);
+        projection->clear(needRect);
+
+        decorateRect(cacheDevice, projection, applyRect, parentPos);
 
         m_d->paintDeviceCache.putDevice(cacheDevice);
     }
diff --git a/krita/image/kis_mask.h b/krita/image/kis_mask.h
index 561c72b..4fb7ef5 100644
--- a/krita/image/kis_mask.h
+++ b/krita/image/kis_mask.h
@@ -178,10 +178,11 @@ protected:
      * Apply the effect the projection using the mask as a selection.
      * Made public in KisEffectMask
      */
-    virtual void apply(KisPaintDeviceSP projection, const QRect & rc) const;
+    void apply(KisPaintDeviceSP projection, const QRect & applyRect, const QRect & \
needRect, PositionToFilthy parentPos) const;  virtual QRect \
decorateRect(KisPaintDeviceSP &src,  KisPaintDeviceSP &dst,
-                               const QRect & rc) const;
+                               const QRect & rc,
+                               PositionToFilthy parentPos) const;
 
 private:
 
diff --git a/krita/image/kis_perspectivetransform_worker.cpp \
b/krita/image/kis_perspectivetransform_worker.cpp index 7839099..8d0a98b 100644
--- a/krita/image/kis_perspectivetransform_worker.cpp
+++ b/krita/image/kis_perspectivetransform_worker.cpp
@@ -24,6 +24,12 @@
 #include <QMatrix4x4>
 #include <QTransform>
 #include <QVector3D>
+#include <QPolygonF>
+
+#include <KoProgressUpdater.h>
+#include <KoUpdater.h>
+#include <KoColor.h>
+#include <KoCompositeOpRegistry.h>
 
 #include "kis_paint_device.h"
 #include "kis_perspective_math.h"
@@ -33,10 +39,8 @@
 #include <kis_iterator_ng.h>
 #include "krita_utils.h"
 #include "kis_progress_update_helper.h"
+#include "kis_painter.h"
 
-#include <KoProgressUpdater.h>
-#include <KoUpdater.h>
-#include <KoColor.h>
 
 KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, \
QPointF center, double aX, double aY, double distance, KoUpdaterPtr progress)  : \
m_dev(dev), m_progressUpdater(progress) @@ -60,21 +64,38 @@ \
KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP de  \
init(transform);  }
 
-void KisPerspectiveTransformWorker::init(const QTransform &transform)
+void KisPerspectiveTransformWorker::fillParams(const QRectF &srcRect,
+                                               const QRect &dstBaseClipRect,
+                                               QRegion *dstRegion,
+                                               QPolygonF *dstClipPolygon)
 {
-    QPolygon bounds(m_dev->exactBounds());
-    QPolygon newBounds = transform.map(bounds);
+    QPolygonF bounds = srcRect;
+    QPolygonF newBounds = m_forwardTransform.map(bounds);
+
+    newBounds = newBounds.intersected(QRectF(dstBaseClipRect));
+
+    QPainterPath path;
+    path.addPolygon(newBounds);
+    *dstRegion = KritaUtils::splitPath(path);
+    *dstClipPolygon = newBounds;
+}
 
+void KisPerspectiveTransformWorker::init(const QTransform &transform)
+{
     m_isIdentity = transform.isIdentity();
 
-    if (!m_isIdentity && transform.isInvertible()) {
-        m_newTransform = transform.inverted();
+    m_forwardTransform = transform;
+    m_backwardTransform = transform.inverted();
+
+    if (m_dev) {
         m_srcRect = m_dev->exactBounds();
-        newBounds = newBounds.intersected(m_dev->defaultBounds()->bounds());
 
-        QPainterPath path;
-        path.addPolygon(newBounds);
-        m_dstRegion = KritaUtils::splitPath(path);
+        QPolygonF dstClipPolygonUnused;
+
+        fillParams(m_srcRect,
+                   m_dev->defaultBounds()->bounds(),
+                   &m_dstRegion,
+                   &dstClipPolygonUnused);
     }
 }
 
@@ -82,11 +103,16 @@ KisPerspectiveTransformWorker::~KisPerspectiveTransformWorker()
 {
 }
 
+void KisPerspectiveTransformWorker::setForwardTransform(const QTransform &transform)
+{
+    init(transform);
+}
+
 void KisPerspectiveTransformWorker::run()
 {
-    KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, \
                m_dstRegion.rectCount());
-    if (m_isIdentity) return;
+    KIS_ASSERT_RECOVER_RETURN(m_dev);
 
+    if (m_isIdentity) return;
 
     KisPaintDeviceSP cloneDevice = new KisPaintDevice(*m_dev.data());
 
@@ -94,7 +120,11 @@ void KisPerspectiveTransformWorker::run()
     // shared with cloneDevice
     m_dev->clear();
 
-    KisRandomSubAccessorSP srcAcc = cloneDevice->createRandomSubAccessor();
+    KIS_ASSERT_RECOVER_NOOP(!m_isIdentity);
+
+    KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, \
m_dstRegion.rectCount()); +
+    KisRandomSubAccessorSP srcAcc = m_dev->createRandomSubAccessor();
     KisRandomAccessorSP accessor = m_dev->createRandomAccessorNG(0, 0);
 
     foreach(const QRect &rect, m_dstRegion.rects()) {
@@ -102,19 +132,60 @@ void KisPerspectiveTransformWorker::run()
             for (int x = rect.x(); x < rect.x() + rect.width(); ++x) {
 
                 QPointF dstPoint(x, y);
-                QPointF srcPoint = m_newTransform.map(dstPoint);
+                QPointF srcPoint = m_backwardTransform.map(dstPoint);
 
                 if (m_srcRect.contains(srcPoint)) {
                     accessor->moveTo(dstPoint.x(), dstPoint.y());
                     srcAcc->moveTo(srcPoint.x(), srcPoint.y());
                     srcAcc->sampledOldRawData(accessor->rawData());
                 }
+            }
+        }
+        progressHelper.step();
+    }
+}
+
+void KisPerspectiveTransformWorker::runPartialDst(KisPaintDeviceSP srcDev,
+                                                  KisPaintDeviceSP dstDev,
+                                                  const QRect &dstRect)
+{
+    if (m_isIdentity) {
+        KisPainter gc(dstDev);
+        gc.setCompositeOp(COMPOSITE_COPY);
+        gc.bitBltOldData(dstRect.topLeft(), srcDev, dstRect);
+        return;
+    }
 
+    QRectF srcClipRect = srcDev->defaultBounds()->bounds();
 
+    KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, \
dstRect.height()); +
+    KisRandomSubAccessorSP srcAcc = srcDev->createRandomSubAccessor();
+    KisRandomAccessorSP accessor = dstDev->createRandomAccessorNG(0, 0);
+
+    for (int y = dstRect.y(); y < dstRect.y() + dstRect.height(); ++y) {
+        for (int x = dstRect.x(); x < dstRect.x() + dstRect.width(); ++x) {
+
+            QPointF dstPoint(x, y);
+            QPointF srcPoint = m_backwardTransform.map(dstPoint);
+
+            if (srcClipRect.contains(srcPoint)) {
+                accessor->moveTo(dstPoint.x(), dstPoint.y());
+                srcAcc->moveTo(srcPoint.x(), srcPoint.y());
+                srcAcc->sampledOldRawData(accessor->rawData());
             }
         }
         progressHelper.step();
     }
+
 }
 
-#include "kis_perspectivetransform_worker.moc"
+QTransform KisPerspectiveTransformWorker::forwardTransform() const
+{
+    return m_forwardTransform;
+}
+
+QTransform KisPerspectiveTransformWorker::backwardTransform() const
+{
+    return m_backwardTransform;
+}
diff --git a/krita/image/kis_perspectivetransform_worker.h \
b/krita/image/kis_perspectivetransform_worker.h index 9d78295..d0455be 100644
--- a/krita/image/kis_perspectivetransform_worker.h
+++ b/krita/image/kis_perspectivetransform_worker.h
@@ -30,11 +30,8 @@
 #include <KoUpdater.h>
 
 
-class KRITAIMAGE_EXPORT KisPerspectiveTransformWorker : public QObject
+class KRITAIMAGE_EXPORT KisPerspectiveTransformWorker
 {
-
-    Q_OBJECT
-
 public:
     KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, \
                double aY, double distance, KoUpdaterPtr progress);
     KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const QTransform &transform, \
KoUpdaterPtr progress); @@ -42,16 +39,30 @@ public:
     ~KisPerspectiveTransformWorker();
 
     void run();
+    void runPartialDst(KisPaintDeviceSP srcDev,
+                       KisPaintDeviceSP dstDev,
+                       const QRect &dstRect);
+
+    void setForwardTransform(const QTransform &transform);
+
+    QTransform forwardTransform() const;
+    QTransform backwardTransform() const;
 
 private:
     void init(const QTransform &transform);
 
+    void fillParams(const QRectF &srcRect,
+                    const QRect &dstBaseClipRect,
+                    QRegion *dstRegion,
+                    QPolygonF *dstClipPolygon);
+
 private:
     KisPaintDeviceSP m_dev;
     KoUpdaterPtr m_progressUpdater;
     QRegion m_dstRegion;
     QRectF m_srcRect;
-    QTransform m_newTransform;
+    QTransform m_backwardTransform;
+    QTransform m_forwardTransform;
     bool m_isIdentity;
 };
 
diff --git a/krita/image/kis_recalculate_transform_mask_job.cpp \
b/krita/image/kis_recalculate_transform_mask_job.cpp new file mode 100644
index 0000000..55d4967
--- /dev/null
+++ b/krita/image/kis_recalculate_transform_mask_job.cpp
@@ -0,0 +1,41 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_recalculate_transform_mask_job.h"
+
+#include "kis_transform_mask.h"
+
+
+KisRecalculateTransformMaskJob::KisRecalculateTransformMaskJob(KisTransformMaskSP \
mask) +    : m_mask(mask)
+{
+}
+
+bool KisRecalculateTransformMaskJob::overrides(const KisSpontaneousJob *_otherJob)
+{
+    const KisRecalculateTransformMaskJob *otherJob =
+        dynamic_cast<const KisRecalculateTransformMaskJob*>(_otherJob);
+
+    return otherJob && otherJob->m_mask == m_mask;
+}
+
+void KisRecalculateTransformMaskJob::run()
+{
+    m_mask->recaclulateStaticImage();
+    m_mask->setDirty();
+}
diff --git a/krita/image/kis_recalculate_transform_mask_job.h \
b/krita/image/kis_recalculate_transform_mask_job.h new file mode 100644
index 0000000..1faf4f9
--- /dev/null
+++ b/krita/image/kis_recalculate_transform_mask_job.h
@@ -0,0 +1,38 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_RECALCULATE_TRANSFORM_MASK_JOB_H
+#define __KIS_RECALCULATE_TRANSFORM_MASK_JOB_H
+
+#include "kis_types.h"
+#include "kis_spontaneous_job.h"
+
+
+class KRITAIMAGE_EXPORT KisRecalculateTransformMaskJob : public KisSpontaneousJob
+{
+public:
+    KisRecalculateTransformMaskJob(KisTransformMaskSP mask);
+
+    bool overrides(const KisSpontaneousJob *otherJob);
+    void run();
+
+private:
+    KisTransformMaskSP m_mask;
+};
+
+#endif /* __KIS_RECALCULATE_TRANSFORM_MASK_JOB_H */
diff --git a/krita/image/kis_transform_mask.cpp b/krita/image/kis_transform_mask.cpp
new file mode 100644
index 0000000..f15bd51
--- /dev/null
+++ b/krita/image/kis_transform_mask.cpp
@@ -0,0 +1,253 @@
+/*
+ *  Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
+ *
+ *
+ *  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 <KoIcon.h>
+#include <KoCompositeOpRegistry.h>
+
+#include "kis_layer.h"
+#include "kis_transform_mask.h"
+#include "filter/kis_filter.h"
+#include "filter/kis_filter_configuration.h"
+#include "filter/kis_filter_registry.h"
+#include "kis_selection.h"
+#include "kis_processing_information.h"
+#include "kis_node.h"
+#include "kis_node_visitor.h"
+#include "kis_processing_visitor.h"
+#include "kis_node_progress_proxy.h"
+#include "kis_transaction.h"
+#include "kis_painter.h"
+
+#include <KoUpdater.h>
+#include "kis_perspectivetransform_worker.h"
+#include "kis_transform_mask_params_interface.h"
+#include "kis_recalculate_transform_mask_job.h"
+#include "kis_signal_compressor.h"
+
+#define UPDATE_DELAY 3000 /*ms */
+
+struct KisTransformMask::Private
+{
+    Private()
+        : worker(0, QTransform(), 0),
+          staticCacheValid(false),
+          recalculatingStaticImage(false),
+          updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE)
+    {
+    }
+
+    Private(const Private &rhs)
+        : worker(rhs.worker),
+          params(rhs.params),
+          staticCacheValid(false),
+          recalculatingStaticImage(rhs.recalculatingStaticImage),
+          updateSignalCompressor(UPDATE_DELAY, KisSignalCompressor::POSTPONE)
+    {
+    }
+
+    KisPerspectiveTransformWorker worker;
+    KisTransformMaskParamsInterfaceSP params;
+
+    bool staticCacheValid;
+    bool recalculatingStaticImage;
+    KisPaintDeviceSP staticCacheDevice;
+
+    KisSignalCompressor updateSignalCompressor;
+};
+
+
+KisTransformMask::KisTransformMask()
+    : KisEffectMask(),
+      m_d(new Private)
+{
+    setTransformParams(
+        KisTransformMaskParamsInterfaceSP(
+            new KisDumbTransformMaskParams()));
+
+    connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), \
SLOT(slotDelayedStaticUpdate())); +}
+
+KisTransformMask::~KisTransformMask()
+{
+}
+
+KisTransformMask::KisTransformMask(const KisTransformMask& rhs)
+    : KisEffectMask(rhs),
+      m_d(new Private(*rhs.m_d))
+{
+    connect(&m_d->updateSignalCompressor, SIGNAL(timeout()), \
SLOT(slotDelayedStaticUpdate())); +}
+
+KisPaintDeviceSP KisTransformMask::paintDevice() const
+{
+    return 0;
+}
+
+QIcon KisTransformMask::icon() const
+{
+    return koIcon("edit-cut");
+}
+
+void KisTransformMask::setTransformParams(KisTransformMaskParamsInterfaceSP params)
+{
+    KIS_ASSERT_RECOVER(params) {
+        params = KisTransformMaskParamsInterfaceSP(
+            new KisDumbTransformMaskParams());
+    }
+
+    QTransform affineTransform;
+    if (params->isAffine()) {
+        affineTransform = params->finalAffineTransform();
+    }
+    m_d->worker.setForwardTransform(affineTransform);
+
+    m_d->params = params;
+    m_d->updateSignalCompressor.stop();
+}
+
+KisTransformMaskParamsInterfaceSP KisTransformMask::transformParams() const
+{
+    return m_d->params;
+}
+
+void KisTransformMask::slotDelayedStaticUpdate()
+{
+    KisLayerSP parentLayer = dynamic_cast<KisLayer*>(parent().data());
+    KIS_ASSERT_RECOVER_RETURN(parentLayer);
+
+    KisImageSP image = parentLayer->image();
+    if (image) {
+        image->addSpontaneousJob(new KisRecalculateTransformMaskJob(this));
+    }
+}
+
+void KisTransformMask::recaclulateStaticImage()
+{
+    /**
+     * Note: this function must be called from within the scheduler's
+     * context. We are accessing parent's updateProjection(), which
+     * is not entirely safe.
+     */
+
+    KisLayerSP parentLayer = dynamic_cast<KisLayer*>(parent().data());
+    KIS_ASSERT_RECOVER_RETURN(parentLayer);
+
+    if (!m_d->staticCacheDevice) {
+        m_d->staticCacheDevice =
+            new KisPaintDevice(parentLayer->original()->colorSpace());
+    }
+
+    m_d->recalculatingStaticImage = true;
+    parentLayer->updateProjection(parentLayer->exactBounds(), N_FILTHY_PROJECTION);
+    m_d->recalculatingStaticImage = false;
+
+    m_d->staticCacheValid = true;
+}
+
+QRect KisTransformMask::decorateRect(KisPaintDeviceSP &src,
+                                     KisPaintDeviceSP &dst,
+                                     const QRect & rc,
+                                     PositionToFilthy parentPos) const
+{
+    Q_ASSERT(nodeProgressProxy());
+    Q_ASSERT_X(src != dst, "KisTransformMask::decorateRect",
+               "src must be != dst, because we cant create transactions "
+               "during merge, as it breaks reentrancy");
+
+    KIS_ASSERT_RECOVER(m_d->params) { return rc; }
+
+    if (m_d->params->isHidden()) return rc;
+
+    if (parentPos != N_FILTHY_PROJECTION) {
+        m_d->staticCacheValid = false;
+        m_d->updateSignalCompressor.start();
+    }
+
+    if (m_d->recalculatingStaticImage) {
+        m_d->params->transformDevice(const_cast<KisTransformMask*>(this), src, \
m_d->staticCacheDevice); +        dst->makeCloneFrom(m_d->staticCacheDevice, \
m_d->staticCacheDevice->extent()); +    } else if (!m_d->staticCacheValid && \
m_d->params->isAffine()) { +        m_d->worker.runPartialDst(src, dst, rc);
+    } else {
+        KisPainter gc(dst);
+        gc.setCompositeOp(COMPOSITE_COPY);
+        gc.bitBlt(rc.topLeft(), m_d->staticCacheDevice, rc);
+    }
+
+    return rc;
+}
+
+bool KisTransformMask::accept(KisNodeVisitor &v)
+{
+    return v.visit(this);
+}
+
+void KisTransformMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter \
*undoAdapter) +{
+    return visitor.visit(this, undoAdapter);
+}
+
+QRect KisTransformMask::changeRect(const QRect &rect, PositionToFilthy pos) const
+{
+    Q_UNUSED(pos);
+
+    /**
+     * FIXME: This check of the emptiness should be done
+     * on the higher/lower level
+     */
+    if (rect.isEmpty()) return rect;
+    if (!m_d->params->isAffine()) return rect;
+
+    QRect changeRect = m_d->worker.forwardTransform()
+        .mapRect(QRectF(rect)).toAlignedRect();
+
+    KisNodeSP parentNode;
+    KisPaintDeviceSP parentOriginal;
+
+    if ((parentNode = parent()) &&
+        (parentOriginal = parentNode->original())) {
+
+        QRect backwardRect = m_d->worker.backwardTransform().mapRect(rect);
+        QRegion backwardRegion(backwardRect);
+        backwardRegion -= parentOriginal->defaultBounds()->bounds();
+
+        backwardRegion = m_d->worker.forwardTransform().map(backwardRegion);
+
+        // FIXME: d-oh... please fix me and use region instead :(
+        changeRect |= backwardRegion.boundingRect();
+    }
+
+    return changeRect;
+}
+
+QRect KisTransformMask::needRect(const QRect& rect, PositionToFilthy pos) const
+{
+    Q_UNUSED(pos);
+
+    /**
+     * FIXME: This check of the emptiness should be done
+     * on the higher/lower level
+     */
+    if (rect.isEmpty()) return rect;
+    if (!m_d->params->isAffine()) return rect;
+
+    return kisGrowRect(m_d->worker.backwardTransform().mapRect(rect), 2);
+}
+
+#include "kis_transform_mask.moc"
diff --git a/krita/image/kis_filter_mask.h b/krita/image/kis_transform_mask.h
similarity index 62%
copy from krita/image/kis_filter_mask.h
copy to krita/image/kis_transform_mask.h
index e3c093d..8a61cfa 100644
--- a/krita/image/kis_filter_mask.h
+++ b/krita/image/kis_transform_mask.h
@@ -15,24 +15,19 @@
  *  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_FILTER_MASK_
-#define _KIS_FILTER_MASK_
+#ifndef _KIS_TRANSFORM_MASK_
+#define _KIS_TRANSFORM_MASK_
+
+#include <QScopedPointer>
 
 #include "kis_types.h"
 #include "kis_effect_mask.h"
 
-#include "kis_node_filter_interface.h"
-
-class KisFilterConfiguration;
-
 /**
-   An filter mask is a single channel mask that applies a particular
-   filter to the layer the mask belongs to. It differs from an
-   adjustment layer in that it only works on its parent layer, while
-   adjustment layers work on all layers below it in its layer group.
+   Transform a layer according to a matrix transform
 */
 
-class KRITAIMAGE_EXPORT KisFilterMask : public KisEffectMask, public \
KisNodeFilterInterface +class KRITAIMAGE_EXPORT KisTransformMask : public \
KisEffectMask  {
     Q_OBJECT
 
@@ -41,29 +36,42 @@ public:
     /**
      * Create an empty filter mask.
      */
-    KisFilterMask();
+    KisTransformMask();
 
-    virtual ~KisFilterMask();
+    virtual ~KisTransformMask();
 
     QIcon icon() const;
 
     KisNodeSP clone() const {
-        return KisNodeSP(new KisFilterMask(*this));
+        return KisNodeSP(new KisTransformMask(*this));
     }
 
+    KisPaintDeviceSP paintDevice() const;
+
     bool accept(KisNodeVisitor &v);
     void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
 
-    KisFilterMask(const KisFilterMask& rhs);
-
-    void setFilter(KisFilterConfiguration *filterConfig);
+    KisTransformMask(const KisTransformMask& rhs);
 
     QRect decorateRect(KisPaintDeviceSP &src,
                        KisPaintDeviceSP &dst,
-                       const QRect & rc) const;
+                       const QRect & rc,
+                       PositionToFilthy parentPos) const;
 
     QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
     QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
+
+    void setTransformParams(KisTransformMaskParamsInterfaceSP params);
+    KisTransformMaskParamsInterfaceSP transformParams() const;
+
+    void recaclulateStaticImage();
+
+private slots:
+    void slotDelayedStaticUpdate();
+
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
 };
 
-#endif //_KIS_FILTER_MASK_
+#endif //_KIS_TRANSFORM_MASK_
diff --git a/krita/image/kis_transform_mask_params_interface.cpp \
b/krita/image/kis_transform_mask_params_interface.cpp new file mode 100644
index 0000000..0cc0ed1
--- /dev/null
+++ b/krita/image/kis_transform_mask_params_interface.cpp
@@ -0,0 +1,76 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_transform_mask_params_interface.h"
+
+#include <QTransform>
+
+
+KisTransformMaskParamsInterface::~KisTransformMaskParamsInterface()
+{
+}
+
+///////////////// KisDumbTransformMaskParams ////////////////////////////
+
+struct KisDumbTransformMaskParams::Private
+{
+    Private() : isHidden(false) {}
+
+    QTransform transform;
+    bool isHidden;
+};
+
+KisDumbTransformMaskParams::KisDumbTransformMaskParams()
+    : m_d(new Private)
+{
+}
+
+KisDumbTransformMaskParams::KisDumbTransformMaskParams(const QTransform &transform)
+    : m_d(new Private)
+{
+    m_d->isHidden = false;
+    m_d->transform = transform;
+}
+
+KisDumbTransformMaskParams::KisDumbTransformMaskParams(bool isHidden)
+    : m_d(new Private)
+{
+    m_d->isHidden = isHidden;
+}
+
+QTransform KisDumbTransformMaskParams::finalAffineTransform() const
+{
+    return m_d->transform;
+}
+
+bool KisDumbTransformMaskParams::isAffine() const
+{
+    return true;
+}
+
+bool KisDumbTransformMaskParams::isHidden() const
+{
+    return m_d->isHidden;
+}
+
+void KisDumbTransformMaskParams::transformDevice(KisNodeSP node, KisPaintDeviceSP \
src, KisPaintDeviceSP dst) const +{
+    Q_UNUSED(node);
+    Q_UNUSED(src);
+    Q_UNUSED(dst);
+}
diff --git a/krita/image/kis_transform_mask_params_interface.h \
b/krita/image/kis_transform_mask_params_interface.h new file mode 100644
index 0000000..ada0935
--- /dev/null
+++ b/krita/image/kis_transform_mask_params_interface.h
@@ -0,0 +1,59 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_TRANSFORM_MASK_PARAMS_INTERFACE_H
+#define __KIS_TRANSFORM_MASK_PARAMS_INTERFACE_H
+
+#include "krita_export.h"
+#include "kis_types.h"
+
+#include <QScopedPointer>
+
+
+class QTransform;
+
+class KRITAIMAGE_EXPORT KisTransformMaskParamsInterface
+{
+public:
+    virtual ~KisTransformMaskParamsInterface();
+
+    virtual QTransform finalAffineTransform() const = 0;
+    virtual bool isAffine() const = 0;
+    virtual bool isHidden() const = 0;
+
+    virtual void transformDevice(KisNodeSP node, KisPaintDeviceSP src, \
KisPaintDeviceSP dst) const = 0; +};
+
+class KRITAIMAGE_EXPORT KisDumbTransformMaskParams : public \
KisTransformMaskParamsInterface +{
+public:
+    KisDumbTransformMaskParams();
+    KisDumbTransformMaskParams(const QTransform &transform);
+    KisDumbTransformMaskParams(bool isHidden);
+
+    QTransform finalAffineTransform() const;
+    bool isAffine() const;
+    bool isHidden() const;
+    void transformDevice(KisNodeSP node, KisPaintDeviceSP src, KisPaintDeviceSP dst) \
const; +
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
+};
+
+#endif /* __KIS_TRANSFORM_MASK_PARAMS_INTERFACE_H */
diff --git a/krita/image/kis_transform_params.cpp \
b/krita/image/kis_transform_params.cpp new file mode 100644
index 0000000..f290806
--- /dev/null
+++ b/krita/image/kis_transform_params.cpp
@@ -0,0 +1,28 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_transform_params.h"
+
+
+KisTransformParams::KisTransformParams()
+{
+}
+
+KisTransformParams::~KisTransformParams()
+{
+}
diff --git a/krita/image/kis_transform_params.h b/krita/image/kis_transform_params.h
new file mode 100644
index 0000000..d59d095
--- /dev/null
+++ b/krita/image/kis_transform_params.h
@@ -0,0 +1,30 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_TRANSFORM_PARAMS_H
+#define __KIS_TRANSFORM_PARAMS_H
+
+
+class KisTransformParams
+{
+public:
+    KisTransformParams();
+    ~KisTransformParams();
+};
+
+#endif /* __KIS_TRANSFORM_PARAMS_H */
diff --git a/krita/image/kis_transform_worker.cc \
b/krita/image/kis_transform_worker.cc index a602670..beee995 100644
--- a/krita/image/kis_transform_worker.cc
+++ b/krita/image/kis_transform_worker.cc
@@ -233,6 +233,11 @@ void swapValues(T *a, T *b) {
 
 bool KisTransformWorker::run()
 {
+    return runPartial(m_dev->exactBounds());
+}
+
+bool KisTransformWorker::runPartial(const QRect &processRect)
+{
     /* Check for nonsense and let the user know, this helps debugging.
     Otherwise the program will crash at a later point, in a very obscure way, \
                probably by division by zero */
     Q_ASSERT_X(m_xscale != 0, "KisTransformer::run() validation step", "xscale == \
0"); @@ -240,7 +245,7 @@ bool KisTransformWorker::run()
     // Fallback safety line in case Krita is compiled without ASSERTS
     if (m_xscale == 0 || m_yscale == 0) return false;
 
-    m_boundRect = m_dev->exactBounds();
+    m_boundRect = processRect;
 
     if (m_boundRect.isNull()) {
         if (!m_progressUpdater.isNull()) {
diff --git a/krita/image/kis_transform_worker.h b/krita/image/kis_transform_worker.h
index 3775b61..c536dce 100644
--- a/krita/image/kis_transform_worker.h
+++ b/krita/image/kis_transform_worker.h
@@ -91,6 +91,7 @@ public:
 
     // returns false if interrupted
     bool run();
+    bool runPartial(const QRect &processRect);
 
     /**
      * Returns a matrix of the transformation executed by the worker.
diff --git a/krita/image/kis_transparency_mask.cc \
b/krita/image/kis_transparency_mask.cc index ce9ce43..01f41c5 100644
--- a/krita/image/kis_transparency_mask.cc
+++ b/krita/image/kis_transparency_mask.cc
@@ -46,8 +46,11 @@ KisTransparencyMask::~KisTransparencyMask()
 
 QRect KisTransparencyMask::decorateRect(KisPaintDeviceSP &src,
                                         KisPaintDeviceSP &dst,
-                                        const QRect & rc) const
+                                        const QRect & rc,
+                                        PositionToFilthy parentPos) const
 {
+    Q_UNUSED(parentPos);
+
     if (src != dst) {
         KisPainter gc(dst);
         gc.setCompositeOp(src->colorSpace()->compositeOp(COMPOSITE_COPY));
diff --git a/krita/image/kis_transparency_mask.h \
b/krita/image/kis_transparency_mask.h index f9bda97..56b4531 100644
--- a/krita/image/kis_transparency_mask.h
+++ b/krita/image/kis_transparency_mask.h
@@ -49,7 +49,8 @@ public:
     }
 
     QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst,
-                       const QRect & rc) const;
+                       const QRect & rc,
+                       PositionToFilthy parentPos) const;
     QIcon icon() const;
     bool accept(KisNodeVisitor &v);
     void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
diff --git a/krita/image/kis_types.h b/krita/image/kis_types.h
index 8a277cc..20a0b7a 100644
--- a/krita/image/kis_types.h
+++ b/krita/image/kis_types.h
@@ -27,6 +27,9 @@ class KisWeakSharedPtr;
 template<class T>
 class KisSharedPtr;
 
+template<class T> class QSharedPointer;
+template<class T> class QWeakPointer;
+
 template <class T>
 uint qHash(KisSharedPtr<T> ptr) {
     return qHash(ptr.data());
@@ -80,6 +83,14 @@ class KisFilterMask;
 typedef KisSharedPtr<KisFilterMask> KisFilterMaskSP;
 typedef KisWeakSharedPtr<KisFilterMask> KisFilterMaskWSP;
 
+class KisTransformMask;
+typedef KisSharedPtr<KisTransformMask> KisTransformMaskSP;
+typedef KisWeakSharedPtr<KisTransformMask> KisTransformMaskWSP;
+
+class KisTransformMaskParamsInterface;
+typedef QSharedPointer<KisTransformMaskParamsInterface> \
KisTransformMaskParamsInterfaceSP; +typedef \
QWeakPointer<KisTransformMaskParamsInterface> KisTransformMaskParamsInterfaceWSP; +
 class KisTransparencyMask;
 typedef KisSharedPtr<KisTransparencyMask> KisTransparencyMaskSP;
 typedef KisWeakSharedPtr<KisTransparencyMask> KisTransparencyMaskWSP;
@@ -201,9 +212,6 @@ typedef QPointer<KoUpdater> KoUpdaterPtr;
 class KisProcessingVisitor;
 typedef KisSharedPtr<KisProcessingVisitor> KisProcessingVisitorSP;
 
-template<class T> class QSharedPointer;
-template<class T> class QWeakPointer;
-
 class KUndo2Command;
 typedef QSharedPointer<KUndo2Command> KUndo2CommandSP;
 
diff --git a/krita/image/tests/CMakeLists.txt b/krita/image/tests/CMakeLists.txt
index f585687..8075b38 100644
--- a/krita/image/tests/CMakeLists.txt
+++ b/krita/image/tests/CMakeLists.txt
@@ -137,6 +137,14 @@ target_link_libraries(KisFilterMaskTest  ${KDE4_KDEUI_LIBS} \
kritaimage ${QT_QTTE  
 ########### next target ###############
 
+set(kis_transform_mask_test_SRCS kis_transform_mask_test.cpp )
+
+kde4_add_unit_test(KisTransformMaskTest TESTNAME krita-image-KisTransformMaskTest \
${kis_transform_mask_test_SRCS}) +
+target_link_libraries(KisTransformMaskTest  ${KDE4_KDEUI_LIBS} kritaimage \
${QT_QTTEST_LIBRARY}) +
+########### next target ###############
+
 set(kis_paint_layer_test_SRCS kis_paint_layer_test.cpp )
 
 kde4_add_unit_test(KisPaintLayerTest TESTNAME krita-image-KisPaintLayerTest \
                ${kis_paint_layer_test_SRCS})
diff --git a/krita/image/tests/kis_filter_mask_test.cpp \
b/krita/image/tests/kis_filter_mask_test.cpp index d652006..86b52f9 100644
--- a/krita/image/tests/kis_filter_mask_test.cpp
+++ b/krita/image/tests/kis_filter_mask_test.cpp
@@ -76,7 +76,8 @@ void KisFilterMaskTest::testProjectionNotSelected()
     mask->initSelection(layer);
     mask->createNodeProgressProxy();
     mask->select(qimage.rect(), MIN_SELECTED);
-    mask->apply(projection, QRect(0, 0, qimage.width(), qimage.height()));
+
+    mask->apply(projection, qimage.rect(), qimage.rect(), KisNode::N_FILTHY);
 
     QPoint errpoint;
     if (!TestUtil::compareQImages(errpoint, qimage, projection->convertToQImage(0, \
0, 0, qimage.width(), qimage.height()))) { @@ -110,7 +111,7 @@ void \
KisFilterMaskTest::testProjectionSelected()  
     mask->initSelection(layer);
     mask->select(qimage.rect(), MAX_SELECTED);
-    mask->apply(projection, QRect(0, 0, qimage.width(), qimage.height()));
+    mask->apply(projection, qimage.rect(), qimage.rect(), KisNode::N_FILTHY);
     QCOMPARE(mask->exactBounds(), QRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT));
 
     QPoint errpoint;
diff --git a/krita/image/tests/kis_mask_test.cpp \
b/krita/image/tests/kis_mask_test.cpp index 5bb4eff..942719a 100644
--- a/krita/image/tests/kis_mask_test.cpp
+++ b/krita/image/tests/kis_mask_test.cpp
@@ -98,7 +98,7 @@ void KisMaskTest::testCropUpdateBySelection()
 
     mask->initSelection(sel, p.layer);
 
-    mask->apply(p.layer->projection(), updateRect);
+    mask->apply(p.layer->projection(), updateRect, updateRect, KisNode::N_FILTHY);
     // Here we crash! :)
 
     /**
diff --git a/krita/image/tests/kis_paint_layer_test.cpp \
b/krita/image/tests/kis_paint_layer_test.cpp index 420cf6c..ebf757f 100644
--- a/krita/image/tests/kis_paint_layer_test.cpp
+++ b/krita/image/tests/kis_paint_layer_test.cpp
@@ -59,7 +59,7 @@ void KisPaintLayerTest::testProjection()
     Q_ASSERT(layer->hasEffectMasks());
 
     // And now we're going to update the projection, but nothing is dirty yet
-    layer->updateProjection(qimage.rect());
+    layer->updateProjection(qimage.rect(), KisNode::N_FILTHY);
 
     // Which also means that the projection is no longer the paint device
     QVERIFY(layer->paintDevice().data() != layer->projection().data());
@@ -68,7 +68,7 @@ void KisPaintLayerTest::testProjection()
     layer->setDirty(qimage.rect());
 
     // And now we're going to update the projection, but nothing is dirty yet
-    layer->updateProjection(qimage.rect());
+    layer->updateProjection(qimage.rect(), KisNode::N_FILTHY);
 
     // Which also means that the projection is no longer the paint device
     QVERIFY(layer->paintDevice().data() != layer->projection().data());
@@ -78,7 +78,7 @@ void KisPaintLayerTest::testProjection()
     QVERIFY(layer->projection().data() != 0);
 
     // The selection is initially empty, so after an update, all pixels are still \
                visible
-    layer->updateProjection(qimage.rect());
+    layer->updateProjection(qimage.rect(), KisNode::N_FILTHY);
 
     // We've inverted the mask, so now nothing is seen
     KisSequentialConstIterator it(layer->projection(), qimage.rect());
diff --git a/krita/image/tests/kis_transform_mask_test.cpp \
b/krita/image/tests/kis_transform_mask_test.cpp new file mode 100644
index 0000000..b1719d2
--- /dev/null
+++ b/krita/image/tests/kis_transform_mask_test.cpp
@@ -0,0 +1,59 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_transform_mask_test.h"
+
+#include <qtest_kde.h>
+#include "kis_transform_mask.h"
+#include "kis_transform_mask_params_interface.h"
+
+#include "testutil.h"
+
+
+void KisTransformMaskTest::test()
+{
+    QImage refImage(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
+    TestUtil::MaskParent p(refImage.rect());
+
+    p.layer->paintDevice()->convertFromQImage(refImage, 0);
+
+    KisTransformMaskSP mask = new KisTransformMask();
+
+    p.image->addNode(mask, p.layer);
+
+    QTransform transform =
+        QTransform::fromTranslate(100.0, 0.0) *
+        QTransform::fromScale(2.0, 1.0);
+
+    mask->setTransformParams(KisTransformMaskParamsInterfaceSP(
+                                 new KisDumbTransformMaskParams(transform)));
+
+    p.layer->setDirty(QRect(160, 160, 150, 300));
+    p.layer->setDirty(QRect(310, 160, 150, 300));
+    p.layer->setDirty(QRect(460, 160, 150, 300));
+    p.layer->setDirty(QRect(610, 160, 150, 300));
+    p.layer->setDirty(QRect(760, 160, 150, 300));
+
+    p.image->waitForDone();
+
+    QImage result = p.layer->projection()->convertToQImage(0);
+    TestUtil::checkQImage(result, "transform_mask_test", "partial", "single");
+
+}
+
+QTEST_KDEMAIN(KisTransformMaskTest, GUI)
diff --git a/krita/image/tests/kis_transform_mask_test.h \
b/krita/image/tests/kis_transform_mask_test.h new file mode 100644
index 0000000..32afb01
--- /dev/null
+++ b/krita/image/tests/kis_transform_mask_test.h
@@ -0,0 +1,31 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_TRANSFORM_MASK_TEST_H
+#define __KIS_TRANSFORM_MASK_TEST_H
+
+#include <QtTest/QtTest>
+
+class KisTransformMaskTest : public QObject
+{
+    Q_OBJECT
+private slots:
+    void test();
+};
+
+#endif /* __KIS_TRANSFORM_MASK_TEST_H */
diff --git a/krita/image/tests/kis_transform_worker_test.cpp \
b/krita/image/tests/kis_transform_worker_test.cpp index 5ee16a3..a6e4ec0 100644
--- a/krita/image/tests/kis_transform_worker_test.cpp
+++ b/krita/image/tests/kis_transform_worker_test.cpp
@@ -951,5 +951,37 @@ void KisTransformWorkerTest::generateTestImages()
     }
 }
 
+#include "kis_perspectivetransform_worker.h"
+
+
+void KisTransformWorkerTest::testPartialProcessing()
+{
+    TestUtil::TestProgressBar bar;
+    KoProgressUpdater pu(&bar);
+    KoUpdaterPtr updater = pu.startSubtask();
+
+    const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
+    QImage image(TestUtil::fetchDataFileLazy("test_transform_quality.png"));
+    KisPaintDeviceSP dev = new KisPaintDevice(cs);
+    dev->convertFromQImage(image, 0);
+
+    KisTransaction t(dev);
+
+    QTransform transform = QTransform::fromScale(2.0, 1.1);
+    transform.shear(1.1, 0);
+    transform.rotateRadians(M_PI / 18);
+
+    KisPerspectiveTransformWorker tw(0, transform, updater);
+    tw.runPartialDst(dev, dev, QRect(1200, 1200, 150, 150));
+    tw.runPartialDst(dev, dev, QRect(1350, 1200, 150, 150));
+    tw.runPartialDst(dev, dev, QRect(1200, 1350, 150, 150));
+    tw.runPartialDst(dev, dev, QRect(1350, 1350, 150, 150));
+
+    t.end();
+
+    QImage result = dev->convertToQImage(0);
+    TestUtil::checkQImage(result, "transform_test", "partial", "single");
+}
+
 QTEST_KDEMAIN(KisTransformWorkerTest, GUI)
 #include "kis_transform_worker_test.moc"
diff --git a/krita/image/tests/kis_transform_worker_test.h \
b/krita/image/tests/kis_transform_worker_test.h index d44c910..3f82def 100644
--- a/krita/image/tests/kis_transform_worker_test.h
+++ b/krita/image/tests/kis_transform_worker_test.h
@@ -62,6 +62,8 @@ private slots:
     void benchmarkShear();
     void benchmarkScaleRotateShear();
 
+    void testPartialProcessing();
+
 private:
     void generateTestImages();
 };
diff --git a/krita/image/tests/kis_transparency_mask_test.cpp \
b/krita/image/tests/kis_transparency_mask_test.cpp index 3fae211..c565632 100644
--- a/krita/image/tests/kis_transparency_mask_test.cpp
+++ b/krita/image/tests/kis_transparency_mask_test.cpp
@@ -67,10 +67,12 @@ void KisTransparencyMaskTest::testApply()
     KisTransparencyMaskSP mask;
 
 
+    QRect applyRect(0, 0, 200, 100);
+
     // Everything is selected
     initImage(image, layer, dev, mask);
     mask->initSelection(layer);
-    mask->apply(dev, QRect(0, 0, 200, 100));
+    mask->apply(dev, applyRect, applyRect, KisNode::N_FILTHY);
     QImage qimage = dev->convertToQImage(0, 0, 0, 200, 100);
 
     if (!TestUtil::compareQImages(errpoint,
@@ -83,7 +85,7 @@ void KisTransparencyMaskTest::testApply()
     initImage(image, layer, dev, mask);
     mask->initSelection(layer);
     mask->selection()->pixelSelection()->invert();
-    mask->apply(dev, QRect(0, 0, 200, 100));
+    mask->apply(dev, applyRect, applyRect, KisNode::N_FILTHY);
     qimage = dev->convertToQImage(0, 0, 0, 200, 100);
 
     if (!TestUtil::compareQImages(errpoint,
@@ -96,7 +98,7 @@ void KisTransparencyMaskTest::testApply()
     mask->initSelection(layer);
     mask->selection()->pixelSelection()->invert();
     mask->select(QRect(50, 0, 100, 100));
-    mask->apply(dev, QRect(0, 0, 200, 100));
+    mask->apply(dev, applyRect, applyRect, KisNode::N_FILTHY);
     qimage = dev->convertToQImage(0, 0, 0, 200, 100);
 
     if (!TestUtil::compareQImages(errpoint,
diff --git a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp \
b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp index \
                c834f4b..2970fa0 100644
--- a/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
+++ b/krita/plugins/extensions/dockers/defaultdockers/kis_layer_box.cpp
@@ -365,6 +365,7 @@ void KisLayerBox::setCanvas(KoCanvasBase *canvas)
         m_newLayerMenu->addSeparator();
         addActionToMenu(m_newLayerMenu, "add_new_transparency_mask");
         addActionToMenu(m_newLayerMenu, "add_new_filter_mask");
+        addActionToMenu(m_newLayerMenu, "add_new_transform_mask");
         addActionToMenu(m_newLayerMenu, "add_new_selection_mask");
     }
 
@@ -489,6 +490,7 @@ void KisLayerBox::slotContextMenuRequested(const QPoint &pos, \
const QModelIndex  addActionToMenu(convertToMenu, "convert_to_paint_layer");
         addActionToMenu(convertToMenu, "convert_to_transparency_mask");
         addActionToMenu(convertToMenu, "convert_to_filter_mask");
+        addActionToMenu(convertToMenu, "convert_to_transform_mask");
         addActionToMenu(convertToMenu, "convert_to_selection_mask");
 
         addActionToMenu(&menu, "isolate_layer");
@@ -496,6 +498,7 @@ void KisLayerBox::slotContextMenuRequested(const QPoint &pos, \
const QModelIndex  menu.addSeparator();
     addActionToMenu(&menu, "add_new_transparency_mask");
     addActionToMenu(&menu, "add_new_filter_mask");
+    addActionToMenu(&menu, "add_new_transform_mask");
     addActionToMenu(&menu, "add_new_selection_mask");
     menu.addSeparator();
     menu.addAction(m_selectOpaque);
diff --git a/krita/plugins/tools/tool_transform2/CMakeLists.txt \
b/krita/plugins/tools/tool_transform2/CMakeLists.txt index 14d3d7e..7e22ace 100644
--- a/krita/plugins/tools/tool_transform2/CMakeLists.txt
+++ b/krita/plugins/tools/tool_transform2/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(kritatooltransform_PART_SRCS
     tool_transform.cc
     tool_transform_args.cc
+    kis_transform_mask_adapter.cpp
     tool_transform_changes_tracker.cpp
     kis_tool_transform.cc
     kis_tool_transform_config_widget.cpp
diff --git a/krita/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp \
b/krita/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp index \
                4166647..dd154f7 100644
--- a/krita/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_free_transform_strategy.cpp
@@ -332,7 +332,7 @@ void KisFreeTransformStrategy::paint(QPainter &gc)
     handles.addRect(handleRect.translated(m_d->transaction.originalMiddleTop()));
     handles.addRect(handleRect.translated(m_d->transaction.originalMiddleBottom()));
 
-    QPointF rotationCenter = m_d->transaction.originalCenter() + \
m_d->currentArgs.rotationCenterOffset(); +    QPointF rotationCenter = \
m_d->currentArgs.originalCenter() + m_d->currentArgs.rotationCenterOffset();  QPointF \
dx(r + 3, 0);  QPointF dy(0, r + 3);
     handles.addEllipse(rotationCenterRect.translated(rotationCenter));
@@ -577,7 +577,7 @@ void KisFreeTransformStrategy::continuePrimaryAction(const \
QPointF &mousePos, bo  QPointF pt = m_d->transform.inverted().map(mousePos);
         pt = KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect());
 
-        QPointF newRotationCenterOffset = pt - m_d->transaction.originalCenter();
+        QPointF newRotationCenterOffset = pt - m_d->currentArgs.originalCenter();
 
         if (specialModifierActive) {
             if (qAbs(newRotationCenterOffset.x()) > \
qAbs(newRotationCenterOffset.y())) { @@ -652,7 +652,7 @@ void \
                KisFreeTransformStrategy::Private::recalculateTransformations()
      * The center of the original image should still
      * stay the the origin of CS
      */
-    KIS_ASSERT_RECOVER_NOOP(sanityCheckMatrix.map(transaction.originalCenter()).manhattanLength() \
< 1e-4); +    KIS_ASSERT_RECOVER_NOOP(sanityCheckMatrix.map(currentArgs.originalCenter()).manhattanLength() \
< 1e-4);  
     transform = m.finalTransform();
 
diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform.cc \
b/krita/plugins/tools/tool_transform2/kis_tool_transform.cc index 498de63..084312c \
                100644
--- a/krita/plugins/tools/tool_transform2/kis_tool_transform.cc
+++ b/krita/plugins/tools/tool_transform2/kis_tool_transform.cc
@@ -82,6 +82,9 @@
 #include "kis_free_transform_strategy.h"
 #include "kis_perspective_transform_strategy.h"
 
+#include "kis_transform_mask.h"
+#include "kis_transform_mask_adapter.h"
+
 #include "strokes/transform_stroke_strategy.h"
 
 KisToolTransform::KisToolTransform(KoCanvasBase * canvas)
@@ -630,6 +633,29 @@ void KisToolTransform::setWarpPointDensity( int density )
     m_optionsWidget->slotSetWarpDensity(density);
 }
 
+bool KisToolTransform::tryInitTransformModeFromNode(KisNodeSP node)
+{
+    bool result = false;
+
+    if (KisTransformMaskSP mask =
+        dynamic_cast<KisTransformMask*>(node.data())) {
+
+        KisTransformMaskParamsInterfaceSP savedParams =
+            mask->transformParams();
+
+        KisTransformMaskAdapter *adapter =
+            dynamic_cast<KisTransformMaskAdapter*>(savedParams.data());
+
+        if (adapter) {
+            m_currentArgs = adapter->savedArgs();
+            initGuiAfterTransformMode();
+            result = true;
+        }
+    }
+
+    return result;
+}
+
 void KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)
 {
     // NOTE: we are requesting an old value of m_currentArgs variable
@@ -638,8 +664,8 @@ void \
KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)  QString \
filterId = m_currentArgs.filterId();  
     m_currentArgs = ToolTransformArgs();
-    m_currentArgs.setOriginalCenter(m_transaction.originalCenter());
-    m_currentArgs.setTransformedCenter(m_transaction.originalCenter());
+    m_currentArgs.setOriginalCenter(m_transaction.originalCenterGeometric());
+    m_currentArgs.setTransformedCenter(m_transaction.originalCenterGeometric());
 
     if (mode == ToolTransformArgs::FREE_TRANSFORM) {
         m_currentArgs.setMode(ToolTransformArgs::FREE_TRANSFORM);
@@ -657,6 +683,11 @@ void \
KisToolTransform::initTransformMode(ToolTransformArgs::TransformMode mode)  \
m_currentArgs.setMode(ToolTransformArgs::PERSPECTIVE_4POINT);  }
 
+    initGuiAfterTransformMode();
+}
+
+void KisToolTransform::initGuiAfterTransformMode()
+{
     currentStrategy()->externalConfigChanged();
     outlineChanged();
     updateOptionWidget();
@@ -814,7 +845,9 @@ void \
KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode)  \
initThumbnailImage(previewDevice);  updateSelectionPath();
 
-    initTransformMode(mode);
+    if (!tryInitTransformModeFromNode(currentNode)) {
+        initTransformMode(mode);
+    }
 
     m_strokeData = StrokeData(image()->startStroke(strategy));
     clearDevices(m_transaction.rootNode(), m_workRecursively);
diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform.h \
b/krita/plugins/tools/tool_transform2/kis_tool_transform.h index a4df787..0dbb35f \
                100644
--- a/krita/plugins/tools/tool_transform2/kis_tool_transform.h
+++ b/krita/plugins/tools/tool_transform2/kis_tool_transform.h
@@ -232,7 +232,9 @@ private:
     void commitChanges();
 
 
+    bool tryInitTransformModeFromNode(KisNodeSP node);
     void initTransformMode(ToolTransformArgs::TransformMode mode);
+    void initGuiAfterTransformMode();
 
     void initThumbnailImage(KisPaintDeviceSP previewDevice);
     void updateSelectionPath();
diff --git a/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp \
b/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp index \
                841e0a2..362e340 100644
--- a/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_tool_transform_config_widget.cpp
@@ -844,8 +844,8 @@ void KisToolTransformConfigWidget::setDefaultWarpPoints(int \
pointsPerLine)  
     if (nbPoints == 1) {
         //there is actually no grid
-        origPoints[0] = m_transaction->originalCenter();
-        transfPoints[0] = m_transaction->originalCenter();
+        origPoints[0] = m_transaction->originalCenterGeometric();
+        transfPoints[0] = m_transaction->originalCenterGeometric();
     }
     else if (nbPoints > 1) {
         gridSpaceX = m_transaction->originalRect().width() / (pointsPerLine - 1);
diff --git a/krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.cpp \
b/krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.cpp new file mode \
100644 index 0000000..6ec5275
--- /dev/null
+++ b/krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.cpp
@@ -0,0 +1,72 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_transform_mask_adapter.h"
+
+#include <QTransform>
+
+#include "tool_transform_args.h"
+#include "kis_transform_utils.h"
+
+
+struct KisTransformMaskAdapter::Private
+{
+    ToolTransformArgs args;
+};
+
+
+KisTransformMaskAdapter::KisTransformMaskAdapter(const ToolTransformArgs &args)
+    : m_d(new Private)
+{
+    m_d->args = args;
+}
+
+KisTransformMaskAdapter::~KisTransformMaskAdapter()
+{
+}
+
+QTransform KisTransformMaskAdapter::finalAffineTransform() const
+{
+    KisTransformUtils::MatricesPack m(m_d->args);
+    return m.finalTransform();
+}
+
+bool KisTransformMaskAdapter::isAffine() const
+{
+    return m_d->args.mode() == ToolTransformArgs::FREE_TRANSFORM ||
+        m_d->args.mode() == ToolTransformArgs::PERSPECTIVE_4POINT;
+}
+
+bool KisTransformMaskAdapter::isHidden() const
+{
+    return false;
+}
+
+void KisTransformMaskAdapter::transformDevice(KisNodeSP node, KisPaintDeviceSP src, \
KisPaintDeviceSP dst) const +{
+    dst->makeCloneFrom(src, src->extent());
+
+    KisProcessingVisitor::ProgressHelper helper(node);
+    KisTransformUtils::transformDevice(m_d->args, dst, &helper);
+}
+
+const ToolTransformArgs& KisTransformMaskAdapter::savedArgs() const
+{
+    return m_d->args;
+}
+
diff --git a/krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.h \
b/krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.h new file mode \
100644 index 0000000..5963310
--- /dev/null
+++ b/krita/plugins/tools/tool_transform2/kis_transform_mask_adapter.h
@@ -0,0 +1,47 @@
+/*
+ *  Copyright (c) 2014 Dmitry Kazakov <dimula73@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_TRANSFORM_MASK_ADAPTER_H
+#define __KIS_TRANSFORM_MASK_ADAPTER_H
+
+#include <QScopedPointer>
+#include "kis_transform_mask_params_interface.h"
+
+class ToolTransformArgs;
+
+
+class KisTransformMaskAdapter : public KisTransformMaskParamsInterface
+{
+public:
+    KisTransformMaskAdapter(const ToolTransformArgs &args);
+    ~KisTransformMaskAdapter();
+
+    QTransform finalAffineTransform() const;
+    bool isAffine() const;
+    bool isHidden() const;
+
+    void transformDevice(KisNodeSP node, KisPaintDeviceSP src, KisPaintDeviceSP dst) \
const; +
+    const ToolTransformArgs& savedArgs() const;
+
+private:
+    struct Private;
+    const QScopedPointer<Private> m_d;
+};
+
+#endif /* __KIS_TRANSFORM_MASK_ADAPTER_H */
diff --git a/krita/plugins/tools/tool_transform2/kis_transform_utils.cpp \
b/krita/plugins/tools/tool_transform2/kis_transform_utils.cpp index cf037fd..5f908ec \
                100644
--- a/krita/plugins/tools/tool_transform2/kis_transform_utils.cpp
+++ b/krita/plugins/tools/tool_transform2/kis_transform_utils.cpp
@@ -148,3 +148,107 @@ bool KisTransformUtils::checkImageTooBig(const QRectF &bounds, \
const MatricesPac  
     return imageTooBig;
 }
+
+#include <kis_transform_worker.h>
+#include <kis_perspectivetransform_worker.h>
+#include <kis_warptransform_worker.h>
+#include <kis_cage_transform_worker.h>
+#include <kis_liquify_transform_worker.h>
+
+KisTransformWorker KisTransformUtils::createTransformWorker(const ToolTransformArgs \
&config, +                                                            \
KisPaintDeviceSP device, +                                                            \
KoUpdaterPtr updater, +                                                            \
QVector3D *transformedCenter /* OUT */) +{
+    {
+        KisTransformWorker t(0,
+                             config.scaleX(), config.scaleY(),
+                             config.shearX(), config.shearY(),
+                             config.originalCenter().x(),
+                             config.originalCenter().y(),
+                             config.aZ(),
+                             0, // set X and Y translation
+                             0, // to null for calculation
+                             0,
+                             config.filter());
+
+        *transformedCenter = QVector3D(t.transform().map(config.originalCenter()));
+    }
+
+    QPointF translation = config.transformedCenter() - \
(*transformedCenter).toPointF(); +
+    KisTransformWorker transformWorker(device,
+                                       config.scaleX(), config.scaleY(),
+                                       config.shearX(), config.shearY(),
+                                       config.originalCenter().x(),
+                                       config.originalCenter().y(),
+                                       config.aZ(),
+                                       (int)(translation.x()),
+                                       (int)(translation.y()),
+                                       updater,
+                                       config.filter());
+
+    return transformWorker;
+}
+
+void KisTransformUtils::transformDevice(const ToolTransformArgs &config,
+                                        KisPaintDeviceSP device,
+                                        KisProcessingVisitor::ProgressHelper \
*helper) +{
+    if (config.mode() == ToolTransformArgs::WARP) {
+        KoUpdaterPtr updater = helper->updater();
+
+        KisWarpTransformWorker worker(config.warpType(),
+                                      device,
+                                      config.origPoints(),
+                                      config.transfPoints(),
+                                      config.alpha(),
+                                      updater);
+        worker.run();
+    } else if (config.mode() == ToolTransformArgs::CAGE) {
+        KoUpdaterPtr updater = helper->updater();
+
+        KisCageTransformWorker worker(device,
+                                      config.origPoints(),
+                                      updater,
+                                      8);
+
+        worker.prepareTransform();
+        worker.setTransformedCage(config.transfPoints());
+        worker.run();
+    } else if (config.mode() == ToolTransformArgs::LIQUIFY) {
+        KoUpdaterPtr updater = helper->updater();
+        //FIXME:
+        Q_UNUSED(updater);
+
+        config.liquifyWorker()->run(device);
+    } else {
+        QVector3D transformedCenter;
+        KoUpdaterPtr updater1 = helper->updater();
+        KoUpdaterPtr updater2 = helper->updater();
+
+        KisTransformWorker transformWorker =
+            createTransformWorker(config, device, updater1, &transformedCenter);
+
+        transformWorker.run();
+
+        if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) {
+            KisPerspectiveTransformWorker perspectiveWorker(device,
+                                                            \
config.transformedCenter(), +                                                         \
config.aX(), +                                                            \
config.aY(), +                                                            \
config.cameraPos().z(), +                                                            \
updater2); +            perspectiveWorker.run();
+        } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
+            QTransform T =
+                QTransform::fromTranslate(config.transformedCenter().x(),
+                                          config.transformedCenter().y());
+
+            KisPerspectiveTransformWorker perspectiveWorker(device,
+                                                            T.inverted() * \
config.flattenedPerspectiveTransform() * T, +                                         \
updater2); +            perspectiveWorker.run();
+        }
+    }
+}
diff --git a/krita/plugins/tools/tool_transform2/kis_transform_utils.h \
b/krita/plugins/tools/tool_transform2/kis_transform_utils.h index 97e52a8..ab788d1 \
                100644
--- a/krita/plugins/tools/tool_transform2/kis_transform_utils.h
+++ b/krita/plugins/tools/tool_transform2/kis_transform_utils.h
@@ -25,8 +25,10 @@
 
 #include <QTransform>
 #include <QMatrix4x4>
+#include <kis_processing_visitor.h>
 
 class ToolTransformArgs;
+class KisTransformWorker;
 
 class KisTransformUtils
 {
@@ -73,6 +75,15 @@ public:
     };
 
     static bool checkImageTooBig(const QRectF &bounds, const MatricesPack &m);
+
+    static KisTransformWorker createTransformWorker(const ToolTransformArgs &config,
+                                                    KisPaintDeviceSP device,
+                                                    KoUpdaterPtr updater,
+                                                    QVector3D *transformedCenter /* \
OUT */); +
+    static void transformDevice(const ToolTransformArgs &config,
+                                KisPaintDeviceSP device,
+                                KisProcessingVisitor::ProgressHelper *helper);
 };
 
 #endif /* __KIS_TRANSFORM_UTILS_H */
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 \
                6fb1e17..28391a5 100644
--- a/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
+++ b/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.cpp
@@ -29,10 +29,47 @@
 #include <kis_transaction.h>
 #include <kis_painter.h>
 #include <kis_transform_worker.h>
-#include <kis_perspectivetransform_worker.h>
-#include <kis_warptransform_worker.h>
-#include <kis_cage_transform_worker.h>
-#include <kis_liquify_transform_worker.h>
+#include <kis_transform_mask.h>
+#include "kis_transform_mask_adapter.h"
+#include "kis_transform_utils.h"
+
+
+
+class ModifyTransformMaskCommand : public KUndo2Command {
+public:
+    ModifyTransformMaskCommand(KisTransformMaskSP mask, \
KisTransformMaskParamsInterfaceSP params) +        : m_mask(mask),
+          m_params(params),
+          m_oldParams(m_mask->transformParams())
+        {
+        }
+
+    void redo() {
+        m_mask->setTransformParams(m_params);
+
+        /**
+         * NOTE: this code "duplicates" the functionality provided
+         * by KisRecalculateTransformMaskJob, but there is not much
+         * reason for starting a separate stroke when a transformation
+         * has happened
+         */
+
+        m_mask->recaclulateStaticImage();
+        m_mask->setDirty();
+    }
+
+    void undo() {
+        m_mask->setTransformParams(m_oldParams);
+
+        m_mask->recaclulateStaticImage();
+        m_mask->setDirty();
+    }
+
+private:
+    KisTransformMaskSP m_mask;
+    KisTransformMaskParamsInterfaceSP m_params;
+    KisTransformMaskParamsInterfaceSP m_oldParams;
+};
 
 
 TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode,
@@ -42,7 +79,22 @@ TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP \
rootNode,  m_selection(selection)
 {
     if (rootNode->childCount() || !rootNode->paintDevice()) {
-        m_previewDevice = createDeviceCache(rootNode->projection());
+        KisPaintDeviceSP device;
+
+        if (dynamic_cast<KisTransformMask*>(rootNode.data())) {
+            KisNodeSP parentNode = rootNode->parent();
+            device = parentNode->paintDevice();
+
+            if (!device) {
+                device = parentNode->projection();
+            }
+
+        } else {
+            device = rootNode->projection();
+        }
+
+        m_previewDevice = createDeviceCache(device);
+
     } else {
         m_previewDevice = createDeviceCache(rootNode->paintDevice());
         putDeviceCache(rootNode->paintDevice(), m_previewDevice);
@@ -101,42 +153,6 @@ KisPaintDeviceSP \
TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src)  return cache;
 }
 
-KisTransformWorker createTransformWorker(const ToolTransformArgs &config,
-                                         KisPaintDeviceSP device,
-                                         KoUpdaterPtr updater,
-                                         QVector3D *transformedCenter /* OUT */)
-{
-    {
-        KisTransformWorker t(0,
-                             config.scaleX(), config.scaleY(),
-                             config.shearX(), config.shearY(),
-                             config.originalCenter().x(),
-                             config.originalCenter().y(),
-                             config.aZ(),
-                             0, // set X and Y translation
-                             0, // to null for calculation
-                             0,
-                             config.filter());
-
-        *transformedCenter = QVector3D(t.transform().map(config.originalCenter()));
-    }
-
-    QPointF translation = config.transformedCenter() - \
                (*transformedCenter).toPointF();
-
-    KisTransformWorker transformWorker(device,
-                                       config.scaleX(), config.scaleY(),
-                                       config.shearX(), config.shearY(),
-                                       config.originalCenter().x(),
-                                       config.originalCenter().y(),
-                                       config.aZ(),
-                                       (int)(translation.x()),
-                                       (int)(translation.y()),
-                                       updater,
-                                       config.filter());
-
-    return transformWorker;
-}
-
 bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const
 {
     return m_selection &&
@@ -172,21 +188,31 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData \
*data)  } if (KisExternalLayer *extLayer =
                   dynamic_cast<KisExternalLayer*>(td->node.data())) {
 
-                if (td->config.mode() == ToolTransformArgs::WARP) {
-                    qWarning() << "Warp transform of an external layer is not \
                supported:" << extLayer->name();
-                } else {
+                if (td->config.mode() == ToolTransformArgs::FREE_TRANSFORM ||
+                    td->config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
+
                     if (td->config.aX() || td->config.aY()) {
                         qWarning() << "Perspective transform of an external layer is \
not supported:" << extLayer->name();  }
 
                     QVector3D transformedCenter;
-                    KisTransformWorker w = createTransformWorker(td->config, 0, 0, \
&transformedCenter); +                    KisTransformWorker w = \
KisTransformUtils::createTransformWorker(td->config, 0, 0, &transformedCenter);  \
QTransform t = w.transform();  
                     runAndSaveCommand(KUndo2CommandSP(extLayer->transform(t)),
                                       KisStrokeJobData::CONCURRENT,
-                                      KisStrokeJobData::NORMAL);;
+                                      KisStrokeJobData::NORMAL);
                 }
+
+            } else if (KisTransformMask *transformMask =
+                       dynamic_cast<KisTransformMask*>(td->node.data())) {
+
+                runAndSaveCommand(KUndo2CommandSP(
+                                      new ModifyTransformMaskCommand(transformMask,
+                                                                     \
KisTransformMaskParamsInterfaceSP( +                                                  \
new KisTransformMaskAdapter(td->config)))), +                                  \
KisStrokeJobData::CONCURRENT, +                                  \
KisStrokeJobData::NORMAL);  }
         } else if (m_selection) {
 
@@ -197,9 +223,9 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData \
*data)  KisTransaction transaction(m_selection->pixelSelection());
 
             KisProcessingVisitor::ProgressHelper helper(td->node);
-            transformDevice(td->config,
-                            m_selection->pixelSelection(),
-                            &helper);
+            KisTransformUtils::transformDevice(td->config,
+                                               m_selection->pixelSelection(),
+                                               &helper);
 
             runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
                               KisStrokeJobData::CONCURRENT,
@@ -212,6 +238,15 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData \
*data)  putDeviceCache(device, createDeviceCache(device));
             }
             clearSelection(device);
+        } else if (KisTransformMask *transformMask =
+                   dynamic_cast<KisTransformMask*>(csd->node.data())) {
+
+            runAndSaveCommand(KUndo2CommandSP(
+                                  new ModifyTransformMaskCommand(transformMask,
+                                                                 \
KisTransformMaskParamsInterfaceSP( +                                                  \
new KisDumbTransformMaskParams(true)))), +                                  \
KisStrokeJobData::SEQUENTIAL, +                                  \
KisStrokeJobData::NORMAL);  }
     } else {
         KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
@@ -240,7 +275,7 @@ void TransformStrokeStrategy::transformAndMergeDevice(const \
ToolTransformArgs &c  {
     KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0;
 
-    transformDevice(config, src, helper);
+    KisTransformUtils::transformDevice(config, src, helper);
     if (src != dst) {
         QRect mergeRect = src->extent();
         KisPainter painter(dst);
@@ -249,65 +284,3 @@ void TransformStrokeStrategy::transformAndMergeDevice(const \
ToolTransformArgs &c  painter.end();
     }
 }
-
-void TransformStrokeStrategy::transformDevice(const ToolTransformArgs &config,
-                                              KisPaintDeviceSP device,
-                                              KisProcessingVisitor::ProgressHelper \
                *helper)
-{
-    if (config.mode() == ToolTransformArgs::WARP) {
-        KoUpdaterPtr updater = helper->updater();
-
-        KisWarpTransformWorker worker(config.warpType(),
-                                      device,
-                                      config.origPoints(),
-                                      config.transfPoints(),
-                                      config.alpha(),
-                                      updater);
-        worker.run();
-    } else if (config.mode() == ToolTransformArgs::CAGE) {
-        KoUpdaterPtr updater = helper->updater();
-
-        KisCageTransformWorker worker(device,
-                                      config.origPoints(),
-                                      updater,
-                                      8);
-
-        worker.prepareTransform();
-        worker.setTransformedCage(config.transfPoints());
-        worker.run();
-    } else if (config.mode() == ToolTransformArgs::LIQUIFY) {
-        KoUpdaterPtr updater = helper->updater();
-        //FIXME:
-        Q_UNUSED(updater);
-
-        config.liquifyWorker()->run(device);
-    } else {
-        QVector3D transformedCenter;
-        KoUpdaterPtr updater1 = helper->updater();
-        KoUpdaterPtr updater2 = helper->updater();
-
-        KisTransformWorker transformWorker =
-            createTransformWorker(config, device, updater1, &transformedCenter);
-
-        transformWorker.run();
-
-        if (config.mode() == ToolTransformArgs::FREE_TRANSFORM) {
-            KisPerspectiveTransformWorker perspectiveWorker(device,
-                                                            \
                config.transformedCenter(),
-                                                            config.aX(),
-                                                            config.aY(),
-                                                            config.cameraPos().z(),
-                                                            updater2);
-            perspectiveWorker.run();
-        } else if (config.mode() == ToolTransformArgs::PERSPECTIVE_4POINT) {
-            QTransform T =
-                QTransform::fromTranslate(config.transformedCenter().x(),
-                                          config.transformedCenter().y());
-
-            KisPerspectiveTransformWorker perspectiveWorker(device,
-                                                            T.inverted() * \
                config.flattenedPerspectiveTransform() * T,
-                                                            updater2);
-            perspectiveWorker.run();
-        }
-    }
-}
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 \
                308336b..466fb86 100644
--- a/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
+++ b/krita/plugins/tools/tool_transform2/strokes/transform_stroke_strategy.h
@@ -106,6 +106,7 @@ private:
     QHash<KisPaintDevice*, KisPaintDeviceSP> m_devicesCacheHash;
 
     KisPaintDeviceSP m_previewDevice;
+    KisTransformMaskSP writeToTransformMask;
 };
 
 #endif /* __TRANSFORM_STROKE_STRATEGY_H */
diff --git a/krita/plugins/tools/tool_transform2/transform_transaction_properties.h \
b/krita/plugins/tools/tool_transform2/transform_transaction_properties.h index \
                71be742..82dc2b8 100644
--- a/krita/plugins/tools/tool_transform2/transform_transaction_properties.h
+++ b/krita/plugins/tools/tool_transform2/transform_transaction_properties.h
@@ -52,7 +52,7 @@ TransformTransactionProperties(const QRectF &originalRect, \
ToolTransformArgs *cu  return m_originalRect;
     }
 
-    QPointF originalCenter() const {
+    QPointF originalCenterGeometric() const {
         return m_originalRect.center();
     }
 
diff --git a/krita/sdk/tests/testutil.h b/krita/sdk/tests/testutil.h
index 51060cd..651a18f 100644
--- a/krita/sdk/tests/testutil.h
+++ b/krita/sdk/tests/testutil.h
@@ -402,8 +402,8 @@ namespace TestUtil {
 
 struct MaskParent
 {
-    MaskParent()
-        : imageRect(0,0,512,512) {
+    MaskParent(const QRect &_imageRect = QRect(0,0,512,512))
+        : imageRect(_imageRect) {
         const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8();
         image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), \
imageRect.height(), cs, "test image");  layer = new KisPaintLayer(image, "paint1", \
                OPACITY_OPAQUE_U8);
diff --git a/krita/ui/kis_mask_manager.cc b/krita/ui/kis_mask_manager.cc
index 266d4d2..ec702d5 100644
--- a/krita/ui/kis_mask_manager.cc
+++ b/krita/ui/kis_mask_manager.cc
@@ -33,6 +33,7 @@
 #include <kis_clone_layer.h>
 #include <kis_group_layer.h>
 #include <kis_filter_mask.h>
+#include <kis_transform_mask.h>
 #include <kis_transparency_mask.h>
 #include <kis_selection_mask.h>
 #include <kis_effect_mask.h>
@@ -140,7 +141,7 @@ void KisMaskManager::adjustMaskPosition(KisNodeSP node, KisNodeSP \
activeNode, bo  }
 }
 
-void KisMaskManager::createMaskCommon(KisMaskSP mask, KisNodeSP activeNode, \
KisPaintDeviceSP copyFrom, const KUndo2MagicString& macroName, const QString \
&nodeType, const QString &nodeName) +void KisMaskManager::createMaskCommon(KisMaskSP \
mask, KisNodeSP activeNode, KisPaintDeviceSP copyFrom, const KUndo2MagicString& \
macroName, const QString &nodeType, const QString &nodeName, bool suppressSelection)  \
{  m_commandsAdapter->beginMacro(macroName);
 
@@ -151,10 +152,12 @@ void KisMaskManager::createMaskCommon(KisMaskSP mask, KisNodeSP \
activeNode, KisP  KisLayerSP parentLayer = dynamic_cast<KisLayer*>(parent.data());
     Q_ASSERT(parentLayer);
 
-    if (copyFrom) {
-        mask->initSelection(copyFrom, parentLayer);
-    } else {
-        mask->initSelection(m_view->selection(), parentLayer);
+    if (!suppressSelection) {
+        if (copyFrom) {
+            mask->initSelection(copyFrom, parentLayer);
+        } else {
+            mask->initSelection(m_view->selection(), parentLayer);
+        }
     }
 
     //counting number of KisSelectionMask
@@ -221,6 +224,12 @@ void KisMaskManager::createFilterMask(KisNodeSP activeNode, \
KisPaintDeviceSP cop  }
 }
 
+void KisMaskManager::createTransformMask(KisNodeSP activeNode)
+{
+    KisTransformMaskSP mask = new KisTransformMask();
+    createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Transform Mask"), \
"KisTransformMask", i18n("Transform Mask"), true); +}
+
 void KisMaskManager::duplicateMask()
 {
     if (!m_activeMask) return;
diff --git a/krita/ui/kis_mask_manager.h b/krita/ui/kis_mask_manager.h
index 9e8abec..4eb444f 100644
--- a/krita/ui/kis_mask_manager.h
+++ b/krita/ui/kis_mask_manager.h
@@ -115,10 +115,11 @@ private:
     void activateMask(KisMaskSP mask);
 
     void adjustMaskPosition(KisNodeSP node, KisNodeSP activeNode, bool \
                avoidActiveNode, KisNodeSP &parent, KisNodeSP &above);
-    void createMaskCommon(KisMaskSP mask, KisNodeSP activeNode, KisPaintDeviceSP \
copyFrom, const KUndo2MagicString &macroName, const QString &nodeType, const QString \
&nodeName); +    void createMaskCommon(KisMaskSP mask, KisNodeSP activeNode, \
KisPaintDeviceSP copyFrom, const KUndo2MagicString &macroName, const QString \
&nodeType, const QString &nodeName, bool suppressSelection = false);  
     void createSelectionMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom);
     void createFilterMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool \
quiet = false); +    void createTransformMask(KisNodeSP activeNode);
     void createTransparencyMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom);
 
     KisView2 * m_view;
diff --git a/krita/ui/kis_node_manager.cpp b/krita/ui/kis_node_manager.cpp
index 75a3ece..d462bd3 100644
--- a/krita/ui/kis_node_manager.cpp
+++ b/krita/ui/kis_node_manager.cpp
@@ -262,6 +262,9 @@ void KisNodeManager::setup(KActionCollection * actionCollection, \
KisActionManage  NEW_MASK_ACTION("add_new_filter_mask", i18n("&Filter Mask..."),
                     "KisFilterMask", koIcon("bookmarks"));
 
+    NEW_MASK_ACTION("add_new_transform_mask", i18n("&Transform Mask..."),
+                    "KisTransformMask", koIcon("bookmarks"));
+
     NEW_MASK_ACTION("add_new_selection_mask", i18n("&Local Selection"),
                     "KisSelectionMask", koIcon("edit-paste"));
 
@@ -446,6 +449,8 @@ void KisNodeManager::createNode(const QString & nodeType, bool \
quiet)  m_d->maskManager->createTransparencyMask(activeNode, 0);
     } else if (nodeType == "KisFilterMask") {
         m_d->maskManager->createFilterMask(activeNode, 0, quiet);
+    } else if (nodeType == "KisTransformMask") {
+        m_d->maskManager->createTransformMask(activeNode);
     } else if (nodeType == "KisSelectionMask") {
         m_d->maskManager->createSelectionMask(activeNode, 0);
     } else if (nodeType == "KisFileLayer") {
diff --git a/krita/ui/widgets/kis_scratch_pad.cpp \
b/krita/ui/widgets/kis_scratch_pad.cpp index 6c85e67..bdc3c20 100644
--- a/krita/ui/widgets/kis_scratch_pad.cpp
+++ b/krita/ui/widgets/kis_scratch_pad.cpp
@@ -294,7 +294,7 @@ void KisScratchPad::paintEvent ( QPaintEvent * event ) {
 
     QPointF offset = alignedImageRect.topLeft();
 
-    m_paintLayer->updateProjection(alignedImageRect);
+    m_paintLayer->updateProjection(alignedImageRect, KisNode::N_FILTHY);
     KisPaintDeviceSP projection = m_paintLayer->projection();
 
     QImage image = projection->convertToQImage(m_displayProfile,
_______________________________________________
Krita mailing list
kimageshop@kde.org
https://mail.kde.org/mailman/listinfo/kimageshop


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

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