## Spline Blur

Millolab
Posts: 32
Joined: Wed Oct 24, 2018 6:26 am
Been thanked: 5 times

### Spline Blur

Hi everybody, recently, with a huge help from a Pirates Of Confusion's Discord user (Lewis Saunders), I found a way to use a spline to drive a vector motion blur. I was trying to wrap the whole thing inside a Macro, but I eventually came to something I have no idea how to do.

here a screenshot of what I'm doing:

I need to be able to access the polyline... And maybe add/delete points. Is it possible to do so in a Macro?
I'm starting to think the best thing to do is a simple group, with some controls... or just save a preset in the Bins.

what do you think?
Last edited by Millolab on Thu Jun 27, 2019 8:59 am, edited 1 time in total.

Millolab
Posts: 32
Joined: Wed Oct 24, 2018 6:26 am
Been thanked: 5 times

### Re: Spline Blur

I think I found a relatively nice solution.
Here an example video.

I attached the macro, in case someone would like to give it a try.

Let me try to explain what is going on under the hood:
the spline serves as a mask into a white BG; from it, using 2 custom filter i made 2 sobel filters, one for the X, one for the Y

then combined those in the X and Y vectors channels using a channel boolean.

Last is a vector blur.

It is not the fastest blur, But I think it's a nice option to have!
You do not have the required permissions to view the files attached to this post.

SecondMan
Posts: 3383
Joined: Thu Jul 31, 2014 5:31 pm
Been thanked: 70 times
Contact:

### Re: Spline Blur

Only now finding a moment to look at this - this is excellent! An extremely useful tool and very nicely implemented.

Thank you for sharing!

I have a tip or two about the internals of the Macro itself, if you want them - hope to get to that this weekend

Millolab
Posts: 32
Joined: Wed Oct 24, 2018 6:26 am
Been thanked: 5 times

### Re: Spline Blur

SecondMan wrote:
Sat Jun 29, 2019 4:18 pm
Only now finding a moment to look at this - this is excellent! An extremely useful tool and very nicely implemented.

Thank you for sharing!

I have a tip or two about the internals of the Macro itself, if you want them - hope to get to that this weekend
I’d love that! For sure!

SecondMan
Posts: 3383
Joined: Thu Jul 31, 2014 5:31 pm
Been thanked: 70 times
Contact:

### Re: Spline Blur

OK then, but bear with me

## Independence Day

First of all, resolution independence. It's always nice when tools output similar results when turning on Proxy Scale (especially heavier ones like this one), or if you do a complex setup at 2K and all of a sudden the client wishes to up-res to 4K or 8K...

When using SplineBlur, you can see that as soon as you turn on Proxy Scale, the blur gets much more severe. So let's compensate for that.

At the core of your Macro is the VectorMotionBlur node. The annoying part of that node is that when you use Vector channels, it calculates a blur distance that is absolute (distance in pixels), rather than relative to image size.

Easier to show with a simplified example:

Code: Select all

