File: //opt/resolve/Developer/Fusion Fuse/Example 6 Text.fuse
--[[--
A simple Text Annotation Fuse, which prints a single window of text onto the input image.
Unlike Text+, the resolution comes from the input image, and there is no need for
an additional Merge. This keeps the composition simple. Useful for slates and templates.
Written by : eyeon Software (sdk@eyeonline.com)
Written on : Feb 22nd, 2008
Modified : May 14th - made Text input work with simple expressions
Requires : Fusion 5.2 or later.
Modified May 2020 by SR
--]]--
--******************************************************************************************************
-- This Registers the Fuse into Fusion, gives its Name, and Menu location. Location of Help. Can Fuse be edited.
FuRegisterClass("TextAnnotate", CT_Tool, {
REGS_Name = "Ex6_Text",
REGS_Category = "Fuses\\Examples",
REGS_OpIconString = "E6Tx",
REGS_OpDescription = "Example, using Text and Strings",
REGS_HelpTopic = "Example Location of Help", --This can be a URL
REGS_URL = "www.blackmagicdesign.com",
--REGS_IconID = "Icons.Tools.Icons.Example",-- This can be Inline as an array of Values
REG_OpNoMask = false,
REG_NoBlendCtrls = false,
REG_NoObjMatCtrls = false,
REG_NoMotionBlurCtrls = false,
REG_Fuse_NoEdit = false,
REG_Fuse_NoReload = false,
REG_Version = 1,
}) -- End of RegisterClass
function Create()
InText = self:AddInput("Styled Text", "StyledText", {
LINKID_DataType = "Text",
INPID_InputControl = "TextEditControl",
TEC_Lines = 3, --How many lines high is the Input.
})
InFont = self:AddInput("Font", "Font", {
LINKID_DataType = "Text",
INPID_InputControl = "FontFileControl",
IC_ControlGroup = 2,
IC_ControlID = 0,
INP_Level = 1,
INP_DoNotifyChanged = true,
})
InFontStyle = self:AddInput("Style", "Style", {
LINKID_DataType = "Text",
INPID_InputControl = "FontFileControl",
IC_ControlGroup = 2,
IC_ControlID = 1,
INP_Level = 1,
INP_DoNotifyChanged = true,
})
InPosition = self:AddInput("Position", "Position", {
LINKID_DataType = "Point",
INPID_InputControl = "OffsetControl",
INPID_PreviewControl = "CrosshairControl",
})
InSize = self:AddInput("Size", "Size", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MinScale = 0.0,
INP_MaxScale = 0.5,
INP_Default = 0.08,
})
InJustify = self:AddInput("Justification", "Justification", {
LINKID_DataType = "Number",
INPID_InputControl = "MultiButtonControl",
INP_Default = 0.0,
{ MBTNC_AddButton = "Left", MBTNCD_ButtonWidth = 1/3, },
{ MBTNC_AddButton = "Center", MBTNCD_ButtonWidth = 1/3, },
{ MBTNC_AddButton = "Right", MBTNCD_ButtonWidth = 1/3, },
INP_Integer = true,
})
InR = self:AddInput("Red", "Red", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
ICS_Name = "Color",
CLRC_ShowWheel = false,
IC_ControlGroup = 1,
IC_ControlID = 0, -- Red ID
})
InG = self:AddInput("Green", "Green", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
IC_ControlGroup = 1,
IC_ControlID = 1, -- Green ID
})
InB = self:AddInput("Blue", "Blue", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
IC_ControlGroup = 1,
IC_ControlID = 2, -- Blue ID
})
InA = self:AddInput("Alpha", "Alpha", {
LINKID_DataType = "Number",
INPID_InputControl = "ColorControl",
INP_MinScale = 0.0,
INP_MaxScale = 1.0,
INP_Default = 1.0,
IC_ControlGroup = 1,
IC_ControlID = 3, -- Alpha ID
})
InImage = self:AddInput("Input", "Input", {
LINKID_DataType = "Image",
LINK_Main = 1,
})
OutImage = self:AddOutput("Output", "Output", {
LINKID_DataType = "Image",
LINK_Main = 1,
})
end
function drawstring(img, font, style, size, justify, quality, x, y, colour, text)
local ic = ImageChannel(img, quality)
local fs = FillStyle()
local cs = ChannelStyle()
cs.Color = colour
ic:SetStyleFill(fs)
-- get the fonts metrics (see http://freetype.sourceforge.net/freetype2/docs/glyphs/index.html for a great guide)
local font = TextStyleFont(font, style)
local tfm = TextStyleFontMetrics(font)
-- This is the distance between this line and the next one.
local line_height = (tfm.TextAscent + tfm.TextDescent + tfm.TextExternalLeading) * 10 * size
local x_move = 0
local mat = Matrix4()
mat:Scale(1.0/tfm.Scale, 1.0/tfm.Scale, 1.0)
mat:Scale(size, size, 1)
-- set the initial baseline position of the text cursor
local sh, ch, prevch
local shape = Shape()
mat:Move(x, y, 0)
-- split the text into separate lines
for line in string.gmatch(text, "%C+") do
-- First pass, work out what the total width of this line is going to be
local line_width = 0
for i = 1, #line do
ch = line:sub(i,i):byte()
-- is this ignoring kerning?
line_width = line_width + tfm:CharacterWidth(ch)*10*size
end
-- Now work out our initial cursor position, based on the justification
-- 0 = left justify,
-- 1 = centered
-- 2 = right justify
if justify == 0 then
--mat:Move(0, 0, 0)
elseif justify == 1 then
mat:Move(-line_width/2, 0, 0)
elseif justify == 2 then
mat:Move(-line_width, 0, 0)
end
-- Second pass, now we assemble the actual shape
for i = 1, #line do
prevch = ch
-- get the character, or glyph
ch = line:sub(i,i):byte()
-- first we want to know what the width of the character is,
-- so we know where to start drawing this next character
-- not really sure why we multiply this by 10, we just do :-)
local cw = tfm:CharacterWidth(ch) * 10 * size
-- if there is a previous character, we need to get the kerning
-- between the current character and the last one.
if prevch then
x_offset = tfm:CharacterKerning(prevch, ch)*10*size
x_move = x_move + x_offset
mat:Move(x_offset, 0, 0)
end
-- move the cursor to the center of the character
mat:Move(cw/2, 0, 0)
-- I think this renders the shape we are interested in
sh = tfm:GetCharacterShape(ch, false)
if sh then
sh = sh:TransformOfShape(mat)
-- move the text cursor to the end of the glyph.
mat:Move(cw/2, 0, 0)
x_move = x_move + cw
shape:AddShape(sh)
end
end
-- line end, move the cursor back to the start
if justify == 0 then
mat:Move(-x_move, -line_height, 0)
elseif justify == 1 then
mat:Move(-x_move/2, -line_height, 0)
elseif justify == 2 then
mat:Move(0, -line_height, 0)
end
x_move = 0
end
ic:ShapeFill(shape)
ic:PutToImage("CM_Merge", cs)
end
function Process(req)
local img = InImage:GetValue(req)
local font = InFont:GetValue(req).Value
local style = InFontStyle:GetValue(req).Value
local out = img:CopyOf()
local text = InText:GetValue(req).Value
local size = InSize:GetValue(req).Value
local center = InPosition:GetValue(req)
local justify = InJustify:GetValue(req).Value
local r = InR:GetValue(req).Value
local g = InG:GetValue(req).Value
local b = InB:GetValue(req).Value
local a = InA:GetValue(req).Value
local cx = center.X
local cy = center.Y * (out.Height * out.YScale) / (out.Width * out.XScale)
local quality = 32
-- if the FontManager list is empty, scan the font list
-- If the UI has never been shown, as would always be the case on a render node,
-- nothing will scan the font list for available fonts. So we check for that here,
-- and force a scan if needed.
if not next( FontManager:GetFontList() ) then
FontManager:ScanDir()
end
if req:IsQuick() then
quality = 1
end
-- the drawstring function is doing all the heavy lifting
drawstring(out, font, style, size, justify, quality, cx, cy, Pixel{R=r,G=g,B=b,A=a}, text)
OutImage:Set(req, out)
end
function NotifyChanged(inp, param, time)
-- when the tools FontFileControl is first created, the FontManager has not yet
-- provided a FontList, so we can't set a default value. Instead we do it here.
if inp == InFont then
local f = param.Value
if f == nil or string.len(f) == 0 then
InFont:SetSource(Text("Open Sans"), time)
end
elseif inp == InFontStyle then
local f = param.Value
if f == nil or string.len(f) == 0 then
InFontStyle:SetSource(Text("Regular"), time)
end
end
end