diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 2763ef05ff..e954a8bd77 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,988 +1,990 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * 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.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_debug.h" #include "kis_image.h" #include "kis_painter.h" #include "kis_mask.h" #include "kis_effect_mask.h" #include "kis_selection_mask.h" #include "kis_meta_data_store.h" #include "kis_selection.h" #include "kis_paint_layer.h" #include "kis_raster_keyframe_channel.h" #include "kis_clone_layer.h" #include "kis_psd_layer_style.h" #include "kis_layer_projection_plane.h" #include "layerstyles/kis_layer_style_projection_plane.h" #include "krita_utils.h" #include "kis_layer_properties_icons.h" #include "kis_layer_utils.h" #include "kis_projection_leaf.h" #include "KisSafeNodeProjectionStore.h" class KisCloneLayersList { public: void addClone(KisCloneLayerWSP cloneLayer) { m_clonesList.append(cloneLayer); } void removeClone(KisCloneLayerWSP cloneLayer) { m_clonesList.removeOne(cloneLayer); } void setDirty(const QRect &rect) { Q_FOREACH (KisCloneLayerSP clone, m_clonesList) { if (clone) { clone->setDirtyOriginal(rect); } } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; class KisLayerMasksCache { public: KisLayerMasksCache(KisLayer *parent) : m_parent(parent) { } KisSelectionMaskSP selectionMask() { QReadLocker readLock(&m_lock); if (!m_isSelectionMaskValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isSelectionMaskValid) { KoProperties properties; properties.setProperty("active", true); properties.setProperty("visible", true); QList masks = m_parent->childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask) { m_selectionMask = dynamic_cast(mask.data()); break; } } m_isSelectionMaskValid = true; } // return under write lock return m_selectionMask; } // return under read lock return m_selectionMask; } QList effectMasks() { QReadLocker readLock(&m_lock); if (!m_isEffectMasksValid) { readLock.unlock(); QWriteLocker writeLock(&m_lock); if (!m_isEffectMasksValid) { m_effectMasks = m_parent->searchEffectMasks(0); m_isEffectMasksValid = true; } // return under write lock return m_effectMasks; } // return under read lock return m_effectMasks; } void setDirty() { QWriteLocker l(&m_lock); m_isSelectionMaskValid = false; m_isEffectMasksValid = false; m_selectionMask = 0; m_effectMasks.clear(); } private: KisLayer *m_parent; QReadWriteLock m_lock; bool m_isSelectionMaskValid = false; bool m_isEffectMasksValid = false; KisSelectionMaskSP m_selectionMask; QList m_effectMasks; }; struct Q_DECL_HIDDEN KisLayer::Private { Private(KisLayer *q) : masksCache(q) { } QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisLayerStyleProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; KisSafeNodeProjectionStoreSP safeProjection; KisLayerMasksCache masksCache; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode(image) , m_d(new Private(this)) { setName(name); setOpacity(opacity); m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(); + m_d->safeProjection->setImage(image); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private(this)) { if (this != &rhs) { m_d->metaDataStore = new KisMetaData::Store(*rhs.m_d->metaDataStore); m_d->channelFlags = rhs.m_d->channelFlags; setName(rhs.name()); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); m_d->safeProjection = new KisSafeNodeProjectionStore(*rhs.m_d->safeProjection); + m_d->safeProjection->setImage(image()); if (rhs.m_d->layerStyle) { m_d->layerStyle = rhs.m_d->layerStyle->clone(); if (rhs.m_d->layerStyleProjectionPlane) { m_d->layerStyleProjectionPlane = toQShared( new KisLayerStyleProjectionPlane(*rhs.m_d->layerStyleProjectionPlane, this, m_d->layerStyle)); } } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { KisImageSP image = this->image(); if (!image) { return nullptr; } return image->colorSpace(); } const KoCompositeOp * KisLayer::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisMask. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ KisNodeSP parentNode = parent(); if (!parentNode) return 0; if (!parentNode->colorSpace()) return 0; const KoCompositeOp* op = parentNode->colorSpace()->compositeOp(compositeOpId()); return op ? op : parentNode->colorSpace()->compositeOp(COMPOSITE_OVER); } KisPSDLayerStyleSP KisLayer::layerStyle() const { return m_d->layerStyle; } void KisLayer::setLayerStyle(KisPSDLayerStyleSP layerStyle) { if (layerStyle) { m_d->layerStyle = layerStyle; KisLayerStyleProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisLayerStyleProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisLayerStyleProjectionPlaneSP(0); m_d->layerStyleProjectionPlane = plane; } else { m_d->layerStyleProjectionPlane.clear(); m_d->layerStyle.clear(); } } KisBaseNode::PropertyList KisLayer::sectionModelProperties() const { KisBaseNode::PropertyList l = KisBaseNode::sectionModelProperties(); l << KisBaseNode::Property(KoID("opacity", i18n("Opacity")), i18n("%1%", percentOpacity())); const KoCompositeOp * compositeOp = this->compositeOp(); if (compositeOp) { l << KisBaseNode::Property(KoID("compositeop", i18n("Blending Mode")), compositeOp->description()); } if (m_d->layerStyle && !m_d->layerStyle->isEmpty()) { l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::layerStyle, m_d->layerStyle->isEnabled()); } l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::inheritAlpha, alphaChannelDisabled()); return l; } void KisLayer::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { KisBaseNode::setSectionModelProperties(properties); Q_FOREACH (const KisBaseNode::Property &property, properties) { if (property.id == KisLayerPropertiesIcons::inheritAlpha.id()) { disableAlphaChannel(property.state.toBool()); } if (property.id == KisLayerPropertiesIcons::layerStyle.id()) { if (m_d->layerStyle && m_d->layerStyle->isEnabled() != property.state.toBool()) { m_d->layerStyle->setEnabled(property.state.toBool()); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } } } void KisLayer::disableAlphaChannel(bool disable) { QBitArray newChannelFlags = m_d->channelFlags; if(newChannelFlags.isEmpty()) newChannelFlags = colorSpace()->channelFlags(true, true); if(disable) newChannelFlags &= colorSpace()->channelFlags(true, false); else newChannelFlags |= colorSpace()->channelFlags(false, true); setChannelFlags(newChannelFlags); } bool KisLayer::alphaChannelDisabled() const { QBitArray flags = colorSpace()->channelFlags(false, true) & m_d->channelFlags; return flags.count(true) == 0 && !m_d->channelFlags.isEmpty(); } void KisLayer::setChannelFlags(const QBitArray & channelFlags) { Q_ASSERT(channelFlags.isEmpty() ||((quint32)channelFlags.count() == colorSpace()->channelCount())); if (KritaUtils::compareChannelFlags(channelFlags, this->channelFlags())) { return; } if (!channelFlags.isEmpty() && channelFlags == QBitArray(channelFlags.size(), true)) { m_d->channelFlags.clear(); } else { m_d->channelFlags = channelFlags; } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } QBitArray & KisLayer::channelFlags() const { return m_d->channelFlags; } bool KisLayer::temporary() const { return nodeProperties().boolProperty("temporary", false); } void KisLayer::setTemporary(bool t) { setNodeProperty("temporary", t); } void KisLayer::setImage(KisImageWSP image) { // we own the projection device, so we should take care about it KisPaintDeviceSP projection = this->projection(); if (projection && projection != original()) { projection->setDefaultBounds(new KisDefaultBounds(image)); } m_d->safeProjection->setImage(image); KisNode::setImage(image); } bool KisLayer::canMergeAndKeepBlendOptions(KisLayerSP otherLayer) { return this->compositeOpId() == otherLayer->compositeOpId() && this->opacity() == otherLayer->opacity() && this->channelFlags() == otherLayer->channelFlags() && !this->layerStyle() && !otherLayer->layerStyle() && (this->colorSpace() == otherLayer->colorSpace() || *this->colorSpace() == *otherLayer->colorSpace()); } KisLayerSP KisLayer::createMergedLayerTemplate(KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); KisLayerSP newLayer = new KisPaintLayer(image(), prevLayer->name(), OPACITY_OPAQUE_U8); if (keepBlendingOptions) { newLayer->setCompositeOpId(compositeOpId()); newLayer->setOpacity(opacity()); newLayer->setChannelFlags(channelFlags()); } return newLayer; } void KisLayer::fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer) { const bool keepBlendingOptions = canMergeAndKeepBlendOptions(prevLayer); QRect layerProjectionExtent = this->projection()->extent(); QRect prevLayerProjectionExtent = prevLayer->projection()->extent(); bool alphaDisabled = this->alphaChannelDisabled(); bool prevAlphaDisabled = prevLayer->alphaChannelDisabled(); KisPaintDeviceSP mergedDevice = dstLayer->paintDevice(); if (!keepBlendingOptions) { KisPainter gc(mergedDevice); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | imageSP->bounds()); //Restore the previous prevLayer disableAlpha status for correct undo/redo prevLayer->disableAlphaChannel(prevAlphaDisabled); //Paint the pixels of the current layer, using their actual alpha value if (alphaDisabled == prevAlphaDisabled) { this->disableAlphaChannel(false); } this->projectionPlane()->apply(&gc, layerProjectionExtent | imageSP->bounds()); //Restore the layer disableAlpha status for correct undo/redo this->disableAlphaChannel(alphaDisabled); } else { //Copy prevLayer KisPaintDeviceSP srcDev = prevLayer->projection(); mergedDevice->makeCloneFrom(srcDev, srcDev->extent()); //Paint layer on the copy KisPainter gc(mergedDevice); gc.bitBlt(layerProjectionExtent.topLeft(), this->projection(), layerProjectionExtent); } } void KisLayer::registerClone(KisCloneLayerWSP clone) { m_d->clonesList.addClone(clone); } void KisLayer::unregisterClone(KisCloneLayerWSP clone) { m_d->clonesList.removeClone(clone); } const QList KisLayer::registeredClones() const { return m_d->clonesList.registeredClones(); } bool KisLayer::hasClones() const { return m_d->clonesList.hasClones(); } void KisLayer::updateClones(const QRect &rect) { m_d->clonesList.setDirty(rect); } void KisLayer::notifyChildMaskChanged() { m_d->masksCache.setDirty(); } KisSelectionMaskSP KisLayer::selectionMask() const { return m_d->masksCache.selectionMask(); } KisSelectionSP KisLayer::selection() const { KisSelectionMaskSP mask = selectionMask(); if (mask) { return mask->selection(); } KisImageSP image = this->image(); if (image) { return image->globalSelection(); } return KisSelectionSP(); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks() const { return m_d->masksCache.effectMasks(); } QList KisLayer::effectMasks(KisNodeSP lastNode) const { if (lastNode.isNull()) { return effectMasks(); } else { // happens rarely. return searchEffectMasks(lastNode); } } QList KisLayer::searchEffectMasks(KisNodeSP lastNode) const { QList masks; KIS_SAFE_ASSERT_RECOVER_NOOP(projectionLeaf()); KisProjectionLeafSP child = projectionLeaf()->firstChild(); while (child) { if (child->node() == lastNode) break; KIS_SAFE_ASSERT_RECOVER_NOOP(child); KIS_SAFE_ASSERT_RECOVER_NOOP(child->node()); if (child->visible()) { KisEffectMaskSP mask = dynamic_cast(const_cast(child->node().data())); if (mask) { masks.append(mask); } } child = child->nextSibling(); } return masks; } bool KisLayer::hasEffectMasks() const { return !m_d->masksCache.effectMasks().isEmpty(); } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection->releaseDevice(); } else { if (!updatedRect.isEmpty()) { KisPaintDeviceSP projection = m_d->safeProjection->getDeviceLazy(originalDevice); updatedRect = applyMasks(originalDevice, projection, updatedRect, filthyNode, 0); } } return updatedRect; } QRect KisLayer::partialChangeRect(KisNodeSP lastNode, const QRect& rect) { bool changeRectVaries = false; QRect changeRect = outgoingChangeRect(rect); changeRect = masksChangeRect(effectMasks(lastNode), changeRect, changeRectVaries); return changeRect; } /** * \p rect is a dirty rect in layer's original() coordinates! */ void KisLayer::buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect) { QRect changeRect = partialChangeRect(lastNode, rect); KisPaintDeviceSP originalDevice = original(); KIS_ASSERT_RECOVER_RETURN(needProjection() || hasEffectMasks()); if (!changeRect.isEmpty()) { applyMasks(originalDevice, projection, changeRect, this, lastNode); } } bool KisLayer::needProjection() const { return false; } void KisLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect); } KisAbstractProjectionPlaneSP KisLayer::projectionPlane() const { return m_d->layerStyleProjectionPlane ? KisAbstractProjectionPlaneSP(m_d->layerStyleProjectionPlane) : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection->getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } void KisLayer::childNodeChanged(KisNodeSP changedChildNode) { if (dynamic_cast(changedChildNode.data())) { notifyChildMaskChanged(); } } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::needRectForOriginal(const QRect &rect) const { QRect needRect = rect; const QList masks = effectMasks(); if (!masks.isEmpty()) { QStack applyRects; bool needRectVaries; needRect = masksNeedRect(masks, rect, applyRects, needRectVaries); } return needRect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); if (originalDevice ) { KisRasterKeyframeChannel *channel = originalDevice->keyframeChannel(); if (channel) { KisPaintDeviceSP targetDevice = new KisPaintDevice(colorSpace()); KisKeyframeSP keyframe = channel->activeKeyframeAt(time); channel->fetchFrame(keyframe, targetDevice); return targetDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } return createThumbnail(w, h); } qint32 KisLayer::x() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->x() : 0; } qint32 KisLayer::y() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->y() : 0; } void KisLayer::setX(qint32 x) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setX(x); } void KisLayer::setY(qint32 y) { KisPaintDeviceSP originalDevice = original(); if (originalDevice) originalDevice->setY(y); } QRect KisLayer::layerExtentImpl(bool needExactBounds) const { QRect additionalMaskExtent = QRect(); QList effectMasks = this->effectMasks(); Q_FOREACH(KisEffectMaskSP mask, effectMasks) { additionalMaskExtent |= mask->nonDependentExtent(); } KisPaintDeviceSP originalDevice = original(); QRect layerExtent; if (originalDevice) { layerExtent = needExactBounds ? originalDevice->exactBounds() : originalDevice->extent(); } QRect additionalCompositeOpExtent; if (compositeOpId() == COMPOSITE_DESTINATION_IN || compositeOpId() == COMPOSITE_DESTINATION_ATOP) { additionalCompositeOpExtent = originalDevice->defaultBounds()->bounds(); } return layerExtent | additionalMaskExtent | additionalCompositeOpExtent; } QRect KisLayer::extent() const { return layerExtentImpl(false); } QRect KisLayer::exactBounds() const { return layerExtentImpl(true); } KisLayerSP KisLayer::parentLayer() const { return qobject_cast(parent().data()); } KisMetaData::Store* KisLayer::metaData() { return m_d->metaDataStore; } diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc index cf7c4d560a..2a71588134 100644 --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -1,510 +1,511 @@ /* * Copyright (c) 2006 Boudewijn Rempt * (c) 2009 Dmitry Kazakov * * 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_mask.h" #include // to prevent incomplete class types on "delete selection->flatten();" #include #include #include #include #include #include "kis_paint_device.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "kis_painter.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_cached_paint_device.h" #include "kis_mask_projection_plane.h" #include "kis_raster_keyframe_channel.h" #include "KisSafeNodeProjectionStore.h" struct Q_DECL_HIDDEN KisMask::Private { Private(KisMask *_q) : q(_q), projectionPlane(new KisMaskProjectionPlane(q)) { } mutable KisSelectionSP selection; KisCachedPaintDevice paintDeviceCache; KisMask *q; /** * Due to the design of the Kra format the X,Y offset of the paint * device belongs to the node, but not to the device itself. So * the offset is set when the node is created, but not when the * selection is initialized. This causes the X,Y values to be * lost, since the selection doen not exist at the moment. That is * why we save it separately. */ QScopedPointer deferredSelectionOffset; KisAbstractProjectionPlaneSP projectionPlane; KisSafeSelectionNodeProjectionStoreSP safeProjection; void initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice); }; KisMask::KisMask(const QString & name) : KisNode(nullptr) , m_d(new Private(this)) { setName(name); m_d->safeProjection = new KisSafeSelectionNodeProjectionStore(); + m_d->safeProjection->setImage(image()); } KisMask::KisMask(const KisMask& rhs) : KisNode(rhs) , KisIndirectPaintingSupport() , m_d(new Private(this)) { setName(rhs.name()); m_d->safeProjection = new KisSafeSelectionNodeProjectionStore(*rhs.m_d->safeProjection); if (rhs.m_d->selection) { m_d->selection = new KisSelection(*rhs.m_d->selection.data()); m_d->selection->setParentNode(this); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } } } KisMask::~KisMask() { if (m_d->selection) { m_d->selection->setParentNode(0); } delete m_d; } void KisMask::setImage(KisImageWSP image) { KisPaintDeviceSP parentPaintDevice = parent() ? parent()->original() : 0; KisDefaultBoundsBaseSP defaultBounds = new KisSelectionDefaultBounds(parentPaintDevice, image); if (m_d->selection) { m_d->selection->setDefaultBounds(defaultBounds); } m_d->safeProjection->setImage(image); KisNode::setImage(image); } bool KisMask::allowAsChild(KisNodeSP node) const { Q_UNUSED(node); return false; } const KoColorSpace * KisMask::colorSpace() const { KisNodeSP parentNode = parent(); return parentNode ? parentNode->colorSpace() : 0; } const KoCompositeOp * KisMask::compositeOp() const { /** * FIXME: This function duplicates the same function from * KisLayer. We can't move it to KisBaseNode as it doesn't * know anything about parent() method of KisNode * Please think it over... */ const KoColorSpace *colorSpace = this->colorSpace(); if (!colorSpace) return 0; const KoCompositeOp* op = colorSpace->compositeOp(compositeOpId()); return op ? op : colorSpace->compositeOp(COMPOSITE_OVER); } void KisMask::initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer) { m_d->initSelectionImpl(copyFrom, parentLayer, 0); } void KisMask::initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, copyFromDevice); } void KisMask::initSelection(KisLayerSP parentLayer) { m_d->initSelectionImpl(0, parentLayer, 0); } void KisMask::Private::initSelectionImpl(KisSelectionSP copyFrom, KisLayerSP parentLayer, KisPaintDeviceSP copyFromDevice) { Q_ASSERT(parentLayer); KisPaintDeviceSP parentPaintDevice = parentLayer->original(); if (copyFrom) { /** * We can't use setSelection as we may not have parent() yet */ selection = new KisSelection(*copyFrom); selection->setDefaultBounds(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); if (copyFrom->hasShapeSelection()) { delete selection->flatten(); } } else if (copyFromDevice) { KritaUtils::DeviceCopyMode copyMode = q->inherits("KisFilterMask") || q->inherits("KisTransparencyMask") ? KritaUtils::CopyAllFrames : KritaUtils::CopySnapshot; selection = new KisSelection(copyFromDevice, copyMode, new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (pixelSelection->framesInterface()) { q->addKeyframeChannel(pixelSelection->keyframeChannel()); q->enableAnimation(); } } else { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice, parentLayer->image())); selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, selection->pixelSelection()->colorSpace())); if (deferredSelectionOffset) { selection->setX(deferredSelectionOffset->x()); selection->setY(deferredSelectionOffset->y()); deferredSelectionOffset.reset(); } } selection->setParentNode(q); selection->updateProjection(); } KisSelectionSP KisMask::selection() const { return m_d->selection; } KisPaintDeviceSP KisMask::paintDevice() const { KisSelectionSP selection = this->selection(); return selection ? selection->pixelSelection() : 0; } KisPaintDeviceSP KisMask::original() const { return paintDevice(); } KisPaintDeviceSP KisMask::projection() const { KisPaintDeviceSP originalDevice = original(); KisPaintDeviceSP result = originalDevice; KisSelectionSP selection = this->selection(); if (selection && hasTemporaryTarget()) { result = m_d->safeProjection->getDeviceLazy(selection)->pixelSelection(); } return result; } KisAbstractProjectionPlaneSP KisMask::projectionPlane() const { return m_d->projectionPlane; } void KisMask::setSelection(KisSelectionSP selection) { m_d->selection = selection; if (parent()) { const KisLayer *parentLayer = qobject_cast(parent()); m_d->selection->setDefaultBounds(new KisDefaultBounds(parentLayer->image())); } m_d->selection->setParentNode(this); } void KisMask::select(const QRect & rc, quint8 selectedness) { KisSelectionSP sel = selection(); KisPixelSelectionSP psel = sel->pixelSelection(); psel->select(rc, selectedness); sel->updateProjection(rc); } QRect KisMask::decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const { Q_UNUSED(src); Q_UNUSED(dst); Q_UNUSED(maskPos); Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors"); return rc; } bool KisMask::paintsOutsideSelection() const { return false; } void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy maskPos) const { if (selection()) { flattenSelectionProjection(m_d->selection, applyRect); KisSelectionSP effectiveSelection = m_d->selection; { // Access temporary target under the lock held KisIndirectPaintingSupport::ReadLocker l(this); if (!paintsOutsideSelection()) { // extent of m_d->selection should also be accessed under a lock, // because it might be being merged in by the temporary target atm QRect effectiveExtent = m_d->selection->selectedRect(); if (hasTemporaryTarget()) { effectiveExtent |= temporaryTarget()->extent(); } if(!effectiveExtent.intersects(applyRect)) { return; } } if (hasTemporaryTarget()) { effectiveSelection = m_d->safeProjection->getDeviceLazy(m_d->selection); KisPainter::copyAreaOptimized(applyRect.topLeft(), m_d->selection->pixelSelection(), effectiveSelection->pixelSelection(), applyRect); KisPainter gc(effectiveSelection->pixelSelection()); setupTemporaryPainter(&gc); gc.bitBlt(applyRect.topLeft(), temporaryTarget(), applyRect); } else { m_d->safeProjection->releaseDevice(); } mergeInMaskInternal(projection, effectiveSelection, applyRect, needRect, maskPos); } } else { mergeInMaskInternal(projection, 0, applyRect, needRect, maskPos); } } void KisMask::mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, KisNode::PositionToFilthy maskPos) const { KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection); if (effectiveSelection) { QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, maskPos); // masks don't have any compositioning KisPainter::copyAreaOptimized(updatedRect.topLeft(), cacheDevice, projection, updatedRect, effectiveSelection); } else { cacheDevice->makeCloneFromRough(projection, preparedNeedRect); projection->clear(preparedNeedRect); decorateRect(cacheDevice, projection, applyRect, maskPos); } m_d->paintDeviceCache.putDevice(cacheDevice); } void KisMask::flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const { selection->updateProjection(dirtyRect); } QRect KisMask::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); QRect resultRect = rect; if (m_d->selection) { QRect selectionExtent = m_d->selection->selectedRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { selectionExtent |= temporaryTarget->extent(); } resultRect &= selectionExtent; } return resultRect; } QRect KisMask::changeRect(const QRect &rect, PositionToFilthy pos) const { return KisMask::needRect(rect, pos); } QRect KisMask::extent() const { QRect resultRect; if (m_d->selection) { resultRect = m_d->selection->selectedRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { resultRect |= temporaryTarget->extent(); } } else if (KisNodeSP parent = this->parent()) { resultRect = parent->extent(); } return resultRect; } QRect KisMask::exactBounds() const { QRect resultRect; if (m_d->selection) { resultRect = m_d->selection->selectedExactRect(); // copy for thread safety! KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); if (temporaryTarget) { resultRect |= temporaryTarget->exactBounds(); } } else if (KisNodeSP parent = this->parent()) { resultRect = parent->exactBounds(); } return resultRect; } qint32 KisMask::x() const { return m_d->selection ? m_d->selection->x() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->x() : parent() ? parent()->x() : 0; } qint32 KisMask::y() const { return m_d->selection ? m_d->selection->y() : m_d->deferredSelectionOffset ? m_d->deferredSelectionOffset->y() : parent() ? parent()->y() : 0; } void KisMask::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(x, 0)); } else { m_d->deferredSelectionOffset->rx() = x; } } void KisMask::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } else if (!m_d->deferredSelectionOffset) { m_d->deferredSelectionOffset.reset(new QPoint(0, y)); } else { m_d->deferredSelectionOffset->ry() = y; } } QRect KisMask::nonDependentExtent() const { return QRect(); } QImage KisMask::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = selection() ? selection()->projection() : 0; return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } void KisMask::testingInitSelection(const QRect &rect, KisLayerSP parentLayer) { if (parentLayer) { m_d->selection = new KisSelection(new KisSelectionDefaultBounds(parentLayer->paintDevice(), parentLayer->image())); } else { m_d->selection = new KisSelection(); } m_d->selection->pixelSelection()->select(rect, OPACITY_OPAQUE_U8); m_d->selection->updateProjection(rect); m_d->selection->setParentNode(this); } KisKeyframeChannel *KisMask::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisPaintDeviceSP device = paintDevice(); if (device) { KisRasterKeyframeChannel *contentChannel = device->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } } return KisNode::requestKeyframeChannel(id); } void KisMask::baseNodeChangedCallback() { KisNodeSP up = parent(); KisLayer *layer = dynamic_cast(up.data()); if (layer) { layer->notifyChildMaskChanged(); } KisNode::baseNodeChangedCallback(); }