Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_scalar_keyframe_channel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Jouni Pentikäinen <joupent@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8#include "kis_node.h"
9#include "kundo2command.h"
11#include "kis_time_span.h"
12#include <kis_global.h>
13#include <kis_dom_utils.h>
14
15
17 : KisKeyframe(),
18 m_value(value),
19 m_interpolationMode(Constant),
20 m_tangentsMode(Smooth),
21 m_channelLimits(limits)
22{
23}
24
26 QPointF leftTangent, QPointF rightTangent,
28 : m_value(value),
29 m_interpolationMode(interpMode),
30 m_tangentsMode(tangentMode),
31 m_leftTangent(leftTangent),
32 m_rightTangent(rightTangent),
33 m_channelLimits(limits)
34{
35}
36
38{
39 if (newChannel) {
40 KisScalarKeyframeChannel *scalarChannel = dynamic_cast<KisScalarKeyframeChannel*>(newChannel);
41 KIS_ASSERT(scalarChannel);
42 // When transitioning between channels, set limits to those of the new channel.
43 KisScalarKeyframeSP scalarKey = toQShared(new KisScalarKeyframe(m_value, scalarChannel->limits()));
44 scalarKey->setInterpolationMode(m_interpolationMode);
45 scalarKey->setTangentsMode(m_tangentsMode);
46 scalarKey->setInterpolationTangents(leftTangent(), rightTangent());
47 return scalarKey;
48 } else {
51 m_channelLimits.toStrongRef()));
52 }
53}
54
56{
57 return m_value;
58}
59
61{
62 if (parentUndoCmd) {
63 KUndo2Command* cmd = new KisScalarKeyframeUpdateCommand(this, value, parentUndoCmd);
64 cmd->redo();
65 } else {
66 m_value = value;
67
69 if (limits) {
70 m_value = limits->clamp(m_value);
71 }
72
73 Q_EMIT sigChanged(this);
74 }
75}
76
78{
79 if (parentUndoCmd) {
80 KUndo2Command* cmd = new KisScalarKeyframeUpdateCommand(this, mode, parentUndoCmd);
81 cmd->redo();
82 } else {
84 Q_EMIT sigChanged(this);
85 }
86}
87
92
94{
95 if (parentUndoCmd) {
96 KUndo2Command* cmd = new KisScalarKeyframeUpdateCommand(this, mode, parentUndoCmd);
97 cmd->redo();
98 } else {
99 m_tangentsMode = mode;
100 Q_EMIT sigChanged(this);
101 }
102}
103
108
109void KisScalarKeyframe::setInterpolationTangents(QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentUndoCmd)
110{
111 if (parentUndoCmd) {
113 cmd->redo();
114 } else {
117 Q_EMIT sigChanged(this);
118 }
119}
120
122{
123 return m_leftTangent;
124}
125
127{
128 return m_rightTangent;
129}
130
135
136
137// ========================================================================================================
138// ==================================== KisScalarKeyframeChannel ==========================================
139// ========================================================================================================
140
141
166
172
174 : KisKeyframeChannel(rhs)
175{
176 m_d.reset(new Private(*rhs.m_d));
177
178 Q_FOREACH (int time, rhs.constKeys().keys()) {
179 KisKeyframeChannel::copyKeyframe(&rhs, time, this, time);
180 }
181}
182
186
188 KisScalarKeyframeSP scalarKey = keyframeAt<KisScalarKeyframe>(time);
189 if (!scalarKey) {
190 addKeyframe(time, parentUndoCmd);
191 scalarKey = keyframeAt<KisScalarKeyframe>(time);
192 }
193
194 if (scalarKey) {
195 scalarKey->setValue(value, parentUndoCmd);
196 }
197}
198
203
204void KisScalarKeyframeChannel::setLimits(qreal low, qreal high)
205{
206 m_d->limits = toQShared(new ScalarKeyframeLimits(low, high));
207 QSet<int> keyEntries = allKeyframeTimes();
208 foreach (const int &time, keyEntries) {
209 KisScalarKeyframeSP scalarKey = keyframeAt<KisScalarKeyframe>(time);
210 scalarKey->setLimits(m_d->limits);
211 scalarKey->setValue(scalarKey->value());
212 }
213}
214
216{
217 if (m_d->limits) {
218 m_d->limits.reset();
219 }
220}
221
223{
224 const int activeKeyTime = activeKeyframeTime(time);
225 KisScalarKeyframeSP activeKey = keyframeAt<KisScalarKeyframe>(activeKeyTime);
226 KisScalarKeyframeSP nextKeyframe = keyframeAt<KisScalarKeyframe>(nextKeyframeTime(time));
227 qreal result = qQNaN();
228
229 if (activeKey) {
230 if (!nextKeyframe) {
231 result = activeKey->value();
232 } else {
233 switch (activeKey->interpolationMode()) {
235 result = activeKey->value();
236 break;
237 }
239 const int nextKeyTime = nextKeyframeTime(time);
240 const qreal activeKeyValue = activeKey->value();
241 const qreal nextKeyValue = keyframeAt<KisScalarKeyframe>(nextKeyTime)->value();
242 const int interpolationLength = (nextKeyTime - activeKeyTime);
243 if (interpolationLength > 0) {
244 result = activeKeyValue + (nextKeyValue - activeKeyValue) * (time - activeKeyTime) / (interpolationLength);
245 } else {
246 result = activeKeyValue;
247 }
248 break;
249 }
251 const int nextKeyTime = nextKeyframeTime(time);
252 const KisScalarKeyframeSP nextKey = keyframeAt<KisScalarKeyframe>(nextKeyTime);
253 QPointF point0 = QPointF(activeKeyTime, activeKey->value());
254 QPointF point1 = QPointF(nextKeyTime, nextKey->value());
255
256 QPointF tangent0 = activeKey->rightTangent();
257 QPointF tangent1 = nextKey->leftTangent();
258
259 normalizeTangents(point0, tangent0, tangent1, point1);
260 qreal t = KisScalarKeyframeChannel::findCubicCurveParameter(point0.x(), tangent0.x(), tangent1.x(), point1.x(), time);
261 result = KisScalarKeyframeChannel::interpolate(point0, tangent0, tangent1, point1, t).y();
262 break;
263 }
264 default: {
266 break;
267 }
268 }
269 }
270 } else {
271 if (nextKeyframe) {
272 result = nextKeyframe->value();
273 } else {
274 return qQNaN();
275 }
276 }
277
278 // Output value must be also be clamped to account for interpolation.
279 if (m_d->limits) {
280 return m_d->limits->clamp(result);
281 } else {
282 return result;
283 }
284}
285
289
291{
292 m_d->defaultValue = value;
293}
294
299
300QPointF KisScalarKeyframeChannel::interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t)
301{
302 normalizeTangents(point1, rightTangent, leftTangent, point2);
303
304 qreal x = cubicBezier(point1.x(), rightTangent.x(), leftTangent.x(), point2.x(), t);
305 qreal y = cubicBezier(point1.y(), rightTangent.y(), leftTangent.y(), point2.y(), t);
306
307 return QPointF(x,y);
308}
309
311{
312 KisScalarKeyframeSP scalarKeyframe = keyframe.dynamicCast<KisScalarKeyframe>();
313 if (scalarKeyframe) {
314 scalarKeyframe->valueChangedChannelConnection =
315 QObject::connect(scalarKeyframe.data(),
317 [this, time](const KisScalarKeyframe* key){
318 Q_UNUSED(key);
319 Q_EMIT sigKeyframeChanged(this, time);
320 });
321 }
322
323 KisKeyframeChannel::insertKeyframe(time, keyframe, parentUndoCmd);
324}
325
327{
328 KisScalarKeyframeSP keyframe = keyframeAt<KisScalarKeyframe>(time);
329 if (keyframe) {
330 disconnect(keyframe->valueChangedChannelConnection);
331 }
332
333 KisKeyframeChannel::removeKeyframe(time, parentUndoCmd);
334}
335
337{
339
340 const int activeKeyTime = activeKeyframeTime(time);
341 const int previousKeyTime = previousKeyframeTime(activeKeyTime);
342 const KisScalarKeyframeSP prevScalarKey = keyframeAt<KisScalarKeyframe>(previousKeyTime);
343
344 if(prevScalarKey) {
345 // In the case that a previous keyframe is present with a non-constant interpolation mode,
346 // the affected frames must include all the frames just after the previous keyframe.
347 if (prevScalarKey->interpolationMode() == KisScalarKeyframe::Constant) {
348 return normalSpan;
349 } else {
350 return normalSpan | KisTimeSpan::fromTimeToTime(previousKeyTime + 1, activeKeyTime);
351 }
352 } else {
353 const KisScalarKeyframeSP firstScalarKey = keyframeAt<KisScalarKeyframe>(firstKeyframeTime());
354 if (!firstScalarKey) {
355 return KisTimeSpan::infinite(0);
356 }
357 return normalSpan | KisTimeSpan::fromTimeToTime(0, activeKeyTime);
358 }
359}
360
362{
363 //Failsafe == no keys should mean all frames are identical!
364 if (allKeyframeTimes().count() == 0) {
365 return KisTimeSpan::infinite(0);
366 }
367
368 KisScalarKeyframeSP activeScalarKey = activeKeyframeAt<KisScalarKeyframe>(time);
369 if ( activeScalarKey &&
370 activeScalarKey->interpolationMode() != KisScalarKeyframe::Constant &&
371 activeScalarKey != keyframeAt(lastKeyframeTime()) ) {
372 //TODO: Two keyframes should be considered identical if linear with same value..
373 //TODO: Also, if bezier with same value AND tangents lie between points.
374 // (tangenty == keyframey)
375 return KisTimeSpan::fromTimeToTime(time, time);
376 }
377
378 const int nextKeyTime = nextKeyframeTime(time);
379
380 //Before the first frame => there's no active frame but a valid next frame.
381 if (!activeScalarKey && keyframeAt(nextKeyTime)) {
382 return KisTimeSpan::fromTimeToTime(0, nextKeyTime);
383 }
384
385 //No next frame, all frames after are identical.
386 if (!keyframeAt(nextKeyTime)) {
388 }
389
390 return KisTimeSpan::fromTimeToTime(activeKeyframeTime(time), nextKeyTime - 1);
391}
392
393qreal KisScalarKeyframeChannel::findCubicCurveParameter(int time0, qreal delta0, qreal delta1, int time1, int time)
394{
395 if (time == time0) return 0.0;
396 if (time == time1) return 1.0;
397
398 qreal min_t = 0.0;
399 qreal max_t = 1.0;
400
401 while (true) {
402 qreal t = (max_t + min_t) / 2;
403 qreal time_t = cubicBezier(time0, delta0, delta1, time1, t);
404
405 if (time_t < time - 0.05) {
406 min_t = t;
407 } else if (time_t > time + 0.05) {
408 max_t = t;
409 } else {
410 // Close enough
411 return t;
412 }
413 }
414}
415
416qreal KisScalarKeyframeChannel::cubicBezier(qreal p0, qreal delta1, qreal delta2, qreal p3, qreal t) {
417 qreal p1 = p0 + delta1;
418 qreal p2 = p3 + delta2;
419
420 qreal c = 1-t;
421 return c*c*c * p0 + 3*c*c*t * p1 + 3*c*t*t * p2 + t*t*t * p3;
422}
423
424void KisScalarKeyframeChannel::normalizeTangents(const QPointF point1, QPointF &rightTangent, QPointF &leftTangent, const QPointF point2)
425{
426 // To ensure that the curve is monotonic wrt time,
427 // check that control points lie between the endpoints.
428 // If not, force them into range by scaling down the tangents
429
430 float interval = point2.x() - point1.x();
431 if (rightTangent.x() < 0) rightTangent *= 0;
432 if (leftTangent.x() > 0) leftTangent *= 0;
433
434 if (rightTangent.x() > interval) {
435 rightTangent *= interval / rightTangent.x();
436 }
437 if (leftTangent.x() < -interval) {
438 leftTangent *= interval / -leftTangent.x();
439 }
440}
441
443{
444 KisScalarKeyframe *keyframe = new KisScalarKeyframe(m_d->defaultValue, m_d->limits);
445 keyframe->setInterpolationMode(m_d->defaultInterpolationMode);
446 return toQShared(keyframe);
447}
448
450{
451 Q_UNUSED(time);
452
453 if (node()) {
454 return node()->exactBounds();
455 } else {
456 return QRect();
457 }
458}
459
460void KisScalarKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename)
461{
462 Q_UNUSED(layerFilename);
463 KisScalarKeyframeSP scalarKey = keyframe.dynamicCast<KisScalarKeyframe>();
465 const qreal value = scalarKey->value();
466 keyframeElement.setAttribute("value", KisDomUtils::toString(value));
467
468 QString interpolationMode;
469 if (scalarKey->interpolationMode() == KisScalarKeyframe::Constant) interpolationMode = "constant";
470 if (scalarKey->interpolationMode() == KisScalarKeyframe::Linear) interpolationMode = "linear";
471 if (scalarKey->interpolationMode() == KisScalarKeyframe::Bezier) interpolationMode = "bezier";
472
473 QString tangentsMode;
474 if (scalarKey->tangentsMode() == KisScalarKeyframe::Smooth) tangentsMode = "smooth";
475 if (scalarKey->tangentsMode() == KisScalarKeyframe::Sharp) tangentsMode = "sharp";
476
477 keyframeElement.setAttribute("interpolation", interpolationMode);
478 keyframeElement.setAttribute("tangents", tangentsMode);
479 KisDomUtils::saveValue(&keyframeElement, "leftTangent", scalarKey->leftTangent());
480 KisDomUtils::saveValue(&keyframeElement, "rightTangent", scalarKey->rightTangent());
481}
482
483QPair<int, KisKeyframeSP> KisScalarKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode)
484{
485 int time = keyframeNode.toElement().attribute("time").toInt();
487
488 qreal value = KisDomUtils::toDouble(keyframeNode.toElement().attribute("value"));
489
490 KisScalarKeyframeSP keyframe = createKeyframe().dynamicCast<KisScalarKeyframe>();
491 keyframe->setValue(value);
492
493 KisScalarKeyframeSP scalarKey = keyframe.dynamicCast<KisScalarKeyframe>();
494
495 QString interpolationMode = keyframeNode.toElement().attribute("interpolation");
496 if (interpolationMode == "constant") {
497 scalarKey->setInterpolationMode(KisScalarKeyframe::Constant);
498 } else if (interpolationMode == "linear") {
499 scalarKey->setInterpolationMode(KisScalarKeyframe::Linear);
500 } else if (interpolationMode == "bezier") {
501 scalarKey->setInterpolationMode(KisScalarKeyframe::Bezier);
502 }
503
504 QString tangentsMode = keyframeNode.toElement().attribute("tangents");
505 if (tangentsMode == "smooth") {
506 scalarKey->setTangentsMode(KisScalarKeyframe::Smooth);
507 } else if (tangentsMode == "sharp") {
508 scalarKey->setTangentsMode(KisScalarKeyframe::Sharp);
509 }
510
511 QPointF leftTangent;
512 QPointF rightTangent;
513 KisDomUtils::loadValue(keyframeNode, "leftTangent", &leftTangent);
514 KisDomUtils::loadValue(keyframeNode, "rightTangent", &rightTangent);
515 scalarKey->setInterpolationTangents(leftTangent, rightTangent);
516
517 return QPair<int, KisKeyframeSP>(time, keyframe);
518}
float value(const T *src, size_t ch)
QPointF p0
QPointF p2
QPointF p3
QPointF p1
virtual void redo()
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
QSet< int > allKeyframeTimes() const
Get a set of all integer times that map to a keyframe.
int previousKeyframeTime(const int time) const
virtual void insertKeyframe(int time, KisKeyframeSP keyframe, KUndo2Command *parentUndoCmd=nullptr)
Insert an existing keyframe into the channel at the specified time.
virtual void removeKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Remove a keyframe from the channel at the specified time.
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
static void copyKeyframe(const KisKeyframeChannel *sourceChannel, int sourceTime, KisKeyframeChannel *targetChannel, int targetTime, KUndo2Command *parentUndoCmd=nullptr)
Copy a keyframe across channel(s) at the specified times.
const TimeKeyframeMap & constKeys() const
void addKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr)
Add a new keyframe to the channel at the specified time.
virtual KisTimeSpan affectedFrames(int time) const
Get the set of frames affected by any changes to the value or content of the active keyframe at the g...
Q_DECL_DEPRECATED void workaroundBrokenFrameTimeBug(int *time)
Between Krita 4.1 and 4.4 Krita had a bug which resulted in creating frames with negative time stamp....
int nextKeyframeTime(const int time) const
Krita's base keyframe class. Mainly contained by KisKeyframeChannels. A core part of Krita's animatio...
The KisScalarKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisSca...
KisScalarKeyframeChannel(const KoID &id, KisDefaultBoundsBaseSP bounds)
virtual KisTimeSpan affectedFrames(int time) const override
Get the set of frames affected by any changes to the value or content of the active keyframe at the g...
virtual QRect affectedRect(int time) const override
void setDefaultInterpolationMode(KisScalarKeyframe::InterpolationMode mode)
qreal valueAt(int time) const
Quickly get the interpolated value at the given time.
static void normalizeTangents(const QPointF point1, QPointF &rightTangent, QPointF &leftTangent, const QPointF point2)
static qreal cubicBezier(qreal p0, qreal delta1, qreal delta2, qreal p3, qreal t)
virtual KisTimeSpan identicalFrames(int time) const override
Get a span of times for which the channel gives identical results compared to frame at a given time....
static qreal findCubicCurveParameter(int time0, qreal delta0, qreal delta1, int time1, int time)
virtual void saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) override
virtual KisKeyframeSP createKeyframe() override
Virtual keyframe creation function. Derived classes implement this function based on the needs of the...
void addScalarKeyframe(int time, qreal value, KUndo2Command *parentUndoCmd=nullptr)
virtual QPair< int, KisKeyframeSP > loadKeyframe(const QDomElement &keyframeNode) override
virtual void removeKeyframe(int time, KUndo2Command *parentUndoCmd=nullptr) override
Remove a keyframe from the channel at the specified time.
static QPointF interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t)
QSharedPointer< ScalarKeyframeLimits > limits() const
void setLimits(qreal low, qreal high)
virtual void insertKeyframe(int time, KisKeyframeSP keyframe, KUndo2Command *parentUndoCmd=nullptr) override
Insert an existing keyframe into the channel at the specified time.
The KisScalarKeyframe class is a concrete subclass of KisKeyframe that wraps a scalar value and inter...
KisScalarKeyframe(qreal value, QSharedPointer< ScalarKeyframeLimits > limits)
InterpolationMode
Controls the type of interpolation between this KisScalarKeyframe and the next.
InterpolationMode interpolationMode() const
KisKeyframeSP duplicate(KisKeyframeChannel *newChannel=0) override
friend class KisScalarKeyframeUpdateCommand
void setValue(qreal val, KUndo2Command *parentUndoCmd=nullptr)
InterpolationMode m_interpolationMode
void sigChanged(const KisScalarKeyframe *scalarKey)
void setInterpolationTangents(QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentUndoCmd=nullptr)
void setInterpolationMode(InterpolationMode mode, KUndo2Command *parentUndoCmd=nullptr)
TangentsMode
Controls the behavior of the left and right tangents on a given keyframe for different curve shapes.
void setTangentsMode(TangentsMode mode, KUndo2Command *parentUndoCmd=nullptr)
QWeakPointer< ScalarKeyframeLimits > m_channelLimits
TangentsMode tangentsMode() const
void setLimits(QSharedPointer< ScalarKeyframeLimits > limits)
bool contains(int time) const
static KisTimeSpan infinite(int start)
static KisTimeSpan fromTimeToTime(int start, int end)
Definition KoID.h:30
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT_RECOVER_BREAK(cond)
Definition kis_assert.h:65
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define bounds(x, a, b)
QSharedPointer< T > toQShared(T *ptr)
void saveValue(QDomElement *parent, const QString &tag, const QSize &size)
double toDouble(const QString &str, bool *ok=nullptr)
bool loadValue(const QDomElement &e, float *v)
QString toString(const QString &value)
virtual QRect exactBounds() const
KisScalarKeyframe::InterpolationMode defaultInterpolationMode
QSharedPointer< ScalarKeyframeLimits > limits
This structure represents an optional limited range of values that be handled by KisScalarKeyframeCha...