kis_async_merger.cpp 11.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/* 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.
 */

#include "kis_async_merger.h"


22
#include <kis_debug.h>
23 24
#include <QBitArray>

25
#include <KoChannelInfo.h>
26
#include <KoCompositeOpRegistry.h>
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

#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_clone_layer.h"
#include "kis_processing_information.h"
43
#include "kis_busy_progress_indicator.h"
44 45 46


#include "kis_merge_walker.h"
Dmitry Kazakov's avatar
Dmitry Kazakov committed
47
#include "kis_refresh_subtree_walker.h"
48

49 50
#include "kis_abstract_projection_plane.h"

51 52 53 54

//#define DEBUG_MERGER

#ifdef DEBUG_MERGER
55
#define DEBUG_NODE_ACTION(message, type, leaf, rect)            \
56
    dbgKrita << message << type << ":" << leaf->node()->name() << rect
57
#else
58
#define DEBUG_NODE_ACTION(message, type, leaf, rect)
59 60 61 62 63 64
#endif


class KisUpdateOriginalVisitor : public KisNodeVisitor
{
public:
Dmitry Kazakov's avatar
Dmitry Kazakov committed
65 66 67 68
    KisUpdateOriginalVisitor(QRect updateRect, KisPaintDeviceSP projection, QRect cropRect)
        : m_updateRect(updateRect),
          m_cropRect(cropRect),
          m_projection(projection)
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
        {
        }

    ~KisUpdateOriginalVisitor() {
    }

public:
    using KisNodeVisitor::visit;

    bool visit(KisAdjustmentLayer* layer) {
        if (!layer->visible()) return true;

        if (!m_projection) {
            warnImage << "ObligeChild mechanism has been activated for "
                "an adjustment layer! Do nothing...";
            layer->original()->clear();
            return true;
        }

        KisPaintDeviceSP originalDevice = layer->original();
        originalDevice->clear(m_updateRect);

91
        const QRect applyRect = m_updateRect & m_projection->extent();
92 93 94 95 96

        // If the intersection of the updaterect and the projection extent is
        //      null, we are finish here.
        if(applyRect.isNull()) return true;

97 98 99 100 101 102 103
        KisSafeFilterConfigurationSP filterConfig = layer->filter();
        if (!filterConfig) {
            /**
             * When an adjustment layer is just created, it may have no
             * filter inside. Then the layer has work as a pass-through
             * node. Just copy the merged data to the layer's original.
             */
104
            KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect);
105 106 107
            return true;
        }

108 109 110
        KisSelectionSP selection = layer->fetchComposedInternalSelection(applyRect);
        const QRect filterRect = selection ? applyRect & selection->selectedRect() : applyRect;

111 112 113
        KisFilterSP filter = KisFilterRegistry::instance()->value(filterConfig->name());
        if (!filter) return false;

114 115 116 117 118 119 120
        KisPaintDeviceSP dstDevice = originalDevice;

        if (selection) {
            dstDevice = new KisPaintDevice(originalDevice->colorSpace());
        }

        if (!filterRect.isEmpty()) {
121 122 123
            KIS_ASSERT_RECOVER_NOOP(layer->busyProgressIndicator());
            layer->busyProgressIndicator()->update();

124
            // We do not create a transaction here, as srcDevice != dstDevice
125
            filter->process(m_projection, dstDevice, 0, filterRect, filterConfig.data(), 0);
126 127 128 129 130 131
        }

        if (selection) {
            KisPainter::copyAreaOptimized(applyRect.topLeft(), m_projection, originalDevice, applyRect);
            KisPainter::copyAreaOptimized(filterRect.topLeft(), dstDevice, originalDevice, filterRect, selection);
        }
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151

        return true;
    }

    bool visit(KisExternalLayer*) {
        return true;
    }

    bool visit(KisGeneratorLayer*) {
        return true;
    }

    bool visit(KisPaintLayer*) {
        return true;
    }

    bool visit(KisGroupLayer*) {
        return true;
    }

Dmitry Kazakov's avatar
Dmitry Kazakov committed
152 153 154 155 156 157 158 159 160 161 162
    bool visit(KisCloneLayer *layer) {
        QRect emptyRect;
        KisRefreshSubtreeWalker walker(emptyRect);
        KisAsyncMerger merger;

        KisLayerSP srcLayer = layer->copyFrom();
        QRect srcRect = m_updateRect.translated(-layer->x(), -layer->y());

        QRegion prepareRegion(srcRect);
        prepareRegion -= m_cropRect;

163 164 165 166 167 168 169 170 171 172

        /**
         * If a clone has complicated masks, we should prepare additional
         * source area to ensure the rect is prepared.
         */
        QRect needRectOnSource = layer->needRectOnSourceForMasks(srcRect);
        if (!needRectOnSource.isEmpty()) {
            prepareRegion += needRectOnSource;
        }

Dmitry Kazakov's avatar
Dmitry Kazakov committed
173 174 175 176 177
        foreach(const QRect &rect, prepareRegion.rects()) {
            walker.collectRects(srcLayer, rect);
            merger.startMerge(walker, false);
        }

178 179 180 181 182 183 184 185 186
        return true;
    }

    bool visit(KisNode*) {
        return true;
    }
    bool visit(KisFilterMask*) {
        return true;
    }
187 188 189
    bool visit(KisTransformMask*) {
        return true;
    }
190 191 192 193 194 195 196 197 198
    bool visit(KisTransparencyMask*) {
        return true;
    }
    bool visit(KisSelectionMask*) {
        return true;
    }

private:
    QRect m_updateRect;
Dmitry Kazakov's avatar
Dmitry Kazakov committed
199
    QRect m_cropRect;
200 201 202 203 204 205 206 207
    KisPaintDeviceSP m_projection;
};


