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