diff --git a/krita/image/CMakeLists.txt b/krita/image/CMakeLists.txt
index 2cf384f..20e33c5 100644
--- a/krita/image/CMakeLists.txt
+++ b/krita/image/CMakeLists.txt
@@ -109,6 +109,7 @@ set(kritaimage_LIB_SRCS
    kis_filter_strategy.cc
    kis_gradient_painter.cc
    kis_gradient_job.cpp
+   kis_merge_walker.cc
    kis_group_layer.cc
    kis_count_visitor.cpp
    kis_histogram.cc
@@ -120,7 +121,6 @@ set(kritaimage_LIB_SRCS
    kis_rect_mask_generator.cpp
    kis_circle_mask_generator.cpp
    kis_math_toolbox.cpp
-   kis_merge_visitor.cpp
    kis_name_server.cpp
    kis_node.cpp
    kis_node_facade.cpp
diff --git a/krita/image/kis_adjustment_layer.cc b/krita/image/kis_adjustment_layer.cc
index 8b2436d..1d66bfa 100644
--- a/krita/image/kis_adjustment_layer.cc
+++ b/krita/image/kis_adjustment_layer.cc
@@ -71,6 +71,42 @@ void KisAdjustmentLayer::setFilter(KisFilterConfiguration * filterConfig)
     m_d->filterConfig = filterConfig;
 }
 
+QRect KisAdjustmentLayer::changeRect(const QRect& rect) const
+{
+    QRect filteredRect = rect;
+
+    if (m_d->filterConfig) {
+        KisFilterSP filter = KisFilterRegistry::instance()->value(m_d->filterConfig->name());
+        filteredRect = filter->changedRect(rect, m_d->filterConfig);
+    }
+
+    /**
+     * We can't paint outside a selection, that is why we call
+     * KisSelectionBasedLayer::changeRect to crop actual change
+     * area in the end
+     */
+    filteredRect = KisSelectionBasedLayer::changeRect(filteredRect);
+
+    return rect | filteredRect;
+}
+
+QRect KisAdjustmentLayer::needRect(const QRect& rect, PositionToFilthy pos) const
+{
+    Q_UNUSED(pos);
+    if (!m_d->filterConfig) return rect;
+    KisFilterSP filter = KisFilterRegistry::instance()->value(m_d->filterConfig->name());
+
+    /**
+     * If we need some additional pixels even outside of a selection
+     * for accurate layer filtering, we'll get them!
+     * And no KisSelectionBasedLayer::needRect will prevent us
+     * from doing this! ;)
+     * That's why simply we do not call
+     * KisSelectionBasedLayer::needRect here :)
+     */
+    return filter->neededRect(rect, m_d->filterConfig);
+}
+
 bool KisAdjustmentLayer::accept(KisNodeVisitor & v)
 {
     return v.visit(this);
diff --git a/krita/image/kis_adjustment_layer.h b/krita/image/kis_adjustment_layer.h
index 10094a4..7dd49e0 100644
--- a/krita/image/kis_adjustment_layer.h
+++ b/krita/image/kis_adjustment_layer.h
@@ -107,6 +107,9 @@ public:
      */
     void setFilter(KisFilterConfiguration *filterConfig);
 
+    QRect changeRect(const QRect& rect) const;
+    QRect needRect(const QRect& rect, PositionToFilthy pos = NORMAL) const;
+
 public slots:
     /**
      * gets this AdjustmentLayer. Overriddes function in
diff --git a/krita/image/kis_async_merger.h b/krita/image/kis_async_merger.h
new file mode 100644
index 0000000..4135b6d
--- /dev/null
+++ b/krita/image/kis_async_merger.h
@@ -0,0 +1,273 @@
+/* Copyright (c) Dmitry Kazakov <dimula73@gmail.com>, 2009
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KIS_ASYNC_MERGER_H
+#define __KIS_ASYNC_MERGER_H
+
+#include <QDebug>
+
+//#include <KoColorSpace.h>
+#include <KoCompositeOp.h>
+#include <KoUpdater.h>
+
+#include "kis_types.h"
+#include "kis_paint_device.h"
+#include "kis_node_visitor.h"
+#include "kis_painter.h"
+#include "kis_layer.h"
+#include "kis_group_layer.h"
+#include "kis_adjustment_layer.h"
+#include "generator/kis_generator_layer.h"
+#include "kis_external_layer_iface.h"
+#include "kis_paint_layer.h"
+#include "filter/kis_filter.h"
+#include "filter/kis_filter_configuration.h"
+#include "filter/kis_filter_registry.h"
+#include "kis_selection.h"
+#include "kis_transaction.h"
+//#include "kis_iterators_pixel.h"
+#include "kis_clone_layer.h"
+#include "kis_processing_information.h"
+//#include "kis_node.h"
+//#include "kis_projection.h"
+#include "kis_node_progress_proxy.h"
+
+
+#include "kis_merge_walker.h"
+
+
+class KisUpdateOriginalVisitor : public KisNodeVisitor
+{
+public:
+    KisUpdateOriginalVisitor(QRect updateRect, KisPaintDeviceSP projection)
+        : m_updateRect(updateRect), m_projection(projection)
+    {
+    }
+
+    ~KisUpdateOriginalVisitor() {
+    }
+
+public:
+    using KisNodeVisitor::visit;
+
+    bool visit(KisAdjustmentLayer* layer) {
+        Q_ASSERT(m_projection);
+        if (!layer->visible()) return true;
+
+        KisFilterConfiguration *filterConfig = layer->filter();
+        if (!filterConfig) return true;
+
+        KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name());
+        if (!filter) return false;
+
+        KisPaintDeviceSP originalDevice = layer->original();
+        originalDevice->clear(m_updateRect);
+
+        if(!m_projection) return true;
+        QRect applyRect = m_updateRect & m_projection->extent();
+
+        /**
+         * FIXME: check whether it's right to leave a selection to
+         * a projection mechanism, not for the filter
+         */
+        KisConstProcessingInformation srcCfg(m_projection, applyRect.topLeft(), 0);
+        KisProcessingInformation dstCfg(originalDevice, applyRect.topLeft(), 0);
+
+        Q_ASSERT(layer->nodeProgressProxy());
+
+        KoProgressUpdater updater(layer->nodeProgressProxy());
+        updater.start(100, filter->name());
+        QPointer<KoUpdater> updaterPtr = updater.startSubtask();
+
+        KisTransaction* transaction =
+            new KisTransaction("", originalDevice);
+        filter->process(srcCfg, dstCfg, applyRect.size(),
+                        filterConfig, updaterPtr);
+        delete transaction;
+
+        updaterPtr->setProgress(100);
+
+        return true;
+    }
+
+    bool visit(KisExternalLayer*) {
+        return true;
+    }
+
+    bool visit(KisGeneratorLayer*) {
+        return true;
+    }
+
+    bool visit(KisPaintLayer*) {
+        return true;
+    }
+
+    bool visit(KisGroupLayer*) {
+        return true;
+    }
+
+    bool visit(KisCloneLayer*) {
+        return true;
+    }
+
+    bool visit(KisNode*) {
+        return true;
+    }
+    bool visit(KisFilterMask*) {
+        return true;
+    }
+    bool visit(KisTransparencyMask*) {
+        return true;
+    }
+    bool visit(KisTransformationMask*) {
+        return true;
+    }
+    bool visit(KisSelectionMask*) {
+        return true;
+    }
+
+private:
+    QRect m_updateRect;
+    KisPaintDeviceSP m_projection;
+};
+
+
+class KisAsyncMerger
+{
+public:
+    KisAsyncMerger() {
+    }
+
+    ~KisAsyncMerger() {
+    }
+
+    /**
+     * FIXME: Check node<->layer transitions
+     */
+
+    void startMerge(KisBaseRectsWalker &walker) {
+        KisMergeWalker::NodeStack &nodeStack = walker.nodeStack();
+
+        const bool useTempProjections = walker.needRectVaries();
+
+        while(!nodeStack.isEmpty()) {
+            KisMergeWalker::JobItem item = nodeStack.pop();
+            if(isRootNode(item.m_node)) continue;
+
+            KisLayerSP currentNode = dynamic_cast<KisLayer*>(item.m_node.data());
+            QRect applyRect = item.m_applyRect;
+
+            if(!m_currentProjection)
+                setupProjection(currentNode, applyRect, useTempProjections);
+
+            KisUpdateOriginalVisitor originalVisitor(applyRect,
+                                                     m_currentProjection);
+
+            switch(item.m_position) {
+            case KisMergeWalker::N_TOPMOST:
+                currentNode->accept(originalVisitor);
+                currentNode->updateProjection(applyRect);
+                compositeWithProjection(currentNode, applyRect);
+                writeProjection(currentNode, useTempProjections, applyRect);
+                resetProjection();
+                break;
+            case KisMergeWalker::N_NORMAL:
+                currentNode->accept(originalVisitor);
+                currentNode->updateProjection(applyRect);
+            case KisMergeWalker::N_LOWER:
+            case KisMergeWalker::N_BOTTOMMOST:
+                compositeWithProjection(currentNode, applyRect);
+            }
+
+        }
+    }
+
+private:
+    static inline bool isRootNode(KisNodeSP node) {
+        return !node->parent();
+    }
+
+    void resetProjection() {
+        m_currentProjection = 0;
+    }
+
+    void setupProjection(KisNodeSP currentNode, const QRect& rect, bool useTempProjection) {
+        KisPaintDeviceSP parentOriginal = currentNode->parent()->original();
+
+        if (parentOriginal != currentNode->projection()) {
+            if (useTempProjection) {
+                m_currentProjection =
+                    new KisPaintDevice(parentOriginal->colorSpace());
+            }
+            else {
+                parentOriginal->clear(rect);
+                m_currentProjection = parentOriginal;
+            }
+        }
+        else {
+            /**
+             * It happened so that our parent uses our own projection as
+             * its original. It means obligeChild mechanism works.
+             * We won't initialise m_currentProjection. This will cause
+             * writeProjection() and compositeWithProjection() do nothing
+             * when called.
+             */
+            /* NOP */
+        }
+    }
+
+    void writeProjection(KisNodeSP topmostNode, bool useTempProjection, QRect rect) {
+        Q_UNUSED(useTempProjection);
+        if (!m_currentProjection) return;
+
+        KisPaintDeviceSP parentOriginal = topmostNode->parent()->original();
+
+        if(m_currentProjection != parentOriginal) {
+            KisPainter gc(parentOriginal);
+            gc.setCompositeOp(parentOriginal->colorSpace()->compositeOp(COMPOSITE_COPY));
+            gc.bitBlt(rect.topLeft(), m_currentProjection, rect);
+        }
+    }
+
+    bool compositeWithProjection(KisLayerSP layer, const QRect &rect) {
+
+        if (!m_currentProjection) return true;
+        if (!layer->visible()) return true;
+
+        KisPaintDeviceSP device = layer->projection();
+        if (!device) return true;
+
+        QRect needRect = rect & device->extent();
+        if(needRect.isEmpty()) return true;
+
+        KisPainter gc(m_currentProjection);
+        gc.setChannelFlags(layer->channelFlags());
+        gc.setCompositeOp(layer->compositeOp());
+        gc.setOpacity(layer->opacity());
+        gc.bitBlt(needRect.topLeft(), device, needRect);
+
+        return true;
+    }
+
+private:
+    KisPaintDeviceSP m_currentProjection;
+};
+
+
+#endif /* __KIS_ASYNC_MERGER_H */
+
diff --git a/krita/image/kis_base_rects_walker.h b/krita/image/kis_base_rects_walker.h
new file mode 100644
index 0000000..360131b
--- /dev/null
+++ b/krita/image/kis_base_rects_walker.h
@@ -0,0 +1,202 @@
+/*
+ *  Copyright (c) 2009 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_BASE_RECTS_WALKER_H
+#define __KIS_BASE_RECTS_WALKER_H
+
+#include <QStack>
+
+class KRITAIMAGE_EXPORT KisBaseRectsWalker
+{
+public:
+    enum NodePosition {
+        N_NORMAL,
+        N_LOWER,
+        N_TOPMOST,
+        N_BOTTOMMOST
+    };
+
+    struct JobItem {
+        KisNodeSP m_node;
+        NodePosition m_position;
+
+        /**
+         * The rect that should be prepared on this node.
+         * E.g. area where the filter applies on filter layer
+         * or an area of a paint layer that will be copied to
+         * the projection.
+         */
+        QRect m_applyRect;
+    };
+
+    typedef QStack<JobItem> NodeStack;
+
+public:
+    KisBaseRectsWalker(QRect cropRect) {
+        setCropRect(cropRect);
+    }
+
+    virtual ~KisBaseRectsWalker() {
+    }
+
+    void collectRects(KisNodeSP node, const QRect& requestedRect) {
+        clear();
+        m_resultChangeRect = requestedRect;
+        m_requestedRect = requestedRect;
+        m_startNode = node;
+        startTrip(node);
+    }
+
+    inline void setCropRect(QRect cropRect) {
+        m_cropRect = cropRect;
+    }
+
+    inline QRect cropRect() const{
+        return m_cropRect;
+    }
+
+    // return a reference for efficiency reasons
+    inline NodeStack& nodeStack() {
+        return m_mergeTask;
+    }
+
+    inline const QRect& accessRect() const {
+        return m_resultAccessRect;
+    }
+
+    inline const QRect& changeRect() const {
+        return m_resultChangeRect;
+    }
+
+    inline bool needRectVaries() const {
+        return m_needRectVaries;
+    }
+
+    inline bool changeRectVaries() const {
+        return m_changeRectVaries;
+    }
+
+protected:
+
+    /**
+     * Initiates collecting of rects.
+     * Should be implemented in derived classes
+     */
+    virtual void startTrip(KisNodeSP startWith) = 0;
+
+protected:
+    inline void clear() {
+        m_resultAccessRect = /*m_resultChangeRect =*/
+            m_childNeedRect = m_lastNeedRect = QRect();
+
+        m_needRectVaries = m_changeRectVaries = false;
+        m_mergeTask.clear();
+
+        // Not needed really. Think over removing.
+        //m_startNode = 0;
+        //m_requestedRect = QRect();
+    }
+
+    inline void pushJob(KisNodeSP node, NodePosition position, QRect applyRect) {
+        JobItem item = {node, position, applyRect};
+        m_mergeTask.push(item);
+    }
+
+    inline QRect cropThisRect(const QRect& rect) {
+        return m_cropRect.isValid() ? rect & m_cropRect : rect;
+    }
+
+    /**
+     * Called for every node we meet on a forward way of the trip.
+     */
+    virtual void registerChangeRect(KisNodeSP node) {
+        QRect currentChangeRect = node->changeRect(m_resultChangeRect);
+        currentChangeRect = cropThisRect(currentChangeRect);
+
+        if(!m_changeRectVaries)
+            m_changeRectVaries = currentChangeRect != m_resultChangeRect;
+
+        m_resultChangeRect = currentChangeRect;
+    }
+
+    /**
+     * Called for every node we meet on a backward way of the trip.
+     */
+    virtual void registerNeedRect(KisNodeSP node, NodePosition position) {
+        if(m_mergeTask.isEmpty())
+            m_resultAccessRect = m_childNeedRect =
+                m_lastNeedRect = m_resultChangeRect;
+
+        QRect currentNeedRect;
+
+        switch(position) {
+        case N_TOPMOST:
+            m_lastNeedRect = m_childNeedRect;
+        case N_NORMAL:
+            if(!m_lastNeedRect.isEmpty())
+                pushJob(node, position, m_lastNeedRect);
+            //else /* Why push empty rect? */;
+
+            m_lastNeedRect = node->needRect(m_lastNeedRect,
+                                            KisNode::NORMAL);
+            m_lastNeedRect = cropThisRect(m_lastNeedRect);
+            m_childNeedRect = m_lastNeedRect;
+            break;
+        case N_LOWER:
+        case N_BOTTOMMOST:
+            if(!m_lastNeedRect.isEmpty()) {
+                pushJob(node, position, m_lastNeedRect);
+                m_lastNeedRect = node->needRect(m_lastNeedRect,
+                                                KisNode::BELOW_FILTHY);
+                m_lastNeedRect = cropThisRect(m_lastNeedRect);
+            }
+            break;
+        default:
+            qFatal("Merge visitor: node position(%d) is out of range", position);
+        }
+
+        if(!m_needRectVaries)
+            m_needRectVaries = m_resultAccessRect != m_lastNeedRect;
+        m_resultAccessRect |= m_lastNeedRect;
+    }
+
+private:
+    /**
+     * The result variables.
+     * By the end of a recursion they will store a complete
+     * data for a successful merge operation.
+     */
+    QRect m_resultAccessRect;
+    QRect m_resultChangeRect;
+    bool m_needRectVaries;
+    bool m_changeRectVaries;
+    NodeStack m_mergeTask;
+
+    /**
+     * Temporary variables
+     */
+    KisNodeSP m_startNode;
+    QRect m_requestedRect;
+    QRect m_cropRect;
+
+    QRect m_childNeedRect;
+    QRect m_lastNeedRect;
+};
+
+#endif /* __KIS_BASE_RECTS_WALKER_H */
+
diff --git a/krita/image/kis_clone_layer.cpp b/krita/image/kis_clone_layer.cpp
index 99ffac5..f68090f 100644
--- a/krita/image/kis_clone_layer.cpp
+++ b/krita/image/kis_clone_layer.cpp
@@ -100,13 +100,6 @@ bool KisCloneLayer::needProjection() const
     return m_d->x || m_d->y;
 }
 
