diff --git a/libs/image/kis_base_node.cpp b/libs/image/kis_base_node.cpp index e1cd4882b0..3b53c8cf42 100644 --- a/libs/image/kis_base_node.cpp +++ b/libs/image/kis_base_node.cpp @@ -1,432 +1,433 @@ /* * 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" #include "kis_scalar_keyframe_channel.h" struct Q_DECL_HIDDEN KisBaseNode::Private { QString compositeOp; KoProperties properties; bool systemLocked; KisBaseNode::Property hack_visible; //HACK QUuid id; bool collapsed; bool supportsLodMoves; QMap keyframeChannels; bool animated; bool useInTimeline; QScopedPointer opacityChannel; Private() : animated(false) , useInTimeline(false) { } }; 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 { if (m_d->opacityChannel) { - KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); + qreal value = m_d->opacityChannel->currentValue(); - if (!activeKeyframe.isNull()) { - return m_d->opacityChannel->scalarValue(activeKeyframe); + if (!qIsNaN(value)) { + return value; } } return nodeProperties().intProperty("opacity", OPACITY_OPAQUE_U8); } void KisBaseNode::setOpacity(quint8 val) { if (m_d->opacityChannel) { KisKeyframeSP activeKeyframe = m_d->opacityChannel->currentlyActiveKeyframe(); if (activeKeyframe) { m_d->opacityChannel->setScalarValue(activeKeyframe, 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::setSupportsLodMoves(bool value) { m_d->supportsLodMoves = value; } QList KisBaseNode::keyframeChannels() const { return m_d->keyframeChannels.values(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id) const { QMap::const_iterator i = m_d->keyframeChannels.constFind(id); if (i == m_d->keyframeChannels.constEnd()) { return 0; } return i.value(); } KisKeyframeChannel * KisBaseNode::getKeyframeChannel(const QString &id, bool create) { KisKeyframeChannel *channel = getKeyframeChannel(id); if (!channel && create) { channel = requestKeyframeChannel(id); if (channel) { addKeyframeChannel(channel); } } return channel; } bool KisBaseNode::isAnimated() const { return m_d->animated; } void KisBaseNode::enableAnimation() { m_d->animated = true; baseNodeChangedCallback(); } bool KisBaseNode::useInTimeline() const { return m_d->useInTimeline; } void KisBaseNode::setUseInTimeline(bool value) { if (value == m_d->useInTimeline) return; m_d->useInTimeline = value; baseNodeChangedCallback(); } void KisBaseNode::addKeyframeChannel(KisKeyframeChannel *channel) { m_d->keyframeChannels.insert(channel->id(), channel); } KisKeyframeChannel *KisBaseNode::requestKeyframeChannel(const QString &id) { if (id == KisKeyframeChannel::Opacity.id()) { Q_ASSERT(m_d->opacityChannel.isNull()); KisPaintDeviceSP device = original(); if (device) { KisScalarKeyframeChannel * channel = new KisScalarKeyframeChannel( KisKeyframeChannel::Opacity, 0, 255, - device->defaultBounds() + device->defaultBounds(), + KisKeyframe::Linear ); m_d->opacityChannel.reset(channel); return channel; } } return 0; } diff --git a/libs/image/kis_scalar_keyframe_channel.cpp b/libs/image/kis_scalar_keyframe_channel.cpp index 840424810e..b59aff495a 100644 --- a/libs/image/kis_scalar_keyframe_channel.cpp +++ b/libs/image/kis_scalar_keyframe_channel.cpp @@ -1,294 +1,303 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_scalar_keyframe_channel.h" #include "kis_node.h" #include "kundo2command.h" #include "kis_time_range.h" #include #include struct KisScalarKeyframeChannel::Private { public: - Private(qreal min, qreal max) - : minValue(min), maxValue(max), firstFreeIndex(0) + Private(qreal min, qreal max, KisKeyframe::InterpolationMode defaultInterpolation) + : minValue(min), maxValue(max), firstFreeIndex(0), defaultInterpolation(defaultInterpolation) {} qreal minValue; qreal maxValue; QMap values; int firstFreeIndex; + KisKeyframe::InterpolationMode defaultInterpolation; + struct InsertValueCommand; struct SetValueCommand; }; -KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KoID &id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds) +KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KoID &id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation) : KisKeyframeChannel(id, defaultBounds), - m_d(new Private(minValue, maxValue)) + m_d(new Private(minValue, maxValue, defaultInterpolation)) { } KisScalarKeyframeChannel::~KisScalarKeyframeChannel() {} bool KisScalarKeyframeChannel::hasScalarValue() const { return true; } qreal KisScalarKeyframeChannel::minScalarValue() const { return m_d->minValue; } qreal KisScalarKeyframeChannel::maxScalarValue() const { return m_d->maxValue; } qreal KisScalarKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { return m_d->values[keyframe->value()]; } struct KisScalarKeyframeChannel::Private::SetValueCommand : public KUndo2Command { SetValueCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, qreal oldValue, qreal newValue, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldValue(oldValue), m_newValue(newValue) { } void redo() { m_channel->setScalarValueImpl(m_keyframe, m_newValue); } void undo() { m_channel->setScalarValueImpl(m_keyframe, m_oldValue); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; qreal m_oldValue; qreal m_newValue; }; void KisScalarKeyframeChannel::setScalarValueImpl(KisKeyframeSP keyframe, qreal value) { int index = keyframe->value(); m_d->values[index] = value; QRect rect = affectedRect(keyframe); KisTimeRange range = affectedFrames(keyframe->time()); requestUpdate(range, rect); } void KisScalarKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } int index = keyframe->value(); KUndo2Command *cmd = new Private::SetValueCommand(this, keyframe, m_d->values[index], value, parentCommand); cmd->redo(); } qreal cubicBezier(qreal p0, qreal delta1, qreal delta2, qreal p3, qreal t) { qreal p1 = p0 + delta1; qreal p2 = p3 + delta2; qreal c = 1-t; return c*c*c * p0 + 3*c*c*t * p1 + 3*c*t*t * p2 + t*t*t * p3; } qreal findCubicCurveParameter(int time0, qreal delta0, qreal delta1, int time1, int time) { if (time == time0) return 0.0; if (time == time1) return 1.0; qreal min_t = 0.0; qreal max_t = 1.0; while (true) { qreal t = (max_t + min_t) / 2; qreal time_t = cubicBezier(time0, delta0, delta1, time1, t); if (time_t < time - 0.05) { min_t = t; } else if (time_t > time + 0.05) { max_t = t; } else { // Close enough return t; } } } qreal KisScalarKeyframeChannel::interpolatedValue(int time) const { KisKeyframeSP activeKey = activeKeyframeAt(time); if (activeKey.isNull()) return qQNaN(); if (activeKey->interpolationMode() == KisKeyframe::Constant) return scalarValue(activeKey); KisKeyframeSP nextKey = nextKeyframe(activeKey); if (nextKey.isNull()) return scalarValue(activeKey); int time0 = activeKey->time(); int time1 = nextKey->time(); int interval = time1 - time0; qreal value0 = scalarValue(activeKey); qreal value1 = scalarValue(nextKey); QPointF tangent0 = activeKey->rightTangent(); QPointF tangent1 = nextKey->leftTangent(); // To ensure that the curve is monotonic wrt time, // check that control points lie between the endpoints. // If not, force them into range by scaling down the tangents if (tangent0.x() < 0) tangent0 = QPointF(0,0); if (tangent1.x() > 0) tangent1 = QPointF(0,0); if (tangent0.x() > interval) { tangent0 = tangent0 * (interval / tangent0.x()); } if (tangent1.x() < -interval) { tangent1 = tangent1 * (interval / -tangent1.x()); } qreal t = findCubicCurveParameter(time0, tangent0.x(), tangent1.x(), time1, time); qreal res = cubicBezier(value0, tangent0.y(), tangent1.y(), value1, t); if (res > m_d->maxValue) return m_d->maxValue; if (res < m_d->minValue) return m_d->minValue; return res; } +qreal KisScalarKeyframeChannel::currentValue() const +{ + return interpolatedValue(currentTime()); +} + struct KisScalarKeyframeChannel::Private::InsertValueCommand : public KUndo2Command { InsertValueCommand(KisScalarKeyframeChannel::Private *d, int index, qreal value, bool insert, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_d(d), m_index(index), m_value(value), m_insert(insert) { } void redo() { doSwap(m_insert); } void undo() { doSwap(!m_insert); } private: void doSwap(bool insert) { if (insert) { m_d->values[m_index] = m_value; } else { m_d->values.remove(m_index); } } private: KisScalarKeyframeChannel::Private *m_d; int m_index; qreal m_value; bool m_insert; }; KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { qreal value = (copySrc.isNull() ? 0 : scalarValue(copySrc)); int index = m_d->firstFreeIndex++; KUndo2Command *cmd = new Private::InsertValueCommand(m_d.data(), index, value, true, parentCommand); cmd->redo(); - return toQShared(new KisKeyframe(this, time, index)); + KisKeyframeSP keyframe = toQShared(new KisKeyframe(this, time, index)); + keyframe->setInterpolationMode(m_d->defaultInterpolation); + return keyframe; } void KisScalarKeyframeChannel::destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) { int index = key->value(); KIS_ASSERT_RECOVER_RETURN(m_d->values.contains(index)); KUndo2Command *cmd = new Private::InsertValueCommand(m_d.data(), index, m_d->values[index], false, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) { KisScalarKeyframeChannel *srcScalarChannel = dynamic_cast(srcChannel); KIS_ASSERT_RECOVER_RETURN(srcScalarChannel); KisKeyframeSP srcFrame = srcScalarChannel->keyframeAt(srcTime); KIS_ASSERT_RECOVER_RETURN(srcFrame); const qreal newValue = scalarValue(srcFrame); const int dstId = dstFrame->value(); KIS_ASSERT_RECOVER_RETURN(m_d->values.contains(dstId)); m_d->values[dstId] = newValue; } QRect KisScalarKeyframeChannel::affectedRect(KisKeyframeSP key) { Q_UNUSED(key); if (node()) { return node()->extent(); } else { return QRect(); } } void KisScalarKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) { Q_UNUSED(layerFilename); keyframeElement.setAttribute("value", KisDomUtils::toString(m_d->values[keyframe->value()])); } KisKeyframeSP KisScalarKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode) { int time = keyframeNode.toElement().attribute("time").toUInt(); qreal value = KisDomUtils::toDouble(keyframeNode.toElement().attribute("value")); KUndo2Command tempParentCommand; KisKeyframeSP keyframe = createKeyframe(time, KisKeyframeSP(), &tempParentCommand); setScalarValue(keyframe, value); return keyframe; } diff --git a/libs/image/kis_scalar_keyframe_channel.h b/libs/image/kis_scalar_keyframe_channel.h index cb90977a8b..834c86d3ac 100644 --- a/libs/image/kis_scalar_keyframe_channel.h +++ b/libs/image/kis_scalar_keyframe_channel.h @@ -1,56 +1,57 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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_SCALAR_KEYFRAME_CHANNEL_H #define _KIS_SCALAR_KEYFRAME_CHANNEL_H #include "kis_keyframe_channel.h" class KRITAIMAGE_EXPORT KisScalarKeyframeChannel : public KisKeyframeChannel { Q_OBJECT public: - KisScalarKeyframeChannel(const KoID& id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds); + KisScalarKeyframeChannel(const KoID& id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation=KisKeyframe::Constant); ~KisScalarKeyframeChannel(); bool hasScalarValue() const; qreal minScalarValue() const; qreal maxScalarValue() const; qreal scalarValue(const KisKeyframeSP keyframe) const; void setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand = 0); qreal interpolatedValue(int time) const; + qreal currentValue() const; protected: KisKeyframeSP createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand); void destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand); void uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame); QRect affectedRect(KisKeyframeSP key); void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename); KisKeyframeSP loadKeyframe(const QDomElement &keyframeNode); private: void setScalarValueImpl(KisKeyframeSP keyframe, qreal value); struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/image/tests/kis_base_node_test.cpp b/libs/image/tests/kis_base_node_test.cpp index 26e106fa75..1790be5262 100644 --- a/libs/image/tests/kis_base_node_test.cpp +++ b/libs/image/tests/kis_base_node_test.cpp @@ -1,176 +1,192 @@ /* * 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_test.h" #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_base_node.h" #include "kis_paint_device.h" #include "testutil.h" #include "kis_scalar_keyframe_channel.h" #include "KoColor.h" #include "kis_image_animation_interface.h" #include class TestNode : public KisBaseNode { using KisBaseNode::accept; bool accept(KisNodeVisitor &) { return false; } KisPaintDeviceSP paintDevice() const { return 0; } const KoColorSpace * colorSpace() const { return 0; } virtual const KoCompositeOp * compositeOp() const { return 0; } }; void KisBaseNodeTest::testCreation() { KisBaseNodeSP node = new TestNode(); QVERIFY(node->name().isEmpty()); QVERIFY(node->name() == node->objectName()); QVERIFY(node->icon().isNull()); QVERIFY(node->visible() == true); QVERIFY(node->userLocked() == false); QVERIFY(node->x() == 0); QVERIFY(node->y() == 0); } void KisBaseNodeTest::testContract() { KisBaseNodeSP node = new TestNode(); node->setName("bla"); QVERIFY(node->name() == "bla"); QVERIFY(node->objectName() == "bla"); node->setObjectName("zxc"); QVERIFY(node->name() == "zxc"); QVERIFY(node->objectName() == "zxc"); node->setVisible(!node->visible()); QVERIFY(node->visible() == false); node->setUserLocked(!node->userLocked()); QVERIFY(node->userLocked() == true); KisBaseNode::PropertyList list = node->sectionModelProperties(); QVERIFY(list.count() == 2); QVERIFY(list.at(0).state == node->visible()); QVERIFY(list.at(1).state == node->userLocked()); QImage image = node->createThumbnail(10, 10); QCOMPARE(image.size(), QSize(10, 10)); QVERIFY(image.pixel(5, 5) == QColor(0, 0, 0, 0).rgba()); } void KisBaseNodeTest::testProperties() { KisBaseNodeSP node = new TestNode(); { KoProperties props; props.setProperty("bladiebla", false); QVERIFY(node->check(props)); props.setProperty("visible", true); props.setProperty("locked", false); QVERIFY(node->check(props)); props.setProperty("locked", true); QVERIFY(!node->check(props)); node->nodeProperties().setProperty("locked", false); QVERIFY(node->userLocked() == false); } { KoProperties props; props.setProperty("blablabla", 10); node->mergeNodeProperties(props); QVERIFY(node->nodeProperties().intProperty("blablabla") == 10); QVERIFY(node->check(props)); props.setProperty("blablabla", 12); QVERIFY(!node->check(props)); } } void KisBaseNodeTest::testOpacityKeyframing() { TestUtil::MaskParent p; KisPaintLayerSP layer2 = new KisPaintLayer(p.image, "paint2", OPACITY_OPAQUE_U8); p.image->addNode(layer2); KisPaintDeviceSP dev1 = p.layer->paintDevice(); dev1->fill(QRect(0,0,32,32), KoColor(Qt::red, dev1->colorSpace())); KisPaintDeviceSP dev2 = layer2->paintDevice(); dev2->fill(QRect(0,0,32,32), KoColor(Qt::green, dev2->colorSpace())); layer2->setOpacity(192); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Opacity.id(), true); KisScalarKeyframeChannel *opacityChannel = dynamic_cast(channel); QVERIFY(opacityChannel); - KisKeyframeSP key = opacityChannel->addKeyframe(7); - opacityChannel->setScalarValue(key, 128); + KisKeyframeSP key1 = opacityChannel->addKeyframe(7); + opacityChannel->setScalarValue(key1, 128); - key = opacityChannel->addKeyframe(20); - opacityChannel->setScalarValue(key, 64); + KisKeyframeSP key2 = opacityChannel->addKeyframe(20); + opacityChannel->setScalarValue(key2, 64); p.image->refreshGraph(); + // No interpolation + + key1->setInterpolationMode(KisKeyframe::Constant); + QColor sample; p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(63, 192, 0, 255)); p.image->animationInterface()->requestTimeSwitchNonGUI(10); p.image->waitForDone(); p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(127, 128, 0, 255)); p.image->animationInterface()->requestTimeSwitchNonGUI(30); layer2->setOpacity(32); - QCOMPARE(opacityChannel->scalarValue(key), 32.0); + QCOMPARE(opacityChannel->scalarValue(key2), 32.0); p.image->waitForDone(); p.image->projection()->pixel(16, 16, &sample); QCOMPARE(sample, QColor(223, 32, 0, 255)); + // With interpolation + + key1->setInterpolationMode(KisKeyframe::Linear); + key1->setInterpolationTangents(QPointF(), QPointF(0,0)); + key2->setInterpolationTangents(QPointF(0,0), QPointF()); + + p.image->animationInterface()->requestTimeSwitchNonGUI(10); + p.image->waitForDone(); + p.image->projection()->pixel(16, 16, &sample); + + QCOMPARE(sample, QColor(150, 105, 0, 255)); + } QTEST_MAIN(KisBaseNodeTest)