Third Party Fuses/Gradient Tutorial
From VFXPedia
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.