diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index ffd4f15ea3..4f96a61467 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,501 +1,504 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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_base_node.h" #include #include #include #include #include #include #include "kis_paint_device.h" #include "kis_layer_properties_icons.h" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; KisBaseNode::Property hack_visible; //HACK QUuid id; QMap keyframeChannels; QScopedPointer opacityChannel; bool systemLocked; bool collapsed; bool supportsLodMoves; bool animated; bool pinnedToTimeline; KisImageWSP image; Private(KisImageWSP image) : id(QUuid::createUuid()) , systemLocked(false) , collapsed(false) , supportsLodMoves(false) , animated(false) , pinnedToTimeline(false) , image(image) { } Private(const Private &rhs) : compositeOp(rhs.compositeOp), id(QUuid::createUuid()), systemLocked(false), collapsed(rhs.collapsed), supportsLodMoves(rhs.supportsLodMoves), animated(rhs.animated), pinnedToTimeline(rhs.pinnedToTimeline), image(rhs.image) { QMapIterator iter = rhs.properties.propertyIterator(); while (iter.hasNext()) { iter.next(); properties.setProperty(iter.key(), iter.value()); } } }; KisBaseNode::KisBaseNode(KisImageWSP image) : m_d(new Private(image)) { /** * Be cautious! These two calls are vital to warm-up KoProperties. * We use it and its QMap in a threaded environment. This is not * officially supported by Qt, but our environment guarantees, that * there will be the only writer and several readers. Whilst the * value of the QMap is boolean and there are no implicit-sharing * calls provocated, it is safe to work with it in such an * environment. */ setVisible(true, true); setUserLocked(false); setCollapsed(false); setSupportsLodMoves(true); m_d->compositeOp = COMPOSITE_OVER; } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private(*rhs.m_d)) { if (rhs.m_d->keyframeChannels.size() > 0) { Q_FOREACH(QString key, rhs.m_d->keyframeChannels.keys()) { KisKeyframeChannel* channel = rhs.m_d->keyframeChannels.value(key); if (!channel) { continue; } if (channel->inherits("KisScalarKeyframeChannel")) { KisScalarKeyframeChannel* pchannel = qobject_cast(channel); KIS_ASSERT_RECOVER(pchannel) { continue; } KisScalarKeyframeChannel* channelNew = new KisScalarKeyframeChannel(*pchannel, nullptr); KIS_ASSERT(channelNew); m_d->keyframeChannels.insert(channelNew->id(), channelNew); if (KoID(key) == KisKeyframeChannel::Opacity) { m_d->opacityChannel.reset(channelNew); } } } } } KisBaseNode::~KisBaseNode() { delete m_d; } KisPaintDeviceSP KisBaseNode::colorPickSourceDevice() const { return projection(); } quint8 KisBaseNode::opacity() const { if (m_d->opacityChannel) { qreal value = m_d->opacityChannel->currentValue(); if (!qIsNaN(value)) { return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, val); } } if (opacity() == val) return; setNodeProperty("opacity", val); baseNodeInvalidateAllFramesCallback(); } quint8 KisBaseNode::percentOpacity() const { return int(float(opacity() * 100) / 255 + 0.5); } void KisBaseNode::setPercentOpacity(quint8 val) { setOpacity(int(float(val * 255) / 100 + 0.5)); } const QString& KisBaseNode::compositeOpId() const { return m_d->compositeOp; } void KisBaseNode::setCompositeOpId(const QString& compositeOp) { if (m_d->compositeOp == compositeOp) return; m_d->compositeOp = compositeOp; baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const { KisBaseNode::PropertyList l; l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis); l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked()); return l; } void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties) { setVisible(properties.at(0).state.toBool()); m_d->hack_visible = properties.at(0); setUserLocked(properties.at(1).state.toBool()); } const KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value) { m_d->properties.setProperty(name, value); baseNodeChangedCallback(); } void KisBaseNode::mergeNodeProperties(const KoProperties & properties) { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } bool KisBaseNode::check(const KoProperties & properties) const { QMapIterator iter = properties.propertyIterator(); while (iter.hasNext()) { iter.next(); if (m_d->properties.contains(iter.key())) { if (m_d->properties.value(iter.key()) != iter.value()) return false; } } return true; } -QImage KisBaseNode::createThumbnail(qint32 w, qint32 h) +QImage KisBaseNode::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { + Q_UNUSED(aspectRatioMode); + try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (const std::bad_alloc&) { return QImage(); } } -QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time) +QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode) { Q_UNUSED(time) + Q_UNUSED(aspectRatioMode); return createThumbnail(w, h); } bool KisBaseNode::visible(bool recursive) const { bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); KisBaseNodeSP parentNode = parentCallback(); return recursive && isVisible && parentNode ? parentNode->visible(recursive) : isVisible; } void KisBaseNode::setVisible(bool visible, bool loading) { const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true); if (!loading && isVisible == visible) return; m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible); notifyParentVisibilityChanged(visible); if (!loading) { baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } bool KisBaseNode::belongsToIsolatedGroup() const { if (!m_d->image) { return false; } const KisBaseNode* element = this; while (element) { if (element->isIsolatedRoot()) { return true; } else { element = element->parentCallback().data(); } } return false; } bool KisBaseNode::isIsolatedRoot() const { if (!m_d->image) { return false; } const KisBaseNode* isolatedRoot = m_d->image->isolatedModeRoot().data(); return (this == isolatedRoot); } void KisBaseNode::setUserLocked(bool locked) { const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true); if (isLocked == locked) return; m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked); baseNodeChangedCallback(); } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = ((visible(false) || belongsToIsolatedGroup()) && !userLocked()); } else { editable = (!userLocked()); } if (editable) { KisBaseNodeSP parentNode = parentCallback(); if (parentNode && parentNode != this) { editable = parentNode->isEditable(checkVisibility); } } return editable; } bool KisBaseNode::hasEditablePaintDevice() const { return paintDevice() && isEditable(); } void KisBaseNode::setCollapsed(bool collapsed) { const bool oldCollapsed = m_d->collapsed; m_d->collapsed = collapsed; if (oldCollapsed != collapsed) { baseNodeCollapsedChangedCallback(); } } bool KisBaseNode::collapsed() const { return m_d->collapsed; } void KisBaseNode::setColorLabelIndex(int index) { const int currentLabel = colorLabelIndex(); if (currentLabel == index) return; m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index); baseNodeChangedCallback(); } int KisBaseNode::colorLabelIndex() const { return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0); } QUuid KisBaseNode::uuid() const { return m_d->id; } void KisBaseNode::setUuid(const QUuid& id) { m_d->id = id; baseNodeChangedCallback(); } bool KisBaseNode::supportsLodMoves() const { return m_d->supportsLodMoves; } void KisBaseNode::setImage(KisImageWSP image) { m_d->image = image; } KisImageWSP KisBaseNode::image() const { return m_d->image; } bool KisBaseNode::isFakeNode() const { return false; } void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } QMap KisBaseNode::keyframeChannels() const { return m_d->keyframeChannels; } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } bool KisBaseNode::isPinnedToTimeline() const { return m_d->pinnedToTimeline; } void KisBaseNode::setPinnedToTimeline(bool pinned) { if (pinned == m_d->pinnedToTimeline) return; m_d->pinnedToTimeline = pinned; baseNodeChangedCallback(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); emit keyframeChannelAdded(channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisNode* node = dynamic_cast(this); KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, KisNodeWSP( node ), KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h index 818804ade7..4213ef73d8 100644 --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -1,608 +1,608 @@ /* * Copyright (c) 2007 Boudewijn Rempt * * 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_NODE_H #define _KIS_BASE_NODE_H #include #include #include #include #include #include "kis_shared.h" #include "kis_paint_device.h" #include "kis_processing_visitor.h" // included, not forward declared for msvc class KoProperties; class KoColorSpace; class KoCompositeOp; class KisNodeVisitor; class KisUndoAdapter; class KisKeyframeChannel; #include "kritaimage_export.h" /** * A KisBaseNode is the base class for all components of an image: * nodes, layers masks, selections. A node has a number of properties, * can be represented as a thumbnail and knows what to do when it gets * a certain paint device to process. A KisBaseNode does not know * anything about its peers. You should not directly inherit from a * KisBaseNode; inherit from KisNode instead. */ class KRITAIMAGE_EXPORT KisBaseNode : public QObject, public KisShared { Q_OBJECT public: /** * Describes a property of a document section. * * FIXME: using a QList instead of QMap and not having an untranslated identifier, * either enum or string, forces applications to rely on the order of properties * or to compare the translated strings. This makes it hard to robustly extend the * properties of document section items. */ struct Property { QString id; /** i18n-ed name, suitable for displaying */ QString name; /** Whether the property is a boolean (e.g. locked, visible) which can be toggled directly from the widget itself. */ bool isMutable; /** Provide these if the property isMutable. */ QIcon onIcon; QIcon offIcon; /** If the property isMutable, provide a boolean. Otherwise, a string suitable for displaying. */ QVariant state; /** If the property is mutable, specifies whether it can be put into stasis. When a property is in stasis, a new state is created, and the old one is stored in stateInStasis. When stasis ends, the old value is restored and the new one discarded */ bool canHaveStasis; /** If the property isMutable and canHaveStasis, indicate whether it is in stasis or not */ bool isInStasis; /** If the property isMutable and canHaveStasis, provide this value to store the property's state while in stasis */ bool stateInStasis; bool operator==(const Property &rhs) const { return rhs.name == name && rhs.state == state && isInStasis == rhs.isInStasis; } Property(): isMutable( false ), isInStasis(false) { } /// Constructor for a mutable property. Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn ) : id(n.id()), name( n.name() ), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( false ), isInStasis(false) { } /** Constructor for a mutable property accepting stasis */ Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn, bool _isInStasis, bool _stateInStasis = false ) : id(n.id()), name(n.name()), isMutable( true ), onIcon( on ), offIcon( off ), state( isOn ), canHaveStasis( true ), isInStasis( _isInStasis ), stateInStasis( _stateInStasis ) { } /// Constructor for a nonmutable property. Property( const KoID &n, const QString &s ) : id(n.id()), name(n.name()), isMutable( false ), state( s ), isInStasis(false) { } }; /** Return this type for PropertiesRole. */ typedef QList PropertyList; public: /** * Create a new, empty base node. The node is unnamed, unlocked * visible and unlinked. */ KisBaseNode(KisImageWSP image); /** * Create a copy of this node. */ KisBaseNode(const KisBaseNode & rhs); /** * Delete this node */ ~KisBaseNode() override; /** * Return the paintdevice you can use to change pixels on. For a * paint layer these will be paint pixels, for an adjustment layer or a mask * the selection paint device. * * @return the paint device to paint on. Can be 0 if the actual * node type does not support painting. */ virtual KisPaintDeviceSP paintDevice() const = 0; /** * @return the rendered representation of a node * before the effect masks have had their go at it. Can be 0. */ virtual KisPaintDeviceSP original() const = 0; /** * @return the fully rendered representation of this layer: its * rendered original and its effect masks. Can be 0. */ virtual KisPaintDeviceSP projection() const = 0; /** * @return a special device from where the color picker tool should pick * color when in layer-only mode. For most of the nodes just shortcuts * to projection() device. TODO: can it be null? */ virtual KisPaintDeviceSP colorPickSourceDevice() const; virtual const KoColorSpace *colorSpace() const = 0; /** * Return the opacity of this layer, scaled to a range between 0 * and 255. * XXX: Allow true float opacity */ quint8 opacity() const; //0-255 /** * Set the opacity for this layer. The range is between 0 and 255. * The layer will be marked dirty. * * XXX: Allow true float opacity */ void setOpacity(quint8 val); //0-255 /** * return the 8-bit opacity of this layer scaled to the range * 0-100 * * XXX: Allow true float opacity */ quint8 percentOpacity() const; //0-100 /** * Set the opacity of this layer with a number between 0 and 100; * the number will be scaled to between 0 and 255. * XXX: Allow true float opacity */ void setPercentOpacity(quint8 val); //0-100 /** * Return the composite op associated with this layer. */ virtual const KoCompositeOp *compositeOp() const = 0; const QString& compositeOpId() const; /** * Set a new composite op for this layer. The layer will be marked * dirty. */ void setCompositeOpId(const QString& compositeOpId); /** * @return unique id, which is now used by clone layers. */ QUuid uuid() const; /** * Set the uuid of node. This should only be used when loading * existing node and in constructor. */ void setUuid(const QUuid& id); /** * return the name of this node. This is the same as the * QObject::objectName. */ QString name() const { return objectName(); } /** * set the QObject::objectName. This is also the user-visible name * of the layer. The reason for this is that we want to see the * layer name also when debugging. */ void setName(const QString& name) { setObjectName(name); baseNodeChangedCallback(); } /** * @return the icon used to represent the node type, for instance * in the layerbox and in the menu. */ virtual QIcon icon() const { return QIcon(); } /** * Return a the properties of this base node (locked, visible etc, * with the right icons for their representation and their state. * * Subclasses can extend this list with new properties, like * opacity for layers or visualized for masks. * * The order of properties is, unfortunately, for now, important, * so take care which properties superclasses of your class * define. * * KisBaseNode defines visible = 0, locked = 1 * KisLayer defines opacity = 2, compositeOp = 3 * KisMask defines active = 2 (KisMask does not inherit kislayer) */ virtual PropertyList sectionModelProperties() const; /** * Change the section model properties. */ virtual void setSectionModelProperties(const PropertyList &properties); /** * Return all the properties of this layer as a KoProperties-based * serializable key-value list. */ const KoProperties & nodeProperties() const; /** * Set a node property. * @param name name of the property to be set. * @param value value to set the property to. */ void setNodeProperty(const QString & name, const QVariant & value); /** * Merge the specified properties with the properties of this * layer. Wherever these properties overlap, the value of the * node properties is changed. No properties on the node are * deleted. If there are new properties in this list, they will be * added on the node. */ void mergeNodeProperties(const KoProperties & properties); /** * Compare the given properties list with the properties of this * node. * * @return false only if the same property exists in both lists * but with a different value. Properties that are not in both * lists are disregarded. */ bool check(const KoProperties & properties) const; /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisNodeVisitor for this * node type, so you need to override it for all leaf classes in * the node inheritance hierarchy. * * return false if the visitor could not successfully act on this * node instance. */ virtual bool accept(KisNodeVisitor &) { return false; } /** * Accept the KisNodeVisitor (for the Visitor design pattern), * should call the correct function on the KisProcessingVisitor * for this node type, so you need to override it for all leaf * classes in the node inheritance hierarchy. * * The processing visitor differs from node visitor in the way * that it accepts undo adapter, that allows the processing to * be multithreaded */ virtual void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) { Q_UNUSED(visitor); Q_UNUSED(undoAdapter); } /** * @return a thumbnail in requested size. The thumbnail is a rgba * QImage and may have transparent parts. Returns a fully * transparent QImage of the requested size if the current node * type cannot generate a thumbnail. If the requested size is too * big, return a null QImage. */ - virtual QImage createThumbnail(qint32 w, qint32 h); + virtual QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio); /** * @return a thumbnail in requested size for the defined timestamp. * The thumbnail is a rgba Image and may have transparent parts. * Returns a fully transparent QImage of the requested size if the * current node type cannot generate a thumbnail. If the requested * size is too big, return a null QImage. */ - virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time); + virtual QImage createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio); /** * Ask this node to re-read the pertinent settings from the krita * configuration. */ virtual void updateSettings() { } /** * @return true if this node is visible (i.e, active (except for * selection masks where visible and active properties are * different)) in the graph * * @param bool recursive if true, check whether all parents of * this node are visible as well. */ virtual bool visible(bool recursive = false) const; /** * Set the visible status of this node. Visible nodes are active * in the graph (except for selections masks which can be active * while hidden), that is to say, they are taken into account * when merging. Invisible nodes play no role in the final image *, but will be modified when modifying all layers, for instance * when cropping. * * Toggling the visibility of a node will not automatically lead * to recomposition. * * @param visible the new visibility state * @param isLoading if true, the property is set during loading. */ virtual void setVisible(bool visible, bool loading = false); /** * Return the locked status of this node. Locked nodes cannot be * edited. */ bool userLocked() const; /** * Return whether or not the given node is isolated. */ bool belongsToIsolatedGroup() const; /** * Return whether or not the given node is the root of * isolation. */ bool isIsolatedRoot() const; /** * Set the locked status of this node. Locked nodes cannot be * edited. */ virtual void setUserLocked(bool l); /** * @return true if the node can be edited: * * if checkVisibility is true, then the node is only editable if it is visible and not locked. * if checkVisibility is false, then the node is editable if it's not locked. */ bool isEditable(bool checkVisibility = true) const; /** * @return true if the node is editable and has a paintDevice() * which which can be used for accessing pixels. It is an * equivalent to (isEditable() && paintDevice()) */ bool hasEditablePaintDevice() const; /** * @return the x-offset of this layer in the image plane. */ virtual qint32 x() const { return 0; } /** * Set the x offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setX(qint32) { } /** * @return the y-offset of this layer in the image plane. */ virtual qint32 y() const { return 0; } /** * Set the y offset of this layer in the image place. * Re-implement this where it makes sense, by default it does * nothing. It should not move child nodes. */ virtual void setY(qint32) { } /** * Returns an approximation of where the bounds on actual data are * in this node. */ virtual QRect extent() const { return QRect(); } /** * Returns the exact bounds of where the actual data resides in * this node. */ virtual QRect exactBounds() const { return QRect(); } /** * Sets the state of the node to the value of @param collapsed */ void setCollapsed(bool collapsed); /** * returns the collapsed state of this node */ bool collapsed() const; /** * Sets a color label index associated to the layer. The actual * color of the label and the number of available colors is * defined by Krita GUI configuration. */ void setColorLabelIndex(int index); /** * \see setColorLabelIndex */ int colorLabelIndex() const; /** * Returns true if the offset of the node can be changed in a LodN * stroke. Currently, all the nodes except shape layers support that. */ bool supportsLodMoves() const; /** * Return the keyframe channels associated with this node * @return list of keyframe channels */ QMap keyframeChannels() const; /** * Get the keyframe channel with given id. * If the channel does not yet exist and the node supports the requested * channel, it will be created if create is true. * @param id internal name for channel * @param create attempt to create the channel if it does not exist yet * @return keyframe channel with the id, or null if not found */ KisKeyframeChannel *getKeyframeChannel(const QString &id, bool create); KisKeyframeChannel *getKeyframeChannel(const QString &id) const; /** * @return If true, node will be visible on animation timeline even when inactive. */ bool isPinnedToTimeline() const; /** * Set whether node should be visible on animation timeline even when inactive. */ void setPinnedToTimeline(bool pinned); bool isAnimated() const; void enableAnimation(); virtual void setImage(KisImageWSP image); KisImageWSP image() const; /** * Fake node is not present in the layer stack and is not used * for normal projection rendering algorithms. */ virtual bool isFakeNode() const; protected: void setSupportsLodMoves(bool value); /** * FIXME: This method is a workaround for getting parent node * on a level of KisBaseNode. In fact, KisBaseNode should inherit * KisNode (in terms of current Krita) to be able to traverse * the node stack */ virtual KisBaseNodeSP parentCallback() const { return KisBaseNodeSP(); } virtual void notifyParentVisibilityChanged(bool value) { Q_UNUSED(value); } /** * This callback is called when some meta state of the base node * that can be interesting to the UI has changed. E.g. visibility, * lockness, opacity, compositeOp and etc. This signal is * forwarded by the KisNode and KisNodeGraphListener to the model * in KisLayerBox, so it can update its controls when information * changes. */ virtual void baseNodeChangedCallback() { } /** * This callback is called when collapsed state of the base node * has changed. This signal is forwarded by the KisNode and * KisNodeGraphListener to the model in KisLayerBox, so it can * update its controls when information changes. */ virtual void baseNodeCollapsedChangedCallback() { } virtual void baseNodeInvalidateAllFramesCallback() { } /** * Add a keyframe channel for this node. The channel will be added * to the common hash table which will be available to the UI. * * WARNING: the \p channel object *NOT* become owned by the node! * The caller must ensure manually that the lifetime of * the object coincide with the lifetime of the node. */ virtual void addKeyframeChannel(KisKeyframeChannel* channel); /** * Attempt to create the requested channel. Used internally by getKeyframeChannel. * Subclasses should implement this method to catch any new channel types they support. * @param id channel to create * @return newly created channel or null */ virtual KisKeyframeChannel * requestKeyframeChannel(const QString &id); Q_SIGNALS: void keyframeChannelAdded(KisKeyframeChannel *channel); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE( KisBaseNode::PropertyList ) #endif diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 1c326b894b..d4a0766823 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,1006 +1,1006 @@ /* * 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; KisLayerProjectionPlaneSP 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().dynamicCast(); 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() && !isIsolatedRoot() && !hasClones()) || !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) : KisAbstractProjectionPlaneSP(m_d->projectionPlane); } KisLayerProjectionPlaneSP 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::tightUserVisibleBounds() const { QRect changeRect = exactBounds(); /// we do not use incomingChangeRect() here, because /// exactBounds() already takes it into account (it /// was used while preparing original()) bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); return changeRect; } 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) +QImage KisLayer::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { if (w == 0 || h == 0) { return QImage(); } KisPaintDeviceSP originalDevice = original(); return originalDevice ? - originalDevice->createThumbnail(w, h, 1, + originalDevice->createThumbnail(w, h, aspectRatioMode, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } -QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time) +QImage KisLayer::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode) { 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, + return targetDevice->createThumbnail(w, h, aspectRatioMode, 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_layer.h b/libs/image/kis_layer.h index 0590aa6be5..7418f11ebe 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,426 +1,426 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005 C. Boemann * Copyright (c) 2007 Boudewijn Rempt * 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. */ #ifndef KIS_LAYER_H_ #define KIS_LAYER_H_ #include #include #include #include "kritaimage_export.h" #include "kis_base_node.h" #include "kis_types.h" #include "kis_node.h" #include "kis_psd_layer_style.h" template class QStack; class QBitArray; class KisCloneLayer; class KisPSDLayerStyle; class KisAbstractProjectionPlane; class KisLayerProjectionPlane; typedef QSharedPointer KisLayerProjectionPlaneSP; namespace KisMetaData { class Store; } /** * Abstract class that represents the concept of a Layer in Krita. This is not related * to the paint devices: this is merely an abstraction of how layers can be stacked and * rendered differently. * Regarding the previous-, first-, next- and lastChild() calls, first means that it the layer * is at the top of the group in the layerlist, using next will iterate to the bottom to last, * whereas previous will go up to first again. * * * TODO: Add a layer mode whereby the projection of the layer is used * as a clipping path? **/ class KRITAIMAGE_EXPORT KisLayer : public KisNode { Q_OBJECT public: /** * @param image is the pointer of the image or null * @param opacity is a value between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 **/ KisLayer(KisImageWSP image, const QString &name, quint8 opacity); KisLayer(const KisLayer& rhs); ~KisLayer() override; /// returns the image's colorSpace or null, if there is no image const KoColorSpace * colorSpace() const override; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const override; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ KisAbstractProjectionPlaneSP projectionPlane() const override; /** * The projection plane representing the layer itself without any * styles or anything else. It is used by the layer styles projection * plane to stack up the planes. */ virtual KisLayerProjectionPlaneSP internalProjectionPlane() const; QRect partialChangeRect(KisNodeSP lastNode, const QRect& rect); void buildProjectionUpToNode(KisPaintDeviceSP projection, KisNodeSP lastNode, const QRect& rect); virtual bool needProjection() const; /** * Return the fully rendered representation of this layer: its * data and its effect masks */ KisPaintDeviceSP projection() const override; /** * Return the layer data before the effect masks have had their go * at it. */ KisPaintDeviceSP original() const override = 0; /** * @return the selection associated with this layer, if there is * one. Otherwise, return 0; */ virtual KisSelectionMaskSP selectionMask() const; /** * @return the selection contained in the first KisSelectionMask associated * with this layer or the image, if either exists, otherwise, return 0. */ virtual KisSelectionSP selection() const; KisBaseNode::PropertyList sectionModelProperties() const override; void setSectionModelProperties(const KisBaseNode::PropertyList &properties) override; /** * set/unset the channel flag for the alpha channel of this layer */ void disableAlphaChannel(bool disable); /** * returns true if the channel flag for the alpha channel * of this layer is not set. * returns false otherwise. */ bool alphaChannelDisabled() const; /** * set the channelflags for this layer to the specified bit array. * The bit array must have exactly the same number of channels as * the colorspace this layer is in, or be empty, in which case all * channels are active. */ virtual void setChannelFlags(const QBitArray & channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is active or not. If the channelflags bit * array is empty, all channels are active. */ QBitArray & channelFlags() const; /** * Returns true if this layer is temporary: i.e., it should not * appear in the layerbox, even though it is temporarily in the * layer stack and taken into account on recomposition. */ bool temporary() const; /** * Set to true if this layer should not appear in the layerbox, * even though it is temporarily in the layer stack and taken into * account on recomposition. */ void setTemporary(bool t); /** * Set the image this layer belongs to. */ void setImage(KisImageWSP image) override; /** * Create and return a layer that is the result of merging * this with layer. * * This method is designed to be called only within KisImage::mergeLayerDown(). * * Decendands override this to create specific merged types when possible. * The KisLayer one creates a KisPaintLayerSP via a bitBlt, and can work on all layer types. * * Descendants that perform their own version do NOT call KisLayer::createMergedLayer */ virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clones should be informed about updates of the original * layer, so this is a way to register them */ void registerClone(KisCloneLayerWSP clone); /** * Deregisters the clone from the update list * * \see registerClone() */ void unregisterClone(KisCloneLayerWSP clone); /** * Return the list of the clones of this node. Be careful * with the list, because it is not thread safe. */ const QList registeredClones() const; /** * Returns whether we have a clone. * * Be careful with it. It is not thread safe to add/remove * clone while checking hasClones(). So there should be no updates. */ bool hasClones() const; /** * It is calles by the async merger after projection update is done */ void updateClones(const QRect &rect); /** * Informs this layers that its masks might have changed. */ void notifyChildMaskChanged(); public: qint32 x() const override; qint32 y() const override; void setX(qint32 x) override; void setY(qint32 y) override; /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const override; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const override; - QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; - QImage createThumbnailForFrame(qint32 w, qint32 h, int time) override; + QImage createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; /** * Return a tight rectange, where the contents of the layer * is placed from user's point of view. This rectangle includes * all the masks and effects the layer has (excluding layer * styles, they report their bounds via projection plane). */ QRect tightUserVisibleBounds() const; public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ QList effectMasks() const; /** * @return the list of effect masks up to a certain node */ QList effectMasks(KisNodeSP lastNode) const; /** * Get the group layer that contains this layer. */ KisLayerSP parentLayer() const; /** * @return the metadata object associated with this object. */ KisMetaData::Store* metaData(); protected: // override from KisNode QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; void childNodeChanged(KisNodeSP changedChildNode) override; 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. * * 1) incomingChangeRect(rect) chande rect transformation * performed by the transformations done basing on global * projection. It is performed in KisAsyncMerger + * KisUpdateOriginalVisitor classes. It happens before data * coming to KisLayer::original() therefore it is * 'incoming'. See KisAdjustmentLayer for example of usage. * * 2) outgoingChangeRect(rect) change rect transformation that * happens in KisLayer::copyOriginalToProjection(). It applies * *only* when the layer is 'filthy', that is was the cause of * the merge process. See KisCloneLayer for example of usage. * * The flow of changed areas can be illustrated in the * following way: * * 1. Current projection of size R1 is stored in KisAsyncMerger::m_currentProjection * | * | <-- KisUpdateOriginalVisitor writes data into layer's original() device. * | The changed area on KisLayer::original() is * | R2 = KisLayer::incomingChangeRect(R1) * | * 2. KisLayer::original() / changed rect: R2 * | * | <-- KisLayer::updateProjection() starts composing a layer * | It calls KisLayer::copyOriginalToProjection() which copies some area * | to a temporaty device. The temporary device now stores * | R3 = KisLayer::outgoingChangeRect(R2) * | * 3. Temporary device / changed rect: R3 * | * | <-- KisLayer::updateProjection() continues composing a layer. It merges a mask. * | R4 = KisMask::changeRect(R3) * | * 4. KisLayer::original() / changed rect: R4 * * So in the end rect R4 will be passed up to the next layers in the stack. */ virtual QRect incomingChangeRect(const QRect &rect) const; /** * \see incomingChangeRect() */ virtual QRect outgoingChangeRect(const QRect &rect) const; /** * Return need rect that should be prepared on original() * device of the layer to get \p rect on its projection. * * This method is used either for layers that can have other * layers as children (yes, KisGroupLayer, I'm looking at you!), * or for layers that depend on the lower nodes (it's you, * KisAdjustmentLayer!). * * These layers may have some filter masks that need a bit * more pixels than requested, therefore child nodes should do * a bit more work to prepare them. */ QRect needRectForOriginal(const QRect &rect) const; /** * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return an area that should be updated because of * the change of @requestedRect of the layer */ QRect masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const; /** * Get needRects for all masks * @param changeRect requested rect to be updated on final * projection. Should be a return value * of @ref masksChangedRect() * @param applyRects (out param) a stack of the rects where filters * should be applied * @param rectVariesFlag (out param) a flag, showing whether * a rect varies from mask to mask * @return a needRect that should be prepared on the layer's * paintDevice for all masks to succeed */ QRect masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const; QRect applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const; bool canMergeAndKeepBlendOptions(KisLayerSP otherLayer); QList searchEffectMasks(KisNodeSP lastNode) const; private: friend class KisLayerMasksCache; friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: QRect layerExtentImpl(bool exactBounds) const; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_mask.cc b/libs/image/kis_mask.cc index 71743c2c7b..4d7a36bfef 100644 --- a/libs/image/kis_mask.cc +++ b/libs/image/kis_mask.cc @@ -1,513 +1,513 @@ /* * 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); 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)); 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)); KisPixelSelectionSP pixelSelection = selection->pixelSelection(); if (pixelSelection->framesInterface()) { KisRasterKeyframeChannel *keyframeChannel = pixelSelection->keyframeChannel(); keyframeChannel->setFilenameSuffix(".pixelselection"); q->addKeyframeChannel(keyframeChannel); q->enableAnimation(); } } else { selection = new KisSelection(new KisSelectionDefaultBounds(parentPaintDevice)); 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 { KisCachedPaintDevice::Guard d1(projection, m_d->paintDeviceCache); KisPaintDeviceSP cacheDevice = d1.device(); 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); } } 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) +QImage KisMask::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { KisPaintDeviceSP originalDevice = selection() ? selection()->projection() : 0; return originalDevice ? - originalDevice->createThumbnail(w, h, 1, + originalDevice->createThumbnail(w, h, aspectRatioMode, 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())); } 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(); } diff --git a/libs/image/kis_mask.h b/libs/image/kis_mask.h index 2c11ed079c..862f65b3b4 100644 --- a/libs/image/kis_mask.h +++ b/libs/image/kis_mask.h @@ -1,236 +1,236 @@ /* * 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.b */ #ifndef _KIS_MASK_ #define _KIS_MASK_ #include #include "kis_types.h" #include "kis_global.h" #include "kis_node.h" #include "kis_indirect_painting_support.h" #include /** KisMask is the base class for all single channel mask-like paint devices in Krita. Masks can be rendered in different ways at different moments during the rendering stack. Masks are "owned" by layers (of any type), and cannot occur by themselves on themselves. The properties that masks implement are made available through the iterators created on their parent layer, or through iterators that can be created on the paint device that holds the mask data: masks are just paint devices, too. Masks should show up in the layerbox as sub-layers for the layer they are associated with and be ccp'able and draggable to other layers. Examples of masks are: - filter masks: like the alpha filter mask that is the most common type of mask and is simply known as "mask" in the gui. Other filter masks use any of krita's filters to filter the pixels of their parent. (In this they differ from adjustment layers, which filter all layers under them in their group stack). - selections: the selection mask is rendered after composition and zooming and determines the selectedness of the pixels of the parent layer. - painterly overlays: painterly overlays indicate a particular property of the pixel in the parent paint device they are associated with, like wetness, height or gravity. XXX: For now, all masks are 8 bit. Make the channel depth settable. */ class KRITAIMAGE_EXPORT KisMask : public KisNode, public KisIndirectPaintingSupport { Q_OBJECT public: /** * Create a new KisMask. */ KisMask(const QString & name); /** * Copy the mask */ KisMask(const KisMask& rhs); ~KisMask() override; void setImage(KisImageWSP image) override; bool allowAsChild(KisNodeSP node) const override; /** * @brief initSelection initializes the selection for the mask from * the given selection's projection. * @param copyFrom the selection we base the mask on * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisSelectionSP copyFrom, KisLayerSP parentLayer); /** * @brief initSelection initializes the selection for the mask from * the given paint device. * @param copyFromDevice the paint device we base the mask on * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisPaintDeviceSP copyFromDevice, KisLayerSP parentLayer); /** * @brief initSelection initializes an empty selection * @param parentLayer the parent of this mask; it determines the default bounds of the mask. */ void initSelection(KisLayerSP parentLayer); const KoColorSpace * colorSpace() const override; const KoCompositeOp * compositeOp() const override; /** * Return the selection associated with this mask. A selection can * contain both a paint device and shapes. */ KisSelectionSP selection() const; /** * @return the selection: if you paint on mask, you paint on the selections */ KisPaintDeviceSP paintDevice() const override; /** * @return the same as paintDevice() */ KisPaintDeviceSP original() const override; /** * @return the same as paintDevice() */ KisPaintDeviceSP projection() const override; KisAbstractProjectionPlaneSP projectionPlane() const override; /** * Change the selection to the specified selection object. The * selection is deep copied. */ void setSelection(KisSelectionSP selection); /** * Selected the specified rect with the specified amount of selectedness. */ void select(const QRect & rc, quint8 selectedness = MAX_SELECTED); /** * The extent and bounds of the mask are those of the selection inside */ QRect extent() const override; QRect exactBounds() const override; /** * overridden from KisBaseNode */ qint32 x() const override; /** * overridden from KisBaseNode */ void setX(qint32 x) override; /** * overridden from KisBaseNode */ qint32 y() const override; /** * overridden from KisBaseNode */ void setY(qint32 y) override; /** * Usually masks themselves do not have any paint device and * all their final effect on the layer stack is computed using * the changeRect() of the dirty rect of the parent layer. Their * extent() and exectBounds() methods work the same way: by taking * the extent of the parent layer and computing the rect basing * on it. But some of the masks like Colorize Mask may have their * own "projection", which is painted independently from the changed * area of the parent layer. This additional "non-dependent" extent * is added to the extent of the parent layer. */ virtual QRect nonDependentExtent() const; QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; - QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; void testingInitSelection(const QRect &rect, KisLayerSP parentLayer); protected: /** * Apply the effect the projection using the mask as a selection. * Made public in KisEffectMask */ void apply(KisPaintDeviceSP projection, const QRect & applyRect, const QRect & needRect, PositionToFilthy maskPos) const; virtual void mergeInMaskInternal(KisPaintDeviceSP projection, KisSelectionSP effectiveSelection, const QRect &applyRect, const QRect &preparedNeedRect, PositionToFilthy maskPos) const; /** * A special callback for calling selection->updateProjection() during * the projection calculation process. Some masks (e.g. selection masks) * don't need it, because they do it separately. */ virtual void flattenSelectionProjection(KisSelectionSP selection, const QRect &dirtyRect) const; virtual QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const; virtual bool paintsOutsideSelection() const; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; void baseNodeChangedCallback() override; private: friend class KisMaskProjectionPlane; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc index fee2b41ce3..9580ea884d 100644 --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1,2265 +1,2276 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004 Boudewijn Rempt * * 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_paint_device.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_image.h" #include "kis_random_sub_accessor.h" #include "kis_selection.h" #include "kis_node.h" #include "kis_datamanager.h" #include "kis_paint_device_writer.h" #include "kis_selection_component.h" #include "kis_pixel_selection.h" #include "kis_repeat_iterators_pixel.h" #include "kis_fixed_paint_device.h" #include "tiles3/kis_hline_iterator.h" #include "tiles3/kis_vline_iterator.h" #include "tiles3/kis_random_accessor.h" #include "kis_default_bounds.h" #include "kis_lod_transform.h" #include "kis_raster_keyframe_channel.h" #include "kis_paint_device_cache.h" #include "kis_paint_device_data.h" #include "kis_paint_device_frames_interface.h" #include "kis_transform_worker.h" #include "kis_filter_strategy.h" #include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { KisPaintDeviceSPStaticRegistrar() { qRegisterMetaType("KisPaintDeviceSP"); } }; static KisPaintDeviceSPStaticRegistrar __registrar; struct KisPaintDevice::Private { /** * Used when the paint device is loading to ensure no lod/animation * interferes the process. */ static const KisDefaultBoundsSP transitionalDefaultBounds; public: class KisPaintDeviceStrategy; class KisPaintDeviceWrappedStrategy; class DeviceChangeProfileCommand; class DeviceChangeColorSpaceCommand; Private(KisPaintDevice *paintDevice); ~Private(); KisPaintDevice *q; KisNodeWSP parent; QScopedPointer contentChannel; KisDefaultBoundsBaseSP defaultBounds; QScopedPointer basicStrategy; QScopedPointer wrappedStrategy; QMutex m_wrappedStrategyMutex; QScopedPointer framesInterface; bool isProjectionDevice; KisPaintDeviceStrategy* currentStrategy(); void init(const KoColorSpace *cs, const quint8 *defaultPixel); void convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand); bool assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); KUndo2Command* reincarnateWithDetachedHistory(bool copyContent); inline const KoColorSpace* colorSpace() const { return currentData()->colorSpace(); } inline KisDataManagerSP dataManager() const { return currentData()->dataManager(); } inline qint32 x() const { return currentData()->x(); } inline qint32 y() const { return currentData()->y(); } inline void setX(qint32 x) { currentData()->setX(x); } inline void setY(qint32 y) { currentData()->setY(y); } inline KisPaintDeviceCache* cache() { return currentData()->cache(); } inline KisIteratorCompleteListener* cacheInvalidator() { return currentData()->cacheInvalidator(); } void cloneAllDataObjects(Private *rhs, bool copyFrames) { m_lodData.reset(); m_externalFrameData.reset(); if (!m_frames.isEmpty()) { m_frames.clear(); } if (!copyFrames) { if (m_data) { m_data->prepareClone(rhs->currentNonLodData(), true); } else { m_data = toQShared(new KisPaintDeviceData(q, rhs->currentNonLodData(), true)); } } else { if (m_data && !rhs->m_data) { m_data.clear(); } else if (!m_data && rhs->m_data) { m_data = toQShared(new KisPaintDeviceData(q, rhs->m_data.data(), true)); } else if (m_data && rhs->m_data) { m_data->prepareClone(rhs->m_data.data(), true); } if (!rhs->m_frames.isEmpty()) { FramesHash::const_iterator it = rhs->m_frames.constBegin(); FramesHash::const_iterator end = rhs->m_frames.constEnd(); for (; it != end; ++it) { DataSP data = toQShared(new KisPaintDeviceData(q, it.value().data(), true)); m_frames.insert(it.key(), data); } } m_nextFreeFrameId = rhs->m_nextFreeFrameId; } if (rhs->m_lodData) { m_lodData.reset(new KisPaintDeviceData(q, rhs->m_lodData.data(), true)); } } void prepareClone(KisPaintDeviceSP src) { prepareCloneImpl(src, src->m_d->currentData()); KIS_SAFE_ASSERT_RECOVER_NOOP(fastBitBltPossible(src)); } bool fastBitBltPossible(KisPaintDeviceSP src) { return fastBitBltPossibleImpl(src->m_d->currentData()); } int currentFrameId() const { KIS_ASSERT_RECOVER(contentChannel) { return -1; } return !defaultBounds->currentLevelOfDetail() ? contentChannel->frameIdAt(defaultBounds->currentTime()) : -1; } KisDataManagerSP frameDataManager(int frameId) const { DataSP data = m_frames[frameId]; return data->dataManager(); } void invalidateFrameCache(int frameId) { DataSP data = m_frames[frameId]; return data->cache()->invalidate(); } private: typedef KisPaintDeviceData Data; typedef QSharedPointer DataSP; typedef QHash FramesHash; class FrameInsertionCommand : public KUndo2Command { public: FrameInsertionCommand(FramesHash *hash, DataSP data, int frameId, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_hash(hash), m_data(data), m_frameId(frameId), m_insert(insert) { } void redo() override { doSwap(m_insert); } void undo() override { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_hash->insert(m_frameId, m_data); } else { DataSP deletedData = m_hash->take(m_frameId); } } private: FramesHash *m_hash; DataSP m_data; int m_frameId; bool m_insert; }; public: int getNextFrameId() { int frameId = 0; while (m_frames.contains(frameId = m_nextFreeFrameId++)); KIS_SAFE_ASSERT_RECOVER_NOOP(!m_frames.contains(frameId)); return frameId; } int createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER(parentCommand) { return -1; } DataSP data; bool initialFrame = false; if (m_frames.isEmpty()) { /** * Here we move the contents of the paint device to the * new frame and clear m_data to make the "background" for * the areas where there is no frame at all. */ data = toQShared(new Data(q, m_data.data(), true)); m_data->dataManager()->clear(); m_data->cache()->invalidate(); initialFrame = true; } else if (copy) { DataSP srcData = m_frames[copySrc]; data = toQShared(new Data(q, srcData.data(), true)); } else { DataSP srcData = m_frames.begin().value(); data = toQShared(new Data(q, srcData.data(), false)); } if (!initialFrame && !copy) { data->setX(offset.x()); data->setY(offset.y()); } int frameId = getNextFrameId(); KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, data, frameId, true, parentCommand); cmd->redo(); return frameId; } void deleteFrame(int frame, KUndo2Command *parentCommand) { KIS_ASSERT_RECOVER_RETURN(m_frames.contains(frame)); KIS_ASSERT_RECOVER_RETURN(parentCommand); DataSP deletedData = m_frames[frame]; KUndo2Command *cmd = new FrameInsertionCommand(&m_frames, deletedData, frame, false, parentCommand); cmd->redo(); } QRect frameBounds(int frameId) { DataSP data = m_frames[frameId]; QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); return extent; } QPoint frameOffset(int frameId) const { DataSP data = m_frames[frameId]; return QPoint(data->x(), data->y()); } void setFrameOffset(int frameId, const QPoint &offset) { DataSP data = m_frames[frameId]; data->setX(offset.x()); data->setY(offset.y()); } const QList frameIds() const { return m_frames.keys(); } bool readFrame(QIODevice *stream, int frameId) { bool retval = false; DataSP data = m_frames[frameId]; retval = data->dataManager()->read(stream); data->cache()->invalidate(); return retval; } bool writeFrame(KisPaintDeviceWriter &store, int frameId) { DataSP data = m_frames[frameId]; return data->dataManager()->write(store); } void setFrameDefaultPixel(const KoColor &defPixel, int frameId) { DataSP data = m_frames[frameId]; KoColor color(defPixel); color.convertTo(data->colorSpace()); data->dataManager()->setDefaultPixel(color.data()); } KoColor frameDefaultPixel(int frameId) const { DataSP data = m_frames[frameId]; return KoColor(data->dataManager()->defaultPixel(), data->colorSpace()); } void fetchFrame(int frameId, KisPaintDeviceSP targetDevice); void uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice); void uploadFrameData(DataSP srcData, DataSP dstData); struct LodDataStructImpl; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); KisRegion regionForLodSyncing() const; void updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: qint64 estimateDataSize(Data *data) const { const QRect &rc = data->dataManager()->extent(); return rc.width() * rc.height() * data->colorSpace()->pixelSize(); } public: void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { imageData = 0; temporaryData = 0; lodData = 0; if (m_data) { imageData += estimateDataSize(m_data.data()); } if (m_lodData) { lodData += estimateDataSize(m_lodData.data()); } if (m_externalFrameData) { temporaryData += estimateDataSize(m_externalFrameData.data()); } Q_FOREACH (DataSP value, m_frames.values()) { imageData += estimateDataSize(value.data()); } } private: inline DataSP currentFrameData() const { DataSP data; const int numberOfFrames = contentChannel->keyframeCount(); if (numberOfFrames > 1) { int frameId = contentChannel->frameIdAt(defaultBounds->currentTime()); if (frameId == -1) { data = m_data; } else { KIS_ASSERT_RECOVER(m_frames.contains(frameId)) { return m_frames.begin().value(); } data = m_frames[frameId]; } } else if (numberOfFrames == 1) { data = m_frames.begin().value(); } else { data = m_data; } return data; } inline Data* currentNonLodData() const { Data *data = m_data.data(); if (contentChannel) { data = currentFrameData().data(); } else if (isProjectionDevice && defaultBounds->externalFrameActive()) { if (!m_externalFrameData) { QMutexLocker l(&m_dataSwitchLock); if (!m_externalFrameData) { m_externalFrameData.reset(new Data(q, m_data.data(), false)); } } data = m_externalFrameData.data(); } return data; } inline void ensureLodDataPresent() const { if (!m_lodData) { Data *srcData = currentNonLodData(); QMutexLocker l(&m_dataSwitchLock); if (!m_lodData) { m_lodData.reset(new Data(q, srcData, false)); } } } inline Data* currentData() const { Data *data; if (defaultBounds->currentLevelOfDetail()) { ensureLodDataPresent(); data = m_lodData.data(); } else { data = currentNonLodData(); } return data; } void prepareCloneImpl(KisPaintDeviceSP src, Data *srcData) { /** * The result of currentData() depends on the current * level of detail and animation frame index. So we * should first connect the device to the new * default bounds object, and only after that ask * currentData() to start cloning. */ q->setDefaultPixel(KoColor(srcData->dataManager()->defaultPixel(), colorSpace())); q->setDefaultBounds(src->defaultBounds()); currentData()->prepareClone(srcData); } bool fastBitBltPossibleImpl(Data *srcData) { return x() == srcData->x() && y() == srcData->y() && *colorSpace() == *srcData->colorSpace(); } QList allDataObjects() const { QList dataObjects; if (m_frames.isEmpty()) { dataObjects << m_data.data(); } dataObjects << m_lodData.data(); dataObjects << m_externalFrameData.data(); Q_FOREACH (DataSP value, m_frames.values()) { dataObjects << value.data(); } return dataObjects; } void transferFromData(Data *data, KisPaintDeviceSP targetDevice); struct Q_DECL_HIDDEN StrategyPolicy; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialConstIterator; typedef KisSequentialIteratorBase, StrategyPolicy> InternalSequentialIterator; private: friend class KisPaintDeviceFramesInterface; private: DataSP m_data; mutable QScopedPointer m_lodData; mutable QScopedPointer m_externalFrameData; mutable QMutex m_dataSwitchLock; FramesHash m_frames; int m_nextFreeFrameId; }; const KisDefaultBoundsSP KisPaintDevice::Private::transitionalDefaultBounds = new KisDefaultBounds(); #include "kis_paint_device_strategies.h" KisPaintDevice::Private::Private(KisPaintDevice *paintDevice) : q(paintDevice), basicStrategy(new KisPaintDeviceStrategy(paintDevice, this)), isProjectionDevice(false), m_data(new Data(paintDevice)), m_nextFreeFrameId(0) { } KisPaintDevice::Private::~Private() { m_frames.clear(); } KisPaintDevice::Private::KisPaintDeviceStrategy* KisPaintDevice::Private::currentStrategy() { if (!defaultBounds->wrapAroundMode()) { return basicStrategy.data(); } const QRect wrapRect = defaultBounds->bounds(); if (!wrappedStrategy || wrappedStrategy->wrapRect() != wrapRect) { QMutexLocker locker(&m_wrappedStrategyMutex); if (!wrappedStrategy) { wrappedStrategy.reset(new KisPaintDeviceWrappedStrategy(wrapRect, q, this)); } else if (wrappedStrategy->wrapRect() != wrapRect) { wrappedStrategy->setWrapRect(wrapRect); } } return wrappedStrategy.data(); } struct KisPaintDevice::Private::StrategyPolicy { StrategyPolicy(KisPaintDevice::Private::KisPaintDeviceStrategy *strategy, KisDataManager *dataManager, qint32 offsetX, qint32 offsetY) : m_strategy(strategy), m_dataManager(dataManager), m_offsetX(offsetX), m_offsetY(offsetY) { } KisHLineConstIteratorSP createConstIterator(const QRect &rect) { return m_strategy->createHLineConstIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } KisHLineIteratorSP createIterator(const QRect &rect) { return m_strategy->createHLineIteratorNG(m_dataManager, rect.x(), rect.y(), rect.width(), m_offsetX, m_offsetY); } int pixelSize() const { return m_dataManager->pixelSize(); } KisPaintDeviceStrategy *m_strategy; KisDataManager *m_dataManager; int m_offsetX; int m_offsetY; }; struct KisPaintDevice::Private::LodDataStructImpl : public KisPaintDevice::LodDataStruct { LodDataStructImpl(Data *_lodData) : lodData(_lodData) {} QScopedPointer lodData; }; KisRegion KisPaintDevice::Private::regionForLodSyncing() const { Data *srcData = currentNonLodData(); return srcData->dataManager()->region().translated(srcData->x(), srcData->y()); } KisPaintDevice::LodDataStruct* KisPaintDevice::Private::createLodDataStruct(int newLod) { KIS_SAFE_ASSERT_RECOVER_NOOP(newLod > 0); Data *srcData = currentNonLodData(); Data *lodData = new Data(q, srcData, false); LodDataStruct *lodStruct = new LodDataStructImpl(lodData); int expectedX = KisLodTransform::coordToLodCoord(srcData->x(), newLod); int expectedY = KisLodTransform::coordToLodCoord(srcData->y(), newLod); /** * We compare color spaces as pure pointers, because they must be * exactly the same, since they come from the common source. */ if (lodData->levelOfDetail() != newLod || lodData->colorSpace() != srcData->colorSpace() || lodData->x() != expectedX || lodData->y() != expectedY) { lodData->prepareClone(srcData); lodData->setLevelOfDetail(newLod); lodData->setX(expectedX); lodData->setY(expectedY); // FIXME: different kind of synchronization } lodData->cache()->invalidate(); return lodStruct; } void KisPaintDevice::Private::updateLodDataManager(KisDataManager *srcDataManager, KisDataManager *dstDataManager, const QPoint &srcOffset, const QPoint &dstOffset, const QRect &originalRect, int lod) { if (originalRect.isEmpty()) return; const int srcStepSize = 1 << lod; KIS_ASSERT_RECOVER_RETURN(lod > 0); const QRect srcRect = KisLodTransform::alignedRect(originalRect, lod); const QRect dstRect = KisLodTransform::scaledRect(srcRect, lod); if (!srcRect.isValid() || !dstRect.isValid()) return; KIS_ASSERT_RECOVER_NOOP(srcRect.width() / srcStepSize == dstRect.width()); const int pixelSize = srcDataManager->pixelSize(); int rowsAccumulated = 0; int columnsAccumulated = 0; KoMixColorsOp *mixOp = colorSpace()->mixColorsOp(); QScopedArrayPointer blendData(new quint8[srcStepSize * srcRect.width() * pixelSize]); quint8 *blendDataPtr = blendData.data(); int blendDataOffset = 0; const int srcCellSize = srcStepSize * srcStepSize; const int srcCellStride = srcCellSize * pixelSize; const int srcStepStride = srcStepSize * pixelSize; const int srcColumnStride = (srcStepSize - 1) * srcStepStride; QScopedArrayPointer weights(new qint16[srcCellSize]); { const qint16 averageWeight = qCeil(255.0 / srcCellSize); const qint16 extraWeight = averageWeight * srcCellSize - 255; KIS_ASSERT_RECOVER_NOOP(extraWeight == 1); for (int i = 0; i < srcCellSize - 1; i++) { weights[i] = averageWeight; } weights[srcCellSize - 1] = averageWeight - extraWeight; } InternalSequentialConstIterator srcIntIt(StrategyPolicy(currentStrategy(), srcDataManager, srcOffset.x(), srcOffset.y()), srcRect); InternalSequentialIterator dstIntIt(StrategyPolicy(currentStrategy(), dstDataManager, dstOffset.x(), dstOffset.y()), dstRect); int rowsRemaining = srcRect.height(); while (rowsRemaining > 0) { int colsRemaining = srcRect.width(); while (colsRemaining > 0 && srcIntIt.nextPixel()) { memcpy(blendDataPtr, srcIntIt.rawDataConst(), pixelSize); blendDataPtr += pixelSize; columnsAccumulated++; if (columnsAccumulated >= srcStepSize) { blendDataPtr += srcColumnStride; columnsAccumulated = 0; } colsRemaining--; } rowsAccumulated++; if (rowsAccumulated >= srcStepSize) { // blend and write the final data blendDataPtr = blendData.data(); int colsRemaining = dstRect.width(); while (colsRemaining > 0 && dstIntIt.nextPixel()) { mixOp->mixColors(blendDataPtr, weights.data(), srcCellSize, dstIntIt.rawData()); blendDataPtr += srcCellStride; colsRemaining--; } // reset counters rowsAccumulated = 0; blendDataPtr = blendData.data(); blendDataOffset = 0; } else { blendDataOffset += srcStepStride; blendDataPtr = blendData.data() + blendDataOffset; } rowsRemaining--; } } void KisPaintDevice::Private::updateLodDataStruct(LodDataStruct *_dst, const QRect &originalRect) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); Data *lodData = dst->lodData.data(); Data *srcData = currentNonLodData(); const int lod = lodData->levelOfDetail(); updateLodDataManager(srcData->dataManager().data(), lodData->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(lodData->x(), lodData->y()), originalRect, lod); } void KisPaintDevice::Private::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { KIS_SAFE_ASSERT_RECOVER_RETURN(fastBitBltPossible(dst)); Data *srcData = currentNonLodData(); updateLodDataManager(srcData->dataManager().data(), dst->dataManager().data(), QPoint(srcData->x(), srcData->y()), QPoint(dst->x(), dst->y()), originalRect, lod); } void KisPaintDevice::Private::uploadLodDataStruct(LodDataStruct *_dst) { LodDataStructImpl *dst = dynamic_cast(_dst); KIS_SAFE_ASSERT_RECOVER_RETURN(dst); KIS_SAFE_ASSERT_RECOVER_RETURN( dst->lodData->levelOfDetail() == defaultBounds->currentLevelOfDetail()); ensureLodDataPresent(); m_lodData->prepareClone(dst->lodData.data()); m_lodData->dataManager()->bitBltRough(dst->lodData->dataManager(), dst->lodData->dataManager()->extent()); } void KisPaintDevice::Private::transferFromData(Data *data, KisPaintDeviceSP targetDevice) { QRect extent = data->dataManager()->extent(); extent.translate(data->x(), data->y()); targetDevice->m_d->prepareCloneImpl(q, data); targetDevice->m_d->currentStrategy()->fastBitBltRough(data->dataManager(), extent); } void KisPaintDevice::Private::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { DataSP data = m_frames[frameId]; transferFromData(data.data(), targetDevice); } void KisPaintDevice::Private::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_frames[srcFrameId]; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { DataSP dstData = m_frames[dstFrameId]; KIS_ASSERT_RECOVER_RETURN(dstData); DataSP srcData = srcDevice->m_d->m_data; KIS_ASSERT_RECOVER_RETURN(srcData); uploadFrameData(srcData, dstData); } void KisPaintDevice::Private::uploadFrameData(DataSP srcData, DataSP dstData) { if (srcData->colorSpace() != dstData->colorSpace() && *srcData->colorSpace() != *dstData->colorSpace()) { KUndo2Command tempCommand; srcData = toQShared(new Data(q, srcData.data(), true)); srcData->convertDataColorSpace(dstData->colorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags(), &tempCommand); } dstData->dataManager()->clear(); dstData->cache()->invalidate(); const QRect rect = srcData->dataManager()->extent(); dstData->dataManager()->bitBltRough(srcData->dataManager(), rect); dstData->setX(srcData->x()); dstData->setY(srcData->y()); } void KisPaintDevice::Private::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { Data *data = m_lodData.data(); Q_ASSERT(data); transferFromData(data, targetDevice); } class KisPaintDevice::Private::DeviceChangeProfileCommand : public KUndo2Command { public: DeviceChangeProfileCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) : KUndo2Command(parent), m_device(device) { } virtual void emitNotifications() { m_device->emitProfileChanged(); } void redo() override { if (m_firstRun) { m_firstRun = false; return; } KUndo2Command::redo(); emitNotifications(); } void undo() override { KUndo2Command::undo(); emitNotifications(); } protected: KisPaintDeviceSP m_device; private: bool m_firstRun {true}; }; class KisPaintDevice::Private::DeviceChangeColorSpaceCommand : public DeviceChangeProfileCommand { public: DeviceChangeColorSpaceCommand(KisPaintDeviceSP device, KUndo2Command *parent = 0) : DeviceChangeProfileCommand(device, parent) { } void emitNotifications() override { m_device->emitColorSpaceChanged(); } }; void KisPaintDevice::Private::convertColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { QList dataObjects = allDataObjects(); if (dataObjects.isEmpty()) return; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->convertDataColorSpace(dstColorSpace, renderingIntent, conversionFlags, mainCommand); } q->emitColorSpaceChanged(); } bool KisPaintDevice::Private::assignProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { if (!profile) return false; const KoColorSpace *dstColorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); if (!dstColorSpace) return false; KUndo2Command *mainCommand = parentCommand ? new DeviceChangeColorSpaceCommand(q, parentCommand) : 0; QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; data->assignColorSpace(dstColorSpace, mainCommand); } q->emitProfileChanged(); // no undo information is provided here return true; } KUndo2Command *KisPaintDevice::Private::reincarnateWithDetachedHistory(bool copyContent) { KUndo2Command *mainCommand = new KUndo2Command(); currentData()->reincarnateWithDetachedHistory(copyContent, mainCommand); return mainCommand; } void KisPaintDevice::Private::init(const KoColorSpace *cs, const quint8 *defaultPixel) { QList dataObjects = allDataObjects(); Q_FOREACH (Data *data, dataObjects) { if (!data) continue; KisDataManagerSP dataManager = new KisDataManager(cs->pixelSize(), defaultPixel); data->init(cs, dataManager); } } KisPaintDevice::KisPaintDevice(const KoColorSpace * colorSpace, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, new KisDefaultBounds(), 0, name); } KisPaintDevice::KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds, const QString& name) : QObject(0) , m_d(new Private(this)) { init(colorSpace, defaultBounds, parent, name); } void KisPaintDevice::init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name) { Q_ASSERT(colorSpace); setObjectName(name); // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; if (!defaultBounds) { // Reuse transitionalDefaultBounds here. Change if you change // semantics of transitionalDefaultBounds defaultBounds = m_d->transitionalDefaultBounds; } QScopedArrayPointer defaultPixel(new quint8[colorSpace->pixelSize()]); colorSpace->fromQColor(Qt::transparent, defaultPixel.data()); m_d->init(colorSpace, defaultPixel.data()); Q_ASSERT(m_d->colorSpace()); setDefaultBounds(defaultBounds); setParentNode(parent); } KisPaintDevice::KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) : QObject() , KisShared() , m_d(new Private(this)) { if (this != &rhs) { makeFullCopyFrom(rhs, copyMode, newParentNode); } } void KisPaintDevice::makeFullCopyFrom(const KisPaintDevice &rhs, KritaUtils::DeviceCopyMode copyMode, KisNode *newParentNode) { // temporary def. bounds object for the initialization phase only m_d->defaultBounds = m_d->transitionalDefaultBounds; // copy data objects with or without frames m_d->cloneAllDataObjects(rhs.m_d, copyMode == KritaUtils::CopyAllFrames); if (copyMode == KritaUtils::CopyAllFrames && rhs.m_d->framesInterface) { KIS_ASSERT_RECOVER_RETURN(rhs.m_d->framesInterface); KIS_ASSERT_RECOVER_RETURN(rhs.m_d->contentChannel); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); m_d->contentChannel.reset(new KisRasterKeyframeChannel(*rhs.m_d->contentChannel.data(), newParentNode, this)); } setDefaultBounds(rhs.m_d->defaultBounds); setParentNode(newParentNode); } KisPaintDevice::~KisPaintDevice() { delete m_d; } void KisPaintDevice::setProjectionDevice(bool value) { m_d->isProjectionDevice = value; } void KisPaintDevice::prepareClone(KisPaintDeviceSP src) { m_d->prepareClone(src); } void KisPaintDevice::makeCloneFrom(KisPaintDeviceSP src, const QRect &rect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = rect & src->extent(); fastBitBlt(src, optimizedRect); } void KisPaintDevice::makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect) { prepareClone(src); // we guarantee that *this is totally empty, so copy pixels that // are areally present on the source image only const QRect optimizedRect = minimalRect & src->extent(); fastBitBltRough(src, optimizedRect); } void KisPaintDevice::setDirty() { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(); } void KisPaintDevice::setDirty(const QRect & rc) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rc); } void KisPaintDevice::setDirty(const KisRegion ®ion) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(region); } void KisPaintDevice::setDirty(const QVector &rects) { m_d->cache()->invalidate(); if (m_d->parent.isValid()) m_d->parent->setDirty(rects); } void KisPaintDevice::requestTimeSwitch(int time) { if (m_d->parent.isValid()) { m_d->parent->requestTimeSwitch(time); } } int KisPaintDevice::sequenceNumber() const { return m_d->cache()->sequenceNumber(); } void KisPaintDevice::estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const { m_d->estimateMemoryStats(imageData, temporaryData, lodData); } void KisPaintDevice::setParentNode(KisNodeWSP parent) { m_d->parent = parent; } // for testing purposes only KisNodeWSP KisPaintDevice::parentNode() const { return m_d->parent; } void KisPaintDevice::setDefaultBounds(KisDefaultBoundsBaseSP defaultBounds) { m_d->defaultBounds = defaultBounds; m_d->cache()->invalidate(); } KisDefaultBoundsBaseSP KisPaintDevice::defaultBounds() const { return m_d->defaultBounds; } void KisPaintDevice::moveTo(const QPoint &pt) { m_d->currentStrategy()->move(pt); m_d->cache()->invalidate(); } QPoint KisPaintDevice::offset() const { return QPoint(x(), y()); } void KisPaintDevice::moveTo(qint32 x, qint32 y) { moveTo(QPoint(x, y)); } void KisPaintDevice::setX(qint32 x) { moveTo(QPoint(x, m_d->y())); } void KisPaintDevice::setY(qint32 y) { moveTo(QPoint(m_d->x(), y)); } qint32 KisPaintDevice::x() const { return m_d->x(); } qint32 KisPaintDevice::y() const { return m_d->y(); } void KisPaintDevice::extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const { QRect rc = extent(); x = rc.x(); y = rc.y(); w = rc.width(); h = rc.height(); } QRect KisPaintDevice::extent() const { return m_d->currentStrategy()->extent(); } KisRegion KisPaintDevice::region() const { return m_d->currentStrategy()->region(); } QRect KisPaintDevice::nonDefaultPixelArea() const { return m_d->cache()->nonDefaultPixelArea(); } QRect KisPaintDevice::exactBounds() const { return m_d->cache()->exactBounds(); } QRect KisPaintDevice::exactBoundsAmortized() const { return m_d->cache()->exactBoundsAmortized(); } namespace Impl { struct CheckFullyTransparent { CheckFullyTransparent(const KoColorSpace *colorSpace) : m_colorSpace(colorSpace) { } bool isPixelEmpty(const quint8 *pixelData) { return m_colorSpace->opacityU8(pixelData) == OPACITY_TRANSPARENT_U8; } private: const KoColorSpace *m_colorSpace; }; struct CheckNonDefault { CheckNonDefault(int pixelSize, const quint8 *defaultPixel) : m_pixelSize(pixelSize), m_defaultPixel(defaultPixel) { } bool isPixelEmpty(const quint8 *pixelData) { return memcmp(m_defaultPixel, pixelData, m_pixelSize) == 0; } private: int m_pixelSize; const quint8 *m_defaultPixel; }; template QRect calculateExactBoundsImpl(const KisPaintDevice *device, const QRect &startRect, const QRect &endRect, ComparePixelOp compareOp) { if (startRect == endRect) return startRect; // the passed extent might have weird invalid structure that // can overflow integer precision when calling startRect.right() if (!startRect.isValid()) return QRect(); // Solution n°2 int x, y, w, h; int boundLeft, boundTop, boundRight, boundBottom; int endDirN, endDirE, endDirS, endDirW; startRect.getRect(&x, &y, &w, &h); if (endRect.isEmpty()) { endDirS = startRect.bottom(); endDirN = startRect.top(); endDirE = startRect.right(); endDirW = startRect.left(); startRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } else { endDirS = endRect.top() - 1; endDirN = endRect.bottom() + 1; endDirE = endRect.left() - 1; endDirW = endRect.right() + 1; endRect.getCoords(&boundLeft, &boundTop, &boundRight, &boundBottom); } // XXX: a small optimization is possible by using H/V line iterators in the first // and third cases, at the cost of making the code a bit more complex KisRandomConstAccessorSP accessor = device->createRandomConstAccessorNG(); bool found = false; { for (qint32 y2 = y; y2 <= endDirS; ++y2) { for (qint32 x2 = x; x2 < x + w || found; ++ x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundTop = y2; found = true; break; } } if (found) break; } } /** * If the first pass hasn't found any opaque pixel, there is no * reason to check that 3 more times. They will not appear in the * meantime. Just return an empty bounding rect. */ if (!found && endRect.isEmpty()) { return QRect(); } found = false; for (qint32 y2 = y + h - 1; y2 >= endDirN ; --y2) { for (qint32 x2 = x + w - 1; x2 >= x || found; --x2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundBottom = y2; found = true; break; } } if (found) break; } found = false; { for (qint32 x2 = x; x2 <= endDirE ; ++x2) { for (qint32 y2 = y; y2 < y + h || found; ++y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundLeft = x2; found = true; break; } } if (found) break; } } found = false; // Look for right edge ) { for (qint32 x2 = x + w - 1; x2 >= endDirW; --x2) { for (qint32 y2 = y + h - 1; y2 >= y || found; --y2) { accessor->moveTo(x2, y2); if (!compareOp.isPixelEmpty(accessor->rawDataConst())) { boundRight = x2; found = true; break; } } if (found) break; } } return QRect(boundLeft, boundTop, boundRight - boundLeft + 1, boundBottom - boundTop + 1); } } QRect KisPaintDevice::calculateExactBounds(bool nonDefaultOnly) const { QRect startRect = extent(); QRect endRect; quint8 defaultOpacity = defaultPixel().opacityU8(); if (defaultOpacity != OPACITY_TRANSPARENT_U8) { if (!nonDefaultOnly) { /** * We will calculate exact bounds only outside of the * image bounds, and that'll be nondefault area only. */ endRect = defaultBounds()->bounds(); nonDefaultOnly = true; } else { startRect = region().boundingRect(); } } if (nonDefaultOnly) { const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } else { Impl::CheckFullyTransparent compareOp(m_d->colorSpace()); endRect = Impl::calculateExactBoundsImpl(this, startRect, endRect, compareOp); } return endRect; } KisRegion KisPaintDevice::regionExact() const { QVector sourceRects = region().rects(); QVector resultRects; const KoColor defaultPixel = this->defaultPixel(); Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel.data()); Q_FOREACH (const QRect &rc1, sourceRects) { const int patchSize = 64; QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); Q_FOREACH (const QRect &rc2, smallerRects) { const QRect result = Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); if (!result.isEmpty()) { resultRects << result; } } } return KisRegion(std::move(resultRects)); } void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { crop(QRect(x, y, w, h)); } void KisPaintDevice::crop(const QRect &rect) { m_d->currentStrategy()->crop(rect); } void KisPaintDevice::purgeDefaultPixels() { KisDataManagerSP dm = m_d->dataManager(); dm->purge(dm->extent()); } void KisPaintDevice::setDefaultPixel(const KoColor &defPixel) { KoColor color(defPixel); color.convertTo(colorSpace()); m_d->dataManager()->setDefaultPixel(color.data()); m_d->cache()->invalidate(); } KoColor KisPaintDevice::defaultPixel() const { return KoColor(m_d->dataManager()->defaultPixel(), colorSpace()); } void KisPaintDevice::clear() { m_d->dataManager()->clear(); m_d->cache()->invalidate(); } void KisPaintDevice::clear(const QRect & rc) { m_d->currentStrategy()->clear(rc); } void KisPaintDevice::fill(const QRect & rc, const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace()); m_d->currentStrategy()->fill(rc, color.data()); } void KisPaintDevice::fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel) { m_d->currentStrategy()->fill(QRect(x, y, w, h), fillPixel); } bool KisPaintDevice::write(KisPaintDeviceWriter &store) { return m_d->dataManager()->write(store); } bool KisPaintDevice::read(QIODevice *stream) { bool retval; retval = m_d->dataManager()->read(stream); m_d->cache()->invalidate(); return retval; } void KisPaintDevice::emitColorSpaceChanged() { emit colorSpaceChanged(m_d->colorSpace()); } void KisPaintDevice::emitProfileChanged() { emit profileChanged(m_d->colorSpace()->profile()); } void KisPaintDevice::convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags, KUndo2Command *parentCommand) { m_d->convertColorSpace(dstColorSpace, renderingIntent, conversionFlags, parentCommand); } bool KisPaintDevice::setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand) { return m_d->assignProfile(profile, parentCommand); } KUndo2Command *KisPaintDevice::reincarnateWithDetachedHistory(bool copyContent) { return m_d->reincarnateWithDetachedHistory(copyContent); } KisDataManagerSP KisPaintDevice::dataManager() const { return m_d->dataManager(); } void KisPaintDevice::convertFromQImage(const QImage& _image, const KoColorProfile *profile, qint32 offsetX, qint32 offsetY) { QImage image = _image; if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } // Don't convert if not no profile is given and both paint dev and qimage are rgba. if (!profile && colorSpace()->id() == "RGBA") { writeBytes(image.constBits(), offsetX, offsetY, image.width(), image.height()); } else { try { quint8 * dstData = new quint8[image.width() * image.height() * pixelSize()]; KoColorSpaceRegistry::instance() ->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), profile) ->convertPixelsTo(image.constBits(), dstData, colorSpace(), image.width() * image.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); writeBytes(dstData, offsetX, offsetY, image.width(), image.height()); delete[] dstData; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertFromQImage: Could not allocate" << image.width() * image.height() * pixelSize() << "bytes"; return; } } m_d->cache()->invalidate(); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { qint32 x1; qint32 y1; qint32 w; qint32 h; QRect rc = exactBounds(); x1 = rc.x(); y1 = rc.y(); w = rc.width(); h = rc.height(); return convertToQImage(dstProfile, x1, y1, w, h, renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { return convertToQImage(dstProfile, rc.x(), rc.y(), rc.width(), rc.height(), renderingIntent, conversionFlags); } QImage KisPaintDevice::convertToQImage(const KoColorProfile *dstProfile, qint32 x1, qint32 y1, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const { if (w < 0) return QImage(); if (h < 0) return QImage(); quint8 *data = 0; try { data = new quint8 [w * h * pixelSize()]; } catch (const std::bad_alloc&) { warnKrita << "KisPaintDevice::convertToQImage std::bad_alloc for " << w << " * " << h << " * " << pixelSize(); //delete[] data; // data is not allocated, so don't free it return QImage(); } Q_CHECK_PTR(data); // XXX: Is this really faster than converting line by line and building the QImage directly? // This copies potentially a lot of data. readBytes(data, x1, y1, w, h); QImage image = colorSpace()->convertToQImage(data, w, h, dstProfile, renderingIntent, conversionFlags); delete[] data; return image; } inline bool moveBy(KisSequentialConstIterator& iter, int numPixels) { int pos = 0; while (pos < numPixels) { int step = std::min(iter.nConseqPixels(), numPixels - pos); if (!iter.nextPixels(step)) return false; pos += step; } return true; } static KisPaintDeviceSP createThumbnailDeviceInternal(const KisPaintDevice* srcDev, qint32 srcX0, qint32 srcY0, qint32 srcWidth, qint32 srcHeight, qint32 w, qint32 h, QRect outputRect) { KisPaintDeviceSP thumbnail = new KisPaintDevice(srcDev->colorSpace()); qint32 pixelSize = srcDev->pixelSize(); KisRandomConstAccessorSP srcIter = srcDev->createRandomConstAccessorNG(); KisRandomAccessorSP dstIter = thumbnail->createRandomAccessorNG(); for (qint32 y = outputRect.y(); y < outputRect.y() + outputRect.height(); ++y) { qint32 iY = srcY0 + (y * srcHeight) / h; for (qint32 x = outputRect.x(); x < outputRect.x() + outputRect.width(); ++x) { qint32 iX = srcX0 + (x * srcWidth) / w; srcIter->moveTo(iX, iY); dstIter->moveTo(x, y); memcpy(dstIter->rawData(), srcIter->rawDataConst(), pixelSize); } } return thumbnail; } QSize fixThumbnailSize(QSize size) { if (!size.width() && size.height()) { size.setWidth(1); } if (size.width() && !size.height()) { size.setHeight(1); } return size; } KisPaintDeviceSP KisPaintDevice::createThumbnailDevice(qint32 w, qint32 h, QRect rect, QRect outputRect) const { QSize thumbnailSize(w, h); QRect imageRect = rect.isValid() ? rect : extent(); if ((thumbnailSize.width() > imageRect.width()) || (thumbnailSize.height() > imageRect.height())) { thumbnailSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailSize = fixThumbnailSize(thumbnailSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } int srcWidth, srcHeight; int srcX0, srcY0; imageRect.getRect(&srcX0, &srcY0, &srcWidth, &srcHeight); if (!outputRect.isValid()) { outputRect = QRect(0, 0, w, h); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailSize.width(), thumbnailSize.height(), outputRect); return thumbnail; } KisPaintDeviceSP KisPaintDevice::createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect, QRect outputTileRect) const { QSize thumbnailSize(w, h); qreal oversampleAdjusted = qMax(oversample, 1.); QSize thumbnailOversampledSize = oversampleAdjusted * thumbnailSize; QRect outputRect; QRect imageRect = rect.isValid() ? rect : extent(); qint32 hstart = thumbnailOversampledSize.height(); if ((thumbnailOversampledSize.width() > imageRect.width()) || (thumbnailOversampledSize.height() > imageRect.height())) { thumbnailOversampledSize.scale(imageRect.size(), Qt::KeepAspectRatio); } thumbnailOversampledSize = fixThumbnailSize(thumbnailOversampledSize); //can't create thumbnail for an empty device, e.g. layer thumbnail for empty image if (imageRect.isEmpty() || thumbnailSize.isEmpty() || thumbnailOversampledSize.isEmpty()) { return new KisPaintDevice(colorSpace()); } oversampleAdjusted *= (hstart > 0) ? ((qreal)thumbnailOversampledSize.height() / hstart) : 1.; //readjusting oversample ratio, given that we had to adjust thumbnail size outputRect = QRect(0, 0, thumbnailOversampledSize.width(), thumbnailOversampledSize.height()); if (outputTileRect.isValid()) { //compensating output rectangle for oversampling outputTileRect = QRect(oversampleAdjusted * outputTileRect.topLeft(), oversampleAdjusted * outputTileRect.bottomRight()); outputRect = outputRect.intersected(outputTileRect); } KisPaintDeviceSP thumbnail = createThumbnailDeviceInternal(this, imageRect.x(), imageRect.y(), imageRect.width(), imageRect.height(), thumbnailOversampledSize.width(), thumbnailOversampledSize.height(), outputRect); if (oversample != 1. && oversampleAdjusted != 1.) { KoDummyUpdater updater; KisTransformWorker worker(thumbnail, 1 / oversampleAdjusted, 1 / oversampleAdjusted, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, &updater, KisFilterStrategyRegistry::instance()->value("Bilinear")); worker.run(); } return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, QRect rect, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); KisPaintDeviceSP dev = createThumbnailDeviceOversampled(size.width(), size.height(), oversample, rect); QImage thumbnail = dev->convertToQImage(KoColorSpaceRegistry::instance()->rgb8()->profile(), 0, 0, w, h, renderingIntent, conversionFlags); return thumbnail; } QImage KisPaintDevice::createThumbnail(qint32 w, qint32 h, qreal oversample, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { QSize size = fixThumbnailSize(QSize(w, h)); return m_d->cache()->createThumbnail(size.width(), size.height(), oversample, renderingIntent, conversionFlags); } +QImage KisPaintDevice::createThumbnail(qint32 maxw, qint32 maxh, + Qt::AspectRatioMode aspectRatioMode, + qreal oversample, KoColorConversionTransformation::Intent renderingIntent, + KoColorConversionTransformation::ConversionFlags conversionFlags) +{ + const QRect deviceExtent = extent(); + const QSize thumbnailSize = deviceExtent.size().scaled(maxw, maxh, aspectRatioMode); + return createThumbnail(thumbnailSize.width(), thumbnailSize.height(), + oversample, renderingIntent, conversionFlags); +} + KisHLineIteratorSP KisPaintDevice::createHLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createHLineIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisHLineConstIteratorSP KisPaintDevice::createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y()); } KisVLineIteratorSP KisPaintDevice::createVLineIteratorNG(qint32 x, qint32 y, qint32 w) { m_d->cache()->invalidate(); return m_d->currentStrategy()->createVLineIteratorNG(x, y, w); } KisVLineConstIteratorSP KisPaintDevice::createVLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const { return m_d->currentStrategy()->createVLineConstIteratorNG(x, y, w); } KisRepeatHLineConstIteratorSP KisPaintDevice::createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const { return new KisRepeatHLineConstIteratorNG(m_d->dataManager().data(), x, y, w, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRepeatVLineConstIteratorSP KisPaintDevice::createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const { return new KisRepeatVLineConstIteratorNG(m_d->dataManager().data(), x, y, h, m_d->x(), m_d->y(), _dataWidth, m_d->cacheInvalidator()); } KisRandomAccessorSP KisPaintDevice::createRandomAccessorNG() { m_d->cache()->invalidate(); return m_d->currentStrategy()->createRandomAccessorNG(); } KisRandomConstAccessorSP KisPaintDevice::createRandomConstAccessorNG() const { return m_d->currentStrategy()->createRandomConstAccessorNG(); } KisRandomSubAccessorSP KisPaintDevice::createRandomSubAccessor() const { KisPaintDevice* pd = const_cast(this); return new KisRandomSubAccessor(pd); } void KisPaintDevice::clearSelection(KisSelectionSP selection) { const KoColorSpace *colorSpace = m_d->colorSpace(); const QRect r = selection->selectedExactRect(); if (r.isValid()) { { KisHLineIteratorSP devIt = createHLineIteratorNG(r.x(), r.y(), r.width()); KisHLineConstIteratorSP selectionIt = selection->projection()->createHLineConstIteratorNG(r.x(), r.y(), r.width()); const KoColor defaultPixel = this->defaultPixel(); bool transparentDefault = (defaultPixel.opacityU8() == OPACITY_TRANSPARENT_U8); for (qint32 y = 0; y < r.height(); y++) { do { // XXX: Optimize by using stretches colorSpace->applyInverseAlphaU8Mask(devIt->rawData(), selectionIt->rawDataConst(), 1); if (transparentDefault && colorSpace->opacityU8(devIt->rawData()) == OPACITY_TRANSPARENT_U8) { memcpy(devIt->rawData(), defaultPixel.data(), colorSpace->pixelSize()); } } while (devIt->nextPixel() && selectionIt->nextPixel()); devIt->nextRow(); selectionIt->nextRow(); } } // purge() must be executed **after** all iterators have been destroyed! m_d->dataManager()->purge(r.translated(-m_d->x(), -m_d->y())); setDirty(r); } } bool KisPaintDevice::pixel(qint32 x, qint32 y, QColor *c) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; colorSpace()->toQColor(pix, c); return true; } bool KisPaintDevice::pixel(qint32 x, qint32 y, KoColor * kc) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(x, y, 1); const quint8 *pix = iter->rawDataConst(); if (!pix) return false; kc->setColor(pix, m_d->colorSpace()); return true; } KoColor KisPaintDevice::pixel(const QPoint &pos) const { KisHLineConstIteratorSP iter = createHLineConstIteratorNG(pos.x(), pos.y(), 1); return KoColor(iter->rawDataConst(), m_d->colorSpace()); } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const QColor& c) { KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); colorSpace()->fromQColor(c, iter->rawData()); m_d->cache()->invalidate(); return true; } bool KisPaintDevice::setPixel(qint32 x, qint32 y, const KoColor& kc) { const quint8 * pix; KisHLineIteratorSP iter = createHLineIteratorNG(x, y, 1); if (kc.colorSpace() != m_d->colorSpace()) { KoColor kc2(kc, m_d->colorSpace()); pix = kc2.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } else { pix = kc.data(); memcpy(iter->rawData(), pix, m_d->colorSpace()->pixelSize()); } m_d->cache()->invalidate(); return true; } bool KisPaintDevice::fastBitBltPossible(KisPaintDeviceSP src) { return m_d->fastBitBltPossible(src); } void KisPaintDevice::fastBitBlt(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBlt(src, rect); } void KisPaintDevice::fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltOldData(src, rect); } void KisPaintDevice::fastBitBltRough(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRough(src, rect); } void KisPaintDevice::fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect) { m_d->currentStrategy()->fastBitBltRoughOldData(src, rect); } void KisPaintDevice::readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const { readBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::readBytes(quint8 *data, const QRect &rect) const { m_d->currentStrategy()->readBytes(data, rect); } void KisPaintDevice::writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) { writeBytes(data, QRect(x, y, w, h)); } void KisPaintDevice::writeBytes(const quint8 *data, const QRect &rect) { m_d->currentStrategy()->writeBytes(data, rect); } QVector KisPaintDevice::readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const { return m_d->currentStrategy()->readPlanarBytes(x, y, w, h); } void KisPaintDevice::writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h) { m_d->currentStrategy()->writePlanarBytes(planes, x, y, w, h); } quint32 KisPaintDevice::pixelSize() const { quint32 _pixelSize = m_d->colorSpace()->pixelSize(); Q_ASSERT(_pixelSize > 0); return _pixelSize; } quint32 KisPaintDevice::channelCount() const { quint32 _channelCount = m_d->colorSpace()->channelCount(); Q_ASSERT(_channelCount > 0); return _channelCount; } KisRasterKeyframeChannel *KisPaintDevice::createKeyframeChannel(const KoID &id) { Q_ASSERT(!m_d->framesInterface); m_d->framesInterface.reset(new KisPaintDeviceFramesInterface(this)); Q_ASSERT(!m_d->contentChannel); if (m_d->parent.isValid()) { m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->parent)); } else { //fallback when paint device is isolated / does not belong to a node. ENTER_FUNCTION() << ppVar(this) << ppVar(m_d->defaultBounds); m_d->contentChannel.reset(new KisRasterKeyframeChannel(id, this, m_d->defaultBounds)); } // Raster channels always have at least one frame (representing a static image) KUndo2Command tempParentCommand; m_d->contentChannel->addKeyframe(0, &tempParentCommand); return m_d->contentChannel.data(); } KisRasterKeyframeChannel* KisPaintDevice::keyframeChannel() const { if (m_d->contentChannel) { return m_d->contentChannel.data(); } return 0; } const KoColorSpace* KisPaintDevice::colorSpace() const { Q_ASSERT(m_d->colorSpace() != 0); return m_d->colorSpace(); } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice() const { KisPaintDeviceSP device = new KisPaintDevice(compositionSourceColorSpace()); device->setDefaultBounds(defaultBounds()); return device; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const { KisPaintDeviceSP clone = new KisPaintDevice(*cloneSource); clone->setDefaultBounds(defaultBounds()); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisPaintDeviceSP KisPaintDevice::createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const { KisPaintDeviceSP clone = new KisPaintDevice(colorSpace()); clone->setDefaultBounds(defaultBounds()); clone->makeCloneFromRough(cloneSource, roughRect); clone->convertTo(compositionSourceColorSpace(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return clone; } KisFixedPaintDeviceSP KisPaintDevice::createCompositionSourceDeviceFixed() const { return new KisFixedPaintDevice(compositionSourceColorSpace()); } const KoColorSpace* KisPaintDevice::compositionSourceColorSpace() const { return colorSpace(); } QVector KisPaintDevice::channelSizes() const { QVector sizes; QList channels = colorSpace()->channels(); std::sort(channels.begin(), channels.end()); Q_FOREACH (KoChannelInfo * channelInfo, channels) { sizes.append(channelInfo->size()); } return sizes; } KisPaintDevice::MemoryReleaseObject::~MemoryReleaseObject() { KisDataManager::releaseInternalPools(); } KisPaintDevice::MemoryReleaseObject* KisPaintDevice::createMemoryReleaseObject() { return new MemoryReleaseObject(); } KisPaintDevice::LodDataStruct::~LodDataStruct() { } KisRegion KisPaintDevice::regionForLodSyncing() const { return m_d->regionForLodSyncing(); } KisPaintDevice::LodDataStruct* KisPaintDevice::createLodDataStruct(int lod) { return m_d->createLodDataStruct(lod); } void KisPaintDevice::updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect) { m_d->updateLodDataStruct(dst, srcRect); } void KisPaintDevice::uploadLodDataStruct(LodDataStruct *dst) { m_d->uploadLodDataStruct(dst); } void KisPaintDevice::generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod) { m_d->generateLodCloneDevice(dst, originalRect, lod); } KisPaintDeviceFramesInterface* KisPaintDevice::framesInterface() { return m_d->framesInterface.data(); } /******************************************************************/ /* KisPaintDeviceFramesInterface */ /******************************************************************/ KisPaintDeviceFramesInterface::KisPaintDeviceFramesInterface(KisPaintDevice *parentDevice) : q(parentDevice) { } QList KisPaintDeviceFramesInterface::frames() { return q->m_d->frameIds(); } int KisPaintDeviceFramesInterface::createFrame(bool copy, int copySrc, const QPoint &offset, KUndo2Command *parentCommand) { return q->m_d->createFrame(copy, copySrc, offset, parentCommand); } void KisPaintDeviceFramesInterface::deleteFrame(int frame, KUndo2Command *parentCommand) { return q->m_d->deleteFrame(frame, parentCommand); } void KisPaintDeviceFramesInterface::fetchFrame(int frameId, KisPaintDeviceSP targetDevice) { q->m_d->fetchFrame(frameId, targetDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int srcFrameId, int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(srcFrameId, dstFrameId, srcDevice); } void KisPaintDeviceFramesInterface::uploadFrame(int dstFrameId, KisPaintDeviceSP srcDevice) { q->m_d->uploadFrame(dstFrameId, srcDevice); } QRect KisPaintDeviceFramesInterface::frameBounds(int frameId) { return q->m_d->frameBounds(frameId); } QPoint KisPaintDeviceFramesInterface::frameOffset(int frameId) const { return q->m_d->frameOffset(frameId); } void KisPaintDeviceFramesInterface::setFrameDefaultPixel(const KoColor &defPixel, int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); q->m_d->setFrameDefaultPixel(defPixel, frameId); } KoColor KisPaintDeviceFramesInterface::frameDefaultPixel(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return KoColor(Qt::red, q->m_d->colorSpace()); } return q->m_d->frameDefaultPixel(frameId); } bool KisPaintDeviceFramesInterface::writeFrame(KisPaintDeviceWriter &store, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->writeFrame(store, frameId); } bool KisPaintDeviceFramesInterface::readFrame(QIODevice *stream, int frameId) { KIS_ASSERT_RECOVER(frameId >= 0) { return false; } return q->m_d->readFrame(stream, frameId); } int KisPaintDeviceFramesInterface::currentFrameId() const { return q->m_d->currentFrameId(); } KisDataManagerSP KisPaintDeviceFramesInterface::frameDataManager(int frameId) const { KIS_ASSERT_RECOVER(frameId >= 0) { return q->m_d->dataManager(); } return q->m_d->frameDataManager(frameId); } void KisPaintDeviceFramesInterface::invalidateFrameCache(int frameId) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->invalidateFrameCache(frameId); } void KisPaintDeviceFramesInterface::setFrameOffset(int frameId, const QPoint &offset) { KIS_ASSERT_RECOVER_RETURN(frameId >= 0); return q->m_d->setFrameOffset(frameId, offset); } KisPaintDeviceFramesInterface::TestingDataObjects KisPaintDeviceFramesInterface::testingGetDataObjects() const { TestingDataObjects objects; objects.m_data = q->m_d->m_data.data(); objects.m_lodData = q->m_d->m_lodData.data(); objects.m_externalFrameData = q->m_d->m_externalFrameData.data(); typedef KisPaintDevice::Private::FramesHash FramesHash; FramesHash::const_iterator it = q->m_d->m_frames.constBegin(); FramesHash::const_iterator end = q->m_d->m_frames.constEnd(); for (; it != end; ++it) { objects.m_frames.insert(it.key(), it.value().data()); } objects.m_currentData = q->m_d->currentData(); return objects; } QList KisPaintDeviceFramesInterface::testingGetDataObjectsList() const { return q->m_d->allDataObjects(); } void KisPaintDevice::tesingFetchLodDevice(KisPaintDeviceSP targetDevice) { m_d->tesingFetchLodDevice(targetDevice); } diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h index 25565ec026..709ada1969 100644 --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -1,917 +1,927 @@ /* * Copyright (c) 2002 patrick julien * Copyright (c) 2006 Boudewijn Rempt * * 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_PAINT_DEVICE_IMPL_H_ #define KIS_PAINT_DEVICE_IMPL_H_ #include #include #include #include "kis_debug.h" #include #include "kis_types.h" #include "kis_shared.h" #include "kis_default_bounds_base.h" #include class KUndo2Command; class QRect; class QImage; class QPoint; class QString; class QColor; class QIODevice; class KoColor; class KoColorSpace; class KoColorProfile; class KisRegion; class KisDataManager; class KisPaintDeviceWriter; class KisKeyframe; class KisRasterKeyframeChannel; class KisPaintDeviceFramesInterface; typedef KisSharedPtr KisDataManagerSP; namespace KritaUtils { enum DeviceCopyMode { CopySnapshot = 0, CopyAllFrames }; } /** * A paint device contains the actual pixel data and offers methods * to read and write pixels. A paint device has an integer x, y position * (it is not positioned on the image with sub-pixel accuracy). * A KisPaintDevice doesn't have any fixed size, the size changes dynamically * when pixels are accessed by an iterator. */ class KRITAIMAGE_EXPORT KisPaintDevice : public QObject , public KisShared { Q_OBJECT public: /** * Create a new paint device with the specified colorspace. * * @param colorSpace the colorspace of this paint device * @param name for debugging purposes */ explicit KisPaintDevice(const KoColorSpace * colorSpace, const QString& name = QString()); /** * Create a new paint device with the specified colorspace. The * parent node will be notified of changes to this paint device. * * @param parent the node that contains this paint device * @param colorSpace the colorspace of this paint device * @param defaultBounds boundaries of the device in case it is empty * @param name for debugging purposes */ KisPaintDevice(KisNodeWSP parent, const KoColorSpace * colorSpace, KisDefaultBoundsBaseSP defaultBounds = KisDefaultBoundsBaseSP(), const QString& name = QString()); /** * Creates a copy of this device. * * If \p copyMode is CopySnapshot, the newly created device clones the * current frame of \p rhs only (default and efficient * behavior). If \p copyFrames is CopyAllFrames, the new device is a deep * copy of the source with all the frames included. */ KisPaintDevice(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); ~KisPaintDevice() override; void makeFullCopyFrom(const KisPaintDevice& rhs, KritaUtils::DeviceCopyMode copyMode = KritaUtils::CopySnapshot, KisNode *newParentNode = 0); protected: /** * A special constructor for usage in KisPixelSelection. It allows * two paint devices to share a data manager. * * @param explicitDataManager data manager to use inside paint device * @param src source paint device to copy parameters from * @param name for debugging purposes */ KisPaintDevice(KisDataManagerSP explicitDataManager, KisPaintDeviceSP src, const QString& name = QString()); public: /** * Write the pixels of this paint device into the specified file store. */ bool write(KisPaintDeviceWriter &store); /** * Fill this paint device with the pixels from the specified file store. */ bool read(QIODevice *stream); public: /** * set the parent node of the paint device */ void setParentNode(KisNodeWSP parent); /** * set the default bounds for the paint device when * the default pixel is not completely transparent */ void setDefaultBounds(KisDefaultBoundsBaseSP bounds); /** * the default bounds rect of the paint device */ KisDefaultBoundsBaseSP defaultBounds() const; /** * Moves the device to these new coordinates (no incremental move) */ void moveTo(qint32 x, qint32 y); /** * Convenience method for the above. */ virtual void moveTo(const QPoint& pt); /** * Return an X,Y offset of the device in a convenient form */ QPoint offset() const; /** * The X offset of the paint device */ qint32 x() const; /** * The Y offset of the paint device */ qint32 y() const; /** * set the X offset of the paint device */ void setX(qint32 x); /** * set the Y offset of the paint device */ void setY(qint32 y); /** * Retrieve the bounds of the paint device. The size is not exact, * but may be larger if the underlying datamanager works that way. * For instance, the tiled datamanager keeps the extent to the nearest * multiple of 64. * * If default pixel is not transparent, then the actual extent * rect is united with the defaultBounds()->bounds() value * (the size of the image, usually). */ QRect extent() const; /// Convenience method for the above void extent(qint32 &x, qint32 &y, qint32 &w, qint32 &h) const; /** * Get the exact bounds of this paint device. The real solution is * very slow because it does a linear scanline search, but it * uses caching, so calling to this function without changing * the device is quite cheap. * * Exactbounds follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned *
  • if default pixel is not transparent, then the union * (defaultBounds()->bounds() | nonDefaultPixelArea()) is * returned *
