[DEV] 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
Midgardsormr
Fusionator
Posts: 1488
Joined: Wed Nov 26, 2014 8:04 pm
Answers: 8
Location: Los Angeles, CA, USA
Been thanked: 34 times
Contact:

Re: [DEV] Vibrance Fuse

#16

Post by Midgardsormr » Mon Feb 10, 2020 2:52 pm

Shem Namo wrote:
Mon Feb 10, 2020 12:19 pm
How I can I calculate to change the color of the input image to some thing else, using the color picker?
There are a few different ways you could go about this. It depends on exactly what it is you hope to accomplish. Probably the simplest method, and the one I use in XGlow, is to just multiply the channels by the desired color. Suppose you have assigned the inputs of your colorControl to the variables red, green, blue, and alpha.

Code: Select all

p.R = p.R * red
p.G = p.G * green
p.B = p.B * blue
p.A = p.A * alpha
If your pixel is pure white, it will be changed to the color you chose with the picker. This is identical to multiplying the image by a Background using a ChannelBooleans. The trouble is that it can be difficult to predict exactly what hue you'll wind up with if you're recoloring something that isn't mono-chromatic. Also, it will typically reduce the luminance of your image.
How can I turn black pixels transparent?
Probably the best bet would be to luma key the image before sending it to the fuse. If you want to do it in the fuse itself, you'll need to decide what you mean—do you want just black pixels to be transparent? Or a luminance key of some kind? I mean, if all you want is to turn (0,0,0,1) into (0,0,0,0), then that's as simple as:

Code: Select all

if p.R == 0 and p.G == 0 and p.B == 0 then
    p.A = 0
end
But you'll wind up with some terrible aliasing. You could multiply the alpha by an average of the channels, perhaps:

Code: Select all

channelAverage = (p.R + p.G + p.B) / 3
p.A = p.A * channelAverage
That's a very quick-and-dirty luma key. For a better one, you'd have to do a weighted average of the three channels. Coefficients vary depending on your color space, but for Rec.709, I think this is correct:

Code: Select all

luma = p.R * 0.299 + p.G * 0.587 + p.B * 0.114
p.A = p.A * luma
What is the math for a saturation control(that can also increase saturation)?
Complicated. Fortunately, we don't really need to always do that math, as there is a Saturate() method built in to the API. Assuming we have img as our source and want to apply the saturation change to out, and we have a control with its value held in the saturation variable:

Code: Select all

out = img:Copy() -- Copy the image directly to the output image.
out:Saturate(saturation, saturation, saturation)   -- Change the output image's saturation.
Saturate() takes three arguments, one for each channel. Usually these will be identical, as above. There may, however, be circumstances where you'd want to only do the operation on one channel, or adjust the weighting to support different color spaces. In any case, a value of 1.0 represents no change in saturation.

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

Re: [DEV] Vibrance Fuse

#17

Post by Shem Namo » Tue Feb 11, 2020 2:57 am

Thanks A million Bryan,
I can't believe how many things are actually working :)
the only thing I can't get to work, is the saturation control.
Other than that everything else works.
Where was I supposed to put the

Code: Select all

out = img:Copy() -- Copy the image directly to the output image.
out:Saturate(saturation, saturation, saturation)   -- Change the output image's saturation.

