From f910117a5880dbad1b50f00d0d800361d0ca5234 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun, 8 May 2022 16:53:04 -0700 Subject: [PATCH] blur: implement radial and directional blur methods --- app/node/filter/blur/blur.cpp | 119 ++++++++++++++++-- app/node/filter/blur/blur.h | 30 +++++ app/node/gizmo/gizmo.cpp | 3 +- app/node/gizmo/gizmo.h | 5 + app/node/node.cpp | 1 + app/node/node.h | 2 + app/shaders/blur.frag | 90 ++++++++++--- .../nodeparamview/nodeparamviewitem.cpp | 30 ++++- app/widget/nodeparamview/nodeparamviewitem.h | 8 ++ app/widget/viewer/viewerdisplay.cpp | 34 ++--- 10 files changed, 270 insertions(+), 52 deletions(-) diff --git a/app/node/filter/blur/blur.cpp b/app/node/filter/blur/blur.cpp index 123443aea..de9bcbaf9 100644 --- a/app/node/filter/blur/blur.cpp +++ b/app/node/filter/blur/blur.cpp @@ -29,25 +29,50 @@ const QString BlurFilterNode::kHorizInput = QStringLiteral("horiz_in"); const QString BlurFilterNode::kVertInput = QStringLiteral("vert_in"); const QString BlurFilterNode::kRepeatEdgePixelsInput = QStringLiteral("repeat_edge_pixels_in"); +const QString BlurFilterNode::kDirectionalDegreesInput = QStringLiteral("directional_degrees_in"); + +const QString BlurFilterNode::kRadialCenterInput = QStringLiteral("radial_center_in"); + #define super Node BlurFilterNode::BlurFilterNode() { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); - AddInput(kMethodInput, NodeValue::kCombo, 1); // Default to gaussian + Method default_method = kGaussian; + + AddInput(kMethodInput, NodeValue::kCombo, default_method, InputFlags(kInputFlagNotKeyframable | kInputFlagNotConnectable)); AddInput(kRadiusInput, NodeValue::kFloat, 10.0); SetInputProperty(kRadiusInput, QStringLiteral("min"), 0.0); - AddInput(kHorizInput, NodeValue::kBoolean, true); + { + // Box and gaussian only + AddInput(kHorizInput, NodeValue::kBoolean, true); + AddInput(kVertInput, NodeValue::kBoolean, true); + } - AddInput(kVertInput, NodeValue::kBoolean, true); + { + // Directional only + AddInput(kDirectionalDegreesInput, NodeValue::kFloat, 0.0); + } + + { + // Radial only + AddInput(kRadialCenterInput, NodeValue::kVec2, QVector2D(0, 0)); + } + + UpdateInputs(default_method); AddInput(kRepeatEdgePixelsInput, NodeValue::kBoolean, true); SetFlags(kVideoEffect); SetEffectInput(kTextureInput); + + radial_center_gizmo_ = AddDraggableGizmo(); + radial_center_gizmo_->SetShape(PointGizmo::kAnchorPoint); + radial_center_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kRadialCenterInput), 0)); + radial_center_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kRadialCenterInput), 1)); } QString BlurFilterNode::Name() const @@ -76,11 +101,14 @@ void BlurFilterNode::Retranslate() SetInputName(kTextureInput, tr("Input")); SetInputName(kMethodInput, tr("Method")); - SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian") }); + SetComboBoxStrings(kMethodInput, { tr("Box"), tr("Gaussian"), tr("Directional"), tr("Radial") }); SetInputName(kRadiusInput, tr("Radius")); SetInputName(kHorizInput, tr("Horizontal")); SetInputName(kVertInput, tr("Vertical")); SetInputName(kRepeatEdgePixelsInput, tr("Repeat Edge Pixels")); + + SetInputName(kDirectionalDegreesInput, tr("Direction")); + SetInputName(kRadialCenterInput, tr("Center")); } ShaderCode BlurFilterNode::GetShaderCode(const ShaderRequest &request) const @@ -96,25 +124,47 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals job.InsertValue(value); job.InsertValue(QStringLiteral("resolution_in"), NodeValue(NodeValue::kVec2, globals.resolution(), this)); + Method method = static_cast(job.GetValue(kMethodInput).data().toInt()); + // If there's no texture, no need to run an operation if (!job.GetValue(kTextureInput).data().isNull()) { - // Check if radius > 0, and both "horiz" and/or "vert" are enabled - if ((job.GetValue(kHorizInput).data().toBool() || job.GetValue(kVertInput).data().toBool()) - && job.GetValue(kRadiusInput).data().toDouble() > 0.0) { + bool can_push_job = true; - // Set iteration count to 2 if we're blurring both horizontally and vertically - if (job.GetValue(kHorizInput).data().toBool() && job.GetValue(kVertInput).data().toBool()) { - job.SetIterations(2, kTextureInput); + // Check if radius is > 0 + if (job.GetValue(kRadiusInput).data().toDouble() > 0.0) { + // Method-specific considerations + switch (method) { + case kBox: + case kGaussian: + { + bool horiz = job.GetValue(kHorizInput).data().toBool(); + bool vert = job.GetValue(kVertInput).data().toBool(); + + if (!horiz && !vert) { + // Disable job if horiz and vert are unchecked + can_push_job = false; + } else if (horiz && vert) { + // Set iteration count to 2 if we're blurring both horizontally and vertically + job.SetIterations(2, kTextureInput); + } + break; } + case kDirectional: + case kRadial: + break; + } + } else { + can_push_job = false; + } + if (can_push_job) { // If we're not repeating pixels, expect an alpha channel to appear if (!job.GetValue(kRepeatEdgePixelsInput).data().toBool()) { job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); } table->Push(NodeValue::kTexture, QVariant::fromValue(job), this); - } else { // If we're not performing the blur job, just push the texture table->Push(job.GetValue(kTextureInput)); @@ -123,4 +173,51 @@ void BlurFilterNode::Value(const NodeValueRow &value, const NodeGlobals &globals } } +void BlurFilterNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +{ + if (row[kMethodInput].data().toInt() == kRadial) { + const QVector2D &sequence_res = globals.resolution(); + QVector2D sequence_half_res = sequence_res * 0.5; + + radial_center_gizmo_->SetVisible(true); + radial_center_gizmo_->SetPoint(sequence_half_res.toPointF() + row[kRadialCenterInput].value().toPointF()); + + SetInputProperty(kRadialCenterInput, QStringLiteral("offset"), sequence_half_res); + } else{ + radial_center_gizmo_->SetVisible(false); + } +} + +void BlurFilterNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + DraggableGizmo *gizmo = static_cast(sender()); + + if (gizmo == radial_center_gizmo_) { + + NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; + + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + + } +} + +void BlurFilterNode::InputValueChangedEvent(const QString &input, int element) +{ + if (input == kMethodInput) { + UpdateInputs(GetMethod()); + } + + super::InputValueChangedEvent(input, element); +} + +void BlurFilterNode::UpdateInputs(Method method) +{ + SetInputFlags(kHorizInput, (method == kBox || method == kGaussian) ? InputFlags() : InputFlags(kInputFlagHidden)); + SetInputFlags(kVertInput, (method == kBox || method == kGaussian) ? InputFlags() : InputFlags(kInputFlagHidden)); + SetInputFlags(kDirectionalDegreesInput, (method == kDirectional) ? InputFlags() : InputFlags(kInputFlagHidden)); + SetInputFlags(kRadialCenterInput, (method == kRadial) ? InputFlags() : InputFlags(kInputFlagHidden)); +} + } diff --git a/app/node/filter/blur/blur.h b/app/node/filter/blur/blur.h index 5e85dc079..9355dd4c6 100644 --- a/app/node/filter/blur/blur.h +++ b/app/node/filter/blur/blur.h @@ -21,6 +21,7 @@ #ifndef BLURFILTERNODE_H #define BLURFILTERNODE_H +#include "node/gizmo/point.h" #include "node/node.h" namespace olive { @@ -31,6 +32,13 @@ class BlurFilterNode : public Node public: BlurFilterNode(); + enum Method { + kBox, + kGaussian, + kDirectional, + kRadial + }; + NODE_DEFAULT_FUNCTIONS(BlurFilterNode) virtual QString Name() const override; @@ -43,6 +51,13 @@ public: virtual ShaderCode GetShaderCode(const ShaderRequest &request) const override; virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + Method GetMethod() const + { + return static_cast(GetStandardValue(kMethodInput).toInt()); + } + + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + static const QString kTextureInput; static const QString kMethodInput; static const QString kRadiusInput; @@ -50,6 +65,21 @@ public: static const QString kVertInput; static const QString kRepeatEdgePixelsInput; + static const QString kDirectionalDegreesInput; + + static const QString kRadialCenterInput; + +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + +protected: + virtual void InputValueChangedEvent(const QString& input, int element) override; + +private: + void UpdateInputs(Method method); + + PointGizmo *radial_center_gizmo_; + }; } diff --git a/app/node/gizmo/gizmo.cpp b/app/node/gizmo/gizmo.cpp index f885a62e9..f69451869 100644 --- a/app/node/gizmo/gizmo.cpp +++ b/app/node/gizmo/gizmo.cpp @@ -22,7 +22,8 @@ namespace olive { -NodeGizmo::NodeGizmo(QObject *parent) +NodeGizmo::NodeGizmo(QObject *parent) : + visible_(true) { setParent(parent); } diff --git a/app/node/gizmo/gizmo.h b/app/node/gizmo/gizmo.h index 5a3e585d2..07c519886 100644 --- a/app/node/gizmo/gizmo.h +++ b/app/node/gizmo/gizmo.h @@ -39,11 +39,16 @@ public: const NodeGlobals &GetGlobals() const { return globals_; } void SetGlobals(const NodeGlobals &globals) { globals_ = globals; } + bool IsVisible() const { return visible_; } + void SetVisible(bool e) { visible_ = e; } + signals: private: NodeGlobals globals_; + bool visible_; + }; } diff --git a/app/node/node.cpp b/app/node/node.cpp index b76f27d83..b2eff40f2 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -917,6 +917,7 @@ void Node::SetInputFlags(const QString &input, const InputFlags &f) if (i) { i->flags = f; + emit InputFlagsChanged(input, i->flags); } else { ReportInvalidInput("set flags of", input); } diff --git a/app/node/node.h b/app/node/node.h index 6125dc01c..8850703ae 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -1168,6 +1168,8 @@ signals: void NodeRemovedFromContext(Node *node); + void InputFlagsChanged(const QString &input, const InputFlags &flags); + private: class ArrayInsertCommand : public UndoCommand { diff --git a/app/shaders/blur.frag b/app/shaders/blur.frag index e0378a4bf..426362813 100644 --- a/app/shaders/blur.frag +++ b/app/shaders/blur.frag @@ -6,6 +6,12 @@ uniform bool vert_in; uniform bool repeat_edge_pixels_in; uniform vec2 resolution_in; +// Directional +uniform float directional_degrees_in; + +// Radial +uniform vec2 radial_center_in; + uniform int ove_iteration; in vec2 ove_texcoord; @@ -17,6 +23,8 @@ out vec4 frag_color; // Methods #define METHOD_BOX_BLUR 0 #define METHOD_GAUSSIAN_BLUR 1 +#define METHOD_DIRECTIONAL_BLUR 2 +#define METHOD_RADIAL_BLUR 3 // Mode #define MODE_NONE 0 @@ -60,6 +68,19 @@ int determine_mode() { } } +vec4 add_to_composite(vec4 composite, vec2 pixel_coord, float weight) +{ + if (repeat_edge_pixels_in + || (pixel_coord.x >= 0.0 + && pixel_coord.x < 1.0 + && pixel_coord.y >= 0.0 + && pixel_coord.y < 1.0)) { + composite += texture(tex_in, pixel_coord) * weight; + } + + return composite; +} + void main(void) { int mode = determine_mode(); @@ -75,7 +96,13 @@ void main(void) { float divider, sigma; - if (method_in == METHOD_BOX_BLUR) { + if (method_in == METHOD_DIRECTIONAL_BLUR || method_in == METHOD_RADIAL_BLUR) { + // Despite similar math, these are lighter methods perceptually, so we double the radius to + // better match box/gaussian + real_radius *= 2.0; + } + + if (method_in == METHOD_BOX_BLUR || method_in == METHOD_DIRECTIONAL_BLUR) { // Calculate the weight of each pixel based on the radius divider = 1.0 / real_radius; @@ -95,28 +122,53 @@ void main(void) { } - for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { - float weight; + if (method_in == METHOD_BOX_BLUR || method_in == METHOD_GAUSSIAN_BLUR) { + for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { + float weight; - if (method_in == METHOD_BOX_BLUR) { - weight = divider; - } else if (method_in == METHOD_GAUSSIAN_BLUR) { - weight = gaussian2(i, 0.0, sigma) / divider; + if (method_in == METHOD_BOX_BLUR) { + weight = divider; + } else if (method_in == METHOD_GAUSSIAN_BLUR) { + weight = gaussian2(i, 0.0, sigma) / divider; + } + + vec2 pixel_coord = ove_texcoord; + if (mode == MODE_HORIZONTAL) { + pixel_coord.x += i / resolution_in.x; + } else if (mode == MODE_VERTICAL) { + pixel_coord.y += i / resolution_in.y; + } + + composite = add_to_composite(composite, pixel_coord, weight); + } + } else if (method_in == METHOD_DIRECTIONAL_BLUR || method_in == METHOD_RADIAL_BLUR) { + float angle; + + if (method_in == METHOD_DIRECTIONAL_BLUR) { + // Convert directional degrees to radians + angle = (directional_degrees_in*M_PI)/180.0; + } else { + // Calculate angle from distance of center to current coordinate + vec2 distance = (ove_texcoord - 0.5) * (resolution_in) - radial_center_in; + angle = atan(distance.y/distance.x); + + float multiplier = length(distance) / resolution_in.y * 2.0; + + real_radius = ceil(radius_in * multiplier); + divider = 1.0 / real_radius; } - vec2 pixel_coord = ove_texcoord; - if (mode == MODE_HORIZONTAL) { - pixel_coord.x += i / resolution_in.x; - } else if (mode == MODE_VERTICAL) { - pixel_coord.y += i / resolution_in.y; - } + // Get angles + float sin_angle = sin(angle); + float cos_angle = cos(angle); - if (repeat_edge_pixels_in - || (pixel_coord.x >= 0.0 - && pixel_coord.x < 1.0 - && pixel_coord.y >= 0.0 - && pixel_coord.y < 1.0)) { - composite += texture(tex_in, pixel_coord) * weight; + for (float i = -real_radius + 0.5; i <= real_radius; i += 2.0) { + vec2 pixel_coord = ove_texcoord; + + pixel_coord.y += sin_angle * i / resolution_in.y; + pixel_coord.x += cos_angle * i / resolution_in.x; + + composite = add_to_composite(composite, pixel_coord, divider); } } diff --git a/app/widget/nodeparamview/nodeparamviewitem.cpp b/app/widget/nodeparamview/nodeparamviewitem.cpp index 9a61e0718..9cba6f83c 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.cpp +++ b/app/widget/nodeparamview/nodeparamviewitem.cpp @@ -46,21 +46,22 @@ const int NodeParamViewItemBody::kMaxWidgetColumn = kKeyControlColumn; NodeParamViewItem::NodeParamViewItem(Node *node, NodeParamViewCheckBoxBehavior create_checkboxes, QWidget *parent) : super(parent), + body_(nullptr), node_(node), + create_checkboxes_(create_checkboxes), ctx_(nullptr) { node_->Retranslate(); // Create and add contents widget - body_ = new NodeParamViewItemBody(node_, create_checkboxes); - connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); - connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); - connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); - connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); - SetBody(body_); + RecreateBody(); connect(node_, &Node::LabelChanged, this, &NodeParamViewItem::Retranslate); + // FIXME: Implemented to pick up when an input is set to hidden or not - DEFINITELY not a fast + // way of doing this, but "fine" for now. + connect(node_, &Node::InputFlagsChanged, this, &NodeParamViewItem::RecreateBody); + setBackgroundRole(QPalette::Window); // Connect title bar enabled checkbox @@ -80,6 +81,23 @@ void NodeParamViewItem::Retranslate() body_->Retranslate(); } +void NodeParamViewItem::RecreateBody() +{ + QWidget *old_body = body_; + + body_ = new NodeParamViewItemBody(node_, create_checkboxes_); + connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode); + connect(body_, &NodeParamViewItemBody::RequestSetTime, this, &NodeParamViewItem::RequestSetTime); + connect(body_, &NodeParamViewItemBody::ArrayExpandedChanged, this, &NodeParamViewItem::ArrayExpandedChanged); + connect(body_, &NodeParamViewItemBody::InputCheckedChanged, this, &NodeParamViewItem::InputCheckedChanged); + body_->Retranslate(); + body_->SetTime(time_); + body_->SetTimebase(timebase_); + SetBody(body_); + + old_body->deleteLater(); +} + int NodeParamViewItem::GetElementY(const NodeInput &c) const { if (IsExpanded()) { diff --git a/app/widget/nodeparamview/nodeparamviewitem.h b/app/widget/nodeparamview/nodeparamviewitem.h index 3db8692a1..1653a5ada 100644 --- a/app/widget/nodeparamview/nodeparamviewitem.h +++ b/app/widget/nodeparamview/nodeparamviewitem.h @@ -179,6 +179,8 @@ public: void SetTimebase(const rational& timebase) { + timebase_ = timebase; + body_->SetTimebase(timebase); } @@ -228,12 +230,18 @@ private: Node* node_; + NodeParamViewCheckBoxBehavior create_checkboxes_; + Node *ctx_; rational time_; + rational timebase_; KeyframeView::NodeConnections keyframe_connections_; +private slots: + void RecreateBody(); + }; } diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 6000e7f0d..231bc40cd 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -529,7 +529,9 @@ void ViewerDisplayWidget::OnPaint() gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range)); foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { - gizmo->Draw(&p); + if (gizmo->IsVisible()) { + gizmo->Draw(&p); + } } } @@ -748,21 +750,23 @@ NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPo { for (auto it=gizmos_->GetGizmos().crbegin(); it!=gizmos_->GetGizmos().crend(); it++) { NodeGizmo *gizmo = *it; - if (PointGizmo *point = dynamic_cast(gizmo)) { - if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) { - return point; + if (gizmo->IsVisible()) { + if (PointGizmo *point = dynamic_cast(gizmo)) { + if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) { + return point; + } + } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { + if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) { + return poly; + } + } else if (PathGizmo *path = dynamic_cast(gizmo)) { + if (path->GetPath().contains(p)) { + return path; + } + } else if (ScreenGizmo *screen = dynamic_cast(gizmo)) { + // NOTE: Perhaps this should limit to the actual visible screen space? We'll see. + return screen; } - } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { - if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) { - return poly; - } - } else if (PathGizmo *path = dynamic_cast(gizmo)) { - if (path->GetPath().contains(p)) { - return path; - } - } else if (ScreenGizmo *screen = dynamic_cast(gizmo)) { - // NOTE: Perhaps this should limit to the actual visible screen space? We'll see. - return screen; } }