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 }