blur: implement radial and directional blur methods

This commit is contained in:
itsmattkc
2022-05-08 16:53:04 -07:00
parent bdb917c591
commit f910117a58
10 changed files with 270 additions and 52 deletions
+108 -11
View File
@@ -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<PointGizmo>();
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<Method>(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<QVector2D>().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<DraggableGizmo*>(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));
}
}
+30
View File
@@ -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<Method>(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_;
};
}
+2 -1
View File
@@ -22,7 +22,8 @@
namespace olive {
NodeGizmo::NodeGizmo(QObject *parent)
NodeGizmo::NodeGizmo(QObject *parent) :
visible_(true)
{
setParent(parent);
}
+5
View File
@@ -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_;
};
}
+1
View File
@@ -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);
}
+2
View File
@@ -1168,6 +1168,8 @@ signals:
void NodeRemovedFromContext(Node *node);
void InputFlagsChanged(const QString &input, const InputFlags &flags);
private:
class ArrayInsertCommand : public UndoCommand
{
+71 -19
View File
@@ -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);
}
}
+24 -6
View File
@@ -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()) {
@@ -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();
};
}
+19 -15
View File
@@ -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<PointGizmo*>(gizmo)) {
if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) {
return point;
if (gizmo->IsVisible()) {
if (PointGizmo *point = dynamic_cast<PointGizmo*>(gizmo)) {
if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) {
return point;
}
} else if (PolygonGizmo *poly = dynamic_cast<PolygonGizmo*>(gizmo)) {
if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) {
return poly;
}
} else if (PathGizmo *path = dynamic_cast<PathGizmo*>(gizmo)) {
if (path->GetPath().contains(p)) {
return path;
}
} else if (ScreenGizmo *screen = dynamic_cast<ScreenGizmo*>(gizmo)) {
// NOTE: Perhaps this should limit to the actual visible screen space? We'll see.
return screen;
}
} else if (PolygonGizmo *poly = dynamic_cast<PolygonGizmo*>(gizmo)) {
if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) {
return poly;
}
} else if (PathGizmo *path = dynamic_cast<PathGizmo*>(gizmo)) {
if (path->GetPath().contains(p)) {
return path;
}
} else if (ScreenGizmo *screen = dynamic_cast<ScreenGizmo*>(gizmo)) {
// NOTE: Perhaps this should limit to the actual visible screen space? We'll see.
return screen;
}
}