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 }