How to deal with edges when using Sobel filter?

User avatar
SecondMan
Site Admin
Posts: 3572
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 5
Location: Vancouver, Canada
Been thanked: 103 times
Contact:

Re: How to deal with edges when using Sobel filter?

#31

Post by SecondMan » Wed Jun 06, 2018 6:02 pm

So as promised I'll walk you through some of the steps used in making this tool.

First of all, the Macro Editor was not used to create this. This is a different workflow which is not compatible with the built in Macro Editor, but is one that I find the most convenient and quick way to build tools, especially when they expand as you go along.

So ultimately a Macro is no different than a Group, in Fusion. The only difference is that a Macro is a closed node, and a Group is one that you can open in Fusion with the click of a button and see the internals. So with that said, here's how I started making the Macro posted here, by selecting the nodes you wish to group, right click and select "Group":

Make_Group.PNG

And here's the Group:

Group.PNG

When you select a Group, you can see that there are no Controls in it, just a Comments field. But a Group can have controls! So next, we right-click on the Group name in the Inspector panel and select Edit Controls...

Group_EditControls....PNG

We want to add a Control named Filter that looks just like the drop down menu in the Filter node. Also we want to have Sobel and Laplacian as our options. So we input Filter as the name. The ID will be filled out automatically. The Type is a number - effectively we're making a list with numbers starting at 0, which will then translate to our Filter choices in the UI.

There are two pages available, Comments and User. Comments is the panel that is already available in the Group node. User is Fusion's default for an extra page that contains User Controls. More on that later, but User is what we want. If we would select Comments, all our additional controls would end up under the Comments text box.

The dropdown menu is called a ComboControl in Fusion, so we add that. Add Sobel and Laplacian as our options and we're good to go.

EditControls....PNG

So we've added our control to the Group now! Lastly rename the group to something appropriate and this should be more or less where you'd be at:

EdgeFinder_v1.PNG

Apart from one thing. See how the User page is named Controls in the screenshot? This is where it gets fun :)

Copy the Group to your clipboard, open your text editor of choice, and paste the node in there. What you see is the contents of the Group, as it is stored in a Fusion comp. Basically it's a Lua table with tons of values. What we're interested in is this snippet here:

  1.             UserControls = ordered() {
  2.                 Filter = {
  3.                     LINKS_Name = "Filter",
  4.                     LINKID_DataType = "Number",
  5.                     INPID_InputControl = "ComboControl",
  6.                     INP_Integer = false,
  7.                     { CCS_AddString = "Sobel" },
  8.                     { CCS_AddString = "Laplacian" },
  9.                     CC_LabelPosition = "Horizontal",
  10.                 }
  11.             }

The UserControls table is - not surprisingly - where our User Controls are stored. There's our filter, with all its properties. We're going to add one more. You can add it anywhere inside the table, the order doesn't matter but I've added it at the bottom here:

ICS_ControlPage = "Controls",

So now the table looks like this:

  1.             UserControls = ordered() {
  2.                 Filter = {
  3.                     LINKS_Name = "Filter",
  4.                     LINKID_DataType = "Number",
  5.                     INPID_InputControl = "ComboControl",
  6.                     INP_Integer = false,
  7.                     { CCS_AddString = "Sobel" },
  8.                     { CCS_AddString = "Laplacian" },
  9.                     CC_LabelPosition = "Horizontal",
  10.                     ICS_ControlPage = "Controls",
  11.                 }
  12.             }

Select everything in your text editor again and paste it back into your comp - preferably in a new comp window, otherwise all your tool names will number up. One of the reasons why the ability to have multiple documents open at the same time is really beneficial for tool development (please take note, @Peter Chamberlain ;))

And we're done!

Right?

Well, not quite. Our Control isn't really doing anything yet. So we need to link the Filter dropdown inside of our Group to the one we just made.

Easy way to do this is first make sure that our tool UI doesn't go away when we click on another node. We can pin Controls using the little Pin icon in the top left hand corner:

PinControls.PNG

Then select our Filter node inside our Group. Right click on Filter Type and select Expression at the bottom:

ModifyWithExpression.PNG

You'll see an Expression box appear with the number 3.0 in it, if your Filter is set to Sobel (because that's its number in the dropdown list). Now click and drag from the little plus sign next to it to our Filter menu in our EdgeFinder UI:

ConnectControls.PNG

The Expression field should now contain EdgeFinder.Filter

There one last thing left. See how when we now switch our Filter from Sobel to Laplacian and we get Relief and Emboss Over instead?

Remember the 3.0 number? Our lists don't line up. So we add 3 to our expression like this: EdgeFinder.Filter+3 and now they do line up!

Now we're done with this first step and this is more or less where we're at:

EdgeFinder_v1.PNG

Next we'll go somewhat further to get to the setup you see below. There are a few good-to-know things in there so don't miss it. :)

EdgeFinder_v2.PNG

In the meantime, feel free to try some of the above techniques and post some results on the forum or ask questions. :)
You do not have the required permissions to view the files attached to this post.

Tags:

User avatar
SirEdric
Fusionator
Posts: 1954
Joined: Tue Aug 05, 2014 10:04 am
Answers: 3
Real name: Eric Westphal
Been thanked: 133 times
Contact:

Re: How to deal with edges when using Sobel filter?

#32

Post by SirEdric » Wed Jun 06, 2018 9:33 pm

Splendid Tutorial, @SecondMan !
What about an extra subforum "Tutorials", where gems like these could live?
Or at least adding a "Tutorial" flag to your post, so new user can dig out stuff like this more easily?

Cheers.
Eric.

User avatar
SecondMan
Site Admin
Posts: 3572
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 5
Location: Vancouver, Canada
Been thanked: 103 times
Contact:

Re: How to deal with edges when using Sobel filter?

#33

Post by SecondMan » Wed Jun 06, 2018 9:39 pm

Thank you!

I added a Tutorial tag to the topic, sir :)

But that's been on my mind for sure. I'm considering several options right now and I will consult with interested parties... ;)

User avatar
intelligent machine
Fusionista
Posts: 427
Joined: Fri May 13, 2016 10:01 pm
Answers: 2
Location: Austin, Texas, USA
Been thanked: 32 times
Contact:

Re: How to deal with edges when using Sobel filter?

#34

Post by intelligent machine » Wed Jun 06, 2018 10:11 pm

I'd love to see a tab added to these tools with a link to the tutorial page and perhaps simplified usage instructions/examples.
...or if at all possible, another fusion reference plugin with a popup/dockable window which contains html formatted tutorial/example pages, a searchable index, and the ability to link to a currently selected or dragged in tools! ;)

User avatar
Chad
Fusionator
Posts: 1419
Joined: Fri Aug 08, 2014 1:11 pm
Been thanked: 14 times

Re: How to deal with edges when using Sobel filter?

#35

Post by Chad » Wed Jun 06, 2018 10:18 pm

You can add a help URL to your tools. Not many do, of course, because documentation is hard.

User avatar
SecondMan
Site Admin
Posts: 3572
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 5
Location: Vancouver, Canada
Been thanked: 103 times
Contact:

Re: How to deal with edges when using Sobel filter?

#36

Post by SecondMan » Wed Jun 06, 2018 10:52 pm

Good documentation is very hard. But also impressive enough to give you lots of bonus points.

The upside of having forum threads like these is that you can often use those for when your users press that F1 key...

I'll make sure to add that to this Macro example, too.

User avatar
SecondMan
Site Admin
Posts: 3572
Joined: Thu Jul 31, 2014 5:31 pm
Answers: 5
Location: Vancouver, Canada
Been thanked: 103 times
Contact:

Re: How to deal with edges when using Sobel filter?

#37

Post by SecondMan » Thu Jun 07, 2018 8:30 am

So for our next installment we'll refer to the version of EdgeFinder posted here and how it has been built from this:

EdgeFinder_v1.PNG

to this:

EdgeFinder_v2.PNG

So by now we've added and connected some more controls and our first version is working. Feel free to dive into the Group/Macro and look at how that was done. The next few steps are easy enough and follow the same pattern as before. We've added an Erode/Dilate node and a Blur node, added two more sliders to our Group UI and connected them up as described in the previous post.

TIP: In your text editor you can change the order of all the controls that you've added very easily. Just look for the UserControls table and copy/paste the controls in the order you would like them to be.


Just one detail I'd like to point out. Let's take a look at our SliderControl settings for the Erode/Dilate control:

ErodeDilate_SliderControl.PNG

When using an Erode/Dilate on a detected edge, shrinking the edge down is much more sensitive than expanding it, because the edge is so thin. That makes the default slider behaviour a little bit awkward. So what we can do is define the Range of the slider to something that works much better interactively, in our case a value between -0.002 and +0.02 is great. At the same time the slider's Center is kept at 0. This results in a slider where the left hand side is 10 times more "sensitive" than the right side, allowing us to shrink and expand the thin edge with equal accuracy.


On to the next step...

One thing to always keep in mind when building tools is to try to keep them fast and efficient. So you look at how you can build something that is not only quick to render, but at the same time try to make it so it uses the least amount of RAM possible. The laws of diminishing returns also apply here of course, sometimes gaining 10% in speed or RAM doesn't outweigh the effort required to build the process to do so...

But as mentioned by @intelligent machine, there are a few things going on with the DoD (or Domain of Definition - see also page 436 of the Fusion 9 User Manual) so let's look at that. To see what the DoD is doing in our Macro, you need to turn it on in the Viewer:

DoD_Button.PNG

Now that we can see the DoD and navigate in our Group, you can see that as soon as we duplicate our edges, the DoD "disappears". That makes sense, because essentially we have now created an infinite image (the edges never end) and while that is great for our edge detect, we'd like our DoD back please. So when we look at a Filter node we can see that a Sobel or Laplacian don't adjust the DoD of an image. Great, this means we can just copy the DoD from our input and still keep the result we're after. Efficiency!

