From 74933951032aa281f49df17a6d80137e0b5ec7f5 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 15 May 2022 11:06:23 -0700 Subject: [PATCH] implemented drop shadow node --- app/node/factory.cpp | 15 ++- app/node/factory.h | 1 + app/node/filter/CMakeLists.txt | 1 + app/node/filter/blur/blur.cpp | 16 +-- app/node/filter/dropshadow/CMakeLists.txt | 22 ++++ .../filter/dropshadow/dropshadowfilter.cpp | 98 ++++++++++++++++ app/node/filter/dropshadow/dropshadowfilter.h | 58 +++++++++ app/render/opengl/openglrenderer.cpp | 14 +-- app/shaders/dropshadow.frag | 110 ++++++++++++++++++ 9 files changed, 312 insertions(+), 23 deletions(-) create mode 100644 app/node/filter/dropshadow/CMakeLists.txt create mode 100644 app/node/filter/dropshadow/dropshadowfilter.cpp create mode 100644 app/node/filter/dropshadow/dropshadowfilter.h create mode 100644 app/shaders/dropshadow.frag 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; + } + +}