block?

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.  InColorR= self:AddInput("Red", "Red", {
  14.         ICS_Name            = "Color",
  15.         LINKID_DataType     = "Number",
  16.         INPID_InputControl  = "ColorControl",
  17.         INP_Default         = 0,
  18.         INP_MaxScale        = 1.0,
  19.         ICD_Center          = 1.0,
  20.         INP_DoNotifyChanged = true,
  21.         CLRC_ShowWheel      = False,
  22.         IC_ControlGroup     = 1,
  23.         IC_ControlID        = 0,
  24.         IC_Visible          = true,
  25.         })
  26.                
  27.     InColorG = self:AddInput("Green", "Green", {
  28.         LINKID_DataType     = "Number",
  29.         INPID_InputControl  = "ColorControl",
  30.         INP_Default         = 0.5,
  31.         INP_DoNotifyChanged = true,
  32.         IC_ControlGroup     = 1,
  33.         IC_ControlID        = 1,
  34.         })
  35.                
  36.     InColorB = self:AddInput("Blue", "Blue", {
  37.         LINKID_DataType     = "Number",
  38.         INPID_InputControl  = "ColorControl",
  39.         INP_Default         = 1,
  40.         INP_DoNotifyChanged = true,
  41.         IC_ControlGroup     = 1,
  42.         IC_ControlID        = 2,
  43.         })
  44.                
  45.         InMatte = self:AddInput("Matte", "UnMultiply", {
  46.             LINKID_DataType = "Number",
  47.             INPID_InputControl = "CheckboxControl",
  48.             INP_Integer = true,
  49.             INP_Default = 1,
  50.             })
  51.                
  52.    InVibrance = self:AddInput("Vibrance", "Vibrance", {
  53.         LINKID_DataType = "Number",
  54.         INPID_InputControl = "SliderControl",
  55.         INP_MinScale = -0,
  56.         INP_MaxScale = 20,
  57.         INP_Default = 3,
  58.         })
  59.                
  60.      InSaturation = self:AddInput("Saturation", "Saturation", {
  61.         LINKID_DataType = "Number",
  62.         INPID_InputControl = "SliderControl",
  63.         INP_MinScale = -0,
  64.         INP_MaxScale = 2,
  65.         INP_Default = 1,
  66.         })
  67.                
  68.      InGain = self:AddInput("Gain", "Gain", {
  69.            LINKID_DataType = "Number",
  70.            INPID_InputControl = "SliderControl",
  71.          INP_MinScale = -0,
  72.        INP_MaxScale = 5,
  73.            INP_Default = 2,
  74.            })
  75.    
  76.  
  77.     InImage = self:AddInput("Input", "Input", {
  78.         LINKID_DataType = "Image",
  79.         LINK_Main = 1,
  80.         })
  81.  
  82.     OutImage = self:AddOutput("Output", "Output", {
  83.         LINKID_DataType = "Image",
  84.         LINK_Main = 1,
  85.         })            
  86.     end
  87.        
  88.       function colorFunc(p,red,green,blue, alpha)
  89.         p.R = p.R * red
  90.     p.G = p.G * green
  91.     p.B = p.B * blue
  92.      return p
  93.         end
  94.    
  95.      function matteFunc(p, matte)
  96.       p.A =p.A * matte
  97.                 return p
  98.         end
  99.        
  100.         function vibranceFunc(p, vibrance)
  101.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  102.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  103.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  104.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  105.           p.G = p.G * (1-amount) + channelMaximum * amount  
  106.           p.B = p.B * (1-amount) + channelMaximum * amount               
  107.     return p
  108.     end        
  109.        
  110.         function satFunc(saturation,saturation, saturation)
  111.         out = img:Copy()
  112.     out:Saturate(saturation, saturation, saturation)
  113.     Saturate = Saturate *   saturation 
  114.         return p
  115.         end
  116.        
  117.      function gainFunc(p, gain)
  118.           p.R = p.R * gain
  119.                 p.G = p.G * gain
  120.                 p.B = p.B * gain
  121.                 return p
  122.                 end
  123.                
  124.        
  125.        
  126. --It appears that I had the functions in the wrong order before.
  127.         function Process(req)
  128.       local img = InImage:GetValue(req)
  129.             local red = InColorR:GetValue(req).Value
  130.             local green = InColorG:GetValue(req).Value
  131.             local blue = InColorB:GetValue(req).Value
  132.             local matte = InMatte:GetValue(req).Value
  133.       local vibrance = InVibrance:GetValue(req).Value
  134.           local saturation = InSaturation:GetValue(req).Value  
  135.             local gain = InGain:GetValue(req).Value            
  136.             local out = Image({ IMG_Like = img })
  137.                
  138.    
  139.  
  140.       local p = Pixel()         -- Create the pixel object only once           
  141.       for x=0, img.Width - 1 do
  142.       for y = 0, img.Height - 1 do
  143.         img:GetPixel(x,y, p)
  144.                 p = colorFunc(p, red, green, blue)
  145.                 p = matteFunc(p, matte)
  146.         p = vibranceFunc(p, vibrance)
  147.                 p = saturationFunc(saturation,saturation, saturation)
  148.                 p = gainFunc(p, gain)              
  149.                 out:SetPixel(x,y, p)               
  150.     end
  151. end
  152.    
  153.  
  154.  
  155.  
  156.     OutImage:Set(req, out)
  157. end