{
Tools = ordered() {
ChannelBooleans1_3 = ChannelBoolean {
Inputs = {
MultiplyByMask = Input { Value = 1, },
EnableExtraChannels = Input { Value = 1, },
ToXVector = Input { Value = 0, },
ToYVector = Input { Value = 5, },
Background = Input {
SourceOp = "Polygon1_1",
},
Foreground = Input {
SourceOp = "Polygon1_1",
},
},
ViewInfo = OperatorInfo { Pos = { 862, 193 } },
},
Inputs = {
MaskWidth = Input { Value = 1920, },
MaskHeight = Input { Value = 1080, },
PixelAspect = Input { Value = { 1, 1 }, },
ClippingMode = Input { Value = FuID { "None" }, },
Width = Input { Value = 0.0519612192691486, },
Height = Input { Value = 0.104991885735697, },
},
ViewInfo = OperatorInfo { Pos = { 738, 203 } },
},
DrawMode = "InsertAndModify",
DrawMode2 = "InsertAndModify",
Inputs = {
OutputSize = Input { Value = FuID { "Custom" }, },
MaskWidth = Input { Value = 192, },
MaskHeight = Input { Value = 108, },
PixelAspect = Input { Value = { 1, 1 }, },
ClippingMode = Input { Value = FuID { "None" }, },
Polyline = Input {
SourceOp = "Polygon1_1Polyline",
Source = "Value",
},
Polyline2 = Input {
Value = Polyline {
},
Disabled = true,
},
},
ViewInfo = OperatorInfo { Pos = { 861.204, 149.968 } },
},
Polygon1_1Polyline = BezierSpline {
SplineColor = { Red = 173, Green = 255, Blue = 47 },
NameSet = true,
KeyFrames = {
[0] = { 0, Flags = { Linear = true, LockedY = true }, Value = Polyline {
Closed = true,
Points = {
{ Linear = true, X = -0.210955197173368, Y = 0.231877092797248, LX = -0.00119787024675957, LY = -0.15595227781215, RX = 0.144567214185801, RY = 0.00125883128799378 },
{ Linear = true, X = 0.222746445384034, Y = 0.235653586661229, LX = -0.144567214185801, LY = -0.00125883128799378, RX = 0.000828619591292582, RY = -0.0091262732861472 },
{ Linear = true, X = 0.225232304157912, Y = 0.208274766802788, LX = -0.000828619591292582, LY = 0.0091262732861472, RX = -0.0019449946099516, RY = -0.148857304193283 },
{ Linear = true, X = 0.219397320328057, Y = -0.238297145777061, LX = 0.0019449946099516, LY = 0.148857304193283, RX = -0.144648709413901, RY = 0.000772468379286489 },
{ Linear = true, X = -0.214548807913647, Y = -0.235979740639202, LX = 0.144648709413901, LY = -0.000772468379286489, RX = 0.00119787024675957, RY = 0.15595227781215 }
}
} }
}
},
VectorMotionBlur1 = VectorMotionBlur {
CtrlWZoom = false,
Inputs = {
Input = Input {
SourceOp = "Background2",
Source = "Output",
},
Vectors = Input {
SourceOp = "ChannelBooleans1_3",
Source = "Output",
},
XScale = Input { Value = 0.01, },
},
ViewInfo = OperatorInfo { Pos = { 865, 237 } },
},
Background2 = Background {
Inputs = {
Width = Input { Value = 192, },
Height = Input { Value = 108, },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftRed = Input { Value = 1, },
TopLeftGreen = Input { Value = 1, },
TopLeftBlue = Input { Value = 1, },
SourceOp = "Rectangle1",
}
},
ViewInfo = OperatorInfo { Pos = { 737, 238 } },
}
}
}
Set Fusion's Proxy Scale to something like 4 and switch it on and off to see the result. Oddly it doesn't do this when using colour channels. But never mind, we can compensate for that by dividing our motion blur Scale by the Proxy Scale. You can get the Proxy Scale in an expression with

Code: Select all

self.Input.ProxyScale
and then our expression for the VectorMotionBlur becomes:

Code: Select all

ChangeDepth.Blur/self.Input.ProxyScale
In SplineBlur it's only half the story, though, because as you can see, the results are still different, only less so (so we're on the right track).

Let's look at the input vectors then. You're using CustomFilters to - very cleverly - generate those. CustomFilters are by definition pixel based, so they will generate different results at different resolution. Basically the smaller the image, the larger the effect, since proportionally pixels are larger in smaller images.

Easy way to figure this one out is to hover over the result in the viewer at different Proxy Scales, and inspect the values in the lower left corner of Fusion:

Proxy Scale off:

Proxy Scale of 4:

That looks like a factor 4 to me, so we compensate for that in our Scale expression as well:

Code: Select all

