Welcome to WSL!

Make yourself at home, but before posting, please may I ask you to read the following topics.


Posting 101
Server space, screenshots, and you

Thank you!

PS. please pretty please:


Image

[RC] Vibrance Fuse

Where the future is being made, today.

Welcome to the WSL development corner!

In this forum, please post your development projects. You get kudos and feedback here.
Topics ideally have preset prefixes, and this is what they (might) mean:

  • [DEV] - very much work in progress, don't build a business on this, could go anywhere
  • [BETA] - should kinda do what it's supposed to do, please test, give feedback
  • [RC] - this may end up in Reactor soon, polishing up, now's the time for last minute thoughts
  • [ABD] - died a premature death, sadness, will not see the light of day ever (unless someone picks up the scraps)

Once a development project has been released (hurray), topics can be marked as - you guessed it - [RELEASED] :cheer:

Development topics only, please. For generic questions, how-to's, questions and inquiries about existing tools etc, please go to the appropriate other forums.
User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

[RC] Vibrance Fuse

#1

Post by Shem Namo » Sat Feb 08, 2020 10:40 am

Hello Everyone, how's it going?
I thought I'd try my luck and see if I can write a very simple fuse over the weekend.
I opened up the VFXpedia "null" fuse in Wordpad++,
Sat there for an hour, to figure out that...
Spoiler
Show
I have no idea how to write a fuse.
The custom tool was made by Bryan @Midgardsormr over in this topic viewtopic.php?f=16&t=3783
I just thought it would be fun to try to make a fuse, but I don't know where to start(besides VFXpedia).
I bet there are a lot of people here who might want to start writing fuses so I thought it might be interesting to create
a topic here.

The only thing that works is a very small part of the UI.
I also think that I have the math expressions figured out, but don't know how to make the controls actually do something.

Code: Select all

	function Process(req) 
	local vibrance    = InVibrance:GetValue(req).Value
	
end

function
	return
	inter1(i1) = (r1 + g1 + b1) / 3
	inter2(i2) = max(r1, max(g1, b1))
	inter2(i3) = (i2 - i1) * (-vibrance * 3)
	inimage* (1-i3) + i2 * i3
end	
I would really appreciate if someone could please help me figure this out and this topic could probably help others learn more about Fuses in a less general way.

Thanks in Advanced,
David Kohen.

Code: [Select all] [Expand/Collapse] [Download] (Vibrance.fuse)
  1. FuRegisterClass("Vibrance", CT_Tool, {
  2.     REGS_Category = "Fuses\\Color",
  3.     REGS_OpIconString = "vib",
  4.     REGS_OpDescription = "Vibrance",
  5.     REG_OpNoMask = true,
  6.     REG_NoBlendCtrls = true,
  7.     REG_NoObjMatCtrls = true,
  8.     REG_NoMotionBlurCtrls = true,
  9.     })
  10.  
  11. function Create()
  12.  
  13.     InVibrance = self:AddInput("Vibrance", "Vibrance", {
  14.         LINKID_DataType = "Number",
  15.         INPID_InputControl = "SliderControl",
  16.         INP_MinScale = -0,
  17.         INP_MaxScale = 20,
  18.         INP_Default = 3,
  19.         })
  20.         InColorR= self:AddInput("Red", "Red1", {
  21.         ICS_Name            = "Color",
  22.         LINKID_DataType     = "Number",
  23.         INPID_InputControl  = "ColorControl",
  24.         INP_Default         = 0.2,
  25.         INP_MaxScale        = 1.0,
  26.         ICD_Center          = 1.0,
  27.         INP_DoNotifyChanged = true,
  28.         CLRC_ShowWheel      = False,
  29.         IC_ControlGroup     = 1,
  30.         IC_ControlID        = 0,
  31.         IC_Visible          = true,
  32.         })
  33.     InColorG = self:AddInput("Green", "Green1", {
  34.         LINKID_DataType     = "Number",
  35.         INPID_InputControl  = "ColorControl",
  36.         INP_Default         = 0.2,
  37.         INP_DoNotifyChanged = true,
  38.         IC_ControlGroup     = 1,
  39.         IC_ControlID        = 1,
  40.         })
  41.     InColorB = self:AddInput("Blue", "Blue1", {
  42.         LINKID_DataType     = "Number",
  43.         INPID_InputControl  = "ColorControl",
  44.         INP_Default         = 0.8,
  45.         INP_DoNotifyChanged = true,
  46.         IC_ControlGroup     = 1,
  47.         IC_ControlID        = 2,
  48.         })
  49.    
  50.  
  51.     InImage = self:AddInput("Input", "Input", {
  52.         LINKID_DataType = "Image",
  53.         LINK_Main = 1,
  54.         })
  55.  
  56.     OutImage = self:AddOutput("Output", "Output", {
  57.         LINKID_DataType = "Image",
  58.         LINK_Main = 1,
  59.         })             
  60. end
  61.  
  62.  
  63. function Process(req)
  64.     local img = InImage:GetValue(req)
  65.     img:Use()
  66.    
  67.    
  68.     OutImage:Set(req, img)
  69. end
Last edited by Shem Namo on Mon Mar 16, 2020 12:28 am, edited 2 times in total.

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

Re: [DEV] Vibrance Fuse

#2

Post by SecondMan » Sat Feb 08, 2020 11:04 am

Changed the topic title to be a bit more specific...
Shem Namo wrote:
Sat Feb 08, 2020 10:40 am
I have no idea how to write a fuse.
Start here, for a basic understanding: https://www.steakunderwater.com/VFXPedi ... ting_Fuses

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