User avatar
thibaud
Fusioneer
Posts: 160
Joined: Thu Sep 04, 2014 1:23 am
Been thanked: 2 times
Contact:

Re: [DEV] Vibrance Fuse

#18

Post by thibaud » Tue Feb 11, 2020 4:03 am

Great to see you picking up on coding.
but before going any further please do yourself and others a favor by applying some basic formatting principles...
Some guidelines here or here

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

Re: [DEV] Vibrance Fuse

#19

Post by Shem Namo » Tue Feb 11, 2020 10:56 am

Thanks, @thibaud !
All of the controls seem to be functioning the way I would want them to,
but the sat control is very fiddly.
Is there any method to normalize only this control?
Thanks,
David.

P.S, what do you guys think if we call this tool something else,
so it doesn't look like we're knocking of Video Copilot?
Thanks again
Code: [Select all] [Expand/Collapse] [Download] (Vibrance beta.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.  InColorR= self:AddInput("Red", "Red", {
  14.         ICS_Name            = "Color",
  15.         LINKID_DataType     = "Number",
  16.         INPID_InputControl  = "ColorControl",
  17.         INP_Default         = 0,
  18.         INP_MaxScale        = 1.0,
  19.         ICD_Center          = 1.0,
  20.         INP_DoNotifyChanged = true,
  21.         CLRC_ShowWheel      = False,
  22.         IC_ControlGroup     = 1,
  23.         IC_ControlID        = 0,
  24.         IC_Visible          = true,
  25.         })
  26.                
  27.     InColorG = self:AddInput("Green", "Green", {
  28.         LINKID_DataType     = "Number",
  29.         INPID_InputControl  = "ColorControl",
  30.         INP_Default         = 0.5,
  31.         INP_DoNotifyChanged = true,
  32.         IC_ControlGroup     = 1,
  33.         IC_ControlID        = 1,
  34.         })
  35.                
  36.     InColorB = self:AddInput("Blue", "Blue", {
  37.         LINKID_DataType     = "Number",
  38.         INPID_InputControl  = "ColorControl",
  39.         INP_Default         = 1,
  40.         INP_DoNotifyChanged = true,
  41.         IC_ControlGroup     = 1,
  42.         IC_ControlID        = 2,
  43.         })
  44.                
  45.         InMatte = self:AddInput("Matte", "UnMultiply", {
  46.             LINKID_DataType = "Number",
  47.             INPID_InputControl = "CheckboxControl",
  48.             INP_Integer = true,
  49.             INP_Default = 1,
  50.             })
  51.                
  52.    InVibrance = self:AddInput("Vibrance", "Vibrance", {
  53.         LINKID_DataType = "Number",
  54.         INPID_InputControl = "SliderControl",
  55.         INP_MinScale = -0,
  56.         INP_MaxScale = 20,
  57.         INP_Default = 3,
  58.         })
  59.                
  60.      InSaturation = self:AddInput("Saturation", "Saturation", {
  61.         LINKID_DataType = "Number",
  62.         INPID_InputControl = "SliderControl",
  63.         INP_MinScale = -0,
  64.         INP_MaxScale = 2,
  65.         INP_Default = 1,
  66.         })
  67.                
  68.      InGain = self:AddInput("Gain", "Gain", {
  69.            LINKID_DataType = "Number",
  70.            INPID_InputControl = "SliderControl",
  71.          INP_MinScale = -0,
  72.        INP_MaxScale = 5,
  73.            INP_Default = 2,
  74.            })
  75.    
  76.  
  77.     InImage = self:AddInput("Input", "Input", {
  78.         LINKID_DataType = "Image",
  79.         LINK_Main = 1,
  80.         })
  81.  
  82.     OutImage = self:AddOutput("Output", "Output", {
  83.         LINKID_DataType = "Image",
  84.         LINK_Main = 1,
  85.         })            
  86.     end
  87.              
  88.       function colorFunc(p,red,green,blue, alpha)
  89.         p.R = p.R * red
  90.     p.G = p.G * green
  91.     p.B = p.B * blue
  92.      return p
  93.         end
  94.    
  95.      function matteFunc(p, matte)
  96.       p.A =p.A * matte
  97.                 return p
  98.         end
  99.        
  100.         function vibranceFunc(p, vibrance)
  101.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  102.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  103.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  104.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  105.           p.G = p.G * (1-amount) + channelMaximum * amount  
  106.           p.B = p.B * (1-amount) + channelMaximum * amount               
  107.     return p
  108.     end
  109.    
  110.    
  111.      function gainFunc(p, gain)
  112.           p.R = p.R * gain
  113.                 p.G = p.G * gain
  114.                 p.B = p.B * gain
  115.                 return p
  116.                 end
  117.                
  118.      
  119.        
  120. --It appears that I had the functions in the wrong order before.
  121.         function Process(req)
  122.       local img = InImage:GetValue(req)
  123.             local red = InColorR:GetValue(req).Value
  124.             local green = InColorG:GetValue(req).Value
  125.             local blue = InColorB:GetValue(req).Value
  126.             local matte = InMatte:GetValue(req).Value
  127.       local vibrance = InVibrance:GetValue(req).Value
  128.           local saturation = InSaturation:GetValue(req).Value  
  129.             local gain = InGain:GetValue(req).Value            
  130.             local out = Image({ IMG_Like = img })
  131.        
  132.  
  133.       local p = Pixel()         -- Create the pixel object only once           
  134.       for x=0, img.Width - 1 do
  135.       for y = 0, img.Height - 1 do
  136.         img:GetPixel(x,y, p)
  137.                 p = colorFunc(p, red, green, blue)
  138.                 p = matteFunc(p, matte)
  139.         p = vibranceFunc(p, vibrance)          
  140.                 p = gainFunc(p, gain)      
  141.                 out:SetPixel(x,y, p)                   
  142.     end
  143. end
  144.    
  145.        
  146.              
  147.      
  148.      out:Saturate(saturation, saturation, saturation)   -- Change the output image's saturation.
  149.     OutImage:Set(req, out)
  150. end
  151.        
  152.              

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

Re: [DEV] Vibrance Fuse

#20

Post by Shem Namo » Wed Feb 12, 2020 8:44 pm

Almost done, at least in my opinion ;)
What do you guys think?
@SecondMan Is it okay to mark this post as BETA?
@Midgardsormr Do you want to submit your MT_Vibrance to Reactor, or can I submit this one? Thanks Again, I couldn't have done it without you!!
:)