ChangeDepth.Blur/self.Input.ProxyScale^2
Resolution independence yay!

I've made some of those adjustments and a couple of others to the Macro already, here it is exposed as a group:

Code: Select all

{
Tools = ordered() {
SplineBlur = GroupOperator {
Inputs = ordered() {
ImageInput = InstanceInput {
SourceOp = "ChangeDepth",
Source = "Input",
},
MainInput1 = InstanceInput {
SourceOp = "PipeRouter1",
Source = "Input",
Name = "Polyline Input",
},
Input1 = InstanceInput {
SourceOp = "ChangeDepth",
Source = "Depth",
Default = 3,
},
Input2 = InstanceInput {
SourceOp = "ChangeDepth",
Source = "WIdth",
Page = "Controls",
Default = 1920,
},
Input3 = InstanceInput {
SourceOp = "ChangeDepth",
Source = "Height",
Page = "Controls",
Default = 1080,
},
Input4 = InstanceInput {
SourceOp = "ChangeDepth",
Source = "PixelAspect",
Page = "Controls",
DefaultX = 1,
DefaultY = 1,
},
Input5 = InstanceInput {
SourceOp = "ChangeDepth",
Source = "SizePreview",
Page = "Controls",
Default = 1,
},
Input6 = InstanceInput {
SourceOp = "ChangeDepth",
Source = "Blur",
Page = "Controls",
Default = 1,
},
MainInput3 = InstanceInput {
SourceOp = "PipeRouter2",
Source = "Input",
}
},
Outputs = {
MainOutput1 = InstanceOutput {
SourceOp = "Merge",
Source = "Output",
}
},
ViewInfo = GroupInfo {
Pos = { 872.999, 165.326 },
Flags = {
Expanded = true,
AllowPan = false,
AutoSnap = true,
RemoveRouters = true
},
Size = { 800.366, 399.395, 391.75, 22 },
Direction = "Horizontal",
PipeStyle = "Direct",
Scale = 1,
Offset = { 9, -1.82843 }
},
Tools = ordered() {
Background1 = Background {
Inputs = {
Width = Input {
Value = 1920,
Expression = "ChangeDepth.Input.OriginalWidth",
},
Height = Input {
Value = 1080,
Expression = "ChangeDepth.Input.OriginalHeight",
},
PixelAspect = Input { Expression = "Point(ChangeDepth.PixelAspect.X, ChangeDepth.PixelAspect.Y)", },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftRed = Input { Value = 1, },
TopLeftGreen = Input { Value = 1, },
TopLeftBlue = Input { Value = 1, },
SourceOp = "PipeRouter1",
Source = "Output",
}
},
ViewInfo = OperatorInfo { Pos = { -250.554, 84.56 } },
},
CustomFilter1 = CustomFilter {
Inputs = {
Matrix = Input {
Value = FilterTable {
NumEntries = 54,
Type = 6,
Offset = 0,
Minimum = 0,
Maximum = 1,
MinimumValue = 0,
MaximumValue = 1,
StartSlope = 1,
EndSlope = 1,
StartIn = 0,
EndIn = 1,
Table = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, -1, 1, 1, 1, 1, 2, 0, -2, 1, 1, 1, 1, 1, 0, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
},
},
Normalize = Input { Value = 1, },
Input = Input {
SourceOp = "Background1",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { -172.554, 192.56 } },
},
PipeRouter1 = PipeRouter {
ViewInfo = PipeRouterInfo { Pos = { -96.5, 28.4 } },
},
ChannelBooleans = ChannelBoolean {
Inputs = {
MultiplyByMask = Input { Value = 1, },
EnableExtraChannels = Input { Value = 1, },
ToXVector = Input { Value = 0, },
ToYVector = Input { Value = 5, },
Background = Input {
SourceOp = "CustomFilter1",
Source = "Output",
},
Foreground = Input {
SourceOp = "CustomFilter2",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { -44.5, 193.614 } },
},
CustomFilter2 = CustomFilter {
Inputs = {
Matrix = Input {
Value = FilterTable {
NumEntries = 54,
Type = 6,
Offset = 0,
Minimum = 0,
Maximum = 1,
MinimumValue = 0,
MaximumValue = 1,
StartSlope = 1,
EndSlope = 1,
StartIn = 0,
EndIn = 1,
Table = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, -1, -2, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
},
},
Normalize = Input { Value = 1, },
Input = Input {
SourceOp = "Background1",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { -171.5, 255.56 } },
},
ChangeDepth = ChangeDepth {
CtrlWZoom = false,
Inputs = {
Depth = Input { Value = 3, },
Size = Input { Value = 1, },
SizePreview = Input { Value = 1, },
Blur = Input { Value = 10, }
},
ViewInfo = OperatorInfo { Pos = { -317.396, 298.998 } },
UserControls = ordered() {
Size = {
INP_MaxAllowed = 1000000,
INP_Integer = false,
LBLC_DropDownButton = true,
INPID_InputControl = "LabelControl",
LBLC_NumInputs = 3,
INP_MaxScale = 1,
INP_MinScale = 0,
INP_MinAllowed = -1000000,
LBLC_NestLevel = 1,
ICS_ControlPage = "Controls",
},
WIdth = {
INP_MaxAllowed = 1000000,
INP_Integer = true,
INPID_InputControl = "SliderControl",
INP_MaxScale = 2046,
INP_Default = 1920,
INP_MinScale = 0,
INP_MinAllowed = -1000000,
ICS_ControlPage = "Controls",
},
Height = {
INP_MaxAllowed = 1000000,
INP_Integer = true,
INPID_InputControl = "SliderControl",
INP_MaxScale = 2046,
INP_Default = 1080,
INP_MinScale = 0,
INP_MinAllowed = -1000000,
ICS_ControlPage = "Controls",
},
PixelAspect = {
INP_DefaultX = 1,
INP_DefaultY = 1,
ICS_ControlPage = "Controls",
INPID_InputControl = "OffsetControl",
},
SizePreview = {
INP_MaxAllowed = 1000000,
INP_Integer = false,
INPID_InputControl = "MultiButtonControl",
MBTNC_ShowBasicButton = true,
INP_MaxScale = 0.300000011920929,
INP_Default = 0.300000011920929,
ICS_ControlPage = "Controls",
INP_MinScale = 0,
INP_MinAllowed = -1000000,
MBTNC_ShowName = true,
MBTNC_StretchToFit = true,
MBTNC_ShowToolTip = true,
},
Blur = {
INP_Integer = false,
ICS_ControlPage = "Controls",
INPID_InputControl = "SliderControl",
INP_MinScale = 0,
INP_MaxScale = 10,
INP_Default = 1
}
}
},
VectorMotionBlur = VectorMotionBlur {
Inputs = {
Input = Input {
SourceOp = "ChangeDepth",
Source = "Output",
},
Vectors = Input {
SourceOp = "ChannelBooleans",
Source = "Output",
},
XScale = Input {
Value = 0.625,
Expression = "ChangeDepth.Blur/self.Input.ProxyScale^2",
},
SourceOp = "PipeRouter2",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { -38.235, 297.89 } },
},
Filter = Filter {
Inputs = {
FilterType = Input { Value = 3, },
Input = Input {
SourceOp = "Background2",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 190.414, 201.086 } },
},
Merge = Merge {
Inputs = {
Blend = Input { Expression = "ChangeDepth.SizePreview", },
Background = Input {
SourceOp = "VectorMotionBlur",
Source = "Output",
},
Foreground = Input {
SourceOp = "ErodeDilate",
Source = "Output",
},
PerformDepthMerge = Input { Value = 0, },
SourceOp = "PipeRouter2",
Source = "Output",
}
},
ViewInfo = OperatorInfo { Pos = { 189.044, 317.057 } },
},
Background2 = Background {
Inputs = {
Width = Input {
Value = 1920,
Expression = "ChangeDepth.Input.OriginalWidth",
},
Height = Input {
Value = 2046,
Expression = "ChangeDepth.Input.OriginalHeight",
},
PixelAspect = Input { Expression = "Point(ChangeDepth.PixelAspect.X, ChangeDepth.PixelAspect.Y)", },
["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
TopLeftGreen = Input { Value = 1, },
SourceOp = "Bitmap2",
}
},
ViewInfo = OperatorInfo { Pos = { 190.468, 151.086 } },
},
ErodeDilate = ErodeDilate {
Inputs = {
XAmount = Input { Value = -0.000497, },
Input = Input {
SourceOp = "Filter",
Source = "Output",
},
},
ViewInfo = OperatorInfo { Pos = { 190.414, 242.287 } },
},
Inputs = {
SoftEdge = Input { Value = 0.001, },
MaskWidth = Input { Value = 1920, },
MaskHeight = Input { Value = 1080, },
PixelAspect = Input { Value = { 1, 1 }, },
Image = Input {
SourceOp = "PipeRouter1",
Source = "Output",
},
High = Input { Value = 0.001, },
},
ViewInfo = OperatorInfo { Pos = { 189.5, 73.4 } },
},
Inputs = {
SoftEdge = Input { Value = 0.001, },
PaintMode = Input { Value = FuID { "Minimum" }, },
Invert = Input { Value = 1, },
MaskWidth = Input { Value = 1920, },
MaskHeight = Input { Value = 1080, },
PixelAspect = Input { Value = { 1, 1 }, },
Image = Input {
SourceOp = "PipeRouter1",
Source = "Output",
},
Low = Input { Value = 0.999, },
SourceOp = "Bitmap1",
}
},
ViewInfo = OperatorInfo { Pos = { 189.5, 117.4 } },
},
Previz_2 = Underlay {
CtrlWShown = false,
ViewInfo = UnderlayInfo {
Pos = { 189.5, 62.844 },
Size = { 172, 303.556 }
},
},
PipeRouter2 = PipeRouter {
ViewInfo = PipeRouterInfo { Pos = { 92.3486, 346.148 } },
}
},
}
},
ActiveTool = "SplineBlur"
}

