Commit b85c89b4 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Added KisAbstractProjectionPlane interface for Layer Styles and other modes

KisAbstarctProjectionPlane makes the abstractions of a merge'able entity.
It regenerate() it when the entity is filthy, fetches need/change/accessRect()
when needed and apply() the entity to the global projection finally.

Now KisAsyncMerger and Kis*Walker use this interface only, instead of
old-fasioned direct methods of KisNode/KisLayer.

The general idea is:

KisLayer uses all its internal framework to generate a projection()
device. The user sets a layer style on a layer by setting a special
projectionPlane() interface, which wraps this generated prejection()
data into Layer Styles.

CCMAIL:kimageshop@kde.org
parent a217ea1b
......@@ -176,6 +176,9 @@ set(kritaimage_LIB_SRCS
kis_acyclic_signal_connector.cpp
kis_layer.cc
kis_indirect_painting_support.cpp
kis_abstract_projection_plane.cpp
kis_layer_projection_plane.cpp
kis_mask_projection_plane.cpp
kis_mask.cc
kis_base_mask_generator.cpp
kis_rect_mask_generator.cpp
......
/*
* Copyright (c) 2015 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_abstract_projection_plane.h"
KisAbstractProjectionPlane::KisAbstractProjectionPlane()
{
}
KisAbstractProjectionPlane::~KisAbstractProjectionPlane()
{
}
QRect KisDumbProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode)
{
Q_UNUSED(filthyNode);
return rect;
}
void KisDumbProjectionPlane::apply(KisPainter *painter, const QRect &rect)
{
Q_UNUSED(painter);
Q_UNUSED(rect);
}
QRect KisDumbProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
QRect KisDumbProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
QRect KisDumbProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const
{
Q_UNUSED(pos);
return rect;
}
/*
* Copyright (c) 2015 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_ABSTRACT_PROJECTION_PLANE_H
#define __KIS_ABSTRACT_PROJECTION_PLANE_H
#include "kis_types.h"
#include "kis_layer.h"
class QRect;
class KisPainter;
/**
* An interface of the node to the compositioning
* system. Compositioning system KisAsyncMerger knows nothing about
* the internals of the layer, it just knows that the layer can:
*
* 1) recalculate() its internal representation if it is filthy
*
* 2) apply() itself onto a global projection using, writing all its data
* to the projection.
*
* 3) report parameters of these operations using needRect(),
* changeRect() and accessRect() methods, which mean the same as
* the corresponding methods of KisNode, but include more
* transformations for the layer, e.g. Layer Styles and
* Pass-through mode.
*/
class KisAbstractProjectionPlane
{
public:
KisAbstractProjectionPlane();
virtual ~KisAbstractProjectionPlane();
/**
* Is called by the async merger when the node is filthy and
* should recalculate its internal representation. For usual
* layers it means just calling updateProjection().
*/
virtual QRect recalculate(const QRect& rect, KisNodeSP filthyNode) = 0;
/**
* Writes the data of the projection plane onto a global
* projection using \p painter object.
*/
virtual void apply(KisPainter *painter, const QRect &rect) = 0;
/**
* Works like KisNode::needRect(), but includes more
* transformations of the layer
*/
virtual QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0;
/**
* Works like KisNode::changeRect(), but includes more
* transformations of the layer
*/
virtual QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0;
/**
* Works like KisNode::needRect(), but includes more
* transformations of the layer
*/
virtual QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos = KisLayer::N_FILTHY) const = 0;
};
/**
* A simple implementation of KisAbstractProjectionPlane interface
* that does nothing.
*/
class KisDumbProjectionPlane : public KisAbstractProjectionPlane
{
public:
QRect recalculate(const QRect& rect, KisNodeSP filthyNode);
void apply(KisPainter *painter, const QRect &rect);
QRect needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const;
QRect changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const;
QRect accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const;
};
#endif /* __KIS_ABSTRACT_PROJECTION_PLANE_H */
......@@ -100,11 +100,14 @@ public:
*/
void setFilter(KisFilterConfiguration *filterConfig);
void setChannelFlags(const QBitArray & channelFlags);
protected:
// override from KisLayer
QRect incomingChangeRect(const QRect &rect) const;
// override from KisNode
QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
void setChannelFlags(const QBitArray & channelFlags);
public Q_SLOTS:
/**
* gets this AdjustmentLayer. Overrides function in
......
......@@ -48,6 +48,8 @@
#include "kis_merge_walker.h"
#include "kis_refresh_subtree_walker.h"
#include "kis_abstract_projection_plane.h"
//#define DEBUG_MERGER
......@@ -223,7 +225,7 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
m_currentProjection,
walker.cropRect());
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect, currentNode);
currentNode->projectionPlane()->recalculate(applyRect, currentNode);
continue;
}
......@@ -239,18 +241,18 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
if(item.m_position & KisMergeWalker::N_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentNode, applyRect);
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect, walker.startNode());
currentNode->projectionPlane()->recalculate(applyRect, walker.startNode());
}
else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentNode, applyRect);
if(dependOnLowerNodes(currentNode)) {
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect, currentNode);
currentNode->projectionPlane()->recalculate(applyRect, currentNode);
}
}
else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentNode, applyRect);
currentNode->updateProjection(applyRect, walker.startNode());
currentNode->projectionPlane()->recalculate(applyRect, walker.startNode());
}
else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ {
DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentNode, applyRect);
......@@ -338,49 +340,8 @@ bool KisAsyncMerger::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;
QBitArray channelFlags = layer->channelFlags();
// if the color spaces don't match we will have a problem with the channel flags
// because the channel flags from the source layer doesn't match with the colorspace of the projection device
// this leads to the situation that the wrong channels will be enabled/disabled
if(!channelFlags.isEmpty() && m_currentProjection->colorSpace() != device->colorSpace()) {
const KoColorSpace* src = device->colorSpace();
const KoColorSpace* dst = m_currentProjection->colorSpace();
bool alphaFlagIsSet = (src->channelFlags(false,true) & channelFlags) == src->channelFlags(false,true);
bool allColorFlagsAreSet = (src->channelFlags(true,false) & channelFlags) == src->channelFlags(true,false);
bool allColorFlagsAreUnset = (src->channelFlags(true,false) & channelFlags).count(true) == 0;
if(allColorFlagsAreSet) {
channelFlags = dst->channelFlags(true, alphaFlagIsSet);
}
else if(allColorFlagsAreUnset) {
channelFlags = dst->channelFlags(false, alphaFlagIsSet);
}
else {
//TODO: convert the cannel flags properly
// for now just the alpha channel bit is copied and the other channels are left alone
for(quint32 i=0; i<dst->channelCount(); ++i) {
if(dst->channels()[i]->channelType() == KoChannelInfo::ALPHA) {
channelFlags.setBit(i, alphaFlagIsSet);
break;
}
}
}
}
KisPainter gc(m_currentProjection);
gc.setChannelFlags(channelFlags);
gc.setCompositeOp(layer->compositeOp());
gc.setOpacity(layer->opacity());
gc.bitBlt(needRect.topLeft(), device, needRect);
layer->projectionPlane()->apply(&gc, rect);
DEBUG_NODE_ACTION("Compositing projection", "", layer, needRect);
return true;
......
......@@ -24,6 +24,9 @@
#include "kis_layer.h"
#include "kis_mask.h"
#include "kis_abstract_projection_plane.h"
class KisBaseRectsWalker;
typedef KisSharedPtr<KisBaseRectsWalker> KisBaseRectsWalkerSP;
......@@ -276,8 +279,8 @@ protected:
// We do not work with masks here. It is KisLayer's job.
if(!isLayer(node)) return;
QRect currentChangeRect = node->changeRect(m_resultChangeRect,
convertPositionToFilthy(position));
QRect currentChangeRect = node->projectionPlane()->changeRect(m_resultChangeRect,
convertPositionToFilthy(position));
currentChangeRect = cropThisRect(currentChangeRect);
if(!m_changeRectVaries)
......@@ -285,8 +288,8 @@ protected:
m_resultChangeRect = currentChangeRect;
m_resultUncroppedChangeRect = node->changeRect(m_resultUncroppedChangeRect,
convertPositionToFilthy(position));
m_resultUncroppedChangeRect = node->projectionPlane()->changeRect(m_resultUncroppedChangeRect,
convertPositionToFilthy(position));
registerCloneNotification(node, position);
}
......@@ -326,11 +329,11 @@ protected:
pushJob(node, position, m_lastNeedRect);
//else /* Why push empty rect? */;
m_resultAccessRect |= node->accessRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_resultAccessRect |= node->projectionPlane()->accessRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = node->needRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = node->projectionPlane()->needRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = cropThisRect(m_lastNeedRect);
m_childNeedRect = m_lastNeedRect;
}
......@@ -338,11 +341,11 @@ protected:
if(!m_lastNeedRect.isEmpty()) {
pushJob(node, position, m_lastNeedRect);
m_resultAccessRect |= node->accessRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_resultAccessRect |= node->projectionPlane()->accessRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = node->needRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = node->projectionPlane()->needRect(m_lastNeedRect,
convertPositionToFilthy(position));
m_lastNeedRect = cropThisRect(m_lastNeedRect);
}
}
......@@ -370,7 +373,7 @@ protected:
(!isMask(currentNode) || !currentNode->visible()));
if(currentNode) {
QRect changeRect = currentNode->changeRect(m_resultChangeRect);
QRect changeRect = currentNode->projectionPlane()->changeRect(m_resultChangeRect);
m_changeRectVaries |= changeRect != m_resultChangeRect;
m_resultChangeRect = changeRect;
m_resultUncroppedChangeRect = changeRect;
......@@ -388,11 +391,11 @@ protected:
qint32 x, y, w, h;
QRect tempRect;
tempRect = node->changeRect(requestedRect);
tempRect = node->projectionPlane()->changeRect(requestedRect);
tempRect.getRect(&x, &y, &w, &h);
checksum += -x - y + w + h;
tempRect = node->needRect(requestedRect);
tempRect = node->projectionPlane()->needRect(requestedRect);
tempRect.getRect(&x, &y, &w, &h);
checksum += -x - y + w + h;
......
......@@ -72,10 +72,6 @@ public:
KisPaintDeviceSP paintDevice() const;
bool needProjection() const;
void copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const;
QIcon icon() const;
KisDocumentSectionModel::PropertyList sectionModelProperties() const;
......@@ -91,8 +87,6 @@ public:
/// Returns the exact bounds of where the actual data resides in this layer
QRect exactBounds() const;
QRect accessRect(const QRect &rect, PositionToFilthy pos) const;
bool accept(KisNodeVisitor &);
void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter);
......@@ -119,6 +113,14 @@ public:
QRect needRectOnSourceForMasks(const QRect &rc) const;
protected:
// override from KisNode
QRect accessRect(const QRect &rect, PositionToFilthy pos) const;
// override from KisLayer
void copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const;
void notifyParentVisibilityChanged(bool value);
QRect outgoingChangeRect(const QRect &rect) const;
private:
......
......@@ -79,6 +79,8 @@
#include "kis_layer_composition.h"
#include "kis_wrapped_rect.h"
#include "kis_layer_projection_plane.h"
// #define SANITY_CHECKS
......@@ -884,7 +886,7 @@ QRect KisImage::realNodeExtent(KisNodeSP rootNode, QRect currentRect)
// TODO: it would be better to count up changeRect inside
// node's extent() method
currentRect |= rootNode->changeRect(rootNode->extent());
currentRect |= rootNode->projectionPlane()->changeRect(rootNode->extent());
return currentRect;
}
......
......@@ -46,6 +46,8 @@
#include "kis_clone_layer.h"
#include "kis_psd_layer_style.h"
#include "kis_layer_projection_plane.h"
class KisSafeProjection {
public:
......@@ -114,6 +116,8 @@ struct KisLayer::Private
KisSafeProjection safeProjection;
KisCloneLayersList clonesList;
KisPSDLayerStyle* layerStyle;
QScopedPointer<KisLayerProjectionPlane> projectionPlane;
};
......@@ -126,6 +130,7 @@ KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity)
m_d->image = image;
m_d->metaDataStore = new KisMetaData::Store();
m_d->layerStyle = 0;
m_d->projectionPlane.reset(new KisLayerProjectionPlane(this));
}
KisLayer::KisLayer(const KisLayer& rhs)
......@@ -142,6 +147,7 @@ KisLayer::KisLayer(const KisLayer& rhs)
m_d->layerStyle = 0;
}
setName(rhs.name());
m_d->projectionPlane.reset(new KisLayerProjectionPlane(this));
}
}
......@@ -652,6 +658,10 @@ void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original,
gc.bitBlt(rect.topLeft(), original, rect);
}
KisAbstractProjectionPlane* KisLayer::projectionPlane() const
{
return m_d->projectionPlane.data();
}
KisPaintDeviceSP KisLayer::projection() const
{
......
......@@ -39,6 +39,8 @@ class QStack;
class QBitArray;
class KisCloneLayer;
class KisPSDLayerStyle;
class KisAbstractProjectionPlane;
namespace KisMetaData
{
......@@ -82,20 +84,15 @@ public:
void setLayerStyle(KisPSDLayerStyle *layerStyle);
/**
* Ask the layer to assemble its data & apply all the effect masks
* to it.
* \see a comment in KisNode::projectionPlane()
*/
QRect updateProjection(const QRect& rect, KisNodeSP filthyNode);
virtual KisAbstractProjectionPlane* projectionPlane() const;
QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect);
void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect);
virtual bool needProjection() const;
virtual void copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const;
/**
* Return the fully rendered representation of this layer: its
* data and its effect masks
......@@ -250,8 +247,6 @@ public:
*/
QList<KisEffectMaskSP> effectMasks(KisNodeSP lastNode = 0) const;
QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
/**
* Get the group layer that contains this layer.
*/
......@@ -262,8 +257,28 @@ public:
*/
KisMetaData::Store* metaData();
protected:
// override from KisNode
QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
protected:
/**
* Ask the layer to assemble its data & apply all the effect masks
* to it.
*/
QRect updateProjection(const QRect& rect, KisNodeSP filthyNode);
/**
* Layers can override this method to get some special behavior
* when copying data from \p original to \p projection, e.g. blend
* in indirect painting device. If you need to modify data
* outside \p rect, please also override outgoingChangeRect()
* method.
*/
virtual void copyOriginalToProjection(const KisPaintDeviceSP original,
KisPaintDeviceSP projection,
const QRect& rect) const;
/**
* For KisLayer classes change rect transformation consists of two
* parts: incoming and outgoing.
......@@ -343,6 +358,10 @@ protected:
const KisPaintDeviceSP destination,
const QRect &requestedRect,
KisNodeSP filthyNode, KisNodeSP lastNode) const;
private:
friend class KisLayerProjectionPlane;
friend class KisTransformMask;
friend class KisLayerTest;
private:
struct Private;
......
/*
* Copyright (c) 2015 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_layer_projection_plane.h"
#include <QBitArray>
#include <KoColorSpace.h>
#include <KoChannelInfo.h>
#include "kis_painter.h"
struct KisLayerProjectionPlane::Private
{
KisLayer *layer;
};
KisLayerProjectionPlane::KisLayerProjectionPlane(KisLayer *layer)
: m_d(new Private)
{
m_d->layer = layer;
}
KisLayerProjectionPlane::~KisLayerProjectionPlane()
{
}
QRect KisLayerProjectionPlane::recalculate(const QRect& rect, KisNodeSP filthyNode)
{
return m_d->layer->updateProjection(rect, filthyNode);
}
void KisLayerProjectionPlane::apply(KisPainter *painter, const QRect &rect)
{
KisPaintDeviceSP device = m_d->layer->projection();
if (!device) return;
QRect needRect = rect & device->extent();
if(needRect.isEmpty()) return;
QBitArray channelFlags = m_d->layer->channelFlags();
// if the color spaces don't match we will have a problem with the channel flags
// because the channel flags from the source layer doesn't match with the colorspace of the projection device
// this leads to the situation that the wrong channels will be enabled/disabled
const KoColorSpace* srcCS = device->colorSpace();
const KoColorSpace* dstCS = painter->device()->colorSpace();
if (!channelFlags.isEmpty() && srcCS != dstCS) {
bool alphaFlagIsSet = (srcCS->channelFlags(false,true) & channelFlags) == srcCS->channelFlags(false,true);
bool allColorFlagsAreSet = (srcCS->channelFlags(true,false) & channelFlags) == srcCS->channelFlags(true,false);
bool allColorFlagsAreUnset = (srcCS->channelFlags(true,false) & channelFlags).count(true) == 0;
if(allColorFlagsAreSet) {
channelFlags = dstCS->channelFlags(true, alphaFlagIsSet);
} else if(allColorFlagsAreUnset) {
channelFlags = dstCS->channelFlags(false, alphaFlagIsSet);
} else {
//TODO: convert the cannel flags properly
// for now just the alpha channel bit is copied and the other channels are left alone
for (quint32 i=0; i < dstCS->channelCount(); ++i) {
if (dstCS->channels()[i]->channelType() == KoChannelInfo::ALPHA) {
channelFlags.setBit(i, alphaFlagIsSet);
break;
}
}
}
}
painter->setChannelFlags(channelFlags);
painter->setCompositeOp(m_d->layer->compositeOp());
painter->setOpacity(m_d->layer->opacity());
painter->bitBlt(needRect.topLeft(), device, needRect);
}
QRect KisLayerProjectionPlane::needRect(const QRect &rect, KisLayer::PositionToFilthy pos) const
{
return m_d->layer->needRect(rect, pos);
}
QRect KisLayerProjectionPlane::changeRect(const QRect &rect, KisLayer::PositionToFilthy pos) const
{
return m_d->layer->changeRect(rect, pos);
}
QRect KisLayerProjectionPlane::accessRect(const QRect &rect, KisLayer::PositionToFilthy pos) const
{
return m_d->layer->accessRect(rect, pos);
}
/*
* Copyright (c) 2015 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_LAYER_PROJECTION_PLANE_H
#define __KIS_LAYER_PROJECTION_PLANE_H
#include "kis_abstract_projection_plane.h"
#include <QScopedPointer>
/**
* An implementation of the KisAbstractProjectionPlane interface for a
* laye