Re: [DEV] Vibrance Fuse

#3

Post by Midgardsormr » Sat Feb 08, 2020 10:08 pm

I documented my first baby steps in Fuse writing on my blog:
http://www.bryanray.name/wordpress/voro ... ers-diary/

Perhaps my stumbling around can be of use. :)

Added in 23 minutes 44 seconds:
Shem Namo wrote:
Sat Feb 08, 2020 10:40 am

Code: Select all

function Process(req) 

	local vibrance    = InVibrance:GetValue(req).Value
	
end
This part is mostly right. It will import the value provided by the Vibrance slider into the process part of the Fuse. You also need to import the image to work on like so:

img = InImage:GetValue(req)

And you need to set up an output image to hold the result:

out = Image({ IMG_Like = img })

Image() is a function that creates a raster. It usually takes a table of parameters to set it up with the bit depth, resolution, channels, etc that you need. But you can use IMG_Like to just make a copy of an existing image instead, so you'll just inherit the properties of whatever's coming in.


Code: Select all

function
	return
	inter1(i1) = (r1 + g1 + b1) / 3
	inter2(i2) = max(r1, max(g1, b1))
	inter2(i3) = (i2 - i1) * (-vibrance * 3)
	inimage* (1-i3) + i2 * i3
end	
This is going to have some problems. For starters, you haven't named the function, so there's no way to address it. A correct function declaration looks like:

function name(arguments)

For instance, suppose I have this function:

Code: Select all

function add(a, b)
return a + b
end
It has a name, add; it takes inputs: a and b; and it returns an output. To call it in my program, I'd write something like sum = add(3, 5). That brings us to the next issue. If you put the return statement at the top of the function, it's going to immediately self-terminate before it's done anything. Usually you want to use return to send the calculated value back to your main function.

In this simple Fuse, though, I don't think you need a separate function to calculate Vibrance. You can do it all in Process() without too much trouble.

I didn't look back at the math, but from my recollection it looks correct. You can't put parenthesis in a variable name, though, because Lua will interpret that as a function call, and then probably complain that you can't assign a value to a function like that.

And finally, you have some options for how to actually execute the operation. The ProcessPixels() method in the article SecondMan linked is the most straightforward way to go about it, probably. Start there, and we can look at brute force methods (slow, but sometimes necessary) and multi-threading later. It might even be instructive to dip into GPU programming later.

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#4

Post by Shem Namo » Sat Feb 08, 2020 10:44 pm

Thanks Pieter, Thanks Bryan,
It looks like I have a lot of work to do, but this looks so cool!!
I'll start right away!

Thanks,
David.

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#5

Post by Shem Namo » Sat Feb 08, 2020 10:59 pm

Like this? How is is possible to work without parentheses? :shock:

Code: Select all

function Process(req) 
	local img = InImage:GetValue(req)
	local vibrance    = InVibrance:GetValue(req).Value
  local out = Image({ IMG_Like = img })
	
end

function	add (r1,g1,b1) 
	inter1"i1" = r1 + g1 + b1 / 3
	inter2"i2" = max r1, max g1, b1
	inter2"i3" = i2 - i1 * -vibrance * 3
	img* 1-i3 + i2 * i3
	return
end

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#6

Post by Shem Namo » Sun Feb 09, 2020 7:58 am

Here's what I have so far, but it's still not working.
The matte control is supposed to be to turn black values into alpha, and I'm still not sure how to do that.
And for some reason the additional controls I added aren't showing up on the fuse.

Could someone please take a look?