Then there is the matter of setting the Width and Height manually in the Macro. You don't need to do this at all. You can simply set the resolution in Background1 and Background2 (you'll notice I've been cleaning up a few tool names for clarity) with the following expressions:

Code: Select all

ChangeDepth.Input.OriginalWidth
and

Code: Select all

ChangeDepth.Input.OriginalHeight
You want to use OriginalWidth and OriginalHeight instead of Width and Height. The former give you the resolution of the input regardless of Proxy Scale, while the latter give you the resolution of the input with Proxy Scale applied. So in case of a 1920x1080 input, Width and Height would give you 480x270 (you can see this by hovering over nodes and reading their DoD properties).

So using Width and Height in those Image Size expressions would be problematic in the sense that when using Proxy Scale, you'd generate an image at the Proxy Scale resolution, which then gets Proxy Scaled again. And then our Scale calculations would have to be adjusted for three factors rather than two... Basically OriginalWidth and OriginalHeight saves you all that headache..

So now that we've done that, you can remove the Width and Height controls from the Macro altogether (I'll leave that part to you ). You can do the same for Pixel Aspect...

Speaking of User Controls. From the structure of your Macro I'm guessing that your workflow is to add UserControls to one of your tools in your Macro - ChangeDepth in this case - and then when you make a Macro you use Fusion's Macro Editor and expose the controls you need?