https://drive.google.com/open?id=1b8QoC ... -3Bu31ANeG

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

Re: [DEV] Vibrance Fuse

#21

Post by SecondMan » Thu Feb 13, 2020 1:00 am

Shem Namo wrote:
Wed Feb 12, 2020 8:44 pm
Is it okay to mark this post as BETA?
Not up to me - it's your dev topic... :)

But if you want to submit to Reactor at some point, filename extensions should be lower case, please and thank you.
Shem Namo wrote:
Wed Feb 12, 2020 8:44 pm
Do you want to submit your MT_Vibrance to Reactor, or can I submit this one?
One does not exclude the other. For whatever the reason, it may be preferable to have a Fuse over a Macro (speed) or a Macro over a Fuse (portability, learning). But currently your Fuse is many times slower than Bryan's Macro so you may want to look into that...

By the way, should you be wondering how you managed to insert an "empty" post in this topic - that's what happens when you try to insert more than seven hundred thousand characters into one. Your browser either crashed when you were trying to submit (mine was when I was trying to read/edit the thing) or some other hiccup happened before everything was sent - the post was incomplete.

About those characters. I have a bit of a "here we go again" feeling about this, to be totally frank. The Fuse isn't finished, yet your priority seems to be to slap your name and banner on it - as garishly as you can - and get it in Reactor. It's your Fuse and you should of course do whatever you feel is appropriate with it, but given how much help you've received so far to make it - it was practically done for you - I find that to be in somewhat poor taste.

