File indexing completed on 2024-05-19 04:26:07

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     baseNodeChangedCallback();
0121 }
0122 
0123 quint8 KisBaseNode::percentOpacity() const
0124 {
0125     return int(float(opacity() * 100) / 255 + 0.5);
0126 }
0127 
0128 void KisBaseNode::setPercentOpacity(quint8 val)
0129 {
0130     setOpacity(int(float(val * 255) / 100 + 0.5));
0131 }
0132 
0133 const QString& KisBaseNode::compositeOpId() const
0134 {
0135     return m_d->compositeOp;
0136 }
0137 
0138 void KisBaseNode::setCompositeOpId(const QString& compositeOp)
0139 {
0140     if (m_d->compositeOp == compositeOp) return;
0141 
0142     m_d->compositeOp = compositeOp;
0143     baseNodeChangedCallback();
0144     baseNodeInvalidateAllFramesCallback();
0145 }
0146 
0147 KisBaseNode::PropertyList KisBaseNode::sectionModelProperties() const
0148 {
0149     KisBaseNode::PropertyList l;
0150     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::visible, visible(), m_d->hack_visible.isInStasis, m_d->hack_visible.stateInStasis);
0151     l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::locked, userLocked());
0152     return l;
0153 }
0154 
0155 void KisBaseNode::setSectionModelProperties(const KisBaseNode::PropertyList &properties)
0156 {
0157     setVisible(properties.at(0).state.toBool());
0158     m_d->hack_visible = properties.at(0);
0159     setUserLocked(properties.at(1).state.toBool());
0160 }
0161 
0162 const KoProperties & KisBaseNode::nodeProperties() const
0163 {
0164     return m_d->properties;
0165 }
0166 
0167 void KisBaseNode::setNodeProperty(const QString & name, const QVariant & value)
0168 {
0169     m_d->properties.setProperty(name, value);
0170     baseNodeChangedCallback();
0171 }
0172 
0173 void KisBaseNode::mergeNodeProperties(const KoProperties & properties)
0174 {
0175     QMapIterator<QString, QVariant> iter = properties.propertyIterator();
0176     while (iter.hasNext()) {
0177         iter.next();
0178         m_d->properties.setProperty(iter.key(), iter.value());
0179     }
0180     baseNodeChangedCallback();
0181     baseNodeInvalidateAllFramesCallback();
0182 }
0183 
0184 bool KisBaseNode::check(const KoProperties & properties) const
0185 {
0186     QMapIterator<QString, QVariant> iter = properties.propertyIterator();
0187     while (iter.hasNext()) {
0188         iter.next();
0189         if (m_d->properties.contains(iter.key())) {
0190             if (m_d->properties.value(iter.key()) != iter.value())
0191                 return false;
0192         }
0193     }
0194     return true;
0195 }
0196 
0197 
0198 QImage KisBaseNode::createThumbnail(qint32 w, qint32 h, Qt::AspectRatioMode aspectRatioMode)
0199 {
0200     Q_UNUSED(aspectRatioMode);
0201 
0202     try {
0203         QImage image(w, h, QImage::Format_ARGB32);
0204         image.fill(0);
0205         return image;
0206     } catch (const std::bad_alloc&) {
0207         return QImage();
0208     }
0209 
0210 }
0211 
0212 int KisBaseNode::thumbnailSeqNo() const
0213 {
0214     return -1;
0215 }
0216 
0217 QImage KisBaseNode::createThumbnailForFrame(qint32 w, qint32 h, int time, Qt::AspectRatioMode aspectRatioMode)
0218 {
0219     Q_UNUSED(time);
0220     Q_UNUSED(aspectRatioMode);
0221     return createThumbnail(w, h);
0222 }
0223 
0224 bool KisBaseNode::visible(bool recursive) const
0225 {
0226     bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
0227     KisBaseNodeSP parentNode = parentCallback();
0228 
0229     return recursive && isVisible && parentNode ?
0230         parentNode->visible(recursive) : isVisible;
0231 }
0232 
0233 void KisBaseNode::setVisible(bool visible, bool loading)
0234 {
0235     const bool isVisible = m_d->properties.boolProperty(KisLayerPropertiesIcons::visible.id(), true);
0236     if (!loading && isVisible == visible) return;
0237 
0238     m_d->properties.setProperty(KisLayerPropertiesIcons::visible.id(), visible);
0239     notifyParentVisibilityChanged(visible);
0240 
0241     if (!loading) {
0242         baseNodeChangedCallback();
0243         baseNodeInvalidateAllFramesCallback();
0244     }
0245 }
0246 
0247 bool KisBaseNode::userLocked() const
0248 {
0249     return m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), false);
0250 }
0251 
0252 bool KisBaseNode::belongsToIsolatedGroup() const
0253 {
0254     if (!m_d->image) {
0255         return false;
0256     }
0257 
0258     const KisBaseNode* element = this;
0259 
0260     while (element) {
0261         if (element->isIsolatedRoot()) {
0262             return true;
0263         } else {
0264             element = element->parentCallback().data();
0265         }
0266     }
0267 
0268     return false;
0269 }
0270 
0271 bool KisBaseNode::isIsolatedRoot() const
0272 {
0273     if (!m_d->image) {
0274         return false;
0275     }
0276 
0277     const KisBaseNode* isolatedRoot = m_d->image->isolationRootNode().data();
0278 
0279     return (this == isolatedRoot);
0280 }
0281 
0282 void KisBaseNode::setUserLocked(bool locked)
0283 {
0284     const bool isLocked = m_d->properties.boolProperty(KisLayerPropertiesIcons::locked.id(), true);
0285     if (isLocked == locked) return;
0286 
0287     m_d->properties.setProperty(KisLayerPropertiesIcons::locked.id(), locked);
0288     baseNodeChangedCallback();
0289 }
0290 
0291 bool KisBaseNode::isEditable(bool checkVisibility) const
0292 {
0293     bool editable = true;
0294     if (checkVisibility) {
0295         editable = ((visible(false) || belongsToIsolatedGroup()) && !userLocked());
0296     }
0297     else {
0298         editable = (!userLocked());
0299     }
0300 
0301     if (editable) {
0302         KisBaseNodeSP parentNode = parentCallback();
0303         if (parentNode && parentNode != this) {
0304             editable = parentNode->isEditable(checkVisibility);
0305         }
0306     }
0307     return editable;
0308 }
0309 
0310 bool KisBaseNode::hasEditablePaintDevice() const
0311 {
0312     return paintDevice() && isEditable();
0313 }
0314 
0315 void KisBaseNode::setCollapsed(bool collapsed)
0316 {
0317     const bool oldCollapsed = m_d->collapsed;
0318 
0319     m_d->collapsed = collapsed;
0320 
0321     if (oldCollapsed != collapsed) {
0322         baseNodeCollapsedChangedCallback();
0323     }
0324 }
0325 
0326 bool KisBaseNode::collapsed() const
0327 {
0328     return m_d->collapsed;
0329 }
0330 
0331 void KisBaseNode::setColorLabelIndex(int index)
0332 {
0333     const int currentLabel = colorLabelIndex();
0334 
0335     if (currentLabel == index) return;
0336 
0337     m_d->properties.setProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), index);
0338     baseNodeChangedCallback();
0339 }
0340 
0341 int KisBaseNode::colorLabelIndex() const
0342 {
0343     return m_d->properties.intProperty(KisLayerPropertiesIcons::colorLabelIndex.id(), 0);
0344 }
0345 
0346 QUuid KisBaseNode::uuid() const
0347 {
0348     return m_d->id;
0349 }
0350 
0351 void KisBaseNode::setUuid(const QUuid& id)
0352 {
0353     m_d->id = id;
0354     baseNodeChangedCallback();
0355 }
0356 
0357 bool KisBaseNode::supportsLodMoves() const
0358 {
0359     return m_d->supportsLodMoves;
0360 }
0361 
0362 bool KisBaseNode::supportsLodPainting() const
0363 {
0364     return true;
0365 }
0366 
0367 void KisBaseNode::setImage(KisImageWSP image)
0368 {
0369     m_d->image = image;
0370     m_d->opacityProperty.updateDefaultBounds(new KisDefaultBounds(image));
0371 }
0372 
0373 KisImageWSP KisBaseNode::image() const
0374 {
0375     return m_d->image;
0376 }
0377 
0378 bool KisBaseNode::isFakeNode() const
0379 {
0380     return false;
0381 }
0382 
0383 void KisBaseNode::setSupportsLodMoves(bool value)
0384 {
0385     m_d->supportsLodMoves = value;
0386 }
0387 
0388 
0389 QMap<QString, KisKeyframeChannel*> KisBaseNode::keyframeChannels() const
0390 {
0391     return m_d->keyframeChannels;
0392 }
0393 
0394 KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const
0395 {
0396     QMap<QString, KisKeyframeChannel*>::const_iterator i = m_d->keyframeChannels.constFind(id);
0397     if (i == m_d->keyframeChannels.constEnd()) {
0398         return 0;
0399     }
0400     return i.value();
0401 }
0402 
0403 bool KisBaseNode::isPinnedToTimeline() const
0404 {
0405     return m_d->pinnedToTimeline;
0406 }
0407 
0408 void KisBaseNode::setPinnedToTimeline(bool pinned)
0409 {
0410    if (pinned == m_d->pinnedToTimeline) return;
0411 
0412    m_d->pinnedToTimeline = pinned;
0413    baseNodeChangedCallback();
0414 }
0415 
0416 KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create)
0417 {
0418     KisKeyframeChannel *channel = getKeyframeChannel(id);
0419 
0420     if (!channel && create) {
0421         channel = requestKeyframeChannel(id);
0422 
0423         if (channel) {
0424             addKeyframeChannel(channel);
0425         }
0426     }
0427 
0428     return channel;
0429 }
0430 
0431 bool KisBaseNode::isAnimated() const
0432 {
0433     return m_d->animated;
0434 }
0435 
0436 void KisBaseNode::enableAnimation()
0437 {
0438     m_d->animated = true;
0439     baseNodeChangedCallback();
0440 }
0441 
0442 void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel)
0443 {
0444     m_d->keyframeChannels.insert(channel->id(), channel);
0445     emit keyframeChannelAdded(channel);
0446 }
0447 
0448 KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id)
0449 {
0450     if (id == KisKeyframeChannel::Opacity.id()) {
0451         Q_ASSERT(!m_d->opacityProperty.hasChannel());
0452 
0453         KisPaintDeviceSP device = original();
0454         KisNode* node = dynamic_cast<KisNode*>(this);
0455 
0456         if (device && node) {
0457             m_d->opacityProperty.makeAnimated(node);
0458             return m_d->opacityProperty.channel();
0459         }
0460     }
0461 
0462     return 0;
0463 }
0464 
0465 bool KisBaseNode::supportsKeyframeChannel(const QString &id)
0466 {
0467     if (id == KisKeyframeChannel::Opacity.id() && original()) {
0468         return true;
0469     }
0470 
0471     return false;
0472 }
0473 
0474 QDebug operator<<(QDebug dbg, const KisBaseNode::Property &prop)
0475 {
0476     dbg.nospace() << "Property(" << prop.id << ", " << prop.state;
0477 
0478     if (prop.isInStasis) {
0479         dbg.nospace() << ", in-stasis";
0480     }
0481 
0482     dbg.nospace() << ")";
0483 
0484     return dbg.space();
0485 }