-QRect KisCloneLayer::repaintOriginal(KisPaintDeviceSP original,
-                                     const QRect& rect)
-{
-    Q_UNUSED(original);
-    return rect;
-}
-
 void KisCloneLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
         KisPaintDeviceSP projection,
         const QRect& rect) const
diff --git a/krita/image/kis_clone_layer.h b/krita/image/kis_clone_layer.h
index 5083627..fa62202 100644
--- a/krita/image/kis_clone_layer.h
+++ b/krita/image/kis_clone_layer.h
@@ -63,9 +63,6 @@ public:
     KisPaintDeviceSP paintDevice() const;
     bool needProjection() const;
 
-    QRect repaintOriginal(KisPaintDeviceSP original,
-                          const QRect& rect);
-
     void copyOriginalToProjection(const KisPaintDeviceSP original,
                                   KisPaintDeviceSP projection,
                                   const QRect& rect) const;
diff --git a/krita/image/kis_filter_mask.cpp b/krita/image/kis_filter_mask.cpp
index 8a9a88d..d1a17e8 100644
--- a/krita/image/kis_filter_mask.cpp
+++ b/krita/image/kis_filter_mask.cpp
@@ -154,8 +154,9 @@ QRect KisFilterMask::changeRect(const QRect& rect) const
     return rect | filteredRect;
 }
 
-QRect KisFilterMask::needRect(const QRect& rect) const
+QRect KisFilterMask::needRect(const QRect& rect, PositionToFilthy pos) const
 {
+    Q_UNUSED(pos);
     if (!m_d->filterConfig) return rect;
     KisFilterSP filter = KisFilterRegistry::instance()->value(m_d->filterConfig->name());
 
diff --git a/krita/image/kis_filter_mask.h b/krita/image/kis_filter_mask.h
index 52e7936..a41caee 100644
--- a/krita/image/kis_filter_mask.h
+++ b/krita/image/kis_filter_mask.h
@@ -65,7 +65,7 @@ public:
                        const QRect & rc) const;
 
     QRect changeRect(const QRect &rect) const;
-    QRect needRect(const QRect &rect) const;
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const;
 
 private:
 