You're also shooting yourself in the foot by making the code harder to share in your own dev topic.

Anyway, just some friendly advice - I'm sure you mean well.

User avatar
thibaud
Fusioneer
Posts: 160
Joined: Thu Sep 04, 2014 1:23 am
Been thanked: 2 times
Contact:

Re: [DEV] Vibrance Fuse

#22

Post by thibaud » Thu Feb 13, 2020 4:47 am

As far as code formatting goes things only got worse since my last advice, some editor (like vscode) will format it for you.

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

Re: [DEV] Vibrance Fuse

#23

Post by Midgardsormr » Thu Feb 13, 2020 7:39 am

No, I don't think it's ready for Reactor yet. You're past step one: You have an algorithm that works. As SecondMan says, it's quite slow right now because you're using that brute-force nested loops method. That approach is single-threaded—it can only work on one core of the CPU, leaving most of your system resources idle while it processes. The next thing to do is optimize!

Remember when I suggested you try to implement the other three methods seen in the Gain Fuse? That wasn't an idle thought—it's something you really ought to do because each of those methods is progressively faster than the previous, and each has cases where you might want to use it. Let's go through them one at a time and see how they work (I don't have time to analyze all of them this morning; this is going to take a couple of days of writing, probably):
  1. self:DoMultiProcess(nil, { In = img, Out = out, Gain = gain }, img.Height, function (y)
  2.     local p = Pixel()
  3.     for x=0,In.Width-1 do
  4.         In:GetPixel(x,y, p)
  5.         p.R = p.R * Gain
  6.         p.G = p.G * Gain
  7.         p.B = p.B * Gain
  8.         Out:SetPixel(x,y, p)
  9.     end
  10. end)
I'm sure this looks familiar. It replaces the outer for loop, the one that looks at each row (y coordinates) with this DoMultiProcess() function. The advantage to this approach is that it can perform the calculation on multiple scanlines at the same time. This might be useful if information in the vertical dimension is independent, but you need to know the results of one pixel before you know what to do with the one next to it.

It's using the method notation I've alluded to earlier: object:method(arguments). In this case, the object the method is a member of is self, which means the Process() function. It therefore doesn't have an innate reference to the image it's supposed to be building—you could run this function to do general multi-threaded processing on something other than an image if you need to.

Here's the VFXPedia documentation about this function:
https://www.steakunderwater.com/VFXPedi ... ltiProcess

Let's take a look at those arguments: The first can hold a function used to set up any initial environment you might need for every thread. It will be processed only once, making it analogous to the Setup tab of the CustomTool. Most of the time, coders just do this kind of processing in the main part of Process() and pass the information in as variables in the second argument.

arg2 is a table containing a list of global variables to be imported into the multi-threaded function. These variables must be global in scope so that all of the threads can see them. Traditionally, global variables are capitalized, as we can see here. Since DoMultiProcess() doesn't get the images to work on by default, they have to be sent in through the variable list with all of the other input values.

arg3 is an integer. It determines how many times the function will run. In this case, we want to do it once for every line in the image, so we give it img.Height. It will start at 0 and run as many times are required (the -1 is implicit in this case because the compiler understand that 0 - 3 is four numbers).

arg4 is a function, which gets a single argument of its own. This argument is always the value provided by arg3. Since in this case we know that's the y coordinate, we call it y. If you have declared the function you want to use elsewhere, you can instead give it the name of that function as arg4 instead of declaring the whole thing here, as illustrated in the documentation.

