diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index 48128589f..dd82a0805 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -37,6 +37,10 @@
#include "distort/mask/mask.h"
#include "distort/transform/transformdistortnode.h"
#include "effect/opacity/opacityeffect.h"
+#include "filter/blur/blur.h"
+#include "filter/dropshadow/dropshadowfilter.h"
+#include "filter/mosaic/mosaicfilternode.h"
+#include "filter/stroke/stroke.h"
#include "generator/matrix/matrix.h"
#include "generator/noise/noise.h"
#include "generator/polygon/polygon.h"
@@ -45,17 +49,14 @@
#include "generator/text/textv1.h"
#include "generator/text/textv2.h"
#include "generator/text/textv3.h"
-#include "filter/blur/blur.h"
-#include "filter/mosaic/mosaicfilternode.h"
-#include "filter/stroke/stroke.h"
#include "input/time/timeinput.h"
#include "input/value/valuenode.h"
+#include "keying/chromakey/chromakey.h"
+#include "keying/colordifferencekey/colordifferencekey.h"
+#include "keying/despill/despill.h"
#include "math/math/math.h"
#include "math/merge/merge.h"
#include "math/trigonometry/trigonometry.h"
-#include "keying/colordifferencekey/colordifferencekey.h"
-#include "keying/despill/despill.h"
-#include "keying/chromakey/chromakey.h"
#include "output/track/track.h"
#include "output/viewer/viewer.h"
#include "project/folder/folder.h"
@@ -288,6 +289,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new ChromaKeyNode();
case kMaskDistort:
return new MaskDistortNode();
+ case kDropShadowFilter:
+ return new DropShadowFilter();
case kInternalNodeCount:
break;
diff --git a/app/node/factory.h b/app/node/factory.h
index 21610a6f1..d7ca955ff 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -74,6 +74,7 @@ public:
kOCIOGradingTransformLinear,
kChromaKey,
kMaskDistort,
+ kDropShadowFilter,
// Count value
kInternalNodeCount
diff --git a/app/node/filter/CMakeLists.txt b/app/node/filter/CMakeLists.txt
index 08b38908e..ea93d9320 100644
--- a/app/node/filter/CMakeLists.txt
+++ b/app/node/filter/CMakeLists.txt
@@ -15,6 +15,7 @@
# along with this program. If not, see .
add_subdirectory(blur)
+add_subdirectory(dropshadow)
add_subdirectory(mosaic)
add_subdirectory(stroke)
diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp
index e37b4d72c..25a1984c2 100644
--- a/app/node/filter/blur/blur.cpp
+++ b/app/node/filter/blur/blur.cpp
@@ -119,15 +119,15 @@ ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const
void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
{
- ShaderJob job;
-
- job.Insert(value);
- job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
-
- Method method = static_cast(job.Get(kMethodInput).toInt());
-
// If there's no texture, no need to run an operation
- if (job.Get(kTextureInput).toTexture()) {
+ if (value[kTextureInput].toTexture()) {
+
+ ShaderJob job;
+
+ job.Insert(value);
+ job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
+
+ Method method = static_cast(job.Get(kMethodInput).toInt());
bool can_push_job = true;
diff --git a/app/node/filter/dropshadow/CMakeLists.txt b/app/node/filter/dropshadow/CMakeLists.txt
new file mode 100644
index 000000000..e86f4b95f
--- /dev/null
+++ b/app/node/filter/dropshadow/CMakeLists.txt
@@ -0,0 +1,22 @@
+# 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/filter/dropshadow/dropshadowfilter.h
+ node/filter/dropshadow/dropshadowfilter.cpp
+ PARENT_SCOPE
+)
diff --git a/app/node/filter/dropshadow/dropshadowfilter.cpp b/app/node/filter/dropshadow/dropshadowfilter.cpp
new file mode 100644
index 000000000..a20b20f23
--- /dev/null
+++ b/app/node/filter/dropshadow/dropshadowfilter.cpp
@@ -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 .
+
+***/
+
+#include "dropshadowfilter.h"
+
+#include "widget/slider/floatslider.h"
+
+namespace olive {
+
+#define super Node
+
+const QString DropShadowFilter::kTextureInput = QStringLiteral("tex_in");
+const QString DropShadowFilter::kColorInput = QStringLiteral("color_in");
+const QString DropShadowFilter::kDistanceInput = QStringLiteral("distance_in");
+const QString DropShadowFilter::kAngleInput = QStringLiteral("angle_in");
+const QString DropShadowFilter::kSoftnessInput = QStringLiteral("radius_in");
+const QString DropShadowFilter::kOpacityInput = QStringLiteral("opacity_in");
+const QString DropShadowFilter::kFastInput = QStringLiteral("fast_in");
+
+DropShadowFilter::DropShadowFilter()
+{
+ AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
+
+ AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(0.0, 0.0, 0.0)));
+
+ AddInput(kDistanceInput, NodeValue::kFloat, 10.0);
+
+ AddInput(kAngleInput, NodeValue::kFloat, 135.0);
+
+ AddInput(kSoftnessInput, NodeValue::kFloat, 10.0);
+ SetInputProperty(kSoftnessInput, QStringLiteral("min"), 0.0);
+
+ AddInput(kOpacityInput, NodeValue::kFloat, 1.0);
+ SetInputProperty(kOpacityInput, QStringLiteral("min"), 0.0);
+ SetInputProperty(kOpacityInput, QStringLiteral("view"), FloatSlider::kPercentage);
+
+ AddInput(kFastInput, NodeValue::kBoolean, false);
+
+ SetEffectInput(kTextureInput);
+ SetFlags(kVideoEffect);
+}
+
+void DropShadowFilter::Retranslate()
+{
+ super::Retranslate();
+
+ SetInputName(kTextureInput, tr("Texture"));
+ SetInputName(kColorInput, tr("Color"));
+ SetInputName(kDistanceInput, tr("Distance"));
+ SetInputName(kAngleInput, tr("Angle"));
+ SetInputName(kSoftnessInput, tr("Softness"));
+ SetInputName(kOpacityInput, tr("Opacity"));
+ SetInputName(kFastInput, tr("Faster (Lower Quality)"));
+}
+
+ShaderCode DropShadowFilter::GetShaderCode(const ShaderRequest &request) const
+{
+ Q_UNUSED(request)
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/dropshadow.frag"));
+}
+
+void DropShadowFilter::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const
+{
+ if (value[kTextureInput].toTexture()) {
+ ShaderJob job;
+
+ QString iterative = QStringLiteral("previous_iteration_in");
+
+ job.Insert(value);
+ job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this));
+ job.Insert(iterative, value[kTextureInput]);
+
+ if (!qIsNull(value[kSoftnessInput].toDouble())) {
+ job.SetIterations(3, iterative);
+ }
+
+ table->Push(NodeValue::kTexture, QVariant::fromValue(job), this);
+ }
+}
+
+}
diff --git a/app/node/filter/dropshadow/dropshadowfilter.h b/app/node/filter/dropshadow/dropshadowfilter.h
new file mode 100644
index 000000000..5e820a280
--- /dev/null
+++ b/app/node/filter/dropshadow/dropshadowfilter.h
@@ -0,0 +1,58 @@
+/***
+
+ 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 DROPSHADOWFILTER_H
+#define DROPSHADOWFILTER_H
+
+#include "node/node.h"
+
+namespace olive {
+
+class DropShadowFilter : public Node
+{
+ Q_OBJECT
+public:
+ DropShadowFilter();
+
+ NODE_DEFAULT_FUNCTIONS(DropShadowFilter)
+
+ virtual QString Name() const override { return tr("Drop Shadow"); }
+ virtual QString id() const override { return QStringLiteral("org.olivevideoeditor.Olive.dropshadow"); }
+ virtual QVector Category() const override { return {kCategoryFilter}; }
+ virtual QString Description() const override { return tr("Adds a drop shadow to an image."); }
+
+ virtual void Retranslate() override;
+
+ virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override;
+ virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override;
+
+ static const QString kTextureInput;
+ static const QString kColorInput;
+ static const QString kDistanceInput;
+ static const QString kAngleInput;
+ static const QString kSoftnessInput;
+ static const QString kOpacityInput;
+ static const QString kFastInput;
+
+};
+
+}
+
+#endif // DROPSHADOWFILTER_H
diff --git a/app/render/opengl/openglrenderer.cpp b/app/render/opengl/openglrenderer.cpp
index 99e80c104..ade819167 100644
--- a/app/render/opengl/openglrenderer.cpp
+++ b/app/render/opengl/openglrenderer.cpp
@@ -422,8 +422,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
GL_PREAMBLE;
// If this node is iterative, we'll pick up which input here
- QString iterative_name;
- GLuint iterative_input = 0;
+ QMap texture_index_map;
QVector textures_to_bind;
GLuint shader = s.value();
@@ -495,11 +494,7 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
// Set value to bound texture
functions_->glUniform1i(variable_location, textures_to_bind.size());
- // If this texture binding is the iterative input, set it here
- if (it.key() == job.GetIterativeInput()) {
- iterative_input = textures_to_bind.size();
- iterative_name = it.key();
- }
+ texture_index_map.insert(it.key(), textures_to_bind.size());
textures_to_bind.append({texture, job.GetInterpolation(it.key())});
@@ -655,11 +650,12 @@ void OpenGLRenderer::Blit(QVariant s, ShaderJob job, Texture *destination, Video
if (iteration > 0) {
// If this is not the first iteration, replace the iterative texture with the one we
// last drew
- functions_->glActiveTexture(GL_TEXTURE0 + iterative_input);
+ const QString &iterative_input = job.GetIterativeInput();
+ functions_->glActiveTexture(GL_TEXTURE0 + texture_index_map.value(iterative_input));
functions_->glBindTexture(GL_TEXTURE_2D, input_tex->id().value());
// At this time, we only support iterating 2D textures
- PrepareInputTexture(GL_TEXTURE_2D, job.GetInterpolation(iterative_name));
+ PrepareInputTexture(GL_TEXTURE_2D, job.GetInterpolation(iterative_input));
}
// Swap so that the next iteration, the texture we draw now will be the input texture next
diff --git a/app/shaders/dropshadow.frag b/app/shaders/dropshadow.frag
new file mode 100644
index 000000000..dbfba07cb
--- /dev/null
+++ b/app/shaders/dropshadow.frag
@@ -0,0 +1,110 @@
+uniform sampler2D tex_in;
+uniform vec4 color_in;
+uniform float distance_in;
+uniform float angle_in;
+uniform float radius_in;
+uniform float opacity_in;
+uniform vec2 resolution_in;
+uniform sampler2D previous_iteration_in;
+uniform bool fast_in;
+
+uniform int ove_iteration;
+
+in vec2 ove_texcoord;
+out vec4 frag_color;
+
+// Gaussian function uses PI
+#define M_PI 3.1415926535897932384626433832795
+
+// Single gaussian formula (unused, mainly here for documentation/just in case)
+//float gaussian(float x, float sigma) {
+// return (1.0/(sigma*sqrt(2.0*M_PI)))*exp(-0.5*pow(x/sigma, 2.0));
+//}
+
+// Double gaussian formula, actually used in the code below
+// Should be faster than the single gaussian above since it doesn't need sqrt()
+float gaussian2(float x, float y, float sigma) {
+ return (1.0/((sigma*sigma)*2.0*M_PI))*exp(-0.5*(((x*x) + (y*y))/(sigma*sigma)));
+}
+
+void main(void) {
+ if (ove_iteration == 2 || radius_in == 0.0) {
+
+ // Merge step
+ vec4 composite = texture(tex_in, ove_texcoord);
+ if (composite.a < 1.0) {
+ // Convert degrees to radians
+ float shadow_angle = ((angle_in + 90.0)*M_PI)/180.0;
+
+ vec2 shadow_offset = vec2(cos(shadow_angle) * distance_in, sin(shadow_angle) * distance_in);
+ shadow_offset /= resolution_in;
+ shadow_offset += ove_texcoord;
+
+ vec4 shadow_color = texture(previous_iteration_in, shadow_offset);
+
+ shadow_color.rgb = color_in.rgb * shadow_color.a;
+ shadow_color *= 1.0 - composite.a;
+ shadow_color *= opacity_in;
+
+ composite += shadow_color;
+ }
+ frag_color = composite;
+
+ } else {
+ // We only sample on hard pixels, so we don't accept decimal radii
+ float real_radius = ceil(radius_in);
+
+ vec4 composite = vec4(0.0);
+
+ float divider, sigma;
+
+ if (fast_in) {
+
+ // Calculate the weight of each pixel based on the radius
+ divider = 1.0 / real_radius;
+
+ } else {
+
+ // Using (radius = 3 * sigma) because 3 standard deviations covers 97% of the blur according to this document:
+ // http://chemaguerra.com/gaussian-filter-radius/
+ sigma = real_radius;
+ real_radius *= 3.0;
+
+ // Use gaussian formula to calculate the weight of all pixels
+ divider = 0.0;
+ for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) {
+ divider += gaussian2(i, 0.0, sigma);
+ }
+
+ }
+
+ for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) {
+ float weight;
+
+ if (fast_in) {
+ weight = divider;
+ } else {
+ weight = gaussian2(i, 0.0, sigma) / divider;
+ }
+
+ vec2 pixel_coord = ove_texcoord;
+ vec4 tex_col;
+ if (ove_iteration == 0) {
+ pixel_coord.x += i / resolution_in.x;
+
+ // Pull from main texture
+ tex_col = texture(tex_in, pixel_coord);
+ } else if (ove_iteration == 1) {
+ pixel_coord.y += i / resolution_in.y;
+
+ // Pull from previous iteration
+ tex_col = texture(previous_iteration_in, pixel_coord);
+ }
+
+ composite += tex_col * weight;
+ }
+
+ frag_color = composite;
+ }
+
+}