diff --git a/krita/image/kis_full_refresh_walker.h b/krita/image/kis_full_refresh_walker.h
new file mode 100644
index 0000000..a446e1d
--- /dev/null
+++ b/krita/image/kis_full_refresh_walker.h
@@ -0,0 +1,79 @@
+/*
+ *  Copyright (c) 2010 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_FULL_REFRESH_WALKER_H
+#define __KIS_FULL_REFRESH_WALKER_H
+
+#include "kis_node.h"
+#include "kis_types.h"
+#include "kis_base_rects_walker.h"
+
+class KRITAIMAGE_EXPORT KisFullRefreshWalker : public KisBaseRectsWalker
+{
+
+public:
+    KisFullRefreshWalker(QRect cropRect)
+        : KisBaseRectsWalker(cropRect)
+    {
+    }
+
+    virtual ~KisFullRefreshWalker()
+    {
+    }
+
+private:
+    inline bool canHaveChildLayers(KisNodeSP node) {
+        return qobject_cast<KisGroupLayer*>(node.data());
+    }
+    static inline bool isRootNode(KisNodeSP node) {
+        return !node->parent();
+    }
+
+protected:
+    void startTrip(KisNodeSP startWith) {
+        /**
+         * We don't even bother about change rect, because
+         * it is gracefully set up to requestedRect by the base class.
+         * Anyway first invocation of startTrip will usually
+         * have startWith set to root layer
+         */
+
+        // Just for consistency reasons
+        if(isRootNode(startWith))
+            registerNeedRect(startWith, N_TOPMOST);
+
+        KisNodeSP currentNode = startWith->lastChild();
+        if(!currentNode) return;
+
+        registerNeedRect(currentNode, N_TOPMOST);
+
+        while ((currentNode = currentNode->prevSibling())) {
+            registerNeedRect(currentNode, N_NORMAL);
+        }
+
+        currentNode = startWith->lastChild();
+        do {
+            if(canHaveChildLayers(currentNode))
+                startTrip(currentNode);
+        } while ((currentNode = currentNode->prevSibling()));
+    }
+};
+
+
+#endif /* __KIS_FULL_REFRESH_WALKER_H */
+
diff --git a/krita/image/kis_group_layer.cc b/krita/image/kis_group_layer.cc
index 30e4e69..d4ee2dc 100644
--- a/krita/image/kis_group_layer.cc
+++ b/krita/image/kis_group_layer.cc
@@ -27,7 +27,6 @@
 #include "kis_debug.h"
 #include "kis_image.h"
 #include "kis_paint_device.h"
-#include "kis_merge_visitor.h"
 
 class KisGroupLayer::Private
 {
@@ -41,7 +40,6 @@ public:
     KisPaintDeviceSP paintDevice;
     qint32 x;
     qint32 y;
-    KisNodeSP dirtyNode;
 };
 
 KisGroupLayer::KisGroupLayer(KisImageWSP image, const QString &name, quint8 opacity) :
