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_; + }; }