Third Party Fuses/Gradient Tutorial

From VFXPedia

Jump to: navigation, search

This document describes the Gradient class and provides some Code examples on how to use it in a Fuse.

Contents


Creating Gradients

To add a gradient control to a Fuse, create an input inside the Create() function like this:

InGradient = self:AddInput("Gradient", "Gradient", {
	LINKID_DataType = "Gradient",
	INPID_InputControl = "GradientControl",
	GRDC_ColorSpace = "RGB", -- one of RGB, HLS, HSV or LAB
	})

To switch the color space like Fusion's tools do, create a MultiButtonIDControl that changes the GRDC_ColorSpace attribute in response to its NotifyChanged event.

Inside Process() you can fetch the gradient as you would a Number or Point type:

grad = InGradient:GetValue(req)

Of course you can also create an instance yourself. Gradients are a collection of color values plus a position value (from 0 to 1) for each of them. There are also methods to interpolate between colors in different color spaces. Refer to the C++ SDK for more (not all methods are available in Fuses though).

To set up a red to blue gradient in your Fuse, do this:

grad = Gradient()
grad:AddColor(0.0, Pixel({R = 1.0, G = 0, B = 0, A = 1}))
grad:AddColor(1.0, Pixel({R = 0, G = 0, B = 1.0, A = 1}))

You can also use pre-defined gradients like this:

grad = Gradient("GP_SolidBlack")
grad = Gradient("GP_SolidWhite")
grad = Gradient("GP_BlackToWhite")
grad = Gradient("GP_WhiteToBlack")
grad = Gradient("GP_BlackToTransparent")
grad = Gradient("GP_WhiteToTransparent")

Eventually you'll want to query intermediate color values along the gradient. This can be done using QuickEvaluate():

-- p will be a Pixel object
-- colorspace is a string ("RGB", "HSV", "HLS" or "LAB")
p = grad:QuickEvaluate(0.5, colorspace)


Gradient Methods

These methods also work on gradients:

-- get number of colors
n = grad:GetColorCount()
-- get color of a certain index (starts with 0) as a Pixel object
p = grad:GetColor(index)
-- set one of the presets mentioned above
grad:SetPreset("GP_BlackToWhite")


Default Gradient

There is no INP_Default for GradientControls. It's always black to white. Neither is there a "Reset to Default" option in the gradient's context menu. However, you can use a trick to add a custom gradient for new tools. Use the OnAddToFlow() event to create a gradient object and add some colors. This will override a saved gradient when you reopen a comp, so you need to have a hidden input control somewhere to make sure the OnAddToFlow() code is only executed once in a tool's lifetime:

function OnAddToFlow()
   local grad = Gradient()
   -- the hidden input that defaults to 1 and gets reset to 0 once the gradient has been set up
   if InHiddenFlag:GetSource(0).Value >= 0.5 then
      grad:AddColor(0.0, Pixel({R = 1.0, G = 0, B = 0, A = 1}))
      grad:AddColor(0.5, Pixel({R = 0, G = 1.0, B = 0, A = 1}))
      grad:AddColor(1.0, Pixel({R = 0, G = 0, B = 1.0, A = 1}))
      InGradient:SetSource(grad, 0, 0)
      InHiddenFlag:SetSource(Number(0.0), 0, 0)
   end
end


Filling with a Gradient

Linear gradients can be used to fill shapes drawn to an ImageChannel instance. The ChannelStyle has some members to define the fill:

.Type               -- set to "CT_Color", "CT_Gradient" or "CT_Image"
.ColorGradient      -- set to Gradient instance
.ColorMappingSize
.ColorMappingAspect
.ColorMappingAngle

The gradient has its own transformation matrix (.ImageTransform) so by default it will be unaffected by where the shape is moved/scaled/rotated to. This minimal Fuse draws a rotating triangle to demonstrate this behaviour:

--[[--
GradientTest.Fuse 
--]]--
 
FuRegisterClass("GradientTest", CT_Tool, {
	REGS_Category = "Fuses",
	REGS_OpIconString = "Gra",
	REGS_OpDescription = "Gradient Test Fuse",
	REG_TimeVariant = true,
	})
 
function Create()
	InGradient = self:AddInput("Gradient", "Gradient", {
		LINKID_DataType = "Gradient",
		INPID_InputControl = "GradientControl",
		})		
	InImage = self:AddInput("Input", "Input", {
		LINKID_DataType = "Image",
		LINK_Main = 1,
		})
	OutImage = self:AddOutput("Output", "Output", {
		LINKID_DataType = "Image",
		LINK_Main = 1,
		})				
end
 
function Process(req) 
	local img = InImage:GetValue(req)
	local out = img:CopyOf()
	local grad  = InGradient:GetValue(req)
	
	local ic = ImageChannel(out, 8)
	local fs = FillStyle()
	ic:SetStyleFill(fs)
	local cs = ChannelStyle()
	cs.Type = "CT_Gradient"
	cs.ColorGradient = grad
	cs.ColorMappingSize = 1
 
	local sh = Shape()
	sh:MoveTo(-0.3, -0.3)
	sh:LineTo(0.3, -0.3)
	sh:LineTo(0.0, 0.3)
	sh:Close()
	
	mat = Matrix4()
	mat:Identity()
	mat:RotZ(req.Time * 10)
	mat:Move(0.5, 0.5 * (out.Height * out.YScale) / (out.Width * out.XScale), 0)
 
	ic:ShapeFill(sh:TransformOfShape(mat))
	ic:PutToImage("CM_Merge", cs)
	
	OutImage:Set(req, out)
end

You can easily pin the gradient to the shape by using the same matrix for TransformOfShape() and cs.ImageTransform:

...
cs.ImageTransform = mat
ic:ShapeFill(sh:TransformOfShape(mat))
ic:PutToImage("CM_Merge", cs)
...

Radial Gradients

Radial Gradients have to be rendered as images which in turn can be used to fill a shape. Iterating over an image and using a lot of trigonomety is slow when done by a Fuse. A quicker way to do this is to have Fusion draw concentric circles at a small but acceptable resolution and simply scale the radial gradient up. Fusion will interpolate smoothly and you don't need a lot of steps to prevent banding.

The FlareStar Fuse serves as an example. It creates a radial ramp with a diameter of 256 pixels (127 concentric circles) that is subsequently being scaled as required.