File indexing completed on 2024-05-12 15:58:09

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_base_node.h"
0008 #include <klocalizedstring.h>
0009 
0010 #include <kis_image.h>
0011 #include <kis_icon.h>
0012 #include <KoProperties.h>
0013 #include <KisAnimatedOpacityProperty.h>
0014 #include <KoColorSpace.h>
0015 #include <KoCompositeOpRegistry.h>
0016 
0017 #include <QSharedPointer>
0018 #include "kis_pointer_utils.h"
0019 
0020 #include "kis_paint_device.h"
0021 #include "kis_layer_properties_icons.h"
0022 #include "kis_default_bounds_node_wrapper.h"
0023 
0024 #include "kis_scalar_keyframe_channel.h"
0025 
0026 struct Q_DECL_HIDDEN KisBaseNode::Private
0027 {
0028     QString compositeOp;
0029     KoProperties properties;
0030     KisBaseNode::Property hack_visible; //HACK
0031     QUuid id;
0032     QMap<QString, KisKeyframeChannel*> keyframeChannels;
0033     KisAnimatedOpacityProperty opacityProperty;
0034 
0035     bool collapsed {false};
0036     bool supportsLodMoves {false};
0037     bool animated {false};
0038     bool pinnedToTimeline {false};
0039     KisImageWSP image;
0040 
0041     Private(KisImageWSP p_image)
0042         : id(QUuid::createUuid())
0043         , opacityProperty(new KisDefaultBounds(p_image), &properties, OPACITY_OPAQUE_U8)
0044         , image(p_image)
0045     {
0046     }
0047 
0048     Private(const Private &rhs)
0049         : compositeOp(rhs.compositeOp),
0050           id(QUuid::createUuid()),
0051           opacityProperty(new KisDefaultBounds(rhs.image), &properties, OPACITY_OPAQUE_U8),
0052           collapsed(rhs.collapsed),
0053           supportsLodMoves(rhs.supportsLodMoves),
0054           animated(rhs.animated),
0055           pinnedToTimeline(rhs.pinnedToTimeline),
0056           image(rhs.image)
0057     {
0058         QMapIterator<QString, QVariant> iter = rhs.properties.propertyIterator();
0059         while (iter.hasNext()) {
0060             iter.next();
0061             properties.setProperty(iter.key(), iter.value());
0062         }
0063     }
0064 };
0065 
0066 KisBaseNode::KisBaseNode(KisImageWSP image)
0067     : m_d(new Private(image))
0068 {
0069     /**
0070      * Be cautious! These two calls are vital to warm-up KoProperties.
0071      * We use it and its QMap in a threaded environment. This is not
0072      * officially supported by Qt, but our environment guarantees, that
0073      * there will be the only writer and several readers. Whilst the
0074      * value of the QMap is boolean and there are no implicit-sharing
0075      * calls provocated, it is safe to work with it in such an
0076      * environment.
0077      */
0078     setVisible(true, true);
0079     setUserLocked(false);
0080     setCollapsed(false);
0081     setSupportsLodMoves(true);
0082 
0083     m_d->compositeOp = COMPOSITE_OVER;
0084 
0085     connect(&m_d->opacityProperty, SIGNAL(changed(quint8)), this, SIGNAL(opacityChanged(quint8)));
0086 }
0087 
0088 
0089 KisBaseNode::KisBaseNode(const KisBaseNode & rhs)
0090     : QObject()
0091     , KisShared()
0092     , m_d(new Private(*rhs.m_d))
0093 {
0094     if (rhs.m_d->opacityProperty.hasChannel()) {
0095         m_d->opacityProperty.transferKeyframeData(rhs.m_d->opacityProperty);
0096         m_d->keyframeChannels.insert(m_d->opacityProperty.channel()->id(), m_d->opacityProperty.channel());
0097     }
0098 
0099     connect(&m_d->opacityProperty, SIGNAL(changed(quint8)), this, SIGNAL(opacityChanged(quint8)));
0100 }
0101 
0102 KisBaseNode::~KisBaseNode()
0103 {
0104     delete m_d;
0105 }
0106 
0107 KisPaintDeviceSP KisBaseNode::colorSampleSourceDevice() const
0108 {
0109     return projection();
0110 }
0111 
0112 quint8 KisBaseNode::opacity() const
0113 {
0114     return m_d->opacityProperty.get();
0115 }
0116 
0117 void KisBaseNode::setOpacity(quint8 val)
0118 {
0119     m_d->opacityProperty.set(val);
0120 }
0121 
0122 quint8 KisBaseNode::percentOpacity() const
0123 {
0124     return int(float(opacity() * 100) / 255 + 0.5);
0125 }
0126 
0127 void KisBaseNode::setPercentOpacity(quint8 val)
0128 {
0129     setOpacity(int(float(val * 255) / 100 + 0.5));
0130 }
0131 
0132 const QString& KisBaseNode::compositeOpId() const
0133 {
0134     return m_d->compositeOp;
0135 }
0136 
0137 void KisBaseNode::setCompositeOpId(const QString& compositeOp)
0138 {
0139     if (m_d->compositeOp == compositeOp) return;
0140 
0141     m_d->compositeOp = compositeOp;
0142     baseNodeChangedCallback();
0143     baseNodeInvalidateAllFramesCallback();
0144 }
0145 
0146 KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const
0147 {
0148     KisBaseNode::PropertyList l;
0149     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis);
0150     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked());
0151     return l;
0152 }
0153 
0154 void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
0155 {
0156     setVisible(properties.at(0).state.toBool());
0157     m_d->hack_visible = properties.at(0);
0158     setUserLocked(properties.at(1).state.toBool());
0159 }
0160 
0161 const KoProperties & KisBaseNode::nodeProperties() const
0162 {
0163     return m_d->properties;
0164 }
0165 
0166 void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value)
0167 {
0168     m_d->properties.setProperty(name, value);
0169     baseNodeChangedCallback();
0170 }
0171 
0172 void KisBaseNode::mergeNodeProperties(const KoProperties & properties)
0173 {
0174     QMapIterator<QString, QVariant> iter = properties.propertyIterator();
0175     while (iter.hasNext()) {
0176         iter.next();
0177         m_d->properties.setProperty(iter.key(), iter.value());
0178     }
0179     baseNodeChangedCallback();
0180     baseNodeInvalidateAllFramesCallback();
0181 }
0182 
0183 bool KisBaseNode::check(const KoProperties & properties) const
0184 {
0185     QMapIterator<QString, QVariant> iter = properties.propertyIterator();
0186     while (iter.hasNext()) {
0187         iter.next();
0188         if (m_d->properties.contains(iter.key())) {
0189             if (m_d->properties.value(iter.key()) != iter.value())
0190                 return false;
0191         }
0192     }
0193     return true;
0194 }
0195 
0196 
0197 QImage KisBaseNode::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode)
0198 {
0199     Q_UNUSED(aspectRatioMode);
0200 
0201     try {
0202         QImage image(w, h, QImage::Format_ARGB32);
0203         image.fill(0);
0204         return image;
0205     } catch (const std::bad_alloc&) {
0206         return QImage();
0207     }
0208 
0209 }
0210 
0211 QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode)
0212 {
0213     Q_UNUSED(time);
0214     Q_UNUSED(aspectRatioMode);
0215     return createThumbnail(w, h);
0216 }
0217 
0218 bool KisBaseNode::visible(bool recursive) const
0219 {
0220     bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
0221     KisBaseNodeSP parentNode = parentCallback();
0222 
0223     return recursive && isVisible && parentNode ?
0224         parentNode->visible(recursive) : isVisible;
0225 }
0226 
0227 void KisBaseNode::setVisible(bool visible, bool loading)
0228 {
0229     const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
0230     if (!loading && isVisible == visible) return;
0231 
0232     m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible);
0233     notifyParentVisibilityChanged(visible);
0234 
0235     if (!loading) {
0236         baseNodeChangedCallback();
0237         baseNodeInvalidateAllFramesCallback();
0238     }
0239 }
0240 
0241 bool KisBaseNode::userLocked() const
0242 {
0243     return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false);
0244 }
0245 
0246 bool KisBaseNode::belongsToIsolatedGroup() const
0247 {
0248     if (!m_d->image) {
0249         return false;
0250     }
0251 
0252     const KisBaseNode* element = this;
0253 
0254     while (element) {
0255         if (element->isIsolatedRoot()) {
0256             return true;
0257         } else {
0258             element = element->parentCallback().data();
0259         }
0260     }
0261 
0262     return false;
0263 }
0264 
0265 bool KisBaseNode::isIsolatedRoot() const
0266 {
0267     if (!m_d->image) {
0268         return false;
0269     }
0270 
0271     const KisBaseNode* isolatedRoot = m_d->image->isolationRootNode().data();
0272 
0273     return (this == isolatedRoot);
0274 }
0275 
0276 void KisBaseNode::setUserLocked(bool locked)
0277 {
0278     const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true);
0279     if (isLocked == locked) return;
0280 
0281     m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked);
0282     baseNodeChangedCallback();
0283 }
0284 
0285 bool KisBaseNode::isEditable(bool checkVisibility) const
0286 {
0287     bool editable = true;
0288     if (checkVisibility) {
0289         editable = ((visible(false) || belongsToIsolatedGroup()) && !userLocked());
0290     }
0291     else {
0292         editable = (!userLocked());
0293     }
0294 
0295     if (editable) {
0296         KisBaseNodeSP parentNode = parentCallback();
0297         if (parentNode && parentNode != this) {
0298             editable = parentNode->isEditable(checkVisibility);
0299         }
0300     }
0301     return editable;
0302 }
0303 
0304 bool KisBaseNode::hasEditablePaintDevice() const
0305 {
0306     return paintDevice() && isEditable();
0307 }
0308 
0309 void KisBaseNode::setCollapsed(bool collapsed)
0310 {
0311     const bool oldCollapsed = m_d->collapsed;
0312 
0313     m_d->collapsed = collapsed;
0314 
0315     if (oldCollapsed != collapsed) {
0316         baseNodeCollapsedChangedCallback();
0317     }
0318 }
0319 
0320 bool KisBaseNode::collapsed() const
0321 {
0322     return m_d->collapsed;
0323 }
0324 
0325 void KisBaseNode::setColorLabelIndex(int index)
0326 {
0327     const int currentLabel = colorLabelIndex();
0328 
0329     if (currentLabel == index) return;
0330 
0331     m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index);
0332     baseNodeChangedCallback();
0333 }
0334 
0335 int KisBaseNode::colorLabelIndex() const
0336 {
0337     return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0);
0338 }
0339 
0340 QUuid KisBaseNode::uuid() const
0341 {
0342     return m_d->id;
0343 }
0344 
0345 void KisBaseNode::setUuid(const QUuid& id)
0346 {
0347     m_d->id = id;
0348     baseNodeChangedCallback();
0349 }
0350 
0351 bool KisBaseNode::supportsLodMoves() const
0352 {
0353     return m_d->supportsLodMoves;
0354 }
0355 
0356 bool KisBaseNode::supportsLodPainting() const
0357 {
0358     return true;
0359 }
0360 
0361 void KisBaseNode::setImage(KisImageWSP image)
0362 {
0363     m_d->image = image;
0364     m_d->opacityProperty.updateDefaultBounds(new KisDefaultBounds(image));
0365 }
0366 
0367 KisImageWSP KisBaseNode::image() const
0368 {
0369     return m_d->image;
0370 }
0371 
0372 bool KisBaseNode::isFakeNode() const
0373 {
0374     return false;
0375 }
0376 
0377 void KisBaseNode::setSupportsLodMoves(bool value)
0378 {
0379     m_d->supportsLodMoves = value;
0380 }
0381 
0382 
0383 QMap<QString, KisKeyframeChannel*> KisBaseNode::keyframeChannels() const
0384 {
0385     return m_d->keyframeChannels;
0386 }
0387 
0388 KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const
0389 {
0390     QMap<QString, KisKeyframeChannel*>::const_iterator i = m_d->keyframeChannels.constFind(id);
0391     if (i == m_d->keyframeChannels.constEnd()) {
0392         return 0;
0393     }
0394     return i.value();
0395 }
0396 
0397 bool KisBaseNode::isPinnedToTimeline() const
0398 {
0399     return m_d->pinnedToTimeline;
0400 }
0401 
0402 void KisBaseNode::setPinnedToTimeline(bool pinned)
0403 {
0404    if (pinned == m_d->pinnedToTimeline) return;
0405 
0406    m_d->pinnedToTimeline = pinned;
0407    baseNodeChangedCallback();
0408 }
0409 
0410 KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create)
0411 {
0412     KisKeyframeChannel *channel = getKeyframeChannel(id);
0413 
0414     if (!channel && create) {
0415         channel = requestKeyframeChannel(id);
0416 
0417         if (channel) {
0418             addKeyframeChannel(channel);
0419         }
0420     }
0421 
0422     return channel;
0423 }
0424 
0425 bool KisBaseNode::isAnimated() const
0426 {
0427     return m_d->animated;
0428 }
0429 
0430 void KisBaseNode::enableAnimation()
0431 {
0432     m_d->animated = true;
0433     baseNodeChangedCallback();
0434 }
0435 
0436 void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel)
0437 {
0438     m_d->keyframeChannels.insert(channel->id(), channel);
0439     emit keyframeChannelAdded(channel);
0440 }
0441 
0442 KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id)
0443 {
0444     if (id == KisKeyframeChannel::Opacity.id()) {
0445         Q_ASSERT(!m_d->opacityProperty.hasChannel());
0446 
0447         KisPaintDeviceSP device = original();
0448         KisNode* node = dynamic_cast<KisNode*>(this);
0449 
0450         if (device && node) {
0451             m_d->opacityProperty.makeAnimated(node);
0452             return m_d->opacityProperty.channel();
0453         }
0454     }
0455 
0456     return 0;
0457 }
0458 
0459 bool KisBaseNode::supportsKeyframeChannel(const QString &id)
0460 {
0461     if (id == KisKeyframeChannel::Opacity.id() && original()) {
0462         return true;
0463     }
0464 
0465     return false;
0466 }
0467 
0468 QDebug operator<<(QDebug dbg, const KisBaseNode::Property &prop)
0469 {
0470     dbg.nospace() << "Property(" << prop.id << ", " << prop.state;
0471 
0472     if (prop.isInStasis) {
0473         dbg.nospace() << ", in-stasis";
0474     }
0475 
0476     dbg.nospace() << ")";
0477 
0478     return dbg.space();
0479 }