From 50a066eeaf3aada87b30178b60696b2a49a9d6d0 Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Fri, 15 Jul 2022 23:49:06 +0200
Subject: [PATCH 01/11] Added basic text animation feature
---
CMakeLists.txt | 18 +-
app/node/factory.cpp | 6 +
app/node/factory.h | 2 +
app/node/generator/CMakeLists.txt | 1 +
app/node/generator/animation/CMakeLists.txt | 31 +++
app/node/generator/animation/textanimation.h | 97 ++++++++
.../animation/textanimationengine.cpp | 212 +++++++++++++++++
.../generator/animation/textanimationengine.h | 75 ++++++
.../generator/animation/textanimationnode.cpp | 151 ++++++++++++
.../generator/animation/textanimationnode.h | 98 ++++++++
.../animation/textanimationrendernode.cpp | 221 ++++++++++++++++++
.../animation/textanimationrendernode.h | 72 ++++++
.../animation/textanimationxmlformatter.cpp | 36 +++
.../animation/textanimationxmlformatter.h | 31 +++
.../animation/textanimationxmlparser.cpp | 45 ++++
.../animation/textanimationxmlparser.h | 24 ++
app/node/generator/text/textv3.cpp | 18 +-
app/node/generator/text/textv3.h | 1 +
18 files changed, 1126 insertions(+), 13 deletions(-)
create mode 100644 app/node/generator/animation/CMakeLists.txt
create mode 100644 app/node/generator/animation/textanimation.h
create mode 100644 app/node/generator/animation/textanimationengine.cpp
create mode 100644 app/node/generator/animation/textanimationengine.h
create mode 100644 app/node/generator/animation/textanimationnode.cpp
create mode 100644 app/node/generator/animation/textanimationnode.h
create mode 100644 app/node/generator/animation/textanimationrendernode.cpp
create mode 100644 app/node/generator/animation/textanimationrendernode.h
create mode 100644 app/node/generator/animation/textanimationxmlformatter.cpp
create mode 100644 app/node/generator/animation/textanimationxmlformatter.h
create mode 100644 app/node/generator/animation/textanimationxmlparser.cpp
create mode 100644 app/node/generator/animation/textanimationxmlparser.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b46d11f6fd..4ca3d62fc0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -157,15 +157,15 @@ endif()
list(APPEND OLIVE_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
list(APPEND OLIVE_LIBRARIES ${PORTAUDIO_LIBRARIES})
-# Optional: Link OpenTimelineIO
-find_package(OpenTimelineIO)
-if (OpenTimelineIO_FOUND)
- list(APPEND OLIVE_DEFINITIONS USE_OTIO)
- list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
- list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
-else()
- message(" OpenTimelineIO interchange will be disabled.")
-endif()
+## # Optional: Link OpenTimelineIO
+## find_package(OpenTimelineIO)
+## if (OpenTimelineIO_FOUND)
+## list(APPEND OLIVE_DEFINITIONS USE_OTIO)
+## list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
+## list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
+## else()
+## message(" OpenTimelineIO interchange will be disabled.")
+## endif()
# Optional: Link Google Crashpad
find_package(GoogleCrashpad)
diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index dd82a0805a..19836a38a5 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -49,6 +49,8 @@
#include "generator/text/textv1.h"
#include "generator/text/textv2.h"
#include "generator/text/textv3.h"
+#include "generator/animation/textanimationnode.h"
+#include "generator/animation/textanimationrendernode.h"
#include "input/time/timeinput.h"
#include "input/value/valuenode.h"
#include "keying/chromakey/chromakey.h"
@@ -243,6 +245,10 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new TextGeneratorV2();
case kTextGeneratorV3:
return new TextGeneratorV3();
+ case kTextAnimation:
+ return new TextAnimationNode();
+ case kTextAnimationRender:
+ return new TextAnimationRenderNode();
case kCrossDissolveTransition:
return new CrossDissolveTransition();
case kDipToColorTransition:
diff --git a/app/node/factory.h b/app/node/factory.h
index d7ca955ff1..64eefd0cdc 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -51,6 +51,8 @@ class NodeFactory
kTextGeneratorV1,
kTextGeneratorV2,
kTextGeneratorV3,
+ kTextAnimation,
+ kTextAnimationRender,
kCrossDissolveTransition,
kDipToColorTransition,
kMosaicFilter,
diff --git a/app/node/generator/CMakeLists.txt b/app/node/generator/CMakeLists.txt
index 424c3a0d4e..f4301f3968 100644
--- a/app/node/generator/CMakeLists.txt
+++ b/app/node/generator/CMakeLists.txt
@@ -20,6 +20,7 @@ add_subdirectory(polygon)
add_subdirectory(shape)
add_subdirectory(solid)
add_subdirectory(text)
+add_subdirectory(animation)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
diff --git a/app/node/generator/animation/CMakeLists.txt b/app/node/generator/animation/CMakeLists.txt
new file mode 100644
index 0000000000..4c70346ac8
--- /dev/null
+++ b/app/node/generator/animation/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2022 Olive Team
+#
+# 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 3 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, see .
+
+set(OLIVE_SOURCES
+ ${OLIVE_SOURCES}
+ node/generator/animation/textanimation.h
+ node/generator/animation/textanimationengine.cpp
+ node/generator/animation/textanimationengine.h
+ node/generator/animation/textanimationxmlformatter.cpp
+ node/generator/animation/textanimationxmlformatter.h
+ node/generator/animation/textanimationxmlparser.cpp
+ node/generator/animation/textanimationxmlparser.h
+ node/generator/animation/textanimationnode.cpp
+ node/generator/animation/textanimationnode.h
+ node/generator/animation/textanimationrendernode.cpp
+ node/generator/animation/textanimationrendernode.h
+ PARENT_SCOPE
+)
diff --git a/app/node/generator/animation/textanimation.h b/app/node/generator/animation/textanimation.h
new file mode 100644
index 0000000000..7f0b9a5b18
--- /dev/null
+++ b/app/node/generator/animation/textanimation.h
@@ -0,0 +1,97 @@
+#ifndef ANIMATION_H
+#define ANIMATION_H
+
+#include
+
+namespace olive {
+
+namespace TextAnimation {
+
+/// The feature of the text that is being animated
+enum Feature{
+ // invalid value
+ None,
+ // vertical offset for each character
+ PositionVertical,
+ // horizontal offset for each character
+ PositionHorizontal,
+ // rotation of each character around its bottom left corner
+ Rotation,
+ // space between letters
+ SpacingFactor
+};
+
+const QMap< Feature, QString> FEATURE_TABLE = {{None, "NONE"},
+ {PositionVertical, "POSITION_VER"},
+ {PositionHorizontal, "POSITION_HOR"},
+ {Rotation, "ROTATION"},
+ {SpacingFactor, "SPACING"}
+ };
+const QMap< QString, Feature> FEATURE_TABLE_REV = {{"NONE", None},
+ {"POSITION_VER", PositionVertical},
+ {"POSITION_HOR", PositionHorizontal},
+ {"ROTATION", Rotation},
+ {"SPACING", SpacingFactor}
+ };
+
+/// shape of animation curve
+enum Curve {
+ Step,
+ Linear,
+ Ease_Sine,
+ Ease_Quad,
+ Ease_Back,
+ Ease_Elastic,
+ Ease_Bounce
+};
+
+const QMap< Curve, QString> CURVE_TABLE = {{Step, "STEP"},
+ {Linear, "LINEAR"},
+ {Ease_Sine, "EASE_SINE"},
+ {Ease_Quad, "EASE_QUAD"},
+ {Ease_Back, "EASE_BACK"},
+ {Ease_Elastic, "EASE_ELASTIC"},
+ {Ease_Bounce, "EASE_BOUNCE"}
+ };
+
+const QMap< QString, Curve> CURVE_TABLE_REV = { { "STEP", Step},
+ { "LINEAR", Linear },
+ { "EASE_SINE", Ease_Sine },
+ { "EASE_QUAD", Ease_Quad },
+ { "EASE_BACK", Ease_Back },
+ { "EASE_ELASTIC", Ease_Elastic },
+ { "EASE_BOUNCE", Ease_Bounce}
+ };
+
+/// full descriptor of an animation
+struct Descriptor {
+ TextAnimation::Feature feature;
+ // first character to be animated. Use 0 for the first character
+ int character_from;
+ // last character to be animated. Use -1 to indicate the end of the text
+ int character_to;
+ // range [0,1]. The lower this value, the more all characters start
+ // their animation together
+ double overlap_in;
+ // range [0,1]. The lower this value, the more all characters end
+ // their animation together
+ double overlap_out;
+ TextAnimation::Curve curve;
+ // coefficient that modifies the curve. May or may not have effect for each curve.
+ // Value 1 should always be a good default.
+ double c1;
+ // coefficient that modifies the curve. May or may not have effect for each curve.
+ // Value 1 should always be a good default.
+ double c2;
+ // value of the feature at the begin of the transition
+ double value;
+ // Evolution of the transition in range [0,1]. When 0, the animated value is equal to 'value';
+ // When 1, the animated value is equal to 0.
+ double alpha;
+};
+
+} // TextAnimation
+
+} // olive
+
+#endif // ANIMATION_H
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
new file mode 100644
index 0000000000..0694edc2bb
--- /dev/null
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -0,0 +1,212 @@
+#include "textanimationengine.h"
+
+#include
+#include
+#include
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+
+
+namespace olive {
+
+namespace {
+
+/// Template for all animation functions.
+/// Each function depends on a value \p x that is in range [0,1]. When x=0, each
+/// function returns 1 (i.e. animation not yet started); When x=1, each
+/// function returns 0 (i.e. animation complete).
+/// \param c1 and c2 are coefficient that may (or may not) affect the curve. In any
+/// case, 1 is always a good default value.
+typedef std::function animationFunction_t;
+
+double Step( double x, double c1, double /*c2*/) {
+ return (x < 0.5*c1) ? 1. : 0.;
+}
+
+double Linear( double x, double /*c1*/, double /*c2*/) {
+ return (1.0 - x);
+}
+
+double EaseOutSine( double x, double /*c1*/, double /*c2*/) {
+ return 1.0 - sin((x * M_PI) / 2.);
+}
+
+double EaseOutQuad( double x, double /*c1*/, double /*c2*/) {
+ return (1.0 - x) * (1.0 - x);
+}
+
+/* a transition with a single overshoot */
+double EaseOutBack( double x, double c1, double c2) {
+ return - 2.7*c2*pow(x-1., 3) - 1.7*c1*pow(x-1., 2);
+}
+
+/* a transition with dumped sine envelope */
+double EaseOutElastic( double x, double c1, double c2) {
+ const double c4 = (2.0 * M_PI) / 3.0;
+ double value;
+
+ if (x < 0.01) { // almost x==0
+ value = 0.;
+ }
+ else if ( x > 0.99) { // almost x==1
+ value = 1.;
+ }
+ else {
+ value = pow(2, -10 * c1 * x) * sin((x * 10 * c2 - 0.75) * c4) + 1;
+ }
+
+ return 1. - value;
+}
+
+/* a transition made up by 4 parabolic bounces */
+double EaseOutBounce( double x, double c1, double /*c2*/) {
+
+ double value;
+
+ if (x <= 0.25) {
+ value = -16.*x*x + 1.;
+ } else if (x <= 0.5) {
+ value = c1*( -16.*(x-3./8.)*(x-3./8.) + 1./4.);
+ } else if (x <= 0.75) {
+ value = c1*( -8.*(x-5./8.)*(x-5./8.) + 1./8.);
+ } else {
+ value = c1*( -4.*(x-7./8.)*(x-7./8.) + 1./16.);
+ }
+
+ return value;
+}
+
+const QMap< TextAnimation::Curve, animationFunction_t> ANIMATION_TABLE = {
+ {TextAnimation::Step, Step},
+ {TextAnimation::Linear, Linear},
+ {TextAnimation::Ease_Sine, EaseOutSine},
+ {TextAnimation::Ease_Quad, EaseOutQuad},
+ {TextAnimation::Ease_Back, EaseOutBack},
+ {TextAnimation::Ease_Elastic, EaseOutElastic},
+ {TextAnimation::Ease_Bounce, EaseOutBounce},
+};
+
+} // namespace
+
+TextAnimationEngine::TextAnimationEngine() :
+ animators_(nullptr),
+ text_size_(0),
+ last_index_(-1)
+{
+}
+
+
+
+void TextAnimationEngine::Calulate()
+{
+ ResetVectors();
+
+ Q_ASSERT( animators_);
+
+ foreach ( const TextAnimation::Descriptor & anim, *animators_) {
+ CalculateAnimator( anim);
+ }
+}
+
+
+void TextAnimationEngine::ResetVectors()
+{
+ horiz_offsets_.clear();
+ vert_offsets_.clear();
+ rotations_.clear();
+ spacings_.clear();
+
+ // preset all vectors with the length of the full text
+ horiz_offsets_.fill( 0.0, text_size_);
+ vert_offsets_.fill( 0.0, text_size_);
+ rotations_.fill( 0.0, text_size_);
+ spacings_.fill( 0.0, text_size_);
+}
+
+void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &animator)
+{
+ int index = animator.character_from;
+ last_index_ = CalculateEndIndex( animator);
+
+ while (index <= last_index_) {
+
+ double anim_value = CalculateAnimatorForChar( animator, index);
+
+ switch( animator.feature) {
+ case TextAnimation::PositionVertical:
+ vert_offsets_[index] = anim_value;
+ break;
+ case TextAnimation::PositionHorizontal:
+ horiz_offsets_[index] = anim_value;
+ break;
+ case TextAnimation::Rotation:
+ rotations_[index] = anim_value;
+ break;
+ case TextAnimation::SpacingFactor:
+ spacings_[index] = anim_value;
+ break;
+ default:
+ case TextAnimation::None:
+ break;
+ }
+
+ index++;
+ }
+}
+
+// the final index is 'animator.character_to', unless -1 is used (that means full text).
+// If the text is empty, 0 is passed.
+int TextAnimationEngine::CalculateEndIndex( const TextAnimation::Descriptor &animator) {
+ int end_index = 0;
+
+ if (text_size_ > 0) {
+ if (animator.character_to == -1) {
+ end_index = (text_size_-1);
+ }
+ else {
+ end_index = qMin( (text_size_-1), animator.character_to);
+ }
+ }
+
+ return end_index;
+}
+
+// based on the number of characters that must be animated, divide the range [0,1] in slices of length 'dt'.
+// When 'overlap_in' and 'overlap_out' are 1, each character perform its transition in one of this slice.
+// By lowering 'overlap_in', the transitions begin earlier (when overlap_in=0 they all start at the begin).
+// By lowering 'overlap_out', the transitions end earlier (when overlap_out=1 they all finish at the end).
+// The field 'alpha' of parameter 'animator' identifies the evolution of the transition: when alpha = 0,
+// the transition is not started; when alpha = 1, the transition is complete.
+double TextAnimationEngine::CalculateAnimatorForChar( const TextAnimation::Descriptor &animator, int char_index)
+{
+ int num_chars = last_index_ - animator.character_from + 1;
+ double animation_value = 0.0;
+
+ Q_ASSERT( num_chars != 0);
+
+ double dt = 1.0/(double)num_chars;
+ const double i = (double)char_index - (double)animator.character_from;
+
+ double low = i*dt*(animator.overlap_in);
+ double hi = (i+1.)*dt*(animator.overlap_out) + (1. - (animator.overlap_out));
+
+ if (animator.alpha <= (low)) {
+ animation_value = 1.0;
+ } else if (animator.alpha < hi) {
+ double x = (animator.alpha - low)/(hi-low);
+ animation_value = ANIMATION_TABLE.value( animator.curve)( x, animator.c1, animator.c2);
+ }
+ else {
+ animation_value = 0.0;
+ }
+
+ animation_value *= animator.value;
+
+ return animation_value;
+}
+
+} // olive
+
diff --git a/app/node/generator/animation/textanimationengine.h b/app/node/generator/animation/textanimationengine.h
new file mode 100644
index 0000000000..d88a51a4b2
--- /dev/null
+++ b/app/node/generator/animation/textanimationengine.h
@@ -0,0 +1,75 @@
+#ifndef TEXTANIMATIONENGINE_H
+#define TEXTANIMATIONENGINE_H
+
+#include "textanimation.h"
+#include
+
+namespace olive {
+
+
+/// This class calculates the values of each character feature
+/// (offset, rotation, spacing) for a given moment of the transition.
+/// Method \a calculate must be called after setting all animators. After that,
+/// output functions can be called.
+/// If there are several animation, each one is calculated in sequence to generate
+/// the final vectors of offset and rotation for each character.
+class TextAnimationEngine
+{
+public:
+ TextAnimationEngine();
+
+ /// to be called before 'calculate' to indicate the number
+ /// of characters of the full document.
+ void SetTextSize( int text_size) {
+ text_size_ = text_size;
+ }
+
+ /// set the list of animators to be applied to text document.
+ void SetAnimators( const QList * animators) {
+ animators_ = animators;
+ }
+
+ /// perform all calculation for the animation of text.
+ void Calulate();
+
+ /// valid after calling \a calculate
+ const QVector & HorizontalOffset() const {
+ return horiz_offsets_;
+ }
+
+ /// valid after calling \a calculate
+ const QVector & VerticalOffset() const {
+ return vert_offsets_;
+ }
+
+ /// valid after calling \a calculate
+ const QVector & Rotation() const {
+ return rotations_;
+ }
+
+ /// valid after calling \a calculate
+ const QVector & Spacing() const {
+ return spacings_;
+ }
+
+private:
+ void ResetVectors();
+ void CalculateAnimator( const TextAnimation::Descriptor & animator);
+ int CalculateEndIndex(const TextAnimation::Descriptor &animator);
+ double CalculateAnimatorForChar( const TextAnimation::Descriptor &animator, int char_index);
+
+private:
+ const QList * animators_;
+ int text_size_;
+ int last_index_;
+
+ QVector horiz_offsets_;
+ QVector vert_offsets_;
+ QVector rotations_;
+ QVector spacings_;
+
+};
+
+} // olive
+
+#endif // TEXTANIMATIONENGINE_H
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
new file mode 100644
index 0000000000..6f9d5e313a
--- /dev/null
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -0,0 +1,151 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2022 Olive Team
+
+ 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 3 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, see .
+
+***/
+
+#include "textanimationnode.h"
+#include "textanimation.h"
+
+#include "textanimationxmlformatter.h"
+
+namespace olive {
+
+const QString TextAnimationNode::kCompositeInput = QStringLiteral("composite_in");
+const QString TextAnimationNode::kCompositeOutput = QStringLiteral("composite_out");
+const QString TextAnimationNode::kFeatureInput = QStringLiteral("feature_in");
+const QString TextAnimationNode::kIndexFromInput = QStringLiteral("from_in");
+const QString TextAnimationNode::kIndexToInput = QStringLiteral("to_in");
+const QString TextAnimationNode::kOverlapInInput = QStringLiteral("overlap_in_in");
+const QString TextAnimationNode::kOverlapOutInput = QStringLiteral("overlap_out_in");
+const QString TextAnimationNode::kCurveInput = QStringLiteral("curve_in");
+const QString TextAnimationNode::kC1Input = QStringLiteral("c1_in");
+const QString TextAnimationNode::kC2Input = QStringLiteral("c2_in");
+const QString TextAnimationNode::kValueInput = QStringLiteral("fvalue_in");
+const QString TextAnimationNode::kAlphaInput = QStringLiteral("alpha_in");
+
+#define super Node
+
+TextAnimationNode::TextAnimationNode()
+{
+ AddInput( kCompositeInput, NodeValue::kText,
+ QStringLiteral(""),
+ InputFlags(kInputFlagNotKeyframable));
+ AddInput( kCompositeOutput, NodeValue::kText, QString(""), InputFlags(kInputFlagHidden));
+
+ AddInput( kFeatureInput, NodeValue::kCombo, 1);
+ // keep order defined in TextAnimation::Feature, not the one in QMaps
+ SetComboBoxStrings( kFeatureInput, QStringList()
+ << tr("None")
+ << tr("Vertical Position")
+ << tr("Horizontal Position")
+ << tr("Rotation")
+ << tr("Spacing")
+ );
+
+ AddInput( kIndexFromInput, NodeValue::kInt, 0);
+ SetInputProperty( kIndexFromInput, QStringLiteral("min"), 0);
+
+ AddInput( kIndexToInput, NodeValue::kInt, -1); // default to -1 to animate the full text
+ SetInputProperty( kIndexToInput, QStringLiteral("min"), -1);
+
+ AddInput( kOverlapInInput, NodeValue::kFloat, 1.);
+ SetInputProperty( kOverlapInInput, QStringLiteral("min"), 0.);
+ SetInputProperty( kOverlapInInput, QStringLiteral("max"), 1.);
+
+ AddInput( kOverlapOutInput, NodeValue::kFloat, 1.);
+ SetInputProperty( kOverlapOutInput, QStringLiteral("min"), 0.);
+ SetInputProperty( kOverlapOutInput, QStringLiteral("max"), 1.);
+
+ AddInput( kCurveInput, NodeValue::kCombo, 0);
+ // keep order defined in TextAnimation::Curve, not the one in QMaps
+ SetComboBoxStrings( kCurveInput, QStringList()
+ << tr("Step")
+ << tr("Linear")
+ << tr("Ease_Sine")
+ << tr("Ease_Quad")
+ << tr("Ease_Back")
+ << tr("Ease_Elastic")
+ << tr("Ease_Bounce")
+ );
+
+ AddInput( kC1Input, NodeValue::kFloat, 1.0);
+
+ AddInput( kC2Input, NodeValue::kFloat, 1.0);
+
+ AddInput( kValueInput, NodeValue::kFloat, 10.);
+
+ AddInput( kAlphaInput, NodeValue::kFloat, 0.);
+ SetInputProperty( kAlphaInput, QStringLiteral("min"), 0.);
+ SetInputProperty( kAlphaInput, QStringLiteral("max"), 1.);
+
+}
+
+void TextAnimationNode::Retranslate()
+{
+ super::Retranslate();
+
+ SetInputName( kCompositeInput, QStringLiteral("Composite"));
+ SetInputName( kFeatureInput, QStringLiteral("Feature"));
+ SetInputName( kIndexFromInput, QStringLiteral("From"));
+ SetInputName( kIndexToInput, QStringLiteral("To"));
+ SetInputName( kOverlapInInput, QStringLiteral("Overlap IN"));
+ SetInputName( kOverlapOutInput, QStringLiteral("Overlap OUT"));
+ SetInputName( kCurveInput, QStringLiteral("Curve"));
+ SetInputName( kC1Input, QStringLiteral("C1"));
+ SetInputName( kC2Input, QStringLiteral("C2"));
+ SetInputName( kValueInput, QStringLiteral("Value"));
+ SetInputName( kAlphaInput, QStringLiteral("Alpha"));
+}
+
+
+void TextAnimationNode::Value(const NodeValueRow &value, const NodeGlobals & /*globals*/, NodeValueTable *table) const
+{
+ GenerateJob job;
+ job.Insert(value);
+
+ // all previous animations
+ QString composite = job.Get(kCompositeInput).toString();
+
+ // add the new animation
+ TextAnimation::Descriptor animation;
+
+ animation.feature = (TextAnimation::Feature) (job.Get(kFeatureInput).toInt());
+ animation.character_from = job.Get(kIndexFromInput).toInt();
+ animation.character_to = job.Get(kIndexToInput).toInt();;
+ animation.overlap_in = job.Get(kOverlapInInput).toDouble ();
+ animation.overlap_out = job.Get(kOverlapOutInput).toDouble ();
+ animation.curve = (TextAnimation::Curve) (job.Get(kCurveInput).toInt());
+ animation.c1 = job.Get(kC1Input).toDouble ();;
+ animation.c2 = job.Get(kC2Input).toDouble ();;
+ animation.value = job.Get(kValueInput).toDouble ();;
+ animation.alpha = job.Get(kAlphaInput).toDouble ();;
+
+ TextAnimationXmlFormatter formatter;
+ formatter.SetAnimationData( & animation);
+
+ // append the new animation descriptor to the composite
+ job.Insert( kCompositeOutput, NodeValue(NodeValue::kText,
+ composite + formatter.Format() + "\n", this));
+
+ // append new to old
+ table->Push( job.Get(kCompositeOutput));
+}
+
+
+} // olive
+
diff --git a/app/node/generator/animation/textanimationnode.h b/app/node/generator/animation/textanimationnode.h
new file mode 100644
index 0000000000..3dac7a8a3c
--- /dev/null
+++ b/app/node/generator/animation/textanimationnode.h
@@ -0,0 +1,98 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2022 Olive Team
+
+ 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 3 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, see .
+
+***/
+
+#ifndef TEXTANIMATIONNODE_H
+#define TEXTANIMATIONNODE_H
+
+#include "node/node.h"
+
+namespace olive {
+
+/// This node produces an XML string that descirbes the animation of one
+/// feature of a rich text. This node has an input that can be connected
+/// to the output of another instance of this node. In this case, the produced
+/// XML string is appended to such input. This allows multiple effects to be cascaded
+class TextAnimationNode : public Node
+{
+ Q_OBJECT
+public:
+ TextAnimationNode();
+
+ NODE_DEFAULT_FUNCTIONS(TextAnimationNode)
+
+ virtual QString Name() const override
+ {
+ return tr("TextAnimation");
+ }
+
+ virtual QString id() const override
+ {
+ return QStringLiteral("org.olivevideoeditor.Olive.textanimation");
+ }
+
+ virtual QVector Category() const override
+ {
+ return {kCategoryGenerator};
+ }
+
+ virtual QString Description() const override
+ {
+ return tr("Create a descriptor for a text animation. More animations can be cascaded.");
+ }
+
+ virtual void Retranslate() override;
+
+ virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
+
+
+ // a string that is the output of another instance of this node
+ static const QString kCompositeInput;
+ // 'kCompositeInput' plus the new animation
+ static const QString kCompositeOutput;
+
+ // the feature that is being animated
+ static const QString kFeatureInput;
+ // index of first character to be animated
+ static const QString kIndexFromInput;
+ // index of last character to be animated
+ static const QString kIndexToInput;
+ // range [0-1]. When 0 all characters start the animation at the begin;
+ // When 1, each character starts the animation when the previous is over
+ static const QString kOverlapInInput;
+ // range [0-1]. When 0 all characters finish the animation at the end;
+ // When 1, each character finishes the animation before the next starts
+ static const QString kOverlapOutInput;
+ // Kind of curve used for animation
+ static const QString kCurveInput;
+ // a coefficent that may (or may not) influence the shape of the curve
+ static const QString kC1Input;
+ // a coefficent that may (or may not) influence the shape of the curve
+ static const QString kC2Input;
+ // maximum value of the feature that is being animated
+ static const QString kValueInput;
+ // Evolution of the transition in range [0,1]. When 0, the animated value is equal to 'value';
+ // When 1, the animated value is equal to 0.
+ static const QString kAlphaInput;
+
+};
+
+}
+
+#endif // TEXTANIMATIONNODE_H
diff --git a/app/node/generator/animation/textanimationrendernode.cpp b/app/node/generator/animation/textanimationrendernode.cpp
new file mode 100644
index 0000000000..cf4076430a
--- /dev/null
+++ b/app/node/generator/animation/textanimationrendernode.cpp
@@ -0,0 +1,221 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2022 Olive Team
+
+ 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 3 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, see .
+
+***/
+
+#include "textanimationrendernode.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "common/cpuoptimize.h"
+#include "common/html.h"
+#include "node/generator/animation/textanimationxmlparser.h"
+
+namespace olive {
+
+const QString TextAnimationRenderNode::kPositionInput = QStringLiteral("pos_in");
+const QString TextAnimationRenderNode::kRichTextInput = QStringLiteral("rich_text_in");
+const QString TextAnimationRenderNode::kAnimatorsInput = QStringLiteral("animators_in");
+const QString TextAnimationRenderNode::kLineHeightInput = QStringLiteral("line_height_in");
+
+
+#define super Node
+
+TextAnimationRenderNode::TextAnimationRenderNode() :
+ position_handle_(nullptr)
+{
+ AddInput(kPositionInput, NodeValue::kVec2, QVector2D(30, 30));
+ AddInput( kRichTextInput, NodeValue::kText,
+ QStringLiteral("%1
").arg(tr("Sample Text")),
+ InputFlags(kInputFlagNotKeyframable));
+ AddInput( kAnimatorsInput, NodeValue::kText,
+ QStringLiteral(""),
+ InputFlags(kInputFlagNotKeyframable));
+ AddInput( kLineHeightInput, NodeValue::kInt, 50, InputFlags(kInputFlagNotKeyframable));
+
+ position_handle_ = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0),
+ NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1)});
+
+ position_handle_->SetShape( PointGizmo::kAnchorPoint);
+ position_handle_->SetDragValueBehavior(PointGizmo::kAbsolute);
+
+ SetEffectInput(kRichTextInput);
+
+ engine_ = new TextAnimationEngine();
+}
+
+QString TextAnimationRenderNode::Name() const
+{
+ return tr("Text Animation Render");
+}
+
+QString TextAnimationRenderNode::id() const
+{
+ return QStringLiteral("org.olivevideoeditor.Olive.TextAnimationRenderNode");
+}
+
+QVector TextAnimationRenderNode::Category() const
+{
+ return {kCategoryGenerator};
+}
+
+QString TextAnimationRenderNode::Description() const
+{
+ return tr("Takes a rich text and a set of animation descriptors to produce the text animation");
+}
+
+void TextAnimationRenderNode::Retranslate()
+{
+ super::Retranslate();
+
+ SetInputName(kPositionInput, tr("Position"));
+ SetInputName(kRichTextInput, tr("Text"));
+ SetInputName(kAnimatorsInput, tr("Animators"));
+ SetInputName(kLineHeightInput, tr("Line Height"));
+}
+
+GenerateJob TextAnimationRenderNode::GetGenerateJob(const NodeValueRow &value) const
+{
+ GenerateJob job;
+
+ job.Insert(value);
+ job.SetRequestedFormat(VideoParams::kFormatUnsigned8);
+ job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
+
+ return job;
+}
+
+void TextAnimationRenderNode::Value(const NodeValueRow &value, const NodeGlobals & /*globals*/, NodeValueTable *table) const
+{
+ GenerateJob job = GetGenerateJob(value);
+
+ table->Push(NodeValue::kTexture, job, this);
+}
+
+
+void TextAnimationRenderNode::GenerateFrame(FramePtr frame, const GenerateJob &job) const
+{
+ QImage img(reinterpret_cast(frame->data()), frame->width(), frame->height(),
+ frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
+ img.fill(Qt::transparent);
+
+ // 96 DPI in DPM (96 / 2.54 * 100)
+ const int dpm = 3780;
+ img.setDotsPerMeterX(dpm);
+ img.setDotsPerMeterY(dpm);
+
+ QTextDocument text_doc;
+ text_doc.setDefaultFont(QFont("Arial", 50));
+
+ QString html = job.Get(kRichTextInput).toString();
+ Html::HtmlToDoc(&text_doc, html);
+ QTextCursor cursor( &text_doc);
+
+ // aspect ratio
+ double par = frame->video_params().pixel_aspect_ratio().toDouble();
+ QPointF scale_factor = QPointF(1.0 / frame->video_params().divider() / par, 1.0 / frame->video_params().divider());
+
+
+ QPainter p(&img);
+ p.scale( scale_factor.x(), scale_factor.y());
+
+ QVector2D pos = job.Get(kPositionInput).toVec2();
+
+ p.translate(pos.x(), pos.y());
+
+ p.setBrush(Qt::NoBrush);
+ p.setPen(Qt::white);
+
+ p.setRenderHints( QPainter::Antialiasing |
+ QPainter::SmoothPixmapTransform);
+
+ int textSize = text_doc.characterCount();
+ cursor.movePosition( QTextCursor::Start);
+ cursor.movePosition( QTextCursor::Right);
+
+ // 'kAnimatorsInput' holds a list of XML tags without main opening and closing tags.
+ // Prepend and append XML start and closing tags.
+ QString animators_xml = QStringLiteral("\n") + job.Get(kAnimatorsInput).toString() +
+ QStringLiteral("
");
+
+ QList animators = TextAnimationXmlParser::Parse( animators_xml);
+
+ engine_->SetTextSize( textSize);
+ engine_->SetAnimators( & animators);
+ engine_->Calulate();
+
+ int posx = 0;
+ int line = 0;
+ int line_height = job.Get(kLineHeightInput).toInt();
+
+
+ const QVector & horiz_offsets = engine_->HorizontalOffset();
+ const QVector & vert_offsets = engine_->VerticalOffset();
+ const QVector & rotations = engine_->Rotation();
+ const QVector & spacings = engine_->Spacing();
+
+ // parse the whole text document and print every character
+ for (int i=0; i < textSize; i++) {
+ QChar ch = text_doc.characterAt(i);
+
+ p.save();
+ p.setFont( cursor.charFormat().font());
+ p.setPen( cursor.charFormat().foreground().color());
+ p.setBrush( cursor.charFormat().foreground());
+ p.translate( QPointF(posx + horiz_offsets[i],
+ line*line_height + vert_offsets[i]));
+ p.rotate( rotations[i]);
+ p.drawText( QPointF(0,0), ch);
+ //p.resetTransform();
+ p.restore();
+
+ posx += QFontMetrics( cursor.charFormat().font()).horizontalAdvance(text_doc.characterAt(i)) + (int)(spacings[i]);
+ cursor.movePosition( QTextCursor::Right);
+
+ if ((ch == QChar::LineSeparator) ||
+ (ch == QChar::ParagraphSeparator) )
+ {
+ line++;
+ posx = 0;
+ }
+ }
+}
+
+
+void TextAnimationRenderNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers & /*modifiers*/)
+{
+ DraggableGizmo *gizmo = static_cast(sender());
+
+ gizmo->GetDraggers()[0].Drag( x);
+ gizmo->GetDraggers()[1].Drag( y);
+}
+
+void TextAnimationRenderNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals & /*globals*/)
+{
+ QVector2D position_vec = row[kPositionInput].toVec2();
+ QPointF position_pt = QPointF(position_vec.x(), position_vec.y());
+
+ // Create handles
+ position_handle_->SetPoint(position_pt);
+}
+
+} // olive
diff --git a/app/node/generator/animation/textanimationrendernode.h b/app/node/generator/animation/textanimationrendernode.h
new file mode 100644
index 0000000000..12b685de93
--- /dev/null
+++ b/app/node/generator/animation/textanimationrendernode.h
@@ -0,0 +1,72 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2022 Olive Team
+
+ 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 3 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, see .
+
+***/
+
+#ifndef TEXTANIMATIONRENDERNODE_H
+#define TEXTANIMATIONRENDERNODE_H
+
+#include
+
+#include "common/bezier.h"
+#include "node/node.h"
+#include "node/gizmo/point.h"
+#include "node/generator/animation/textanimationengine.h"
+
+namespace olive {
+
+class TextAnimationRenderNode : public Node
+{
+ Q_OBJECT
+public:
+ TextAnimationRenderNode();
+
+ NODE_DEFAULT_FUNCTIONS(TextAnimationRenderNode)
+
+ virtual QString Name() const override;
+ virtual QString id() const override;
+ virtual QVector Category() const override;
+ virtual QString Description() const override;
+
+ virtual void Retranslate() override;
+
+ virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
+
+ virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
+
+ virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
+
+ static const QString kPositionInput;
+ static const QString kRichTextInput;
+ static const QString kAnimatorsInput;
+ static const QString kLineHeightInput;
+
+protected:
+ GenerateJob GetGenerateJob(const NodeValueRow &value) const;
+
+protected slots:
+ virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
+
+private:
+ TextAnimationEngine * engine_;
+ PointGizmo * position_handle_;
+};
+
+}
+
+#endif // TEXTANIMATIONRENDERNODE_H
diff --git a/app/node/generator/animation/textanimationxmlformatter.cpp b/app/node/generator/animation/textanimationxmlformatter.cpp
new file mode 100644
index 0000000000..e83cfe9c55
--- /dev/null
+++ b/app/node/generator/animation/textanimationxmlformatter.cpp
@@ -0,0 +1,36 @@
+#include "textanimationxmlformatter.h"
+
+#include
+
+namespace olive {
+
+TextAnimationXmlFormatter::TextAnimationXmlFormatter() :
+ descriptor_(nullptr)
+{
+}
+
+QString TextAnimationXmlFormatter::Format() const
+{
+ QString out;
+ QXmlStreamWriter writer( & out);
+
+ if (descriptor_) {
+ writer.writeStartElement( TextAnimation::FEATURE_TABLE.value(descriptor_->feature, ""));
+
+ writer.writeAttribute( "ch_from", QString("%1").arg(descriptor_->character_from));
+ writer.writeAttribute( "ch_to", QString("%1").arg(descriptor_->character_to));
+ writer.writeAttribute( "overlap_in", QString("%1").arg(descriptor_->overlap_in));
+ writer.writeAttribute( "overlap_out", QString("%1").arg(descriptor_->overlap_out));
+ writer.writeAttribute( "curve", TextAnimation::CURVE_TABLE.value(descriptor_->curve, ""));
+ writer.writeAttribute( "c1", QString("%1").arg(descriptor_->c1));
+ writer.writeAttribute( "c2", QString("%1").arg(descriptor_->c2));
+ writer.writeAttribute( "value", QString("%1").arg(descriptor_->value));
+ writer.writeAttribute( "alpha", QString("%1").arg(descriptor_->alpha));
+
+ writer.writeEndElement();
+ }
+
+ return out;
+}
+
+} // olive
diff --git a/app/node/generator/animation/textanimationxmlformatter.h b/app/node/generator/animation/textanimationxmlformatter.h
new file mode 100644
index 0000000000..3ba0e97619
--- /dev/null
+++ b/app/node/generator/animation/textanimationxmlformatter.h
@@ -0,0 +1,31 @@
+#ifndef ANIMATIONXMLFORMATTER_H
+#define ANIMATIONXMLFORMATTER_H
+
+#include
+
+#include "textanimation.h"
+
+namespace olive {
+
+/// This class takes an animation descriptor and generates
+/// an XML tag with all animation parameters.
+class TextAnimationXmlFormatter
+{
+public:
+ TextAnimationXmlFormatter();
+
+ /// to be called before \a format
+ void SetAnimationData( const struct TextAnimation::Descriptor * descr) {
+ descriptor_ = descr;
+ }
+
+ /// main method to build the XML tag for a text animation
+ QString Format() const;
+
+private:
+ const struct TextAnimation::Descriptor * descriptor_;
+};
+
+} // olive
+
+#endif // ANIMATIONXMLFORMATTER_H
diff --git a/app/node/generator/animation/textanimationxmlparser.cpp b/app/node/generator/animation/textanimationxmlparser.cpp
new file mode 100644
index 0000000000..76de15cc9e
--- /dev/null
+++ b/app/node/generator/animation/textanimationxmlparser.cpp
@@ -0,0 +1,45 @@
+#include "textanimationxmlparser.h"
+#include
+
+
+namespace olive {
+
+TextAnimationXmlParser::TextAnimationXmlParser()
+{
+}
+
+QList TextAnimationXmlParser::Parse(const QString& xmlInput)
+{
+ QList descriptors;
+ QXmlStreamReader reader( xmlInput);
+ TextAnimation::Descriptor item;
+
+ while (reader.atEnd() == false) {
+
+ switch( reader.readNext()) {
+ case QXmlStreamReader::StartElement:
+ item.feature = TextAnimation::FEATURE_TABLE_REV.value( reader.name().toString(), TextAnimation::None);
+ item.character_from = reader.attributes().value(QString("ch_from")).toInt();
+ item.character_to = reader.attributes().value(QString("ch_to")).toInt();
+ item.overlap_in = reader.attributes().value(QString("overlap_in")).toDouble();
+ item.overlap_out = reader.attributes().value(QString("overlap_out")).toDouble();
+ item.curve = TextAnimation::CURVE_TABLE_REV.value( reader.attributes().value("curve").toString());
+ item.c1 = reader.attributes().value(QString("c1")).toDouble();
+ item.c2 = reader.attributes().value(QString("c2")).toDouble();
+ item.value = reader.attributes().value(QString("value")).toDouble();
+ item.alpha = reader.attributes().value(QString("alpha")).toDouble();
+
+ if (item.feature != TextAnimation::None) {
+ descriptors << item;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return descriptors;
+}
+
+} // olive
diff --git a/app/node/generator/animation/textanimationxmlparser.h b/app/node/generator/animation/textanimationxmlparser.h
new file mode 100644
index 0000000000..516cf411db
--- /dev/null
+++ b/app/node/generator/animation/textanimationxmlparser.h
@@ -0,0 +1,24 @@
+#ifndef ANIMATIONXMLPARSER_H
+#define ANIMATIONXMLPARSER_H
+
+#include "textanimation.h"
+#include
+
+namespace olive {
+
+/// This class parses an XML string that is probably generated by
+/// \a TextAnimationXmlFormatter.
+/// A list of animation descriptors is extracted.
+class TextAnimationXmlParser
+{
+public:
+ TextAnimationXmlParser();
+
+ /// \param xmlInput must contain opening and closing XML main tags.
+ /// The format must comply with TextAnimationXmlFormatter
+ static QList Parse( const QString & xmlInput);
+};
+
+} // olive
+
+#endif // ANIMATIONXMLPARSER_H
diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp
index da6f9bfe87..bd12a5dd0e 100644
--- a/app/node/generator/text/textv3.cpp
+++ b/app/node/generator/text/textv3.cpp
@@ -39,6 +39,7 @@ enum TextVerticalAlign {
};
const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in");
+const QString TextGeneratorV3::kOutputHtmlOnly = QStringLiteral("html_only_in");
TextGeneratorV3::TextGeneratorV3() :
ShapeNodeBase(false)
@@ -46,6 +47,8 @@ TextGeneratorV3::TextGeneratorV3() :
AddInput(kTextInput, NodeValue::kText, QStringLiteral("%1
").arg(tr("Sample Text")));
SetInputProperty(kTextInput, QStringLiteral("vieweronly"), true);
+ AddInput(kOutputHtmlOnly, NodeValue::kBoolean, false);
+
SetStandardValue(kSizeInput, QVector2D(400, 300));
text_gizmo_ = new TextGizmo(this);
@@ -77,6 +80,7 @@ void TextGeneratorV3::Retranslate()
super::Retranslate();
SetInputName(kTextInput, tr("Text"));
+ SetInputName(kOutputHtmlOnly, tr("Output HTML"));
}
void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
@@ -89,10 +93,16 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global
// FIXME: Provide user override for this
job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
- if (!job.Get(kTextInput).toString().isEmpty()) {
- PushMergableJob(value, QVariant::fromValue(job), table);
- } else if (value[kBaseInput].toTexture()) {
- table->Push(value[kBaseInput]);
+ if (job.Get(kOutputHtmlOnly).toBool() == false) {
+
+ if (!job.Get(kTextInput).toString().isEmpty()) {
+ PushMergableJob(value, QVariant::fromValue(job), table);
+ } else if (value[kBaseInput].toTexture()) {
+ table->Push(value[kBaseInput]);
+ }
+ }
+ else {
+ table->Push(job.Get(kTextInput));
}
}
diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h
index 69f08f617d..45b64adeab 100644
--- a/app/node/generator/text/textv3.h
+++ b/app/node/generator/text/textv3.h
@@ -48,6 +48,7 @@ class TextGeneratorV3 : public ShapeNodeBase
virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
static const QString kTextInput;
+ static const QString kOutputHtmlOnly;
private:
TextGizmo *text_gizmo_;
From 45522664f71ed6a388a772b66a2ff7592e168d02 Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Sun, 17 Jul 2022 17:19:07 +0200
Subject: [PATCH 02/11] Fixed a bug in cascaded animations
---
app/node/generator/animation/textanimationengine.cpp | 8 ++++----
app/node/generator/animation/textanimationrendernode.cpp | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index 0694edc2bb..f97da4e65b 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -137,16 +137,16 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
switch( animator.feature) {
case TextAnimation::PositionVertical:
- vert_offsets_[index] = anim_value;
+ vert_offsets_[index] += anim_value;
break;
case TextAnimation::PositionHorizontal:
- horiz_offsets_[index] = anim_value;
+ horiz_offsets_[index] += anim_value;
break;
case TextAnimation::Rotation:
- rotations_[index] = anim_value;
+ rotations_[index] += anim_value;
break;
case TextAnimation::SpacingFactor:
- spacings_[index] = anim_value;
+ spacings_[index] += anim_value;
break;
default:
case TextAnimation::None:
diff --git a/app/node/generator/animation/textanimationrendernode.cpp b/app/node/generator/animation/textanimationrendernode.cpp
index cf4076430a..135460f88d 100644
--- a/app/node/generator/animation/textanimationrendernode.cpp
+++ b/app/node/generator/animation/textanimationrendernode.cpp
@@ -43,7 +43,7 @@ const QString TextAnimationRenderNode::kLineHeightInput = QStringLiteral("line_h
TextAnimationRenderNode::TextAnimationRenderNode() :
position_handle_(nullptr)
{
- AddInput(kPositionInput, NodeValue::kVec2, QVector2D(30, 30));
+ AddInput(kPositionInput, NodeValue::kVec2, QVector2D(200, 200));
AddInput( kRichTextInput, NodeValue::kText,
QStringLiteral("%1
").arg(tr("Sample Text")),
InputFlags(kInputFlagNotKeyframable));
From a2110cf56ff10d3b2caa1432ce390ebc0d451e8d Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Sun, 17 Jul 2022 17:48:48 +0200
Subject: [PATCH 03/11] Restore a change that must not be pushed
---
CMakeLists.txt | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4ca3d62fc0..b46d11f6fd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -157,15 +157,15 @@ endif()
list(APPEND OLIVE_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
list(APPEND OLIVE_LIBRARIES ${PORTAUDIO_LIBRARIES})
-## # Optional: Link OpenTimelineIO
-## find_package(OpenTimelineIO)
-## if (OpenTimelineIO_FOUND)
-## list(APPEND OLIVE_DEFINITIONS USE_OTIO)
-## list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
-## list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
-## else()
-## message(" OpenTimelineIO interchange will be disabled.")
-## endif()
+# Optional: Link OpenTimelineIO
+find_package(OpenTimelineIO)
+if (OpenTimelineIO_FOUND)
+ list(APPEND OLIVE_DEFINITIONS USE_OTIO)
+ list(APPEND OLIVE_INCLUDE_DIRS ${OTIO_INCLUDE_DIRS})
+ list(APPEND OLIVE_LIBRARIES ${OTIO_LIBRARIES})
+else()
+ message(" OpenTimelineIO interchange will be disabled.")
+endif()
# Optional: Link Google Crashpad
find_package(GoogleCrashpad)
From c4e7c2ed1f1786523ab7b75032c929f4eda73abe Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Sat, 23 Jul 2022 14:38:26 +0200
Subject: [PATCH 04/11] Removed "text html only" input to Text node bacause no
longer needed. Also added horizontal and vertcal stretch to animation
features
---
app/node/generator/animation/textanimation.h | 18 ++-
.../animation/textanimationengine.cpp | 17 +++
.../generator/animation/textanimationengine.h | 18 +++
.../generator/animation/textanimationnode.cpp | 22 ++--
.../animation/textanimationrendernode.cpp | 116 ++++++++++--------
.../animation/textanimationrendernode.h | 11 +-
app/node/generator/text/textv3.cpp | 19 +--
app/node/generator/text/textv3.h | 2 -
8 files changed, 136 insertions(+), 87 deletions(-)
diff --git a/app/node/generator/animation/textanimation.h b/app/node/generator/animation/textanimation.h
index 7f0b9a5b18..b4c6a60642 100644
--- a/app/node/generator/animation/textanimation.h
+++ b/app/node/generator/animation/textanimation.h
@@ -18,20 +18,32 @@ enum Feature{
// rotation of each character around its bottom left corner
Rotation,
// space between letters
- SpacingFactor
+ SpacingFactor,
+ // vertical stretch of each character
+ StretchVertical,
+ // horizontal stretch of each character
+ StretchHorizontal,
+ // opacity in range 0 - 255
+ Transparency
};
const QMap< Feature, QString> FEATURE_TABLE = {{None, "NONE"},
{PositionVertical, "POSITION_VER"},
{PositionHorizontal, "POSITION_HOR"},
{Rotation, "ROTATION"},
- {SpacingFactor, "SPACING"}
+ {SpacingFactor, "SPACING"},
+ {StretchVertical, "STRETCH_VER"},
+ {StretchHorizontal, "STRETCH_HOR"},
+ {Transparency, "TRANSPARENCY"},
};
const QMap< QString, Feature> FEATURE_TABLE_REV = {{"NONE", None},
{"POSITION_VER", PositionVertical},
{"POSITION_HOR", PositionHorizontal},
{"ROTATION", Rotation},
- {"SPACING", SpacingFactor}
+ {"SPACING", SpacingFactor},
+ {"STRETCH_VER", StretchVertical},
+ {"STRETCH_HOR", StretchHorizontal},
+ {"TRANSPARENCY", Transparency}
};
/// shape of animation curve
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index f97da4e65b..0b27a1bdcd 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -118,12 +118,18 @@ void TextAnimationEngine::ResetVectors()
vert_offsets_.clear();
rotations_.clear();
spacings_.clear();
+ horiz_stretch_.clear();
+ vert_stretch_.clear();
+ transparency_.clear();
// preset all vectors with the length of the full text
horiz_offsets_.fill( 0.0, text_size_);
vert_offsets_.fill( 0.0, text_size_);
rotations_.fill( 0.0, text_size_);
spacings_.fill( 0.0, text_size_);
+ horiz_stretch_.fill( 0.0, text_size_);
+ vert_stretch_.fill( 0.0, text_size_);
+ transparency_.fill( 0, text_size_);
}
void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &animator)
@@ -148,6 +154,17 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
case TextAnimation::SpacingFactor:
spacings_[index] += anim_value;
break;
+ case TextAnimation::StretchVertical:
+ vert_stretch_[index] += anim_value;
+ break;
+ case TextAnimation::StretchHorizontal:
+ horiz_stretch_[index] += anim_value;
+ break;
+ case TextAnimation::Transparency:
+ transparency_[index] += (int)anim_value;
+ transparency_[index] = qMin( transparency_[index], 255);
+ transparency_[index] = qMax( transparency_[index], 0);
+ break;
default:
case TextAnimation::None:
break;
diff --git a/app/node/generator/animation/textanimationengine.h b/app/node/generator/animation/textanimationengine.h
index d88a51a4b2..8663c1de0f 100644
--- a/app/node/generator/animation/textanimationengine.h
+++ b/app/node/generator/animation/textanimationengine.h
@@ -52,6 +52,21 @@ class TextAnimationEngine
return spacings_;
}
+ /// valid after calling \a calculate
+ const QVector & HorizontalStretch() const {
+ return horiz_stretch_;
+ }
+
+ /// valid after calling \a calculate
+ const QVector & VerticalStretch() const {
+ return vert_stretch_;
+ }
+
+ /// valid after calling \a calculate
+ const QVector & Transparency() const {
+ return transparency_;
+ }
+
private:
void ResetVectors();
void CalculateAnimator( const TextAnimation::Descriptor & animator);
@@ -67,6 +82,9 @@ class TextAnimationEngine
QVector vert_offsets_;
QVector rotations_;
QVector spacings_;
+ QVector horiz_stretch_;
+ QVector vert_stretch_;
+ QVector transparency_;
};
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index 6f9d5e313a..449f075989 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -55,13 +55,18 @@ TextAnimationNode::TextAnimationNode()
<< tr("Horizontal Position")
<< tr("Rotation")
<< tr("Spacing")
+ << tr("Vertical Stretch")
+ << tr("Horizontal Stretch")
+ << tr("Transparency")
);
- AddInput( kIndexFromInput, NodeValue::kInt, 0);
- SetInputProperty( kIndexFromInput, QStringLiteral("min"), 0);
+ // index of first character. Should be int, but float is used to allow key-framing over time
+ AddInput( kIndexFromInput, NodeValue::kFloat, 0.);
+ SetInputProperty( kIndexFromInput, QStringLiteral("min"), 0.);
- AddInput( kIndexToInput, NodeValue::kInt, -1); // default to -1 to animate the full text
- SetInputProperty( kIndexToInput, QStringLiteral("min"), -1);
+ // index of last character. Should be int, but float is used to allow key-framing over time
+ AddInput( kIndexToInput, NodeValue::kFloat, -1.); // default to negative value to animate the full text
+ SetInputProperty( kIndexToInput, QStringLiteral("min"), -1.);
AddInput( kOverlapInInput, NodeValue::kFloat, 1.);
SetInputProperty( kOverlapInInput, QStringLiteral("min"), 0.);
@@ -93,6 +98,7 @@ TextAnimationNode::TextAnimationNode()
SetInputProperty( kAlphaInput, QStringLiteral("min"), 0.);
SetInputProperty( kAlphaInput, QStringLiteral("max"), 1.);
+ SetEffectInput(kCompositeInput);
}
void TextAnimationNode::Retranslate()
@@ -101,8 +107,8 @@ void TextAnimationNode::Retranslate()
SetInputName( kCompositeInput, QStringLiteral("Composite"));
SetInputName( kFeatureInput, QStringLiteral("Feature"));
- SetInputName( kIndexFromInput, QStringLiteral("From"));
- SetInputName( kIndexToInput, QStringLiteral("To"));
+ SetInputName( kIndexFromInput, QStringLiteral("Index from"));
+ SetInputName( kIndexToInput, QStringLiteral("Index to"));
SetInputName( kOverlapInInput, QStringLiteral("Overlap IN"));
SetInputName( kOverlapOutInput, QStringLiteral("Overlap OUT"));
SetInputName( kCurveInput, QStringLiteral("Curve"));
@@ -125,8 +131,8 @@ void TextAnimationNode::Value(const NodeValueRow &value, const NodeGlobals & /*g
TextAnimation::Descriptor animation;
animation.feature = (TextAnimation::Feature) (job.Get(kFeatureInput).toInt());
- animation.character_from = job.Get(kIndexFromInput).toInt();
- animation.character_to = job.Get(kIndexToInput).toInt();;
+ animation.character_from = (int)(job.Get(kIndexFromInput).toDouble());
+ animation.character_to = (int)(job.Get(kIndexToInput).toInt());
animation.overlap_in = job.Get(kOverlapInInput).toDouble ();
animation.overlap_out = job.Get(kOverlapOutInput).toDouble ();
animation.curve = (TextAnimation::Curve) (job.Get(kCurveInput).toInt());
diff --git a/app/node/generator/animation/textanimationrendernode.cpp b/app/node/generator/animation/textanimationrendernode.cpp
index 135460f88d..99700c4e24 100644
--- a/app/node/generator/animation/textanimationrendernode.cpp
+++ b/app/node/generator/animation/textanimationrendernode.cpp
@@ -29,34 +29,26 @@
#include "common/cpuoptimize.h"
#include "common/html.h"
#include "node/generator/animation/textanimationxmlparser.h"
+#include "node/generator/text/textv3.h"
+
namespace olive {
-const QString TextAnimationRenderNode::kPositionInput = QStringLiteral("pos_in");
const QString TextAnimationRenderNode::kRichTextInput = QStringLiteral("rich_text_in");
const QString TextAnimationRenderNode::kAnimatorsInput = QStringLiteral("animators_in");
-const QString TextAnimationRenderNode::kLineHeightInput = QStringLiteral("line_height_in");
+const QString kCurrentTime = QStringLiteral("time_now");
#define super Node
-TextAnimationRenderNode::TextAnimationRenderNode() :
- position_handle_(nullptr)
+TextAnimationRenderNode::TextAnimationRenderNode()
{
- AddInput(kPositionInput, NodeValue::kVec2, QVector2D(200, 200));
AddInput( kRichTextInput, NodeValue::kText,
QStringLiteral("%1
").arg(tr("Sample Text")),
InputFlags(kInputFlagNotKeyframable));
AddInput( kAnimatorsInput, NodeValue::kText,
QStringLiteral(""),
InputFlags(kInputFlagNotKeyframable));
- AddInput( kLineHeightInput, NodeValue::kInt, 50, InputFlags(kInputFlagNotKeyframable));
-
- position_handle_ = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0),
- NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1)});
-
- position_handle_->SetShape( PointGizmo::kAnchorPoint);
- position_handle_->SetDragValueBehavior(PointGizmo::kAbsolute);
SetEffectInput(kRichTextInput);
@@ -87,10 +79,8 @@ void TextAnimationRenderNode::Retranslate()
{
super::Retranslate();
- SetInputName(kPositionInput, tr("Position"));
SetInputName(kRichTextInput, tr("Text"));
SetInputName(kAnimatorsInput, tr("Animators"));
- SetInputName(kLineHeightInput, tr("Line Height"));
}
GenerateJob TextAnimationRenderNode::GetGenerateJob(const NodeValueRow &value) const
@@ -99,14 +89,15 @@ GenerateJob TextAnimationRenderNode::GetGenerateJob(const NodeValueRow &value) c
job.Insert(value);
job.SetRequestedFormat(VideoParams::kFormatUnsigned8);
- job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn);
return job;
}
-void TextAnimationRenderNode::Value(const NodeValueRow &value, const NodeGlobals & /*globals*/, NodeValueTable *table) const
+void TextAnimationRenderNode::Value(const NodeValueRow &value, const NodeGlobals & globals, NodeValueTable *table) const
{
GenerateJob job = GetGenerateJob(value);
+ // We store here the current time that will be used in 'GenerateFrame'
+ job.Insert(kCurrentTime, NodeValue(NodeValue::kRational, globals.time().in(), this));
table->Push(NodeValue::kTexture, job, this);
}
@@ -118,6 +109,9 @@ void TextAnimationRenderNode::GenerateFrame(FramePtr frame, const GenerateJob &j
frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
img.fill(Qt::transparent);
+ Node * textConnectedNode = GetConnectedOutput( kRichTextInput);
+ rational time = job.Get(kCurrentTime).toRational();
+
// 96 DPI in DPM (96 / 2.54 * 100)
const int dpm = 3780;
img.setDotsPerMeterX(dpm);
@@ -126,7 +120,11 @@ void TextAnimationRenderNode::GenerateFrame(FramePtr frame, const GenerateJob &j
QTextDocument text_doc;
text_doc.setDefaultFont(QFont("Arial", 50));
+ // default value if no "Text" node is connected
QString html = job.Get(kRichTextInput).toString();
+ if (textConnectedNode != nullptr) {
+ html = textConnectedNode->GetValueAtTime( TextGeneratorV3::kTextInput, time).toString();
+ }
Html::HtmlToDoc(&text_doc, html);
QTextCursor cursor( &text_doc);
@@ -134,18 +132,19 @@ void TextAnimationRenderNode::GenerateFrame(FramePtr frame, const GenerateJob &j
double par = frame->video_params().pixel_aspect_ratio().toDouble();
QPointF scale_factor = QPointF(1.0 / frame->video_params().divider() / par, 1.0 / frame->video_params().divider());
-
QPainter p(&img);
p.scale( scale_factor.x(), scale_factor.y());
- QVector2D pos = job.Get(kPositionInput).toVec2();
-
- p.translate(pos.x(), pos.y());
-
- p.setBrush(Qt::NoBrush);
- p.setPen(Qt::white);
+ QVector2D pos(200.,200.);
+ if (textConnectedNode != nullptr) {
+ // Use input "Text" node to define the base position of the text
+ pos = GetConnectedOutput( kRichTextInput)->GetValueAtTime( ShapeNodeBase::kPositionInput, time).value();
+ QVector2D size = GetConnectedOutput( kRichTextInput)->GetValueAtTime( ShapeNodeBase::kSizeInput, time).value();
+ p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2);
+ p.translate(frame->video_params().width()/2, frame->video_params().height()/2);
+ }
- p.setRenderHints( QPainter::Antialiasing |
+ p.setRenderHints( QPainter::TextAntialiasing | QPainter::LosslessImageRendering |
QPainter::SmoothPixmapTransform);
int textSize = text_doc.characterCount();
@@ -164,58 +163,69 @@ void TextAnimationRenderNode::GenerateFrame(FramePtr frame, const GenerateJob &j
engine_->Calulate();
int posx = 0;
- int line = 0;
- int line_height = job.Get(kLineHeightInput).toInt();
+ int char_size = 0;
+ int line_Voffset = CalculateLineHeight(cursor);
const QVector & horiz_offsets = engine_->HorizontalOffset();
const QVector & vert_offsets = engine_->VerticalOffset();
const QVector & rotations = engine_->Rotation();
const QVector & spacings = engine_->Spacing();
+ const QVector & horiz_stretches = engine_->HorizontalStretch();
+ const QVector & vert_stretches = engine_->VerticalStretch();
+ const QVector & transparencies = engine_->Transparency();
// parse the whole text document and print every character
for (int i=0; i < textSize; i++) {
QChar ch = text_doc.characterAt(i);
- p.save();
- p.setFont( cursor.charFormat().font());
- p.setPen( cursor.charFormat().foreground().color());
- p.setBrush( cursor.charFormat().foreground());
- p.translate( QPointF(posx + horiz_offsets[i],
- line*line_height + vert_offsets[i]));
- p.rotate( rotations[i]);
- p.drawText( QPointF(0,0), ch);
- //p.resetTransform();
- p.restore();
-
- posx += QFontMetrics( cursor.charFormat().font()).horizontalAdvance(text_doc.characterAt(i)) + (int)(spacings[i]);
- cursor.movePosition( QTextCursor::Right);
-
if ((ch == QChar::LineSeparator) ||
(ch == QChar::ParagraphSeparator) )
{
- line++;
posx = 0;
+ cursor.movePosition( QTextCursor::Right);
+ line_Voffset += CalculateLineHeight(cursor);
+ }
+ else {
+
+ p.save();
+ p.setFont( cursor.charFormat().font());
+ QColor ch_color = cursor.charFormat().foreground().color();
+ // "transparencies" can be assumed to be in range 0 - 255
+ ch_color.setAlpha(255 - transparencies[i]);
+ p.setPen( ch_color);
+ p.translate( QPointF(posx + horiz_offsets[i],
+ line_Voffset + vert_offsets[i]));
+ p.rotate( rotations[i]);
+ p.scale( 1.+ horiz_stretches[i], 1.+ vert_stretches[i]);
+ p.drawText( QPointF(0,0), ch);
+ p.restore();
+
+ char_size = QFontMetrics( cursor.charFormat().font()).horizontalAdvance(text_doc.characterAt(i));
+ posx += (int)(((double)char_size*(1. + (spacings[i])))*(1. + horiz_stretches[i]));
+ cursor.movePosition( QTextCursor::Right);
}
}
}
-
-void TextAnimationRenderNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers & /*modifiers*/)
+int TextAnimationRenderNode::CalculateLineHeight(QTextCursor& start) const
{
- DraggableGizmo *gizmo = static_cast(sender());
+ QTextCursor cur(start);
+ int height = QFontMetrics(cur.charFormat().font()).lineSpacing();
+ cur.movePosition( QTextCursor::Right);
- gizmo->GetDraggers()[0].Drag( x);
- gizmo->GetDraggers()[1].Drag( y);
-}
+ while (cur.atBlockEnd() == false) {
+ int new_height = QFontMetrics(cur.charFormat().font()).lineSpacing();
-void TextAnimationRenderNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals & /*globals*/)
-{
- QVector2D position_vec = row[kPositionInput].toVec2();
- QPointF position_pt = QPointF(position_vec.x(), position_vec.y());
+ if (new_height > height) {
+ height = new_height;
+ }
+
+ cur.movePosition( QTextCursor::Right);
+ }
- // Create handles
- position_handle_->SetPoint(position_pt);
+ return height;
}
+
} // olive
diff --git a/app/node/generator/animation/textanimationrendernode.h b/app/node/generator/animation/textanimationrendernode.h
index 12b685de93..7ac7aada75 100644
--- a/app/node/generator/animation/textanimationrendernode.h
+++ b/app/node/generator/animation/textanimationrendernode.h
@@ -28,6 +28,9 @@
#include "node/gizmo/point.h"
#include "node/generator/animation/textanimationengine.h"
+class QTextCursor;
+
+
namespace olive {
class TextAnimationRenderNode : public Node
@@ -49,22 +52,18 @@ class TextAnimationRenderNode : public Node
virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
- virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override;
- static const QString kPositionInput;
static const QString kRichTextInput;
static const QString kAnimatorsInput;
- static const QString kLineHeightInput;
protected:
GenerateJob GetGenerateJob(const NodeValueRow &value) const;
-protected slots:
- virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override;
+private:
+ int CalculateLineHeight(QTextCursor& start) const;
private:
TextAnimationEngine * engine_;
- PointGizmo * position_handle_;
};
}
diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp
index e80b758a2f..958bf94db7 100644
--- a/app/node/generator/text/textv3.cpp
+++ b/app/node/generator/text/textv3.cpp
@@ -42,8 +42,6 @@ const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in");
const QString TextGeneratorV3::kVerticalAlignmentInput = QStringLiteral("valign_in");
const QString TextGeneratorV3::kUseArgsInput = QStringLiteral("use_args_in");
const QString TextGeneratorV3::kArgsInput = QStringLiteral("args_in");
-const QString TextGeneratorV3::kOutputHtmlOnly = QStringLiteral("html_only_in");
-
TextGeneratorV3::TextGeneratorV3() :
ShapeNodeBase(false)
@@ -51,8 +49,6 @@ TextGeneratorV3::TextGeneratorV3() :
AddInput(kTextInput, NodeValue::kText, QStringLiteral("%1
").arg(tr("Sample Text")));
SetInputProperty(kTextInput, QStringLiteral("vieweronly"), true);
- AddInput(kOutputHtmlOnly, NodeValue::kBoolean, false);
-
SetStandardValue(kSizeInput, QVector2D(400, 300));
AddInput(kVerticalAlignmentInput, NodeValue::kCombo);
@@ -96,7 +92,6 @@ void TextGeneratorV3::Retranslate()
SetInputName(kVerticalAlignmentInput, tr("Vertical Alignment"));
SetComboBoxStrings(kVerticalAlignmentInput, {tr("Top"), tr("Middle"), tr("Bottom")});
SetInputName(kArgsInput, tr("Arguments"));
- SetInputName(kOutputHtmlOnly, tr("Output HTML"));
}
void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
@@ -123,16 +118,10 @@ void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &global
// FIXME: Provide user override for this
job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
- if (job.Get(kOutputHtmlOnly).toBool() == false) {
-
- if (!job.Get(kTextInput).toString().isEmpty()) {
- PushMergableJob(value, QVariant::fromValue(job), table);
- } else if (value[kBaseInput].toTexture()) {
- table->Push(value[kBaseInput]);
- }
- }
- else {
- table->Push(job.Get(kTextInput));
+ if (!job.Get(kTextInput).toString().isEmpty()) {
+ PushMergableJob(value, QVariant::fromValue(job), table);
+ } else if (value[kBaseInput].toTexture()) {
+ table->Push(value[kBaseInput]);
}
}
diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h
index e78831dc4d..a815fbb433 100644
--- a/app/node/generator/text/textv3.h
+++ b/app/node/generator/text/textv3.h
@@ -55,11 +55,9 @@ class TextGeneratorV3 : public ShapeNodeBase
};
static const QString kTextInput;
-
static const QString kVerticalAlignmentInput;
static const QString kUseArgsInput;
static const QString kArgsInput;
- static const QString kOutputHtmlOnly;
static QString FormatString(const QString &input, const QStringList &args);
From f39dee52f842435a21dea2c63d9663e406f8c4a7 Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Sun, 31 Jul 2022 23:08:06 +0200
Subject: [PATCH 05/11] Added more flexibility to effects
---
app/node/generator/animation/textanimation.h | 3 ++
.../animation/textanimationengine.cpp | 5 ++-
.../generator/animation/textanimationnode.cpp | 45 +++++++++++--------
.../generator/animation/textanimationnode.h | 2 +
.../animation/textanimationrendernode.cpp | 1 +
.../animation/textanimationxmlformatter.cpp | 1 +
.../animation/textanimationxmlparser.cpp | 1 +
7 files changed, 39 insertions(+), 19 deletions(-)
diff --git a/app/node/generator/animation/textanimation.h b/app/node/generator/animation/textanimation.h
index b4c6a60642..b33913503a 100644
--- a/app/node/generator/animation/textanimation.h
+++ b/app/node/generator/animation/textanimation.h
@@ -82,6 +82,9 @@ struct Descriptor {
int character_from;
// last character to be animated. Use -1 to indicate the end of the text
int character_to;
+ // When 0 (or negative), the effect is applied to all characetrs.
+ // When 1 or more, 'stride' items are skipped for evry one that is applied.
+ int stride;
// range [0,1]. The lower this value, the more all characters start
// their animation together
double overlap_in;
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index 0b27a1bdcd..83953c3c41 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -137,6 +137,9 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
int index = animator.character_from;
last_index_ = CalculateEndIndex( animator);
+ // avoid infinite loop if 'stride' is illegal
+ int step = (animator.stride < 0) ? 1 : (1 + animator.stride);
+
while (index <= last_index_) {
double anim_value = CalculateAnimatorForChar( animator, index);
@@ -170,7 +173,7 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
break;
}
- index++;
+ index += step;
}
}
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index 449f075989..daa5cd0be1 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -30,6 +30,7 @@ const QString TextAnimationNode::kCompositeOutput = QStringLiteral("composite_ou
const QString TextAnimationNode::kFeatureInput = QStringLiteral("feature_in");
const QString TextAnimationNode::kIndexFromInput = QStringLiteral("from_in");
const QString TextAnimationNode::kIndexToInput = QStringLiteral("to_in");
+const QString TextAnimationNode::kStrideInput = QStringLiteral("stride_in");
const QString TextAnimationNode::kOverlapInInput = QStringLiteral("overlap_in_in");
const QString TextAnimationNode::kOverlapOutInput = QStringLiteral("overlap_out_in");
const QString TextAnimationNode::kCurveInput = QStringLiteral("curve_in");
@@ -68,6 +69,12 @@ TextAnimationNode::TextAnimationNode()
AddInput( kIndexToInput, NodeValue::kFloat, -1.); // default to negative value to animate the full text
SetInputProperty( kIndexToInput, QStringLiteral("min"), -1.);
+ // when 0, effect is applied to all characters. When greater then 0, this number of characters is skipped
+ // for each one for which the effect is applied
+ AddInput( kStrideInput, NodeValue::kFloat, 0.);
+ SetInputProperty( kStrideInput, QStringLiteral("min"), 0.);
+
+
AddInput( kOverlapInInput, NodeValue::kFloat, 1.);
SetInputProperty( kOverlapInInput, QStringLiteral("min"), 0.);
SetInputProperty( kOverlapInInput, QStringLiteral("max"), 1.);
@@ -76,7 +83,7 @@ TextAnimationNode::TextAnimationNode()
SetInputProperty( kOverlapOutInput, QStringLiteral("min"), 0.);
SetInputProperty( kOverlapOutInput, QStringLiteral("max"), 1.);
- AddInput( kCurveInput, NodeValue::kCombo, 0);
+ AddInput( kCurveInput, NodeValue::kCombo, 1);
// keep order defined in TextAnimation::Curve, not the one in QMaps
SetComboBoxStrings( kCurveInput, QStringList()
<< tr("Step")
@@ -98,24 +105,25 @@ TextAnimationNode::TextAnimationNode()
SetInputProperty( kAlphaInput, QStringLiteral("min"), 0.);
SetInputProperty( kAlphaInput, QStringLiteral("max"), 1.);
- SetEffectInput(kCompositeInput);
+ SetEffectInput(kCompositeInput);
}
void TextAnimationNode::Retranslate()
{
super::Retranslate();
- SetInputName( kCompositeInput, QStringLiteral("Composite"));
- SetInputName( kFeatureInput, QStringLiteral("Feature"));
- SetInputName( kIndexFromInput, QStringLiteral("Index from"));
- SetInputName( kIndexToInput, QStringLiteral("Index to"));
- SetInputName( kOverlapInInput, QStringLiteral("Overlap IN"));
- SetInputName( kOverlapOutInput, QStringLiteral("Overlap OUT"));
- SetInputName( kCurveInput, QStringLiteral("Curve"));
- SetInputName( kC1Input, QStringLiteral("C1"));
- SetInputName( kC2Input, QStringLiteral("C2"));
- SetInputName( kValueInput, QStringLiteral("Value"));
- SetInputName( kAlphaInput, QStringLiteral("Alpha"));
+ SetInputName( kCompositeInput, tr("Composite"));
+ SetInputName( kFeatureInput, tr("Feature"));
+ SetInputName( kIndexFromInput, tr("Index from"));
+ SetInputName( kIndexToInput, tr("Index to"));
+ SetInputName( kStrideInput, tr("Stride"));
+ SetInputName( kOverlapInInput, tr("Overlap IN"));
+ SetInputName( kOverlapOutInput, tr("Overlap OUT"));
+ SetInputName( kCurveInput, tr("Curve"));
+ SetInputName( kC1Input, tr("C1"));
+ SetInputName( kC2Input, tr("C2"));
+ SetInputName( kValueInput, tr("Value"));
+ SetInputName( kAlphaInput, tr("Alpha"));
}
@@ -132,14 +140,15 @@ void TextAnimationNode::Value(const NodeValueRow &value, const NodeGlobals & /*g
animation.feature = (TextAnimation::Feature) (job.Get(kFeatureInput).toInt());
animation.character_from = (int)(job.Get(kIndexFromInput).toDouble());
- animation.character_to = (int)(job.Get(kIndexToInput).toInt());
+ animation.character_to = (int)(job.Get(kIndexToInput).toDouble());
+ animation.stride = (int)(job.Get(kStrideInput).toDouble());
animation.overlap_in = job.Get(kOverlapInInput).toDouble ();
animation.overlap_out = job.Get(kOverlapOutInput).toDouble ();
animation.curve = (TextAnimation::Curve) (job.Get(kCurveInput).toInt());
- animation.c1 = job.Get(kC1Input).toDouble ();;
- animation.c2 = job.Get(kC2Input).toDouble ();;
- animation.value = job.Get(kValueInput).toDouble ();;
- animation.alpha = job.Get(kAlphaInput).toDouble ();;
+ animation.c1 = job.Get(kC1Input).toDouble ();
+ animation.c2 = job.Get(kC2Input).toDouble ();
+ animation.value = job.Get(kValueInput).toDouble ();
+ animation.alpha = job.Get(kAlphaInput).toDouble ();
TextAnimationXmlFormatter formatter;
formatter.SetAnimationData( & animation);
diff --git a/app/node/generator/animation/textanimationnode.h b/app/node/generator/animation/textanimationnode.h
index 3dac7a8a3c..53148e1f4d 100644
--- a/app/node/generator/animation/textanimationnode.h
+++ b/app/node/generator/animation/textanimationnode.h
@@ -73,6 +73,8 @@ class TextAnimationNode : public Node
static const QString kIndexFromInput;
// index of last character to be animated
static const QString kIndexToInput;
+ // number of characters to be skipped for each one that is applied
+ static const QString kStrideInput;
// range [0-1]. When 0 all characters start the animation at the begin;
// When 1, each character starts the animation when the previous is over
static const QString kOverlapInInput;
diff --git a/app/node/generator/animation/textanimationrendernode.cpp b/app/node/generator/animation/textanimationrendernode.cpp
index 99700c4e24..0920ee51e5 100644
--- a/app/node/generator/animation/textanimationrendernode.cpp
+++ b/app/node/generator/animation/textanimationrendernode.cpp
@@ -50,6 +50,7 @@ TextAnimationRenderNode::TextAnimationRenderNode()
QStringLiteral(""),
InputFlags(kInputFlagNotKeyframable));
+ SetFlags(kVideoEffect);
SetEffectInput(kRichTextInput);
engine_ = new TextAnimationEngine();
diff --git a/app/node/generator/animation/textanimationxmlformatter.cpp b/app/node/generator/animation/textanimationxmlformatter.cpp
index e83cfe9c55..b03ae83ffe 100644
--- a/app/node/generator/animation/textanimationxmlformatter.cpp
+++ b/app/node/generator/animation/textanimationxmlformatter.cpp
@@ -19,6 +19,7 @@ QString TextAnimationXmlFormatter::Format() const
writer.writeAttribute( "ch_from", QString("%1").arg(descriptor_->character_from));
writer.writeAttribute( "ch_to", QString("%1").arg(descriptor_->character_to));
+ writer.writeAttribute( "stride", QString("%1").arg(descriptor_->stride));
writer.writeAttribute( "overlap_in", QString("%1").arg(descriptor_->overlap_in));
writer.writeAttribute( "overlap_out", QString("%1").arg(descriptor_->overlap_out));
writer.writeAttribute( "curve", TextAnimation::CURVE_TABLE.value(descriptor_->curve, ""));
diff --git a/app/node/generator/animation/textanimationxmlparser.cpp b/app/node/generator/animation/textanimationxmlparser.cpp
index 76de15cc9e..531848b32e 100644
--- a/app/node/generator/animation/textanimationxmlparser.cpp
+++ b/app/node/generator/animation/textanimationxmlparser.cpp
@@ -21,6 +21,7 @@ QList TextAnimationXmlParser::Parse(const QString& xm
item.feature = TextAnimation::FEATURE_TABLE_REV.value( reader.name().toString(), TextAnimation::None);
item.character_from = reader.attributes().value(QString("ch_from")).toInt();
item.character_to = reader.attributes().value(QString("ch_to")).toInt();
+ item.stride = reader.attributes().value(QString("stride")).toInt();
item.overlap_in = reader.attributes().value(QString("overlap_in")).toDouble();
item.overlap_out = reader.attributes().value(QString("overlap_out")).toDouble();
item.curve = TextAnimation::CURVE_TABLE_REV.value( reader.attributes().value("curve").toString());
From afae07404764738b6f13bd847d9e288b2705c553 Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Tue, 11 Oct 2022 23:17:38 +0200
Subject: [PATCH 06/11] Added tooltip for inputs and improved their names
---
.../generator/animation/textanimationnode.cpp | 58 ++++++++++++++++---
.../animation/textanimationrendernode.cpp | 3 +
.../nodeparamview/nodeparamviewitem.cpp | 4 ++
3 files changed, 57 insertions(+), 8 deletions(-)
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index daa5cd0be1..953279b0af 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -46,6 +46,9 @@ TextAnimationNode::TextAnimationNode()
AddInput( kCompositeInput, NodeValue::kText,
QStringLiteral(""),
InputFlags(kInputFlagNotKeyframable));
+ SetInputProperty( kCompositeInput, QString("tooltip"),
+ tr("This input is used to cascade multiple animations.
"));
+
AddInput( kCompositeOutput, NodeValue::kText, QString(""), InputFlags(kInputFlagHidden));
AddInput( kFeatureInput, NodeValue::kCombo, 1);
@@ -60,28 +63,54 @@ TextAnimationNode::TextAnimationNode()
<< tr("Horizontal Stretch")
<< tr("Transparency")
);
+ SetInputProperty( kFeatureInput, QString("tooltip"),
+ tr("The aspect of the text that is animated.
"));
// index of first character. Should be int, but float is used to allow key-framing over time
AddInput( kIndexFromInput, NodeValue::kFloat, 0.);
SetInputProperty( kIndexFromInput, QStringLiteral("min"), 0.);
+ SetInputProperty( kIndexFromInput, QString("tooltip"),
+ tr("The index of the first character to which animation applies.
"
+ "Use 0 to start from the first character.
"));
// index of last character. Should be int, but float is used to allow key-framing over time
AddInput( kIndexToInput, NodeValue::kFloat, -1.); // default to negative value to animate the full text
SetInputProperty( kIndexToInput, QStringLiteral("min"), -1.);
+ SetInputProperty( kIndexToInput, QString("tooltip"),
+ tr("The index of the last character to which animation applies.
"
+ "Use -1 for the last character of the whole text.
"));
// when 0, effect is applied to all characters. When greater then 0, this number of characters is skipped
// for each one for which the effect is applied
AddInput( kStrideInput, NodeValue::kFloat, 0.);
SetInputProperty( kStrideInput, QStringLiteral("min"), 0.);
+ SetInputProperty( kStrideInput, QString("tooltip"),
+ tr("When 0, the animation is applied to all characters defined by parameters 'from' and 'to'.
"
+ "When greater than 0, this is the number of characters that are skipped for each character "
+ "that is animated
"));
+ AddInput( kValueInput, NodeValue::kFloat, 10.);
+ SetInputProperty( kValueInput, QString("tooltip"),
+ tr("The amount of the feature that is animated when the animation begins,"
+ "i.e. when 'progress' is 0'.
"));
AddInput( kOverlapInInput, NodeValue::kFloat, 1.);
SetInputProperty( kOverlapInInput, QStringLiteral("min"), 0.);
SetInputProperty( kOverlapInInput, QStringLiteral("max"), 1.);
+ SetInputProperty( kOverlapInInput, QString("tooltip"),
+ tr("Range: 0-1
"
+ "This controls when each charater starts the animation. "
+ "When 0, all characters start the animation at the same time; "
+ "when 1, the begin of the animation for each character is distributed over time.
"));
AddInput( kOverlapOutInput, NodeValue::kFloat, 1.);
SetInputProperty( kOverlapOutInput, QStringLiteral("min"), 0.);
SetInputProperty( kOverlapOutInput, QStringLiteral("max"), 1.);
+ SetInputProperty( kOverlapOutInput, QString("tooltip"),
+ tr("Range: 0-1
"
+ "This controls when each charater ends the animation. "
+ "When 0, all characters end the animation at the same time; "
+ "when 1, the end of the animation for each character is distributed over time.
"));
AddInput( kCurveInput, NodeValue::kCombo, 1);
// keep order defined in TextAnimation::Curve, not the one in QMaps
@@ -94,16 +123,29 @@ TextAnimationNode::TextAnimationNode()
<< tr("Ease_Elastic")
<< tr("Ease_Bounce")
);
+ SetInputProperty( kCurveInput, QString("tooltip"),
+ tr("The shape of the curve used for animation.
"));
AddInput( kC1Input, NodeValue::kFloat, 1.0);
+ SetInputProperty( kC1Input, QString("tooltip"),
+ tr("A coefficient that modifies the animation curve.
"
+ "According to the kind of curve, this may or may not affect the curve. "
+ "Value 1 is always a good defualt value.
"));
AddInput( kC2Input, NodeValue::kFloat, 1.0);
-
- AddInput( kValueInput, NodeValue::kFloat, 10.);
+ SetInputProperty( kC2Input, QString("tooltip"),
+ tr("A coefficient that modifies the animation curve.
"
+ "According to the kind of curve, this may or may not affect the curve. "
+ "Value 1 is always a good defualt value.
"));
AddInput( kAlphaInput, NodeValue::kFloat, 0.);
SetInputProperty( kAlphaInput, QStringLiteral("min"), 0.);
SetInputProperty( kAlphaInput, QStringLiteral("max"), 1.);
+ SetInputProperty( kAlphaInput, QString("tooltip"),
+ tr("Range: 0-1
"
+ "The progress of the animation.
This input must be animated by keyframes from "
+ "0 to 1 to let animation happen. When 0, the animation is not started; when 1,"
+ "the animation is complete
"));
SetEffectInput(kCompositeInput);
}
@@ -112,18 +154,18 @@ void TextAnimationNode::Retranslate()
{
super::Retranslate();
- SetInputName( kCompositeInput, tr("Composite"));
+ SetInputName( kCompositeInput, tr("Animators"));
SetInputName( kFeatureInput, tr("Feature"));
- SetInputName( kIndexFromInput, tr("Index from"));
- SetInputName( kIndexToInput, tr("Index to"));
+ SetInputName( kIndexFromInput, tr("First character"));
+ SetInputName( kIndexToInput, tr("Last character"));
SetInputName( kStrideInput, tr("Stride"));
- SetInputName( kOverlapInInput, tr("Overlap IN"));
- SetInputName( kOverlapOutInput, tr("Overlap OUT"));
+ SetInputName( kOverlapInInput, tr("Start timing"));
+ SetInputName( kOverlapOutInput, tr("End timing"));
SetInputName( kCurveInput, tr("Curve"));
SetInputName( kC1Input, tr("C1"));
SetInputName( kC2Input, tr("C2"));
SetInputName( kValueInput, tr("Value"));
- SetInputName( kAlphaInput, tr("Alpha"));
+ SetInputName( kAlphaInput, tr("Progress"));
}
diff --git a/app/node/generator/animation/textanimationrendernode.cpp b/app/node/generator/animation/textanimationrendernode.cpp
index 0920ee51e5..2023e0c616 100644
--- a/app/node/generator/animation/textanimationrendernode.cpp
+++ b/app/node/generator/animation/textanimationrendernode.cpp
@@ -28,6 +28,7 @@
#include "common/cpuoptimize.h"
#include "common/html.h"
+#include "node/project/project.h"
#include "node/generator/animation/textanimationxmlparser.h"
#include "node/generator/text/textv3.h"
@@ -97,6 +98,8 @@ GenerateJob TextAnimationRenderNode::GetGenerateJob(const NodeValueRow &value) c
void TextAnimationRenderNode::Value(const NodeValueRow &value, const NodeGlobals & globals, NodeValueTable *table) const
{
GenerateJob job = GetGenerateJob(value);
+ job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
+
// We store here the current time that will be used in 'GenerateFrame'
job.Insert(kCurrentTime, NodeValue(NodeValue::kRational, globals.time().in(), this));
diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp
index 7b0e82fd3a..f3c915705b 100644
--- a/app/widget/nodeparamview/nodeparamviewitem.cpp
+++ b/app/widget/nodeparamview/nodeparamviewitem.cpp
@@ -198,6 +198,10 @@ void NodeParamViewItemBody::CreateWidgets(QGridLayout* layout, Node *node, const
// Add descriptor label
ui_objects.main_label = new QLabel();
+ if (node->HasInputProperty( input, "tooltip")) {
+ ui_objects.main_label->setToolTip( node->GetInputProperty( input, "tooltip").toString());
+ }
+
// Create input label
layout->addWidget(ui_objects.main_label, row, kLabelColumn);
From e3af12402b26acff84b6f4d25331c5e97737ac90 Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Sun, 16 Oct 2022 20:04:51 +0200
Subject: [PATCH 07/11] Draw non-animated text when text gizmo is activated
---
app/node/generator/animation/textanimation.h | 2 +-
.../animation/textanimationengine.cpp | 10 +-
.../generator/animation/textanimationnode.cpp | 20 ++--
.../generator/animation/textanimationnode.h | 2 +-
.../animation/textanimationxmlformatter.cpp | 2 +-
.../animation/textanimationxmlparser.cpp | 2 +-
app/node/generator/text/textv3.cpp | 111 +++++++++++++++++-
app/node/generator/text/textv3.h | 12 ++
8 files changed, 141 insertions(+), 20 deletions(-)
diff --git a/app/node/generator/animation/textanimation.h b/app/node/generator/animation/textanimation.h
index b33913503a..d441fa4525 100644
--- a/app/node/generator/animation/textanimation.h
+++ b/app/node/generator/animation/textanimation.h
@@ -102,7 +102,7 @@ struct Descriptor {
double value;
// Evolution of the transition in range [0,1]. When 0, the animated value is equal to 'value';
// When 1, the animated value is equal to 0.
- double alpha;
+ double progress;
};
} // TextAnimation
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index 83953c3c41..3290506506 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -198,8 +198,8 @@ int TextAnimationEngine::CalculateEndIndex( const TextAnimation::Descriptor &ani
// When 'overlap_in' and 'overlap_out' are 1, each character perform its transition in one of this slice.
// By lowering 'overlap_in', the transitions begin earlier (when overlap_in=0 they all start at the begin).
// By lowering 'overlap_out', the transitions end earlier (when overlap_out=1 they all finish at the end).
-// The field 'alpha' of parameter 'animator' identifies the evolution of the transition: when alpha = 0,
-// the transition is not started; when alpha = 1, the transition is complete.
+// The field 'progress' of parameter 'animator' identifies the evolution of the transition: when progress = 0,
+// the transition is not started; when progress = 1, the transition is complete.
double TextAnimationEngine::CalculateAnimatorForChar( const TextAnimation::Descriptor &animator, int char_index)
{
int num_chars = last_index_ - animator.character_from + 1;
@@ -213,10 +213,10 @@ double TextAnimationEngine::CalculateAnimatorForChar( const TextAnimation::Descr
double low = i*dt*(animator.overlap_in);
double hi = (i+1.)*dt*(animator.overlap_out) + (1. - (animator.overlap_out));
- if (animator.alpha <= (low)) {
+ if (animator.progress <= (low)) {
animation_value = 1.0;
- } else if (animator.alpha < hi) {
- double x = (animator.alpha - low)/(hi-low);
+ } else if (animator.progress < hi) {
+ double x = (animator.progress - low)/(hi-low);
animation_value = ANIMATION_TABLE.value( animator.curve)( x, animator.c1, animator.c2);
}
else {
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index 953279b0af..c7200f2206 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -37,7 +37,7 @@ const QString TextAnimationNode::kCurveInput = QStringLiteral("curve_in");
const QString TextAnimationNode::kC1Input = QStringLiteral("c1_in");
const QString TextAnimationNode::kC2Input = QStringLiteral("c2_in");
const QString TextAnimationNode::kValueInput = QStringLiteral("fvalue_in");
-const QString TextAnimationNode::kAlphaInput = QStringLiteral("alpha_in");
+const QString TextAnimationNode::kProgressInput = QStringLiteral("progress_in");
#define super Node
@@ -89,12 +89,12 @@ TextAnimationNode::TextAnimationNode()
"When greater than 0, this is the number of characters that are skipped for each character "
"that is animated
"));
- AddInput( kValueInput, NodeValue::kFloat, 10.);
+ AddInput( kValueInput, NodeValue::kFloat, 250.);
SetInputProperty( kValueInput, QString("tooltip"),
tr("The amount of the feature that is animated when the animation begins,"
"i.e. when 'progress' is 0'.
"));
- AddInput( kOverlapInInput, NodeValue::kFloat, 1.);
+ AddInput( kOverlapInInput, NodeValue::kFloat, 0.5);
SetInputProperty( kOverlapInInput, QStringLiteral("min"), 0.);
SetInputProperty( kOverlapInInput, QStringLiteral("max"), 1.);
SetInputProperty( kOverlapInInput, QString("tooltip"),
@@ -103,7 +103,7 @@ TextAnimationNode::TextAnimationNode()
"When 0, all characters start the animation at the same time; "
"when 1, the begin of the animation for each character is distributed over time.
"));
- AddInput( kOverlapOutInput, NodeValue::kFloat, 1.);
+ AddInput( kOverlapOutInput, NodeValue::kFloat, 0.5);
SetInputProperty( kOverlapOutInput, QStringLiteral("min"), 0.);
SetInputProperty( kOverlapOutInput, QStringLiteral("max"), 1.);
SetInputProperty( kOverlapOutInput, QString("tooltip"),
@@ -138,10 +138,10 @@ TextAnimationNode::TextAnimationNode()
"According to the kind of curve, this may or may not affect the curve. "
"Value 1 is always a good defualt value.
"));
- AddInput( kAlphaInput, NodeValue::kFloat, 0.);
- SetInputProperty( kAlphaInput, QStringLiteral("min"), 0.);
- SetInputProperty( kAlphaInput, QStringLiteral("max"), 1.);
- SetInputProperty( kAlphaInput, QString("tooltip"),
+ AddInput( kProgressInput, NodeValue::kFloat, 0.);
+ SetInputProperty( kProgressInput, QStringLiteral("min"), 0.);
+ SetInputProperty( kProgressInput, QStringLiteral("max"), 1.);
+ SetInputProperty( kProgressInput, QString("tooltip"),
tr("Range: 0-1
"
"The progress of the animation.
This input must be animated by keyframes from "
"0 to 1 to let animation happen. When 0, the animation is not started; when 1,"
@@ -165,7 +165,7 @@ void TextAnimationNode::Retranslate()
SetInputName( kC1Input, tr("C1"));
SetInputName( kC2Input, tr("C2"));
SetInputName( kValueInput, tr("Value"));
- SetInputName( kAlphaInput, tr("Progress"));
+ SetInputName( kProgressInput, tr("Progress"));
}
@@ -190,7 +190,7 @@ void TextAnimationNode::Value(const NodeValueRow &value, const NodeGlobals & /*g
animation.c1 = job.Get(kC1Input).toDouble ();
animation.c2 = job.Get(kC2Input).toDouble ();
animation.value = job.Get(kValueInput).toDouble ();
- animation.alpha = job.Get(kAlphaInput).toDouble ();
+ animation.progress = job.Get(kProgressInput).toDouble ();
TextAnimationXmlFormatter formatter;
formatter.SetAnimationData( & animation);
diff --git a/app/node/generator/animation/textanimationnode.h b/app/node/generator/animation/textanimationnode.h
index 53148e1f4d..197e25577c 100644
--- a/app/node/generator/animation/textanimationnode.h
+++ b/app/node/generator/animation/textanimationnode.h
@@ -91,7 +91,7 @@ class TextAnimationNode : public Node
static const QString kValueInput;
// Evolution of the transition in range [0,1]. When 0, the animated value is equal to 'value';
// When 1, the animated value is equal to 0.
- static const QString kAlphaInput;
+ static const QString kProgressInput;
};
diff --git a/app/node/generator/animation/textanimationxmlformatter.cpp b/app/node/generator/animation/textanimationxmlformatter.cpp
index b03ae83ffe..2e0b946bff 100644
--- a/app/node/generator/animation/textanimationxmlformatter.cpp
+++ b/app/node/generator/animation/textanimationxmlformatter.cpp
@@ -26,7 +26,7 @@ QString TextAnimationXmlFormatter::Format() const
writer.writeAttribute( "c1", QString("%1").arg(descriptor_->c1));
writer.writeAttribute( "c2", QString("%1").arg(descriptor_->c2));
writer.writeAttribute( "value", QString("%1").arg(descriptor_->value));
- writer.writeAttribute( "alpha", QString("%1").arg(descriptor_->alpha));
+ writer.writeAttribute( "progress", QString("%1").arg(descriptor_->progress));
writer.writeEndElement();
}
diff --git a/app/node/generator/animation/textanimationxmlparser.cpp b/app/node/generator/animation/textanimationxmlparser.cpp
index 531848b32e..45795eae64 100644
--- a/app/node/generator/animation/textanimationxmlparser.cpp
+++ b/app/node/generator/animation/textanimationxmlparser.cpp
@@ -28,7 +28,7 @@ QList TextAnimationXmlParser::Parse(const QString& xm
item.c1 = reader.attributes().value(QString("c1")).toDouble();
item.c2 = reader.attributes().value(QString("c2")).toDouble();
item.value = reader.attributes().value(QString("value")).toDouble();
- item.alpha = reader.attributes().value(QString("alpha")).toDouble();
+ item.progress = reader.attributes().value(QString("progress")).toDouble();
if (item.feature != TextAnimation::None) {
descriptors << item;
diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp
index 1964f8cb07..368035781c 100644
--- a/app/node/generator/text/textv3.cpp
+++ b/app/node/generator/text/textv3.cpp
@@ -28,6 +28,11 @@
#include "core.h"
#include "node/project/project.h"
#include "widget/nodeparamview/nodeparamviewundo.h"
+#include "node/generator/animation/textanimationxmlparser.h"
+#include "node/generator/animation/textanimationnode.h"
+
+#include // TODO_
+
namespace olive {
@@ -43,6 +48,11 @@ const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in");
const QString TextGeneratorV3::kVerticalAlignmentInput = QStringLiteral("valign_in");
const QString TextGeneratorV3::kUseArgsInput = QStringLiteral("use_args_in");
const QString TextGeneratorV3::kArgsInput = QStringLiteral("args_in");
+const QString TextGeneratorV3::kAnimatorsInput = QStringLiteral("animators_in");
+
+// TODO_ comment
+bool TextGeneratorV3::editing_;
+
TextGeneratorV3::TextGeneratorV3() :
ShapeNodeBase(false),
@@ -60,10 +70,17 @@ TextGeneratorV3::TextGeneratorV3() :
AddInput(kArgsInput, NodeValue::kText, InputFlags(kInputFlagArray));
SetInputProperty(kArgsInput, QStringLiteral("arraystart"), 1);
+ AddInput( kAnimatorsInput, NodeValue::kText,
+ QStringLiteral(""),
+ InputFlags(kInputFlagNotKeyframable));
+
text_gizmo_ = new TextGizmo(this);
text_gizmo_->SetInput(NodeInput(this, kTextInput));
connect(text_gizmo_, &TextGizmo::Activated, this, &TextGeneratorV3::GizmoActivated);
connect(text_gizmo_, &TextGizmo::Deactivated, this, &TextGeneratorV3::GizmoDeactivated);
+
+ engine_ = new TextAnimationEngine();
+ editing_ = false;
}
QString TextGeneratorV3::Name() const
@@ -94,6 +111,7 @@ void TextGeneratorV3::Retranslate()
SetInputName(kVerticalAlignmentInput, tr("Vertical Alignment"));
SetComboBoxStrings(kVerticalAlignmentInput, {tr("Top"), tr("Middle"), tr("Bottom")});
SetInputName(kArgsInput, tr("Arguments"));
+ SetInputName(kAnimatorsInput, tr("Animators"));
}
void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
@@ -173,7 +191,75 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette.setColor(QPalette::Text, Qt::white);
- text_doc.documentLayout()->draw(&p, ctx);
+ qDebug() << "editing: " << editing_;
+
+ if (editing_) {
+ text_doc.documentLayout()->draw(&p, ctx);
+ }
+ else {
+ int textSize = text_doc.characterCount();
+
+ QTextCursor cursor( &text_doc);
+ cursor.movePosition( QTextCursor::Start);
+ cursor.movePosition( QTextCursor::Right);
+
+ // 'kAnimatorsInput' holds a list of XML tags without main opening and closing tags.
+ // Prepend and append XML start and closing tags.
+ QString animators_xml = QStringLiteral("\n") +
+ job.Get(kAnimatorsInput).toString() +
+ QStringLiteral("
");
+
+ QList animators = TextAnimationXmlParser::Parse( animators_xml);
+
+ engine_->SetTextSize( textSize);
+ engine_->SetAnimators( & animators);
+ engine_->Calulate();
+
+ int posx = 0;
+ int char_size = 0;
+ int line_Voffset = CalculateLineHeight(cursor);
+
+
+ const QVector & horiz_offsets = engine_->HorizontalOffset();
+ const QVector & vert_offsets = engine_->VerticalOffset();
+ const QVector & rotations = engine_->Rotation();
+ const QVector & spacings = engine_->Spacing();
+ const QVector & horiz_stretches = engine_->HorizontalStretch();
+ const QVector & vert_stretches = engine_->VerticalStretch();
+ const QVector & transparencies = engine_->Transparency();
+
+ // parse the whole text document and print every character
+ for (int i=0; i < textSize; i++) {
+ QChar ch = text_doc.characterAt(i);
+
+ if ((ch == QChar::LineSeparator) ||
+ (ch == QChar::ParagraphSeparator) )
+ {
+ posx = 0;
+ cursor.movePosition( QTextCursor::Right);
+ line_Voffset += CalculateLineHeight(cursor);
+ }
+ else {
+
+ p.save();
+ p.setFont( cursor.charFormat().font());
+ QColor ch_color = cursor.charFormat().foreground().color();
+ // "transparencies" can be assumed to be in range 0 - 255
+ ch_color.setAlpha(255 - transparencies[i]);
+ p.setPen( ch_color);
+ p.translate( QPointF(posx + horiz_offsets[i],
+ line_Voffset + vert_offsets[i]));
+ p.rotate( rotations[i]);
+ p.scale( 1.+ horiz_stretches[i], 1.+ vert_stretches[i]);
+ p.drawText( QPointF(0,0), ch);
+ p.restore();
+
+ char_size = QFontMetrics( cursor.charFormat().font()).horizontalAdvance(text_doc.characterAt(i));
+ posx += (int)(((double)char_size*(1. + (spacings[i])))*(1. + horiz_stretches[i]));
+ cursor.movePosition( QTextCursor::Right);
+ }
+ }
+ }
}
void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals)
@@ -264,6 +350,8 @@ void TextGeneratorV3::GizmoActivated()
SetStandardValue(kUseArgsInput, false);
connect(text_gizmo_, &TextGizmo::VerticalAlignmentChanged, this, &TextGeneratorV3::SetVerticalAlignmentUndoable);
dont_emit_valign_ = true;
+
+ editing_ = true;
}
void TextGeneratorV3::GizmoDeactivated()
@@ -271,6 +359,8 @@ void TextGeneratorV3::GizmoDeactivated()
SetStandardValue(kUseArgsInput, true);
disconnect(text_gizmo_, &TextGizmo::VerticalAlignmentChanged, this, &TextGeneratorV3::SetVerticalAlignmentUndoable);
dont_emit_valign_ = true;
+
+ editing_ = false;
}
void TextGeneratorV3::SetVerticalAlignmentUndoable(Qt::Alignment a)
@@ -278,4 +368,23 @@ void TextGeneratorV3::SetVerticalAlignmentUndoable(Qt::Alignment a)
Core::instance()->undo_stack()->push(new NodeParamSetStandardValueCommand(NodeInput(this, kVerticalAlignmentInput), GetOurAlignmentFromQts(a)));
}
+int TextGeneratorV3::CalculateLineHeight(QTextCursor& start) const
+{
+ QTextCursor cur(start);
+ int height = QFontMetrics(cur.charFormat().font()).lineSpacing();
+ cur.movePosition( QTextCursor::Right);
+
+ while (cur.atBlockEnd() == false) {
+ int new_height = QFontMetrics(cur.charFormat().font()).lineSpacing();
+
+ if (new_height > height) {
+ height = new_height;
+ }
+
+ cur.movePosition( QTextCursor::Right);
+ }
+
+ return height;
+}
+
}
diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h
index b7d6a5674f..0373db2c6f 100644
--- a/app/node/generator/text/textv3.h
+++ b/app/node/generator/text/textv3.h
@@ -23,6 +23,9 @@
#include "node/generator/shape/shapenodebase.h"
#include "node/gizmo/text.h"
+#include "node/generator/animation/textanimationengine.h"
+
+class QTextCursor;
namespace olive {
@@ -66,6 +69,7 @@ class TextGeneratorV3 : public ShapeNodeBase
static const QString kVerticalAlignmentInput;
static const QString kUseArgsInput;
static const QString kArgsInput;
+ static const QString kAnimatorsInput;
static QString FormatString(const QString &input, const QStringList &args);
@@ -77,11 +81,19 @@ class TextGeneratorV3 : public ShapeNodeBase
bool dont_emit_valign_;
+ TextAnimationEngine * engine_;
+
+ // any instance of a text node is being edited
+ static bool editing_;
+
private slots:
void GizmoActivated();
void GizmoDeactivated();
void SetVerticalAlignmentUndoable(Qt::Alignment a);
+private:
+ int CalculateLineHeight(QTextCursor& start) const;
+
};
}
From 7a32d8c5edd866cb372fb79b0430257fa9d6202a Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Wed, 26 Oct 2022 22:22:23 +0200
Subject: [PATCH 08/11] Removed TextAnimationRenderNode. Animators are now
plugged into the text node.
---
app/node/factory.cpp | 3 -
app/node/factory.h | 1 -
app/node/generator/animation/CMakeLists.txt | 4 +-
.../animation/textanimationrender.cpp | 196 +++++++++++++++
.../generator/animation/textanimationrender.h | 66 +++++
.../animation/textanimationrendernode.cpp | 235 ------------------
.../animation/textanimationrendernode.h | 71 ------
app/node/generator/text/textv3.cpp | 105 ++------
app/node/generator/text/textv3.h | 9 +-
9 files changed, 282 insertions(+), 408 deletions(-)
create mode 100644 app/node/generator/animation/textanimationrender.cpp
create mode 100644 app/node/generator/animation/textanimationrender.h
delete mode 100644 app/node/generator/animation/textanimationrendernode.cpp
delete mode 100644 app/node/generator/animation/textanimationrendernode.h
diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index b7439c9344..cd6c1413ee 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -54,7 +54,6 @@
#include "generator/text/textv2.h"
#include "generator/text/textv3.h"
#include "generator/animation/textanimationnode.h"
-#include "generator/animation/textanimationrendernode.h"
#include "input/multicam/multicamnode.h"
#include "input/time/timeinput.h"
#include "input/value/valuenode.h"
@@ -253,8 +252,6 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new TextGeneratorV3();
case kTextAnimation:
return new TextAnimationNode();
- case kTextAnimationRender:
- return new TextAnimationRenderNode();
case kCrossDissolveTransition:
return new CrossDissolveTransition();
case kDipToColorTransition:
diff --git a/app/node/factory.h b/app/node/factory.h
index a8122a0900..4ebad00b88 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -52,7 +52,6 @@ class NodeFactory
kTextGeneratorV2,
kTextGeneratorV3,
kTextAnimation,
- kTextAnimationRender,
kCrossDissolveTransition,
kDipToColorTransition,
kMosaicFilter,
diff --git a/app/node/generator/animation/CMakeLists.txt b/app/node/generator/animation/CMakeLists.txt
index 4c70346ac8..aea2624a9e 100644
--- a/app/node/generator/animation/CMakeLists.txt
+++ b/app/node/generator/animation/CMakeLists.txt
@@ -25,7 +25,7 @@ set(OLIVE_SOURCES
node/generator/animation/textanimationxmlparser.h
node/generator/animation/textanimationnode.cpp
node/generator/animation/textanimationnode.h
- node/generator/animation/textanimationrendernode.cpp
- node/generator/animation/textanimationrendernode.h
+ node/generator/animation/textanimationrender.cpp
+ node/generator/animation/textanimationrender.h
PARENT_SCOPE
)
diff --git a/app/node/generator/animation/textanimationrender.cpp b/app/node/generator/animation/textanimationrender.cpp
new file mode 100644
index 0000000000..a93cb0d407
--- /dev/null
+++ b/app/node/generator/animation/textanimationrender.cpp
@@ -0,0 +1,196 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2022 Olive Team
+
+ 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 3 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, see .
+
+***/
+
+#include
+#include
+#include
+#include
+#include
+
+#include "textanimationrender.h"
+#include "node/generator/animation/textanimationxmlparser.h"
+
+#include
+namespace olive {
+
+TextAnimationRender::TextAnimationRender()
+{
+}
+
+void TextAnimationRender::render(const QString & animators_tags,
+ QTextDocument & text_doc,
+ QPainter & p)
+{
+ QTextCursor cursor( &text_doc);
+ int block_count = text_doc.blockCount();
+ // character index in full document
+ int index = 0;
+ cursor.movePosition( QTextCursor::Start);
+
+ qreal line_Voffset = text_doc.documentLayout()->blockBoundingRect(cursor.block()).bottom() -
+ maxDescent(cursor);
+
+ // calculate animation data
+ QList animators = TextAnimationXmlParser::Parse( "" + animators_tags + "");
+ engine_.SetTextSize( text_doc.characterCount());
+ engine_.SetAnimators( & animators);
+ engine_.Calulate();
+
+ const QVector & horiz_offsets = engine_.HorizontalOffset();
+ const QVector & horiz_stretches = engine_.HorizontalStretch();
+ const QVector & rotations = engine_.Rotation();
+ const QVector & spacings = engine_.Spacing();
+ const QVector & vert_offsets = engine_.VerticalOffset();
+ const QVector & vert_stretches = engine_.VerticalStretch();
+ const QVector & transparencies = engine_.Transparency();
+
+ qDebug() << vert_offsets;
+
+ // parse the whole text document and print every character
+ for (int blk=0; blk < block_count; blk++) {
+ qreal posx = 0.;
+
+ // x position of characters of current block without any animation
+ QVector base_position_x;
+ qreal total_width = 0.;
+
+ parseBlock( cursor, base_position_x, total_width);
+ posx = initialPositionX( text_doc, cursor, total_width);
+
+ /* keep the cursor on the right of the character that we're going to
+ * print because the "charFormat()" refers to previous character */
+ cursor.movePosition( QTextCursor::Right);
+
+ for( qreal & x: base_position_x) {
+ p.save();
+ p.setFont( cursor.charFormat().font());
+
+ QColor ch_color = Qt::white;
+
+ if (cursor.charFormat().foreground().style() != Qt::NoBrush) {
+ ch_color = cursor.charFormat().foreground().color();
+ }
+
+ // "transparencies" can be assumed to be in range 0 - 255
+ ch_color.setAlpha(255 - transparencies[index]);
+
+ p.setPen( ch_color);
+ p.translate( QPointF((posx + x)*(1. + horiz_offsets[index])*(1. + spacings[index]),
+ line_Voffset + vert_offsets[index]));
+ p.rotate( rotations[index]);
+ p.scale( 1.+ horiz_stretches[index], 1. + vert_stretches[index]);
+ p.drawText( QPointF(0,0), text_doc.characterAt(cursor.position() - 1));
+ p.restore();
+
+ cursor.movePosition( QTextCursor::Right);
+ index++;
+ }
+
+ line_Voffset = text_doc.documentLayout()->blockBoundingRect(cursor.block()).bottom() -
+ maxDescent(cursor);
+ }
+
+ qDebug() << "index:" << index;
+}
+
+qreal TextAnimationRender::maxDescent( QTextCursor cursor) const
+{
+ int descent = QFontMetrics(cursor.charFormat().font()).descent();
+ cursor.movePosition( QTextCursor::Right);
+
+ while (cursor.atBlockEnd() == false) {
+ int new_descent = QFontMetrics(cursor.charFormat().font()).descent();
+
+ if (new_descent > descent) {
+ descent = new_descent;
+ }
+
+ cursor.movePosition( QTextCursor::Right);
+ }
+
+ return descent;
+}
+
+qreal TextAnimationRender::initialPositionX( QTextDocument & doc,
+ QTextCursor & cur,
+ qreal total_width) const
+{
+ qreal posx = 0;
+ Qt::Alignment align = cur.blockFormat().alignment();
+ QRectF boundingRect = doc.documentLayout()->blockBoundingRect(cur.block());
+
+ if ((align == Qt::AlignLeft) || (align == Qt::AlignJustify)){
+ posx = boundingRect.left();
+ } else if (cur.blockFormat().alignment() == Qt::AlignHCenter) {
+ posx = (boundingRect.right() / 2.) - (total_width/2.);
+ } else { // align right
+ posx = boundingRect.right() - total_width;
+ }
+
+ return posx;
+}
+
+// parse the current cursor until the end of block and calculate the x position
+// of each letter in case of no animation.
+// While we are parsing, it is handy to also calculate the line descent and total text
+// width, that will be useful in case of non-left aligned text
+void TextAnimationRender::parseBlock(const QTextCursor &cursor,
+ QVector & offset_x,
+ qreal & total_width) const
+{
+ double char_width;
+
+ offset_x.clear();
+ total_width = 0.;
+
+ QTextCursor blockCursor( cursor.block());
+ /* the first char is always at offset 0. Keep the cursor on the
+ * right of the character that we're analysing
+ * because the "charFormat()" refers to previous character */
+ offset_x << 0.;
+ blockCursor.movePosition( QTextCursor::Right);
+
+ while (blockCursor.atBlockEnd() == false) {
+ char_width = currentCharWidth( blockCursor);
+ total_width += char_width;
+
+ offset_x << offset_x.last() + char_width;
+ blockCursor.movePosition( QTextCursor::Right);
+ }
+
+ /* the last character only counts for the total width, not for the offset list */
+ char_width = currentCharWidth( blockCursor);
+ total_width += char_width;
+}
+
+double TextAnimationRender::currentCharWidth( QTextCursor & cursor) const
+{
+ QChar ch = cursor.document()->characterAt( cursor.position() - 1);
+
+ qreal char_size = QFontMetrics( cursor.charFormat().font()).horizontalAdvance(ch);
+ qreal spacing = cursor.charFormat().font().letterSpacing();
+ double stretch_factor = (spacing > 0.) ?
+ spacing/100. :
+ 1.;
+
+ return (double)char_size*(stretch_factor);
+}
+
+} // olive
diff --git a/app/node/generator/animation/textanimationrender.h b/app/node/generator/animation/textanimationrender.h
new file mode 100644
index 0000000000..ae4e5faf51
--- /dev/null
+++ b/app/node/generator/animation/textanimationrender.h
@@ -0,0 +1,66 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2022 Olive Team
+
+ 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 3 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, see .
+
+***/
+
+#ifndef TEXTANIMATIONRENDER_H
+#define TEXTANIMATIONRENDER_H
+
+#include
+
+#include "node/generator/animation/textanimationengine.h"
+
+class QTextDocument;
+class QPainter;
+class QTextCursor;
+
+
+namespace olive {
+
+class TextAnimationRender
+{
+public:
+ TextAnimationRender();
+
+ /// render an animated text.
+ /// \param animators is an XML list of animators
+ void render(const QString &animators_tags,
+ QTextDocument &text_doc,
+ QPainter &p);
+
+private:
+
+ qreal maxDescent( QTextCursor cursor) const;
+
+ qreal initialPositionX( QTextDocument & doc,
+ QTextCursor & cur,
+ qreal total_width) const;
+
+ void parseBlock( const QTextCursor & cursor,
+ QVector & offset_x,
+ qreal &total_width) const;
+
+ double currentCharWidth( QTextCursor & cursor) const;
+
+private:
+ TextAnimationEngine engine_;
+};
+
+}
+
+#endif // TEXTANIMATIONRENDER_H
diff --git a/app/node/generator/animation/textanimationrendernode.cpp b/app/node/generator/animation/textanimationrendernode.cpp
deleted file mode 100644
index 2023e0c616..0000000000
--- a/app/node/generator/animation/textanimationrendernode.cpp
+++ /dev/null
@@ -1,235 +0,0 @@
-/***
-
- Olive - Non-Linear Video Editor
- Copyright (C) 2022 Olive Team
-
- 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 3 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, see .
-
-***/
-
-#include "textanimationrendernode.h"
-
-#include
-#include
-#include
-#include
-#include
-
-#include "common/cpuoptimize.h"
-#include "common/html.h"
-#include "node/project/project.h"
-#include "node/generator/animation/textanimationxmlparser.h"
-#include "node/generator/text/textv3.h"
-
-
-namespace olive {
-
-const QString TextAnimationRenderNode::kRichTextInput = QStringLiteral("rich_text_in");
-const QString TextAnimationRenderNode::kAnimatorsInput = QStringLiteral("animators_in");
-
-const QString kCurrentTime = QStringLiteral("time_now");
-
-#define super Node
-
-TextAnimationRenderNode::TextAnimationRenderNode()
-{
- AddInput( kRichTextInput, NodeValue::kText,
- QStringLiteral("%1
").arg(tr("Sample Text")),
- InputFlags(kInputFlagNotKeyframable));
- AddInput( kAnimatorsInput, NodeValue::kText,
- QStringLiteral(""),
- InputFlags(kInputFlagNotKeyframable));
-
- SetFlags(kVideoEffect);
- SetEffectInput(kRichTextInput);
-
- engine_ = new TextAnimationEngine();
-}
-
-QString TextAnimationRenderNode::Name() const
-{
- return tr("Text Animation Render");
-}
-
-QString TextAnimationRenderNode::id() const
-{
- return QStringLiteral("org.olivevideoeditor.Olive.TextAnimationRenderNode");
-}
-
-QVector TextAnimationRenderNode::Category() const
-{
- return {kCategoryGenerator};
-}
-
-QString TextAnimationRenderNode::Description() const
-{
- return tr("Takes a rich text and a set of animation descriptors to produce the text animation");
-}
-
-void TextAnimationRenderNode::Retranslate()
-{
- super::Retranslate();
-
- SetInputName(kRichTextInput, tr("Text"));
- SetInputName(kAnimatorsInput, tr("Animators"));
-}
-
-GenerateJob TextAnimationRenderNode::GetGenerateJob(const NodeValueRow &value) const
-{
- GenerateJob job;
-
- job.Insert(value);
- job.SetRequestedFormat(VideoParams::kFormatUnsigned8);
-
- return job;
-}
-
-void TextAnimationRenderNode::Value(const NodeValueRow &value, const NodeGlobals & globals, NodeValueTable *table) const
-{
- GenerateJob job = GetGenerateJob(value);
- job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace());
-
- // We store here the current time that will be used in 'GenerateFrame'
- job.Insert(kCurrentTime, NodeValue(NodeValue::kRational, globals.time().in(), this));
-
- table->Push(NodeValue::kTexture, job, this);
-}
-
-
-void TextAnimationRenderNode::GenerateFrame(FramePtr frame, const GenerateJob &job) const
-{
- QImage img(reinterpret_cast(frame->data()), frame->width(), frame->height(),
- frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied);
- img.fill(Qt::transparent);
-
- Node * textConnectedNode = GetConnectedOutput( kRichTextInput);
- rational time = job.Get(kCurrentTime).toRational();
-
- // 96 DPI in DPM (96 / 2.54 * 100)
- const int dpm = 3780;
- img.setDotsPerMeterX(dpm);
- img.setDotsPerMeterY(dpm);
-
- QTextDocument text_doc;
- text_doc.setDefaultFont(QFont("Arial", 50));
-
- // default value if no "Text" node is connected
- QString html = job.Get(kRichTextInput).toString();
- if (textConnectedNode != nullptr) {
- html = textConnectedNode->GetValueAtTime( TextGeneratorV3::kTextInput, time).toString();
- }
- Html::HtmlToDoc(&text_doc, html);
- QTextCursor cursor( &text_doc);
-
- // aspect ratio
- double par = frame->video_params().pixel_aspect_ratio().toDouble();
- QPointF scale_factor = QPointF(1.0 / frame->video_params().divider() / par, 1.0 / frame->video_params().divider());
-
- QPainter p(&img);
- p.scale( scale_factor.x(), scale_factor.y());
-
- QVector2D pos(200.,200.);
- if (textConnectedNode != nullptr) {
- // Use input "Text" node to define the base position of the text
- pos = GetConnectedOutput( kRichTextInput)->GetValueAtTime( ShapeNodeBase::kPositionInput, time).value();
- QVector2D size = GetConnectedOutput( kRichTextInput)->GetValueAtTime( ShapeNodeBase::kSizeInput, time).value();
- p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2);
- p.translate(frame->video_params().width()/2, frame->video_params().height()/2);
- }
-
- p.setRenderHints( QPainter::TextAntialiasing | QPainter::LosslessImageRendering |
- QPainter::SmoothPixmapTransform);
-
- int textSize = text_doc.characterCount();
- cursor.movePosition( QTextCursor::Start);
- cursor.movePosition( QTextCursor::Right);
-
- // 'kAnimatorsInput' holds a list of XML tags without main opening and closing tags.
- // Prepend and append XML start and closing tags.
- QString animators_xml = QStringLiteral("\n") + job.Get(kAnimatorsInput).toString() +
- QStringLiteral("
");
-
- QList animators = TextAnimationXmlParser::Parse( animators_xml);
-
- engine_->SetTextSize( textSize);
- engine_->SetAnimators( & animators);
- engine_->Calulate();
-
- int posx = 0;
- int char_size = 0;
- int line_Voffset = CalculateLineHeight(cursor);
-
-
- const QVector & horiz_offsets = engine_->HorizontalOffset();
- const QVector & vert_offsets = engine_->VerticalOffset();
- const QVector & rotations = engine_->Rotation();
- const QVector & spacings = engine_->Spacing();
- const QVector & horiz_stretches = engine_->HorizontalStretch();
- const QVector & vert_stretches = engine_->VerticalStretch();
- const QVector & transparencies = engine_->Transparency();
-
- // parse the whole text document and print every character
- for (int i=0; i < textSize; i++) {
- QChar ch = text_doc.characterAt(i);
-
- if ((ch == QChar::LineSeparator) ||
- (ch == QChar::ParagraphSeparator) )
- {
- posx = 0;
- cursor.movePosition( QTextCursor::Right);
- line_Voffset += CalculateLineHeight(cursor);
- }
- else {
-
- p.save();
- p.setFont( cursor.charFormat().font());
- QColor ch_color = cursor.charFormat().foreground().color();
- // "transparencies" can be assumed to be in range 0 - 255
- ch_color.setAlpha(255 - transparencies[i]);
- p.setPen( ch_color);
- p.translate( QPointF(posx + horiz_offsets[i],
- line_Voffset + vert_offsets[i]));
- p.rotate( rotations[i]);
- p.scale( 1.+ horiz_stretches[i], 1.+ vert_stretches[i]);
- p.drawText( QPointF(0,0), ch);
- p.restore();
-
- char_size = QFontMetrics( cursor.charFormat().font()).horizontalAdvance(text_doc.characterAt(i));
- posx += (int)(((double)char_size*(1. + (spacings[i])))*(1. + horiz_stretches[i]));
- cursor.movePosition( QTextCursor::Right);
- }
- }
-}
-
-int TextAnimationRenderNode::CalculateLineHeight(QTextCursor& start) const
-{
- QTextCursor cur(start);
- int height = QFontMetrics(cur.charFormat().font()).lineSpacing();
- cur.movePosition( QTextCursor::Right);
-
- while (cur.atBlockEnd() == false) {
- int new_height = QFontMetrics(cur.charFormat().font()).lineSpacing();
-
- if (new_height > height) {
- height = new_height;
- }
-
- cur.movePosition( QTextCursor::Right);
- }
-
- return height;
-}
-
-
-} // olive
diff --git a/app/node/generator/animation/textanimationrendernode.h b/app/node/generator/animation/textanimationrendernode.h
deleted file mode 100644
index 7ac7aada75..0000000000
--- a/app/node/generator/animation/textanimationrendernode.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/***
-
- Olive - Non-Linear Video Editor
- Copyright (C) 2022 Olive Team
-
- 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 3 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, see .
-
-***/
-
-#ifndef TEXTANIMATIONRENDERNODE_H
-#define TEXTANIMATIONRENDERNODE_H
-
-#include
-
-#include "common/bezier.h"
-#include "node/node.h"
-#include "node/gizmo/point.h"
-#include "node/generator/animation/textanimationengine.h"
-
-class QTextCursor;
-
-
-namespace olive {
-
-class TextAnimationRenderNode : public Node
-{
- Q_OBJECT
-public:
- TextAnimationRenderNode();
-
- NODE_DEFAULT_FUNCTIONS(TextAnimationRenderNode)
-
- virtual QString Name() const override;
- virtual QString id() const override;
- virtual QVector Category() const override;
- virtual QString Description() const override;
-
- virtual void Retranslate() override;
-
- virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
-
- virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override;
-
-
- static const QString kRichTextInput;
- static const QString kAnimatorsInput;
-
-protected:
- GenerateJob GetGenerateJob(const NodeValueRow &value) const;
-
-private:
- int CalculateLineHeight(QTextCursor& start) const;
-
-private:
- TextAnimationEngine * engine_;
-};
-
-}
-
-#endif // TEXTANIMATIONRENDERNODE_H
diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp
index 368035781c..fe70caa2ed 100644
--- a/app/node/generator/text/textv3.cpp
+++ b/app/node/generator/text/textv3.cpp
@@ -28,10 +28,7 @@
#include "core.h"
#include "node/project/project.h"
#include "widget/nodeparamview/nodeparamviewundo.h"
-#include "node/generator/animation/textanimationxmlparser.h"
-#include "node/generator/animation/textanimationnode.h"
-
-#include // TODO_
+#include "node/generator/animation/textanimationrender.h"
namespace olive {
@@ -50,7 +47,9 @@ const QString TextGeneratorV3::kUseArgsInput = QStringLiteral("use_args_in");
const QString TextGeneratorV3::kArgsInput = QStringLiteral("args_in");
const QString TextGeneratorV3::kAnimatorsInput = QStringLiteral("animators_in");
-// TODO_ comment
+// We need to make this variable static because it is set in 'GizmoActivated' and used in 'GenerateFrame'.
+// These methods are not called from the same instance, but we assume that there is only one text that
+// is currently edited.
bool TextGeneratorV3::editing_;
@@ -79,7 +78,6 @@ TextGeneratorV3::TextGeneratorV3() :
connect(text_gizmo_, &TextGizmo::Activated, this, &TextGeneratorV3::GizmoActivated);
connect(text_gizmo_, &TextGizmo::Deactivated, this, &TextGeneratorV3::GizmoDeactivated);
- engine_ = new TextAnimationEngine();
editing_ = false;
}
@@ -173,7 +171,11 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons
QVector2D pos = job.Get(kPositionInput).toVec2();
p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2);
p.translate(frame->video_params().width()/2, frame->video_params().height()/2);
- p.setClipRect(0, 0, size.x(), size.y());
+
+ // when text is animated, it may go outside the clip rect
+ if (editing_ || (IsInputConnected(kAnimatorsInput) == false)) {
+ p.setClipRect(0, 0, size.x(), size.y());
+ }
switch (static_cast(job.Get(kVerticalAlignmentInput).toInt())) {
case kVAlignTop:
@@ -191,74 +193,16 @@ void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) cons
QAbstractTextDocumentLayout::PaintContext ctx;
ctx.palette.setColor(QPalette::Text, Qt::white);
- qDebug() << "editing: " << editing_;
-
- if (editing_) {
+ // When animators are not used, we use the default drawing method because
+ // the drawing method for animators makes some assumptions and may not work well
+ // for all languages (expecially right-to-left ones) and formatting options.
+ if (editing_ || (IsInputConnected(kAnimatorsInput) == false)) {
text_doc.documentLayout()->draw(&p, ctx);
}
else {
- int textSize = text_doc.characterCount();
-
- QTextCursor cursor( &text_doc);
- cursor.movePosition( QTextCursor::Start);
- cursor.movePosition( QTextCursor::Right);
-
- // 'kAnimatorsInput' holds a list of XML tags without main opening and closing tags.
- // Prepend and append XML start and closing tags.
- QString animators_xml = QStringLiteral("\n") +
- job.Get(kAnimatorsInput).toString() +
- QStringLiteral("
");
-
- QList animators = TextAnimationXmlParser::Parse( animators_xml);
-
- engine_->SetTextSize( textSize);
- engine_->SetAnimators( & animators);
- engine_->Calulate();
-
- int posx = 0;
- int char_size = 0;
- int line_Voffset = CalculateLineHeight(cursor);
-
-
- const QVector & horiz_offsets = engine_->HorizontalOffset();
- const QVector & vert_offsets = engine_->VerticalOffset();
- const QVector & rotations = engine_->Rotation();
- const QVector & spacings = engine_->Spacing();
- const QVector & horiz_stretches = engine_->HorizontalStretch();
- const QVector & vert_stretches = engine_->VerticalStretch();
- const QVector & transparencies = engine_->Transparency();
-
- // parse the whole text document and print every character
- for (int i=0; i < textSize; i++) {
- QChar ch = text_doc.characterAt(i);
-
- if ((ch == QChar::LineSeparator) ||
- (ch == QChar::ParagraphSeparator) )
- {
- posx = 0;
- cursor.movePosition( QTextCursor::Right);
- line_Voffset += CalculateLineHeight(cursor);
- }
- else {
-
- p.save();
- p.setFont( cursor.charFormat().font());
- QColor ch_color = cursor.charFormat().foreground().color();
- // "transparencies" can be assumed to be in range 0 - 255
- ch_color.setAlpha(255 - transparencies[i]);
- p.setPen( ch_color);
- p.translate( QPointF(posx + horiz_offsets[i],
- line_Voffset + vert_offsets[i]));
- p.rotate( rotations[i]);
- p.scale( 1.+ horiz_stretches[i], 1.+ vert_stretches[i]);
- p.drawText( QPointF(0,0), ch);
- p.restore();
-
- char_size = QFontMetrics( cursor.charFormat().font()).horizontalAdvance(text_doc.characterAt(i));
- posx += (int)(((double)char_size*(1. + (spacings[i])))*(1. + horiz_stretches[i]));
- cursor.movePosition( QTextCursor::Right);
- }
- }
+ TextAnimationRender animated_text_render;
+ animated_text_render.render( job.Get(kAnimatorsInput).toString(),
+ text_doc, p);
}
}
@@ -368,23 +312,6 @@ void TextGeneratorV3::SetVerticalAlignmentUndoable(Qt::Alignment a)
Core::instance()->undo_stack()->push(new NodeParamSetStandardValueCommand(NodeInput(this, kVerticalAlignmentInput), GetOurAlignmentFromQts(a)));
}
-int TextGeneratorV3::CalculateLineHeight(QTextCursor& start) const
-{
- QTextCursor cur(start);
- int height = QFontMetrics(cur.charFormat().font()).lineSpacing();
- cur.movePosition( QTextCursor::Right);
-
- while (cur.atBlockEnd() == false) {
- int new_height = QFontMetrics(cur.charFormat().font()).lineSpacing();
-
- if (new_height > height) {
- height = new_height;
- }
- cur.movePosition( QTextCursor::Right);
- }
-
- return height;
-}
}
diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h
index 0373db2c6f..66e53c08b4 100644
--- a/app/node/generator/text/textv3.h
+++ b/app/node/generator/text/textv3.h
@@ -23,7 +23,7 @@
#include "node/generator/shape/shapenodebase.h"
#include "node/gizmo/text.h"
-#include "node/generator/animation/textanimationengine.h"
+
class QTextCursor;
@@ -81,9 +81,7 @@ class TextGeneratorV3 : public ShapeNodeBase
bool dont_emit_valign_;
- TextAnimationEngine * engine_;
-
- // any instance of a text node is being edited
+ // True when any instance of a text node is being edited
static bool editing_;
private slots:
@@ -91,9 +89,6 @@ private slots:
void GizmoDeactivated();
void SetVerticalAlignmentUndoable(Qt::Alignment a);
-private:
- int CalculateLineHeight(QTextCursor& start) const;
-
};
}
From 81b268277999ec5cda2212552137a5a1c92e82a8 Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Mon, 31 Oct 2022 21:06:58 +0100
Subject: [PATCH 09/11] Trivial changes
---
app/node/generator/animation/textanimation.h | 2 ++
.../animation/textanimationengine.cpp | 23 +++++++++++--------
.../generator/animation/textanimationnode.cpp | 10 ++++++++
.../generator/animation/textanimationnode.h | 2 ++
.../animation/textanimationrender.cpp | 12 ++++------
.../animation/textanimationxmlformatter.cpp | 1 +
.../animation/textanimationxmlparser.cpp | 1 +
7 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/app/node/generator/animation/textanimation.h b/app/node/generator/animation/textanimation.h
index d441fa4525..450d039a48 100644
--- a/app/node/generator/animation/textanimation.h
+++ b/app/node/generator/animation/textanimation.h
@@ -82,6 +82,8 @@ struct Descriptor {
int character_from;
// last character to be animated. Use -1 to indicate the end of the text
int character_to;
+ // When true, the animation starts from 'character_to' and ends to 'character_from'
+ bool last_to_first;
// When 0 (or negative), the effect is applied to all characetrs.
// When 1 or more, 'stride' items are skipped for evry one that is applied.
int stride;
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index 3290506506..c3fcb2f81b 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -142,31 +142,36 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
while (index <= last_index_) {
+ // if 'last_to_first' is set, fill data in indexes from last to first
+ int target_index = animator.last_to_first ?
+ last_index_ + animator.character_from - index :
+ index;
+
double anim_value = CalculateAnimatorForChar( animator, index);
switch( animator.feature) {
case TextAnimation::PositionVertical:
- vert_offsets_[index] += anim_value;
+ vert_offsets_[target_index] += anim_value;
break;
case TextAnimation::PositionHorizontal:
- horiz_offsets_[index] += anim_value;
+ horiz_offsets_[target_index] += anim_value;
break;
case TextAnimation::Rotation:
- rotations_[index] += anim_value;
+ rotations_[target_index] += anim_value;
break;
case TextAnimation::SpacingFactor:
- spacings_[index] += anim_value;
+ spacings_[target_index] += anim_value;
break;
case TextAnimation::StretchVertical:
- vert_stretch_[index] += anim_value;
+ vert_stretch_[target_index] += anim_value;
break;
case TextAnimation::StretchHorizontal:
- horiz_stretch_[index] += anim_value;
+ horiz_stretch_[target_index] += anim_value;
break;
case TextAnimation::Transparency:
- transparency_[index] += (int)anim_value;
- transparency_[index] = qMin( transparency_[index], 255);
- transparency_[index] = qMax( transparency_[index], 0);
+ transparency_[target_index] += (int)anim_value;
+ transparency_[target_index] = qMin( transparency_[index], 255);
+ transparency_[target_index] = qMax( transparency_[index], 0);
break;
default:
case TextAnimation::None:
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index c7200f2206..c53b465e71 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -30,6 +30,7 @@ const QString TextAnimationNode::kCompositeOutput = QStringLiteral("composite_ou
const QString TextAnimationNode::kFeatureInput = QStringLiteral("feature_in");
const QString TextAnimationNode::kIndexFromInput = QStringLiteral("from_in");
const QString TextAnimationNode::kIndexToInput = QStringLiteral("to_in");
+const QString TextAnimationNode::kLastToFirstInput = QStringLiteral("from_last_in");
const QString TextAnimationNode::kStrideInput = QStringLiteral("stride_in");
const QString TextAnimationNode::kOverlapInInput = QStringLiteral("overlap_in_in");
const QString TextAnimationNode::kOverlapOutInput = QStringLiteral("overlap_out_in");
@@ -80,6 +81,13 @@ TextAnimationNode::TextAnimationNode()
tr("The index of the last character to which animation applies.
"
"Use -1 for the last character of the whole text.
"));
+ // flag that indicates if transition starts from first or last letter
+ AddInput( kLastToFirstInput, NodeValue::kBoolean, false);
+ SetInputProperty( kLastToFirstInput, QString("tooltip"),
+ tr("When checked, the first character to be animated is the one"
+ " at position 'First character'. Otherwise, the first character to"
+ " be animated is the one at position 'Last character'
"));
+
// when 0, effect is applied to all characters. When greater then 0, this number of characters is skipped
// for each one for which the effect is applied
AddInput( kStrideInput, NodeValue::kFloat, 0.);
@@ -158,6 +166,7 @@ void TextAnimationNode::Retranslate()
SetInputName( kFeatureInput, tr("Feature"));
SetInputName( kIndexFromInput, tr("First character"));
SetInputName( kIndexToInput, tr("Last character"));
+ SetInputName( kLastToFirstInput, tr("Last to first"));
SetInputName( kStrideInput, tr("Stride"));
SetInputName( kOverlapInInput, tr("Start timing"));
SetInputName( kOverlapOutInput, tr("End timing"));
@@ -183,6 +192,7 @@ void TextAnimationNode::Value(const NodeValueRow &value, const NodeGlobals & /*g
animation.feature = (TextAnimation::Feature) (job.Get(kFeatureInput).toInt());
animation.character_from = (int)(job.Get(kIndexFromInput).toDouble());
animation.character_to = (int)(job.Get(kIndexToInput).toDouble());
+ animation.last_to_first = job.Get(kLastToFirstInput).toBool();
animation.stride = (int)(job.Get(kStrideInput).toDouble());
animation.overlap_in = job.Get(kOverlapInInput).toDouble ();
animation.overlap_out = job.Get(kOverlapOutInput).toDouble ();
diff --git a/app/node/generator/animation/textanimationnode.h b/app/node/generator/animation/textanimationnode.h
index 197e25577c..a0d441eaf2 100644
--- a/app/node/generator/animation/textanimationnode.h
+++ b/app/node/generator/animation/textanimationnode.h
@@ -73,6 +73,8 @@ class TextAnimationNode : public Node
static const QString kIndexFromInput;
// index of last character to be animated
static const QString kIndexToInput;
+ // flag that indicates if transition starts from first or last letter
+ static const QString kLastToFirstInput;
// number of characters to be skipped for each one that is applied
static const QString kStrideInput;
// range [0-1]. When 0 all characters start the animation at the begin;
diff --git a/app/node/generator/animation/textanimationrender.cpp b/app/node/generator/animation/textanimationrender.cpp
index a93cb0d407..0664523a92 100644
--- a/app/node/generator/animation/textanimationrender.cpp
+++ b/app/node/generator/animation/textanimationrender.cpp
@@ -27,14 +27,14 @@
#include "textanimationrender.h"
#include "node/generator/animation/textanimationxmlparser.h"
-#include
+
namespace olive {
TextAnimationRender::TextAnimationRender()
{
}
-void TextAnimationRender::render(const QString & animators_tags,
+void TextAnimationRender::render( const QString & animators_tags,
QTextDocument & text_doc,
QPainter & p)
{
@@ -61,8 +61,6 @@ void TextAnimationRender::render(const QString & animators_tags,
const QVector & vert_stretches = engine_.VerticalStretch();
const QVector & transparencies = engine_.Transparency();
- qDebug() << vert_offsets;
-
// parse the whole text document and print every character
for (int blk=0; blk < block_count; blk++) {
qreal posx = 0.;
@@ -92,8 +90,8 @@ void TextAnimationRender::render(const QString & animators_tags,
ch_color.setAlpha(255 - transparencies[index]);
p.setPen( ch_color);
- p.translate( QPointF((posx + x)*(1. + horiz_offsets[index])*(1. + spacings[index]),
- line_Voffset + vert_offsets[index]));
+ p.translate( QPointF(posx + (x + horiz_offsets[index])*(1. + spacings[index]),
+ line_Voffset + vert_offsets[index]));
p.rotate( rotations[index]);
p.scale( 1.+ horiz_stretches[index], 1. + vert_stretches[index]);
p.drawText( QPointF(0,0), text_doc.characterAt(cursor.position() - 1));
@@ -106,8 +104,6 @@ void TextAnimationRender::render(const QString & animators_tags,
line_Voffset = text_doc.documentLayout()->blockBoundingRect(cursor.block()).bottom() -
maxDescent(cursor);
}
-
- qDebug() << "index:" << index;
}
qreal TextAnimationRender::maxDescent( QTextCursor cursor) const
diff --git a/app/node/generator/animation/textanimationxmlformatter.cpp b/app/node/generator/animation/textanimationxmlformatter.cpp
index 2e0b946bff..f3889c1b0a 100644
--- a/app/node/generator/animation/textanimationxmlformatter.cpp
+++ b/app/node/generator/animation/textanimationxmlformatter.cpp
@@ -19,6 +19,7 @@ QString TextAnimationXmlFormatter::Format() const
writer.writeAttribute( "ch_from", QString("%1").arg(descriptor_->character_from));
writer.writeAttribute( "ch_to", QString("%1").arg(descriptor_->character_to));
+ writer.writeAttribute( "last_to_first", QString("%1").arg((int)descriptor_->last_to_first));
writer.writeAttribute( "stride", QString("%1").arg(descriptor_->stride));
writer.writeAttribute( "overlap_in", QString("%1").arg(descriptor_->overlap_in));
writer.writeAttribute( "overlap_out", QString("%1").arg(descriptor_->overlap_out));
diff --git a/app/node/generator/animation/textanimationxmlparser.cpp b/app/node/generator/animation/textanimationxmlparser.cpp
index 45795eae64..2f2fbb9e3d 100644
--- a/app/node/generator/animation/textanimationxmlparser.cpp
+++ b/app/node/generator/animation/textanimationxmlparser.cpp
@@ -21,6 +21,7 @@ QList TextAnimationXmlParser::Parse(const QString& xm
item.feature = TextAnimation::FEATURE_TABLE_REV.value( reader.name().toString(), TextAnimation::None);
item.character_from = reader.attributes().value(QString("ch_from")).toInt();
item.character_to = reader.attributes().value(QString("ch_to")).toInt();
+ item.last_to_first = reader.attributes().value(QString("last_to_first")).toInt();
item.stride = reader.attributes().value(QString("stride")).toInt();
item.overlap_in = reader.attributes().value(QString("overlap_in")).toDouble();
item.overlap_out = reader.attributes().value(QString("overlap_out")).toDouble();
From 3da1890f2b9707df4c1b7d9920c6912829cef64d Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Mon, 31 Oct 2022 21:06:58 +0100
Subject: [PATCH 10/11] Added LastToFirst input
---
app/node/generator/animation/textanimation.h | 2 ++
.../animation/textanimationengine.cpp | 23 +++++++++++--------
.../generator/animation/textanimationnode.cpp | 10 ++++++++
.../generator/animation/textanimationnode.h | 2 ++
.../animation/textanimationrender.cpp | 12 ++++------
.../animation/textanimationxmlformatter.cpp | 1 +
.../animation/textanimationxmlparser.cpp | 1 +
7 files changed, 34 insertions(+), 17 deletions(-)
diff --git a/app/node/generator/animation/textanimation.h b/app/node/generator/animation/textanimation.h
index d441fa4525..450d039a48 100644
--- a/app/node/generator/animation/textanimation.h
+++ b/app/node/generator/animation/textanimation.h
@@ -82,6 +82,8 @@ struct Descriptor {
int character_from;
// last character to be animated. Use -1 to indicate the end of the text
int character_to;
+ // When true, the animation starts from 'character_to' and ends to 'character_from'
+ bool last_to_first;
// When 0 (or negative), the effect is applied to all characetrs.
// When 1 or more, 'stride' items are skipped for evry one that is applied.
int stride;
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index 3290506506..c3fcb2f81b 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -142,31 +142,36 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
while (index <= last_index_) {
+ // if 'last_to_first' is set, fill data in indexes from last to first
+ int target_index = animator.last_to_first ?
+ last_index_ + animator.character_from - index :
+ index;
+
double anim_value = CalculateAnimatorForChar( animator, index);
switch( animator.feature) {
case TextAnimation::PositionVertical:
- vert_offsets_[index] += anim_value;
+ vert_offsets_[target_index] += anim_value;
break;
case TextAnimation::PositionHorizontal:
- horiz_offsets_[index] += anim_value;
+ horiz_offsets_[target_index] += anim_value;
break;
case TextAnimation::Rotation:
- rotations_[index] += anim_value;
+ rotations_[target_index] += anim_value;
break;
case TextAnimation::SpacingFactor:
- spacings_[index] += anim_value;
+ spacings_[target_index] += anim_value;
break;
case TextAnimation::StretchVertical:
- vert_stretch_[index] += anim_value;
+ vert_stretch_[target_index] += anim_value;
break;
case TextAnimation::StretchHorizontal:
- horiz_stretch_[index] += anim_value;
+ horiz_stretch_[target_index] += anim_value;
break;
case TextAnimation::Transparency:
- transparency_[index] += (int)anim_value;
- transparency_[index] = qMin( transparency_[index], 255);
- transparency_[index] = qMax( transparency_[index], 0);
+ transparency_[target_index] += (int)anim_value;
+ transparency_[target_index] = qMin( transparency_[index], 255);
+ transparency_[target_index] = qMax( transparency_[index], 0);
break;
default:
case TextAnimation::None:
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index c7200f2206..c53b465e71 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -30,6 +30,7 @@ const QString TextAnimationNode::kCompositeOutput = QStringLiteral("composite_ou
const QString TextAnimationNode::kFeatureInput = QStringLiteral("feature_in");
const QString TextAnimationNode::kIndexFromInput = QStringLiteral("from_in");
const QString TextAnimationNode::kIndexToInput = QStringLiteral("to_in");
+const QString TextAnimationNode::kLastToFirstInput = QStringLiteral("from_last_in");
const QString TextAnimationNode::kStrideInput = QStringLiteral("stride_in");
const QString TextAnimationNode::kOverlapInInput = QStringLiteral("overlap_in_in");
const QString TextAnimationNode::kOverlapOutInput = QStringLiteral("overlap_out_in");
@@ -80,6 +81,13 @@ TextAnimationNode::TextAnimationNode()
tr("The index of the last character to which animation applies.
"
"Use -1 for the last character of the whole text.
"));
+ // flag that indicates if transition starts from first or last letter
+ AddInput( kLastToFirstInput, NodeValue::kBoolean, false);
+ SetInputProperty( kLastToFirstInput, QString("tooltip"),
+ tr("When checked, the first character to be animated is the one"
+ " at position 'First character'. Otherwise, the first character to"
+ " be animated is the one at position 'Last character'
"));
+
// when 0, effect is applied to all characters. When greater then 0, this number of characters is skipped
// for each one for which the effect is applied
AddInput( kStrideInput, NodeValue::kFloat, 0.);
@@ -158,6 +166,7 @@ void TextAnimationNode::Retranslate()
SetInputName( kFeatureInput, tr("Feature"));
SetInputName( kIndexFromInput, tr("First character"));
SetInputName( kIndexToInput, tr("Last character"));
+ SetInputName( kLastToFirstInput, tr("Last to first"));
SetInputName( kStrideInput, tr("Stride"));
SetInputName( kOverlapInInput, tr("Start timing"));
SetInputName( kOverlapOutInput, tr("End timing"));
@@ -183,6 +192,7 @@ void TextAnimationNode::Value(const NodeValueRow &value, const NodeGlobals & /*g
animation.feature = (TextAnimation::Feature) (job.Get(kFeatureInput).toInt());
animation.character_from = (int)(job.Get(kIndexFromInput).toDouble());
animation.character_to = (int)(job.Get(kIndexToInput).toDouble());
+ animation.last_to_first = job.Get(kLastToFirstInput).toBool();
animation.stride = (int)(job.Get(kStrideInput).toDouble());
animation.overlap_in = job.Get(kOverlapInInput).toDouble ();
animation.overlap_out = job.Get(kOverlapOutInput).toDouble ();
diff --git a/app/node/generator/animation/textanimationnode.h b/app/node/generator/animation/textanimationnode.h
index 197e25577c..a0d441eaf2 100644
--- a/app/node/generator/animation/textanimationnode.h
+++ b/app/node/generator/animation/textanimationnode.h
@@ -73,6 +73,8 @@ class TextAnimationNode : public Node
static const QString kIndexFromInput;
// index of last character to be animated
static const QString kIndexToInput;
+ // flag that indicates if transition starts from first or last letter
+ static const QString kLastToFirstInput;
// number of characters to be skipped for each one that is applied
static const QString kStrideInput;
// range [0-1]. When 0 all characters start the animation at the begin;
diff --git a/app/node/generator/animation/textanimationrender.cpp b/app/node/generator/animation/textanimationrender.cpp
index a93cb0d407..0664523a92 100644
--- a/app/node/generator/animation/textanimationrender.cpp
+++ b/app/node/generator/animation/textanimationrender.cpp
@@ -27,14 +27,14 @@
#include "textanimationrender.h"
#include "node/generator/animation/textanimationxmlparser.h"
-#include
+
namespace olive {
TextAnimationRender::TextAnimationRender()
{
}
-void TextAnimationRender::render(const QString & animators_tags,
+void TextAnimationRender::render( const QString & animators_tags,
QTextDocument & text_doc,
QPainter & p)
{
@@ -61,8 +61,6 @@ void TextAnimationRender::render(const QString & animators_tags,
const QVector & vert_stretches = engine_.VerticalStretch();
const QVector & transparencies = engine_.Transparency();
- qDebug() << vert_offsets;
-
// parse the whole text document and print every character
for (int blk=0; blk < block_count; blk++) {
qreal posx = 0.;
@@ -92,8 +90,8 @@ void TextAnimationRender::render(const QString & animators_tags,
ch_color.setAlpha(255 - transparencies[index]);
p.setPen( ch_color);
- p.translate( QPointF((posx + x)*(1. + horiz_offsets[index])*(1. + spacings[index]),
- line_Voffset + vert_offsets[index]));
+ p.translate( QPointF(posx + (x + horiz_offsets[index])*(1. + spacings[index]),
+ line_Voffset + vert_offsets[index]));
p.rotate( rotations[index]);
p.scale( 1.+ horiz_stretches[index], 1. + vert_stretches[index]);
p.drawText( QPointF(0,0), text_doc.characterAt(cursor.position() - 1));
@@ -106,8 +104,6 @@ void TextAnimationRender::render(const QString & animators_tags,
line_Voffset = text_doc.documentLayout()->blockBoundingRect(cursor.block()).bottom() -
maxDescent(cursor);
}
-
- qDebug() << "index:" << index;
}
qreal TextAnimationRender::maxDescent( QTextCursor cursor) const
diff --git a/app/node/generator/animation/textanimationxmlformatter.cpp b/app/node/generator/animation/textanimationxmlformatter.cpp
index 2e0b946bff..f3889c1b0a 100644
--- a/app/node/generator/animation/textanimationxmlformatter.cpp
+++ b/app/node/generator/animation/textanimationxmlformatter.cpp
@@ -19,6 +19,7 @@ QString TextAnimationXmlFormatter::Format() const
writer.writeAttribute( "ch_from", QString("%1").arg(descriptor_->character_from));
writer.writeAttribute( "ch_to", QString("%1").arg(descriptor_->character_to));
+ writer.writeAttribute( "last_to_first", QString("%1").arg((int)descriptor_->last_to_first));
writer.writeAttribute( "stride", QString("%1").arg(descriptor_->stride));
writer.writeAttribute( "overlap_in", QString("%1").arg(descriptor_->overlap_in));
writer.writeAttribute( "overlap_out", QString("%1").arg(descriptor_->overlap_out));
diff --git a/app/node/generator/animation/textanimationxmlparser.cpp b/app/node/generator/animation/textanimationxmlparser.cpp
index 45795eae64..2f2fbb9e3d 100644
--- a/app/node/generator/animation/textanimationxmlparser.cpp
+++ b/app/node/generator/animation/textanimationxmlparser.cpp
@@ -21,6 +21,7 @@ QList TextAnimationXmlParser::Parse(const QString& xm
item.feature = TextAnimation::FEATURE_TABLE_REV.value( reader.name().toString(), TextAnimation::None);
item.character_from = reader.attributes().value(QString("ch_from")).toInt();
item.character_to = reader.attributes().value(QString("ch_to")).toInt();
+ item.last_to_first = reader.attributes().value(QString("last_to_first")).toInt();
item.stride = reader.attributes().value(QString("stride")).toInt();
item.overlap_in = reader.attributes().value(QString("overlap_in")).toDouble();
item.overlap_out = reader.attributes().value(QString("overlap_out")).toDouble();
From c3716b022247012ad0fbf8a231b173e33b6e1f0a Mon Sep 17 00:00:00 2001
From: WileECoder
Date: Mon, 7 Nov 2022 20:10:39 +0100
Subject: [PATCH 11/11] Changed policy for "spacing"
---
.../animation/textanimationengine.cpp | 4 +--
.../generator/animation/textanimationnode.cpp | 2 +-
.../animation/textanimationrender.cpp | 34 +++++++++++++++++--
.../generator/animation/textanimationrender.h | 3 ++
4 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/app/node/generator/animation/textanimationengine.cpp b/app/node/generator/animation/textanimationengine.cpp
index c3fcb2f81b..19cc7a7deb 100644
--- a/app/node/generator/animation/textanimationengine.cpp
+++ b/app/node/generator/animation/textanimationengine.cpp
@@ -170,8 +170,8 @@ void TextAnimationEngine::CalculateAnimator( const TextAnimation::Descriptor &an
break;
case TextAnimation::Transparency:
transparency_[target_index] += (int)anim_value;
- transparency_[target_index] = qMin( transparency_[index], 255);
- transparency_[target_index] = qMax( transparency_[index], 0);
+ transparency_[target_index] = qMin( transparency_[target_index], 255);
+ transparency_[target_index] = qMax( transparency_[target_index], 0);
break;
default:
case TextAnimation::None:
diff --git a/app/node/generator/animation/textanimationnode.cpp b/app/node/generator/animation/textanimationnode.cpp
index c53b465e71..3d7147e23f 100644
--- a/app/node/generator/animation/textanimationnode.cpp
+++ b/app/node/generator/animation/textanimationnode.cpp
@@ -107,7 +107,7 @@ TextAnimationNode::TextAnimationNode()
SetInputProperty( kOverlapInInput, QStringLiteral("max"), 1.);
SetInputProperty( kOverlapInInput, QString("tooltip"),
tr("Range: 0-1
"
- "This controls when each charater starts the animation. "
+ "
This controls when each character starts the animation. "
"When 0, all characters start the animation at the same time; "
"when 1, the begin of the animation for each character is distributed over time.
"));
diff --git a/app/node/generator/animation/textanimationrender.cpp b/app/node/generator/animation/textanimationrender.cpp
index 0664523a92..fe2a30028f 100644
--- a/app/node/generator/animation/textanimationrender.cpp
+++ b/app/node/generator/animation/textanimationrender.cpp
@@ -34,6 +34,7 @@ TextAnimationRender::TextAnimationRender()
{
}
+
void TextAnimationRender::render( const QString & animators_tags,
QTextDocument & text_doc,
QPainter & p)
@@ -42,6 +43,10 @@ void TextAnimationRender::render( const QString & animators_tags,
int block_count = text_doc.blockCount();
// character index in full document
int index = 0;
+ // index of first character in current block
+ int block_start_index = 0;
+ // calculated by the spacing of each character from the begin of block
+ qreal spacing_offset = 0;
cursor.movePosition( QTextCursor::Start);
qreal line_Voffset = text_doc.documentLayout()->blockBoundingRect(cursor.block()).bottom() -
@@ -64,6 +69,7 @@ void TextAnimationRender::render( const QString & animators_tags,
// parse the whole text document and print every character
for (int blk=0; blk < block_count; blk++) {
qreal posx = 0.;
+ block_start_index = index;
// x position of characters of current block without any animation
QVector base_position_x;
@@ -76,7 +82,10 @@ void TextAnimationRender::render( const QString & animators_tags,
* print because the "charFormat()" refers to previous character */
cursor.movePosition( QTextCursor::Right);
- for( qreal & x: base_position_x) {
+ //for( qreal & x: base_position_x) {
+ for( int i = 0; i < base_position_x.size(); i++) {
+ const qreal & x = base_position_x[i];
+
p.save();
p.setFont( cursor.charFormat().font());
@@ -89,9 +98,12 @@ void TextAnimationRender::render( const QString & animators_tags,
// "transparencies" can be assumed to be in range 0 - 255
ch_color.setAlpha(255 - transparencies[index]);
+ // calcualte spacing from the begin of block
+ spacing_offset = calculateSpacing(block_start_index, index, spacings);
+
p.setPen( ch_color);
- p.translate( QPointF(posx + (x + horiz_offsets[index])*(1. + spacings[index]),
- line_Voffset + vert_offsets[index]));
+ p.translate( QPointF(posx + (x + horiz_offsets[index] + spacing_offset),
+ line_Voffset + vert_offsets[index]));
p.rotate( rotations[index]);
p.scale( 1.+ horiz_stretches[index], 1. + vert_stretches[index]);
p.drawText( QPointF(0,0), text_doc.characterAt(cursor.position() - 1));
@@ -189,4 +201,20 @@ double TextAnimationRender::currentCharWidth( QTextCursor & cursor) const
return (double)char_size*(stretch_factor);
}
+// the spacing for a char at a given 'index' in the whole document is the sum
+// of the spacing values from the begin of the block (whose index is
+// 'block_start_index'). The vector 'spacings' holds the values for the whole
+// document.
+qreal TextAnimationRender::calculateSpacing(int block_start_index, int index,
+ const QVector& spacings)
+{
+ qreal char_spacing = 0.;
+
+ for (int ch=block_start_index; ch < index; ch++) {
+ char_spacing += spacings[ch];
+ }
+
+ return char_spacing;
+}
+
} // olive
diff --git a/app/node/generator/animation/textanimationrender.h b/app/node/generator/animation/textanimationrender.h
index ae4e5faf51..e10f54a22d 100644
--- a/app/node/generator/animation/textanimationrender.h
+++ b/app/node/generator/animation/textanimationrender.h
@@ -57,8 +57,11 @@ class TextAnimationRender
double currentCharWidth( QTextCursor & cursor) const;
+ qreal calculateSpacing(int block_start_index, int index, const QVector& spacings);
+
private:
TextAnimationEngine engine_;
+
};
}