When we use either the Erode/Dilate or the Blur next (and efficiently since we're applying those to a small part of our image), we can see that the DoD adjusts. But tools like Blur have a Clipping Mode that allows you to control what happens to the DoD (and the result) when an effect is applied. We can choose not to clip anything, so the full effect exists even if it goes outside the image border, clip it to the incoming Domain which will cut off the effect at the sides, or clip it to the image border which is the default. We'd like to keep that behaviour for sure, so again let's try to replicate the Control in our Group like this:

ClippinModeButton.PNG

Bingo. Now all we need to do is hook up the Clipping Mode Controls from both the Erode/Dilate and the Blur to this one and we're all set.

But apparently we're not. Fusion lets us connect all of that just fine, but when we try to use our tool and play with the Domain settings, all sorts of weird and unexpected clipping behaviour is happening (try it for yourself if you don't believe me). So what is going on here?

Here's a gotcha. Some Fusion Controls - like the MultiButtonControl that we've just added, or the ComboControl that we've used before - use indexes to define the options in the lists. Not so with Fusion's Clipping Mode Control. That's an entirely different type called MultiButtonIDControl (note the added ID) that's unfortunately not available in Fusion's Edit Controls... window.

But we'll work around that right away.

In our text editor we can look for our ClippingMode control and it looks something like this:

  1.                 ClippingMode = {
  2.                     LINKS_Name = "Clipping Mode",
  3.                     LINKID_DataType = "Number",
  4.                     INPID_InputControl = "MultiButtonControl",
  5.                     INP_Default = 2,
  6.                     INP_Integer = true,
  7.                     { MBTNC_AddButton = "None" },
  8.                     { MBTNC_AddButton = "Domain" },
  9.                     { MBTNC_AddButton = "Frame" },
  10.                     MBTNC_ShowBasicButton = true,
  11.                     MBTNC_ShowToolTip = false,
  12.                     MBTNC_ShowName = true,
  13.                     MBTNC_StretchToFit = true,
  14.                     ICS_ControlPage = "Controls"
  15.                 }

So we see our MultiButtonControl, our three Buttons, and INP_Default = 2, which shows us that the number 2 item in the list will be our default, which is the "Frame" button (we start counting at 0).

But what we want for our Clipping Mode is this:

  1.                 ClippingMode = {
  2.                     LINKS_Name = "Clipping Mode",
  3.                     LINKID_DataType = "Number",
  4.                     INPID_InputControl = "MultiButtonIDControl",
  5.                     INPID_DefaultID = "Frame",
  6.                     INP_Integer = true,
  7.                     { MBTNC_AddButton = "None" },
  8.                     { MBTNC_AddButton = "Domain" },
  9.                     { MBTNC_AddButton = "Frame" },
  10.                     MBTNC_ShowBasicButton = true,
  11.                     MBTNC_ShowToolTip = false,
  12.                     MBTNC_ShowName = true,
  13.                     MBTNC_StretchToFit = true,
  14.                     ICS_ControlPage = "Controls"
  15.                 }

So we need to change 2 things only. First we create a different Control type called a MultiButtonIDControl by changing INPID_InputControl = "MultiButtonControl", to INPID_InputControl = "MultiButtonIDControl",. Secondly we also need to change INP_Default = 2, to INPID_DefaultID = "Frame",. So this type of list doesn't go by index, but by name or rather ID. Notice the ID added to both the MultiButtonIDControl and INPID_DefaultID. This is important.

Ta-daa! Everything works, hurray!

Obviously this is good to know, so how can we tell if a button is of that MultiButtonIDControl type? Easy enough - just right click on a control and add an Expression. Try it with an ErodeDilate node. Right-Click on Filter. The result in the Expression field says 0.0 which indicates this is a MultiButtonControl. Now do the same with Clipping Mode. Now the Expression field says "Frame".That's a MultiButtonIDControl!

DOUBLE GOTCHA. Filter in a Blur node is a MultiButtonIDControl, not a MultiButtonControl. So beware if you ever want to hook up Blur Filters to ErodeDilate Filters...


At this point our Macro is pretty much functional, but there were a few last things more that I wanted to add, namely an Effect Mask, Blend functionality, and to fulfill the request for being able to copy the result of an RGB Edge Detect to the alpha channel, which we can all do with a single Channel Boolean node, but comes with a few challenges as well.

TIP: If you want to insert nodes at an Input our Output of a Group, make sure that they are not connected to anything. It won't work.

So we add our ChannelBoolean and link it all up just so you see in the screenshot.

EdgeFinder_v2.PNG

All good, can all be done in the Flow View. But what about that Effect Mask input? How do we add that?

For that one we need to go into our text editor again. This time we're looking for something that looks like this:

  1.                 Input = InstanceInput {
  2.                     SourceOp = "BGInput",
  3.                     Source = "Input",
  4.                 },

That's our Background input (the yellow triangle on the Group node). We can see that it's called Input, and that it's an InstanceInput (it basically is an input not to the Group itself but to a node inside the group), and it's connected to the "Input" of a tool called "BGInput".

So with that information as a template, all we need to do is add another one. In this example I've named the Channel Boolean node CopyAndBlend, so I want to create another input named EffectMask, which is an instance of the "EffectMask" on my tool called "CopyAndBlend":

  1.                 EffectMask = InstanceInput {
  2.                     SourceOp = "CopyAndBlend",
  3.                     Source = "EffectMask",
  4.                 }
Just add that underneath the other input, copy/paste back into the Flow and you'll see it's all connected.

Nearly there. Just add buttons for some of the channel options and linking them up with expressions. You'll see expressions in this form:

Code: Select all

EdgeFinder.EdgeMatteOutput==0 and 0 or EdgeFinder.EdgeMatteOutput==1 and 10 or 5

If you want to learn more about Expression syntax of this type, read this topic


And lastly, adding a HelpPage. You can add a HelpPage to any tool by inserting CustomData in it. Then, when you press F1, Fusion will take you to that helppage. Ideally, I must add because that seems to be broken in 9.0.2.