Thanks,
David.
Code: [Select all] [Expand/Collapse] [Download] (Vibrance.fuse)
  1. --[[--
  2. Vibrance.Fuse
  3.  
  4.  
  5.  
  6. version 1.0
  7. August 21st, 2007
  8.  
  9. --]]--
  10.  
  11. FuRegisterClass("Vibrance", CT_Tool, {
  12.     REGS_Category = "Fuses\\Color",
  13.     REGS_OpIconString = "vib",
  14.     REGS_OpDescription = "Vibrance",
  15.     REG_OpNoMask = true,
  16.     REG_NoBlendCtrls = true,
  17.     REG_NoObjMatCtrls = true,
  18.     REG_NoMotionBlurCtrls = true,
  19.     })
  20.  
  21. function Create()
  22.  
  23.     InVibrance = self:AddInput("Vibrance","Vibrance", {
  24.         LINKID_DataType = "Number",
  25.         INPID_InputControl = "SliderControl",
  26.         INP_MinScale = -0,
  27.         INP_MaxScale = 20,
  28.         INP_Default = 3,
  29.         })
  30.         InGain = self:AddInput("Gain", "Gain", {
  31.         LINKID_DataType = "Number",
  32.         INPID_InputControl = "SliderControl",      
  33.         INP_MinScale = -0,
  34.         INP_MaxScale = 5,
  35.         INP_Default = 1.0,
  36.         })
  37.         InGammma = self:AddInput("Gamma", "Gamma", {
  38.         LINKID_DataType = "Number",
  39.         INPID_InputControl = "SliderControl",
  40.         INP_MinScale = -0,
  41.         INP_MaxScale = 5,
  42.         INP_Default = 1.0,
  43.         })
  44.         InSaturation = self:AddInput("Saturation", "Saturation", {
  45.         LINKID_DataType = "Number",
  46.         INPID_InputControl = "SliderControl",
  47.         INP_MinScale = -0,
  48.         INP_MaxScale = 2,
  49.         INP_Default = 1.0,
  50.         })
  51.         InMatte = self:AddInput("Matte", "Matte", {
  52.         LINKID_DataType = "Number",
  53.         INPID_InputControl = "SliderControl",
  54.         INP_MinScale = -0,
  55.         INP_MaxScale = 1,
  56.         INP_Default = 1.0,
  57.         })
  58.         InColorR= self:AddInput("Red", "Red1", {
  59.         ICS_Name            = "Color",
  60.         LINKID_DataType     = "Number",
  61.         INPID_InputControl  = "ColorControl",
  62.         INP_Default         = 0.2,
  63.         INP_MaxScale        = 1.0,
  64.         ICD_Center          = 1.0,
  65.         INP_DoNotifyChanged = true,
  66.         CLRC_ShowWheel      = False,
  67.         IC_ControlGroup     = 1,
  68.         IC_ControlID        = 0,
  69.         IC_Visible          = true,
  70.         })
  71.     InColorG = self:AddInput("Green", "Green1", {
  72.         LINKID_DataType     = "Number",
  73.         INPID_InputControl  = "ColorControl",
  74.         INP_Default         = 0.2,
  75.         INP_DoNotifyChanged = true,
  76.         IC_ControlGroup     = 1,
  77.         IC_ControlID        = 1,
  78.         })
  79.     InColorB = self:AddInput("Blue", "Blue1", {
  80.         LINKID_DataType     = "Number",
  81.         INPID_InputControl  = "ColorControl",
  82.         INP_Default         = 0.8,
  83.         INP_DoNotifyChanged = true,
  84.         IC_ControlGroup     = 1,
  85.         IC_ControlID        = 2,
  86.         })
  87.    
  88.  
  89.     InImage = self:AddInput("Input", "Input", {
  90.         LINKID_DataType = "Image",
  91.         LINK_Main = 1,
  92.         })
  93.  
  94.     OutImage = self:AddOutput("Output", "Output", {
  95.         LINKID_DataType = "Image",
  96.         LINK_Main = 1,
  97.         })             
  98. end
  99.  
  100.  
  101. function Process(req)
  102.     local img = InImage:GetValue(req)
  103.     local vibrance    = InVibrance:GetValue(req).Value
  104.     local gain = InGain:GetValue(req).Value
  105.     local gamma = InGamma:GetValue(req).Value
  106.     local saturation = InSaturation:GetValue(req).Value
  107.     local matte = InMatte:GetValue(req).Value
  108.     local red = InColorR:GetValue(req).Value
  109.   local green = InColorG:GetValue(req).Value
  110.   local blue = InColorB:GetValue(req).Value
  111.   local out = Image({ IMG_Like = img })
  112.    
  113. end
  114.  
  115. function    add (r1,g1,b1)
  116.     "i1" = r1 + g1 + b1 / 3
  117.     "i2" = max r1, max g1, b1
  118.     "i3" = i2 - i1 * -vibrance * 3
  119.     img* 1-i3 + i2 * i3
  120.     return
  121. end
  122.  
  123.  
  124.     out:ProcessPixels(0,0, img.Width, img.Height, img,
  125.     function(x,y,p)
  126.         p.R = p.R * gain
  127.         p.G = p.G * gain
  128.         p.B = p.B * gain
  129.         return p
  130.         end
  131.        
  132.             out:ProcessPixels(0,0, img.Width, img.Height, img,
  133.     function(x,y,p)
  134.         p.R = p.R ^ gamma
  135.         p.G = p.G ^ gamma
  136.         p.B = p.B ^ gamma
  137.         return p
  138.         end
  139.        
  140.         function
  141.         src:Saturate(sourceSat,sourceSat, sourceSat)
  142.         return
  143.     end
  144.        
  145. function multiply (r1,red)
  146. r1*red
  147. return
  148. end
  149. function multiply (g1,green)
  150. g1*green
  151. return
  152. end
  153. function multiply (b1,blue)
  154. b1*blue
  155. return
  156. end
  157.    
  158.    
  159.     OutImage:Set(req, img)
  160. end
  161.  
  162.  

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

Re: [DEV] Vibrance Fuse

#7

Post by Midgardsormr » Sun Feb 09, 2020 8:47 am

Quick edit: Looks like I cross-posted with you. There's a bit more to say regarding your latest post. I'll get to it momentarily.


Wow, no. Just all kinds of no. I didn't mean you can't use parentheses at all, I just said you can't use them in variable names!. Parentheses are essential for maintaining the order of operations in the expressions themselves. And you can't use quotes in variable names either (but again, they're quite important in other areas—I'm just talking about variable declarations).

Perhaps we should start with the basic basics. From the book Programming in Lua (first edition is available online for free. Here is the relevant page: https://www.lua.org/pil/1.3.html ), "Identifiers in Lua can be any string of letters, digits, and underscores, not beginning with a digit."

The word identifier here is synonymous with variable. I recommend keeping that book on hand, and reading at least the first five chapters, as well as chapters 11, and 18-20. You don't have to grasp everything immediately—just get an overview first, and keep it on hand for reference. I have a physical copy on my desk at all times, and I open it frequently.

As a matter of style, it's best if your variable names (and function names) actually describe what they hold. Your function isn't adding numbers together, so it's not a good idea to call it add(). What you have there is the entire vibrance operation, so you'd probably want to simply call it vibrance(), or better vibranceFunc(), since you're already using vibrance as a variable name.

In addition, inter1_i1 (an acceptable format for a variable name) won't mean anything to someone who isn't actually looking at a CustomTool. It's a pretty good bet that anyone looking at a Fuse is familiar with CustomTool, but you shouldn't make the assumption. If it's important to you to keep track of the relationship between the variables and where they were used in CustomTool, you can use a comment to do so. In Lua, comments start with --. Anything that follows on the same line will not be treated as executable code.
  1. function vibranceFunc(p, vibrance)
  2.     channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  3.     channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  4.     amount = (channelMaximum - channelAverage) * (-vibrance * 3)
  5.     p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  6.     p.G = p.G * (1-amount) + channelMaximum * amount   
  7.     p.B = p.B * (1-amount) + channelMaximum * amount
  8.    
  9.     return p
  10. end
Every value that a function is going to use should be passed to it as an argument. r1, etc doesn't mean anything because you haven't defined it. In a CustomTool, the channels and inputs are pre-set for convenience, but a Fuse doesn't know anything about that. All it has so far is the imported full raster, which you've stored in img, and the values from any controls, which you stored in vibrance. So you need to give the function those values before it knows what to do with them.

As you can see above, I've passed two variables: p and vibrance. We know where vibrance came from, but what about p? The CustomTool doesn't operate on the entire image at once—it addresses each and every pixel individually. Since this Fuse is based on the CustomTool, we're using the same approach. Since we need to perform the operation on every pixel, it makes the most sense to have the function work on that smallest piece instead of having it try to do the entire image. That way we can say "For every pixel, execute vibranceFunc()." It will be easier to maintain, and easier to multi-thread later. So we need to figure out how to get that individual pixel object. The Fuse API has a method for that:
  1. p = Pixel()
  2. img:GetPixel(x,y,p)
That creates a pixel object and stores it in p, then retrieves the channel values for that pixel at the designated (x,y) coordinates. Before we talk about retrieving a pixel from a specific coordinate, let's talk a little about the structure of the object that Pixel() creates. Lua has the ability to store multiple values in a single variable, in a structure called a table. A table can consist of just a list of things: fruits = { "apple", "orange", "banana" }, or it can be a more complex structure, with each value corresponding to a key. When you create a pixel, you get a table containing a key for each channel, and the value that the channel holds. For instance, a typical pixel might look like this:
  1. p = {
  2.     ["R"] = 0.2,
  3.     ["G"] = 0.6,
  4.     ["B"] = 0.9,
  5.     ["A"] = 1.0,
  6. }
You can access one of the channels in two ways: p.R or p["R"]. Obviously, most of the time the former is easiest, but there are cases when you might need to use the other one.

So if you write p.R = p.R + 0.1, the current value of the Red channel for that pixel will be added to 0.1, and the result stored back into p.R. You can't do math on the entire pixel at once in Lua—you have to work on each channel individually. But you can move the pixel around as a variable as a unit. So we can send p into the function as an argument and use return p to return it to the main program.

Okay, now for the work! As I said, you want to call the function once for every pixel, and you need the x and y coordinates for each pixel in order to retrieve its original value. Since you're just getting started, we'll look at the brute force method using for loops first. This is the slowest way, but it's the easiest to understand.

Every programming language has some way of performing repeated operations. We call these loops. Probably the most common is the for loop, which is used to perform an operation on every member of a collection. In this case, we'll use it to iterate over the pixels in our raster. Unfortunately, images don't make things easy because they're two-dimensional. We therefore need to iterate in two directions: x and y. That requires us to use two for loops: one nested inside the other. The numeric for, which we're using here, is described in Programming in Lua: https://www.lua.org/pil/4.3.4.html.

Based on what you read there, you know that we need a starting value and an ending value. Since we want to process every pixel, we can leave the step value out—the numeric for will default to 1, which is what we want. An image's coordinates start at 0 in the lower left corner. That means that the last pixel in each row will have an x coordinate equal to the image's width - 1. Likewise, the last pixel in each column is at the image's height - 1. Fortunately, we can get width and height from the img object itself: img.Width and img.Height contain that information.
  1. p = Pixel()     -- Create the pixel object only once
  2. for x=0, img.Width - 1 do
  3.     for y = 0, img.Height - 1 do
  4.         img:GetPixel(x,y, p)
  5.         p = vibranceFunc(p, vibrance)
  6.     end
  7. end
  8.    
So, between all of that information and the link SecondMan provided, you should have all of the puzzle pieces. See if you can't assemble them into a working Fuse. Once you have it operating, take a look at this Gain fuse and try to figure out how to implement the three other methods it demonstrates:
https://www.steakunderwater.com/VFXPedi ... /Gain.Fuse

Added in 23 minutes 1 second:
Shem Namo wrote:
Sun Feb 09, 2020 7:58 am
for some reason the additional controls I added aren't showing up on the fuse.
Your control panel is actually working fine from what I can see. The problem is that you have loads of syntax errors in the rest of the code, which prevent it from compiling. So when you refresh nothing will happen—Fusion will get an error when it tries to read the new Fuse and just won't update it. There are probably lots of error messages in your console telling you exactly where the problems are.

Now, you seem to be using the spaghetti method: Throw everything at the wall and hope something sticks. That's not going to get you anywhere. You need to understand exactly what each and every line of code does before you try to use it or, at best, you'll get incorrect output. Far more likely is you'll get lots of error messages. Sometimes you might wind up with a condition that crashes Fusion, or even your entire computer.

One more specific concern: You can't have three functions with the same name in the same program—when you call the function, the program will only ever run the last one you defined because it's overwritten the other two, just like a variable. If you write:
myVariable = 6
myVariable = 12
print(myVariable)


You will always get 12 in your output because the 6 was destroyed by being overwritten. Functions work the same way—the most recent declaration of the same name is the only one the program will be aware of (barring some unusual program flow design).

And you don't need that multiply function at all, really, because Lua does multiplication just fine. If you need to multiply two pixels, you can just multiply each channel. For instance, let's suppose your Fuse takes two images, which you've assigned to img1 and img2, and a third image for output called out. We'll use that same for loop I wrote above to multiply the two images together, pixel by pixel:
  1. p1 = Pixel()
  2. p2 = Pixel()
  3. pOut = Pixel()
  4. for x=0, img1.Width-1 do
  5.     for y = 0, img1.Height-1 do
  6.         img1:GetPixel(x,y, p1)
  7.         img2:GetPixel(x,y, p2)
  8.         pOut.R = p1.R * p2.R
  9.         pOut.G = p1.G * p2.G
  10.         pOut.B = p1.B * p2.B
  11.         pOut.A = p1.A * p2.A
  12.         out:SetPixel(x,y, pOut)
  13.     end
  14. end
There's no need to write a multiply() function because Lua does multiplication already. You also have a Saturate() call in there, which is out of place because it uses an entirely different paradigm than what you've been learning. I imagine we'll talk about the color matrix eventually here, but I think it's important that you get individual pixel processing figured out first.
Last edited by Midgardsormr on Thu Feb 13, 2020 7:01 am, edited 1 time in total.

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#8

Post by Shem Namo » Sun Feb 09, 2020 9:31 am

Thanks Bryan, this is going to last me a while :) :D
Nice analogy about the spaghetti, by the way,
that's about how I'd describe what I was doing.

Thanks for taking your time to write all of this interesting information,
I really appreciate it.

Thanks again,
David.

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#9

Post by Shem Namo » Sun Feb 09, 2020 1:07 pm

I think I'm this close to getting this thing to work,
even after all of the great info you posted, I still seem to be missing something.
I followed all of your instructions and even temporarily removed most of the other controls.
I'll add them back one at a time to make sure they all work as intended.

I'm pretty sure the issue is the

Code: Select all

local p = Pixel()
 for y=0,img.Height-1 do
 for x=0,img.Width-1 do
 img:GetPixel(x,y, p)
 p = vibranceFunc(p, vibrance)
     end
  end
part, but I can't figure out what is the specific issue.
Could you please give me a hint?

Thanks Again,
David.
Code: [Select all] [Expand/Collapse] [Download] (beta vibrance.fuse)
  1. FuRegisterClass("Vibrance", CT_Tool, {
  2.     REGS_Category = "Fuses\\Color",
  3.     REGS_OpIconString = "vib",
  4.     REGS_OpDescription = "Vibrance",
  5.     REG_OpNoMask = true,
  6.     REG_NoBlendCtrls = true,
  7.     REG_NoObjMatCtrls = true,
  8.     REG_NoMotionBlurCtrls = true,
  9.     })
  10.  
  11. function Create()
  12.  
  13.     InVibrance = self:AddInput("Vibrance", "Vibrance", {
  14.         LINKID_DataType = "Number",
  15.         INPID_InputControl = "SliderControl",
  16.         INP_MinScale = -0,
  17.         INP_MaxScale = 20,
  18.         INP_Default = 3,
  19.         })
  20.    
  21.  
  22.     InImage = self:AddInput("Input", "Input", {
  23.         LINKID_DataType = "Image",
  24.         LINK_Main = 1,
  25.         })
  26.  
  27.     OutImage = self:AddOutput("Output", "Output", {
  28.         LINKID_DataType = "Image",
  29.         LINK_Main = 1,
  30.         })            
  31. end
  32.  
  33.  
  34. function Process(req)
  35.     local img = InImage:GetValue(req)
  36.     img:Use()
  37.  function Process(req)
  38.  local img = InImage:GetValue(req)
  39.  local vibrance    = InVibrance:GetValue(req).Value
  40.  local result = Image({ IMG_Like = img })
  41.  
  42.  
  43.  local p = Pixel()
  44.  for y=0,img.Height-1 do
  45.  for x=0,img.Width-1 do
  46.  img:GetPixel(x,y, p)
  47.  p = vibranceFunc(p, vibrance)
  48.      end
  49.   end
  50.  
  51.    
  52.    
  53.     function vibranceFunc(p, vibrance)
  54.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  55.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  56.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)
  57.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  58.           p.G = p.G * (1-amount) + channelMaximum * amount  
  59.           p.B = p.B * (1-amount) + channelMaximum * amount
  60.     return p
  61. end
  62.  
  63.  
  64.  
  65.    
  66.     OutImage:Set(req, result)
  67. end

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

Re: [DEV] Vibrance Fuse

#10

Post by Midgardsormr » Sun Feb 09, 2020 3:39 pm

Two things: First, you still need to set the pixel to the resulting value:

out:SetPixel(x,y, p)

Second, your function declaration is happening too late. The function doesn't exist until it's declared, so when you try to call it in the loop, the Fuse doesn't know what it means yet. Typically you'd declare the function outside of Process(). That way every other function that wants to use it can see it in the global scope. You could, technically still declare it inside Process(), but it would have to go above the loops.

As a matter of style, you'll want to keep better track of your indentation in order to make the program easier to read. Lua doesn't care about indents semantically, but other programmers will thank you if it's easy to tell which lines are part of a loop or a function. And if you ever switch to Python, the indentation is meaningful, so it's a good idea to get used to using it just so you have the habit.

Oh, and you can get rid of img:Use(). That was only necessary in earlier versions of Fusion in the specific case of passing an image from input to output without doing any other processing. So it was really only useful in Null and the Metadata Fuses to begin with, and now it's completely obsolete.

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#11

Post by Shem Namo » Sun Feb 09, 2020 11:20 pm

Okay, now I'm in trouble.
Fusion starts without error, I open the fuse, view it, it doesn't become red, I view it, nothing there, after 2 minutes fusion and the whole system crashes.
I looked at the whole thing and couldn't find out why.
Could someone please help?

Thanks,
David.
  1. FuRegisterClass("Vibrance", CT_Tool, {
  2.     REGS_Category = "Fuses\\Color",
  3.     REGS_OpIconString = "vib",
  4.     REGS_OpDescription = "Vibrance",
  5.     REG_OpNoMask = true,
  6.     REG_NoBlendCtrls = true,
  7.     REG_NoObjMatCtrls = true,
  8.     REG_NoMotionBlurCtrls = true,
  9.     })
  10.  
  11. function Create()
  12.  
  13.     InVibrance = self:AddInput("Vibrance", "Vibrance", {
  14.         LINKID_DataType = "Number",
  15.         INPID_InputControl = "SliderControl",
  16.         INP_MinScale = -0,
  17.         INP_MaxScale = 20,
  18.         INP_Default = 3,
  19.         })
  20.    
  21.  
  22.     InImage = self:AddInput("Input", "Input", {
  23.         LINKID_DataType = "Image",
  24.         LINK_Main = 1,
  25.         })
  26.  
  27.     OutImage = self:AddOutput("Output", "Output", {
  28.         LINKID_DataType = "Image",
  29.         LINK_Main = 1,
  30.         })            
  31. end
  32.  
  33.  
  34.  function Process(req)
  35.  local img = InImage:GetValue(req)
  36.  local vibrance = InVibrance:GetValue(req).Value
  37.  local p = Pixel()
  38.  for y=0,img.Height-1 do
  39.  for x=0,img.Width-1 do
  40.  local result = Image({ IMG_Like = img })
  41. end
  42.  
  43.  
  44.  
  45.    
  46.        
  47.         function vibranceFunc(p, vibrance)
  48.         local p = vibranceFunc(p, vibrance)
  49.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  50.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  51.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  52.               In:GetPixel(x,y, p)
  53.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  54.           p.G = p.G * (1-amount) + channelMaximum * amount  
  55.           p.B = p.B * (1-amount) + channelMaximum * amount                 
  56.     return p
  57. end
  58. result:SetPixel(x,y, p)
  59. end
  60.  
  61.  
  62.  
  63.    
  64.     OutImage:Set(req, result)
  65. end

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

Re: [DEV] Vibrance Fuse

#12

Post by Midgardsormr » Mon Feb 10, 2020 7:42 am

I said move the function outside of process, not move it inside the loops! You're now declaring the function over and over again in the for loops. I'm going to give you a little prototype code. This isn't a working fuse or program, just an illustration of how it should look:
  1. --Function declaration. This function receives three arguments as inputs and returns a table containing those arguments as output.
  2. --Functions are usually separate from one another like this. It's rare to see them nested inside one another.
  3. function myCoolFunction(arg1, arg2, arg3)
  4.     local out = {}
  5.     out[1] = arg1
  6.     out[2] = arg2
  7.     out[3] = arg3
  8.  
  9.     return out
  10. end     -- End of myCoolFunction(). When things get complex with nested loops or if statements, I like to put a comment on the
  11.         -- end statement to help me keep track of them.
  12.  
  13.  
  14. -- In a Fuse, Process() gets called by Fusion and will always run (assuming it doesn't crash before we get here).
  15. -- Other functions will not activate until they are activated by code inside Process(), but every function will be
  16. -- compiled prior to running.
  17. function Process(req)
  18.  
  19.     local myVariable
  20.    
  21.  
  22.     if myVariable then      -- This checks to see whether there is information in the variable.
  23.         print("This variable has stuff in it!")
  24.     else
  25.         myVariable = myCoolFunction("apple", "banana", 17)
  26.     end -- End of if statment
  27.  
  28.  
  29.     for i=1, 35 do
  30.         local thisNumber
  31.         thisNumber = myVariable[3] + i
  32.         print("We're counting up from "..myVariable[3].." for some reason. The next number is "..thisNumber)
  33.     end -- End of for loop
  34.    
  35. end -- End of Process()
  36.  
  37. -- Also, notice how the indentation shows how some lines are subordinate to others. Everything that happens inside a function or a loop is
  38. -- indented one level further than the lines that start and end the structure. Again, this doesn't have any meaning to Lua, but it makes it
  39. -- much easier for you, and anyone else looking at your code, to see what the intended structure is.
  40.  

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#13

Post by Shem Namo » Mon Feb 10, 2020 10:01 am

Thanks Bryan! By any chance, is there a very simple fuse that does something similar(besides the gain fuse) that I can look at to see how it works?
What I still can figure out is, what part of this fuse is actually the "for loop" part?

I really appreciate the help,
Thanks,
David.
  1. FuRegisterClass("Vibrance", CT_Tool, {
  2.     REGS_Category = "Fuses\\Color",
  3.     REGS_OpIconString = "vib",
  4.     REGS_OpDescription = "Vibrance",
  5.     REG_OpNoMask = true,
  6.     REG_NoBlendCtrls = true,
  7.     REG_NoObjMatCtrls = true,
  8.     REG_NoMotionBlurCtrls = true,
  9.     })
  10.  
  11.     function Create()
  12.  
  13.     InVibrance = self:AddInput("Vibrance", "Vibrance", {
  14.         LINKID_DataType = "Number",
  15.         INPID_InputControl = "SliderControl",
  16.         INP_MinScale = -0,
  17.         INP_MaxScale = 20,
  18.         INP_Default = 3,
  19.         })
  20.    
  21.  
  22.     InImage = self:AddInput("Input", "Input", {
  23.         LINKID_DataType = "Image",
  24.         LINK_Main = 1,
  25.         })
  26.  
  27.     OutImage = self:AddOutput("Output", "Output", {
  28.         LINKID_DataType = "Image",
  29.         LINK_Main = 1,
  30.         })            
  31.     end
  32.    
  33.         function vibranceFunc(p, vibrance)
  34.         local p = vibranceFunc(p, vibrance)
  35.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  36.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  37.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  38.               img:GetPixel(x,y, p)
  39.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  40.           p.G = p.G * (1-amount) + channelMaximum * amount  
  41.           p.B = p.B * (1-amount) + channelMaximum * amount
  42.                   out:SetPixel(x,y, p)
  43.     return p
  44.     end
  45.                
  46.        
  47.        
  48. --It appears that I had the functions in the wrong order before.
  49.         function Process(req)
  50.       local img = InImage:GetValue(req)
  51.       local vibrance = InVibrance:GetValue(req).Value
  52.             local out = Image({ IMG_Like = img })
  53.    
  54.  
  55.       local p = Pixel()
  56.       for y=0,img.Height-1 do          
  57.       for x=0,img.Width-1 do
  58.      end
  59.  
  60.  end
  61.  
  62.  
  63.  
  64.     OutImage:Set(req, out)
  65. end

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

Re: [DEV] Vibrance Fuse

#14

Post by Midgardsormr » Mon Feb 10, 2020 10:50 am

There's not another Fuse I can think of off the top of my head that is as simple in operation as Gain. I think what would be most instructive at this point would be to do a trace on the code so you learn how to evaluate it and solve your problems. I'm not going to make or suggest any changes. I'll just add comments that tell you what's happening at each point and leave it to you to come up with the solutions.
  1. -- Define the new tool so Fusion knows about it. This is a function call to a function called
  2. -- FuRegisterClass(). It takes three arguments, one of which is the table enclosed in { }.
  3. -- Lua lets you put line breaks inside a table, which makes it easier to read, but everything
  4. -- between the { } is still just one argument.
  5.  
  6. FuRegisterClass("Vibrance", CT_Tool, {     
  7.     REGS_Category = "Fuses\\Color",        
  8.     REGS_OpIconString = "vib",
  9.     REGS_OpDescription = "Vibrance",
  10.     REG_OpNoMask = true,
  11.     REG_NoBlendCtrls = true,
  12.     REG_NoObjMatCtrls = true,
  13.     REG_NoMotionBlurCtrls = true,
  14.     })
  15.  
  16.  
  17. -- This is a function declaration. Create() is called by Fusion when the Fuse is added to
  18. -- the Flow. Each of the variables being declared here has a global scope, which means
  19. -- that other functions can see them, even if they aren't passed as arguments.
  20. -- Only use variables with a global scope where necessary to reduce the possibility of
  21. -- reusing a name and accidentally overwriting a value.
  22.  
  23. -- You seem to have a decent grasp on how to make controls, so I'll just skip to the next
  24. -- piece.
  25.  
  26.     function Create()
  27.  
  28.     InVibrance = self:AddInput("Vibrance", "Vibrance", {
  29.         LINKID_DataType = "Number",
  30.         INPID_InputControl = "SliderControl",
  31.         INP_MinScale = -0,
  32.         INP_MaxScale = 20,
  33.         INP_Default = 3,
  34.         })
  35.    
  36.  
  37.     InImage = self:AddInput("Input", "Input", {
  38.         LINKID_DataType = "Image",
  39.         LINK_Main = 1,
  40.         })
  41.  
  42.     OutImage = self:AddOutput("Output", "Output", {
  43.         LINKID_DataType = "Image",
  44.         LINK_Main = 1,
  45.         })            
  46.     end -- End of Create()
  47.  
  48.  
  49. -- This is the declaration of the vibranceFunc() function. This code will only execute if it is called. It always asks for two
  50. -- pieces of information: a pixel object and the vibrance value.    
  51.         function vibranceFunc(p, vibrance)
  52.  
  53. -- Here is the first place where things are going wrong. You have a call to vibranceFunc() inside itself. So when the function
  54. -- gets called, the first thing it does is call itself. And again, when the new call runs, it calls itself again. You have here an
  55. -- infinite loop, and the only thing that saved you from crashing Fusion again is that you never actually called vibranceFunc()
  56. -- Process(). In addition, each of the arguments in the function declaration are already inherently local variables inside
  57. -- the scope of the function. By declaring local p, you are overwriting the original value of p.
  58.         local p = vibranceFunc(p, vibrance)
  59.  
  60. -- These three lines calculate the color change.
  61.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  62.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  63.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  64.  
  65. -- Here you're trying to run a method (a special kind of function that works only on certain objects) on img, which
  66. -- is not global and which was not passed into the function. In addition, you're trying to give it two values, x and y,
  67. -- which also do not exist inside the function.
  68.               img:GetPixel(x,y, p)
  69.  
  70. -- Replaces the value for each channel of the pixel with a linear interpolation between its original value and
  71. -- the one calculated for the vibrance operation.
  72.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  73.           p.G = p.G * (1-amount) + channelMaximum * amount  
  74.           p.B = p.B * (1-amount) + channelMaximum * amount
  75.  
  76. -- This is another method that operates on an image. But again, vibranceFunc() does not have access to
  77. -- out because it is neither a global variable nor passed in as an argument. And x and y still don't exist here.
  78.                   out:SetPixel(x,y, p)
  79.  
  80. -- When the return statement is reached, the function's execution halts, and the value of whatever is
  81. -- being returned is sent back to the program chunk that executed it.
  82.     return p
  83.  
  84.     end -- End of vibranceFunc() declaration.
  85. -- A function declaration must always include the end keyword to indicate that it is finished, even if
  86. -- it has a return or break statement.
  87.                
  88.        
  89.        
  90. -- The Process() function runs whenever Fusion requests a frame from the Fuse. The argument req contains
  91. -- the image request, which has information about the state of Fusion at the time of execution, including the
  92. -- current frame number.
  93.         function Process(req)
  94.  
  95. -- These lines copy the values from the global variables that hold the inputs into local variables
  96. -- that are safer and easier to use in the rest of the Fuse.
  97.       local img = InImage:GetValue(req)
  98.       local vibrance = InVibrance:GetValue(req).Value
  99.  
  100. -- This one creates another image for the purpose of output.
  101.             local out = Image({ IMG_Like = img })
  102.    
  103. -- This creates a Pixel object and assigns it to the local variable p.
  104.       local p = Pixel()
  105.  
  106. -- These nested loops examine each pixel in the input image one by one. Each time the loop executes, the control
  107. -- variable is increased by 1, until we reach the upper limit. So the first time through, y = 0 and x = 0. As soon as
  108. -- the end statement of the inner loop is reach, it returns to the beginning, except now x = 1, but y still equals 0.
  109. -- This continues until x = img.Width -1 (the right-most pixel in the row), at which point the loop breaks, returning
  110. -- control to the outer loop. When this happens, y goes up by one, and we go through it again, starting with y = 1,
  111. -- x = 0. When x again reaches img.Width - 1, y becomes 2, and so on. Eventually we'll get to a point where
  112. -- y = img.Height-1 and x = img.Width-1. Both loops break at this point, and we can finally move on to the next
  113. -- chunk of code.
  114.       for y=0,img.Height-1 do          
  115.       for x=0,img.Width-1 do
  116.         --Something should be happening here, but the loops are empty, so they just count
  117.         -- up and then move on without doing anything.
  118.  
  119.      end -- end of for loop on y
  120.  
  121.  end -- end of for loop on x
  122.  
  123.  
  124. -- OutImage is the global variable that holds the output raster. Set() is another method that copies the local
  125. -- image into the output.
  126.     OutImage:Set(req, out)
  127. end

User avatar
Shem Namo
Fusionista
Posts: 502
Joined: Sun Oct 06, 2019 9:15 pm
Location: North Israel
Real name: David Kohen
Been thanked: 13 times

Re: [DEV] Vibrance Fuse

#15

Post by Shem Namo » Mon Feb 10, 2020 11:44 am

Bryan THANK you,
It finally works :cheer: :cheer:
HALLELUYAH!!!
Now I can go on to phase 2
I can't thank you enough!!
Code: [Select all] [Expand/Collapse] [Download] (Vibrance.fuse)
  1. FuRegisterClass("Vibrance", CT_Tool, {
  2.     REGS_Category = "Fuses\\Color",
  3.     REGS_OpIconString = "vib",
  4.     REGS_OpDescription = "Vibrance",
  5.     REG_OpNoMask = true,
  6.     REG_NoBlendCtrls = true,
  7.     REG_NoObjMatCtrls = true,
  8.     REG_NoMotionBlurCtrls = true,
  9.     })
  10.  
  11.     function Create()
  12.  
  13.     InVibrance = self:AddInput("Vibrance", "Vibrance", {
  14.         LINKID_DataType = "Number",
  15.         INPID_InputControl = "SliderControl",
  16.         INP_MinScale = -0,
  17.         INP_MaxScale = 20,
  18.         INP_Default = 3,
  19.         })
  20.    
  21.  
  22.     InImage = self:AddInput("Input", "Input", {
  23.         LINKID_DataType = "Image",
  24.         LINK_Main = 1,
  25.         })
  26.  
  27.     OutImage = self:AddOutput("Output", "Output", {
  28.         LINKID_DataType = "Image",
  29.         LINK_Main = 1,
  30.         })            
  31.     end
  32.    
  33.         function vibranceFunc(p, vibrance)
  34.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  35.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  36.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  37.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  38.           p.G = p.G * (1-amount) + channelMaximum * amount  
  39.           p.B = p.B * (1-amount) + channelMaximum * amount               
  40.     return p
  41.     end
  42.                
  43.        
  44.        
  45. --It appears that I had the functions in the wrong order before.
  46.         function Process(req)
  47.       local img = InImage:GetValue(req)
  48.       local vibrance = InVibrance:GetValue(req).Value
  49.             local out = Image({ IMG_Like = img })
  50.            
  51.    
  52.  
  53.       local p = Pixel()     -- Create the pixel object only once
  54.       for x=0, img.Width - 1 do
  55.       for y = 0, img.Height - 1 do
  56.         img:GetPixel(x,y, p)
  57.         p = vibranceFunc(p, vibrance)
  58.                 out:SetPixel(x,y, p)
  59.     end
  60. end
  61.    
  62.  
  63.  
  64.  
  65.     OutImage:Set(req, out)
  66. end
Added in 35 minutes 6 seconds:
Wow after all of this I still have a lot of things I want to add and a lot of questions like:
  • How I can I calculate to change the color of the input image to some thing else,
    using the color picker?
  • How can I turn black pixels transparent?
  • What is the math for a saturation control(that can also increase saturation)?
I really appreciate all of the help,
Thank you so much,
David.