/*********************************************************************/
/*                     KisAsyncMerger                                */
/*********************************************************************/

Dmitry Kazakov's avatar
Dmitry Kazakov committed
208
void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
209
    KisMergeWalker::LeafStack &leafStack = walker.leafStack();
210 211 212

    const bool useTempProjections = walker.needRectVaries();

213 214 215 216 217
    while(!leafStack.isEmpty()) {
        KisMergeWalker::JobItem item = leafStack.pop();
        KisProjectionLeafSP currentLeaf = item.m_leaf;

        if(currentLeaf->isRoot()) continue;
218 219

        // All the masks should be filtered by the walkers
220
        Q_ASSERT(currentLeaf->isLayer());
221 222 223 224 225 226

        QRect applyRect = item.m_applyRect;

        if(item.m_position & KisMergeWalker::N_EXTRA) {
            // The type of layers that will not go to projection.

227
            DEBUG_NODE_ACTION("Updating", "N_EXTRA", currentLeaf, applyRect);
228
            KisUpdateOriginalVisitor originalVisitor(applyRect,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
229 230
                                                     m_currentProjection,
                                                     walker.cropRect());
231 232
            currentLeaf->accept(originalVisitor);
            currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node());
233 234 235 236 237 238

            continue;
        }


        if(!m_currentProjection)
239
            setupProjection(currentLeaf, applyRect, useTempProjections);
240 241

        KisUpdateOriginalVisitor originalVisitor(applyRect,
Dmitry Kazakov's avatar
Dmitry Kazakov committed
242 243
                                                 m_currentProjection,
                                                 walker.cropRect());
244 245

        if(item.m_position & KisMergeWalker::N_FILTHY) {
246 247 248
            DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentLeaf, applyRect);
            currentLeaf->accept(originalVisitor);
            currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
249 250
        }
        else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
251 252 253 254
            DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentLeaf, applyRect);
            if(currentLeaf->dependsOnLowerNodes()) {
                currentLeaf->accept(originalVisitor);
                currentLeaf->projectionPlane()->recalculate(applyRect, currentLeaf->node());
Dmitry Kazakov's avatar
Dmitry Kazakov committed
255
            }
256 257
        }
        else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
258 259
            DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentLeaf, applyRect);
            currentLeaf->projectionPlane()->recalculate(applyRect, walker.startNode());
260 261
        }
        else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ {
262
            DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentLeaf, applyRect);
263 264 265
            /* nothing to do */
        }

266
        compositeWithProjection(currentLeaf, applyRect);
267 268

        if(item.m_position & KisMergeWalker::N_TOPMOST) {
269
            writeProjection(currentLeaf, useTempProjections, applyRect);
270 271 272
            resetProjection();
        }
    }
Dmitry Kazakov's avatar
Dmitry Kazakov committed
273 274 275 276

    if(notifyClones) {
        doNotifyClones(walker);
    }
277 278 279 280 281 282 283 284 285

    if(m_currentProjection) {
        warnImage << "BUG: The walker hasn't reached the root layer!";
        warnImage << "     Start node:" << walker.startNode() << "Requested rect:" << walker.requestedRect();
        warnImage << "     There must be an inconsistency in the walkers happened!";
        warnImage << "     Please report a bug describing how you got this message.";
        // reset projection to avoid artefacts in next merges and allow people to work further
        resetProjection();
    }
286 287 288 289 290 291 292
}

void KisAsyncMerger::resetProjection() {
    m_currentProjection = 0;
    m_finalProjection = 0;
}

293 294
void KisAsyncMerger::setupProjection(KisProjectionLeafSP currentLeaf, const QRect& rect, bool useTempProjection) {
    KisPaintDeviceSP parentOriginal = currentLeaf->parent()->original();
295

296
    if (parentOriginal != currentLeaf->projection()) {
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
        if (useTempProjection) {
            if(!m_cachedPaintDevice)
                m_cachedPaintDevice = new KisPaintDevice(parentOriginal->colorSpace());
            m_currentProjection = m_cachedPaintDevice;
            m_currentProjection->prepareClone(parentOriginal);
            m_finalProjection = parentOriginal;
        }
        else {
            parentOriginal->clear(rect);
            m_finalProjection = 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 */
    }
}

321
void KisAsyncMerger::writeProjection(KisProjectionLeafSP topmostLeaf, bool useTempProjection, QRect rect) {
322
    Q_UNUSED(useTempProjection);
323
    Q_UNUSED(topmostLeaf);
324 325 326
    if (!m_currentProjection) return;

    if(m_currentProjection != m_finalProjection) {
327
        KisPainter::copyAreaOptimized(rect.topLeft(), m_currentProjection, m_finalProjection, rect);
328
    }
329
    DEBUG_NODE_ACTION("Writing projection", "", topmostLeaf->parent(), rect);
330 331
}

332
bool KisAsyncMerger::compositeWithProjection(KisProjectionLeafSP leaf, const QRect &rect) {
333 334

    if (!m_currentProjection) return true;
335
    if (!leaf->visible()) return true;
336 337

    KisPainter gc(m_currentProjection);
338
    leaf->projectionPlane()->apply(&gc, rect);
339

340
    DEBUG_NODE_ACTION("Compositing projection", "", leaf, rect);
341 342
    return true;
}
Dmitry Kazakov's avatar
Dmitry Kazakov committed
343 344 345 346 347 348 349 350 351 352 353

void KisAsyncMerger::doNotifyClones(KisBaseRectsWalker &walker) {
    KisBaseRectsWalker::CloneNotificationsVector &vector =
        walker.cloneNotifications();

    KisBaseRectsWalker::CloneNotificationsVector::iterator it;

    for(it = vector.begin(); it != vector.end(); ++it) {
        (*it).notify();
    }
}