That way of working has its advantages (you have all your controls in one node so you can retrieve them easily, for example) but also some disadvantages. Have you noticed that using Size Preview in your Macro requires it to recalculate everything?

Even though all you do is controlling the Blend in that last Merge, the Input Control for that is actually in ChangeDepth, i.e. the very first tool in your Macro. Changing that control invalidates the cache for ChangeDepth, and hey presto - coffee break!

There are two ways to go about this better. Either add UserControls to the actual tools that use them and keep track of those for when you create your Macro, or (and this is what I typically do) - start making a Macro by grouping nodes together, then add UserControls to that Group (yes you can add UserControls to a Group) and expression-link controls inside the Group to those.

In the case of the Merge, it will only have to recalculate the Merge itself, making the Size Preview pretty much real-time on any half decent system. Try just passing-through the node to see how fast that would be... and your tool would feel 10 times more sophisticated that way

## The devil is in the detail

I noticed you connected your Effect Mask input to the ChannelBooleans node that goes into VectorMotionBlur. That's fine of course, but strictly speaking the result is not an Effect Mask. An Effect Mask where an effect is applied, not where it is generated from. In other words, in the case of the Effect Mask, the effect is applied first, then masked, whereas you mask first, then apply the effect. In VectorMotionBlur (but also in the Glow tool for example), that is the difference between the EffectMask and the VectorMask.