The lines that follow are actually still a part of the fourth argument. We create the Pixel() object outside the loop, so it only gets done once, then run the for x loop, just like you did in the single-threaded version. It is very important that p is a local variable in this case because you'll be processing multiple threads simultaneously. If p were global, then when each thread created it, they'd overwrite one another, and you'd get a scrambled output image.

And just like every function, this one requires an end statement, and the method call itself closes with that final parenthesis.

There's a subtle trick in this one that isn't obvious if you're not really familiar with Lua. When we use the assignment operator, =, to copy the out image to Out, we don't actually make a copy, but instead a pointer. Out is actually the same image as out. If we change something about one, we change it about both. That's why we don't need to copy the results back into out before we assign it to OutImage. It's confusing, but this doesn't happen with simple values, like numbers and text, but it does happen with tables. So if you ever need an independent copy of a table, you generally have to use a loop to copy the values one at a time instead of just saying table1 = table2.

Okay, I've got other stuff to do before I go to work today, so today's lesson is to see if you can get your fuse to work using DoMultiProcess(). And yes, I really am going to make you experience all three additional techniques! ;)

Also, test the speed of the current Fuse, then compare it to the new one when you're done to see how much improvement you have made.
SecondMan wrote:
Thu Feb 13, 2020 1:00 am
should you be wondering how you managed to insert an "empty" post in this topic
I know I was wondering about that!

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

Re: [DEV] Vibrance Fuse

#24

Post by Shem Namo » Thu Feb 13, 2020 11:53 am

@Midgardsormr You are the best coding teacher ever!!!
I really have too read this a few times and then a few more times to really learn from it.
I've been on the road all day and need to look at this with a fresh mind, so I'll try this method in the morning.
just to get this straight, this has nothing to do with OpenCL or DCTL right?
Thank you so much for explaining this to me, I can't thank you enough!!!

P.S sorry about that @SecondMan I'm just really excited about this fuse that I actually thought that it was ready.
I hope the 700,000 character text didn't cause any damage. :oops:
By the way it looked fine in the preview window :?


Thanks Again,
David.

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

Re: [DEV] Vibrance Fuse

#25

Post by Midgardsormr » Thu Feb 13, 2020 12:41 pm

Correct. The techniques are similar to OpenCL—we're essentially writing a fragment shader—but we haven't touched GPU programming at all. Yet. ;)

User avatar
JUNE
Fusionista
Posts: 269
Joined: Wed Aug 06, 2014 5:45 am
Been thanked: 1 time
Contact:

Re: [DEV] Vibrance Fuse

#26

Post by JUNE » Thu Feb 13, 2020 4:57 pm

Work together!

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

Re: [DEV] Vibrance Fuse

#27

Post by Shem Namo » Sat Feb 15, 2020 10:54 am

Hi Bryan, I thought that I could figure it out with only the information that you already gave me,
but apparently I can't :oops:
Do you happen to know of a Fuse that uses this method(besides the gain fuse), that I can learn from?
I can't figure out how to make this work with multiple functions, like vibranceFunc, gainFunc and the rest.
I already when through all of the fuses of my computer, but I can't find one that uses this method.

Thanks,
David.

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

Re: [DEV] Vibrance Fuse

#28

Post by Midgardsormr » Sat Feb 15, 2020 4:46 pm

I'll give you a hint: The DoMultiProcess() function replaces for y=0, img.Height-1 do...end.

If you still can't figure it out, post your best guess for what should work, and I'll give you some more pointers.

I don't know of a fuse that uses the method off the top of my head—it's not one that sees a lot of use. But it's a good conceptual stepping-stone toward ProcessPixels().

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

Re: [DEV] Vibrance Fuse

#29

Post by Shem Namo » Sun Feb 16, 2020 2:21 am

Thanks Bryan!!
Here is what I think it should look like, but the only thing is that
I don't know how to declare which functions should be in the loop.
In the simple loop method they were declared

Code: Select all