@@ -105,7 +103,7 @@ void KisGroupLayer::resetCache(const KoColorSpace *colorSpace)
 
 KisPaintDeviceSP KisGroupLayer::tryObligeChild() const
 {
-    if (parent().isNull() && childCount() == 1) {
+    if (/*parent().isNull() &&*/ childCount() == 1) {
         const KisLayer *child = dynamic_cast<KisLayer*>(firstChild().data());
 
         if (child &&
@@ -133,62 +131,11 @@ KisPaintDeviceSP KisGroupLayer::original() const
     return childOriginal ? childOriginal : m_d->paintDevice;
 }
 
-void KisGroupLayer::setDirtyNode(KisNodeSP node)
-{
-    m_d->dirtyNode = node;
-}
-
-QRect KisGroupLayer::repaintOriginal(KisPaintDeviceSP original,
-                                     const QRect& rect)
-{
-    if (original == tryObligeChild()) {
-        return rect;
-    }
-
-    // make the original empty, it will be filled with the children
-    original->clear(rect);
-
-    // find the first adjustmentlayer under the dirty child,
-    // if there is one, we will use its projection as a starting point
-    KisNodeSP startWith = firstChild();
-    if (m_d->dirtyNode) {
-        KisNodeSP node = firstChild();
-        while (node) {
-            if (node.data() == m_d->dirtyNode.data())
-                break;
-            if (node->inherits("KisAdjustmentLayer")) {
-                startWith = node;
-            }
-            node = node->nextSibling();
-        }
-    }
-
-    // We can optimize, so copy the projection of the filter layer
-    // to the original of the group layer.
-    if (startWith != firstChild()) {
-        KisPainter gc(original);
-        gc.setCompositeOp(original->colorSpace()->compositeOp(COMPOSITE_COPY));
-        gc.bitBlt(rect.topLeft(), startWith->projection(), rect);
-        // move one node beyond the adjustment layer to avoid refiltering
-        startWith = startWith->nextSibling();
-    }
-
-    KisMergeVisitor visitor(original, rect);
-    while (startWith) {
-        startWith->accept(visitor);
-        startWith = startWith->nextSibling();
-    }
-
-    return rect;
-
-}
-
 bool KisGroupLayer::accept(KisNodeVisitor &v)
 {
     return v.visit(this);
 }
 
-
 qint32 KisGroupLayer::x() const
 {
     return m_d->x;
diff --git a/krita/image/kis_group_layer.h b/krita/image/kis_group_layer.h
index 5cf65de..00adc35 100644
--- a/krita/image/kis_group_layer.h
+++ b/krita/image/kis_group_layer.h
@@ -64,17 +64,6 @@ public:
     /// @return the projection of the layers in the group before the masks are applied.
     KisPaintDeviceSP original() const;
 
-    /**
-     * If a dirty node is set, repaintOriginal can optimize by checking
-     * whether there is an adjustment layer in the stack, under the dirty
-     * node.
-     *
-     * XXX: protect by a mutex?
-     */
-    void setDirtyNode(KisNodeSP node);
-
-    QRect repaintOriginal(KisPaintDeviceSP original, const QRect& rect);
-
     qint32 x() const;
     qint32 y() const;
     void setX(qint32 x);
diff --git a/krita/image/kis_image.cc b/krita/image/kis_image.cc
index 231ce8c..b68f8d9 100644
--- a/krita/image/kis_image.cc
+++ b/krita/image/kis_image.cc
@@ -67,8 +67,6 @@
 #include "kis_crop_visitor.h"
 #include "kis_meta_data_merge_strategy.h"
 
-#include "kis_refresh_visitor.h"
-
 class KisImage::KisImagePrivate
 {
 public:
@@ -1150,8 +1148,9 @@ KisPerspectiveGrid* KisImage::perspectiveGrid()
 
 void KisImage::refreshGraph()
 {
-    KisRefreshVisitor refresher(this);
-    rootLayer()->accept(refresher);
+    if (!locked() && m_d->projection) {
+        m_d->projection->fullRefresh();
+    }
 }
 
 void KisImage::slotProjectionUpdated(const QRect & rc)
diff --git a/krita/image/kis_layer.cc b/krita/image/kis_layer.cc
index 1539da6..9ed557c 100644
--- a/krita/image/kis_layer.cc
+++ b/krita/image/kis_layer.cc
@@ -357,11 +357,8 @@ QRect KisLayer::updateProjection(const QRect& rect)
             !originalDevice) return QRect();
 
     if (!needProjection() && !hasEffectMasks()) {
-        updatedRect = repaintOriginal(originalDevice, updatedRect);
         m_d->projection = 0;
     } else {
-        updatedRect = repaintOriginal(originalDevice, updatedRect);
-
         if (!updatedRect.isEmpty()) {
 
             if (!m_d->projection ||
diff --git a/krita/image/kis_layer.h b/krita/image/kis_layer.h
index f1a36b7..11cb02d 100644
--- a/krita/image/kis_layer.h
+++ b/krita/image/kis_layer.h
@@ -78,9 +78,6 @@ public:
      */
     virtual QRect updateProjection(const QRect& rect);
 
-    virtual QRect repaintOriginal(KisPaintDeviceSP original,
-                                  const QRect& rect) = 0;
-
     virtual bool needProjection() const;
 
     virtual void copyOriginalToProjection(const KisPaintDeviceSP original,
diff --git a/krita/image/kis_mask.cc b/krita/image/kis_mask.cc
index cf2f29b..374a154 100644
--- a/krita/image/kis_mask.cc
+++ b/krita/image/kis_mask.cc
@@ -149,8 +149,9 @@ void KisMask::apply(KisPaintDeviceSP projection, const QRect & rc) const
     }
 }
 
-QRect KisMask::needRect(const QRect &rect) const
+QRect KisMask::needRect(const QRect &rect,  PositionToFilthy pos) const
 {
+    Q_UNUSED(pos);
     QRect resultRect = rect;
     if (m_d->selection)
         resultRect &= m_d->selection->selectedRect();
diff --git a/krita/image/kis_mask.h b/krita/image/kis_mask.h
index 119013c..107ea20 100644
--- a/krita/image/kis_mask.h
+++ b/krita/image/kis_mask.h
@@ -137,7 +137,7 @@ public:
     virtual void setDirty(const QRect & rect);
     virtual void setDirty(const QRegion & region);
 
-    QRect needRect(const QRect &rect) const;
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const;
     QRect changeRect(const QRect &rect) const;
     QImage createThumbnail(qint32 w, qint32 h);
 
diff --git a/krita/image/kis_merge_visitor.cpp b/krita/image/kis_merge_visitor.cpp
deleted file mode 100644
index f51b60f..0000000
--- a/krita/image/kis_merge_visitor.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2008
- *           (C) Dmitry Kazakov <dimula73@gmail.com>, 2009
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB.  If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-#include "kis_merge_visitor.h"
-
-bool KisMergeVisitor::compositeWithProjection(KisLayer *layer, const QRect &rect)
-{
-
-    Q_ASSERT(m_projection);
-    if (!layer->visible()) return true;
-
-    KisPaintDeviceSP device = layer->projection();
-    if (!device) return true;
-
-    Q_ASSERT(layer->compositeOp());
-    if (!layer->compositeOp()) return false;
-
-    QRect needRect = rect & device->extent();
-
-    KisPainter gc(m_projection);
-    gc.setChannelFlags(layer->channelFlags());
-    gc.setCompositeOp(layer->compositeOp());
-    gc.setOpacity(layer->opacity());
-    gc.bitBlt(needRect.topLeft(), device, needRect);
-
-    return true;
-}
-
-
diff --git a/krita/image/kis_merge_visitor.h b/krita/image/kis_merge_visitor.h
deleted file mode 100644
index 5052550..0000000
--- a/krita/image/kis_merge_visitor.h
+++ /dev/null
@@ -1,185 +0,0 @@
-/* This file is part of the KDE project
- * Copyright (C) Boudewijn Rempt <boud@valdyas.org>, (C) 2008
- *           (C) Dmitry Kazakov <dimula73@gmail.com>, 2009
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Library General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library 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
- * Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public License
- * along with this library; see the file COPYING.LIB.  If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
- */
-
-#include <QDebug>
-
-#include <KoCompositeOp.h>
-#include <KoUpdater.h>
-
-#include "kis_types.h"
-#include "kis_paint_device.h"
-#include "kis_node_visitor.h"
-#include "kis_painter.h"
-#include "kis_layer.h"
-#include "kis_group_layer.h"
-#include "kis_adjustment_layer.h"
-#include "generator/kis_generator_layer.h"
-#include "kis_external_layer_iface.h"
-#include "kis_paint_layer.h"
-#include "filter/kis_filter.h"
-#include "filter/kis_filter_configuration.h"
-#include "filter/kis_filter_registry.h"
-#include "kis_selection.h"
-#include "kis_transaction.h"
-#include "kis_iterators_pixel.h"
-#include "kis_clone_layer.h"
-#include "kis_processing_information.h"
-#include "kis_node.h"
-#include "kis_projection.h"
-#include "kis_node_progress_proxy.h"
-
-
-/**
- * The class merge visitor works using a bottom-up recomposition strategy. It does
- * not need to update the projection of the nodes, because this strategy updates
- * all projections before starting to merge.
- */
-class KisMergeVisitor : public KisNodeVisitor
-{
-
-public:
-
-    using KisNodeVisitor::visit;
-
-    KisMergeVisitor(KisPaintDeviceSP projection, const QRect& rc) :
-            KisNodeVisitor() {
-        Q_ASSERT(projection);
-        m_projection = projection;
-        m_rc = rc;
-    }
-
-public:
-
-    bool visit(KisExternalLayer *layer) {
-        return compositeWithProjection(layer, m_rc);
-    }
-
-    bool visit(KisGeneratorLayer * layer) {
-        /**
-         * FIXME: Pay attention to selection
-         * AAA: seems to be done, check it please
-         */
-        return compositeWithProjection(layer, m_rc);
-    }
-
-    bool visit(KisPaintLayer *layer) {
-        return compositeWithProjection(layer, m_rc);
-    }
-
-    bool visit(KisGroupLayer *layer) {
-        return compositeWithProjection(layer, m_rc);
-    }
-
-    bool visit(KisAdjustmentLayer* layer) {
-        Q_ASSERT(m_projection);
-        if (!layer->visible()) return true;
-
-        KisFilterConfiguration *filterConfig = layer->filter();
-        if (!filterConfig) return true;
-
-        KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name());
-        if (!filter) return false;
-
-        /**
-         * FIXME: A set of crutches, until bottom-up
-         * merging strategy is ready
-         */
-        QRect needRect = m_rc;
-        QRect changeRect = m_rc;
-        QRect applyRect = m_rc;
-
-        /**
-         * FIXME: make this like in KisLayer::applyMasks
-         */
-        KisPaintDeviceSP originalDevice = layer->original();
-        KisPaintDeviceSP tempDevice =
-            new KisPaintDevice(originalDevice->colorSpace());
-
-        /**
-         * Assume that needRect has already been prepared for
-         * us by bottom-up update strategy
-         */
-
-        /**
-         * FIXME: check whether it's right to leave a selection to
-         * a projection mechanism, not for the filter
-         */
-        KisConstProcessingInformation srcCfg(m_projection, applyRect.topLeft(), 0);
-        KisProcessingInformation dstCfg(tempDevice, applyRect.topLeft(), 0);
-
-        Q_ASSERT(layer->nodeProgressProxy());
-
-        KoProgressUpdater updater(layer->nodeProgressProxy());
-        updater.start(100, filter->name());
-        QPointer<KoUpdater> updaterPtr = updater.startSubtask();
-
-        KisTransaction* transaction =
-            new KisTransaction("", tempDevice);
-        filter->process(srcCfg, dstCfg, applyRect.size(),
-                        filterConfig, updaterPtr);
-        delete transaction;
-
-        updaterPtr->setProgress(100);
-
-        KisPainter gc(originalDevice);
-        gc.setCompositeOp(originalDevice->colorSpace()->compositeOp(COMPOSITE_COPY));
-        gc.bitBlt(changeRect.topLeft(), tempDevice, changeRect);
-        gc.end();
-
-        /**
-         * FIXME: Pay attention to this change rect too
-         */
-        changeRect = layer->updateProjection(changeRect);
-
-        /**
-         * FIXME: another crutch (changeRect)
-         */
-        return compositeWithProjection(layer, changeRect);
-    }
-
-
-    bool visit(KisCloneLayer * layer) {
-        return compositeWithProjection(layer, m_rc);
-    }
-
-    bool visit(KisNode*) {
-        return true;
-    }
-    bool visit(KisFilterMask*) {
-        return true;
-    }
-    bool visit(KisTransparencyMask*) {
-        return true;
-    }
-    bool visit(KisTransformationMask*) {
-        return true;
-    }
-    bool visit(KisSelectionMask*) {
-        return true;
-    }
-
-private:
-
-    bool compositeWithProjection(KisLayer *layer, const QRect &rect);
-
-    KisPaintDeviceSP m_projection;
-    QRect m_rc;
-};
-
diff --git a/krita/image/kis_merge_walker.cc b/krita/image/kis_merge_walker.cc
new file mode 100644
index 0000000..330afec
--- /dev/null
+++ b/krita/image/kis_merge_walker.cc
@@ -0,0 +1,61 @@
+/*
+ *  Copyright (c) 2009 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_merge_walker.h"
+
+
+KisMergeWalker::KisMergeWalker(QRect cropRect)
+    : KisBaseRectsWalker(cropRect)
+{
+}
+
+KisMergeWalker::~KisMergeWalker()
+{
+}
+
+void KisMergeWalker::startTrip(KisNodeSP startWith)
+{
+    visitHigherNode(startWith);
+
+    KisNodeSP prevNode = startWith->prevSibling();
+    if(prevNode)
+        visitLowerNode(prevNode);
+}
+
+void KisMergeWalker::visitHigherNode(KisNodeSP node)
+{
+    KisNodeSP nextNode = node->nextSibling();
+
+    registerChangeRect(node);
+
+    if (nextNode)
+        visitHigherNode(nextNode);
+    else if (node->parent())
+        startTrip(node->parent());
+
+    registerNeedRect(node, nextNode ? N_NORMAL : N_TOPMOST);
+}
+
+void KisMergeWalker::visitLowerNode(KisNodeSP node)
+{
+    KisNodeSP prevNode = node->prevSibling();
+    registerNeedRect(node, prevNode ? N_LOWER : N_BOTTOMMOST);
+
+    if (prevNode)
+        visitLowerNode(prevNode);
+}
diff --git a/krita/image/kis_merge_walker.h b/krita/image/kis_merge_walker.h
new file mode 100644
index 0000000..32dcc30
--- /dev/null
+++ b/krita/image/kis_merge_walker.h
@@ -0,0 +1,63 @@
+/*
+ *  Copyright (c) 2009 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_MERGE_WALKER_H
+#define __KIS_MERGE_WALKER_H
+
+#include "kis_node.h"
+#include "kis_types.h"
+#include "kis_base_rects_walker.h"
+
+class KRITAIMAGE_EXPORT KisMergeWalker : public KisBaseRectsWalker
+{
+
+public:
+    KisMergeWalker(QRect cropRect);
+    virtual ~KisMergeWalker();
+
+protected:
+
+    /**
+     * Begins visiting nodes starting with @startWith.
+     * First it climbs to the top of the graph, collecting
+     * changeRects (it calls @registerChangeRect for every node).
+     * Then it goes down to the bottom collecting needRects
+     * for every branch.
+     */
+    void startTrip(KisNodeSP startWith);
+
+private:
+    /**
+     * Visits a node @node and goes on crowling
+     * towards the top of the graph, caling visitHigherNode() or
+     * startTrip() one more time. After the top is reached
+     * returns back to the @node.
+     */
+    void visitHigherNode(KisNodeSP node);
+
+    /**
+     * Visits a node @node and goes on crowling
+     * towards the bottom of the graph, caling visitLowerNode() or
+     * startTrip() one more time.
+     */
+    void visitLowerNode(KisNodeSP node);
+};
+
+
+#endif /* __KIS_MERGE_WALKER_H */
+
diff --git a/krita/image/kis_node.cpp b/krita/image/kis_node.cpp
index fb39a50..86ccd2b 100644
--- a/krita/image/kis_node.cpp
+++ b/krita/image/kis_node.cpp
@@ -91,8 +91,9 @@ KisNode::~KisNode()
     delete m_d;
 }
 
-QRect KisNode::needRect(const QRect &rect) const
+QRect KisNode::needRect(const QRect &rect, PositionToFilthy pos) const
 {
+    Q_UNUSED(pos);
     return rect;
 }
 
diff --git a/krita/image/kis_node.h b/krita/image/kis_node.h
index 9ce20ba..84a642b 100644
--- a/krita/image/kis_node.h
+++ b/krita/image/kis_node.h
@@ -42,6 +42,13 @@ class KRITAIMAGE_EXPORT KisNode : public KisBaseNode
     Q_OBJECT
 
 public:
+    /**
+     * FIXME: Find better names
+     */
+    enum PositionToFilthy {
+        NORMAL,
+        BELOW_FILTHY
+    };
 
     /**
      * Create an empty node without a parent.
@@ -112,7 +119,7 @@ public:
      * compute the new value (for instance, convolution filters)
      * See \ref changeRect
      */
-    virtual QRect needRect(const QRect &rect) const;
+    virtual QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const;
 
 public: // Graph methods
 
diff --git a/krita/image/kis_paint_layer.cc b/krita/image/kis_paint_layer.cc
index dcac0be..0d9f518 100644
--- a/krita/image/kis_paint_layer.cc
+++ b/krita/image/kis_paint_layer.cc
@@ -104,13 +104,6 @@ KisPaintDeviceSP KisPaintLayer::paintDevice() const
     return m_d->paintDevice;
 }
 
-QRect KisPaintLayer::repaintOriginal(KisPaintDeviceSP original,
-                                     const QRect& rect)
-{
-    Q_UNUSED(original);
-    return rect;
-}
-
 bool KisPaintLayer::needProjection() const
 {
     return hasTemporaryTarget();
diff --git a/krita/image/kis_paint_layer.h b/krita/image/kis_paint_layer.h
index f0f263d..75f7daf 100644
--- a/krita/image/kis_paint_layer.h
+++ b/krita/image/kis_paint_layer.h
@@ -53,9 +53,6 @@ public:
 
     const KoColorSpace * colorSpace() const;
 
-    QRect repaintOriginal(KisPaintDeviceSP original,
-                          const QRect& rect);
-
     bool needProjection() const;
 
     void copyOriginalToProjection(const KisPaintDeviceSP original,
diff --git a/krita/image/kis_projection.cc b/krita/image/kis_projection.cc
index 05bf1a5..932d445 100644
--- a/krita/image/kis_projection.cc
+++ b/krita/image/kis_projection.cc
@@ -32,6 +32,10 @@
 #include "kis_image.h"
 #include "kis_group_layer.h"
 
+#include "kis_merge_walker.h"
+#include "kis_full_refresh_walker.h"
+#include "kis_async_merger.h"
+
 class KisProjection::Private
 {
 public:
@@ -71,7 +75,8 @@ void KisProjection::run()
     // The image updater is created in the run() method so it lives in the thread, otherwise
     // startUpdate will be executed in gui thread.
     m_d->updater = new KisImageUpdater();
-    connect(this, SIGNAL(sigUpdateProjection(KisNodeSP, QRect)), m_d->updater, SLOT(startUpdate(KisNodeSP, QRect)));
+    connect(this, SIGNAL(sigUpdateProjection(KisNodeSP, QRect, QRect)), m_d->updater, SLOT(startUpdate(KisNodeSP, QRect, QRect)));
+    connect(this, SIGNAL(sigFullRefresh(KisNodeSP, QRect)), m_d->updater, SLOT(startFullRefresh(KisNodeSP, QRect)));
     connect(m_d->updater, SIGNAL(updateDone(QRect)), m_d->image, SLOT(slotProjectionUpdated(QRect)));
     exec(); // start the event loop
 }
@@ -122,7 +127,7 @@ void KisProjection::updateProjection(KisNodeSP node, const QRect& rc)
     // at the bottom, we have as few and as long runs of pixels left
     // as possible.
     if (w <= m_d->updateRectSize && h <= m_d->updateRectSize) {
-        emit sigUpdateProjection(node, interestingRect);
+        emit sigUpdateProjection(node, interestingRect, m_d->image->bounds());
         return;
     }
     int wleft = w;
@@ -133,7 +138,7 @@ void KisProjection::updateProjection(KisNodeSP node, const QRect& rc)
         while (hleft > 0) {
             QRect rc2(col + x, row + y, qMin(wleft, m_d->updateRectSize), qMin(hleft, m_d->updateRectSize));
 
-            emit sigUpdateProjection(node, rc2);
+            emit sigUpdateProjection(node, rc2, m_d->image->bounds());
 
             hleft -= m_d->updateRectSize;
             row += m_d->updateRectSize;
@@ -144,6 +149,12 @@ void KisProjection::updateProjection(KisNodeSP node, const QRect& rc)
     }
 }
 
+void KisProjection::fullRefresh()
+{
+    // No splitting - trivial, but works :)
+    emit sigFullRefresh(m_d->image->rootLayer(), m_d->image->bounds());
+}
+
 void KisProjection::updateSettings()
 {
     KConfigGroup cfg = KGlobal::config()->group("");
@@ -151,38 +162,28 @@ void KisProjection::updateSettings()
     m_d->useRegionOfInterest = cfg.readEntry("use_region_of_interest", false);
 }
 
-void KisImageUpdater::startUpdate(KisNodeSP node, const QRect& rc)
-{
-    update(node, 0, rc);
-    emit updateDone(rc);
-}
-
-void KisImageUpdater::update(KisNodeSP node, KisNodeSP child, const QRect & rc)
+void KisImageUpdater::startFullRefresh(KisNodeSP node, const QRect& cropRect)
 {
-    QRect dirtyRect = rc;
+    KisFullRefreshWalker walker(cropRect);
+    KisAsyncMerger merger;
 
-    // group layers get special treatment so we can optimize if there
-    // are clean adjustmentlayers in the stack
-    KisGroupLayer* grouplayer = dynamic_cast<KisGroupLayer*>(node.data());
+    walker.collectRects(node, cropRect);
+    merger.startMerge(walker);
 
-    if (grouplayer && child) {
-        grouplayer->setDirtyNode(child);
-    }
+    QRect rect = walker.changeRect();
+    emit updateDone(rect);
+}
 
-    // update the projection of the layer: this may increase the dirty rect
-    if (KisLayer* layer = dynamic_cast<KisLayer*>(node.data())) {
-        dirtyRect |= layer->updateProjection(dirtyRect);
-    }
+void KisImageUpdater::startUpdate(KisNodeSP node, const QRect& rc, const QRect& cropRect)
+{
+    KisMergeWalker walker(cropRect);
+    KisAsyncMerger merger;
 
-    if (grouplayer) {
-        grouplayer->setDirtyNode(0);
-    }
+    walker.collectRects(node, rc);
+    merger.startMerge(walker);
 
-    // go up in the stack, recursively.
-    if (node->parent()) {
-        update(node->parent(), node, dirtyRect);
-    }
+    QRect rect = walker.changeRect();
+    emit updateDone(rect);
 }
 
-
 #include "kis_projection.moc"
diff --git a/krita/image/kis_projection.h b/krita/image/kis_projection.h
index 7537cff..2b6c368 100644
--- a/krita/image/kis_projection.h
+++ b/krita/image/kis_projection.h
@@ -56,13 +56,16 @@ public:
      * for KisImageUpdater to catch. KisImageUpdater belongs to this thread.
      */
     void updateProjection(KisNodeSP node, const QRect& rc);
+    void fullRefresh();
+
     void setRegionOfInterest(const QRect & roi);
     void updateSettings();
     void stop();
 
 signals:
 
-    void sigUpdateProjection(KisNodeSP node, const QRect& rc);
+    void sigUpdateProjection(KisNodeSP node, const QRect& rc, const QRect& cropRect);
+    void sigFullRefresh(KisNodeSP node, const QRect& rc);
 
 private:
 
@@ -84,7 +87,8 @@ class KisImageUpdater : public QObject
 
 public slots:
 
-    void startUpdate(KisNodeSP node, const QRect& rc);
+    void startUpdate(KisNodeSP node, const QRect& rc, const QRect& cropRect);
+    void startFullRefresh(KisNodeSP node, const QRect& rc);
 
 signals:
 
diff --git a/krita/image/kis_refresh_visitor.h b/krita/image/kis_refresh_visitor.h
deleted file mode 100644
index fedaa67..0000000
--- a/krita/image/kis_refresh_visitor.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- *  Copyright (c) 2009 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_REFRESH_VISITOR_H_
-#define KIS_REFRESH_VISITOR_H_
-
-#include "kis_node_visitor.h"
-#include "kis_image.h"
-
-/**
- * Update the projections of all nodes in the image.
- */
-class KisRefreshVisitor : public KisNodeVisitor
-{
-
-public:
-
-    using KisNodeVisitor::visit;
-
-    KisRefreshVisitor(KisImageWSP image)
-            : KisNodeVisitor() {
-        m_image = image;
-    }
-
-public:
-
-    bool visit(KisExternalLayer *layer) {
-        layer->updateProjection(layer->extent());
-        return true;
-    }
-
-    bool visit(KisGeneratorLayer * layer) {
-        layer->updateProjection(layer->extent());
-        return true;
-    }
-
-    bool visit(KisPaintLayer *layer) {
-        layer->updateProjection(layer->extent());
-        return true;
-    }
-
-    bool visit(KisAdjustmentLayer* layer) {
-        layer->updateProjection(layer->extent());
-        return true;
-    }
-
-    bool visit(KisCloneLayer * layer) {
-        layer->updateProjection(layer->extent());
-        return true;
-    }
-
-
-    bool visit(KisGroupLayer *layer) {
-        KisNodeSP child = layer->firstChild();
-        QRect unitedExtent;
-
-
-        while (child) {
-            child->accept(*this);
-            unitedExtent |= child->extent();
-
-            child = child->nextSibling();
-        }
-
-        layer->updateProjection(unitedExtent);
-
-        return true;
-    }
-
-    bool visit(KisNode*) {
-        return true;
-    }
-    bool visit(KisFilterMask*) {
-        return true;
-    }
-    bool visit(KisTransparencyMask*) {
-        return true;
-    }
-    bool visit(KisTransformationMask*) {
-        return true;
-    }
-    bool visit(KisSelectionMask*) {
-        return true;
-    }
-
-
-private:
-    KisImageWSP m_image;
-};
-
-
-#endif /* KIS_REFRESH_VISITOR_H_ */
-
diff --git a/krita/image/kis_selection_based_layer.cpp b/krita/image/kis_selection_based_layer.cpp
index ad2c5d3..c4a95a4 100644
--- a/krita/image/kis_selection_based_layer.cpp
+++ b/krita/image/kis_selection_based_layer.cpp
@@ -106,13 +106,6 @@ KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const
 }
 
 
-QRect KisSelectionBasedLayer::repaintOriginal(KisPaintDeviceSP original,
-        const QRect& rect)
-{
-    Q_UNUSED(original);
-    return rect;
-}
-
 bool KisSelectionBasedLayer::needProjection() const
 {
     return m_d->selection;
@@ -160,8 +153,9 @@ QRect KisSelectionBasedLayer::changeRect(const QRect &rect) const
            rect;
 }
 
-QRect KisSelectionBasedLayer::needRect(const QRect &rect) const
+QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const
 {
+    Q_UNUSED(pos);
     return rect;
 }
 
diff --git a/krita/image/kis_selection_based_layer.h b/krita/image/kis_selection_based_layer.h
index 2ffe742..ea2c504 100644
--- a/krita/image/kis_selection_based_layer.h
+++ b/krita/image/kis_selection_based_layer.h
@@ -64,9 +64,6 @@ public:
     KisPaintDeviceSP original() const;
     KisPaintDeviceSP paintDevice() const;
 
-    QRect repaintOriginal(KisPaintDeviceSP original,
-                          const QRect& rect);
-
     bool needProjection() const;
     void copyOriginalToProjection(const KisPaintDeviceSP original,
                                   KisPaintDeviceSP projection,
@@ -74,7 +71,7 @@ public:
 
     // From KisNode
     QRect changeRect(const QRect &rect) const;
-    QRect needRect(const QRect &rect) const;
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const;
 
     /**
      * resets cached projection of lower layer to a new device
diff --git a/krita/image/kis_transformation_mask.cpp b/krita/image/kis_transformation_mask.cpp
index 02facb7..2ab661c 100644
--- a/krita/image/kis_transformation_mask.cpp
+++ b/krita/image/kis_transformation_mask.cpp
@@ -82,8 +82,9 @@ QRect KisTransformationMask::changeRect(const QRect &rect) const
     return rect;
 }
 
-QRect KisTransformationMask::needRect(const QRect &rect) const
+QRect KisTransformationMask::needRect(const QRect &rect, PositionToFilthy pos) const
 {
+    Q_UNUSED(pos);
     //FIXME: selections?
     return rect;
 }
diff --git a/krita/image/kis_transformation_mask.h b/krita/image/kis_transformation_mask.h
index 32f945f..6924ad1 100644
--- a/krita/image/kis_transformation_mask.h
+++ b/krita/image/kis_transformation_mask.h
@@ -129,7 +129,7 @@ public:
     virtual void apply(KisPaintDeviceSP projection, const QRect & rc) const;
 
     QRect changeRect(const QRect &rect) const;
-    QRect needRect(const QRect &rect) const;
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const;
 
     bool accept(KisNodeVisitor &v);
 private:
diff --git a/krita/image/kis_transparency_mask.cc b/krita/image/kis_transparency_mask.cc
index f5aa2e6..2312d56 100644
--- a/krita/image/kis_transparency_mask.cc
+++ b/krita/image/kis_transparency_mask.cc
@@ -72,8 +72,9 @@ QRect KisTransparencyMask::changeRect(const QRect &rect) const
     return rect;
 }
 
-QRect KisTransparencyMask::needRect(const QRect &rect) const
+QRect KisTransparencyMask::needRect(const QRect &rect, PositionToFilthy pos) const
 {
+    Q_UNUSED(pos);
     /**
      * Selection on transparency masks have special meaning:
      * They do not crop change area, they crop need area.
diff --git a/krita/image/kis_transparency_mask.h b/krita/image/kis_transparency_mask.h
index df0ca93..66bf702 100644
--- a/krita/image/kis_transparency_mask.h
+++ b/krita/image/kis_transparency_mask.h
@@ -55,7 +55,7 @@ public:
     bool accept(KisNodeVisitor &v);
 
     QRect changeRect(const QRect &rect) const;
-    QRect needRect(const QRect &rect) const;
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const;
 };
 
 #endif //_KIS_TRANSPARENCY_MASK_
diff --git a/krita/image/tests/CMakeLists.txt b/krita/image/tests/CMakeLists.txt
index 85684c3..a411a95 100644
--- a/krita/image/tests/CMakeLists.txt
+++ b/krita/image/tests/CMakeLists.txt
@@ -304,6 +304,18 @@ target_link_libraries(KisLayerCommandsTest  ${KDE4_KDEUI_LIBS} kritaimage ${QT_Q
 
 ########### next target ###############
 
+set(kis_walkers_test_SRCS kis_walkers_test.cpp )
+kde4_add_unit_test(KisWalkersTest TESTNAME krita-image-KisWalkersTest ${kis_walkers_test_SRCS})
+target_link_libraries(KisWalkersTest  ${KDE4_KDEUI_LIBS} kritaimage ${QT_QTTEST_LIBRARY})
+
+########### next target ###############
+
+set(kis_async_merger_test_SRCS kis_async_merger_test.cpp )
+kde4_add_unit_test(KisAsyncMergerTest TESTNAME krita-image-KisAsyncMergerTest ${kis_async_merger_test_SRCS})
+target_link_libraries(KisAsyncMergerTest  ${KDE4_KDEUI_LIBS} kritaimage ${QT_QTTEST_LIBRARY})
+
+########### next target ###############
+
 set(kis_macro_test_SRCS kis_macro_test.cpp )
 kde4_add_unit_test(KisMacroTest TESTNAME krita-image-KisMacroTest ${kis_macro_test_SRCS})
 target_link_libraries(KisMacroTest  ${KDE4_KDEUI_LIBS} kritaimage ${QT_QTTEST_LIBRARY})
diff --git a/krita/image/tests/data/merged_hakonepa.png b/krita/image/tests/data/merged_hakonepa.png
new file mode 100644
index 0000000..8c4aec6
Binary files /dev/null and b/krita/image/tests/data/merged_hakonepa.png differ
diff --git a/krita/image/tests/kis_async_merger_test.cpp b/krita/image/tests/kis_async_merger_test.cpp
new file mode 100644
index 0000000..2a2973d
--- /dev/null
+++ b/krita/image/tests/kis_async_merger_test.cpp
@@ -0,0 +1,168 @@
+/*
+ *  Copyright (c) 2009 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_async_merger_test.h"
+
+#include "kis_merge_walker.h"
+#include "kis_async_merger.h"
+
+#include <qtest_kde.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoColorSpace.h>
+#include "kis_image.h"
+#include "kis_paint_layer.h"
+#include "kis_group_layer.h"
+#include "kis_adjustment_layer.h"
+#include "kis_selection.h"
+
+#include "filter/kis_filter.h"
+#include "filter/kis_filter_configuration.h"
+#include "filter/kis_filter_registry.h"
+
+
+    /*
+      +-----------+
+      |root       |
+      | group     |
+      |  blur 1   |
+      |  paint 2  |
+      | paint 1   |
+      +-----------+
+     */
+
+void KisAsyncMergerTest::testMerger()
+{
+    const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    KisImageWSP image = new KisImage(0, 640, 441, colorSpace, "merger test");
+
+    QImage sourceImage1(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
+    QImage sourceImage2(QString(FILES_DATA_DIR) + QDir::separator() + "inverted_hakonepa.png");
+    QImage referenceProjection(QString(FILES_DATA_DIR) + QDir::separator() + "merged_hakonepa.png");
+
+    KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace);
+    KisPaintDeviceSP device2 = new KisPaintDevice(colorSpace);
+    device1->convertFromQImage(sourceImage1, "", 0, 0);
+    device2->convertFromQImage(sourceImage2, "", 0, 0);
+
+    KisFilterSP filter = KisFilterRegistry::instance()->value("blur");
+    Q_ASSERT(filter);
+    KisFilterConfiguration *configuration = filter->defaultConfiguration(0);
+    Q_ASSERT(configuration);
+
+    KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE, device1);
+    KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE, device2);
+    KisLayerSP groupLayer = new KisGroupLayer(image, "group", 200/*OPACITY_OPAQUE*/);
+    KisLayerSP blur1 = new KisAdjustmentLayer(image, "blur1", configuration, 0);
+
+    image->addNode(paintLayer1, image->rootLayer());
+    image->addNode(groupLayer, image->rootLayer());
+
+    image->addNode(paintLayer2, groupLayer);
+    image->addNode(blur1, groupLayer);
+
+    QRect testRect1(0,0,100,441);
+    QRect testRect2(100,0,400,441);
+    QRect testRect3(500,0,140,441);
+    QRect testRect4(580,381,40,40);
+
+    QRect cropRect(image->bounds());
+
+    KisMergeWalker walker(cropRect);
+    KisAsyncMerger merger;
+
+    walker.collectRects(paintLayer2, testRect1);
+    merger.startMerge(walker);
+
+    walker.collectRects(paintLayer2, testRect2);
+    merger.startMerge(walker);
+
+    walker.collectRects(paintLayer2, testRect3);
+    merger.startMerge(walker);
+
+    walker.collectRects(paintLayer2, testRect4);
+    merger.startMerge(walker);
+
+    // Old style merging: has artefacts at x=100 and x=500
+    // And should be turned on inside KisLayer
+/*    paintLayer2->setDirty(testRect1);
+    QTest::qSleep(3000);
+    paintLayer2->setDirty(testRect2);
+    QTest::qSleep(3000);
+    paintLayer2->setDirty(testRect3);
+    QTest::qSleep(3000);
+    paintLayer2->setDirty(testRect4);
+    QTest::qSleep(3000);
+*/
+
+    KisLayerSP rootLayer = image->rootLayer();
+    QVERIFY(rootLayer->exactBounds() == image->bounds());
+
+    QImage resultProjection = rootLayer->projection()->convertToQImage(0);
+    resultProjection.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + "actual_merge_result.png");
+    QVERIFY(resultProjection == referenceProjection);
+}
+
+
+/**
+ * This in not fully automated test for child obliging in KisAsyncMerger.
+ * It just checks whether devices are shared. To check if the merger
+ * touches originals you can add a debug message to the merger
+ * and take a look.
+ */
+
+    /*
+      +-----------+
+      |root       |
+      | group     |
+      |  paint 1  |
+      +-----------+
+     */
+
+void KisAsyncMergerTest::debugObligeChild()
+{
+    const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    KisImageWSP image = new KisImage(0, 640, 441, colorSpace, "merger test");
+
+    QImage sourceImage1(QString(FILES_DATA_DIR) + QDir::separator() + "hakonepa.png");
+    KisPaintDeviceSP device1 = new KisPaintDevice(colorSpace);
+    device1->convertFromQImage(sourceImage1, "", 0, 0);
+
+    KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE, device1);
+    KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE);
+
+    image->addNode(groupLayer, image->rootLayer());
+    image->addNode(paintLayer1, groupLayer);
+
+    QRect testRect1(0,0,640,441);
+    QRect cropRect(image->bounds());
+
+    KisMergeWalker walker(cropRect);
+    KisAsyncMerger merger;
+
+    walker.collectRects(paintLayer1, testRect1);
+    merger.startMerge(walker);
+
+    KisLayerSP rootLayer = image->rootLayer();
+    QVERIFY(rootLayer->original() == groupLayer->projection());
+    QVERIFY(groupLayer->original() == paintLayer1->projection());
+}
+
+
+QTEST_KDEMAIN(KisAsyncMergerTest, NoGUI)
+#include "kis_async_merger_test.moc"
+
diff --git a/krita/image/tests/kis_async_merger_test.h b/krita/image/tests/kis_async_merger_test.h
new file mode 100644
index 0000000..bdf2b8b
--- /dev/null
+++ b/krita/image/tests/kis_async_merger_test.h
@@ -0,0 +1,35 @@
+/*
+ *  Copyright (c) 2009 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_ASYNC_MERGER_TEST_H
+#define KIS_ASYNC_MERGER_TEST_H
+
+#include <QtTest/QtTest>
+
+class KisAsyncMergerTest : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void testMerger();
+    void debugObligeChild();
+private:
+};
+
+#endif /* KIS_ASYNC_MERGER_TEST_H */
+
diff --git a/krita/image/tests/kis_layer_test.h b/krita/image/tests/kis_layer_test.h
index b2e4fde..d170043 100644
--- a/krita/image/tests/kis_layer_test.h
+++ b/krita/image/tests/kis_layer_test.h
@@ -47,11 +47,6 @@ public:
         return "TEST";
     }
 