In any case, this is how you would add it to our EdgeFinder if you wanted it to point to this very thread:
  1. {
  2.     Tools = ordered() {
  3.         EdgeFinder = GroupOperator {
  4.             CustomData = {
  5.                 HelpPage = "https://www.steakunderwater.com/wesuckless/viewtopic.php?f=6&t=2206"
  6.             },

And here, finally, is our result:

Code: [Select all] [Expand/Collapse] [Download] (EdgeFinder.setting)
  1. {
  2.     Tools = ordered() {
  3.         EdgeFinder = GroupOperator {
  4.             CustomData = {
  5.                 HelpPage = "https://www.steakunderwater.com/wesuckless/viewtopic.php?f=6&t=2206"
  6.             },
  7.             CtrlWZoom = false,
  8.             NameSet = true,
  9.             Inputs = ordered() {
  10.                 Input = InstanceInput {
  11.                     SourceOp = "BGInput",
  12.                     Source = "Input",
  13.                 },
  14.                 EffectMask = InstanceInput {
  15.                     SourceOp = "CopyAndBlend",
  16.                     Source = "EffectMask",
  17.                 }
  18.             },
  19.             Outputs = {
  20.                 Output1 = InstanceOutput {
  21.                     SourceOp = "CopyAndBlend",
  22.                     Source = "Output",
  23.                 }
  24.             },
  25.             ViewInfo = GroupInfo {
  26.                 Pos = { 550, 148.5 },
  27.                 Flags = {
  28.                     AllowPan = false,
  29.                     ForceSource = true,
  30.                     GridSnap = true,
  31.                     ConnectedSnap = true,
  32.                     ShowGrid = false,
  33.                     AutoSnap = true,
  34.                     FullIndicator = true,
  35.                     RemoveRouters = true
  36.                 },
  37.                 Size = { 963, 203.3, 228, 66 },
  38.                 Direction = "Horizontal",
  39.                 PipeStyle = "Direct",
  40.                 Scale = 1,
  41.                 Offset = { -825, -38.8 }
  42.             },
  43.             Tools = ordered() {
  44.                 OutputCrop = Crop {
  45.                     CtrlWShown = false,
  46.                     NameSet = true,
  47.                     Inputs = {
  48.                         XOffset = Input { Value = 5, },
  49.                         YOffset = Input { Value = 5, },
  50.                         XSize = Input {
  51.                             Value = 1544,
  52.                             Expression = "InputCrop.Input.Width",
  53.                         },
  54.                         YSize = Input {
  55.                             Value = 1012,
  56.                             Expression = "InputCrop.Input.Height",
  57.                         },
  58.                         Input = Input {
  59.                             SourceOp = "BlurEdges",
  60.                             Source = "Output",
  61.                         },
  62.                         ClippingMode = Input { Value = FuID { "None" }, },
  63.                     },
  64.                     ViewInfo = OperatorInfo { Pos = { 1375, 82.5 } },
  65.                 },
  66.                 EdgeDetect = Filter {
  67.                     CtrlWShown = false,
  68.                     NameSet = true,
  69.                     Inputs = {
  70.                         FilterType = Input {
  71.                             Value = 3,
  72.                             Expression = "EdgeFinder.Filter+3",
  73.                         },
  74.                         Red = Input { Expression = "EdgeFinder.Red", },
  75.                         Green = Input { Expression = "EdgeFinder.Green", },
  76.                         Blue = Input { Expression = "EdgeFinder.Blue", },
  77.                         Alpha = Input { Expression = "EdgeFinder.Alpha", },
  78.                         Input = Input {
  79.                             SourceOp = "DupeEdges",
  80.                             Source = "Output",
  81.                         },
  82.                     },
  83.                     ViewInfo = OperatorInfo { Pos = { 880, 115.5 } },
  84.                 },
  85.                 DupeEdges = Transform {
  86.                     CtrlWShown = false,
  87.                     NameSet = true,
  88.                     Inputs = {
  89.                         UseSizeAndAspect = Input { Value = 0, },
  90.                         Edges = Input {
  91.                             Value = 2,
  92.                             Expression = "EdgeFinder.ExcludeImageEdges==1 and 2 or 0",
  93.                         },
  94.                         Input = Input {
  95.                             SourceOp = "InputCrop",
  96.                             Source = "Output",
  97.                         },
  98.                     },
  99.                     ViewInfo = OperatorInfo { Pos = { 770, 115.5 } },
  100.                 },
  101.                 InputCrop = Crop {
  102.                     CtrlWShown = false,
  103.                     NameSet = true,
  104.                     Inputs = {
  105.                         XOffset = Input { Value = -5, },
  106.                         YOffset = Input { Value = -5, },
  107.                         XSize = Input {
  108.                             Value = 1554,
  109.                             Expression = "InputCrop.Input.Width+10",
  110.                         },
  111.                         YSize = Input {
  112.                             Value = 1022,
  113.                             Expression = "InputCrop.Input.Height+10",
  114.                         },
  115.                         Input = Input {
  116.                             SourceOp = "BGInput",
  117.                             Source = "Output",
  118.                         },
  119.                     },
  120.                     ViewInfo = OperatorInfo { Pos = { 715, 82.5 } },
  121.                 },
  122.                 ErodeDilateEdges = ErodeDilate {
  123.                     CtrlWShown = false,
  124.                     NameSet = true,
  125.                     Inputs = {
  126.                         XAmount = Input { Expression = "EdgeFinder.ErodeDilate", },
  127.                         ClippingMode = Input { Expression = "EdgeFinder.ClippingMode", },
  128.                         Input = Input {
  129.                             SourceOp = "CopyDomain",
  130.                             Source = "Output",
  131.                         },
  132.                     },
  133.                     ViewInfo = OperatorInfo { Pos = { 1155, 82.5 } },
  134.                 },
  135.                 BlurEdges = Blur {
  136.                     CtrlWShown = false,
  137.                     NameSet = true,
  138.                     Inputs = {
  139.                         XBlurSize = Input {
  140.                             Value = 0,
  141.                             Expression = "EdgeFinder.Blur",
  142.                         },
  143.                         ClippingMode = Input { Expression = "EdgeFinder.ClippingMode", },
  144.                         Input = Input {
  145.                             SourceOp = "ErodeDilateEdges",
  146.                             Source = "Output",
  147.                         },
  148.                     },
  149.                     ViewInfo = OperatorInfo { Pos = { 1265, 82.5 } },
  150.                 },
  151.                 CopyDomain = SetDomain {
  152.                     CtrlWShown = false,
  153.                     NameSet = true,
  154.                     Inputs = {
  155.                         Input = Input {
  156.                             SourceOp = "EdgeDetect",
  157.                             Source = "Output",
  158.                         },
  159.                         Foreground = Input {
  160.                             SourceOp = "InputCrop",
  161.                             Source = "Output",
  162.                         },
  163.                     },
  164.                     ViewInfo = OperatorInfo { Pos = { 990, 82.5 } },
  165.                 },
  166.                 CopyAndBlend = ChannelBoolean {
  167.                     CtrlWShown = false,
  168.                     NameSet = true,
  169.                     Inputs = {
  170.                         Blend = Input { Expression = "EdgeFinder.Blend", },
  171.                         ToRed = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 0 or EdgeFinder.EdgeMatteOutput==1 and 10 or 5", },
  172.                         ToGreen = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 1 or EdgeFinder.EdgeMatteOutput==1 and 10 or 6", },
  173.                         ToBlue = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 2 or EdgeFinder.EdgeMatteOutput==1 and 10 or 7", },
  174.                         ToAlpha = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 3 or 10", },
  175.                         Background = Input {
  176.                             SourceOp = "BGInput",
  177.                             Source = "Output",
  178.                         },
  179.                         Foreground = Input {
  180.                             SourceOp = "OutputCrop",
  181.                             Source = "Output",
  182.                         },
  183.                     },
  184.                     ViewInfo = OperatorInfo { Pos = { 1430, 49.5 } },
  185.                 },
  186.                 BGInput = PipeRouter {
  187.                     CtrlWShown = false,
  188.                     NameSet = true,
  189.                     ViewInfo = PipeRouterInfo { Pos = { 660, 49.5 } },
  190.                 }
  191.             },
  192.             UserControls = ordered() {
  193.                 Filter = {
  194.                     { CCS_AddString = "Sobel" },
  195.                     { CCS_AddString = "Lapacian" },
  196.                     INP_Integer = false,
  197.                     ICS_ControlPage = "Controls",
  198.                     LINKID_DataType = "Number",
  199.                     CC_LabelPosition = "Horizontal",
  200.                     INPID_InputControl = "ComboControl",
  201.                     LINKS_Name = "Filter"
  202.                 },
  203.                 Red = {
  204.                     ICD_Width = 0.25,
  205.                     INP_Integer = true,
  206.                     INP_Default = 1,
  207.                     ICS_ControlPage = "Controls",
  208.                     CBC_TriState = false,
  209.                     LINKID_DataType = "Number",
  210.                     INPID_InputControl = "CheckboxControl",
  211.                     LINKS_Name = "Red"
  212.                 },
  213.                 Green = {
  214.                     ICD_Width = 0.25,
  215.                     INP_Integer = true,
  216.                     INP_Default = 1,
  217.                     ICS_ControlPage = "Controls",
  218.                     CBC_TriState = false,
  219.                     LINKID_DataType = "Number",
  220.                     INPID_InputControl = "CheckboxControl",
  221.                     LINKS_Name = "Green"
  222.                 },
  223.                 Blue = {
  224.                     ICD_Width = 0.25,
  225.                     INP_Integer = true,
  226.                     INP_Default = 1,
  227.                     ICS_ControlPage = "Controls",
  228.                     CBC_TriState = false,
  229.                     LINKID_DataType = "Number",
  230.                     INPID_InputControl = "CheckboxControl",
  231.                     LINKS_Name = "Blue"
  232.                 },
  233.                 Alpha = {
  234.                     ICD_Width = 0.25,
  235.                     INP_Integer = true,
  236.                     INP_Default = 1,
  237.                     ICS_ControlPage = "Controls",
  238.                     CBC_TriState = false,
  239.                     LINKID_DataType = "Number",
  240.                     INPID_InputControl = "CheckboxControl",
  241.                     LINKS_Name = "Alpha"
  242.                 },
  243.                 ErodeDilate = {
  244.                     INP_Integer = false,
  245.                     INPID_InputControl = "SliderControl",
  246.                     INP_MaxScale = 0.02,
  247.                     INP_Default = 0,
  248.                     INP_MinScale = -0.002,
  249.                     LINKID_DataType = "Number",
  250.                     ICS_ControlPage = "Controls",
  251.                     ICD_Center = 0,
  252.                     LINKS_Name = "Erode/Dilate"
  253.                 },
  254.                 Blur = {
  255.                     INP_Default = 0,
  256.                     INP_Integer = false,
  257.                     ICS_ControlPage = "Controls",
  258.                     LINKID_DataType = "Number",
  259.                     INP_MinScale = 0,
  260.                     INPID_InputControl = "SliderControl",
  261.                     INP_MaxScale = 100,
  262.                     LINKS_Name = "Blur"
  263.                 },
  264.                 Blend = {
  265.                     INP_Integer = false,
  266.                     LINKID_DataType = "Number",
  267.                     LINKS_Name = "Blend",
  268.                     INPID_InputControl = "SliderControl",
  269.                     ICS_ControlPage = "Controls",
  270.                     INP_Default = 1
  271.                 },
  272.                 ExcludeImageEdges = {
  273.                     CBC_TriState = false,
  274.                     INP_Integer = true,
  275.                     LINKID_DataType = "Number",
  276.                     INP_Default = 1,
  277.                     ICS_ControlPage = "Controls",
  278.                     INPID_InputControl = "CheckboxControl",
  279.                     LINKS_Name = "Exclude Image Edges"
  280.                 },
  281.                 EdgeMatteOutput = {
  282.                     { MBTNC_AddButton = "R|G|B|A" },
  283.                     { MBTNC_AddButton = "R=G=B=A" },
  284.                     { MBTNC_AddButton = "RGB -> A" },
  285.                     INP_Integer = true,
  286.                     INPID_InputControl = "MultiButtonControl",
  287.                     MBTNC_ShowBasicButton = true,
  288.                     INP_Default = 0,
  289.                     ICS_ControlPage = "Controls",
  290.                     LINKID_DataType = "Number",
  291.                     MBTNC_ShowName = true,
  292.                     MBTNC_StretchToFit = true,
  293.                     MBTNC_ShowToolTip = false,
  294.                     LINKS_Name = "Edge Matte Output"
  295.                 },
  296.                 ClippingMode = {
  297.                     { MBTNC_AddButton = "None" },
  298.                     { MBTNC_AddButton = "Domain" },
  299.                     { MBTNC_AddButton = "Frame" },
  300.                     INP_Integer = true,
  301.                     INPID_InputControl = "MultiButtonIDControl",
  302.                     MBTNC_ShowBasicButton = true,
  303.                     INPID_DefaultID = "Frame",
  304.                     ICS_ControlPage = "Controls",
  305.                     LINKID_DataType = "FuID",
  306.                     MBTNC_ShowName = true,
  307.                     MBTNC_StretchToFit = true,
  308.                     MBTNC_ShowToolTip = false,
  309.                     LINKS_Name = "Clipping Mode"
  310.                 }
  311.             }
  312.         }
  313.     },
  314.     ActiveTool = "EdgeFinder"
  315. }

I think I'll call it a day for now...
Hope you enjoyed this and learned a few new things. If I'm not mistaken @AndrewHazelden is dying to add something to this, too, so I'll pass the topic to him :D

Cheers!
You do not have the required permissions to view the files attached to this post.

User avatar
AndrewHazelden
Fusionator
Posts: 1380
Joined: Fri Apr 03, 2015 3:20 pm
Answers: 3
Location: West Dover, Nova Scotia, Canada
Been thanked: 56 times
Contact:

Re: How to deal with edges when using Sobel filter?

#38

Post by AndrewHazelden » Thu Jun 07, 2018 9:46 am

SecondMan wrote:
Thu Jun 07, 2018 8:30 am
Hope you enjoyed this and learned a few new things. If I'm not mistaken @AndrewHazelden is dying to add something to this, too, so I'll pass the topic to him :D

Thanks @Secondman.

Your excellent work on creating this tutorial thread would surely make Gringo and the original VFXPedia UserControls documentation team proud to see. :)

SecondMan wrote:
Wed Jun 06, 2018 10:52 pm
Good documentation is very hard. But also impressive enough to give you lots of bonus points.

Oooh. Someone said bonus points! I love collecting those. :D

Here's my entry to this WSL special edition tutorial creation event:


The Macro Loaded in Fusion

This is what the updated macro looks like when you load it into Fusion.

Fusion - Help Page on Macro.png

The Macro Loaded in Resolve

The macro's GUI also loads in the Resolve 15 too. The only issue at the moment is the Inspector window's width is non-resizable which makes the longer button labels start to clip at the edge. BOO-urns. :(

Resolve - Help Page on Macro.png

Download the Modified Macro

Code: [Select all] [Expand/Collapse] [Download] (EdgeFinderUltra.setting)
  1. {
  2.     Tools = ordered() {
  3.         EdgeFinderUltra = GroupOperator {
  4.             CustomData = {
  5.                 HelpPage = "https://www.steakunderwater.com/wesuckless/viewtopic.php?f=6&t=2206"
  6.             },
  7.             CtrlWZoom = false,
  8.             NameSet = true,
  9.             Inputs = ordered() {
  10.                 Input = InstanceInput {
  11.                     SourceOp = "BGInput",
  12.                     Source = "Input",
  13.                 },
  14.                 EffectMask = InstanceInput {
  15.                     SourceOp = "CopyAndBlend",
  16.                     Source = "EffectMask",
  17.                 },
  18.                 HelpTextText = Input {
  19.                     Value = "This help section provides you with resources and examples to help you master the art and science of Sobel related edge finding in Fusion 9 and Resolve 15.",
  20.                 },
  21.             },
  22.             Outputs = {
  23.                 Output1 = InstanceOutput {
  24.                     SourceOp = "CopyAndBlend",
  25.                     Source = "Output",
  26.                 }
  27.             },
  28.             ViewInfo = GroupInfo {
  29.                 Pos = { 550, 148.5 },
  30.                 Flags = {
  31.                     AllowPan = false,
  32.                     ForceSource = true,
  33.                     GridSnap = true,
  34.                     ConnectedSnap = true,
  35.                     ShowGrid = false,
  36.                     AutoSnap = true,
  37.                     FullIndicator = true,
  38.                     RemoveRouters = true
  39.                 },
  40.                 Size = { 963, 203.3, 228, 66 },
  41.                 Direction = "Horizontal",
  42.                 PipeStyle = "Direct",
  43.                 Scale = 1,
  44.                 Offset = { -825, -38.8 }
  45.             },
  46.             Tools = ordered() {
  47.                 OutputCrop = Crop {
  48.                     CtrlWShown = false,
  49.                     NameSet = true,
  50.                     Inputs = {
  51.                         XOffset = Input { Value = 5, },
  52.                         YOffset = Input { Value = 5, },
  53.                         XSize = Input {
  54.                             Value = 1544,
  55.                             Expression = "InputCrop.Input.Width",
  56.                         },
  57.                         YSize = Input {
  58.                             Value = 1012,
  59.                             Expression = "InputCrop.Input.Height",
  60.                         },
  61.                         Input = Input {
  62.                             SourceOp = "BlurEdges",
  63.                             Source = "Output",
  64.                         },
  65.                         ClippingMode = Input { Value = FuID { "None" }, },
  66.                     },
  67.                     ViewInfo = OperatorInfo { Pos = { 1375, 82.5 } },
  68.                 },
  69.                 EdgeDetect = Filter {
  70.                     CtrlWShown = false,
  71.                     NameSet = true,
  72.                     Inputs = {
  73.                         FilterType = Input {
  74.                             Value = 3,
  75.                             Expression = "EdgeFinder.Filter+3",
  76.                         },
  77.                         Red = Input { Expression = "EdgeFinder.Red", },
  78.                         Green = Input { Expression = "EdgeFinder.Green", },
  79.                         Blue = Input { Expression = "EdgeFinder.Blue", },
  80.                         Alpha = Input { Expression = "EdgeFinder.Alpha", },
  81.                         Input = Input {
  82.                             SourceOp = "DupeEdges",
  83.                             Source = "Output",
  84.                         },
  85.                     },
  86.                     ViewInfo = OperatorInfo { Pos = { 880, 115.5 } },
  87.                 },
  88.                 DupeEdges = Transform {
  89.                     CtrlWShown = false,
  90.                     NameSet = true,
  91.                     Inputs = {
  92.                         UseSizeAndAspect = Input { Value = 0, },
  93.                         Edges = Input {
  94.                             Value = 2,
  95.                             Expression = "EdgeFinder.ExcludeImageEdges==1 and 2 or 0",
  96.                         },
  97.                         Input = Input {
  98.                             SourceOp = "InputCrop",
  99.                             Source = "Output",
  100.                         },
  101.                     },
  102.                     ViewInfo = OperatorInfo { Pos = { 770, 115.5 } },
  103.                 },
  104.                 InputCrop = Crop {
  105.                     CtrlWShown = false,
  106.                     NameSet = true,
  107.                     Inputs = {
  108.                         XOffset = Input { Value = -5, },
  109.                         YOffset = Input { Value = -5, },
  110.                         XSize = Input {
  111.                             Value = 1554,
  112.                             Expression = "InputCrop.Input.Width+10",
  113.                         },
  114.                         YSize = Input {
  115.                             Value = 1022,
  116.                             Expression = "InputCrop.Input.Height+10",
  117.                         },
  118.                         Input = Input {
  119.                             SourceOp = "BGInput",
  120.                             Source = "Output",
  121.                         },
  122.                     },
  123.                     ViewInfo = OperatorInfo { Pos = { 715, 82.5 } },
  124.                 },
  125.                 ErodeDilateEdges = ErodeDilate {
  126.                     CtrlWShown = false,
  127.                     NameSet = true,
  128.                     Inputs = {
  129.                         XAmount = Input { Expression = "EdgeFinder.ErodeDilate", },
  130.                         ClippingMode = Input { Expression = "EdgeFinder.ClippingMode", },
  131.                         Input = Input {
  132.                             SourceOp = "CopyDomain",
  133.                             Source = "Output",
  134.                         },
  135.                     },
  136.                     ViewInfo = OperatorInfo { Pos = { 1155, 82.5 } },
  137.                 },
  138.                 BlurEdges = Blur {
  139.                     CtrlWShown = false,
  140.                     NameSet = true,
  141.                     Inputs = {
  142.                         XBlurSize = Input {
  143.                             Value = 0,
  144.                             Expression = "EdgeFinder.Blur",
  145.                         },
  146.                         ClippingMode = Input { Expression = "EdgeFinder.ClippingMode", },
  147.                         Input = Input {
  148.                             SourceOp = "ErodeDilateEdges",
  149.                             Source = "Output",
  150.                         },
  151.                     },
  152.                     ViewInfo = OperatorInfo { Pos = { 1265, 82.5 } },
  153.                 },
  154.                 CopyDomain = SetDomain {
  155.                     CtrlWShown = false,
  156.                     NameSet = true,
  157.                     Inputs = {
  158.                         Input = Input {
  159.                             SourceOp = "EdgeDetect",
  160.                             Source = "Output",
  161.                         },
  162.                         Foreground = Input {
  163.                             SourceOp = "InputCrop",
  164.                             Source = "Output",
  165.                         },
  166.                     },
  167.                     ViewInfo = OperatorInfo { Pos = { 990, 82.5 } },
  168.                 },
  169.                 CopyAndBlend = ChannelBoolean {
  170.                     CtrlWShown = false,
  171.                     NameSet = true,
  172.                     Inputs = {
  173.                         Blend = Input { Expression = "EdgeFinder.Blend", },
  174.                         ToRed = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 0 or EdgeFinder.EdgeMatteOutput==1 and 10 or 5", },
  175.                         ToGreen = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 1 or EdgeFinder.EdgeMatteOutput==1 and 10 or 6", },
  176.                         ToBlue = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 2 or EdgeFinder.EdgeMatteOutput==1 and 10 or 7", },
  177.                         ToAlpha = Input { Expression = "EdgeFinder.EdgeMatteOutput==0 and 3 or 10", },
  178.                         Background = Input {
  179.                             SourceOp = "BGInput",
  180.                             Source = "Output",
  181.                         },
  182.                         Foreground = Input {
  183.                             SourceOp = "OutputCrop",
  184.                             Source = "Output",
  185.                         },
  186.                     },
  187.                     ViewInfo = OperatorInfo { Pos = { 1430, 49.5 } },
  188.                 },
  189.                 BGInput = PipeRouter {
  190.                     CtrlWShown = false,
  191.                     NameSet = true,
  192.                     ViewInfo = PipeRouterInfo { Pos = { 660, 49.5 } },
  193.                 }
  194.             },
  195.             UserControls = ordered() {
  196.                 Filter = {
  197.                     { CCS_AddString = "Sobel" },
  198.                     { CCS_AddString = "Lapacian" },
  199.                     INP_Integer = false,
  200.                     ICS_ControlPage = "Controls",
  201.                     LINKID_DataType = "Number",
  202.                     CC_LabelPosition = "Horizontal",
  203.                     INPID_InputControl = "ComboControl",
  204.                     LINKS_Name = "Filter"
  205.                 },
  206.                 Red = {
  207.                     ICD_Width = 0.25,
  208.                     INP_Integer = true,
  209.                     INP_Default = 1,
  210.                     ICS_ControlPage = "Controls",
  211.                     CBC_TriState = false,
  212.                     LINKID_DataType = "Number",
  213.                     INPID_InputControl = "CheckboxControl",
  214.                     LINKS_Name = "Red"
  215.                 },
  216.                 Green = {
  217.                     ICD_Width = 0.25,
  218.                     INP_Integer = true,
  219.                     INP_Default = 1,
  220.                     ICS_ControlPage = "Controls",
  221.                     CBC_TriState = false,
  222.                     LINKID_DataType = "Number",
  223.                     INPID_InputControl = "CheckboxControl",
  224.                     LINKS_Name = "Green"
  225.                 },
  226.                 Blue = {
  227.                     ICD_Width = 0.25,
  228.                     INP_Integer = true,
  229.                     INP_Default = 1,
  230.                     ICS_ControlPage = "Controls",
  231.                     CBC_TriState = false,
  232.                     LINKID_DataType = "Number",
  233.                     INPID_InputControl = "CheckboxControl",
  234.                     LINKS_Name = "Blue"
  235.                 },
  236.                 Alpha = {
  237.                     ICD_Width = 0.25,
  238.                     INP_Integer = true,
  239.                     INP_Default = 1,
  240.                     ICS_ControlPage = "Controls",
  241.                     CBC_TriState = false,
  242.                     LINKID_DataType = "Number",
  243.                     INPID_InputControl = "CheckboxControl",
  244.                     LINKS_Name = "Alpha"
  245.                 },
  246.                 ErodeDilate = {
  247.                     INP_Integer = false,
  248.                     INPID_InputControl = "SliderControl",
  249.                     INP_MaxScale = 0.02,
  250.                     INP_Default = 0,
  251.                     INP_MinScale = -0.002,
  252.                     LINKID_DataType = "Number",
  253.                     ICS_ControlPage = "Controls",
  254.                     ICD_Center = 0,
  255.                     LINKS_Name = "Erode/Dilate"
  256.                 },
  257.                 Blur = {
  258.                     INP_Default = 0,
  259.                     INP_Integer = false,
  260.                     ICS_ControlPage = "Controls",
  261.                     LINKID_DataType = "Number",
  262.                     INP_MinScale = 0,
  263.                     INPID_InputControl = "SliderControl",
  264.                     INP_MaxScale = 100,
  265.                     LINKS_Name = "Blur"
  266.                 },
  267.                 Blend = {
  268.                     INP_Integer = false,
  269.                     LINKID_DataType = "Number",
  270.                     LINKS_Name = "Blend",
  271.                     INPID_InputControl = "SliderControl",
  272.                     ICS_ControlPage = "Controls",
  273.                     INP_Default = 1
  274.                 },
  275.                 ExcludeImageEdges = {
  276.                     CBC_TriState = false,
  277.                     INP_Integer = true,
  278.                     LINKID_DataType = "Number",
  279.                     INP_Default = 1,
  280.                     ICS_ControlPage = "Controls",
  281.                     INPID_InputControl = "CheckboxControl",
  282.                     LINKS_Name = "Exclude Image Edges"
  283.                 },
  284.                 EdgeMatteOutput = {
  285.                     { MBTNC_AddButton = "R|G|B|A" },
  286.                     { MBTNC_AddButton = "R=G=B=A" },
  287.                     { MBTNC_AddButton = "RGB -> A" },
  288.                     INP_Integer = true,
  289.                     INPID_InputControl = "MultiButtonControl",
  290.                     MBTNC_ShowBasicButton = true,
  291.                     INP_Default = 0,
  292.                     ICS_ControlPage = "Controls",
  293.                     LINKID_DataType = "Number",
  294.                     MBTNC_ShowName = true,
  295.                     MBTNC_StretchToFit = true,
  296.                     MBTNC_ShowToolTip = false,
  297.                     LINKS_Name = "Edge Matte Output"
  298.                 },
  299.                 ClippingMode = {
  300.                     { MBTNC_AddButton = "None" },
  301.                     { MBTNC_AddButton = "Domain" },
  302.                     { MBTNC_AddButton = "Frame" },
  303.                     INP_Integer = true,
  304.                     INPID_InputControl = "MultiButtonIDControl",
  305.                     MBTNC_ShowBasicButton = true,
  306.                     INPID_DefaultID = "Frame",
  307.                     ICS_ControlPage = "Controls",
  308.                     LINKID_DataType = "FuID",
  309.                     MBTNC_ShowName = true,
  310.                     MBTNC_StretchToFit = true,
  311.                     MBTNC_ShowToolTip = false,
  312.                     LINKS_Name = "Clipping Mode"
  313.                 },
  314.                 HelpTextText = {
  315.                     LINKS_Name = "HelpText",
  316.                     LINKID_DataType = "Text",
  317.                     INPID_InputControl = "TextEditControl",
  318.                     TEC_Wrap = true,
  319.                     TEC_ReadOnly = false,
  320.                     ICS_ControlPage = "Help"
  321.                 },
  322.                 OpenHelpButton = {
  323.                     LINKS_Name = "Open Reactor Comp Examples Folder",
  324.                     LINKID_DataType = "Number",
  325.                     INPID_InputControl = "ButtonControl",
  326.                     BTNCS_Execute = [[
  327. ------------------------------------------------------------------------
  328. -- Open a folder window up using your desktop file browser
  329. function OpenFile(mediaName)
  330.     if bmd.fileexists(mediaName) then
  331.         print('[Open File] ', mediaName)
  332.         bmd.openfileexternal('Open', mediaName)
  333.     else
  334.         print('[Open File Error] ', mediaName, ' is missing.')
  335.     end
  336. end
  337.  
  338. filename = comp:MapPath('Reactor:/Deploy/Comps/')
  339. OpenFile(filename)
  340. ]],
  341.                     ICS_ControlPage = "Help",
  342.                 },
  343.                 OpenOnlineHelpButton = {
  344.                     LINKS_Name = "WSL - How to deal with edges when using Sobel filter?",
  345.                     LINKID_DataType = "Number",
  346.                     INPID_InputControl = "ButtonControl",
  347.                     BTNCS_Execute = [[
  348. ------------------------------------------------------------------------
  349. -- Open a webpage window up using your default web browser
  350.                     platform = (FuPLATFORM_WINDOWS and "Windows") or (FuPLATFORM_MAC and "Mac") or (FuPLATFORM_LINUX and "Linux")
  351.                     function OpenURL(siteName, path)
  352.                         if platform == "Windows" then
  353.                             -- Running on Windows
  354.                             command = "explorer \"" .. path .. "\""
  355.                         elseif platform == "Mac" then
  356.                             -- Running on Mac
  357.                             command = "open \"" .. path .. "\" &"
  358.                          elseif platform == "Linux" then
  359.                             -- Running on Linux
  360.                             command = "xdg-open \"" .. path .. "\" &"
  361.                         else
  362.                             print("[Error] There is an invalid Fusion platform detected")
  363.                             return
  364.                         end
  365.                         os.execute(command)
  366.                         -- print("[Launch Command] ", command)
  367.                         print("[Opening URL] [" .. siteName .. "] " .. path)
  368.                     end
  369.  
  370.                     OpenURL("We Suck Less - How to deal with edges when using Sobel filter?", "https://www.steakunderwater.com/wesuckless/viewtopic.php?f=6&t=2206")
  371. ]],
  372.                     ICS_ControlPage = "Help",
  373.                 },
  374.                 UIManagerButton = {
  375.                     LINKS_Name = "Show UI Manager Help Window",
  376.                     LINKID_DataType = "Number",
  377.                     INPID_InputControl = "ButtonControl",
  378.                     BTNCS_Execute = [[
  379. -- Create a UI Manager dialog
  380. function HelpWindow()
  381.     -- Init UI Manager
  382.     local ui = fu.UIManager
  383.     local disp = bmd.UIDispatcher(ui)
  384.     local width,height = 640,740
  385.     local x = fu:GetMousePos()[1]
  386.     local y = fu:GetMousePos()[2]
  387.  
  388.     -- Help Resources
  389.     local URL = 'https://www.steakunderwater.com/wesuckless/'
  390.     local GetReactorURL = 'https://www.youtube.com/watch?v=iEIFl6gp58Q'
  391.    
  392.     -- Build the new window
  393.     win = disp:AddWindow({
  394.         ID = 'HelpWin',
  395.         TargetID = 'HelpWin',
  396.         WindowTitle = 'A Fusion Macro Help Window',
  397.         Geometry = {x-(width/2), y+8, width, height},
  398.         Spacing = 10,
  399.  
  400.         ui:VGroup{
  401.             ID = 'root',
  402.             -- Add your GUI elements here:
  403.  
  404.             -- Add the main Message
  405.             ui:TextEdit{
  406.                 ID = 'AboutText',
  407.                 ReadOnly = true,
  408.                 Alignment = {AlignHCenter = true, AlignTop = true},
  409.                 HTML = ReactorLogo() .. [=[
  410. <center><h1>About Edge Finder</h1></center>
  411. <center><h2>Version 1.0 - 2018-06-07</h2></center>
  412. <center><p>This dialog can be used to share important help details about a custom Fusion macro and all the exotic things you can do with it.</p></center>
  413.  
  414. <center><h2>Macro Node GUI</h2></center>
  415.  
  416. ]=] .. UserControlsImage() ..[=[
  417.  
  418. <center><p>This image shows the GUI of the EdgeFilter macro node in the Fusion tools view.</p></center>
  419.  
  420. <center><p>The macro has two tabs labelled "Controls" and "help".</p></center>
  421.  
  422. <center><p>In the "Controls" tab the macro has a Filter attribute that lets you switch between applying Sobel or Lapacian image filtering:</p></center>
  423.  
  424. <center><p>In the "Help" tab the macro has a series of Lua scripted ButtonControls that provide access to help resources.</p></center>
  425.  
  426. <center><h2>Macro Node Connections</h2></center>
  427. ]=] .. FlowViewImage() ..[=[
  428.  
  429. <center><p>This image shows a quick view of the EdgeFilter macro node connections in the Fusion Flow view.</p></center>
  430.  
  431. <center><p>The macro's yellow triangle shaped input connection accepts an RGB image that you want to apply frame boundary based filtering too. The red square output connection shows the result.</p></center>
  432.  
  433. <center><h2>Sobel Filtered Image With No Edge Filtering</h2></center>
  434.  
  435. <center><p>The raw image that is unfiltered and processed with the sobel effect will have a strange edge condition where there is a white line traced around the image DoD perimeter if the active content of the frame stretches out to the edge of the canvas.</p></center>
  436.  
  437. ]=] .. NoSobelImage() ..[=[
  438.  
  439. <center><h2>Sobel Filtered Image With Fusion Edge Filtering</h2></center>
  440.  
  441. <center><p>The edge filtered image that is processed with the sobel effect will have a correctly clipped edge boundary that leaves no unsightly stroke line like effects.</p></center>
  442.  
  443. ]=] .. SobelImage() ..  [=[
  444.  
  445. ]=]
  446.             },
  447.             -- Add clickable links
  448.             ui:VGroup{
  449.                 Weight = 0,
  450.  
  451.                 ui:Label{
  452.                     ID = "URL",
  453.                     Text = 'Web: <a style="color: rgb(139,155,216)" href="' .. URL .. '">' .. URL .. '</a>',
  454.                     Alignment = {
  455.                         AlignHCenter = true,
  456.                         AlignTop = true,
  457.                     },
  458.                     WordWrap = true,
  459.                     OpenExternalLinks = true,
  460.                 },
  461.  
  462.                 ui:Label{
  463.                     ID = "GetReactorURL",
  464.                     Text = 'Get Reactor YouTube Video: <a style="color: rgb(139,155,216)" href="' .. GetReactorURL .. '">' .. GetReactorURL .. '</a>',
  465.                     Alignment = {
  466.                         AlignHCenter = true,
  467.                         AlignTop = true,
  468.                     },
  469.                     WordWrap = true,
  470.                     OpenExternalLinks = true,
  471.                 },
  472.             },
  473.         },
  474.     })
  475.  
  476.     function win.On.HelpWin.Close(ev)
  477.         disp:ExitLoop()
  478.     end
  479.  
  480.     function win.On.DoneButton.Clicked(ev)
  481.         disp:ExitLoop()
  482.     end
  483.    
  484.     -- The app:AddConfig() command that will capture the "Control + W" or "Control + F4" hotkeys so they will close the window instead of closing the foreground composite.
  485.     app:AddConfig('HelpWin', {
  486.         Target {
  487.             ID = 'HelpWin',
  488.         },
  489.  
  490.         Hotkeys {
  491.             Target = 'HelpWin',
  492.             Defaults = true,
  493.            
  494.             CONTROL_W    = 'Execute{cmd = [=[app.UIManager:QueueEvent(obj, "Close", {})]=]}',
  495.             CONTROL_F4 = 'Execute{cmd = [=[app.UIManager:QueueEvent(obj, "Close", {})]=]}',
  496.         },
  497.     })
  498.    
  499.     itm = win:GetItems()
  500.     win:Show()
  501.     disp:RunLoop()
  502.     win:Hide()
  503.  
  504.     return win,win:GetItems()
  505. end
  506.  
  507. -- Reactor logo encoded as Base64 content
  508. -- Example: itm.Logo.HTML = ReactorLogo()
  509. function ReactorLogo()
  510.     return [=[<center><img width="68" height="68" src='data:image/png;base64,
  511. iVBORw0KGgoAAAANSUhEUgAAAEQAAABECAYAAAA4E5OyAAACmGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZkVYPSJodHRwOi8vY2lwYS5qcC9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOmF1eD0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC9hdXgvIgogICBleGlmRVg6R2FtbWE9IjExLzUiCiAgIGV4aWZFWDpMZW5zTW9kZWw9IiIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMTkyIgogICB0aWZmOkltYWdlV2lkdGg9IjE5MiIKICAgeG1wOkNyZWF0b3JUb29sPSJJbWFnZU1hZ2ljayA2LjcuOC05IDIwMTQtMDUtMTIgUTE2IGh0dHA6Ly93d3cuaW1hZ2VtYWdpY2sub3JnIgogICBhdXg6TGVucz0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+25K21QAAAYJpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHLS0JBFIc/tSjSMqhFixYS1cqijKQ2QUpYECFm0Guj11egdrlXCWkbtA0Koja9FvUX1DZoHQRFEUTQrnVRm4rbuSookWc4c775zZzDzBmwhtNKRq8bgEw2p4UCPtf8wqKr4QU7LbTTyXBE0dXxYHCamvZ5j8WMt31mrdrn/jV7LK4rYGkUHlNULSc8KTy9llNN3hFuV1KRmPCZsFuTCwrfmXq0xK8mJ0v8bbIWDvnB2irsSlZxtIqVlJYRlpfTnUnnlfJ9zJc44tm5WYld4p3ohAjgw8UUE/jxMsiozF768NAvK2rkDxTzZ1iVXEVmlQIaKyRJkcMtal6qxyUmRI/LSFMw+/+3r3piyFOq7vBB/bNhvPdAwzb8bBnG15Fh/ByD7Qkus5X81UMY+RB9q6J1H4BzA86vKlp0Fy42oeNRjWiRomQTtyYS8HYKzQvQdgNNS6Welfc5eYDwunzVNeztQ6+cdy7/AoHRZ/ILlAA2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAIxElEQVR4nO3ce4xdVRUG8N8pL0EpFqiIMQRBVIocQQz+AZbyVEGERigUkFdBaBXDozwMKgUxCojgEwQKChSwpaGAIBYjiBRj1aoHgQAWSVWIAiXThlao9PjH2sd7Z7zTmblzHzMJXzK5d86++5x1v7PO2mvv9e3LG+iFrJsXLwsbYnd8KR36ChZnude6ZVPXCCkLH8LHcRK2SYeX4Trcl+V+2w27Ok5IWdgah+EU7NTPxx7DD3B7lnu+U7bRQULKwvo4BF/Ee7BJalqKS9L7c7F9er8KT+Fi3Jnl/tMJO9tOSIoTW2EWTqxregl34rws90L67Hh8XRC3Rd1nr0/9/9nu+NI2QsrCengvDhJeMTY19eA+zM5y9/fTd39Mw8ewWTq8QnjLPXgyy73eDrvbScgFOBA7Y2OswQO4BXOz3OoB+m+MKTgKe2MDrMajuDfLXdgOu1tOSLq73xDesVFd06dxL3oGe3eTl20miL2prulVPImZ/XlZs2gJIWVhI2yNszAd66EUbj4PZ2W5FcO8xlhcjsPF45fhdVyVjj+f5V4dzjVoASFlYQdMxbFqI8QLWIQ5wr1XDfc66VqbCG85GntgfGpaihtxa5Z7ejjXaJqQZNw0HCGyzQ1S0xIxUjxYjR6tRhqNJuE8fDAdXoPF+LEI2E3dhKYIKQu74KvJqCqfWIFzcAdezHJrmzn3EGwYgy0xGZeqjWKr8CDOz3J/HOp5B01IMmBHnClcdiOsFcPoL3FhMwa0AukGXYC9RBAeIwLvHHwTTwz2Bg2KkLLwbhwgHpHKRV/D/SKoLcxya4bwHVqOsrCBsHE69seGqWkJZgsb/zLQedZJSFn8b8g7WeQCFf6Mr+HhLLdsyNa3EWVhG+yJL+D9dU0P4FoR5Hv6698vIWVhR3xfeMSm6bMvCI+YPdKI6ItEzDThMeNFGrBSeMyMLPdEo34NCSkLE0VcqLBCJEJHZrlnWmh321EWthOBPu/TtFeWe6jv58f0c55d694/gulZbvfRRkbCGNze4PiuDY5ZfxAnnJ3lbhmWSV1AXZ40BR8ebL/BENLWfKIdSMPwhdhPLU8aFAZDyKhAypPeJ/KkY9QmlmvwIs4XgfWGdZ2nvxgyqpAC56liRjxNkPE6Hsf3MDHL101EhVHtIXV50knYp0/zHEHGowOtvdRj1BJSFrYXK/RVnlRhIc7Gs80sOYwqQtKC0ZaYgS/XNa3BP8Ta69XDmWWPGkJS5rm3WITaua5pGebju63Ik0YFIWXhZByJ3dQWnV/FXPxQzKlasho/ogkpCxNwGSbiLXVNfxB5xi+y3MpWXnPEEZKm8ePwKbEINS41rRX5xOX4VivWTxthRBFSFrYQi8gnq627ELPs+bg2yy1ppw0jhpCyMFXUYPbEW9PhHlGYmifKmWW77eg6ISnLPFcEzbF1TctF6fMBrOwEGXSRkLKwlUizT1crJ1S4Msud0XmrukBIKiHsixPEGmgjTEiP0MNZ7m8dM04HCUmjxyRBxGS8qa55YXo9oO51En5SFq4WNZ6OLGJ3ZLabUu65uE0s2FRkPIVPispf9ff31LahkEXchrll4Z2dsLVtHlIWMjFaHCaK31XAXCuG0TtwSZ90+7aysFgE2ckitmyOQ7FPWZgpht+X2xVk2+IhKU4cItYnrlEjY7m44wdmuVMazT2y3DNZ7hR8AnelPtI5rhGlykPTNVqOlhNSFvbF1cL4g+qaHhHx47Qs97uBzpPlFqfPT8Nv6pr2E/qza8qi1/lbgpY9MkmucJlIuTdXK3FUGrI7styLQzlnlluOBWVhkXhsKg1a5YF7lYV5OHu4cosKwyKkTtDyEVyJbeual+NuYeywVACp/7Vl4WciHu0rSB+Hz+C4snAEfmUIgpxGaPqRSWK6g3GrSK23TU09WCCKWse3UhKR5ZZluSkiq12YrkWsoc5LthyeBDxNoSkPKQv7ied7TzXRbb2GbMG66qfDRZa7vyw8Jora9Rq0A7ALDikL1zcjtxoSISmynyrqpVv3aT5bjCrDctnBIss9VxZuFo/l54UcAt4mPGhSWbgO3x6Klw6KkBQw9xfBsZJNVcXjeTizVUFtKEjEL8essnAFviOC7aZ4u5CDTi0Ll4jhekAMhpA91FyzQqUhm4t7ukFGX2S5nrJwGn4qsuGPCjno9iIFmMTAMqvBEHJSn//briFrFilu3VoWfi4Su8+pLTQd1W/HOgxllHkFM8UOhvkjjYx6JNt+JGz9Gv492L79eUij9cqpYttGV6VTg0XSlP0rKap/LaYB9Wi4JtufhyxMJ6gfOm/CjLIwISVkIxplYb20aj9DbxV0j/huCxv1W5ekapyYmjfSms/Pcpe2xvT2oCycI6YRjbT2d2W5lxv1G0h0V6XmJ4iUucJqPK0NWvPhok5rv4MgosJMIYVYZ540FJ3qWKH5PFxNhLdSiPCu0iKteTOo09pPT3+bajJPGpKSuU5+MEVM6KpNPktFSfHGTqsTU833WByvljS+JCZ6cw0gw+yLZqXd44Ue4xy9hbyLxEpY01rzIdhQacgmi+SxXqh7qShzDjk1GI74v9KaHy+kCW9OTauEwnlWu6TeSUM2S2TQlYbsFVwkPLVprX2r9stUWvO99a7OXy9qsX8d7maAdAPeJeQQJ6ppyHrE6NESrX3LdlSlMsPBIqhN9P9a8wVZ7rkmz/0OsWLWV2v/kAjod7cqYWzHFrNKa36RWpAjtpfdbAhBri6IH5NeKywVj2nLtfbt3IS4rchfGmnNpwwU8FLgnqux1v6GLPdsO+zuxL7dncREawe9i9kXCZXgS1WilBLBLfBZvTVkK0QieFyWe6yd9nZyZ/fRYrVtN7UM8k+4QgRFIiifgQ+k/1fj90JIN6cTdnaSkDHYTtRqjlULjj3iS9NbQ7ZEbCy8B8+0e8tahW78GEIlmbpYKIUa4TohxX6508sN3f79kAminrNHOrQIp2e5x7tlU1cJodfPZ9CFn8d4AwPgv/k8pd+M44JRAAAAAElFTkSuQmCC
  512. '/></center>]=]
  513. end
  514.  
  515. -- Sample 320x240 image with no sobel applied encoded as Base64 content
  516. -- Example: itm.Logo.HTML = NoSobelImage()
  517. function NoSobelImage()
  518.     return [=[<center><img src='data:image/png;base64,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'/></center>]=]
  519. end
  520.  
  521. -- Sample 320x240 image with sobel applied encoded as Base64 content
  522. -- Example: itm.Logo.HTML = SobelImage()
  523. function SobelImage()
  524.     return [=[<center><img src='data:image/png;base64,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'/></center>]=]
  525. end
  526.  
  527. -- Sample 335x100 image of the node connections as Base64 content
  528. -- Example: itm.Logo.HTML = FlowViewImage()
  529. function FlowViewImage()
  530.     return [=[<center><img src='data:image/png;base64,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899Tf7++L3xfy363/bsPmB6oPsg4WNKANMxs6G0UNHY2JTd1HAo71NLs2XzwD9s/th02PFx1RPPI8qPUo0VHB44VHOs7Lj7ecyLjxKOWqS13Tk44efXU+FNtp8NPnzsTfObkWb+zx855nTt83uP8oQtuFxovulxsaHVuPfin858H21zaGi65Xmpqd29v7hjXcfSyz+UTVwKvnLnKvnrxWtS1jusJ12/emHSj8ybv5rNb2bde3c6/3X9n/l3C3ZJ7qvfK7+ver/nL8q89nS6dRx4EPmh9GPfwziPuoxePpY8/dxU9oT8pf2rwtO6Zw7PD3cHd7c8nPu96IX7R31P8t9rf1S8tXh74x/ef1t4JvV2vJK8GXi99o/1m21unty19MX333+W8639f8kH7w/aPbh/Pfkr69LR/+mfS54ovll+av4Z/vTuQMzAg5kg4g0cBDA40PR2A19sAoCfDs0M7ANSJirvaoCCK++UgAv8NK+5zg+ICwDZfABLmAxABzygb4DCFmAbf8iN4vC9AHR1HxpBI0x0dFL5o8MZC+DAw8EYPAFIzAF8kAwP96wcGvtRCsrcAOJ6ruCPKhQjP/Jts5ai96yX4Uf4D9b50FxyBDZgAAADAUExURdCAgIWbb3CahIZ9lzc1RQQwU4qJj01qmIBAADlGOicHK4l0buCgAG1UKC0AAjk5OSpmcHZwTLCw///QABYWFvT09KCgoHqjhikpKRMTE4akhiMjI3KMcysrK3JqMk4fB4yGpE1diiAggHNwi4N/oSNLhBxTY2iIlIZpVRsbGwIBAo6GpIAAAAYLKYaje2BPEAQPUCwtMkg8RlNcX1+EayUlJYuEoYOEo2lzosAAAIOihYWkhYOPWSYpKcvLyyZFyQpNAr4AAABAdFJOU////////////////////////////////////////////////////////////////////////////////////wDCe7FEAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKe0lEQVR4nO2ca3uivBaGC3iottjaugelUm2xTgsjVTm8bqLv//9Ze60cAAUpncm0H3YeT5BASO48Kwl0rrmIlWTqwpGv+P+4TMVTrhRPuVI85UrxlCvFU64UT7lSPOVK8ZQrxVOuFE+5UjzlSvGUK8VTrhRPuVI85UrxlCvFU64UT7lS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'/></center>]=]
  531. end
  532.  
  533.  
  534. -- Sample 328x457 image of the EdgeFinderUltra macro node's GUI as Base64 content
  535. -- Example: itm.Logo.HTML = UserControlsImage()
  536. function UserControlsImage()
  537.     return [=[<center><img src='data:image/png;base64,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'/></center>]=]
  538. end
  539.  
  540. -- Create a UI Manager dialog
  541. HelpWindow()
  542. ]],
  543.                     INP_Integer = false,
  544.                     ICS_ControlPage = "Help",
  545.                 },
  546.             }
  547.         }
  548.     },
  549.     ActiveTool = "EdgeFinderUltra"
  550. }

The HelpPage Tag


The previously mentioned "HelpPage" tag is nice in that it allows Windows based Fusion users to press F1 when the macro node is selected in the flow area and it will open up a help webpage link:

Code: Select all

CustomData = {
	HelpPage = "https://www.steakunderwater.com/wesuckless/viewtopic.php?f=6&t=2206",
},

The downside to this approach is that the HelpPage tag doesn't actually respond and open this weblink when you press the F1 hotkey and Fusion is running on macOS/Linux systems. Since I'm primarily using macOS/Linux for my compositing TD work I'd like to list some alternative options for providing users with access to help information about your fancy new Fusion macro.

Embedding Help Resources in a Macro

intelligent machine wrote:
Wed Jun 06, 2018 10:11 pm
I'd love to see a tab added to these tools with a link to the tutorial page and perhaps simplified usage instructions/examples.
...or if at all possible, another fusion reference plugin with a popup/dockable window which contains html formatted tutorial/example pages, a searchable index, and the ability to link to a currently selected or dragged in tools! ;)

@intelligent machine's post on this thread has a few workflow ideas so, heck, let's take things further and create a custom page tab on the macro called "Help". He also said he wanted a popup window that shows a HTML formatted tutorial page and we can do that, too by the end of this post. :wip:


Adding Control Page Tabs to a Macro

Adding Custom ControlPage Tabs.png

If you want to move an individual User Control element to another tab in your macros's GUI you can do it by adding a ControlPage tag like ICS_ControlPage = "Help" and it will assign the current GUI element to the named tab. If a tab of that name doesn't already exist it will be created automatically.

Here is the code to add a TextEditControl that will create a text field that will be visible in a new "Help" tab.

Code: [Select all] [Expand/Collapse] [Download] (HelpTextText.setting)
  1. HelpTextText = {
  2.     LINKS_Name = "HelpText",
  3.     LINKID_DataType = "Text",
  4.     INPID_InputControl = "TextEditControl",
  5.     TEC_Wrap = true,
  6.     TEC_ReadOnly = false,
  7.     ICS_ControlPage = "Help"
  8. },

If we want to pre-fill a message in the text field we added we can use the Inputs section on the macro to define the default contents of the "HelpTextText" named TextEditControl:

Code: [Select all] [Expand/Collapse] [Download] (HelpTextTextInputs.setting)
  1. Inputs = ordered() {
  2.     ...,
  3.     ...,
  4.     ...,
  5.     HelpTextText = Input { Value = "This help section provides you with resources and examples to help you master the art and science of Sobel related edge finding in Fusion 9 and Resolve 15.", },
  6. },

Opening a File or a Folder

open comp examples.png

I added a Open Reactor Comp Examples Folder ButtonControl to the new Help tab that allows the user to quickly open a Reactor:/Deploy/Comps/ example folder where your Reactor atom package might have saved the resources for your macro's accompanying .comp based tutorial files. This is a centralized location that is common for any Reactor composite example so it is easy for anyone to access and store content here when they create a new atom package.

Code: [Select all] [Expand/Collapse] [Download] (OpenHelpButton.setting)
  1. OpenHelpButton = {
  2.     LINKS_Name = "Open Reactor Comp Examples Folder",
  3.     LINKID_DataType = "Number",
  4.     INPID_InputControl = "ButtonControl",
  5.     BTNCS_Execute = [[
  6. ------------------------------------------------------------------------
  7. -- Open a folder window up using your desktop file browser
  8. function OpenFile(mediaName)
  9.     if bmd.fileexists(mediaName) then
  10.         print('[Open File] ', mediaName)
  11.         bmd.openfileexternal('Open', mediaName)
  12.     else
  13.         print('[Open File Error] ', mediaName, ' is missing.')
  14.     end
  15. end
  16.  
  17. filename = comp:MapPath('Reactor:/Deploy/Comps/')
  18. OpenFile(filename)
  19. ]],
  20.     ICS_ControlPage = "Help",
  21. },

Opening a Webpage URL

sobel filter weblink.png

I also added a WSL - How to deal with edges when using Sobel filter? ButtonControl that loads a WSL thread webpage in your default web browser to let people take part in the conversation thread on the tool. I'm using a slightly ugly piece of code that uses the native OS functions from the shell to open the default web browser so the button would function in both Fusion 9 and Resolve 15. (If we only needed this macro to work in Resolve 15+ we could take a shortcut and use the far simpler bmd.openurl(url) Lua function.)

Code: [Select all] [Expand/Collapse] [Download] (OpenOnlineHelpButton.setting)
  1. OpenOnlineHelpButton = {
  2.     LINKS_Name = "WSL - How to deal with edges when using Sobel filter?",
  3.     LINKID_DataType = "Number",
  4.     INPID_InputControl = "ButtonControl",
  5.     BTNCS_Execute = [[
  6. ------------------------------------------------------------------------
  7. -- Open a webpage window up using your default web browser
  8. platform = (FuPLATFORM_WINDOWS and "Windows") or (FuPLATFORM_MAC and "Mac") or (FuPLATFORM_LINUX and "Linux")
  9.  
  10. function OpenURL(siteName, path)
  11.     if platform == "Windows" then
  12.         -- Running on Windows
  13.         command = "explorer \"" .. path .. "\""
  14.     elseif platform == "Mac" then
  15.         -- Running on Mac
  16.         command = "open \"" .. path .. "\" &"
  17.     elseif platform == "Linux" then
  18.         -- Running on Linux
  19.         command = "xdg-open \"" .. path .. "\" &"
  20.     else
  21.         print("[Error] There is an invalid Fusion platform detected")
  22.         return
  23.     end
  24.     os.execute(command)
  25.     -- print("[Launch Command] ", command)
  26.     print("[Opening URL] [" .. siteName .. "] " .. path)
  27. end
  28.  
  29. OpenURL("We Suck Less - How to deal with edges when using Sobel filter?", "https://www.steakunderwater.com/wesuckless/viewtopic.php?f=6&t=2206")
  30. ]],
  31.     ICS_ControlPage = "Help",
  32. },

Tip: The command platform = (FuPLATFORM_WINDOWS and "Windows") or (FuPLATFORM_MAC and "Mac") or (FuPLATFORM_LINUX and "Linux") is a quick and easy way to get Fusion 9 / Resolve 15 to report back the current operating system. If you need to have your macro work in Fusion 7 or 8 you will need to use a more complex approach that is longer and a bit uglier to look at.


Creating a Window and Displaying HTML Content

ui manager window.png
And finally there is a Show UI Manager Help Window ButtonControl that shows a pop-up help window with HTML formatted help documentation including inline images that are base64 encoded PNGs attached right into the UserControl button code block.

The UI Manager tutorial series on WSL covers how to base64 encode images as part of the HTML Image Source example that uses a technique developed by @Dunn.

Fusion - Show UI Manager Help Window.png

Tip: When nesting inline Lua code in a UserControl element it is handy to use the Lua string escaping approach of double square brackets like BTNCS_Execute = [[--Your code here]], or BTNCS_Execute = [=[--Your code here]=], to allow you to enter multi-line text and not have to worry about the formatting and single/double quotes used.


Tip: It you wanted to, you could store the Lua code that is run on a ButtonControl GUI element external to your macro.setting file using a separate .lua text file that is linked to using either a relative PathMap or an absolute file path. This technique helps to cut down on the amount of inline code added and stored in each copy of the macro that is in your Fusion .comp file. The down side to using an external Lua file to hold your ButtonControl code is that you now have more files to install on each system that wants to use your macro.
You do not have the required permissions to view the files attached to this post.
Last edited by AndrewHazelden on Fri Jun 08, 2018 8:38 pm, edited 13 times in total.

User avatar
SirEdric
Fusionator
Posts: 1954
Joined: Tue Aug 05, 2014 10:04 am
Answers: 3
Real name: Eric Westphal
Been thanked: 133 times
Contact:

Re: How to deal with edges when using Sobel filter?

#39

Post by SirEdric » Thu Jun 07, 2018 9:53 am

And @AndrewHazelden 's next Tutorial will be about programming your very own Fusion GUI....:-)

User avatar
drswoboda
Posts: 13
Joined: Tue Aug 05, 2014 8:27 am
Location: Milwaukee, WI
Contact:

Re: How to deal with edges when using Sobel filter?

#40

Post by drswoboda » Fri Jun 08, 2018 8:24 pm

Wow, very impressive work here. Thank you for the tools and the lessons.
-David

User avatar
rslittle
Posts: 42
Joined: Fri Dec 12, 2014 11:32 am
Been thanked: 1 time
Contact:

Re: How to deal with edges when using Sobel filter?

#41

Post by rslittle » Sun Jun 10, 2018 5:28 pm

Do an xor merge instead of using the sobel? use a matte controller to handle the edge size?

User avatar
Midgardsormr
Fusionator
Posts: 1203
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 89 times
Contact:

Re: How to deal with edges when using Sobel filter?

#42

Post by Midgardsormr » Sun Jun 10, 2018 5:53 pm

Interesting. I'd never thought of using XOR that way. It really only detects anti-aliasing or blurred alpha, though, not the edge itself.

What exactly is the math behind XOR in a non-binary system?
edit: Never mind. It's in the Tool Reference Manual. Although the formula given there doesn't accord with the pixel values I'm seeing.

User avatar
rslittle
Posts: 42
Joined: Fri Dec 12, 2014 11:32 am
Been thanked: 1 time
Contact:

Re: How to deal with edges when using Sobel filter?

#43

Post by rslittle » Sun Jun 10, 2018 6:34 pm

pretty standard way. you just xor the the input by its self with a tiny blur on one input before you feed it into the xor. Then you have an edge. Or the other not horrible slow way is to just take a ccv curves and make a 0,1,0 curve and instant edge. Then use MT or erode to control and whatever other tools make it work. Sobel is just way to slow for just finding the edge of alpha channels or other. Naturally these only work when you are dealing with solid alpha or may require other tweaks.
Midgardsormr wrote:
Sun Jun 10, 2018 5:53 pm
Interesting. I'd never thought of using XOR that way. It really only detects anti-aliasing or blurred alpha, though, not the edge itself.

What exactly is the math behind XOR in a non-binary system?
edit: Never mind. It's in the Tool Reference Manual. Although the formula given there doesn't accord with the pixel values I'm seeing.

User avatar
Midgardsormr
Fusionator
Posts: 1203
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 3
Location: Los Angeles, CA, USA
Been thanked: 89 times
Contact:

Re: How to deal with edges when using Sobel filter?

#44

Post by Midgardsormr » Mon Jun 11, 2018 7:44 am

I like that it gives a falloff from the edge automatically instead of needing a separate blur. I'll do some tests; if it's faster and simpler than the convolutions, then I may update MT_EdgeBlur.

User avatar
rslittle
Posts: 42
Joined: Fri Dec 12, 2014 11:32 am
Been thanked: 1 time
Contact:

Re: How to deal with edges when using Sobel filter?

#45

Post by rslittle » Mon Jun 11, 2018 10:58 am

well it just gives you a crazy amount of control as you can also make controls to inside edge outside edge, middle. Control how much in either direction the edge goes etc. At SPI there was an edge macro that was based on the curve 0,1,0 method and then other stuff to add features. It was also pretty fast but for the inside outside stuff I always though xor was the most options and fastest math. but thats based on Nukes tools and how they work vs Fusion so.. whatever work and works fast. I just see so much crazy make it complicated cause it makes me look smart tools out there. One day I was just going around looking over shoulders and taking over the mouse and deleting parts of scripts (can do that when one is the boss :-) ) Then showing the fast way. I feel like no-one teaches comp optimization anymore.