p = vibranceFunc(p, vibrance)
and like this for all of the other functions(except "Saturate"),
but I guess that type of declaration doesn't work with "DoMultiProcess"
I wrote a few notes inside the fuse that describe what I think is going on.

Thanks again,
David


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.  InColorR= self:AddInput("Red", "Red", {
  14.         ICS_Name            = "Color",
  15.         LINKID_DataType     = "Number",
  16.         INPID_InputControl  = "ColorControl",
  17.         INP_Default         = 0,
  18.         INP_MaxScale        = 1.0,
  19.         ICD_Center          = 1.0,
  20.         INP_DoNotifyChanged = true,
  21.         CLRC_ShowWheel      = False,
  22.         IC_ControlGroup     = 1,
  23.         IC_ControlID        = 0,
  24.         IC_Visible          = true,
  25.         })
  26.                
  27.     InColorG = self:AddInput("Green", "Green", {
  28.         LINKID_DataType     = "Number",
  29.         INPID_InputControl  = "ColorControl",
  30.         INP_Default         = 0.5,
  31.         INP_DoNotifyChanged = true,
  32.         IC_ControlGroup     = 1,
  33.         IC_ControlID        = 1,
  34.         })
  35.                
  36.     InColorB = self:AddInput("Blue", "Blue", {
  37.         LINKID_DataType     = "Number",
  38.         INPID_InputControl  = "ColorControl",
  39.         INP_Default         = 1,
  40.         INP_DoNotifyChanged = true,
  41.         IC_ControlGroup     = 1,
  42.         IC_ControlID        = 2,
  43.         })
  44.                
  45.         InMatte = self:AddInput("Matte", "UnMultiply", {
  46.             LINKID_DataType = "Number",
  47.             INPID_InputControl = "CheckboxControl",
  48.             INP_Integer = true,
  49.             INP_Default = 1,
  50.             })
  51.                
  52.    InVibrance = self:AddInput("Vibrance", "Vibrance", {
  53.         LINKID_DataType = "Number",
  54.         INPID_InputControl = "SliderControl",
  55.         INP_MinScale = -0,
  56.         INP_MaxScale = 20,
  57.         INP_Default = 3,
  58.         })
  59.                
  60.      InSaturation = self:AddInput("Saturation", "Saturation", {
  61.         LINKID_DataType = "Number",
  62.         INPID_InputControl = "SliderControl",
  63.         INP_MinScale = -0,
  64.         INP_MaxScale = 1,
  65.         INP_Default = 1,
  66.         })
  67.                
  68.      InGain = self:AddInput("Gain", "Gain", {
  69.            LINKID_DataType = "Number",
  70.            INPID_InputControl = "SliderControl",
  71.          INP_MinScale = -0,
  72.        INP_MaxScale = 5,
  73.            INP_Default = 2,
  74.            })
  75.              
  76.      InGamma = self:AddInput("Gamma", "Gamma", {
  77.            LINKID_DataType = "Number",
  78.            INPID_InputControl = "SliderControl",
  79.          INP_MinScale = -7,
  80.        INP_MaxScale = 0,
  81.            INP_Default = -1,
  82.            })
  83.              
  84.         InLift = self:AddInput("Lift", "Lift", {
  85.            LINKID_DataType = "Number",
  86.            INPID_InputControl = "SliderControl",
  87.          INP_MinScale = -1,
  88.        INP_MaxScale = 1,
  89.            INP_Default = 0,
  90.            })
  91.    
  92.  
  93.     InImage = self:AddInput("Input", "Input", {
  94.         LINKID_DataType = "Image",
  95.         LINK_Main = 1,
  96.         })
  97.  
  98.     OutImage = self:AddOutput("Output", "Output", {
  99.         LINKID_DataType = "Image",
  100.         LINK_Main = 1,
  101.         })            
  102.     end
  103.              
  104.       function colorFunc(p,red,green, blue)
  105.         p.R = p.R * red
  106.     p.G = p.G * green
  107.     p.B = p.B * blue
  108.      return p
  109.         end
  110.    
  111. function matteFunc(p, matte)
  112.      p.A =p.A * matte
  113.      return p
  114. end
  115.        
  116.         function vibranceFunc(p, vibrance)
  117.     local channelAverage = (p.R + p.G + p.B) / 3                  -- From Inter, i1
  118.     local channelMaximum = math.max(p.R, math.max(p.G, p.B))      -- From Inter, i2
  119.     local amount = (channelMaximum - channelAverage) * (-vibrance * 3)     
  120.           p.R = p.R * (1-amount) + channelMaximum * amount        -- From channel expressions
  121.           p.G = p.G * (1-amount) + channelMaximum * amount  
  122.           p.B = p.B * (1-amount) + channelMaximum * amount               
  123.     return p
  124.     end
  125.    
  126.    
  127.      function gainFunc(p, gain)
  128.           p.R = p.R * gain
  129.                 p.G = p.G * gain
  130.                 p.B = p.B * gain
  131.                 return p
  132.                 end
  133.                
  134.         function gammaFunc(p, gamma)
  135.         local midtones = gamma*-1
  136.           p.R = p.R ^ midtones
  137.                 p.G = p.G ^ midtones
  138.                 p.B = p.B ^ midtones
  139.                 return p
  140.                 end
  141.                
  142.         function liftFunc(p, lift)
  143.           p.R = p.R *(1-lift)+lift
  144.                 p.G = p.G *(1-lift)+lift
  145.                 p.B = p.B *(1-lift)+lift
  146.                 return p
  147.                 end
  148.                
  149.      
  150.        
  151. --It appears that I had the functions in the wrong order before.
  152.         function Process(req)
  153.       local img = InImage:GetValue(req)
  154.             local red = InColorR:GetValue(req).Value
  155.             local green = InColorG:GetValue(req).Value
  156.             local blue = InColorB:GetValue(req).Value
  157.             local matte = InMatte:GetValue(req).Value
  158.       local vibrance = InVibrance:GetValue(req).Value
  159.           local saturation = InSaturation:GetValue(req).Value  
  160.             local gain = InGain:GetValue(req).Value
  161.             local gamma = InGamma:GetValue(req).Value
  162.             local lift = InLift:GetValue(req).Value
  163.             local out = Image({ IMG_Like = img })
  164.  
  165.         self:DoMultiProcess(nil, { In = img, Out = out, Red = red, Green = green, Blue = blue, Matte = matte, Vibrance = vibrance, Gain = gain, Gamma = gamma, Lift = lift  }, img.Height, function (y)
  166.       local p = Pixel()
  167.       for x=0,In.Width-1 do
  168.             In:GetPixel(x,y, p)
  169.             -- In this version, the fuse doesn't fail when I try to open it in Fusion, but none of the controls do anything,
  170.             --which is something I would expect because:
  171.             --I know that I need to declare which functions should be in the loop here, but I don't know how to declare them.
  172.             -- In the simple loop method I would declare them as "p = vibranceFunc(p, vibrance)" and the same for the rest of the functions.
  173.             -- I already tried using the same declarations here, but it doesn't work
  174.         -- and I'm almost sure that is the reason the controls don't do anything.
  175.             Out:SetPixel(x,y, p)
  176.             end
  177.          end)
  178.  
  179.  
  180.    
  181.        
  182.              
  183.      
  184.      out:Saturate(saturation,saturation, saturation)   -- Change the output image's saturation.
  185.     OutImage:Set(req, out)
  186. end
  187.        
  188.              

User avatar
tberakis
Posts: 31
Joined: Mon Dec 29, 2014 2:30 pm
Been thanked: 1 time

Re: [DEV] Vibrance Fuse

#30

Post by tberakis » Sun Feb 16, 2020 4:42 am

It's exactly the same as the last Fuse, as Brian was trying to tell you.

While I would never suggest learning is a bad thing, I'm curious what you're trying to do here, other than absorb. everyone's time to develop already established ideas?

You're very close, but then, you've been guided there every step of the way.