-    QRect repaintOriginal(KisPaintDeviceSP original, const QRect& rect) {
-        Q_UNUSED(original);
-        return rect;
-    }
-
     KisPaintDeviceSP original() const {
         // This test doesn't use updateProjection so just return 0
         return 0;
diff --git a/krita/image/tests/kis_walkers_test.cpp b/krita/image/tests/kis_walkers_test.cpp
new file mode 100644
index 0000000..7c36b98
--- /dev/null
+++ b/krita/image/tests/kis_walkers_test.cpp
@@ -0,0 +1,421 @@
+/*
+ *  Copyright (c) 2009 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_walkers_test.h"
+
+#include "kis_base_rects_walker.h"
+#include "kis_merge_walker.h"
+#include "kis_full_refresh_walker.h"
+
+#include <qtest_kde.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoColorSpace.h>
+#include "kis_image.h"
+#include "kis_paint_layer.h"
+#include "kis_group_layer.h"
+#include "kis_adjustment_layer.h"
+#include "kis_selection.h"
+
+#define DEBUG_VISITORS
+
+QString nodeTypeString(KisMergeWalker::NodePosition position);
+QString nodeTypePostfix(KisMergeWalker::NodePosition position);
+
+/************** Test Implementation Of A Walker *********************/
+class KisTestWalker : public KisMergeWalker
+{
+public:
+    KisTestWalker()
+        :KisMergeWalker(QRect())
+    {
+    }
+
+    QStringList popResult() {
+        QStringList order(m_order);
+        m_order.clear();
+        return order;
+    }
+
+    using KisMergeWalker::startTrip;
+
+protected:
+
+    void registerChangeRect(KisNodeSP node) {
+#ifdef DEBUG_VISITORS
+        qDebug()<< "FW:"<< node->name();
+#endif
+        m_order.append(node->name());
+    }
+
+    void registerNeedRect(KisNodeSP node, NodePosition position) {
+#ifdef DEBUG_VISITORS
+        qDebug()<< "BW:"<< node->name() <<'\t'<< nodeTypeString(position);
+#endif
+        m_order.append(node->name() + nodeTypePostfix(position));
+    }
+
+protected:
+    QStringList m_order;
+};
+
+/************** Debug And Verify Code *******************************/
+void reportStartWith(QString nodeName)
+{
+    qDebug();
+    qDebug() << "Start with:" << nodeName;
+}
+
+QString nodeTypeString(KisMergeWalker::NodePosition position)
+{
+    static QString pos("  normal,  lower,top,bottom");
+    static QStringList positionName  = pos.split(",");
+
+    return positionName[position];
+}
+
+QString nodeTypePostfix(KisMergeWalker::NodePosition position)
+{
+    static QString index("_N,_L,_T,_B");
+    static QStringList indexName  = index.split(",");
+
+    return indexName[position];
+}
+
+void KisWalkersTest::verifyResult(KisBaseRectsWalker &walker, QStringList reference,
+                                  QRect accessRect, bool changeRectVaries,
+                                  bool needRectVaries)
+{
+    KisMergeWalker::NodeStack &list = walker.nodeStack();
+    QStringList::const_iterator iter = reference.constBegin();
+
+    foreach(KisMergeWalker::JobItem item, list) {
+#ifdef DEBUG_VISITORS
+        qDebug() << item.m_node->name() << '\t'
+                 << item.m_applyRect << '\t'
+                 << nodeTypeString(item.m_position);
+#endif
+
+        QVERIFY(item.m_node->name() == *iter);
+
+        iter++;
+    }
+
+#ifdef DEBUG_VISITORS
+    qDebug() << "Result AR:\t" << walker.accessRect();
+#endif
+
+    QVERIFY(walker.accessRect() == accessRect);
+    QVERIFY(walker.changeRectVaries() == changeRectVaries);
+    QVERIFY(walker.needRectVaries() == needRectVaries);
+}
+
+
+/************** Actual Testing **************************************/
+
+    /*
+      +----------+
+      |root      |
+      | layer 5  |
+      | group    |
+      |  paint 4 |
+      |  paint 3 |
+      |  adj     |
+      |  paint 2 |
+      | paint 1  |
+      +----------+
+     */
+
+void KisWalkersTest::testUsualVisiting()
+{
+    const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    KisImageWSP image = new KisImage(0, 512, 512, colorSpace, "walker test");
+
+    KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE);
+    KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE);
+    KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE);
+    KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE);
+    KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE);
+
+    KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE);
+    KisLayerSP adjustmentLayer = new KisAdjustmentLayer(image, "adj", 0, 0);
+
+
+    image->addNode(paintLayer1, image->rootLayer());
+    image->addNode(groupLayer, image->rootLayer());
+    image->addNode(paintLayer5, image->rootLayer());
+
+    image->addNode(paintLayer2, groupLayer);
+    image->addNode(adjustmentLayer, groupLayer);
+    image->addNode(paintLayer3, groupLayer);
+    image->addNode(paintLayer4, groupLayer);
+
+    KisTestWalker walker;
+
+    {
+        QString order("paint3,paint4,group,paint5,root,"
+                      "root_T,paint5_T,group_N,paint1_B,"
+                      "paint4_T,paint3_N,adj_L,paint2_B");
+        QStringList orderList = order.split(",");
+
+        reportStartWith("paint3");
+        walker.startTrip(paintLayer3);
+        QVERIFY(walker.popResult() == orderList);
+    }
+
+    {
+        QString order("adj,paint3,paint4,group,paint5,root,"
+                      "root_T,paint5_T,group_N,paint1_B,"
+                      "paint4_T,paint3_N,adj_N,paint2_B");
+        QStringList orderList = order.split(",");
+
+        reportStartWith("adj");
+        walker.startTrip(adjustmentLayer);
+        QVERIFY(walker.popResult() == orderList);
+    }
+
+    {
+        QString order("group,paint5,root,"
+                      "root_T,paint5_T,group_N,paint1_B");
+        QStringList orderList = order.split(",");
+
+        reportStartWith("group");
+        walker.startTrip(groupLayer);
+        QVERIFY(walker.popResult() == orderList);
+    }
+}
+
+    /*
+      +----------+
+      |root      |
+      | layer 5  |
+      | cplx  2  |
+      | group    |
+      |  paint 4 |
+      |  paint 3 |
+      |  cplx  1 |
+      |  paint 2 |
+      | paint 1  |
+      +----------+
+     */
+
+void KisWalkersTest::testMergeVisiting()
+{
+    const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    KisImageWSP image = new KisImage(0, 512, 512, colorSpace, "walker test");
+
+    KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE);
+    KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE);
+    KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE);
+    KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE);
+    KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE);
+
+    KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE);
+    KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE);
+    KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE);
+
+    image->addNode(paintLayer1, image->rootLayer());
+    image->addNode(groupLayer, image->rootLayer());
+    image->addNode(complexRectsLayer2, image->rootLayer());
+    image->addNode(paintLayer5, image->rootLayer());
+
+    image->addNode(paintLayer2, groupLayer);
+    image->addNode(complexRectsLayer1, groupLayer);
+    image->addNode(paintLayer3, groupLayer);
+    image->addNode(paintLayer4, groupLayer);
+
+    QRect testRect(10,10,10,10);
+    // Empty rect to show we don't need any cropping
+    QRect cropRect;
+
+    KisMergeWalker walker(cropRect);
+
+    {
+        QString order("root,paint5,cplx2,group,paint1,"
+                      "paint4,paint3,cplx1,paint2");
+        QStringList orderList = order.split(",");
+        QRect accessRect(-7,-7,44,44);
+
+        reportStartWith("paint3");
+        walker.collectRects(paintLayer3, testRect);
+        verifyResult(walker, orderList, accessRect, true, true);
+    }
+
+    {
+        QString order("root,paint5,cplx2,group,paint1,"
+                      "paint4,paint3,cplx1,paint2");
+        QStringList orderList = order.split(",");
+        QRect accessRect(-10,-10,50,50);
+
+        reportStartWith("paint2");
+        walker.collectRects(paintLayer2, testRect);
+        verifyResult(walker, orderList, accessRect, true, true);
+    }
+
+    {
+        QString order("root,paint5,cplx2,group,paint1");
+        QStringList orderList = order.split(",");
+        QRect accessRect(3,3,24,24);
+
+        reportStartWith("paint5");
+        walker.collectRects(paintLayer5, testRect);
+        verifyResult(walker, orderList, accessRect, false, true);
+    }
+
+    {
+        /**
+         * Test cropping
+         */
+        QString order("root,paint5,cplx2,group,paint1,"
+                      "paint4,paint3,cplx1,paint2");
+        QStringList orderList = order.split(",");
+        QRect accessRect(0,0,40,40);
+
+        reportStartWith("paint2 (with cropping)");
+        walker.setCropRect(image->bounds());
+        walker.collectRects(paintLayer2, testRect);
+        walker.setCropRect(cropRect);
+        verifyResult(walker, orderList, accessRect, true, true);
+    }
+
+}
+
+    /*
+      +----------+
+      |root      |
+      | layer 5  |
+      | cplx  2  |
+      | group    |
+      |  paint 4 |
+      |  paint 3 |
+      |  cplx  1 |
+      |  paint 2 |
+      | paint 1  |
+      +----------+
+     */
+
+void KisWalkersTest::testFullRefreshVisiting()
+{
+    const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    KisImageWSP image = new KisImage(0, 512, 512, colorSpace, "walker test");
+
+    KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE);
+    KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE);
+    KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE);
+    KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE);
+    KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE);
+
+    KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE);
+    KisLayerSP complexRectsLayer1 = new ComplexRectsLayer(image, "cplx1", OPACITY_OPAQUE);
+    KisLayerSP complexRectsLayer2 = new ComplexRectsLayer(image, "cplx2", OPACITY_OPAQUE);
+
+    image->addNode(paintLayer1, image->rootLayer());
+    image->addNode(groupLayer, image->rootLayer());
+    image->addNode(complexRectsLayer2, image->rootLayer());
+    image->addNode(paintLayer5, image->rootLayer());
+
+    image->addNode(paintLayer2, groupLayer);
+    image->addNode(complexRectsLayer1, groupLayer);
+    image->addNode(paintLayer3, groupLayer);
+    image->addNode(paintLayer4, groupLayer);
+
+    QRect testRect(10,10,10,10);
+    // Empty rect to show we don't need any cropping
+    QRect cropRect;
+
+    KisFullRefreshWalker walker(cropRect);
+
+    {
+        QString order("root,paint5,cplx2,group,paint1,"
+                      "paint4,paint3,cplx1,paint2");
+        QStringList orderList = order.split(",");
+        QRect accessRect(-4,-4,38,38);
+
+        reportStartWith("root");
+        walker.collectRects(image->rootLayer(), testRect);
+        verifyResult(walker, orderList, accessRect, false, true);
+    }
+}
+
+    /*
+      +----------+
+      |root      |
+      | layer 5  |
+      | cache1   |
+      | group    |
+      |  paint 4 |
+      |  paint 3 |
+      |  paint 2 |
+      | paint 1  |
+      +----------+
+     */
+
+void KisWalkersTest::testCachedVisiting()
+{
+    const KoColorSpace * colorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    KisImageWSP image = new KisImage(0, 512, 512, colorSpace, "walker test");
+
+    KisLayerSP paintLayer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE);
+    KisLayerSP paintLayer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE);
+    KisLayerSP paintLayer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE);
+    KisLayerSP paintLayer4 = new KisPaintLayer(image, "paint4", OPACITY_OPAQUE);
+    KisLayerSP paintLayer5 = new KisPaintLayer(image, "paint5", OPACITY_OPAQUE);
+
+    KisLayerSP groupLayer = new KisGroupLayer(image, "group", OPACITY_OPAQUE);
+    KisLayerSP cacheLayer1 = new CacheLayer(image, "cache1", OPACITY_OPAQUE);
+
+    image->addNode(paintLayer1, image->rootLayer());
+    image->addNode(groupLayer, image->rootLayer());
+    image->addNode(cacheLayer1, image->rootLayer());
+    image->addNode(paintLayer5, image->rootLayer());
+
+    image->addNode(paintLayer2, groupLayer);
+    image->addNode(paintLayer3, groupLayer);
+    image->addNode(paintLayer4, groupLayer);
+
+    QRect testRect(10,10,10,10);
+    // Empty rect to show we don't need any cropping
+    QRect cropRect;
+
+    KisMergeWalker walker(cropRect);
+
+    {
+        QString order("root,paint5,cache1,group,paint1,"
+                      "paint4,paint3,paint2");
+        QStringList orderList = order.split(",");
+        QRect accessRect(0,0,30,30);
+
+        reportStartWith("paint3");
+        walker.collectRects(paintLayer3, testRect);
+        verifyResult(walker, orderList, accessRect, true, true);
+    }
+
+    {
+        QString order("root,paint5,cache1");
+        QStringList orderList = order.split(",");
+        QRect accessRect(10,10,10,10);
+
+        reportStartWith("paint5");
+        walker.collectRects(paintLayer5, testRect);
+        verifyResult(walker, orderList, accessRect, false, true);
+    }
+
+}
+
+QTEST_KDEMAIN(KisWalkersTest, NoGUI)
+#include "kis_walkers_test.moc"
+
diff --git a/krita/image/tests/kis_walkers_test.h b/krita/image/tests/kis_walkers_test.h
new file mode 100644
index 0000000..abf707d
--- /dev/null
+++ b/krita/image/tests/kis_walkers_test.h
@@ -0,0 +1,214 @@
+/*
+ *  Copyright (c) 2009 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_WALKERS_TEST_H
+#define KIS_WALKERS_TEST_H
+
+#include <QtTest/QtTest>
+
+#include "kis_layer.h"
+#include "kis_types.h"
+#include "kis_node_visitor.h"
+#include "kis_paint_device.h"
+
+class TestLayer : public KisLayer
+{
+
+    Q_OBJECT
+
+public:
+
+    TestLayer(KisImageWSP image, const QString & name, quint8 opacity)
+            : KisLayer(image, name, opacity) {
+    }
+
+    KisNodeSP clone() {
+        return new TestLayer(*this);
+    }
+    bool allowAsChild(KisNodeSP) const {
+        return true;
+    }
+
+    virtual QString nodeType() {
+        return "TEST";
+    }
+
+    KisPaintDeviceSP original() const {
+        // This test doesn't use updateProjection so just return 0
+        return 0;
+    }
+
+    KisPaintDeviceSP paintDevice() const {
+        return 0;
+    }
+
+    QIcon icon() const {
+        return QIcon();
+    }
+
+    KisNodeSP clone() const {
+        return new TestLayer(image(), name(), opacity());
+    }
+
+    qint32 x() const {
+        return 0;
+    }
+
+    void setX(qint32) {
+    }
+
+    qint32 y() const {
+        return 0;
+    }
+
+    void setY(qint32) {
+    }
+
+    QRect extent() const {
+        return QRect();
+    }
+
+    QRect exactBounds() const {
+        return QRect();
+    }
+
+    bool accept(KisNodeVisitor& v) {
+        return v.visit(this);
+    }
+};
+
+class ComplexRectsLayer : public KisLayer
+{
+
+    Q_OBJECT
+
+public:
+
+    ComplexRectsLayer(KisImageWSP image, const QString & name, quint8 opacity)
+            : KisLayer(image, name, opacity) {
+    }
+
+    KisNodeSP clone() {
+        return new ComplexRectsLayer(*this);
+    }
+    bool allowAsChild(KisNodeSP) const {
+        return true;
+    }
+
+    virtual QString nodeType() {
+        return "TEST";
+    }
+
+    KisPaintDeviceSP original() const {
+        // This test doesn't use updateProjection so just return 0
+        return 0;
+    }
+
+    KisPaintDeviceSP paintDevice() const {
+        return 0;
+    }
+
+    QIcon icon() const {
+        return QIcon();
+    }
+
+    KisNodeSP clone() const {
+        return new ComplexRectsLayer(image(), name(), opacity());
+    }
+
+    qint32 x() const {
+        return 0;
+    }
+
+    void setX(qint32) {
+    }
+
+    qint32 y() const {
+        return 0;
+    }
+
+    void setY(qint32) {
+    }
+
+    QRect extent() const {
+        return QRect();
+    }
+
+    QRect exactBounds() const {
+        return QRect();
+    }
+
+    bool accept(KisNodeVisitor& v) {
+        return v.visit(this);
+    }
+
+    QRect changeRect(const QRect &rect) const {
+        const qint32 delta = 3;
+        return rect.adjusted(-delta, -delta, delta, delta);
+    }
+
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const {
+        Q_UNUSED(pos);
+        const qint32 delta = 7;
+        return rect.adjusted(-delta, -delta, delta, delta);
+    }
+
+};
+
+class CacheLayer : public ComplexRectsLayer
+{
+
+    Q_OBJECT
+
+public:
+    CacheLayer(KisImageWSP image, const QString & name, quint8 opacity)
+            : ComplexRectsLayer(image, name, opacity) {
+    }
+
+
+    QRect needRect(const QRect &rect, PositionToFilthy pos = NORMAL) const {
+        QRect retval;
+
+        if(pos == KisNode::NORMAL) {
+            const qint32 delta = 7;
+            retval = rect.adjusted(-delta, -delta, delta, delta);
+        }
+        return retval;
+    }
+};
+
+class KisBaseRectsWalker;
+
+class KisWalkersTest : public QObject
+{
+    Q_OBJECT
+
+private slots:
+
+    void testUsualVisiting();
+    void testMergeVisiting();
+    void testFullRefreshVisiting();
+    void testCachedVisiting();
+private:
+    void verifyResult(KisBaseRectsWalker &walker, QStringList reference,
+                      QRect accessRect, bool changeRectVaries,
+                      bool needRectVaries);
+};
+
+#endif
+
