diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index 672ee338e0..f12cc5b2dc 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,322 +1,327 @@ /* * 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 "kis_paint_device.h" #include "kis_layer_properties_icons.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; bool systemLocked; KisBaseNode::Property hack_visible; //HACK QUuid id; bool collapsed; bool supportsLodMoves; }; KisBaseNode::KisBaseNode() : m_d(new Private()) { /** * 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 suported 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); setSystemLocked(false); m_d->compositeOp = COMPOSITE_OVER; setUuid(QUuid::createUuid()); } KisBaseNode::KisBaseNode(const KisBaseNode & rhs) : QObject() , KisShared() , m_d(new Private()) { QMapIterator iter = rhs.m_d->properties.propertyIterator(); while (iter.hasNext()) { iter.next(); m_d->properties.setProperty(iter.key(), iter.value()); } setCollapsed(rhs.collapsed()); setSupportsLodMoves(rhs.supportsLodMoves()); setSystemLocked(false); m_d->compositeOp = rhs.m_d->compositeOp; setUuid(QUuid::createUuid()); } KisBaseNode::~KisBaseNode() { delete m_d; } KisPaintDeviceSP KisBaseNode::paintDevice() const { return 0; } KisPaintDeviceSP KisBaseNode::original() const { return 0; } KisPaintDeviceSP KisBaseNode::projection() const { return 0; } quint8 KisBaseNode::opacity() const { return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (opacity() == val) return; nodeProperties().setProperty("opacity", val); baseNodeChangedCallback(); 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()); } KoProperties & KisBaseNode::nodeProperties() const { return m_d->properties; } 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) { try { QImage image(w, h, QImage::Format_ARGB32); image.fill(0); return image; } catch (std::bad_alloc) { return QImage(); } } 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() : 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) { emit visibilityChanged(visible); baseNodeChangedCallback(); baseNodeInvalidateAllFramesCallback(); } } bool KisBaseNode::userLocked() const { return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false); } 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); emit userLockingChanged(locked); baseNodeChangedCallback(); } bool KisBaseNode::systemLocked() const { return m_d->systemLocked; } void KisBaseNode::setSystemLocked(bool locked, bool update) { m_d->systemLocked = locked; if (update) { emit systemLockingChanged(locked); baseNodeChangedCallback(); } } bool KisBaseNode::isEditable(bool checkVisibility) const { bool editable = true; if (checkVisibility) { editable = (visible(false) && !userLocked() && !systemLocked()); } else { editable = (!userLocked() && !systemLocked()); } 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) { m_d->collapsed = collapsed; } 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) +{ + Q_UNUSED(image); +} + void KisBaseNode::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } diff --git a/libs/image/kis_base_node.h b/libs/image/kis_base_node.h index 5582bebbad..37ad2a0fa3 100644 --- a/libs/image/kis_base_node.h +++ b/libs/image/kis_base_node.h @@ -1,532 +1,534 @@ /* * 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; #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; } Property(): isMutable( 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 ) { } /** Constructor for a mutable property accepting stasis */ Property( const KoID &n, const QIcon &on, const QIcon &off, bool isOn, bool _isInStasis, bool _stateInStasis ) : 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 ) { } }; /** Return this type for PropertiesRole. */ typedef QList PropertyList; public: /** * Create a new, empty base node. The node is unnamed, unlocked * visible and unlinked. */ KisBaseNode(); /** * Create a copy of this node. */ KisBaseNode(const KisBaseNode & rhs); /** * Delete this node */ virtual ~KisBaseNode(); /** * 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; /** * @return the rendered representation of a node * before the effect masks have had their go at it. Can be 0. */ virtual KisPaintDeviceSP original() const; /** * @return the fully rendered representation of this layer: its * rendered original and its effect masks. Can be 0. */ virtual KisPaintDeviceSP projection() 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. */ KoProperties & nodeProperties() const; /** * Merge the specified properties with the properties of this * layer. Whereever 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); /** * 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 */ 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 visibile, bool loading = false); /** * Return the locked status of this node. Locked nodes cannot be * edited. */ bool userLocked() const; /** * Set the locked status of this node. Locked nodes cannot be * edited. */ void setUserLocked(bool l); /** * Return the locked status of this node. System Locked nodes indicates * that an algorithm is processing them and that an other * algorithm need to wait before accessing it. */ bool systemLocked() const; /** * Set the locked status of this node. System Locked nodes indicates * that an algorithm is processing them and that an other * algorithm need to wait before accessing it. * * A KisNode will update the layer model when the lock is released. * * @param l lock state * @param update set false if the tools shouldn't be locked */ virtual void setSystemLocked(bool l, bool update = true); /** * @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; + virtual void setImage(KisImageWSP image); + 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 0; } 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() { } virtual void baseNodeInvalidateAllFramesCallback() { } Q_SIGNALS: /** * This signal is emitted when the visibility of the layer is changed with \ref setVisible. */ void visibilityChanged(bool); /** * This signal is emitted when the node is locked or unlocked with \ref setUserLocked. */ void userLockingChanged(bool); /** * This signal is emitted when the node is locked or unlocked with \ref setSystemLocked. */ void systemLockingChanged(bool); private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE( KisBaseNode::PropertyList ) #endif diff --git a/libs/image/kis_group_layer.h b/libs/image/kis_group_layer.h index 0e19c2f0a2..dedbda9f34 100644 --- a/libs/image/kis_group_layer.h +++ b/libs/image/kis_group_layer.h @@ -1,121 +1,121 @@ /* * Copyright (c) 2005 C. Boemann * 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_GROUP_LAYER_H_ #define KIS_GROUP_LAYER_H_ #include "kis_layer.h" #include "kis_types.h" class KoColorSpace; /** * A KisLayer that bundles child layers into a single layer. * The top layer is firstChild(), with index 0; the bottommost lastChild() with index childCount() - 1. * KisLayer::nextSibling() moves towards higher indices, from the top to the bottom layer; prevSibling() the reverse. * (Implementation detail: internally, the indices are reversed, for speed.) **/ class KRITAIMAGE_EXPORT KisGroupLayer : public KisLayer { Q_OBJECT public: KisGroupLayer(KisImageWSP image, const QString &name, quint8 opacity); KisGroupLayer(const KisGroupLayer& rhs); virtual ~KisGroupLayer(); KisNodeSP clone() const { return KisNodeSP(new KisGroupLayer(*this)); } bool allowAsChild(KisNodeSP) const; QIcon icon() const; KisBaseNode::PropertyList sectionModelProperties() const; void setSectionModelProperties(const KisBaseNode::PropertyList &properties); - virtual void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_OVERRIDE; virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); /** * Clear the projection */ void resetCache(const KoColorSpace *colorSpace = 0); /** * XXX: make the colorspace of a layergroup user-settable: we want * to be able to have, for instance, a group of grayscale layers * resulting in a grayscale projection that is then merged with an * rgb image stack. */ const KoColorSpace * colorSpace() const; /// @return the projection of the layers in the group before the masks are applied. KisPaintDeviceSP original() const; qint32 x() const; qint32 y() const; void setX(qint32 x); void setY(qint32 y); /** Accect the specified visitor. @return true if the operation succeeded, false if it failed. */ bool accept(KisNodeVisitor &v); void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter); /** * A special method that changes the default color of the * projection merged onto this group layer. Please note, that you * cannot use original()->setDefaultPixel(), because original() * device can be switched by tryOblidgeChild() mechanism randomly. */ void setDefaultProjectionColor(KoColor color); /** * \see setDefaultProjectionColor() */ KoColor defaultProjectionColor() const; bool passThroughMode() const; void setPassThroughMode(bool value); QRect extent() const; QRect exactBounds() const; bool projectionIsValid() const; protected: KisLayer* onlyMeaningfulChild() const; KisPaintDeviceSP tryObligeChild() const; private: bool checkCloneLayer(KisCloneLayerSP clone) const; bool checkNodeRecursively(KisNodeSP node) const; private: struct Private; Private * const m_d; }; #endif // KIS_GROUP_LAYER_H_ diff --git a/libs/image/kis_image.cc b/libs/image/kis_image.cc index cc6189b293..8457e823a9 100644 --- a/libs/image/kis_image.cc +++ b/libs/image/kis_image.cc @@ -1,1618 +1,1619 @@ /* * Copyright (c) 2002 Patrick Julien * 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_image.h" #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include "KoColorSpaceRegistry.h" #include "KoColor.h" #include "KoColorProfile.h" #include #include "KisProofingConfiguration.h" #include "recorder/kis_action_recorder.h" #include "kis_adjustment_layer.h" #include "kis_annotation.h" #include "kis_change_profile_visitor.h" #include "kis_colorspace_convert_visitor.h" #include "kis_count_visitor.h" #include "kis_filter_strategy.h" #include "kis_group_layer.h" #include "commands/kis_image_commands.h" #include "kis_layer.h" #include "kis_meta_data_merge_strategy_registry.h" #include "kis_name_server.h" #include "kis_paint_layer.h" #include "kis_painter.h" #include "kis_selection.h" #include "kis_transaction.h" #include "kis_meta_data_merge_strategy.h" #include "kis_memory_statistics_server.h" #include "kis_image_config.h" #include "kis_update_scheduler.h" #include "kis_image_signal_router.h" #include "kis_image_animation_interface.h" #include "kis_stroke_strategy.h" #include "kis_image_barrier_locker.h" #include "kis_undo_stores.h" #include "kis_legacy_undo_adapter.h" #include "kis_post_execution_undo_adapter.h" #include "kis_transform_worker.h" #include "kis_processing_applicator.h" #include "processing/kis_crop_processing_visitor.h" #include "processing/kis_crop_selections_processing_visitor.h" #include "processing/kis_transform_processing_visitor.h" #include "commands_new/kis_image_resize_command.h" #include "commands_new/kis_image_set_resolution_command.h" #include "commands_new/kis_activate_selection_mask_command.h" #include "kis_composite_progress_proxy.h" #include "kis_layer_composition.h" #include "kis_wrapped_rect.h" #include "kis_crop_saved_extra_data.h" #include "kis_layer_utils.h" #include "kis_lod_transform.h" #include "kis_suspend_projection_updates_stroke_strategy.h" #include "kis_sync_lod_cache_stroke_strategy.h" #include "kis_projection_updates_filter.h" #include "kis_layer_projection_plane.h" #include "kis_update_time_monitor.h" #include "kis_image_barrier_locker.h" #include #include #include "kis_time_range.h" // #define SANITY_CHECKS #ifdef SANITY_CHECKS #define SANITY_CHECK_LOCKED(name) \ if (!locked()) warnKrita() << "Locking policy failed:" << name \ << "has been called without the image" \ "being locked"; #else #define SANITY_CHECK_LOCKED(name) #endif struct KisImageSPStaticRegistrar { KisImageSPStaticRegistrar() { qRegisterMetaType("KisImageSP"); } }; static KisImageSPStaticRegistrar __registrar; class KisImage::KisImagePrivate { public: KisImagePrivate(KisImage *_q, qint32 w, qint32 h, const KoColorSpace *c, KisUndoStore *u) : q(_q) , lockedForReadOnly(false) , width(w) , height(h) , colorSpace(c) , nserver(1) , undoStore(u) , legacyUndoAdapter(u, _q) , postExecutionUndoAdapter(u, _q) , recorder(_q) , signalRouter(_q) , animationInterface(0) , scheduler(_q) {} KisImage *q; quint32 lockCount = 0; bool lockedForReadOnly; qint32 width; qint32 height; double xres = 1.0; double yres = 1.0; const KoColorSpace * colorSpace; KisProofingConfigurationSP proofingConfig; KisSelectionSP deselectedGlobalSelection; KisGroupLayerSP rootLayer; // The layers are contained in here QList dirtyLayers; // for thumbnails QList compositions; KisNodeSP isolatedRootNode; bool wrapAroundModePermitted = false; KisNameServer nserver; KisUndoStore *undoStore; KisLegacyUndoAdapter legacyUndoAdapter; KisPostExecutionUndoAdapter postExecutionUndoAdapter; KisActionRecorder recorder; vKisAnnotationSP annotations; QAtomicInt disableUIUpdateSignals; KisProjectionUpdatesFilterSP projectionUpdatesFilter; KisImageSignalRouter signalRouter; KisImageAnimationInterface *animationInterface; KisUpdateScheduler scheduler; QAtomicInt disableDirtyRequests; KisCompositeProgressProxy compositeProgressProxy; bool blockLevelOfDetail = false; bool tryCancelCurrentStrokeAsync(); void notifyProjectionUpdatedInPatches(const QRect &rc); }; KisImage::KisImage(KisUndoStore *undoStore, qint32 width, qint32 height, const KoColorSpace * colorSpace, const QString& name) : QObject(0) , KisShared() { setObjectName(name); // Handle undoStore == 0 and colorSpace == 0 cases if (!undoStore) { undoStore = new KisDumbUndoStore(); } const KoColorSpace *c; if (colorSpace != 0) { c = colorSpace; } else { c = KoColorSpaceRegistry::instance()->rgb8(); } m_d = new KisImagePrivate(this, width, height, c, undoStore); { KisImageConfig cfg; if (cfg.enableProgressReporting()) { m_d->scheduler.setProgressProxy(&m_d->compositeProgressProxy); } // Each of these lambdas defines a new factory function. m_d->scheduler.setLod0ToNStrokeStrategyFactory( [=](bool forgettable) { return KisLodSyncPair( new KisSyncLodCacheStrokeStrategy(KisImageWSP(this), forgettable), KisSyncLodCacheStrokeStrategy::createJobsData(KisImageWSP(this))); }); m_d->scheduler.setSuspendUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), true), KisSuspendProjectionUpdatesStrokeStrategy::createSuspendJobsData(KisImageWSP(this))); }); m_d->scheduler.setResumeUpdatesStrokeStrategyFactory( [=]() { return KisSuspendResumePair( new KisSuspendProjectionUpdatesStrokeStrategy(KisImageWSP(this), false), KisSuspendProjectionUpdatesStrokeStrategy::createResumeJobsData(KisImageWSP(this))); }); } setRootLayer(new KisGroupLayer(this, "root", OPACITY_OPAQUE_U8)); m_d->animationInterface = new KisImageAnimationInterface(this); connect(this, SIGNAL(sigImageModified()), KisMemoryStatisticsServer::instance(), SLOT(notifyImageChanged())); } KisImage::~KisImage() { dbgImage << "deleting kisimage" << objectName(); /** * Request the tools to end currently running strokes */ waitForDone(); /** * Stop animation interface. It may use the rootLayer. */ delete m_d->animationInterface; /** * First delete the nodes, while strokes * and undo are still alive */ m_d->rootLayer = 0; delete m_d->undoStore; delete m_d; disconnect(); // in case Qt gets confused } void KisImage::aboutToAddANode(KisNode *parent, int index) { KisNodeGraphListener::aboutToAddANode(parent, index); SANITY_CHECK_LOCKED("aboutToAddANode"); } void KisImage::nodeHasBeenAdded(KisNode *parent, int index) { KisNodeGraphListener::nodeHasBeenAdded(parent, index); SANITY_CHECK_LOCKED("nodeHasBeenAdded"); m_d->signalRouter.emitNodeHasBeenAdded(parent, index); KisNodeSP newNode = parent->at(index); if (!dynamic_cast(newNode.data())) { stopIsolatedMode(); } } void KisImage::aboutToRemoveANode(KisNode *parent, int index) { KisNodeSP deletedNode = parent->at(index); if (!dynamic_cast(deletedNode.data())) { stopIsolatedMode(); } KisNodeGraphListener::aboutToRemoveANode(parent, index); SANITY_CHECK_LOCKED("aboutToRemoveANode"); m_d->signalRouter.emitAboutToRemoveANode(parent, index); } void KisImage::nodeChanged(KisNode* node) { KisNodeGraphListener::nodeChanged(node); requestStrokeEnd(); m_d->signalRouter.emitNodeChanged(node); } void KisImage::invalidateAllFrames() { invalidateFrames(KisTimeRange::infinite(0), QRect()); } KisSelectionSP KisImage::globalSelection() const { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (selectionMask) { return selectionMask->selection(); } else { return 0; } } void KisImage::setGlobalSelection(KisSelectionSP globalSelection) { KisSelectionMaskSP selectionMask = m_d->rootLayer->selectionMask(); if (!globalSelection) { if (selectionMask) { removeNode(selectionMask); } } else { if (!selectionMask) { selectionMask = new KisSelectionMask(this); selectionMask->initSelection(m_d->rootLayer); addNode(selectionMask); // If we do not set the selection now, the setActive call coming next // can be very, very expensive, depending on the size of the image. selectionMask->setSelection(globalSelection); selectionMask->setActive(true); } else { selectionMask->setSelection(globalSelection); } Q_ASSERT(m_d->rootLayer->childCount() > 0); Q_ASSERT(m_d->rootLayer->selectionMask()); } m_d->deselectedGlobalSelection = 0; m_d->legacyUndoAdapter.emitSelectionChanged(); } void KisImage::deselectGlobalSelection() { KisSelectionSP savedSelection = globalSelection(); setGlobalSelection(0); m_d->deselectedGlobalSelection = savedSelection; } bool KisImage::canReselectGlobalSelection() { return m_d->deselectedGlobalSelection; } void KisImage::reselectGlobalSelection() { if(m_d->deselectedGlobalSelection) { setGlobalSelection(m_d->deselectedGlobalSelection); } } QString KisImage::nextLayerName(const QString &_baseName) const { QString baseName = _baseName; if (m_d->nserver.currentSeed() == 0) { m_d->nserver.number(); return i18n("background"); } if (baseName.isEmpty()) { baseName = i18n("Layer"); } return QString("%1 %2").arg(baseName).arg(m_d->nserver.number()); } void KisImage::rollBackLayerName() { m_d->nserver.rollback(); } KisCompositeProgressProxy* KisImage::compositeProgressProxy() { return &m_d->compositeProgressProxy; } bool KisImage::locked() const { return m_d->lockCount != 0; } void KisImage::barrierLock(bool readOnly) { if (!locked()) { requestStrokeEnd(); m_d->scheduler.barrierLock(); m_d->lockedForReadOnly = readOnly; } else { m_d->lockedForReadOnly &= readOnly; } m_d->lockCount++; } bool KisImage::tryBarrierLock(bool readOnly) { bool result = true; if (!locked()) { result = m_d->scheduler.tryBarrierLock(); m_d->lockedForReadOnly = readOnly; } if (result) { m_d->lockCount++; m_d->lockedForReadOnly &= readOnly; } return result; } bool KisImage::isIdle() { return !locked() && m_d->scheduler.isIdle(); } void KisImage::lock() { if (!locked()) { requestStrokeEnd(); m_d->scheduler.lock(); } m_d->lockCount++; m_d->lockedForReadOnly = false; } void KisImage::unlock() { Q_ASSERT(locked()); if (locked()) { m_d->lockCount--; if (m_d->lockCount == 0) { m_d->scheduler.unlock(!m_d->lockedForReadOnly); } } } void KisImage::blockUpdates() { m_d->scheduler.blockUpdates(); } void KisImage::unblockUpdates() { m_d->scheduler.unblockUpdates(); } void KisImage::setSize(const QSize& size) { m_d->width = size.width(); m_d->height = size.height(); } void KisImage::resizeImageImpl(const QRect& newRect, bool cropLayers) { if (newRect == bounds() && !cropLayers) return; KUndo2MagicString actionName = cropLayers ? kundo2_i18n("Crop Image") : kundo2_i18n("Resize Image"); KisImageSignalVector emitSignals; emitSignals << ComplexSizeChangedSignal(newRect, newRect.size()); emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(cropLayers ? KisCropSavedExtraData::CROP_IMAGE : KisCropSavedExtraData::RESIZE_IMAGE, newRect); KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | KisProcessingApplicator::NO_UI_UPDATES, emitSignals, actionName, extraData); if (cropLayers || !newRect.topLeft().isNull()) { KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, cropLayers, true); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); } applicator.applyCommand(new KisImageResizeCommand(this, newRect.size())); applicator.end(); } void KisImage::resizeImage(const QRect& newRect) { resizeImageImpl(newRect, false); } void KisImage::cropImage(const QRect& newRect) { resizeImageImpl(newRect, true); } void KisImage::cropNode(KisNodeSP node, const QRect& newRect) { bool isLayer = dynamic_cast(node.data()); KUndo2MagicString actionName = isLayer ? kundo2_i18n("Crop Layer") : kundo2_i18n("Crop Mask"); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisCropSavedExtraData *extraData = new KisCropSavedExtraData(KisCropSavedExtraData::CROP_LAYER, newRect, node); KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName, extraData); KisProcessingVisitorSP visitor = new KisCropProcessingVisitor(newRect, true, false); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::scaleImage(const QSize &size, qreal xres, qreal yres, KisFilterStrategy *filterStrategy) { bool resolutionChanged = xres != xRes() && yres != yRes(); bool sizeChanged = size != this->size(); if (!resolutionChanged && !sizeChanged) return; KisImageSignalVector emitSignals; if (resolutionChanged) emitSignals << ResolutionChangedSignal; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), size); emitSignals << ModifiedSignal; KUndo2MagicString actionName = sizeChanged ? kundo2_i18n("Scale Image") : kundo2_i18n("Change Image Resolution"); KisProcessingApplicator::ProcessingFlags signalFlags = (resolutionChanged || sizeChanged) ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, m_d->rootLayer, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); qreal sx = qreal(size.width()) / this->size().width(); qreal sy = qreal(size.height()) / this->size().height(); QTransform shapesCorrection; if (resolutionChanged) { shapesCorrection = QTransform::fromScale(xRes() / xres, yRes() / yres); } KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(sx, sy, 0, 0, QPointF(), 0, 0, 0, filterStrategy, shapesCorrection); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resolutionChanged) { KUndo2Command *parent = new KisResetShapesCommand(m_d->rootLayer); new KisImageSetResolutionCommand(this, xres, yres, parent); applicator.applyCommand(parent); } if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, size)); } applicator.end(); } void KisImage::scaleNode(KisNodeSP node, qreal scaleX, qreal scaleY, KisFilterStrategy *filterStrategy) { KUndo2MagicString actionName(kundo2_i18n("Scale Layer")); KisImageSignalVector emitSignals; emitSignals << ModifiedSignal; KisProcessingApplicator applicator(this, node, KisProcessingApplicator::RECURSIVE, emitSignals, actionName); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(scaleX, scaleY, 0, 0, QPointF(), 0, 0, 0, filterStrategy); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } void KisImage::rotateImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double radians) { QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, 0, 0, 0, 0, radians, 0, 0, 0, 0); QTransform transform = worker.transform(); if (resizeImage) { QRect newRect = transform.mapRect(bounds()); newSize = newRect.size(); offset = -newRect.topLeft(); } else { QPointF origin = QRectF(rootNode->exactBounds()).center(); newSize = size(); offset = -(transform.map(origin) - origin); } } bool sizeChanged = resizeImage && (newSize.width() != width() || newSize.height() != height()); // These signals will be emitted after processing is done KisImageSignalVector emitSignals; if (sizeChanged) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; // These flags determine whether updates are transferred to the UI during processing KisProcessingApplicator::ProcessingFlags signalFlags = sizeChanged ? KisProcessingApplicator::NO_UI_UPDATES : KisProcessingApplicator::NONE; KisProcessingApplicator applicator(this, rootNode, KisProcessingApplicator::RECURSIVE | signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bicubic"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, 0.0, 0.0, QPointF(), radians, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (sizeChanged) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::rotateImage(double radians) { rotateImpl(kundo2_i18n("Rotate Image"), root(), true, radians); } void KisImage::rotateNode(KisNodeSP node, double radians) { rotateImpl(kundo2_i18n("Rotate Layer"), node, false, radians); } void KisImage::shearImpl(const KUndo2MagicString &actionName, KisNodeSP rootNode, bool resizeImage, double angleX, double angleY, const QPointF &origin) { //angleX, angleY are in degrees const qreal pi = 3.1415926535897932385; const qreal deg2rad = pi / 180.0; qreal tanX = tan(angleX * deg2rad); qreal tanY = tan(angleY * deg2rad); QPointF offset; QSize newSize; { KisTransformWorker worker(0, 1.0, 1.0, tanX, tanY, origin.x(), origin.y(), 0, 0, 0, 0, 0); QRect newRect = worker.transform().mapRect(bounds()); newSize = newRect.size(); if (resizeImage) offset = -newRect.topLeft(); } if (newSize == size()) return; KisImageSignalVector emitSignals; if (resizeImage) emitSignals << ComplexSizeChangedSignal(bounds(), newSize); emitSignals << ModifiedSignal; KisProcessingApplicator::ProcessingFlags signalFlags = KisProcessingApplicator::RECURSIVE; if (resizeImage) signalFlags |= KisProcessingApplicator::NO_UI_UPDATES; KisProcessingApplicator applicator(this, rootNode, signalFlags, emitSignals, actionName); KisFilterStrategy *filter = KisFilterStrategyRegistry::instance()->value("Bilinear"); KisProcessingVisitorSP visitor = new KisTransformProcessingVisitor(1.0, 1.0, tanX, tanY, origin, 0, offset.x(), offset.y(), filter); applicator.applyVisitorAllFrames(visitor, KisStrokeJobData::CONCURRENT); if (resizeImage) { applicator.applyCommand(new KisImageResizeCommand(this, newSize)); } applicator.end(); } void KisImage::shearNode(KisNodeSP node, double angleX, double angleY) { QPointF shearOrigin = QRectF(bounds()).center(); shearImpl(kundo2_i18n("Shear layer"), node, false, angleX, angleY, shearOrigin); } void KisImage::shear(double angleX, double angleY) { shearImpl(kundo2_i18n("Shear Image"), m_d->rootLayer, true, angleX, angleY, QPointF()); } void KisImage::convertImageColorSpace(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) { if (!dstColorSpace) return; const KoColorSpace *srcColorSpace = m_d->colorSpace; undoAdapter()->beginMacro(kundo2_i18n("Convert Image Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); KisColorSpaceConvertVisitor visitor(this, srcColorSpace, dstColorSpace, renderingIntent, conversionFlags); m_d->rootLayer->accept(visitor); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } bool KisImage::assignImageProfile(const KoColorProfile *profile) { if (!profile) return false; const KoColorSpace *dstCs = KoColorSpaceRegistry::instance()->colorSpace(colorSpace()->colorModelId().id(), colorSpace()->colorDepthId().id(), profile); const KoColorSpace *srcCs = colorSpace(); if (!dstCs) return false; m_d->colorSpace = dstCs; KisChangeProfileVisitor visitor(srcCs, dstCs); return m_d->rootLayer->accept(visitor); } void KisImage::convertProjectionColorSpace(const KoColorSpace *dstColorSpace) { if (*m_d->colorSpace == *dstColorSpace) return; undoAdapter()->beginMacro(kundo2_i18n("Convert Projection Color Space")); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), true)); undoAdapter()->addCommand(new KisImageSetProjectionColorSpaceCommand(KisImageWSP(this), dstColorSpace)); undoAdapter()->addCommand(new KisImageLockCommand(KisImageWSP(this), false)); undoAdapter()->endMacro(); setModified(); } void KisImage::setProjectionColorSpace(const KoColorSpace * colorSpace) { m_d->colorSpace = colorSpace; m_d->rootLayer->resetCache(); m_d->signalRouter.emitNotification(ColorSpaceChangedSignal); } const KoColorSpace * KisImage::colorSpace() const { return m_d->colorSpace; } const KoColorProfile * KisImage::profile() const { return colorSpace()->profile(); } double KisImage::xRes() const { return m_d->xres; } double KisImage::yRes() const { return m_d->yres; } void KisImage::setResolution(double xres, double yres) { m_d->xres = xres; m_d->yres = yres; m_d->signalRouter.emitNotification(ResolutionChangedSignal); } QPointF KisImage::documentToPixel(const QPointF &documentCoord) const { return QPointF(documentCoord.x() * xRes(), documentCoord.y() * yRes()); } QPoint KisImage::documentToIntPixel(const QPointF &documentCoord) const { QPointF pixelCoord = documentToPixel(documentCoord); return QPoint((int)pixelCoord.x(), (int)pixelCoord.y()); } QRectF KisImage::documentToPixel(const QRectF &documentRect) const { return QRectF(documentToPixel(documentRect.topLeft()), documentToPixel(documentRect.bottomRight())); } QRect KisImage::documentToIntPixel(const QRectF &documentRect) const { return documentToPixel(documentRect).toAlignedRect(); } QPointF KisImage::pixelToDocument(const QPointF &pixelCoord) const { return QPointF(pixelCoord.x() / xRes(), pixelCoord.y() / yRes()); } QPointF KisImage::pixelToDocument(const QPoint &pixelCoord) const { return QPointF((pixelCoord.x() + 0.5) / xRes(), (pixelCoord.y() + 0.5) / yRes()); } QRectF KisImage::pixelToDocument(const QRectF &pixelCoord) const { return QRectF(pixelToDocument(pixelCoord.topLeft()), pixelToDocument(pixelCoord.bottomRight())); } qint32 KisImage::width() const { return m_d->width; } qint32 KisImage::height() const { return m_d->height; } KisGroupLayerSP KisImage::rootLayer() const { Q_ASSERT(m_d->rootLayer); return m_d->rootLayer; } KisPaintDeviceSP KisImage::projection() const { if (m_d->isolatedRootNode) { return m_d->isolatedRootNode->projection(); } Q_ASSERT(m_d->rootLayer); KisPaintDeviceSP projection = m_d->rootLayer->projection(); Q_ASSERT(projection); return projection; } qint32 KisImage::nlayers() const { QStringList list; list << "KisLayer"; KisCountVisitor visitor(list, KoProperties()); m_d->rootLayer->accept(visitor); return visitor.count(); } qint32 KisImage::nHiddenLayers() const { QStringList list; list << "KisLayer"; KoProperties properties; properties.setProperty("visible", false); KisCountVisitor visitor(list, properties); m_d->rootLayer->accept(visitor); return visitor.count(); } void KisImage::flatten() { KisLayerUtils::flattenImage(this); } void KisImage::mergeMultipleLayers(QList mergedNodes, KisNodeSP putAfter) { if (!KisLayerUtils::tryMergeSelectionMasks(this, mergedNodes, putAfter)) { KisLayerUtils::mergeMultipleLayers(this, mergedNodes, putAfter); } } void KisImage::mergeDown(KisLayerSP layer, const KisMetaData::MergeStrategy* strategy) { KisLayerUtils::mergeDown(this, layer, strategy); } void KisImage::flattenLayer(KisLayerSP layer) { KisLayerUtils::flattenLayer(this, layer); } void KisImage::setModified() { m_d->signalRouter.emitNotification(ModifiedSignal); } QImage KisImage::convertToQImage(QRect imageRect, const KoColorProfile * profile) { qint32 x; qint32 y; qint32 w; qint32 h; imageRect.getRect(&x, &y, &w, &h); return convertToQImage(x, y, w, h, profile); } QImage KisImage::convertToQImage(qint32 x, qint32 y, qint32 w, qint32 h, const KoColorProfile * profile) { KisPaintDeviceSP dev = projection(); if (!dev) return QImage(); QImage image = dev->convertToQImage(const_cast(profile), x, y, w, h, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); return image; } QImage KisImage::convertToQImage(const QSize& scaledImageSize, const KoColorProfile *profile) { if (scaledImageSize.isEmpty()) { return QImage(); } KisPaintDeviceSP dev = new KisPaintDevice(colorSpace()); KisPainter gc; gc.copyAreaOptimized(QPoint(0, 0), projection(), dev, bounds()); gc.end(); double scaleX = qreal(scaledImageSize.width()) / width(); double scaleY = qreal(scaledImageSize.height()) / height(); QPointer updater = new KoDummyUpdater(); KisTransformWorker worker(dev, scaleX, scaleY, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, updater, KisFilterStrategyRegistry::instance()->value("Bicubic")); worker.run(); delete updater; return dev->convertToQImage(profile); } void KisImage::notifyLayersChanged() { m_d->signalRouter.emitNotification(LayersChangedSignal); } QRect KisImage::bounds() const { return QRect(0, 0, width(), height()); } QRect KisImage::effectiveLodBounds() const { QRect boundRect = bounds(); const int lod = currentLevelOfDetail(); if (lod > 0) { KisLodTransform t(lod); boundRect = t.map(boundRect); } return boundRect; } KisPostExecutionUndoAdapter* KisImage::postExecutionUndoAdapter() const { return &m_d->postExecutionUndoAdapter; } void KisImage::setUndoStore(KisUndoStore *undoStore) { m_d->legacyUndoAdapter.setUndoStore(undoStore); m_d->postExecutionUndoAdapter.setUndoStore(undoStore); delete m_d->undoStore; m_d->undoStore = undoStore; } KisUndoStore* KisImage::undoStore() { return m_d->undoStore; } KisUndoAdapter* KisImage::undoAdapter() const { return &m_d->legacyUndoAdapter; } KisActionRecorder* KisImage::actionRecorder() const { return &m_d->recorder; } void KisImage::setDefaultProjectionColor(const KoColor &color) { KIS_ASSERT_RECOVER_RETURN(m_d->rootLayer); m_d->rootLayer->setDefaultProjectionColor(color); } KoColor KisImage::defaultProjectionColor() const { KIS_ASSERT_RECOVER(m_d->rootLayer) { return KoColor(Qt::transparent, m_d->colorSpace); } return m_d->rootLayer->defaultProjectionColor(); } void KisImage::setRootLayer(KisGroupLayerSP rootLayer) { stopIsolatedMode(); KoColor defaultProjectionColor(Qt::transparent, m_d->colorSpace); if (m_d->rootLayer) { m_d->rootLayer->setGraphListener(0); m_d->rootLayer->disconnect(); KisPaintDeviceSP original = m_d->rootLayer->original(); defaultProjectionColor = original->defaultPixel(); } m_d->rootLayer = rootLayer; m_d->rootLayer->disconnect(); m_d->rootLayer->setGraphListener(this); + m_d->rootLayer->setImage(this); KisPaintDeviceSP newOriginal = m_d->rootLayer->original(); newOriginal->setDefaultPixel(defaultProjectionColor); setRoot(m_d->rootLayer.data()); } void KisImage::addAnnotation(KisAnnotationSP annotation) { // Find the icc annotation, if there is one vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == annotation->type()) { *it = annotation; return; } ++it; } m_d->annotations.push_back(annotation); } KisAnnotationSP KisImage::annotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { return *it; } ++it; } return KisAnnotationSP(0); } void KisImage::removeAnnotation(const QString& type) { vKisAnnotationSP_it it = m_d->annotations.begin(); while (it != m_d->annotations.end()) { if ((*it)->type() == type) { m_d->annotations.erase(it); return; } ++it; } } vKisAnnotationSP_it KisImage::beginAnnotations() { return m_d->annotations.begin(); } vKisAnnotationSP_it KisImage::endAnnotations() { return m_d->annotations.end(); } void KisImage::notifyAboutToBeDeleted() { emit sigAboutToBeDeleted(); } KisImageSignalRouter* KisImage::signalRouter() { return &m_d->signalRouter; } void KisImage::waitForDone() { requestStrokeEnd(); m_d->scheduler.waitForDone(); } KisStrokeId KisImage::startStroke(KisStrokeStrategy *strokeStrategy) { /** * Ask open strokes to end gracefully. All the strokes clients * (including the one calling this method right now) will get * a notification that they should probably end their strokes. * However this is purely their choice whether to end a stroke * or not. */ if (strokeStrategy->requestsOtherStrokesToEnd()) { requestStrokeEnd(); } /** * Some of the strokes can cancel their work with undoing all the * changes they did to the paint devices. The problem is that undo * stack will know nothing about it. Therefore, just notify it * explicitly */ if (strokeStrategy->clearsRedoOnStart()) { m_d->undoStore->purgeRedoState(); } return m_d->scheduler.startStroke(strokeStrategy); } void KisImage::KisImagePrivate::notifyProjectionUpdatedInPatches(const QRect &rc) { KisImageConfig imageConfig; int patchWidth = imageConfig.updatePatchWidth(); int patchHeight = imageConfig.updatePatchHeight(); for (int y = 0; y < rc.height(); y += patchHeight) { for (int x = 0; x < rc.width(); x += patchWidth) { QRect patchRect(x, y, patchWidth, patchHeight); patchRect &= rc; QtConcurrent::run(std::bind(&KisImage::notifyProjectionUpdated, q, patchRect)); } } } bool KisImage::startIsolatedMode(KisNodeSP node) { if (!tryBarrierLock()) return false; unlock(); m_d->isolatedRootNode = node; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); return true; } void KisImage::stopIsolatedMode() { if (!m_d->isolatedRootNode) return; KisNodeSP oldRootNode = m_d->isolatedRootNode; m_d->isolatedRootNode = 0; emit sigIsolatedModeChanged(); // the GUI uses our thread to do the color space conversion so we // need to emit this signal in multiple threads m_d->notifyProjectionUpdatedInPatches(bounds()); invalidateAllFrames(); // TODO: Substitute notifyProjectionUpdated() with this code // when update optimization is implemented // // QRect updateRect = bounds() | oldRootNode->extent(); // oldRootNode->setDirty(updateRect); } KisNodeSP KisImage::isolatedModeRoot() const { return m_d->isolatedRootNode; } void KisImage::addJob(KisStrokeId id, KisStrokeJobData *data) { KisUpdateTimeMonitor::instance()->reportJobStarted(data); m_d->scheduler.addJob(id, data); } void KisImage::endStroke(KisStrokeId id) { m_d->scheduler.endStroke(id); } bool KisImage::cancelStroke(KisStrokeId id) { return m_d->scheduler.cancelStroke(id); } bool KisImage::KisImagePrivate::tryCancelCurrentStrokeAsync() { return scheduler.tryCancelCurrentStrokeAsync(); } void KisImage::requestUndoDuringStroke() { emit sigUndoDuringStrokeRequested(); } void KisImage::requestStrokeCancellation() { if (!m_d->tryCancelCurrentStrokeAsync()) { emit sigStrokeCancellationRequested(); } } void KisImage::requestStrokeEnd() { emit sigStrokeEndRequested(); emit sigStrokeEndRequestedActiveNodeFiltered(); } void KisImage::requestStrokeEndActiveNode() { emit sigStrokeEndRequested(); } void KisImage::refreshGraph(KisNodeSP root) { refreshGraph(root, bounds(), bounds()); } void KisImage::refreshGraph(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefresh(root, rc, cropRect); } void KisImage::initialRefreshGraph() { /** * NOTE: Tricky part. We set crop rect to null, so the clones * will not rely on precalculated projections of their sources */ refreshGraphAsync(0, bounds(), QRect()); waitForDone(); } void KisImage::refreshGraphAsync(KisNodeSP root) { refreshGraphAsync(root, bounds(), bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc) { refreshGraphAsync(root, rc, bounds()); } void KisImage::refreshGraphAsync(KisNodeSP root, const QRect &rc, const QRect &cropRect) { if (!root) root = m_d->rootLayer; m_d->animationInterface->notifyNodeChanged(root.data(), rc, true); m_d->scheduler.fullRefreshAsync(root, rc, cropRect); } void KisImage::requestProjectionUpdateNoFilthy(KisNodeSP pseudoFilthy, const QRect &rc, const QRect &cropRect) { KIS_ASSERT_RECOVER_RETURN(pseudoFilthy); m_d->animationInterface->notifyNodeChanged(pseudoFilthy.data(), rc, false); m_d->scheduler.updateProjectionNoFilthy(pseudoFilthy, rc, cropRect); } void KisImage::addSpontaneousJob(KisSpontaneousJob *spontaneousJob) { m_d->scheduler.addSpontaneousJob(spontaneousJob); } void KisImage::setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP filter) { // udpate filters are *not* recursive! KIS_ASSERT_RECOVER_NOOP(!filter || !m_d->projectionUpdatesFilter); m_d->projectionUpdatesFilter = filter; } KisProjectionUpdatesFilterSP KisImage::projectionUpdatesFilter() const { return m_d->projectionUpdatesFilter; } void KisImage::disableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP(new KisDropAllProjectionUpdatesFilter())); } void KisImage::enableDirtyRequests() { setProjectionUpdatesFilter(KisProjectionUpdatesFilterSP()); } void KisImage::disableUIUpdates() { m_d->disableUIUpdateSignals.ref(); } void KisImage::enableUIUpdates() { m_d->disableUIUpdateSignals.deref(); } void KisImage::notifyProjectionUpdated(const QRect &rc) { KisUpdateTimeMonitor::instance()->reportUpdateFinished(rc); if (!m_d->disableUIUpdateSignals) { int lod = currentLevelOfDetail(); QRect dirtyRect = !lod ? rc : KisLodTransform::upscaledRect(rc, lod); if (dirtyRect.isEmpty()) return; emit sigImageUpdated(dirtyRect); } } void KisImage::notifySelectionChanged() { /** * The selection is calculated asynchromously, so it is not * handled by disableUIUpdates() and other special signals of * KisImageSignalRouter */ m_d->legacyUndoAdapter.emitSelectionChanged(); /** * Editing of selection masks doesn't necessary produce a * setDirty() call, so in the end of the stroke we need to request * direct update of the UI's cache. */ if (m_d->isolatedRootNode && dynamic_cast(m_d->isolatedRootNode.data())) { notifyProjectionUpdated(bounds()); } } void KisImage::requestProjectionUpdateImpl(KisNode *node, const QRect &rect, const QRect &cropRect) { if (rect.isEmpty()) return; KisNodeGraphListener::requestProjectionUpdate(node, rect); m_d->scheduler.updateProjection(node, rect, cropRect); } void KisImage::requestProjectionUpdate(KisNode *node, const QRect& rect) { if (m_d->projectionUpdatesFilter && m_d->projectionUpdatesFilter->filter(this, node, rect)) { return; } m_d->animationInterface->notifyNodeChanged(node, rect, false); /** * Here we use 'permitted' instead of 'active' intentively, * because the updates may come after the actual stroke has been * finished. And having some more updates for the stroke not * supporting the wrap-around mode will not make much harm. */ if (m_d->wrapAroundModePermitted) { const QRect boundRect = effectiveLodBounds(); KisWrappedRect splitRect(rect, boundRect); Q_FOREACH (const QRect &rc, splitRect) { requestProjectionUpdateImpl(node, rc, boundRect); } } else { requestProjectionUpdateImpl(node, rect, bounds()); } } void KisImage::invalidateFrames(const KisTimeRange &range, const QRect &rect) { m_d->animationInterface->invalidateFrames(range, rect); } void KisImage::requestTimeSwitch(int time) { m_d->animationInterface->requestTimeSwitchNonGUI(time); } QList KisImage::compositions() { return m_d->compositions; } void KisImage::addComposition(KisLayerCompositionSP composition) { m_d->compositions.append(composition); } void KisImage::removeComposition(KisLayerCompositionSP composition) { m_d->compositions.removeAll(composition); } bool checkMasksNeedConversion(KisNodeSP root, const QRect &bounds) { KisSelectionMask *mask = dynamic_cast(root.data()); if (mask && (!bounds.contains(mask->paintDevice()->exactBounds()) || mask->selection()->hasShapeSelection())) { return true; } KisNodeSP node = root->firstChild(); while (node) { if (checkMasksNeedConversion(node, bounds)) { return true; } node = node->nextSibling(); } return false; } void KisImage::setWrapAroundModePermitted(bool value) { m_d->wrapAroundModePermitted = value; if (m_d->wrapAroundModePermitted && checkMasksNeedConversion(root(), bounds())) { KisProcessingApplicator applicator(this, root(), KisProcessingApplicator::RECURSIVE, KisImageSignalVector() << ModifiedSignal, kundo2_i18n("Crop Selections")); KisProcessingVisitorSP visitor = new KisCropSelectionsProcessingVisitor(bounds()); applicator.applyVisitor(visitor, KisStrokeJobData::CONCURRENT); applicator.end(); } } bool KisImage::wrapAroundModePermitted() const { return m_d->wrapAroundModePermitted; } bool KisImage::wrapAroundModeActive() const { return m_d->wrapAroundModePermitted && m_d->scheduler.wrapAroundModeSupported(); } void KisImage::setDesiredLevelOfDetail(int lod) { if (m_d->blockLevelOfDetail) { qWarning() << "WARNING: KisImage::setDesiredLevelOfDetail()" << "was called while LoD functionality was being blocked!"; return; } m_d->scheduler.setDesiredLevelOfDetail(lod); } int KisImage::currentLevelOfDetail() const { if (m_d->blockLevelOfDetail) { return 0; } return m_d->scheduler.currentLevelOfDetail(); } void KisImage::setLevelOfDetailBlocked(bool value) { KisImageBarrierLockerRaw l(this); if (value && !m_d->blockLevelOfDetail) { m_d->scheduler.setDesiredLevelOfDetail(0); } m_d->blockLevelOfDetail = value; } void KisImage::explicitRegenerateLevelOfDetail() { if (!m_d->blockLevelOfDetail) { m_d->scheduler.explicitRegenerateLevelOfDetail(); } } bool KisImage::levelOfDetailBlocked() const { return m_d->blockLevelOfDetail; } void KisImage::notifyNodeCollpasedChanged() { emit sigNodeCollapsedChanged(); } KisImageAnimationInterface* KisImage::animationInterface() const { return m_d->animationInterface; } void KisImage::setProofingConfiguration(KisProofingConfigurationSP proofingConfig) { m_d->proofingConfig = proofingConfig; emit sigProofingConfigChanged(); } KisProofingConfigurationSP KisImage::proofingConfiguration() const { if (!m_d->proofingConfig) { KisImageConfig cfg; m_d->proofingConfig = cfg.defaultProofingconfiguration(); } return m_d->proofingConfig; } diff --git a/libs/image/kis_layer.cc b/libs/image/kis_layer.cc index 0150ad893d..5561e94e41 100644 --- a/libs/image/kis_layer.cc +++ b/libs/image/kis_layer.cc @@ -1,840 +1,839 @@ /* * 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 "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_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" class KisSafeProjection { public: KisPaintDeviceSP getDeviceLazy(KisPaintDeviceSP prototype) { QMutexLocker locker(&m_lock); if (!m_reusablePaintDevice) { m_reusablePaintDevice = new KisPaintDevice(*prototype); } if(!m_projection || *m_projection->colorSpace() != *prototype->colorSpace()) { m_projection = m_reusablePaintDevice; m_projection->makeCloneFromRough(prototype, prototype->extent()); m_projection->setProjectionDevice(true); } return m_projection; } void freeDevice() { QMutexLocker locker(&m_lock); m_projection = 0; if(m_reusablePaintDevice) { m_reusablePaintDevice->clear(); } } private: QMutex m_lock; KisPaintDeviceSP m_projection; KisPaintDeviceSP m_reusablePaintDevice; }; 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 (KisCloneLayerWSP clone, m_clonesList) { clone->setDirtyOriginal(rect); } } const QList registeredClones() const { return m_clonesList; } bool hasClones() const { return !m_clonesList.isEmpty(); } private: QList m_clonesList; }; struct Q_DECL_HIDDEN KisLayer::Private { KisImageWSP image; QBitArray channelFlags; KisMetaData::Store* metaDataStore; KisSafeProjection safeProjection; KisCloneLayersList clonesList; KisPSDLayerStyleSP layerStyle; KisAbstractProjectionPlaneSP layerStyleProjectionPlane; KisAbstractProjectionPlaneSP projectionPlane; }; KisLayer::KisLayer(KisImageWSP image, const QString &name, quint8 opacity) : KisNode() , m_d(new Private) { setName(name); setOpacity(opacity); m_d->image = image; m_d->metaDataStore = new KisMetaData::Store(); m_d->projectionPlane = toQShared(new KisLayerProjectionPlane(this)); } KisLayer::KisLayer(const KisLayer& rhs) : KisNode(rhs) , m_d(new Private()) { if (this != &rhs) { m_d->image = rhs.m_d->image; 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)); if (rhs.m_d->layerStyle) { setLayerStyle(rhs.m_d->layerStyle->clone()); } } } KisLayer::~KisLayer() { delete m_d->metaDataStore; delete m_d; } const KoColorSpace * KisLayer::colorSpace() const { if (m_d->image) return m_d->image->colorSpace(); return 0; } 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; KisAbstractProjectionPlaneSP plane = !layerStyle->isEmpty() ? KisAbstractProjectionPlaneSP(new KisLayerStyleProjectionPlane(this)) : KisAbstractProjectionPlaneSP(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())); if (compositeOp()) { l << KisBaseNode::Property(KoID("compositeop", i18n("Composite 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) { nodeProperties().setProperty("temporary", t); } KisImageWSP KisLayer::image() const { return m_d->image; } void KisLayer::setImage(KisImageWSP image) { m_d->image = image; - for (uint i = 0; i < childCount(); ++i) { - // Only layers know about the image - KisLayer *layer = dynamic_cast(at(i).data()); - if (layer) { - layer->setImage(image); - } - // We lied, through the defaultBounds, masks also know about the image - KisMask *mask = dynamic_cast(at(i).data()); - if (mask) { - mask->setImage(image); - } + + KisNodeSP node = firstChild(); + while (node) { + KisLayerUtils::recursiveApplyNodes(node, + [image] (KisNodeSP node) { + node->setImage(image); + }); + + node = node->nextSibling(); } } 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) { KisNodeSP parentNode = parent(); KisPainter gc(mergedDevice); //Copy the pixels of previous layer with their actual alpha value prevLayer->disableAlphaChannel(false); prevLayer->projectionPlane()->apply(&gc, prevLayerProjectionExtent | image()->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 | image()->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); } KisSelectionMaskSP KisLayer::selectionMask() const { KoProperties properties; properties.setProperty("active", true); QList masks = childNodes(QStringList("KisSelectionMask"), properties); // return the first visible mask Q_FOREACH (KisNodeSP mask, masks) { if (mask->visible()) { return dynamic_cast(mask.data()); } } return 0; } KisSelectionSP KisLayer::selection() const { if (selectionMask()) { return selectionMask()->selection(); } else if (m_d->image) { return m_d->image->globalSelection(); } else { return 0; } } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// QList KisLayer::effectMasks(KisNodeSP lastNode) const { QList masks; if (childCount() > 0) { KoProperties properties; properties.setProperty("visible", true); QList nodes = childNodes(QStringList("KisEffectMask"), properties); Q_FOREACH (const KisNodeSP& node, nodes) { if (node == lastNode) break; KisEffectMaskSP mask = dynamic_cast(const_cast(node.data())); if (mask) masks.append(mask); } } return masks; } bool KisLayer::hasEffectMasks() const { if (childCount() == 0) return false; KisNodeSP node = firstChild(); while (node) { if (node->inherits("KisEffectMask") && node->visible()) { return true; } node = node->nextSibling(); } return false; } QRect KisLayer::masksChangeRect(const QList &masks, const QRect &requestedRect, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevChangeRect = requestedRect; /** * We set default value of the change rect for the case * when there is no mask at all */ QRect changeRect = requestedRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { changeRect = mask->changeRect(prevChangeRect); if (changeRect != prevChangeRect) rectVariesFlag = true; prevChangeRect = changeRect; } return changeRect; } QRect KisLayer::masksNeedRect(const QList &masks, const QRect &changeRect, QStack &applyRects, bool &rectVariesFlag) const { rectVariesFlag = false; QRect prevNeedRect = changeRect; QRect needRect; for (qint32 i = masks.size() - 1; i >= 0; i--) { applyRects.push(prevNeedRect); needRect = masks[i]->needRect(prevNeedRect); if (prevNeedRect != needRect) rectVariesFlag = true; prevNeedRect = needRect; } return needRect; } KisNode::PositionToFilthy calculatePositionToFilthy(KisNodeSP nodeInQuestion, KisNodeSP filthy, KisNodeSP parent) { if (parent == filthy || parent != filthy->parent()) { return KisNode::N_ABOVE_FILTHY; } if (nodeInQuestion == filthy) { return KisNode::N_FILTHY; } KisNodeSP node = nodeInQuestion->prevSibling(); while (node) { if (node == filthy) { return KisNode::N_ABOVE_FILTHY; } node = node->prevSibling(); } return KisNode::N_BELOW_FILTHY; } QRect KisLayer::applyMasks(const KisPaintDeviceSP source, KisPaintDeviceSP destination, const QRect &requestedRect, KisNodeSP filthyNode, KisNodeSP lastNode) const { Q_ASSERT(source); Q_ASSERT(destination); QList masks = effectMasks(lastNode); QRect changeRect; QRect needRect; if (masks.isEmpty()) { changeRect = requestedRect; if (source != destination) { copyOriginalToProjection(source, destination, requestedRect); } } else { QStack applyRects; bool changeRectVaries; bool needRectVaries; /** * FIXME: Assume that varying of the changeRect has already * been taken into account while preparing walkers */ changeRectVaries = false; changeRect = requestedRect; //changeRect = masksChangeRect(masks, requestedRect, // changeRectVaries); needRect = masksNeedRect(masks, changeRect, applyRects, needRectVaries); if (!changeRectVaries && !needRectVaries) { /** * A bit of optimization: * All filters will read/write exactly from/to the requested * rect so we needn't create temporary paint device, * just apply it onto destination */ Q_ASSERT(needRect == requestedRect); if (source != destination) { copyOriginalToProjection(source, destination, needRect); } Q_FOREACH (const KisEffectMaskSP& mask, masks) { const QRect maskApplyRect = applyRects.pop(); const QRect maskNeedRect = applyRects.isEmpty() ? needRect : applyRects.top(); PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(destination, maskApplyRect, maskNeedRect, maskPosition); } Q_ASSERT(applyRects.isEmpty()); } else { /** * We can't eliminate additional copy-op * as filters' behaviour may be quite insane here, * so let them work on their own paintDevice =) */ KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace()); tempDevice->prepareClone(source); copyOriginalToProjection(source, tempDevice, needRect); QRect maskApplyRect = applyRects.pop(); QRect maskNeedRect = needRect; Q_FOREACH (const KisEffectMaskSP& mask, masks) { PositionToFilthy maskPosition = calculatePositionToFilthy(mask, filthyNode, const_cast(this)); mask->apply(tempDevice, maskApplyRect, maskNeedRect, maskPosition); if (!applyRects.isEmpty()) { maskNeedRect = maskApplyRect; maskApplyRect = applyRects.pop(); } } Q_ASSERT(applyRects.isEmpty()); KisPainter::copyAreaOptimized(changeRect.topLeft(), tempDevice, destination, changeRect); } } return changeRect; } QRect KisLayer::updateProjection(const QRect& rect, KisNodeSP filthyNode) { QRect updatedRect = rect; KisPaintDeviceSP originalDevice = original(); if (!rect.isValid() || !visible() || !originalDevice) return QRect(); if (!needProjection() && !hasEffectMasks()) { m_d->safeProjection.freeDevice(); } 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 ? m_d->layerStyleProjectionPlane : m_d->projectionPlane; } KisAbstractProjectionPlaneSP KisLayer::internalProjectionPlane() const { return m_d->projectionPlane; } KisPaintDeviceSP KisLayer::projection() const { KisPaintDeviceSP originalDevice = original(); return needProjection() || hasEffectMasks() ? m_d->safeProjection.getDeviceLazy(originalDevice) : originalDevice; } QRect KisLayer::changeRect(const QRect &rect, PositionToFilthy pos) const { QRect changeRect = rect; changeRect = incomingChangeRect(changeRect); if(pos == KisNode::N_FILTHY) { QRect projectionToBeUpdated = projection()->exactBoundsAmortized() & changeRect; bool changeRectVaries; changeRect = outgoingChangeRect(changeRect); changeRect = masksChangeRect(effectMasks(), changeRect, changeRectVaries); /** * If the projection contains some dirty areas we should also * add them to the change rect, because they might have * changed. E.g. when a visibility of the mask has chnaged * while the parent layer was invinisble. */ if (!projectionToBeUpdated.isEmpty() && !changeRect.contains(projectionToBeUpdated)) { changeRect |= projectionToBeUpdated; } } // TODO: string comparizon: optimize! if (pos != KisNode::N_FILTHY && pos != KisNode::N_FILTHY_PROJECTION && compositeOpId() != COMPOSITE_COPY) { changeRect |= rect; } return changeRect; } QRect KisLayer::incomingChangeRect(const QRect &rect) const { return rect; } QRect KisLayer::outgoingChangeRect(const QRect &rect) const { return rect; } QImage KisLayer::createThumbnail(qint32 w, qint32 h) { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->createThumbnail(w, h, 1, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()) : QImage(); } 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::extent() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->extent() : QRect(); } QRect KisLayer::exactBounds() const { KisPaintDeviceSP originalDevice = original(); return originalDevice ? originalDevice->exactBounds() : QRect(); } KisLayerSP KisLayer::parentLayer() const { return dynamic_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 537d73ae04..be7eec3142 100644 --- a/libs/image/kis_layer.h +++ b/libs/image/kis_layer.h @@ -1,384 +1,384 @@ /* * 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 #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; 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); virtual ~KisLayer(); /// returns the image's colorSpace or null, if there is no image virtual const KoColorSpace * colorSpace() const; /// returns the layer's composite op for the colorspace of the layer's parent. const KoCompositeOp * compositeOp() const; KisPSDLayerStyleSP layerStyle() const; void setLayerStyle(KisPSDLayerStyleSP layerStyle); /** * \see a comment in KisNode::projectionPlane() */ virtual KisAbstractProjectionPlaneSP projectionPlane() const; /** * 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 KisAbstractProjectionPlaneSP 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; /** * Return the layer data before the effect masks have had their go * at it. */ virtual KisPaintDeviceSP original() const = 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; virtual KisBaseNode::PropertyList sectionModelProperties() const; virtual void setSectionModelProperties(const KisBaseNode::PropertyList &properties); /** * 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); /// returns the image this layer belongs to, or null if there is no image KisImageWSP image() const; /** * Set the image this layer belongs to. */ - virtual void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_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. * * Decendants that perform there 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); public: qint32 x() const; qint32 y() const; void setX(qint32 x); void setY(qint32 y); /** * Returns an approximation of where the bounds * of actual data of this layer are */ QRect extent() const; /** * Returns the exact bounds of where the actual data * of this layer resides */ QRect exactBounds() const; QImage createThumbnail(qint32 w, qint32 h); public: /** * Returns true if there are any effect masks present */ bool hasEffectMasks() const; /** * @return the list of effect masks */ QList effectMasks(KisNodeSP lastNode = 0) 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; 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; /** * @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); private: friend class KisLayerProjectionPlane; friend class KisTransformMask; friend class KisLayerTest; private: struct Private; Private * const m_d; }; Q_DECLARE_METATYPE(KisLayerSP) #endif // KIS_LAYER_H_ diff --git a/libs/image/kis_mask.h b/libs/image/kis_mask.h index f18906a85d..25197d3452 100644 --- a/libs/image/kis_mask.h +++ b/libs/image/kis_mask.h @@ -1,204 +1,204 @@ /* * 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); virtual ~KisMask(); - void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_OVERRIDE; bool allowAsChild(KisNodeSP node) const; /** * @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; const KoCompositeOp * compositeOp() const; /** * 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; /** * @return the same as paintDevice() */ KisPaintDeviceSP original() const; /** * @return the same as paintDevice() */ KisPaintDeviceSP projection() const; KisAbstractProjectionPlaneSP projectionPlane() const; /** * 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; QRect exactBounds() const; /** * overridden from KisBaseNode */ qint32 x() const; /** * overridden from KisBaseNode */ void setX(qint32 x); /** * overridden from KisBaseNode */ qint32 y() const; /** * overridden from KisBaseNode */ void setY(qint32 y); QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const; QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const; QImage createThumbnail(qint32 w, qint32 h); 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 QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const; private: friend class KisMaskProjectionPlane; private: struct Private; Private * const m_d; }; #endif diff --git a/libs/image/kis_paint_layer.h b/libs/image/kis_paint_layer.h index a119cc0b4d..8c9d121d59 100644 --- a/libs/image/kis_paint_layer.h +++ b/libs/image/kis_paint_layer.h @@ -1,180 +1,180 @@ /* * Copyright (c) 2005 C. Boemann * 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_PAINT_LAYER_H_ #define KIS_PAINT_LAYER_H_ #include "kis_types.h" #include "kis_layer.h" #include "kis_indirect_painting_support.h" #include class KoColorSpace; /** * This layer is of a type that can be d on. A paint layer can * have any number of effect masks, a transparency mask, a local * selection and a protection mask. * * The protection mask can be read/write, read-only or write-only. * The transparency mask has two rendering forms: as a selection mask * and by changing the transparency of the paint layer's pixels. */ class KRITAIMAGE_EXPORT KisPaintLayer : public KisLayer, public KisIndirectPaintingSupport { Q_OBJECT public: /** * Construct a paint layer with the given parameters. The default bounds of the paintdevice are overwriten. * @param image this layer belongs to, or null, if it shouldn't belong to any image * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 * @param dev is the paint device, that should be used */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, KisPaintDeviceSP dev); /** * Construct a paint layer with the given parameters * @param image this layer belongs to. it must not be null and it must have a valid color space. * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity); /** * Construct a paint layer with the given parameters * @param image this layer belongs to, or null, if it shouldn't belong to any image. image must not be null, if colorSpace is null * @param name of the layer * @param opacity in the range between OPACITY_TRANSPARENT_U8 and OPACITY_OPAQUE_U8 * @param colorSpace is the color space, that should be used to construct the paint device. it can be null, if the image is valid. */ KisPaintLayer(KisImageWSP image, const QString& name, quint8 opacity, const KoColorSpace * colorSpace); /** * Copy Constructor */ KisPaintLayer(const KisPaintLayer& rhs); virtual ~KisPaintLayer(); KisNodeSP clone() const { return KisNodeSP(new KisPaintLayer(*this)); } bool allowAsChild(KisNodeSP) const; const KoColorSpace * colorSpace() const; bool needProjection() const; using KisLayer::setDirty; void setDirty(const QRect & rect); QIcon icon() const; - void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_OVERRIDE; KisBaseNode::PropertyList sectionModelProperties() const; void setSectionModelProperties(const KisBaseNode::PropertyList &properties); public: QRect extent() const; QRect exactBounds() const; bool accept(KisNodeVisitor &v); void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter); /** * set the channelflags for locking certain channels (used by painting tools) * 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. */ void setChannelLockFlags(const QBitArray& channelFlags); /** * Return a bit array where each bit indicates whether a * particular channel is locked or not (used by painting tools). * If the channelflags bit array is empty, all channels are active. */ const QBitArray& channelLockFlags() const; /** * Returns the paintDevice that accompanies this layer */ KisPaintDeviceSP paintDevice() const; /** * Returns the original pixels before masks have been applied. */ KisPaintDeviceSP original() const; /** * @returns true when painting should not affect the alpha channel */ bool alphaLocked() const; /** * @param l if true, the alpha channel will be protected from modification */ void setAlphaLocked(bool lock); /** * @return true if onion skins should be rendered on this layer */ bool onionSkinEnabled() const; /** * @param state whether onion skins should be rendered */ void setOnionSkinEnabled(bool state); void enableAnimation(); KisPaintDeviceList getLodCapableDevices() const; public Q_SLOTS: void slotExternalUpdateOnionSkins(); public: // KisIndirectPaintingSupport KisLayer* layer() { return this; } protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; private: void init(KisPaintDeviceSP paintDevice, const QBitArray &paintChannelFlags = QBitArray()); struct Private; Private * const m_d; }; typedef KisSharedPtr KisPaintLayerSP; #endif // KIS_PAINT_LAYER_H_ diff --git a/libs/image/kis_selection_based_layer.h b/libs/image/kis_selection_based_layer.h index ceb4ef9048..1902444f00 100644 --- a/libs/image/kis_selection_based_layer.h +++ b/libs/image/kis_selection_based_layer.h @@ -1,209 +1,209 @@ /* * 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 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, bool useGeneratorRegistry = false); KisSelectionBasedLayer(const KisSelectionBasedLayer& rhs); virtual ~KisSelectionBasedLayer(); /** * 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; - void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_OVERRIDE; KisPaintDeviceSP original() const; KisPaintDeviceSP paintDevice() const; bool needProjection() const; /** * resets cached projection of lower layer to a new device * @return void */ void resetCache(const KoColorSpace *colorSpace = 0); /** * for KisLayer::setDirty(const QRegion&) */ 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(); void setDirty(const QRect & rect); 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; /** * gets this layer's y coordinate, taking selection into account * @return y-coordinate value */ qint32 y() const; /** * sets this layer's y coordinate, taking selection into account * @param x x coordinate */ void setX(qint32 x); /** * sets this layer's y coordinate, taking selection into account * @param y y coordinate */ void setY(qint32 y); public: /** * gets an approximation of where the bounds on actual data * are in this layer, taking selection into account */ QRect extent() const; /** * returns the exact bounds of where the actual data resides * in this layer, taking selection into account */ QRect exactBounds() const; /** * 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); protected: // override from KisLayer void copyOriginalToProjection(const KisPaintDeviceSP original, KisPaintDeviceSP projection, const QRect& rect) const; // override from KisNode QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const; 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; 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/image/lazybrush/kis_colorize_mask.h b/libs/image/lazybrush/kis_colorize_mask.h index 4fd517c20f..f5666690fa 100644 --- a/libs/image/lazybrush/kis_colorize_mask.h +++ b/libs/image/lazybrush/kis_colorize_mask.h @@ -1,142 +1,142 @@ /* * Copyright (c) 2016 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_COLORIZE_MASK_H #define __KIS_COLORIZE_MASK_H #include #include "kis_types.h" #include "kis_effect_mask.h" #include "kritaimage_export.h" class KoColor; class KUndo2Command; namespace KisLazyFillTools { struct KeyStroke; } class KRITAIMAGE_EXPORT KisColorizeMask : public KisEffectMask { Q_OBJECT public: struct KeyStrokeColors { QVector colors; int transparentIndex; }; public: KisColorizeMask(); ~KisColorizeMask(); KisColorizeMask(const KisColorizeMask& rhs); void initializeCompositeOp(); const KoColorSpace* colorSpace() const; // assign color profile without conversion of pixel data void setProfile(const KoColorProfile *profile); KUndo2Command* setColorSpace(const KoColorSpace * dstColorSpace, KoColorConversionTransformation::Intent renderingIntent = KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags()); KisPaintDeviceSP paintDevice() const; KisPaintDeviceSP coloringProjection() const; KisNodeSP clone() const { return KisNodeSP(new KisColorizeMask(*this)); } QIcon icon() const; - void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_OVERRIDE; bool accept(KisNodeVisitor &v); void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter); QRect decorateRect(KisPaintDeviceSP &src, KisPaintDeviceSP &dst, const QRect & rc, PositionToFilthy maskPos) const; void setCurrentColor(const KoColor &color); void mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID); void writeMergeData(KisPainter *painter, KisPaintDeviceSP src); QRect exactBounds() const; QRect extent() const; void setSectionModelProperties(const KisBaseNode::PropertyList &properties); KisBaseNode::PropertyList sectionModelProperties() const; KeyStrokeColors keyStrokesColors() const; void setKeyStrokesColors(KeyStrokeColors colors); void removeKeyStroke(const KoColor &color); QVector allPaintDevices() const; void resetCache(); void testingAddKeyStroke(KisPaintDeviceSP dev, const KoColor &color, bool isTransparent = false); void testingRegenerateMask(); KisPaintDeviceSP testingFilteredSource() const; QList fetchKeyStrokesDirect() const; void setKeyStrokesDirect(const QList &strokes); qint32 x() const; qint32 y() const; void setX(qint32 x); void setY(qint32 y); KisPaintDeviceList getLodCapableDevices() const; private Q_SLOTS: void slotUpdateRegenerateFilling(); void slotRegenerationFinished(); Q_SIGNALS: void sigKeyStrokesListChanged(); private: // NOTE: please access this methods using model properties only! bool needsUpdate() const; void setNeedsUpdate(bool value); bool showColoring() const; void setShowColoring(bool value); bool showKeyStrokes() const; void setShowKeyStrokes(bool value); private: void rerenderFakePaintDevice(); KisImageSP fetchImage() const; void moveAllInternalDevices(const QPoint &diff); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_COLORIZE_MASK_H */ diff --git a/libs/ui/flake/kis_shape_layer.h b/libs/ui/flake/kis_shape_layer.h index 35fb081dfd..c4b29d62d2 100644 --- a/libs/ui/flake/kis_shape_layer.h +++ b/libs/ui/flake/kis_shape_layer.h @@ -1,150 +1,150 @@ /* * Copyright (c) 2006 Boudewijn Rempt * Copyright (c) 2007 Thomas Zander * * 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_SHAPE_LAYER_H_ #define KIS_SHAPE_LAYER_H_ #include #include #include #include class QRect; class QIcon; class QRect; class QString; class KoShapeManager; class KoStore; class KoViewConverter; class KoShapeBasedDocumentBase; const QString KIS_SHAPE_LAYER_ID = "KisShapeLayer"; /** A KisShapeLayer contains any number of non-krita flakes, such as path shapes, text shapes and anything else people come up with. The KisShapeLayer has a shapemanager and a canvas of its own. The canvas paints onto the projection, and the projection is what we render in Krita. This means that no matter how many views you have, you cannot have a different view on your shapes per view. XXX: what about removing shapes? */ class KRITAUI_EXPORT KisShapeLayer : public KisExternalLayer, public KoShapeLayer { Q_OBJECT public: KisShapeLayer(KoShapeBasedDocumentBase* shapeController, KisImageWSP image, const QString &name, quint8 opacity); KisShapeLayer(const KisShapeLayer& _rhs); KisShapeLayer(const KisShapeLayer& _rhs, KoShapeBasedDocumentBase* controller); /** * Merge constructor. * * Creates a new layer as a merge of two existing layers. * * This is used by createMergedLayer() */ KisShapeLayer(const KisShapeLayer& _merge, const KisShapeLayer &_addShapes); virtual ~KisShapeLayer(); private: void initShapeLayer(KoShapeBasedDocumentBase* controller); public: KisNodeSP clone() const { return new KisShapeLayer(*this); } bool allowAsChild(KisNodeSP) const; - virtual void setImage(KisImageWSP image); + void setImage(KisImageWSP image) Q_DECL_OVERRIDE; virtual KisLayerSP createMergedLayerTemplate(KisLayerSP prevLayer); virtual void fillMergedLayerTemplate(KisLayerSP dstLayer, KisLayerSP prevLayer); public: // KoShape overrides bool isSelectable() const { return false; } void setParent(KoShapeContainer *parent); // KisExternalLayer implementation QIcon icon() const; void resetCache(); KisPaintDeviceSP original() const; KisPaintDeviceSP paintDevice() const; qint32 x() const; qint32 y() const; void setX(qint32); void setY(qint32); bool accept(KisNodeVisitor&); void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter); KoShapeManager *shapeManager() const; bool saveLayer(KoStore * store) const; bool loadLayer(KoStore* store); KUndo2Command* crop(const QRect & rect); KUndo2Command* transform(const QTransform &transform); bool visible(bool recursive = false) const; void setVisible(bool visible, bool isLoading = false); protected: using KoShape::isVisible; friend class ShapeLayerContainerModel; KoViewConverter* converter() const; Q_SIGNALS: /** * These signals are forwarded from the local shape manager * This is done because we switch KoShapeManager and therefore * KoSelection in KisCanvas2, so we need to connect local managers * to the UI as well. * * \see comment in the constructor of KisCanvas2 */ void selectionChanged(); void currentLayerChanged(const KoShapeLayer *layer); Q_SIGNALS: /** * A signal + slot to synchronize UI and image * threads. Image thread emits the signal, UI * thread performes the action */ void sigMoveShapes(const QPointF &diff); private Q_SLOTS: void slotMoveShapes(const QPointF &diff); private: struct Private; Private * const m_d; }; #endif