diff --git a/app/node/factory.cpp b/app/node/factory.cpp
index 1839d8f080..eff2abfa03 100644
--- a/app/node/factory.cpp
+++ b/app/node/factory.cpp
@@ -43,6 +43,7 @@
#include "effect/opacity/opacityeffect.h"
#include "filter/blur/blur.h"
#include "filter/dropshadow/dropshadowfilter.h"
+#include "filter/dilateerode/dilateerode.h"
#include "filter/mosaic/mosaicfilternode.h"
#include "filter/stroke/stroke.h"
#include "generator/matrix/matrix.h"
@@ -309,6 +310,8 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id)
return new RippleDistortNode();
case kMulticamNode:
return new MultiCamNode();
+ case kDilateErodeFilter:
+ return new DilateErodeFilterNode();
case kInternalNodeCount:
break;
diff --git a/app/node/factory.h b/app/node/factory.h
index fbf3535514..74674f241e 100644
--- a/app/node/factory.h
+++ b/app/node/factory.h
@@ -81,6 +81,7 @@ class NodeFactory
kTileDistort,
kSwirlDistort,
kMulticamNode,
+ kDilateErodeFilter,
// Count value
kInternalNodeCount
diff --git a/app/node/filter/CMakeLists.txt b/app/node/filter/CMakeLists.txt
index ea93d93205..a870b98c99 100644
--- a/app/node/filter/CMakeLists.txt
+++ b/app/node/filter/CMakeLists.txt
@@ -18,6 +18,7 @@ add_subdirectory(blur)
add_subdirectory(dropshadow)
add_subdirectory(mosaic)
add_subdirectory(stroke)
+add_subdirectory(dilateerode)
set(OLIVE_SOURCES
${OLIVE_SOURCES}
diff --git a/app/node/filter/dilateerode/CMakeLists.txt b/app/node/filter/dilateerode/CMakeLists.txt
new file mode 100644
index 0000000000..0d7b0b3937
--- /dev/null
+++ b/app/node/filter/dilateerode/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Olive - Non-Linear Video Editor
+# Copyright (C) 2021 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/dilateerode/dilateerode.h
+ node/filter/dilateerode/dilateerode.cpp
+ PARENT_SCOPE
+)
diff --git a/app/node/filter/dilateerode/dilateerode.cpp b/app/node/filter/dilateerode/dilateerode.cpp
new file mode 100644
index 0000000000..903c55fb6f
--- /dev/null
+++ b/app/node/filter/dilateerode/dilateerode.cpp
@@ -0,0 +1,100 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 "dilateerode.h"
+
+namespace olive {
+
+const QString DilateErodeFilterNode::kTextureInput = QStringLiteral("tex_in");
+const QString DilateErodeFilterNode::kMethodInput = QStringLiteral("method_in");
+const QString DilateErodeFilterNode::kPixelsInput = QStringLiteral("pixels_in");
+
+#define super Node
+
+DilateErodeFilterNode::DilateErodeFilterNode()
+{
+ AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
+
+ AddInput(kMethodInput, NodeValue::kCombo, 0);
+
+ AddInput(kPixelsInput, NodeValue::kInt, 0);
+
+ SetFlags(kVideoEffect);
+ SetEffectInput(kTextureInput);
+}
+
+Node* DilateErodeFilterNode::copy() const
+{
+ return new DilateErodeFilterNode();
+}
+
+QString DilateErodeFilterNode::Name() const
+{
+ return tr("Dilate/Erode");
+}
+
+QString DilateErodeFilterNode::id() const
+{
+ return QStringLiteral("org.olivevideoeditor.Olive.dilateerode");
+}
+
+QVector DilateErodeFilterNode::Category() const
+{
+ return {kCategoryFilter};
+}
+
+QString DilateErodeFilterNode::Description() const
+{
+ return tr("Grows or shrinks bright areas by the set number of pixels");
+}
+
+void DilateErodeFilterNode::Retranslate()
+{
+ super::Retranslate();
+
+ SetInputName(kTextureInput, tr("Input"));
+ SetInputName(kMethodInput, tr("Method"));
+ SetComboBoxStrings(kMethodInput, {tr("Box"), tr("Distance"), tr("Gaussian")});
+ SetInputName(kPixelsInput, tr("Pixels"));
+}
+
+ShaderCode DilateErodeFilterNode::GetShaderCode(const ShaderRequest& request) const
+{
+ Q_UNUSED(request)
+ return ShaderCode(FileFunctions::ReadFileAsString(":/shaders/dilateerode.frag"));
+}
+
+void DilateErodeFilterNode::Value(const NodeValueRow& value, const NodeGlobals& globals, NodeValueTable* table) const
+{
+ // If there's no texture and no dilation/erosion, no need to run an operation
+ if (value[kTextureInput].toTexture() && value[kPixelsInput].data().toInt() != 0) {
+ TexturePtr tex = value[kTextureInput].toTexture();
+ //job.SetIterations(2, kTextureInput);
+ ShaderJob job(value);
+ job.Insert(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, tex->virtual_resolution(), this));
+ job.SetIterations(2, kTextureInput);
+ table->Push(NodeValue::kTexture, tex->toJob(job), this);
+ } else {
+ // If we're not doing anything just push the texture
+ table->Push(value[kTextureInput]);
+ }
+}
+
+}
diff --git a/app/node/filter/dilateerode/dilateerode.h b/app/node/filter/dilateerode/dilateerode.h
new file mode 100644
index 0000000000..53753c991d
--- /dev/null
+++ b/app/node/filter/dilateerode/dilateerode.h
@@ -0,0 +1,56 @@
+/***
+
+ Olive - Non-Linear Video Editor
+ Copyright (C) 2021 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 DILATEERODEFILTERNODE_H
+#define DILATEERODEFILTERNODE_H
+
+#include "node/node.h"
+
+namespace olive {
+
+class DilateErodeFilterNode : public Node
+{
+ Q_OBJECT
+public:
+ DilateErodeFilterNode();
+
+ NODE_DEFAULT_DESTRUCTOR(DilateErodeFilterNode)
+
+ virtual Node* copy() const override;
+
+ 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 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 kMethodInput;
+ static const QString kPixelsInput;
+
+};
+
+}
+
+#endif //DILATEERODEFILTERNODE_H
diff --git a/app/shaders/dilateerode.frag b/app/shaders/dilateerode.frag
new file mode 100644
index 0000000000..bff706381f
--- /dev/null
+++ b/app/shaders/dilateerode.frag
@@ -0,0 +1,81 @@
+uniform sampler2D tex_in;
+uniform int method_in;
+uniform int pixels_in;
+uniform vec2 resolution_in;
+
+uniform int ove_iteration;
+
+in vec2 ove_texcoord;
+out vec4 frag_color;
+
+// Gaussian function uses PI
+#define M_PI 3.1415926535897932384626433832795
+
+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) {
+ vec2 pixel_coord;
+ vec2 offset;
+ vec4 sample;
+ vec4 composite;
+
+ int size = int(abs(float(pixels_in)));
+
+ pixel_coord = ove_texcoord;
+
+ // Constants for gaussian method
+ float sigma = float(size) / 2.0;
+ float max_weight = gaussian2(0.0, 0.0, sigma);
+ size*=3;
+
+ // GLSL has no FLOAT_MIN or FLOAT_MAX
+ // Gaussian erode essentially inverts the image, does a gaussian dilate and then
+ // reinverts the image hence there being a special case here for method_in == 2
+ composite = pixels_in > 0 || method_in == 2 ? vec4(-9999.0) : vec4(9999.0);
+ for(int i = -size; i <= size; i++) {
+
+ if(ove_iteration == 0){
+ offset.x = float(i) / resolution_in.x;
+ offset.y = 0.0;
+ } else{
+ offset.x = 0.0;
+ offset.y = float(i) / resolution_in.y;
+ }
+
+ if (method_in == 0) { // Box
+ sample = texture(tex_in, pixel_coord+offset);
+ if (pixels_in > 0) {
+ composite = max(sample, composite);
+ } else if (pixels_in < 0) {
+ composite = min(sample, composite);
+ }
+ } else if (method_in == 1) { // Distance
+ float len = length(offset);
+ float scaled_size = float(size) / length(resolution_in);
+ if (len <= scaled_size){
+ sample = texture(tex_in, pixel_coord+offset);
+ if (pixels_in > 0) {
+ composite = max(sample, composite);
+ } else if (pixels_in < 0) {
+ composite = min(sample, composite);
+ }
+ }
+ } else if (method_in == 2) { // Gaussian
+ float weight = gaussian2(float(i), 0.0, sigma) / max_weight;
+ sample = texture(tex_in, pixel_coord+offset);
+ if (pixels_in > 0) {
+ // weight^2 seems to give a better result
+ composite = max(sample*weight*weight, composite);
+ } else if (pixels_in < 0) {
+ composite = max((1.0-sample)*weight*weight, composite);
+ }
+ }
+ }
+ if (method_in == 2 && pixels_in < 0) {
+ frag_color = vec4(1.0-composite);
+ } else{
+ frag_color = vec4(composite);
+ }
+}