So back to your SplineBlur. Putting the Effect Mask into the ChannelBooleans' EffectMask input is identical to connecting it to the VectorMask input in VectorMotionBlur (try it). But it is less efficient, because it's earlier in the tool chain. So I've put it into the VectorMotionBlur Effect Mask instead. The VectorMask is very useful however, so I would suggest making another input that connects to that one.

You may have noticed that Effect Masks are blue in Fusion, by convention. In your Macro, however, it's pink. It's pretty easy to fix this, with a little editing in your favourite text editor.

First let's learn a thing or two about our input by hovering our mouse pointer over it:

In the lower left corner we can see that the Effect Mask input is actually called MainInput3

So copy the Macro (or Group at this point) from Fusion into a text editor and look inside the Inputs = ordered() {} table (basically the list of inputs for the tool) for the following section:

Code: Select all

				MainInput3 = InstanceInput {
SourceOp = "PipeRouter2",
Source = "Input",
}
And simply change it to:

Code: Select all

				EffectMask = InstanceInput {
SourceOp = "PipeRouter2",
Source = "Input",
}
EffectMask is a special reserved name for an Image Input in Fusion, and when used it will turn the input triangle blue (well, lavender apparently):

Ta-daa!

So next up would be to add that extra input and make that one white. Again, I'm going to leave that for you to figure out, but here's a good page with lots of info that will get you going: https://www.steakunderwater.com/VFXPedi ... mage_Input

Ultimately, a great Macro is easy to use and efficient, and pretty much indistinguishable from native tools to the average user (average is not the same as mediocre ). If you wish to take it even further, think also of the Common Controls tab perhaps (not always realistic), or make sure that the scripting names (the names of the Inputs you see in the bottom left when you hover over them) have the same names as their counterparts in the Tools tab:

Well, almost the same name...

Anyway, hope all this helps, and I'm looking forward to seeing the next version of SplineBlur submitted to Reactor

Millolab
Posts: 32
Joined: Wed Oct 24, 2018 6:26 am
Been thanked: 5 times

### Re: Spline Blur

WOW
this is just fantastic. You know. I'm still learning and this is definitely a huge help!

*start studying*

Millolab
Posts: 32
Joined: Wed Oct 24, 2018 6:26 am
Been thanked: 5 times

### Re: Spline Blur

So I updated it. I think I followed all of your suggestions.
I added a coupe more options

-2 kind of blur - path and zoom.
-a control for smoothing the corners (when the corners are too steep the vectors tend to brake).
-a global blend

The preview now happens separate from the vector blur, we have 2 separate mask inputs, the effect mask and the vector mask... I think that's all.

let me know what you think!
You do not have the required permissions to view the files attached to this post.