* \see calculateExactBounds() */ QRect exactBounds() const; /** * Relaxed version of the exactBounds() that can be used in tight * loops. If the exact bounds value is present in the paint * device cache, returns this value. If the cache is invalidated, * returns extent() and tries to recalculate the exact bounds not * faster than once in 1000 ms. */ QRect exactBoundsAmortized() const; /** * Returns exact rectangle of the paint device that contains * non-default pixels. For paint devices with fully transparent * default pixel is equivalent to exactBounds(). * * nonDefaultPixelArea() follows these rules: * *
    *
  • if default pixel is transparent, then exact bounds * of actual pixel data are returned. The same as exactBounds() *
  • if default pixel is not transparent, then calculates the * rectangle of non-default pixels. May be smaller or greater * than image bounds *
* \see calculateExactBounds() */ QRect nonDefaultPixelArea() const; /** * Returns a rough approximation of region covered by device. * For tiled data manager, it region will consist of a number * of rects each corresponding to a tile. */ KisRegion region() const; /** * The slow version of region() that searches for exact bounds of * each rectangle in the region */ KisRegion regionExact() const; /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ void crop(qint32 x, qint32 y, qint32 w, qint32 h); /// Convenience method for the above void crop(const QRect & r); /** * Complete erase the current paint device. Its size will become 0. This * does not take the selection into account. */ virtual void clear(); /** * Clear the given rectangle to transparent black. The paint device will expand to * contain the given rect. */ void clear(const QRect & rc); /** * Frees the memory occupied by the pixels containing default * values. The extents() and exactBounds() of the paint device will * probably also shrink */ void purgeDefaultPixels(); /** * Sets the default pixel. New data will be initialised with this pixel. The pixel is copied: the * caller still owns the pointer and needs to delete it to avoid memory leaks. * If frame ID is given, set default pixel for that frame. Otherwise use active frame. */ void setDefaultPixel(const KoColor &defPixel); /** * Get a pointer to the default pixel. * If the frame parameter is given, get the default pixel of * specified frame. Otherwise use currently active frame. */ KoColor defaultPixel() const; /** * Fill the given rectangle with the given pixel. The paint device will expand to * contain the given rect. */ void fill(const QRect & rc, const KoColor &color); /** * Overloaded function. For legacy purposes only. * Please use fill(const QRect & rc, const KoColor &color) instead */ void fill(qint32 x, qint32 y, qint32 w, qint32 h, const quint8 *fillPixel); public: /** * Prepares the device for fastBitBlt operation. It clears * the device, switches x,y shifts and colorspace if needed. * After this call fastBitBltPossible will return true. * May be used for initialization of temporary devices. */ void prepareClone(KisPaintDeviceSP src); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * After calling this function: * (this->extent() >= this->exactBounds() == rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFrom(KisPaintDeviceSP src, const QRect &rect); /** * Make this device to become a clone of \a src. It will have the same * x,y shifts, colorspace and will share pixels inside \a rect. * Be careful, this function will copy *at least* \a rect * of pixels. Actual copy area will be a bigger - it will * be aligned by tiles borders. So after calling this function: * (this->extent() == this->exactBounds() >= rect). * * Rule of thumb: * * "Use makeCloneFrom() or makeCloneFromRough() if and only if you * are the only owner of the destination paint device and you are * 100% sure no other thread has access to it" */ void makeCloneFromRough(KisPaintDeviceSP src, const QRect &minimalRect); protected: friend class KisPaintDeviceTest; friend class DataReaderThread; /** * Checks whether a src paint device can be used as source * of fast bitBlt operation. The result of the check may * depend on whether color spaces coincide, whether there is * any shift of tiles between the devices and etc. * * WARNING: This check must be done before performing any * fast bitBlt operation! * * \see fastBitBlt * \see fastBitBltRough */ bool fastBitBltPossible(KisPaintDeviceSP src); /** * Clones rect from another paint device. The cloned area will be * shared between both paint devices as much as possible using * copy-on-write. Parts of the rect that cannot be shared * (cross tiles) are deep-copied, * * \see fastBitBltPossible * \see fastBitBltRough */ void fastBitBlt(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBlt() but reads old data */ void fastBitBltOldData(KisPaintDeviceSP src, const QRect &rect); /** * Clones rect from another paint device in a rough and fast way. * All the tiles touched by rect will be shared, between both * devices, that means it will copy a bigger area than was * requested. This method is supposed to be used for bitBlt'ing * into temporary paint devices. * * \see fastBitBltPossible * \see fastBitBlt */ void fastBitBltRough(KisPaintDeviceSP src, const QRect &rect); /** * The same as \ref fastBitBltRough() but reads old data */ void fastBitBltRoughOldData(KisPaintDeviceSP src, const QRect &rect); public: /** * Read the bytes representing the rectangle described by x, y, w, h into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. */ void readBytes(quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Read the bytes representing the rectangle rect into * data. If data is not big enough, Krita will gladly overwrite the rest * of your precious memory. * * Since this is a copy, you need to make sure you have enough memory. * * Reading from areas not previously initialized will read the default * pixel value into data but not initialize that region. * @param data The address of the memory to receive the bytes read * @param rect The rectangle in the paint device to read from */ void readBytes(quint8 * data, const QRect &rect) const; /** * Copy the bytes in data into the rect specified by x, y, w, h. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. */ void writeBytes(const quint8 * data, qint32 x, qint32 y, qint32 w, qint32 h); /** * Copy the bytes in data into the rectangle rect. If the * data is too small or uninitialized, Krita will happily read parts of * memory you never wanted to be read. * * If the data is written to areas of the paint device not previously initialized, * the paint device will grow. * @param data The address of the memory to write bytes from * @param rect The rectangle in the paint device to write to */ void writeBytes(const quint8 * data, const QRect &rect); /** * Copy the bytes in the paint device into a vector of arrays of bytes, * where the number of arrays is the number of channels in the * paint device. If the specified area is larger than the paint * device's extent, the default pixel will be read. */ QVector readPlanarBytes(qint32 x, qint32 y, qint32 w, qint32 h) const; /** * Write the data in the separate arrays to the channes. If there * are less vectors than channels, the remaining channels will not * be copied. If any of the arrays points to 0, the channel in * that location will not be touched. If the specified area is * larger than the paint device, the paint device will be * extended. There are no guards: if the area covers more pixels * than there are bytes in the arrays, krita will happily fill * your paint device with areas of memory you never wanted to be * read. Krita may also crash. * * XXX: what about undo? */ void writePlanarBytes(QVector planes, qint32 x, qint32 y, qint32 w, qint32 h); /** * Converts the paint device to a different colorspace */ void convertTo(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand = 0); /** * Changes the profile of the colorspace of this paint device to the given * profile. If the given profile is 0, nothing happens. */ bool setProfile(const KoColorProfile * profile, KUndo2Command *parentCommand); /** * Fill this paint device with the data from image; starting at (offsetX, offsetY) * @param image the image * @param profile name of the RGB profile to interpret the image as. 0 is interpreted as sRGB * @param offsetX x offset * @param offsetY y offset */ void convertFromQImage(const QImage& image, const KoColorProfile *profile, qint32 offsetX = 0, qint32 offsetY = 0); /** * Create an RGBA QImage from a rectangle in the paint device. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param x Left coordinate of the rectangle * @param y Top coordinate of the rectangle * @param w Width of the rectangle in pixels * @param h Height of the rectangle in pixels * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Overridden method for convenience */ QImage convertToQImage(const KoColorProfile *dstProfile, const QRect &rc, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Create an RGBA QImage from a rectangle in the paint device. The * rectangle is defined by the parent image's bounds. * * @param dstProfile RGB profile to use in conversion. May be 0, in which * case it's up to the color strategy to choose a profile (most * like sRGB). * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage convertToQImage(const KoColorProfile * dstProfile, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()) const; /** * Creates a paint device thumbnail of the paint device, retaining * the aspect ratio. The width and height of the returned device * won't exceed \p maxw and \p maxw, but they may be smaller. * * @param w maximum width * @param h maximum height * @param rect only this rect will be used for the thumbnail * @param outputRect output rectangle * */ KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect = QRect(), QRect outputRect = QRect()) const; KisPaintDeviceSP createThumbnailDeviceOversampled(qint32 w, qint32 h, qreal oversample, QRect rect = QRect(), QRect outputRect = QRect()) const; /** * Creates a thumbnail of the paint device, retaining the aspect ratio. * The width and height of the returned QImage won't exceed \p maxw and \p maxw, but they may be smaller. * The colors are not corrected for display! * * @param maxw: maximum width * @param maxh: maximum height * @param rect: only this rect will be used for the thumbnail * @param oversample: ratio used for antialiasing * @param renderingIntent Rendering intent * @param conversionFlags Conversion flags */ QImage createThumbnail(qint32 maxw, qint32 maxh, QRect rect, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); /** * Cached version of createThumbnail(qint32 maxw, qint32 maxh, const KisSelection *selection, QRect rect) */ QImage createThumbnail(qint32 maxw, qint32 maxh, qreal oversample = 1, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + /** + * Cached version of createThumbnail that also adjusts aspect ratio of the + * thumbnail to fit the extents of the paint device. + */ + QImage createThumbnail(qint32 maxw, qint32 maxh, + Qt::AspectRatioMode aspectRatioMode, + qreal oversample = 1, + KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), + KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); + /** * Fill c and opacity with the values found at x and y. * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, QColor *c) const; /** * Fill kc with the values found at x and y. This method differs * from the above in using KoColor, which can be of any colorspace * * The color values will be transformed from the profile of * this paint device to the display profile. * * @return true if the operation was successful. */ bool pixel(qint32 x, qint32 y, KoColor * kc) const; /** * Return pixel value in a form of KoColor. Please don't use this method * for iteration, it is highly inefficient. Use iterators instead. */ KoColor pixel(const QPoint &pos) const; /** * Set the specified pixel to the specified color. Note that this * bypasses KisPainter. the PaintDevice is here used as an equivalent * to QImage, not QPixmap. This means that this is not undoable; also, * there is no compositing with an existing value at this location. * * The color values will be transformed from the display profile to * the paint device profile. * * Note that this will use 8-bit values and may cause a significant * degradation when used on 16-bit or hdr quality images. * * @return true if the operation was successful */ bool setPixel(qint32 x, qint32 y, const QColor& c); /// Convenience method for the above bool setPixel(qint32 x, qint32 y, const KoColor& kc); /** * @return the colorspace of the pixels in this paint device */ const KoColorSpace* colorSpace() const; /** * There is quite a common technique in Krita. It is used in * cases, when we want to paint something over a paint device * using the composition, opacity or selection. E.g. painting a * dab in a paint op, filling the selection in the Fill Tool. * Such work is usually done in the following way: * * 1) Create a paint device * * 2) Fill it with the desired color or data * * 3) Create a KisPainter and set all the properties of the * transaction: selection, compositeOp, opacity and etc. * * 4) Paint a newly created paint device over the destination * device. * * The following two methods (createCompositionSourceDevice() or * createCompositionSourceDeviceFixed())should be used for the * accomplishing the step 1). The point is that the desired color * space of the temporary device may not coincide with the color * space of the destination. That is the case, for example, for * the alpha8() colorspace used in the selections. So for such * devices the temporary target would have a different (grayscale) * color space. * * So there are two rules of thumb: * * 1) If you need a temporary device which you are going to fill * with some data and then paint over the paint device, create * it with either createCompositionSourceDevice() or * createCompositionSourceDeviceFixed(). * * 2) Do *not* expect that the color spaces of the destination and * the temporary device would coincide. If you need to copy a * single pixel from one device to another, you can use * KisCrossDeviceColorPicker class, that will handle all the * necessary conversions for you. * * \see createCompositionSourceDeviceFixed() * \see compositionSourceColorSpace() * \see KisCrossDeviceColorPicker * \see KisCrossDeviceColorPickerInt */ KisPaintDeviceSP createCompositionSourceDevice() const; /** * The same as createCompositionSourceDevice(), but initializes the * newly created device with the content of \p cloneSource * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource) const; /** * The same as createCompositionSourceDevice(), but initializes * the newly created device with the *rough* \p roughRect of * \p cloneSource. * * "Rough rect" means that it may copy a bit more than * requested. It is expected that the caller will not use the area * outside \p roughRect. * * \see createCompositionSourceDevice() */ KisPaintDeviceSP createCompositionSourceDevice(KisPaintDeviceSP cloneSource, const QRect roughRect) const; /** * This is a convenience method for createCompositionSourceDevice() * * \see createCompositionSourceDevice() */ KisFixedPaintDeviceSP createCompositionSourceDeviceFixed() const; /** * This is a lowlevel method for the principle used in * createCompositionSourceDevice(). In most of the cases the paint * device creation methods should be used instead of this function. * * \see createCompositionSourceDevice() * \see createCompositionSourceDeviceFixed() */ virtual const KoColorSpace* compositionSourceColorSpace() const; /** * @return the internal datamanager that keeps the pixels. */ KisDataManagerSP dataManager() const; /** * Replace the pixel data, color strategy, and profile. */ void setDataManager(KisDataManagerSP data, const KoColorSpace * colorSpace = 0); /** * Return the number of bytes a pixel takes. */ quint32 pixelSize() const; /** * Return the number of channels a pixel takes */ quint32 channelCount() const; /** * Create a keyframe channel for the content on this device. * @param id identifier for the channel * @return keyframe channel or 0 if there is not one */ KisRasterKeyframeChannel *createKeyframeChannel(const KoID &id); KisRasterKeyframeChannel* keyframeChannel() const; /** * An interface to modify/load/save frames stored inside this device */ KisPaintDeviceFramesInterface* framesInterface(); public: /** * Add the specified rect to the parent layer's set of dirty rects * (if there is a parent layer) */ void setDirty(const QRect & rc); void setDirty(const KisRegion ®ion); /** * Set the parent layer completely dirty, if this paint device has * as parent layer. */ void setDirty(); void setDirty(const QVector &rects); /** * Called by KisTransactionData when it thinks current time should * be changed. And the requests is forwarded to the image if * needed. */ void requestTimeSwitch(int time); /** * \return a sequence number corresponding to the current paint * device state. Every time the paint device is changed, * the sequence number is increased */ int sequenceNumber() const; void estimateMemoryStats(qint64 &imageData, qint64 &temporaryData, qint64 &lodData) const; public: KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w); KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const; KisVLineIteratorSP createVLineIteratorNG(qint32 x, qint32 y, qint32 h); KisVLineConstIteratorSP createVLineConstIteratorNG(qint32 x, qint32 y, qint32 h) const; KisRandomAccessorSP createRandomAccessorNG(); KisRandomConstAccessorSP createRandomConstAccessorNG() const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param w width of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatHLineConstIteratorSP createRepeatHLineConstIterator(qint32 x, qint32 y, qint32 w, const QRect& _dataWidth) const; /** * Create an iterator that will "artificially" extend the paint device with the * value of the border when trying to access values outside the range of data. * * @param x x of top left corner * @param y y of top left corner * @param h height of the border * @param _dataWidth indicates the rectangle that truly contains data */ KisRepeatVLineConstIteratorSP createRepeatVLineConstIterator(qint32 x, qint32 y, qint32 h, const QRect& _dataWidth) const; /** * This function create a random accessor which can easily access to sub pixel values. */ KisRandomSubAccessorSP createRandomSubAccessor() const; /** Clear the selected pixels from the paint device */ void clearSelection(KisSelectionSP selection); /** * Converts a paint device into a "new" paint device, that has * unconnected history. That is, after reincarnation, the device's * life starts a new page. No history. No memories. * * When the device is fed up with the new life, it can reincarnate * back to its previous life by undoing the command returned by * reincarnateWithDetachedHistory(). The old undo will continue * working as if nothing has happened. * * NOTE: reincarnation affects only the current lod plane and/or * current frame. All other frames are kept unaffected. * * @param copyContent decides if the device should take its current * content to the new life * @return undo command for execution and undoing of the reincarnation */ KUndo2Command* reincarnateWithDetachedHistory(bool copyContent); Q_SIGNALS: void profileChanged(const KoColorProfile * profile); void colorSpaceChanged(const KoColorSpace *colorspace); public: friend class PaintDeviceCache; /** * Caclculates exact bounds of the device. Used internally * by a transparent caching system. The solution is very slow * because it does a linear scanline search. So the complexity * is n*n at worst. * * \see exactBounds(), nonDefaultPixelArea() */ QRect calculateExactBounds(bool nonDefaultOnly) const; public: struct MemoryReleaseObject : public QObject { ~MemoryReleaseObject() override; }; static MemoryReleaseObject* createMemoryReleaseObject(); public: struct LodDataStruct { virtual ~LodDataStruct(); }; KisRegion regionForLodSyncing() const; LodDataStruct* createLodDataStruct(int lod); void updateLodDataStruct(LodDataStruct *dst, const QRect &srcRect); void uploadLodDataStruct(LodDataStruct *dst); void generateLodCloneDevice(KisPaintDeviceSP dst, const QRect &originalRect, int lod); void setProjectionDevice(bool value); void tesingFetchLodDevice(KisPaintDeviceSP targetDevice); private: KisPaintDevice& operator=(const KisPaintDevice&); void init(const KoColorSpace *colorSpace, KisDefaultBoundsBaseSP defaultBounds, KisNodeWSP parent, const QString& name); // Only KisPainter is allowed to have access to these low-level methods friend class KisPainter; /** * Return a vector with in order the size in bytes of the channels * in the colorspace of this paint device. */ QVector channelSizes() const; void emitColorSpaceChanged(); void emitProfileChanged(); private: friend class KisPaintDeviceFramesInterface; protected: friend class KisSelectionTest; KisNodeWSP parentNode() const; private: struct Private; Private * const m_d; }; #endif // KIS_PAINT_DEVICE_IMPL_H_ diff --git a/libs/image/kis_selection_based_layer.cpp b/libs/image/kis_selection_based_layer.cpp index 3bad0c4d41..bcd3bdfa87 100644 --- a/libs/image/kis_selection_based_layer.cpp +++ b/libs/image/kis_selection_based_layer.cpp @@ -1,358 +1,358 @@ /* * 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_selection_based_layer.h" #include #include "kis_debug.h" #include #include "kis_image.h" #include "kis_painter.h" #include "kis_default_bounds.h" #include "kis_selection.h" #include "kis_pixel_selection.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter_registry.h" #include "filter/kis_filter.h" #include "kis_raster_keyframe_channel.h" struct Q_DECL_HIDDEN KisSelectionBasedLayer::Private { public: Private() : useSelectionInProjection(true) {} Private(const Private &rhs) : useSelectionInProjection(rhs.useSelectionInProjection) {} KisSelectionSP selection; KisPaintDeviceSP paintDevice; bool useSelectionInProjection; }; KisSelectionBasedLayer::KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig) : KisLayer(image.data(), name, OPACITY_OPAQUE_U8), KisNodeFilterInterface(filterConfig), m_d(new Private()) { if (!selection) { initSelection(); } else { setInternalSelection(selection); } KisImageSP imageSP = image.toStrongRef(); if (!imageSP) { return; } m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(this, imageSP->colorSpace(), KisDefaultBoundsSP(new KisDefaultBounds(image)))); connect(imageSP.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } KisSelectionBasedLayer::KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs) : KisLayer(rhs) , KisIndirectPaintingSupport() , KisNodeFilterInterface(rhs) , m_d(new Private(*rhs.m_d)) { setInternalSelection(rhs.m_d->selection); m_d->paintDevice = new KisPaintDevice(*rhs.m_d->paintDevice.data()); } KisSelectionBasedLayer::~KisSelectionBasedLayer() { delete m_d; } void KisSelectionBasedLayer::initSelection() { m_d->selection = KisSelectionSP(new KisSelection(KisDefaultBoundsSP(new KisDefaultBounds(image())))); m_d->selection->pixelSelection()->setDefaultPixel(KoColor(Qt::white, m_d->selection->pixelSelection()->colorSpace())); m_d->selection->setParentNode(this); m_d->selection->updateProjection(); } void KisSelectionBasedLayer::slotImageSizeChanged() { if (m_d->selection) { /** * Make sure exactBounds() of the selection got recalculated after * the image changed */ m_d->selection->pixelSelection()->setDirty(); setDirty(); } } void KisSelectionBasedLayer::setImage(KisImageWSP image) { m_d->paintDevice->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); m_d->selection->pixelSelection()->setDefaultBounds(KisDefaultBoundsSP(new KisDefaultBounds(image))); KisLayer::setImage(image); connect(image.data(), SIGNAL(sigSizeChanged(QPointF,QPointF)), SLOT(slotImageSizeChanged())); } bool KisSelectionBasedLayer::allowAsChild(KisNodeSP node) const { return node->inherits("KisMask"); } KisPaintDeviceSP KisSelectionBasedLayer::original() const { return m_d->paintDevice; } KisPaintDeviceSP KisSelectionBasedLayer::paintDevice() const { return m_d->selection->pixelSelection(); } bool KisSelectionBasedLayer::needProjection() const { return m_d->selection; } void KisSelectionBasedLayer::setUseSelectionInProjection(bool value) const { m_d->useSelectionInProjection = value; } KisSelectionSP KisSelectionBasedLayer::fetchComposedInternalSelection(const QRect &rect) const { if (!m_d->selection) return KisSelectionSP(); m_d->selection->updateProjection(rect); KisSelectionSP tempSelection = m_d->selection; KisIndirectPaintingSupport::ReadLocker l(this); if (hasTemporaryTarget()) { /** * WARNING: we don't try to clone the selection entirely, because * it might be unsafe for shape selections. * * TODO: make cloning of vector selections safe! See a comment in * KisShapeSelection::clone(). */ tempSelection = new KisSelection(); KisPainter::copyAreaOptimized(rect.topLeft(), m_d->selection->pixelSelection(), tempSelection->pixelSelection(), rect); KisPainter gc2(tempSelection->pixelSelection()); setupTemporaryPainter(&gc2); gc2.bitBlt(rect.topLeft(), temporaryTarget(), rect); } return tempSelection; } void KisSelectionBasedLayer::copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const { KisSelectionSP tempSelection; if (m_d->useSelectionInProjection) { tempSelection = fetchComposedInternalSelection(rect); /** * When we paint with a selection, the deselected areas will *not* be * overwritten by copyAreaOptimized(), so we need to clear them beforehand */ projection->clear(rect); } KisPainter::copyAreaOptimized(rect.topLeft(), original, projection, rect, tempSelection); } QRect KisSelectionBasedLayer::cropChangeRectBySelection(const QRect &rect) const { return m_d->selection ? rect & m_d->selection->selectedRect() : rect; } QRect KisSelectionBasedLayer::needRect(const QRect &rect, PositionToFilthy pos) const { Q_UNUSED(pos); return rect; } void KisSelectionBasedLayer::resetCache() { KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } if (!m_d->paintDevice || *m_d->paintDevice->colorSpace() != *imageSP->colorSpace()) { m_d->paintDevice = KisPaintDeviceSP(new KisPaintDevice(KisNodeWSP(this), imageSP->colorSpace(), new KisDefaultBounds(image()))); } else { m_d->paintDevice->clear(); } } KisSelectionSP KisSelectionBasedLayer::internalSelection() const { return m_d->selection; } void KisSelectionBasedLayer::setInternalSelection(KisSelectionSP selection) { if (selection) { m_d->selection = new KisSelection(*selection.data()); m_d->selection->setParentNode(this); m_d->selection->setDefaultBounds(new KisDefaultBounds(image())); m_d->selection->updateProjection(); KisPixelSelectionSP pixelSelection = m_d->selection->pixelSelection(); if (pixelSelection->framesInterface()) { addKeyframeChannel(pixelSelection->keyframeChannel()); enableAnimation(); } KisImageSP imageSP = image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN(imageSP); if (m_d->selection->pixelSelection()->defaultBounds()->bounds() != imageSP->bounds()) { qWarning() << "WARNING: KisSelectionBasedLayer::setInternalSelection" << "New selection has suspicious default bounds"; qWarning() << "WARNING:" << ppVar(m_d->selection->pixelSelection()->defaultBounds()->bounds()); qWarning() << "WARNING:" << ppVar(imageSP->bounds()); } } else { m_d->selection = 0; } } qint32 KisSelectionBasedLayer::x() const { return m_d->selection ? m_d->selection->x() : 0; } qint32 KisSelectionBasedLayer::y() const { return m_d->selection ? m_d->selection->y() : 0; } void KisSelectionBasedLayer::setX(qint32 x) { if (m_d->selection) { m_d->selection->setX(x); } } void KisSelectionBasedLayer::setY(qint32 y) { if (m_d->selection) { m_d->selection->setY(y); } } KisKeyframeChannel *KisSelectionBasedLayer::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Content.id()) { KisRasterKeyframeChannel *contentChannel = m_d->selection->pixelSelection()->createKeyframeChannel(KisKeyframeChannel::Content); contentChannel->setFilenameSuffix(".pixelselection"); return contentChannel; } return KisLayer::requestKeyframeChannel(id); } void KisSelectionBasedLayer::setDirty() { Q_ASSERT(image()); KisImageSP imageSP = image().toStrongRef(); if (!imageSP) { return; } setDirty(imageSP->bounds()); } QRect KisSelectionBasedLayer::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 { KisImageSP image = this->image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); resultRect = image->bounds(); } return resultRect; } QRect KisSelectionBasedLayer::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 { KisImageSP image = this->image().toStrongRef(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(image, QRect()); resultRect = image->bounds(); } return resultRect; } -QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h) +QImage KisSelectionBasedLayer::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode) { KisSelectionSP originalSelection = internalSelection(); KisPaintDeviceSP originalDevice = original(); return originalDevice && originalSelection ? - originalDevice->createThumbnail(w, h, 1, + originalDevice->createThumbnail(w, h, aspectRatioMode, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } diff --git a/libs/image/kis_selection_based_layer.h b/libs/image/kis_selection_based_layer.h index 80a8eefdef..57604c0e44 100644 --- a/libs/image/kis_selection_based_layer.h +++ b/libs/image/kis_selection_based_layer.h @@ -1,211 +1,211 @@ /* * 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. */ #ifndef KIS_SELECTION_BASED_LAYER_H_ #define KIS_SELECTION_BASED_LAYER_H_ #include #include "kis_types.h" #include "kis_layer.h" #include "kis_indirect_painting_support.h" #include #include "kis_node_filter_interface.h" class KisFilterConfiguration; /** * @class KisSelectionBasedLayer * @brief Describes base behaviour for * selection base classes like KisAdjustmentLayer and KisGeneratorLayer. * These classes should have a persistent selection that controls * the area where filter/generators are applied. The area outside * this selection is not affected by the layer */ class KRITAIMAGE_EXPORT KisSelectionBasedLayer : public KisLayer, public KisIndirectPaintingSupport, public KisNodeFilterInterface { Q_OBJECT public: /** * creates a new layer with the given selection. * Note that the selection will be _copied_ (with COW, though). * @param image the image to set this layer to * @param name name of the layer * @param selection is a mask used by the layer to know * where to apply the filter/generator. */ KisSelectionBasedLayer(KisImageWSP image, const QString &name, KisSelectionSP selection, KisFilterConfigurationSP filterConfig); KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs); ~KisSelectionBasedLayer() override; /** * tells whether the @node can be a child of this layer * @param node to be connected node * @return tells if to be connected is a child of KisMask */ bool allowAsChild(KisNodeSP node) const override; void setImage(KisImageWSP image) override; KisPaintDeviceSP original() const override; KisPaintDeviceSP paintDevice() const override; bool needProjection() const override; /** * resets cached projection of lower layer to a new device * @return void */ virtual void resetCache(); /** * for KisLayer::setDirty(const KisRegion&) */ using KisLayer::setDirty; /** * Mark a layer as dirty. We can't use KisLayer's one * as our extent() function doesn't fit for this */ void setDirty() override; public: /** * Returns the selection of the layer * * Do not mix it with selection() which returns * the currently active selection of the image */ KisSelectionSP internalSelection() const; /** * sets the selection of this layer to a copy of * selection * @param selection the selection to set * @return void */ void setInternalSelection(KisSelectionSP selection); /** * When painted in indirect painting mode, the internal selection * might not contain actual selection, because a part of it is * stored on an indirect painting device. This method returns the * merged copy of the real selection. The area in \p rect only is * guaranteed to be prepared. The content of the rest of the * selection is undefined. */ KisSelectionSP fetchComposedInternalSelection(const QRect &rect) const; /** * gets this layer's x coordinate, taking selection into account * @return x-coordinate value */ qint32 x() const override; /** * gets this layer's y coordinate, taking selection into account * @return y-coordinate value */ qint32 y() const override; /** * sets this layer's y coordinate, taking selection into account * @param x x coordinate */ void setX(qint32 x) override; /** * sets this layer's y coordinate, taking selection into account * @param y y coordinate */ void setY(qint32 y) override; public: /** * gets an approximation of where the bounds on actual data * are in this layer, taking selection into account */ QRect extent() const override; /** * returns the exact bounds of where the actual data resides * in this layer, taking selection into account */ QRect exactBounds() const override; /** * copies the image and reformats it to thumbnail size * and returns the new thumbnail image. * @param w width of the thumbnail to create * @param h height of the thumbnail to create * @return the thumbnail image created. */ - QImage createThumbnail(qint32 w, qint32 h) override; + QImage createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) override; protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const override; // override from KisNode QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const override; protected: void initSelection(); QRect cropChangeRectBySelection(const QRect &rect) const; /** * Sets if the selection should be used in * copyOriginalToProjection() method. * * Default value is 'true'. The descendants should override it to * get desired behaviour. * * Must be called only once in the child's constructor */ void setUseSelectionInProjection(bool value) const; KisKeyframeChannel *requestKeyframeChannel(const QString &id) override; public Q_SLOTS: void slotImageSizeChanged(); /** * gets this layer. Overriddes function in * KisIndirectPaintingSupport * @return this AdjustmentLayer */ KisLayer* layer() { return this; } private: struct Private; Private * const m_d; }; #endif /* KIS_SELECTION_BASED_LAYER_H_ */ diff --git a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp index c9fe7bc192..b061f065c6 100644 --- a/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp +++ b/libs/libqml/plugins/kritasketchplugin/models/LayerModel.cpp @@ -1,977 +1,977 @@ /* This file is part of the KDE project * Copyright (C) 2012 Dan Leinir Turthra Jensen * * 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 "LayerModel.h" #include "LayerThumbProvider.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisSelectionActionsAdapter.h" #include struct LayerModelMetaInfo { LayerModelMetaInfo() : canMoveUp(false) , canMoveRight(false) , canMoveDown(false) , canMoveLeft(false) , depth(-1) {} bool canMoveUp; bool canMoveRight; bool canMoveDown; bool canMoveLeft; int depth; }; class LayerModel::Private { public: Private(LayerModel* qq) : q(qq) , nodeModel(new KisNodeModel(qq)) , aboutToRemoveRoots(false) , canvas(0) , nodeManager(0) , image(0) , activeNode(0) , declarativeEngine(0) , thumbProvider(0) , updateActiveLayerWithNewFilterConfigTimer(new QTimer(qq)) , imageChangedTimer(new QTimer(qq)) { QList tmpFilters = KisFilterRegistry::instance()->values(); Q_FOREACH (const KisFilterSP& filter, tmpFilters) { filters[filter.data()->id()] = filter.data(); } updateActiveLayerWithNewFilterConfigTimer->setInterval(0); updateActiveLayerWithNewFilterConfigTimer->setSingleShot(true); connect(updateActiveLayerWithNewFilterConfigTimer, SIGNAL(timeout()), qq, SLOT(updateActiveLayerWithNewFilterConfig())); imageChangedTimer->setInterval(250); imageChangedTimer->setSingleShot(true); connect(imageChangedTimer, SIGNAL(timeout()), qq, SLOT(imageHasChanged())); } LayerModel* q; QList layers; QHash layerMeta; KisNodeModel* nodeModel; bool aboutToRemoveRoots; KisViewManager* view; KisCanvas2* canvas; QScopedPointer selectionActionsAdapter; QPointer nodeManager; KisImageWSP image; KisNodeSP activeNode; QQmlEngine* declarativeEngine; LayerThumbProvider* thumbProvider; QHash filters; KisFilterConfigurationSP newConfig; QTimer* updateActiveLayerWithNewFilterConfigTimer; QTimer* imageChangedTimer; static int counter() { static int count = 0; return count++; } static QStringList layerClassNames() { QStringList list; list << "KisGroupLayer"; list << "KisPaintLayer"; list << "KisFilterMask"; list << "KisAdjustmentLayer"; return list; } int deepChildCount(KisNodeSP layer) { quint32 childCount = layer->childCount(); QList children = layer->childNodes(layerClassNames(), KoProperties()); for(quint32 i = 0; i < childCount; ++i) childCount += deepChildCount(children.at(i)); return childCount; } void rebuildLayerList(KisNodeSP layer = 0) { bool refreshingFromRoot = false; if (!image) { layers.clear(); return; } if (layer == 0) { refreshingFromRoot = true; layers.clear(); layer = image->rootLayer(); } // implementation node: The root node is not a visible node, and so // is never added to the list of layers QList children = layer->childNodes(layerClassNames(), KoProperties()); if (children.count() == 0) return; for(quint32 i = children.count(); i > 0; --i) { layers << children.at(i-1); rebuildLayerList(children.at(i-1)); } if (refreshingFromRoot) refreshLayerMovementAbilities(); } void refreshLayerMovementAbilities() { layerMeta.clear(); if (layers.count() == 0) return; for(int i = 0; i < layers.count(); ++i) { const KisNodeSP layer = layers.at(i); LayerModelMetaInfo ability; if (i > 0) ability.canMoveUp = true; if (i < layers.count() - 1) ability.canMoveDown = true; KisNodeSP parent = layer; while(parent) { ++ability.depth; parent = parent->parent(); } if (ability.depth > 1) ability.canMoveLeft = true; if (i < layers.count() - 1 && qobject_cast(layers.at(i + 1).constData())) ability.canMoveRight = true; layerMeta[layer] = ability; } } }; LayerModel::LayerModel(QObject* parent) : QAbstractListModel(parent) , d(new Private(this)) { connect(d->nodeModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)), this, SLOT(source_rowsAboutToBeInserted(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(source_rowsInserted(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(source_rowsAboutToBeRemoved(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(source_rowsRemoved(QModelIndex,int,int))); connect(d->nodeModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(source_dataChanged(QModelIndex,QModelIndex))); connect(d->nodeModel, SIGNAL(modelReset()), this, SLOT(source_modelReset())); connect(d->nodeModel, SIGNAL(layoutAboutToBeChanged()), this, SIGNAL(layoutAboutToBeChanged())); connect(d->nodeModel, SIGNAL(layoutChanged()), this, SIGNAL(layoutChanged())); } LayerModel::~LayerModel() { delete d; } QHash LayerModel::roleNames() const { QHash roles; roles[IconRole] = "icon"; roles[NameRole] = "name"; roles[ActiveLayerRole] = "activeLayer"; roles[OpacityRole] = "opacity"; roles[PercentOpacityRole] = "percentOpacity"; roles[VisibleRole] = "visible"; roles[CompositeDetailsRole] = "compositeDetails"; roles[FilterRole] = "filter"; roles[ChildCountRole] = "childCount"; roles[DeepChildCountRole] = "deepChildCount"; roles[DepthRole] = "depth"; roles[PreviousItemDepthRole] = "previousItemDepth"; roles[NextItemDepthRole] = "nextItemDepth"; roles[CanMoveDownRole] = "canMoveDown"; roles[CanMoveLeftRole] = "canMoveLeft"; roles[CanMoveRightRole] = "canMoveRight"; roles[CanMoveUpRole] = "canMoveUp"; return roles; } QObject* LayerModel::view() const { return d->view; } void LayerModel::setView(QObject *newView) { KisViewManager* view = qobject_cast(newView); // This has to happen very early, and we will be filling it back up again soon anyway... if (d->canvas) { d->canvas->disconnectCanvasObserver(this); disconnect(d->image, 0, this, 0); disconnect(d->nodeManager, 0, this, 0); disconnect(d->nodeModel, 0, d->nodeManager, 0); disconnect(d->nodeModel, SIGNAL(nodeActivated(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP))); d->image = 0; d->nodeManager = 0; d->layers.clear(); d->activeNode.clear(); d->canvas = 0; d->nodeModel->setDummiesFacade(0, 0, 0, 0, 0); d->selectionActionsAdapter.reset(); } d->view = view; if (!d->view) { return; } d->canvas = view->canvasBase(); d->thumbProvider = new LayerThumbProvider(); d->thumbProvider->setLayerModel(this); d->thumbProvider->setLayerID(Private::counter()); // QT5TODO: results in a crash d->declarativeEngine->addImageProvider(QString("layerthumb%1").arg(d->thumbProvider->layerID()), d->thumbProvider); if (d->canvas) { d->image = d->canvas->imageView()->image(); d->nodeManager = d->canvas->viewManager()->nodeManager(); KisDummiesFacadeBase *kritaDummiesFacade = dynamic_cast(d->canvas->imageView()->document()->shapeController()); KisShapeController *shapeController = dynamic_cast(d->canvas->imageView()->document()->shapeController()); d->selectionActionsAdapter.reset(new KisSelectionActionsAdapter(d->canvas->viewManager()->selectionManager())); d->nodeModel->setDummiesFacade(kritaDummiesFacade, d->image, shapeController, d->selectionActionsAdapter.data(), d->nodeManager); connect(d->image, SIGNAL(sigAboutToBeDeleted()), SLOT(notifyImageDeleted())); connect(d->image, SIGNAL(sigNodeChanged(KisNodeSP)), SLOT(nodeChanged(KisNodeSP))); connect(d->image, SIGNAL(sigImageUpdated(QRect)), SLOT(imageChanged())); connect(d->image, SIGNAL(sigRemoveNodeAsync(KisNodeSP)), SLOT(aboutToRemoveNode(KisNodeSP))); // cold start currentNodeChanged(d->nodeManager->activeNode()); // Connection KisNodeManager -> KisLayerBox connect(d->nodeManager, SIGNAL(sigUiNeedChangeActiveNode(KisNodeSP)), this, SLOT(currentNodeChanged(KisNodeSP))); d->rebuildLayerList(); beginResetModel(); endResetModel(); } } QObject* LayerModel::engine() const { return d->declarativeEngine; } void LayerModel::setEngine(QObject* newEngine) { d->declarativeEngine = qobject_cast(newEngine); emit engineChanged(); } QString LayerModel::fullImageThumbUrl() const { return QString("image://layerthumb%1/fullimage/%2").arg(d->thumbProvider->layerID()).arg(QDateTime::currentMSecsSinceEpoch()); } void LayerModel::currentNodeChanged(KisNodeSP newActiveNode) { if (!d->activeNode.isNull()) { QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode); source_dataChanged(oldIndex, oldIndex); } d->activeNode = newActiveNode; emitActiveChanges(); if (!d->activeNode.isNull()) { QModelIndex oldIndex = d->nodeModel->indexFromNode(d->activeNode); source_dataChanged(oldIndex, oldIndex); } } QVariant LayerModel::data(const QModelIndex& index, int role) const { QVariant data; if (index.isValid()) { index.internalPointer(); KisNodeSP node = d->layers.at(index.row()); if (node.isNull()) return data; KisNodeSP parent; switch(role) { case IconRole: if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_group-black.svg"); else if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_filter-black.svg"); else if (dynamic_cast(node.constData())) data = QLatin1String("../images/svg/icon-layer_filter-black.svg"); else // We add the currentMSecsSinceEpoch to ensure we force an update (even with cache turned // off, we still apparently get some caching behaviour on delegates in QML) data = QString("image://layerthumb%1/%2/%3").arg(d->thumbProvider->layerID()).arg(index.row()).arg(QDateTime::currentMSecsSinceEpoch()); break; case NameRole: data = node->name(); break; case ActiveLayerRole: data = (node == d->activeNode); break; case OpacityRole: data = node->opacity(); break; case PercentOpacityRole: data = node->percentOpacity(); break; case VisibleRole: data = node->visible(); break; case CompositeDetailsRole: // composite op goes here... if (node->compositeOp()) data = node->compositeOp()->description(); break; case FilterRole: break; case ChildCountRole: data = node->childNodes(d->layerClassNames(), KoProperties()).count(); break; case DeepChildCountRole: data = d->deepChildCount(d->layers.at(index.row())); break; case DepthRole: data = d->layerMeta[node.data()].depth; break; case PreviousItemDepthRole: if (index.row() == 0) data = -1; else data = d->layerMeta[d->layers[index.row() - 1].data()].depth; break; case NextItemDepthRole: if (index.row() == d->layers.count() - 1) data = -1; else data = d->layerMeta[d->layers[index.row() + 1].data()].depth; break; case CanMoveDownRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveDown; break; case CanMoveLeftRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveLeft; break; case CanMoveRightRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveRight; break; case CanMoveUpRole: data = (node == d->activeNode) && d->layerMeta[node.data()].canMoveUp; break; default: break; } } return data; } int LayerModel::rowCount(const QModelIndex& parent) const { if ( parent.isValid() ) { return 0; } return d->layers.count(); } QVariant LayerModel::headerData(int section, Qt::Orientation orientation, int role) const { return QAbstractItemModel::headerData(section, orientation, role); } void LayerModel::setActive(int index) { if (index > -1 && index < d->layers.count()) { KisNodeSP newNode = d->layers.at(index); d->nodeManager->slotUiActivatedNode(newNode); currentNodeChanged(newNode); } } void LayerModel::moveUp() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!d->nodeManager->activeNode()->nextSibling()) { //dbgKrita << "Active node apparently has no next sibling, however that has happened..."; if (!grandParent) return; //dbgKrita << "Node has grandparent"; if (!grandParent->parent() && node->inherits("KisMask")) return; //dbgKrita << "Node isn't a mask"; d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } else { //dbgKrita << "Move node directly"; d->nodeManager->lowerNode(); } } void LayerModel::moveDown() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); if (!d->nodeManager->activeNode()->prevSibling()) { //dbgKrita << "Active node apparently has no previous sibling, however that has happened..."; if (!grandParent) return; //dbgKrita << "Node has grandparent"; if (!grandParent->parent() && node->inherits("KisMask")) return; //dbgKrita << "Node isn't a mask"; d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { //dbgKrita << "Move node directly"; d->nodeManager->raiseNode(); } } void LayerModel::moveLeft() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = node->parent(); KisNodeSP grandParent = parent->parent(); quint16 nodeIndex = parent->index(node); if (!grandParent) return; if (!grandParent->parent() && node->inherits("KisMask")) return; if (nodeIndex <= parent->childCount() / 2) { d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent)); } else { d->nodeManager->moveNodeAt(node, grandParent, grandParent->index(parent) + 1); } } void LayerModel::moveRight() { KisNodeSP node = d->nodeManager->activeNode(); KisNodeSP parent = d->nodeManager->activeNode()->parent(); KisNodeSP newParent; int nodeIndex = parent->index(node); int indexAbove = nodeIndex + 1; int indexBelow = nodeIndex - 1; if (parent->at(indexBelow) && parent->at(indexBelow)->allowAsChild(node)) { newParent = parent->at(indexBelow); d->nodeManager->moveNodeAt(node, newParent, newParent->childCount()); } else if (parent->at(indexAbove) && parent->at(indexAbove)->allowAsChild(node)) { newParent = parent->at(indexAbove); d->nodeManager->moveNodeAt(node, newParent, 0); } else { return; } } void LayerModel::clear() { d->canvas->viewManager()->selectionManager()->clear(); } void LayerModel::clone() { d->nodeManager->duplicateActiveNode(); } void LayerModel::setLocked(int index, bool newLocked) { if (index > -1 && index < d->layers.count()) { if(d->layers[index]->userLocked() == newLocked) return; d->layers[index]->setUserLocked(newLocked); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } void LayerModel::setOpacity(int index, float newOpacity) { if (index > -1 && index < d->layers.count()) { if(qFuzzyCompare(d->layers[index]->opacity() + 1, newOpacity + 1)) return; d->layers[index]->setOpacity(newOpacity); d->layers[index]->setDirty(); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } void LayerModel::setVisible(int index, bool newVisible) { if (index > -1 && index < d->layers.count()) { KisBaseNode::PropertyList props = d->layers[index]->sectionModelProperties(); if(props[0].state == newVisible) return; KisBaseNode::Property prop = props[0]; prop.state = newVisible; props[0] = prop; d->nodeModel->setData( d->nodeModel->indexFromNode(d->layers[index]), QVariant::fromValue(props), KisNodeModel::PropertiesRole ); d->layers[index]->setDirty(d->layers[index]->extent()); QModelIndex idx = createIndex(index, 0); dataChanged(idx, idx); } } QImage LayerModel::layerThumbnail(QString layerID) const { // So, yeah, this is a complete cheatery hack. However, it ensures // we actually get updates when we want them (every time the image is supposed // to be changed). Had hoped we could avoid it, but apparently not. int index = layerID.section(QChar('/'), 0, 0).toInt(); QImage thumb; if (index > -1 && index < d->layers.count()) { if (d->thumbProvider) - thumb = d->layers[index]->createThumbnail(120, 120); + thumb = d->layers[index]->createThumbnail(120, 120, Qt::KeepAspectRatio); } return thumb; } void LayerModel::deleteCurrentLayer() { d->activeNode.clear(); d->nodeManager->removeNode(); } void LayerModel::deleteLayer(int index) { if (index > -1 && index < d->layers.count()) { if (d->activeNode == d->layers.at(index)) d->activeNode.clear(); d->nodeManager->slotUiActivatedNode(d->layers.at(index)); d->nodeManager->removeNode(); d->rebuildLayerList(); beginResetModel(); endResetModel(); } } void LayerModel::addLayer(int layerType) { switch(layerType) { case 0: d->nodeManager->createNode("KisPaintLayer"); break; case 1: d->nodeManager->createNode("KisGroupLayer"); break; case 2: d->nodeManager->createNode("KisFilterMask", true); break; default: break; } } void LayerModel::source_rowsAboutToBeInserted(QModelIndex /*p*/, int /*from*/, int /*to*/) { beginResetModel(); } void LayerModel::source_rowsInserted(QModelIndex /*p*/, int, int) { d->rebuildLayerList(); emit countChanged(); endResetModel(); } void LayerModel::source_rowsAboutToBeRemoved(QModelIndex /*p*/, int /*from*/, int /*to*/) { beginResetModel(); } void LayerModel::source_rowsRemoved(QModelIndex, int, int) { d->rebuildLayerList(); emit countChanged(); endResetModel(); } void LayerModel::source_dataChanged(QModelIndex /*tl*/, QModelIndex /*br*/) { QModelIndex top = createIndex(0, 0); QModelIndex bottom = createIndex(d->layers.count() - 1, 0); dataChanged(top, bottom); } void LayerModel::source_modelReset() { beginResetModel(); d->rebuildLayerList(); d->activeNode.clear(); if (d->layers.count() > 0) { d->nodeManager->slotUiActivatedNode(d->layers.at(0)); currentNodeChanged(d->layers.at(0)); } emit countChanged(); endResetModel(); } void LayerModel::notifyImageDeleted() { } void LayerModel::nodeChanged(KisNodeSP node) { QModelIndex index = createIndex(d->layers.indexOf(node), 0); dataChanged(index, index); } void LayerModel::imageChanged() { d->imageChangedTimer->start(); } void LayerModel::imageHasChanged() { QModelIndex top = createIndex(0, 0); QModelIndex bottom = createIndex(d->layers.count() - 1, 0); dataChanged(top, bottom); } void LayerModel::aboutToRemoveNode(KisNodeSP node) { Q_UNUSED(node) QTimer::singleShot(0, this, SLOT(source_modelReset())); } void LayerModel::emitActiveChanges() { emit activeFilterConfigChanged(); emit activeNameChanged(); emit activeTypeChanged(); emit activeCompositeOpChanged(); emit activeOpacityChanged(); emit activeVisibleChanged(); emit activeLockedChanged(); emit activeRChannelActiveChanged(); emit activeGChannelActiveChanged(); emit activeBChannelActiveChanged(); emit activeAChannelActiveChanged(); } QString LayerModel::activeName() const { if (d->activeNode.isNull()) return QString(); return d->activeNode->name(); } void LayerModel::setActiveName(QString newName) { if (d->activeNode.isNull()) return; d->activeNode->setName(newName); emit activeNameChanged(); } QString LayerModel::activeType() const { return d->activeNode->metaObject()->className(); } int LayerModel::activeCompositeOp() const { if (d->activeNode.isNull()) return 0; KoID entry(d->activeNode->compositeOp()->id()); QModelIndex idx = KisCompositeOpListModel::sharedInstance()->indexOf(entry); if (idx.isValid()) return idx.row(); return 0; } void LayerModel::setActiveCompositeOp(int newOp) { if (d->activeNode.isNull()) return; KoID entry; if (KisCompositeOpListModel::sharedInstance()->entryAt(entry, KisCompositeOpListModel::sharedInstance()->index(newOp))) { d->activeNode->setCompositeOpId(entry.id()); d->activeNode->setDirty(); emit activeCompositeOpChanged(); } } int LayerModel::activeOpacity() const { if (d->activeNode.isNull()) return 0; return d->activeNode->opacity(); } void LayerModel::setActiveOpacity(int newOpacity) { d->activeNode->setOpacity(newOpacity); d->activeNode->setDirty(); emit activeOpacityChanged(); } bool LayerModel::activeVisible() const { if (d->activeNode.isNull()) return false; return d->activeNode->visible(); } void LayerModel::setActiveVisible(bool newVisible) { if (d->activeNode.isNull()) return; setVisible(d->layers.indexOf(d->activeNode), newVisible); emit activeVisibleChanged(); } bool LayerModel::activeLocked() const { if (d->activeNode.isNull()) return false; return d->activeNode->userLocked(); } void LayerModel::setActiveLocked(bool newLocked) { if (d->activeNode.isNull()) return; d->activeNode->setUserLocked(newLocked); emit activeLockedChanged(); } bool LayerModel::activeAChannelActive() const { KisLayer* layer = qobject_cast(d->activeNode.data()); bool state = false; if (layer) state = !layer->alphaChannelDisabled(); return state; } void LayerModel::setActiveAChannelActive(bool newActive) { KisLayer* layer = qobject_cast(d->activeNode.data()); if (layer) { layer->disableAlphaChannel(!newActive); layer->setDirty(); emit activeAChannelActiveChanged(); } } bool getActiveChannel(KisNodeSP node, int channelIndex) { KisLayer* layer = qobject_cast(node.data()); bool flag = false; if (layer) { QBitArray flags = layer->channelFlags(); if (channelIndex < flags.size()) { flag = flags[channelIndex]; } } return flag; } void setChannelActive(KisNodeSP node, int channelIndex, bool newActive) { KisLayer* layer = qobject_cast(node.data()); if (layer) { QBitArray flags = layer->channelFlags(); flags.setBit(channelIndex, newActive); layer->setChannelFlags(flags); layer->setDirty(); } } bool LayerModel::activeBChannelActive() const { return getActiveChannel(d->activeNode, 2); } void LayerModel::setActiveBChannelActive(bool newActive) { setChannelActive(d->activeNode, 2, newActive); emit activeBChannelActiveChanged(); } bool LayerModel::activeGChannelActive() const { return getActiveChannel(d->activeNode, 1); } void LayerModel::setActiveGChannelActive(bool newActive) { setChannelActive(d->activeNode, 1, newActive); emit activeGChannelActiveChanged(); } bool LayerModel::activeRChannelActive() const { return getActiveChannel(d->activeNode, 0); } void LayerModel::setActiveRChannelActive(bool newActive) { setChannelActive(d->activeNode, 0, newActive); emit activeRChannelActiveChanged(); } QObject* LayerModel::activeFilterConfig() const { QMap props; QString filterId; KisFilterMask* filterMask = qobject_cast(d->activeNode.data()); if (filterMask) { props = filterMask->filter()->getProperties(); filterId = filterMask->filter()->name(); } else { KisAdjustmentLayer* adjustmentLayer = qobject_cast(d->activeNode.data()); if (adjustmentLayer) { props = adjustmentLayer->filter()->getProperties(); filterId = adjustmentLayer->filter()->name(); } } PropertyContainer* config = new PropertyContainer(filterId, 0); QMap::const_iterator i; for(i = props.constBegin(); i != props.constEnd(); ++i) { config->setProperty(i.key().toLatin1(), i.value()); //dbgKrita << "Getting active config..." << i.key() << i.value(); } return config; } void LayerModel::setActiveFilterConfig(QObject* newConfig) { if (d->activeNode.isNull()) return; PropertyContainer* config = qobject_cast(newConfig); if (!config) return; //dbgKrita << "Attempting to set new config" << config->name(); KisFilterConfigurationSP realConfig = d->filters.value(config->name())->factoryConfiguration(KisGlobalResourcesInterface::instance()); QMap::const_iterator i; for(i = realConfig->getProperties().constBegin(); i != realConfig->getProperties().constEnd(); ++i) { realConfig->setProperty(i.key(), config->property(i.key().toLatin1())); //dbgKrita << "Creating config..." << i.key() << i.value(); } // The following code causes sporadic crashes, and disabling causes leaks. So, leaks it must be, for now. // The cause is the lack of a smart pointer interface for passing filter configs around // Must be remedied, but for now... // if (d->newConfig) // delete(d->newConfig); d->newConfig = realConfig; //d->updateActiveLayerWithNewFilterConfigTimer->start(); updateActiveLayerWithNewFilterConfig(); } void LayerModel::updateActiveLayerWithNewFilterConfig() { if (!d->newConfig) return; //dbgKrita << "Setting new config..." << d->newConfig->name(); KisFilterMask* filterMask = qobject_cast(d->activeNode.data()); if (filterMask) { //dbgKrita << "Setting filter mask"; filterMask->setFilter(d->newConfig->cloneWithResourcesSnapshot()); } else { KisAdjustmentLayer* adjustmentLayer = qobject_cast(d->activeNode.data()); if (adjustmentLayer) { //dbgKrita << "Setting filter on adjustment layer"; adjustmentLayer->setFilter(d->newConfig->cloneWithResourcesSnapshot()); } else { //dbgKrita << "UNKNOWN, BAIL OUT!"; } } d->newConfig = 0; d->activeNode->setDirty(d->activeNode->extent()); d->image->setModified(); QTimer::singleShot(100, this, SIGNAL(activeFilterConfigChanged())); } diff --git a/libs/ui/kis_node_model.cpp b/libs/ui/kis_node_model.cpp index b6fe3bed6e..cc627f952e 100644 --- a/libs/ui/kis_node_model.cpp +++ b/libs/ui/kis_node_model.cpp @@ -1,753 +1,745 @@ /* * Copyright (c) 2007 Boudewijn Rempt * Copyright (c) 2008 Cyrille Berger * * 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_node_model.h" #include #include #include #include #include #include #include "kis_mimedata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_dummies_facade_base.h" #include "kis_node_dummies_graph.h" #include "kis_model_index_converter.h" #include "kis_model_index_converter_show_all.h" #include "kis_node_selection_adapter.h" #include "kis_node_insertion_adapter.h" #include "kis_node_manager.h" #include #include #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_signal_auto_connection.h" #include "kis_signal_compressor.h" struct KisNodeModel::Private { Private() : updateCompressor(100, KisSignalCompressor::FIRST_ACTIVE) {} KisImageWSP image; KisShapeController *shapeController = 0; KisNodeSelectionAdapter *nodeSelectionAdapter = 0; KisNodeInsertionAdapter *nodeInsertionAdapter = 0; KisSelectionActionsAdapter *selectionActionsAdapter = 0; KisNodeDisplayModeAdapter *nodeDisplayModeAdapter = 0; KisNodeManager *nodeManager = 0; KisSignalAutoConnectionsStore nodeDisplayModeAdapterConnections; QList updateQueue; KisSignalCompressor updateCompressor; KisModelIndexConverterBase *indexConverter = 0; QPointer dummiesFacade = 0; bool needFinishRemoveRows = false; bool needFinishInsertRows = false; bool showRootLayer = false; bool showGlobalSelection = false; QPersistentModelIndex activeNodeIndex; QPointer parentOfRemovedNode = 0; QSet dropEnabled; }; KisNodeModel::KisNodeModel(QObject * parent) : QAbstractItemModel(parent) , m_d(new Private) { connect(&m_d->updateCompressor, SIGNAL(timeout()), SLOT(processUpdateQueue())); } KisNodeModel::~KisNodeModel() { delete m_d->indexConverter; delete m_d; } KisNodeSP KisNodeModel::nodeFromIndex(const QModelIndex &index) const { Q_ASSERT(index.isValid()); KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index); if (dummy) { return dummy->node(); } return 0; } QModelIndex KisNodeModel::indexFromNode(KisNodeSP node) const { KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if(dummy) return m_d->indexConverter->indexFromDummy(dummy); return QModelIndex(); } bool KisNodeModel::belongsToIsolatedGroup(KisImageSP image, KisNodeSP node, KisDummiesFacadeBase *dummiesFacade) { KisNodeSP isolatedRoot = image->isolatedModeRoot(); if (!isolatedRoot) return true; KisNodeDummy *isolatedRootDummy = dummiesFacade->dummyForNode(isolatedRoot); KisNodeDummy *dummy = dummiesFacade->dummyForNode(node); while (dummy) { if (dummy == isolatedRootDummy) { return true; } dummy = dummy->parent(); } return false; } bool KisNodeModel::belongsToIsolatedGroup(KisNodeSP node) const { return belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade); } void KisNodeModel::resetIndexConverter() { delete m_d->indexConverter; m_d->indexConverter = 0; if(m_d->dummiesFacade) { m_d->indexConverter = createIndexConverter(); } } KisModelIndexConverterBase *KisNodeModel::createIndexConverter() { if(m_d->showRootLayer) { return new KisModelIndexConverterShowAll(m_d->dummiesFacade, this); } else { return new KisModelIndexConverter(m_d->dummiesFacade, this, m_d->showGlobalSelection); } } void KisNodeModel::regenerateItems(KisNodeDummy *dummy) { const QModelIndex &index = m_d->indexConverter->indexFromDummy(dummy); emit dataChanged(index, index); dummy = dummy->firstChild(); while (dummy) { regenerateItems(dummy); dummy = dummy->nextSibling(); } } void KisNodeModel::slotIsolatedModeChanged() { regenerateItems(m_d->dummiesFacade->rootDummy()); } bool KisNodeModel::showGlobalSelection() const { return m_d->nodeDisplayModeAdapter ? m_d->nodeDisplayModeAdapter->showGlobalSelectionMask() : false; } void KisNodeModel::setShowGlobalSelection(bool value) { if (m_d->nodeDisplayModeAdapter) { m_d->nodeDisplayModeAdapter->setShowGlobalSelectionMask(value); } } void KisNodeModel::slotNodeDisplayModeChanged(bool showRootNode, bool showGlobalSelectionMask) { const bool oldShowRootLayer = m_d->showRootLayer; const bool oldShowGlobalSelection = m_d->showGlobalSelection; m_d->showRootLayer = showRootNode; m_d->showGlobalSelection = showGlobalSelectionMask; if (m_d->showRootLayer != oldShowRootLayer || m_d->showGlobalSelection != oldShowGlobalSelection) { resetIndexConverter(); beginResetModel(); endResetModel(); } } void KisNodeModel::progressPercentageChanged(int, const KisNodeSP node) { if(!m_d->dummiesFacade) return; // Need to check here as the node might already be removed, but there might // still be some signals arriving from another thread if (m_d->dummiesFacade->hasDummyForNode(node)) { QModelIndex index = indexFromNode(node); emit dataChanged(index, index); } } KisModelIndexConverterBase * KisNodeModel::indexConverter() const { return m_d->indexConverter; } KisDummiesFacadeBase *KisNodeModel::dummiesFacade() const { return m_d->dummiesFacade; } void KisNodeModel::connectDummy(KisNodeDummy *dummy, bool needConnect) { KisNodeSP node = dummy->node(); if (!node) { qWarning() << "Dummy node has no node!" << dummy << dummy->node(); return; } KisNodeProgressProxy *progressProxy = node->nodeProgressProxy(); if(progressProxy) { if(needConnect) { connect(progressProxy, SIGNAL(percentageChanged(int,KisNodeSP)), SLOT(progressPercentageChanged(int,KisNodeSP))); } else { progressProxy->disconnect(this); } } } void KisNodeModel::connectDummies(KisNodeDummy *dummy, bool needConnect) { connectDummy(dummy, needConnect); dummy = dummy->firstChild(); while(dummy) { connectDummies(dummy, needConnect); dummy = dummy->nextSibling(); } } void KisNodeModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageWSP image, KisShapeController *shapeController, KisSelectionActionsAdapter *selectionActionsAdapter, KisNodeManager *nodeManager) { QPointer oldDummiesFacade(m_d->dummiesFacade); KisShapeController *oldShapeController = m_d->shapeController; m_d->shapeController = shapeController; m_d->nodeManager = nodeManager; m_d->nodeSelectionAdapter = nodeManager ? nodeManager->nodeSelectionAdapter() : nullptr; m_d->nodeInsertionAdapter = nodeManager ? nodeManager->nodeInsertionAdapter() : nullptr; m_d->selectionActionsAdapter = selectionActionsAdapter; m_d->nodeDisplayModeAdapterConnections.clear(); m_d->nodeDisplayModeAdapter = nodeManager ? nodeManager->nodeDisplayModeAdapter() : nullptr; if (m_d->nodeDisplayModeAdapter) { m_d->nodeDisplayModeAdapterConnections.addConnection( m_d->nodeDisplayModeAdapter, SIGNAL(sigNodeDisplayModeChanged(bool,bool)), this, SLOT(slotNodeDisplayModeChanged(bool,bool))); // cold initialization m_d->showGlobalSelection = m_d->nodeDisplayModeAdapter->showGlobalSelectionMask(); m_d->showRootLayer = false; } if (oldDummiesFacade && m_d->image) { m_d->image->disconnect(this); oldDummiesFacade->disconnect(this); connectDummies(m_d->dummiesFacade->rootDummy(), false); } m_d->image = image; m_d->dummiesFacade = dummiesFacade; m_d->parentOfRemovedNode = 0; resetIndexConverter(); if (m_d->dummiesFacade) { KisNodeDummy *rootDummy = m_d->dummiesFacade->rootDummy(); if (rootDummy) { connectDummies(rootDummy, true); } connect(m_d->dummiesFacade, SIGNAL(sigBeginInsertDummy(KisNodeDummy*,int,QString)), SLOT(slotBeginInsertDummy(KisNodeDummy*,int,QString))); connect(m_d->dummiesFacade, SIGNAL(sigEndInsertDummy(KisNodeDummy*)), SLOT(slotEndInsertDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigBeginRemoveDummy(KisNodeDummy*)), SLOT(slotBeginRemoveDummy(KisNodeDummy*))); connect(m_d->dummiesFacade, SIGNAL(sigEndRemoveDummy()), SLOT(slotEndRemoveDummy())); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); if (m_d->image.isValid()) { connect(m_d->image, SIGNAL(sigIsolatedModeChanged()), SLOT(slotIsolatedModeChanged())); } } if (m_d->dummiesFacade != oldDummiesFacade || m_d->shapeController != oldShapeController) { beginResetModel(); endResetModel(); } } void KisNodeModel::slotBeginInsertDummy(KisNodeDummy *parent, int index, const QString &metaObjectType) { int row = 0; QModelIndex parentIndex; bool willAdd = m_d->indexConverter->indexFromAddedDummy(parent, index, metaObjectType, parentIndex, row); if(willAdd) { beginInsertRows(parentIndex, row, row); m_d->needFinishInsertRows = true; } } void KisNodeModel::slotEndInsertDummy(KisNodeDummy *dummy) { if(m_d->needFinishInsertRows) { connectDummy(dummy, true); endInsertRows(); m_d->needFinishInsertRows = false; } } void KisNodeModel::slotBeginRemoveDummy(KisNodeDummy *dummy) { if (!dummy) return; // FIXME: is it really what we want? m_d->updateCompressor.stop(); m_d->updateQueue.clear(); m_d->parentOfRemovedNode = dummy->parent(); QModelIndex parentIndex; if (m_d->parentOfRemovedNode) { parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode); } QModelIndex itemIndex = m_d->indexConverter->indexFromDummy(dummy); if (itemIndex.isValid()) { connectDummy(dummy, false); beginRemoveRows(parentIndex, itemIndex.row(), itemIndex.row()); m_d->needFinishRemoveRows = true; } } void KisNodeModel::slotEndRemoveDummy() { if(m_d->needFinishRemoveRows) { endRemoveRows(); m_d->needFinishRemoveRows = false; } } void KisNodeModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateCompressor.start(); } void addChangedIndex(const QModelIndex &idx, QSet *indexes) { if (!idx.isValid() || indexes->contains(idx)) return; indexes->insert(idx); const int rowCount = idx.model()->rowCount(idx); for (int i = 0; i < rowCount; i++) { addChangedIndex(idx.model()->index(i, 0, idx), indexes); } } void KisNodeModel::processUpdateQueue() { QSet indexes; Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { QModelIndex index = m_d->indexConverter->indexFromDummy(dummy); addChangedIndex(index, &indexes); } Q_FOREACH (const QModelIndex &index, indexes) { emit dataChanged(index, index); } m_d->updateQueue.clear(); } QModelIndex KisNodeModel::index(int row, int col, const QModelIndex &parent) const { if(!m_d->dummiesFacade || !hasIndex(row, col, parent)) return QModelIndex(); QModelIndex itemIndex; KisNodeDummy *dummy = m_d->indexConverter->dummyFromRow(row, parent); if(dummy) { itemIndex = m_d->indexConverter->indexFromDummy(dummy); } return itemIndex; } int KisNodeModel::rowCount(const QModelIndex &parent) const { if(!m_d->dummiesFacade) return 0; return m_d->indexConverter->rowCount(parent); } int KisNodeModel::columnCount(const QModelIndex&) const { return 1; } QModelIndex KisNodeModel::parent(const QModelIndex &index) const { if(!m_d->dummiesFacade || !index.isValid()) return QModelIndex(); KisNodeDummy *dummy = m_d->indexConverter->dummyFromIndex(index); KisNodeDummy *parentDummy = dummy->parent(); QModelIndex parentIndex; if(parentDummy) { parentIndex = m_d->indexConverter->indexFromDummy(parentDummy); } return parentIndex; } QVariant KisNodeModel::data(const QModelIndex &index, int role) const { if (!m_d->dummiesFacade || !index.isValid() || !m_d->image.isValid()) return QVariant(); KisNodeSP node = nodeFromIndex(index); switch (role) { case Qt::DisplayRole: return node->name(); case Qt::DecorationRole: return node->icon(); case Qt::EditRole: return node->name(); case Qt::SizeHintRole: return m_d->image->size(); // FIXME case Qt::TextColorRole: return belongsToIsolatedGroup(node) && !node->projectionLeaf()->isDroppedNode() ? QVariant() : QVariant(QColor(Qt::gray)); case Qt::FontRole: { QFont baseFont; if (node->projectionLeaf()->isDroppedNode()) { baseFont.setStrikeOut(true); } if (m_d->activeNodeIndex == index) { baseFont.setBold(true); } return baseFont; } case KisNodeModel::PropertiesRole: return QVariant::fromValue(node->sectionModelProperties()); case KisNodeModel::AspectRatioRole: return double(m_d->image->width()) / m_d->image->height(); case KisNodeModel::ProgressRole: { KisNodeProgressProxy *proxy = node->nodeProgressProxy(); return proxy ? proxy->percentage() : -1; } case KisNodeModel::ActiveRole: { return m_d->activeNodeIndex == index; } case KisNodeModel::ShouldGrayOutRole: { return !node->visible(true); } case KisNodeModel::ColorLabelIndexRole: { return node->colorLabelIndex(); } case KisNodeModel::DropReasonRole: { QString result; KisProjectionLeaf::NodeDropReason reason = node->projectionLeaf()->dropReason(); if (reason == KisProjectionLeaf::DropPassThroughMask) { result = i18nc("@info:tooltip", "Disabled: masks on pass-through groups are not supported!"); } else if (reason == KisProjectionLeaf::DropPassThroughClone) { result = i18nc("@info:tooltip", "Disabled: cloning pass-through groups is not supported!"); } return result; } default: if (role >= int(KisNodeModel::BeginThumbnailRole) && belongsToIsolatedGroup(node)) { const int maxSize = role - int(KisNodeModel::BeginThumbnailRole); - - QSize size = node->extent().size(); - size.scale(maxSize, maxSize, Qt::KeepAspectRatio); - if (size.width() == 0 || size.height() == 0) { - // No thumbnail can be shown if there isn't width or height... - return QVariant(); - } - - return node->createThumbnail(size.width(), size.height()); + return node->createThumbnail(maxSize, maxSize, Qt::KeepAspectRatio); } else { return QVariant(); } } return QVariant(); } Qt::ItemFlags KisNodeModel::flags(const QModelIndex &index) const { if(!m_d->dummiesFacade || !index.isValid()) return Qt::ItemIsDropEnabled; Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEditable; if (m_d->dropEnabled.contains(index.internalId())) { flags |= Qt::ItemIsDropEnabled; } return flags; } bool KisNodeModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == KisNodeModel::DropEnabled) { const QMimeData *mimeData = static_cast(value.value()); setDropEnabled(mimeData); return true; } if (role == KisNodeModel::ActiveRole || role == KisNodeModel::AlternateActiveRole) { QModelIndex parentIndex; if (!index.isValid() && m_d->parentOfRemovedNode && m_d->dummiesFacade && m_d->indexConverter) { parentIndex = m_d->indexConverter->indexFromDummy(m_d->parentOfRemovedNode); m_d->parentOfRemovedNode = 0; } KisNodeSP activatedNode; if (index.isValid() && value.toBool()) { activatedNode = nodeFromIndex(index); } else if (parentIndex.isValid() && value.toBool()) { activatedNode = nodeFromIndex(parentIndex); } else { activatedNode = 0; } QModelIndex newActiveNode = activatedNode ? indexFromNode(activatedNode) : QModelIndex(); if (role == KisNodeModel::ActiveRole && value.toBool() && m_d->activeNodeIndex == newActiveNode) { return true; } m_d->activeNodeIndex = newActiveNode; if (m_d->nodeSelectionAdapter) { m_d->nodeSelectionAdapter->setActiveNode(activatedNode); } if (role == KisNodeModel::AlternateActiveRole) { emit toggleIsolateActiveNode(); } emit dataChanged(index, index); return true; } if(!m_d->dummiesFacade || !index.isValid()) return false; bool result = true; bool shouldUpdate = true; bool shouldUpdateRecursively = false; KisNodeSP node = nodeFromIndex(index); switch (role) { case Qt::DisplayRole: case Qt::EditRole: m_d->nodeManager->setNodeName(node, value.toString()); break; case KisNodeModel::PropertiesRole: { // don't record undo/redo for visibility, locked or alpha locked changes KisBaseNode::PropertyList proplist = value.value(); m_d->nodeManager->trySetNodeProperties(node, m_d->image, proplist); shouldUpdateRecursively = true; break; } case KisNodeModel::SelectOpaqueRole: if (node && m_d->selectionActionsAdapter) { SelectionAction action = SelectionAction(value.toInt()); m_d->selectionActionsAdapter->selectOpaqueOnNode(node, action); } shouldUpdate = false; break; default: result = false; } if (result && shouldUpdate) { if (shouldUpdateRecursively) { QSet indexes; addChangedIndex(index, &indexes); Q_FOREACH (const QModelIndex &index, indexes) { emit dataChanged(index, index); } } else { emit dataChanged(index, index); } } return result; } Qt::DropActions KisNodeModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction; } Qt::DropActions KisNodeModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } bool KisNodeModel::hasDummiesFacade() { return m_d->dummiesFacade != 0; } QStringList KisNodeModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-node"); types << QLatin1String("application/x-qt-image"); return types; } QMimeData * KisNodeModel::mimeData(const QModelIndexList &indexes) const { KisNodeList nodes; Q_FOREACH (const QModelIndex &idx, indexes) { nodes << nodeFromIndex(idx); } return KisMimeData::mimeForLayers(nodes, m_d->image); } bool KisNodeModel::dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) { Q_UNUSED(column); bool copyNode = (action == Qt::CopyAction); KisNodeDummy *parentDummy = 0; KisNodeDummy *aboveThisDummy = 0; parentDummy = parent.isValid() ? m_d->indexConverter->dummyFromIndex(parent) : m_d->dummiesFacade->rootDummy(); if (row == -1) { aboveThisDummy = parent.isValid() ? parentDummy->lastChild() : 0; } else { aboveThisDummy = row < m_d->indexConverter->rowCount(parent) ? m_d->indexConverter->dummyFromRow(row, parent) : 0; } return KisMimeData::insertMimeLayers(data, m_d->image, m_d->shapeController, parentDummy, aboveThisDummy, copyNode, m_d->nodeInsertionAdapter); } bool KisNodeModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { // drop occurred on an item. always return true as returning false will mess up // QT5's drag handling (see KisNodeModel::setDropEnabled). return true; } else { return QAbstractItemModel::canDropMimeData(data, action, row, column, parent); } } void KisNodeModel::setDropEnabled(const QMimeData *data) { // what happens here should really happen in KisNodeModel::canDropMimeData(), but QT5 // will mess up if an item's Qt::ItemIsDropEnabled does not match what is returned by // canDropMimeData; specifically, if we set the flag, but decide in canDropMimeData() // later on that an "onto" drag is not allowed, QT will display an drop indicator for // insertion, but not perform any drop when the mouse is released. // the only robust implementation seems to set all flags correctly, which is done here. bool copyNode = false; KisNodeList nodes = KisMimeData::loadNodesFast(data, m_d->image, m_d->shapeController, copyNode); m_d->dropEnabled.clear(); updateDropEnabled(nodes); } void KisNodeModel::updateDropEnabled(const QList &nodes, QModelIndex parent) { for (int r = 0; r < rowCount(parent); r++) { QModelIndex idx = index(r, 0, parent); KisNodeSP target = nodeFromIndex(idx); bool dropEnabled = true; Q_FOREACH (const KisNodeSP &node, nodes) { if (!target->allowAsChild(node)) { dropEnabled = false; break; } } if (dropEnabled) { m_d->dropEnabled.insert(idx.internalId()); } emit dataChanged(idx, idx); // indicate to QT that flags() have changed if (hasChildren(idx)) { updateDropEnabled(nodes, idx); } } } diff --git a/plugins/dockers/animation/timeline_frames_model.cpp b/plugins/dockers/animation/timeline_frames_model.cpp index d672ae59a7..480e4e6b1a 100644 --- a/plugins/dockers/animation/timeline_frames_model.cpp +++ b/plugins/dockers/animation/timeline_frames_model.cpp @@ -1,1027 +1,1021 @@ /* * Copyright (c) 2015 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 "timeline_frames_model.h" #include #include #include #include #include #include #include "kis_layer.h" #include "kis_config.h" #include "kis_global.h" #include "kis_debug.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_undo_adapter.h" #include "kis_node_dummies_graph.h" #include "kis_dummies_facade_base.h" #include "KisNodeDisplayModeAdapter.h" #include "kis_signal_compressor.h" #include "kis_signal_compressor_with_param.h" #include "kis_keyframe_channel.h" #include "kundo2command.h" #include "kis_post_execution_undo_adapter.h" #include #include #include "kis_animation_utils.h" #include "timeline_color_scheme.h" #include "kis_node_model.h" #include "kis_projection_leaf.h" #include "kis_time_range.h" #include "kis_node_view_color_scheme.h" #include "krita_utils.h" #include "KisPart.h" #include #include "KisDocument.h" #include "KisViewManager.h" #include "kis_processing_applicator.h" #include #include "kis_node_uuid_info.h" struct TimelineFramesModel::Private { Private() : activeLayerIndex(0), dummiesFacade(0), needFinishInsertRows(false), needFinishRemoveRows(false), updateTimer(200, KisSignalCompressor::FIRST_INACTIVE), parentOfRemovedNode(0) {} int activeLayerIndex; QPointer dummiesFacade; KisImageWSP image; bool needFinishInsertRows; bool needFinishRemoveRows; QList updateQueue; KisSignalCompressor updateTimer; KisNodeDummy* parentOfRemovedNode; QScopedPointer converter; QScopedPointer nodeInterface; QPersistentModelIndex lastClickedIndex; QVariant layerName(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); return dummy->node()->name(); } bool layerEditable(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return true; return dummy->node()->visible() && !dummy->node()->userLocked(); } bool frameExists(int row, int column) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); return (primaryChannel && primaryChannel->keyframeAt(column)); } bool frameHasContent(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return false; // first check if we are a key frame KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return false; return frame->hasContent(); } bool specialKeyframeExists(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; Q_FOREACH(KisKeyframeChannel *channel, dummy->node()->keyframeChannels()) { if (channel->id() != KisKeyframeChannel::Content.id() && channel->keyframeAt(column)) { return true; } } return false; } int frameColorLabel(int row, int column) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return -1; KisKeyframeSP frame = primaryChannel->activeKeyframeAt(column); if (!frame) return -1; return frame->colorLabel(); } void setFrameColorLabel(int row, int column, int color) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return; KisKeyframeChannel *primaryChannel = dummy->node()->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!primaryChannel) return; KisKeyframeSP frame = primaryChannel->keyframeAt(column); if (!frame) return; frame->setColorLabel(color); } int layerColorLabel(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return -1; return dummy->node()->colorLabelIndex(); } QVariant layerProperties(int row) const { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return QVariant(); PropertyList props = dummy->node()->sectionModelProperties(); return QVariant::fromValue(props); } bool setLayerProperties(int row, PropertyList props) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; nodeInterface->setNodeProperties(dummy->node(), image, props); return true; } bool addKeyframe(int row, int column, bool copy) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) return false; KisAnimationUtils::createKeyframeLazy(image, node, KisKeyframeChannel::Content.id(), column, copy); return true; } bool addNewLayer(int row) { Q_UNUSED(row); if (nodeInterface) { KisLayerSP layer = nodeInterface->addPaintLayer(); layer->setPinnedToTimeline(true); } return true; } bool removeLayer(int row) { KisNodeDummy *dummy = converter->dummyFromRow(row); if (!dummy) return false; if (nodeInterface) { nodeInterface->removeNode(dummy->node()); } return true; } }; TimelineFramesModel::TimelineFramesModel(QObject *parent) : ModelWithExternalNotifications(parent), m_d(new Private) { connect(&m_d->updateTimer, SIGNAL(timeout()), SLOT(processUpdateQueue())); } TimelineFramesModel::~TimelineFramesModel() { } bool TimelineFramesModel::hasConnectionToCanvas() const { return m_d->dummiesFacade; } void TimelineFramesModel::setNodeManipulationInterface(NodeManipulationInterface *iface) { m_d->nodeInterface.reset(iface); } KisNodeSP TimelineFramesModel::nodeAt(QModelIndex index) const { /** * The dummy might not exist because the user could (quickly) change * active layer and the list of the nodes in m_d->converter will change. */ KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); return dummy ? dummy->node() : 0; } QMap TimelineFramesModel::channelsAt(QModelIndex index) const { KisNodeDummy *srcDummy = m_d->converter->dummyFromRow(index.row()); return srcDummy->node()->keyframeChannels(); } void TimelineFramesModel::setDummiesFacade(KisDummiesFacadeBase *dummiesFacade, KisImageSP image, KisNodeDisplayModeAdapter *displayModeAdapter) { KisDummiesFacadeBase *oldDummiesFacade = m_d->dummiesFacade; if (m_d->dummiesFacade && m_d->image) { m_d->image->animationInterface()->disconnect(this); m_d->image->disconnect(this); m_d->dummiesFacade->disconnect(this); } m_d->image = image; KisTimeBasedItemModel::setImage(image); m_d->dummiesFacade = dummiesFacade; m_d->converter.reset(); if (m_d->dummiesFacade) { m_d->converter.reset(new TimelineNodeListKeeper(this, m_d->dummiesFacade, displayModeAdapter)); connect(m_d->dummiesFacade, SIGNAL(sigDummyChanged(KisNodeDummy*)), SLOT(slotDummyChanged(KisNodeDummy*))); connect(m_d->image->animationInterface(), SIGNAL(sigFullClipRangeChanged()), SIGNAL(sigInfiniteTimelineUpdateNeeded())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioChannelChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image->animationInterface(), SIGNAL(sigAudioVolumeChanged()), SIGNAL(sigAudioChannelChanged())); connect(m_d->image, SIGNAL(sigImageModified()), SLOT(slotImageContentChanged())); } if (m_d->dummiesFacade != oldDummiesFacade) { beginResetModel(); endResetModel(); } if (m_d->dummiesFacade) { emit sigInfiniteTimelineUpdateNeeded(); emit sigAudioChannelChanged(); slotCurrentTimeChanged(m_d->image->animationInterface()->currentUITime()); } } void TimelineFramesModel::slotDummyChanged(KisNodeDummy *dummy) { if (!m_d->updateQueue.contains(dummy)) { m_d->updateQueue.append(dummy); } m_d->updateTimer.start(); } void TimelineFramesModel::slotImageContentChanged() { if (m_d->activeLayerIndex < 0) return; KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); if (!dummy) return; slotDummyChanged(dummy); } void TimelineFramesModel::processUpdateQueue() { if (!m_d->converter) return; Q_FOREACH (KisNodeDummy *dummy, m_d->updateQueue) { int row = m_d->converter->rowForDummy(dummy); if (row >= 0) { emit headerDataChanged (Qt::Vertical, row, row); emit dataChanged(this->index(row, 0), this->index(row, columnCount() - 1)); } } m_d->updateQueue.clear(); } void TimelineFramesModel::slotCurrentNodeChanged(KisNodeSP node) { if (!node) { m_d->activeLayerIndex = -1; return; } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(node); if (!dummy) { // It's perfectly normal that dummyForNode returns 0; that happens // when views get activated while Krita is closing down. return; } m_d->converter->updateActiveDummy(dummy); const int row = m_d->converter->rowForDummy(dummy); if (row < 0) { qWarning() << "WARNING: TimelineFramesModel::slotCurrentNodeChanged: node not found!"; } if (row >= 0 && m_d->activeLayerIndex != row) { setData(index(row, 0), true, ActiveLayerRole); } } int TimelineFramesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if(!m_d->dummiesFacade) return 0; return m_d->converter->rowCount(); } QVariant TimelineFramesModel::data(const QModelIndex &index, int role) const { if(!m_d->dummiesFacade) return QVariant(); switch (role) { case ActiveLayerRole: { return index.row() == m_d->activeLayerIndex; } case FrameEditableRole: { return m_d->layerEditable(index.row()); } case FrameHasContent: { return m_d->frameHasContent(index.row(), index.column()); } case FrameExistsRole: { return m_d->frameExists(index.row(), index.column()); } case SpecialKeyframeExists: { return m_d->specialKeyframeExists(index.row(), index.column()); } case FrameColorLabelIndexRole: { int label = m_d->frameColorLabel(index.row(), index.column()); return label > 0 ? label : QVariant(); } case Qt::DisplayRole: { return m_d->layerName(index.row()); } case Qt::TextAlignmentRole: { return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); } case Qt::UserRole + KisResourceModel::LargeThumbnail: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(index.row()); if (!dummy) { return QVariant(); } const int maxSize = 200; - QSize size = dummy->node()->extent().size(); - size.scale(maxSize, maxSize, Qt::KeepAspectRatio); - if (size.width() == 0 || size.height() == 0) { - // No thumbnail can be shown if there isn't width or height... - return QVariant(); - } - QImage image(dummy->node()->createThumbnailForFrame(size.width(), size.height(), index.column())); + QImage image(dummy->node()->createThumbnailForFrame(maxSize, maxSize, index.column(), Qt::KeepAspectRatio)); return image; } } return ModelWithExternalNotifications::data(index, role); } bool TimelineFramesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || !m_d->dummiesFacade) return false; switch (role) { case ActiveLayerRole: { if (value.toBool() && index.row() != m_d->activeLayerIndex) { int prevLayer = m_d->activeLayerIndex; m_d->activeLayerIndex = index.row(); emit dataChanged(this->index(prevLayer, 0), this->index(prevLayer, columnCount() - 1)); emit dataChanged(this->index(m_d->activeLayerIndex, 0), this->index(m_d->activeLayerIndex, columnCount() - 1)); emit headerDataChanged(Qt::Vertical, prevLayer, prevLayer); emit headerDataChanged(Qt::Vertical, m_d->activeLayerIndex, m_d->activeLayerIndex); KisNodeDummy *dummy = m_d->converter->dummyFromRow(m_d->activeLayerIndex); KIS_ASSERT_RECOVER(dummy) { return true; } emit requestCurrentNodeChanged(dummy->node()); emit sigEnsureRowVisible(m_d->activeLayerIndex); } break; } case FrameColorLabelIndexRole: { m_d->setFrameColorLabel(index.row(), index.column(), value.toInt()); } break; } return ModelWithExternalNotifications::setData(index, value, role); } QVariant TimelineFramesModel::headerData(int section, Qt::Orientation orientation, int role) const { if(!m_d->dummiesFacade) return QVariant(); if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: return section == m_d->activeLayerIndex; case Qt::DisplayRole: { QVariant value = headerData(section, orientation, Qt::ToolTipRole); if (!value.isValid()) return value; QString name = value.toString(); const int maxNameSize = 13; if (name.size() > maxNameSize) { name = QString("%1...").arg(name.left(maxNameSize)); } return name; } case Qt::TextColorRole: { // WARNING: this role doesn't work for header views! Use // bold font to show isolated mode instead! return QVariant(); } case Qt::FontRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); KisNodeSP node = dummy->node(); QFont baseFont; if (node->projectionLeaf()->isDroppedNode()) { baseFont.setStrikeOut(true); } else if (m_d->image && m_d->image->isolatedModeRoot() && KisNodeModel::belongsToIsolatedGroup(m_d->image, node, m_d->dummiesFacade)) { baseFont.setBold(true); } return baseFont; } case Qt::ToolTipRole: { return m_d->layerName(section); } case TimelinePropertiesRole: { return QVariant::fromValue(m_d->layerProperties(section)); } case OtherLayersRole: { TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); return QVariant::fromValue(list); } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return QVariant(); return dummy->node()->isPinnedToTimeline(); } case Qt::BackgroundRole: { int label = m_d->layerColorLabel(section); if (label > 0) { KisNodeViewColorScheme scm; QColor color = scm.colorLabel(label); QPalette pal = qApp->palette(); color = KritaUtils::blendColors(color, pal.color(QPalette::Button), 0.3); return QBrush(color); } else { return QVariant(); } } } } return ModelWithExternalNotifications::headerData(section, orientation, role); } bool TimelineFramesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) { if (!m_d->dummiesFacade) return false; if (orientation == Qt::Vertical) { switch (role) { case ActiveLayerRole: { setData(index(section, 0), value, role); break; } case TimelinePropertiesRole: { TimelineFramesModel::PropertyList props = value.value(); int result = m_d->setLayerProperties(section, props); emit headerDataChanged (Qt::Vertical, section, section); return result; } case LayerUsedInTimelineRole: { KisNodeDummy *dummy = m_d->converter->dummyFromRow(section); if (!dummy) return false; dummy->node()->setPinnedToTimeline(value.toBool()); return true; } } } return ModelWithExternalNotifications::setHeaderData(section, orientation, value, role); } Qt::DropActions TimelineFramesModel::supportedDragActions() const { return Qt::MoveAction | Qt::CopyAction; } Qt::DropActions TimelineFramesModel::supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } QStringList TimelineFramesModel::mimeTypes() const { QStringList types; types << QLatin1String("application/x-krita-frame"); return types; } void TimelineFramesModel::setLastClickedIndex(const QModelIndex &index) { m_d->lastClickedIndex = index; } QMimeData* TimelineFramesModel::mimeData(const QModelIndexList &indexes) const { return mimeDataExtended(indexes, m_d->lastClickedIndex, UndefinedPolicy); } QMimeData *TimelineFramesModel::mimeDataExtended(const QModelIndexList &indexes, const QModelIndex &baseIndex, TimelineFramesModel::MimeCopyPolicy copyPolicy) const { QMimeData *data = new QMimeData(); QByteArray encoded; QDataStream stream(&encoded, QIODevice::WriteOnly); const int baseRow = baseIndex.row(); const int baseColumn = baseIndex.column(); const QByteArray uuidDataRoot = m_d->image->root()->uuid().toRfc4122(); stream << int(uuidDataRoot.size()); stream.writeRawData(uuidDataRoot.data(), uuidDataRoot.size()); stream << indexes.size(); stream << baseRow << baseColumn; Q_FOREACH (const QModelIndex &index, indexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } stream << index.row() - baseRow << index.column() - baseColumn; const QByteArray uuidData = node->uuid().toRfc4122(); stream << int(uuidData.size()); stream.writeRawData(uuidData.data(), uuidData.size()); } stream << int(copyPolicy); data->setData("application/x-krita-frame", encoded); return data; } inline void decodeBaseIndex(QByteArray *encoded, int *row, int *col) { int size_UNUSED = 0; QDataStream stream(encoded, QIODevice::ReadOnly); stream >> size_UNUSED >> *row >> *col; } bool TimelineFramesModel::canDropFrameData(const QMimeData */*data*/, const QModelIndex &index) { if (!index.isValid()) return false; /** * Now we support D&D around any layer, so just return 'true' all * the time. */ return true; } bool TimelineFramesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row); Q_UNUSED(column); return dropMimeDataExtended(data, action, parent); } bool TimelineFramesModel::dropMimeDataExtended(const QMimeData *data, Qt::DropAction action, const QModelIndex &parent, bool *dataMoved) { bool result = false; if ((action != Qt::MoveAction && action != Qt::CopyAction) || !parent.isValid()) return result; QByteArray encoded = data->data("application/x-krita-frame"); QDataStream stream(&encoded, QIODevice::ReadOnly); int uuidLenRoot = 0; stream >> uuidLenRoot; QByteArray uuidDataRoot(uuidLenRoot, '\0'); stream.readRawData(uuidDataRoot.data(), uuidLenRoot); QUuid nodeUuidRoot = QUuid::fromRfc4122(uuidDataRoot); KisPart *partInstance = KisPart::instance(); QList> documents = partInstance->documents(); KisImageSP srcImage = 0; Q_FOREACH(KisDocument *doc, documents) { KisImageSP tmpSrcImage = doc->image(); if (tmpSrcImage->root()->uuid() == nodeUuidRoot) { srcImage = tmpSrcImage; break; } } if (!srcImage) { KisPart *kisPartInstance = KisPart::instance(); kisPartInstance->currentMainwindow()->viewManager()->showFloatingMessage( i18n("Dropped frames are not available in this Krita instance") , QIcon()); return false; } int size, baseRow, baseColumn; stream >> size >> baseRow >> baseColumn; const QPoint offset(parent.column() - baseColumn, parent.row() - baseRow); KisAnimationUtils::FrameMovePairList frameMoves; for (int i = 0; i < size; i++) { int relRow, relColumn; stream >> relRow >> relColumn; const int srcRow = baseRow + relRow; const int srcColumn = baseColumn + relColumn; int uuidLen = 0; stream >> uuidLen; QByteArray uuidData(uuidLen, '\0'); stream.readRawData(uuidData.data(), uuidLen); QUuid nodeUuid = QUuid::fromRfc4122(uuidData); KisNodeSP srcNode; if (!nodeUuid.isNull()) { KisNodeUuidInfo nodeInfo(nodeUuid); srcNode = nodeInfo.findNode(srcImage->root()); } else { QModelIndex index = this->index(srcRow, srcColumn); srcNode = nodeAt(index); } KIS_SAFE_ASSERT_RECOVER(srcNode) { continue; } const QModelIndex dstRowIndex = this->index(srcRow + offset.y(), 0); if (!dstRowIndex.isValid()) continue; KisNodeSP dstNode = nodeAt(dstRowIndex); KIS_SAFE_ASSERT_RECOVER(dstNode) { continue; } Q_FOREACH (KisKeyframeChannel *channel, srcNode->keyframeChannels().values()) { KisAnimationUtils::FrameItem srcItem(srcNode, channel->id(), srcColumn); KisAnimationUtils::FrameItem dstItem(dstNode, channel->id(), srcColumn + offset.x()); frameMoves << std::make_pair(srcItem, dstItem); } } MimeCopyPolicy copyPolicy = UndefinedPolicy; if (!stream.atEnd()) { int value = 0; stream >> value; copyPolicy = MimeCopyPolicy(value); } const bool copyFrames = copyPolicy == UndefinedPolicy ? action == Qt::CopyAction : copyPolicy == CopyFramesPolicy; if (dataMoved) { *dataMoved = !copyFrames; } KUndo2Command *cmd = 0; if (!frameMoves.isEmpty()) { KisImageBarrierLockerWithFeedback locker(m_d->image); cmd = KisAnimationUtils::createMoveKeyframesCommand(frameMoves, copyFrames, false, 0); } if (cmd) { KisProcessingApplicator::runSingleCommandStroke(m_d->image, cmd, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); } return cmd; } Qt::ItemFlags TimelineFramesModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = ModelWithExternalNotifications::flags(index); if (!index.isValid()) return flags; if (m_d->frameExists(index.row(), index.column()) || m_d->specialKeyframeExists(index.row(), index.column())) { if (data(index, FrameEditableRole).toBool()) { flags |= Qt::ItemIsDragEnabled; } } /** * Basically we should forbid overrides only if we D&D a single frame * and allow it when we D&D multiple frames. But we cannot distinguish * it here... So allow all the time. */ flags |= Qt::ItemIsDropEnabled; return flags; } bool TimelineFramesModel::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row > rowCount()) return false; bool result = m_d->addNewLayer(row); return result; } bool TimelineFramesModel::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); KIS_ASSERT_RECOVER(count == 1) { return false; } if (row < 0 || row >= rowCount()) return false; bool result = m_d->removeLayer(row); return result; } bool TimelineFramesModel::insertOtherLayer(int index, int dstRow) { Q_UNUSED(dstRow); TimelineNodeListKeeper::OtherLayersList list = m_d->converter->otherLayersList(); if (index < 0 || index >= list.size()) return false; list[index].dummy->node()->setPinnedToTimeline(true); dstRow = m_d->converter->rowForDummy(list[index].dummy); setData(this->index(dstRow, 0), true, ActiveLayerRole); return true; } int TimelineFramesModel::activeLayerRow() const { return m_d->activeLayerIndex; } bool TimelineFramesModel::createFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), false); } bool TimelineFramesModel::copyFrame(const QModelIndex &dstIndex) { if (!dstIndex.isValid()) return false; return m_d->addKeyframe(dstIndex.row(), dstIndex.column(), true); } bool TimelineFramesModel::insertFrames(int dstColumn, const QList &dstRows, int count, int timing) { if (dstRows.isEmpty() || count <= 0) return true; timing = qMax(timing, 1); KUndo2Command *parentCommand = new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count)); { KisImageBarrierLockerWithFeedback locker(m_d->image); QModelIndexList indexes; Q_FOREACH (int row, dstRows) { for (int column = dstColumn; column < columnCount(); column++) { indexes << index(row, column); } } setLastVisibleFrame(columnCount() + (count * timing) - 1); createOffsetFramesCommand(indexes, QPoint((count * timing), 0), false, false, parentCommand); Q_FOREACH (int row, dstRows) { KisNodeDummy *dummy = m_d->converter->dummyFromRow(row); if (!dummy) continue; KisNodeSP node = dummy->node(); if (!KisAnimationUtils::supportsContentFrames(node)) continue; for (int column = dstColumn; column < dstColumn + (count * timing); column += timing) { KisAnimationUtils::createKeyframeCommand(m_d->image, node, KisKeyframeChannel::Content.id(), column, false, parentCommand); } } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = dstColumn > oldTime ? dstColumn : dstColumn + (count * timing) - 1; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand, KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } bool TimelineFramesModel::insertHoldFrames(QModelIndexList selectedIndexes, int count) { if (selectedIndexes.isEmpty() || count == 0) return true; QScopedPointer parentCommand(new KUndo2Command(kundo2_i18np("Insert frame", "Insert %1 frames", count))); { KisImageBarrierLockerWithFeedback locker(m_d->image); QSet uniqueKeyframesInSelection; int minSelectedTime = std::numeric_limits::max(); Q_FOREACH (const QModelIndex &index, selectedIndexes) { KisNodeSP node = nodeAt(index); KIS_SAFE_ASSERT_RECOVER(node) { continue; } KisKeyframeChannel *channel = node->getKeyframeChannel(KisKeyframeChannel::Content.id()); if (!channel) continue; minSelectedTime = qMin(minSelectedTime, index.column()); KisKeyframeSP keyFrame = channel->activeKeyframeAt(index.column()); if (keyFrame) { uniqueKeyframesInSelection.insert(keyFrame); } } QList keyframesToMove; for (auto it = uniqueKeyframesInSelection.begin(); it != uniqueKeyframesInSelection.end(); ++it) { KisKeyframeSP keyframe = *it; KisKeyframeChannel *channel = keyframe->channel(); KisKeyframeSP nextKeyframe = channel->nextKeyframe(keyframe); if (nextKeyframe) { keyframesToMove << nextKeyframe; } } std::sort(keyframesToMove.begin(), keyframesToMove.end(), [] (KisKeyframeSP lhs, KisKeyframeSP rhs) { return lhs->time() > rhs->time(); }); if (keyframesToMove.isEmpty()) return true; const int maxColumn = columnCount(); if (count > 0) { setLastVisibleFrame(columnCount() + count); } Q_FOREACH (KisKeyframeSP keyframe, keyframesToMove) { int plannedFrameMove = count; if (count < 0) { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(keyframe->time() > 0, false); KisKeyframeSP prevFrame = keyframe->channel()->previousKeyframe(keyframe); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(prevFrame, false); plannedFrameMove = qMax(count, prevFrame->time() - keyframe->time() + 1); minSelectedTime = qMin(minSelectedTime, prevFrame->time()); } KisNodeDummy *dummy = m_d->dummiesFacade->dummyForNode(keyframe->channel()->node()); KIS_SAFE_ASSERT_RECOVER(dummy) { continue; } const int row = m_d->converter->rowForDummy(dummy); KIS_SAFE_ASSERT_RECOVER(row >= 0) { continue; } QModelIndexList indexes; for (int column = keyframe->time(); column < maxColumn; column++) { indexes << index(row, column); } createOffsetFramesCommand(indexes, QPoint(plannedFrameMove, 0), false, true, parentCommand.data()); } const int oldTime = m_d->image->animationInterface()->currentUITime(); const int newTime = minSelectedTime; new KisSwitchCurrentTimeCommand(m_d->image->animationInterface(), oldTime, newTime, parentCommand.data()); } KisProcessingApplicator::runSingleCommandStroke(m_d->image, parentCommand.take(), KisStrokeJobData::BARRIER, KisStrokeJobData::EXCLUSIVE); return true; } QString TimelineFramesModel::audioChannelFileName() const { return m_d->image ? m_d->image->animationInterface()->audioChannelFileName() : QString(); } void TimelineFramesModel::setAudioChannelFileName(const QString &fileName) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioChannelFileName(fileName); } bool TimelineFramesModel::isAudioMuted() const { return m_d->image ? m_d->image->animationInterface()->isAudioMuted() : false; } void TimelineFramesModel::setAudioMuted(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioMuted(value); } qreal TimelineFramesModel::audioVolume() const { return m_d->image ? m_d->image->animationInterface()->audioVolume() : 0.5; } void TimelineFramesModel::setAudioVolume(qreal value) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->image); m_d->image->animationInterface()->setAudioVolume(value); } void TimelineFramesModel::setFullClipRangeStart(int column) { m_d->image->animationInterface()->setFullClipRangeStartTime(column); } void TimelineFramesModel::setFullClipRangeEnd(int column) { m_d->image->animationInterface()->setFullClipRangeEndTime(column); }