Page 1 of 1

Rounded Rectangle Fuse

Posted: Fri Nov 08, 2019 10:45 am
by SteveWatson
Hi,

following on from this thread

https://www.steakunderwater.com/wesuckl ... =16&t=3478

I managed to create a fuse for a rounded rectangle(which admittedly does nothing that can't be done with a few nodes).

Code: Select all

--[[--
RoundedRect.Fuse

A rounded rectangle tool, butchered from Bryan Ray's Bezier Fuse

Not elegant in any way. 

version 0.1
8/11/19
Steve Watson


--]]--


FuRegisterClass("RoundRect", CT_SourceTool, {
	REGS_Name = "RoundRect",
	REGS_Category = "Creator",
	REGS_OpIconString = "Rrec",
	REGS_OpDescription = "Rectangle Drawing Path.",
	
	REGS_Company = "SteveWatson",
	REGS_URL = "",
	
	REG_Source_GlobalCtrls = true,
	REG_Source_SizeCtrls = true,
	REG_Source_AspectCtrls = true,
	REG_Source_DepthCtrls = true,
	})
	

function Create()
	InWidth = self:AddInput("RectWidth", "RectWidth", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})

	InHeight = self:AddInput("RectHeight", "RectHeight", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})
	InRounding = self:AddInput("CornerRounding", "CornerRounding", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})
	
		InSubdivs = self:AddInput("Smoothness", "Smoothness", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 1,
		INP_MaxScale = 50,
		INP_Default = 20,
		INP_Integer = true,
	})
	
		InThickness = self:AddInput("Thickness", "Thickness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0,
		INP_MaxScale       = 0.1,
		INP_Default        = 0.01,
		})
		
		self:BeginControlNest("Line Style", "LineStyle", false, {})
	InLineType = self:AddInput("Type", "Type", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "MultiButtonControl",
		INP_Default        = 0.0,
		MBTNC_ShowName     = false,
		{MBTNC_AddButton   = "Solid", MBTNCD_ButtonWidth = 0.33, },
		{MBTNC_AddButton   = "Dash",  MBTNCD_ButtonWidth = 0.34, },
		{MBTNC_AddButton   = "Dot",   MBTNCD_ButtonWidth = 0.33, },
		{MBTNC_AddButton   = "Dash Dot",     MBTNCD_ButtonWidth = 0.5, },
		{MBTNC_AddButton   = "Dash Dot Dot", MBTNCD_ButtonWidth = 0.5, },
		})

	InThickness = self:AddInput("Thickness", "Thickness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0,
		INP_MaxScale       = 0.1,
		INP_Default        = 0.01,
		})

	InFilter = self:AddInput("Filter", "Filter", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "MultiButtonControl",
		INP_Default        = 3.0,
		{MBTNC_AddButton   = "Box",       MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Bartlett",  MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Multi-box", MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Gaussian",  MBTNCD_ButtonWidth = 0.25, },
		})

	InSoftness = self:AddInput("Softness", "Softness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0.0,
		INP_MaxScale       = 100,
		INP_Default        = 0.0,
		ICD_Center         = 10,
		})

	-- color wheel
	InRed = self:AddInput("Red", "Red", {
		ICS_Name            = "Line Color",
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		INP_MaxScale        = 1.0,
		CLRC_ShowWheel      = false,
		IC_ControlGroup     = 1,
		IC_ControlID        = 0,
		})
	InGreen = self:AddInput("Green", "Green", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 1,
		})
	InBlue = self:AddInput("Blue", "Blue", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 2,
		})
	InAlpha = self:AddInput("Alpha", "Alpha", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 3,
		})

	InOnBlack = self:AddInput("Draw lines on black image", "OnBlack", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "CheckboxControl",
		INP_Default         = 0,
		})
	self:EndControlNest()
		
		
	-- outputs
	OutImage = self:AddOutput("Output", "Output", {
		LINKID_DataType = "Image",
		LINK_Main = 1,
		})

end




-- Alternate method of the above. Calculates only once instead of on-demand.
function calcAspect(ref_img)
	return (ref_img.Height * ref_img.YScale) / (ref_img.Width * ref_img.XScale)
end

function Process(req)
-- Standard set up for Creator tools
	local realwidth = Width;
	local realheight = Height;
	
	-- We'll handle proxy ourselves
	Width = Width / Scale
	Height = Height / Scale
	Scale = 1
	
	-- Attributes for new images
	local imgattrs = {
		IMG_Document = self.Comp,
		{ IMG_Channel = "Red", },
		{ IMG_Channel = "Green", },
		{ IMG_Channel = "Blue", },
		{ IMG_Channel = "Alpha", },
		IMG_Width = Width,
		IMG_Height = Height,
		IMG_XScale = XAspect,
		IMG_YScale = YAspect,
		IMAT_OriginalWidth = realwidth,
		IMAT_OriginalHeight = realheight,
		IMG_Quality = not req:IsQuick(),
		IMG_MotionBlurQuality = not req:IsNoMotionBlur(),
		}
		
	if not req:IsStampOnly() then
		imgattrs.IMG_ProxyScale = 1
	end
	
	if SourceDepth ~= 0 then
		imgattrs.IMG_Depth = SourceDepth
	end

	-- Set up image
	local img = Image(imgattrs)
	local out = img:CopyOf()
	local p = Pixel({R=0,G=0,B=0,A=0})
	img:Fill(p) -- Clear the image so the next frame doesn't contain the previous one.
	out:Fill(p)

	local aspect = calcAspect(img)
 
-- Import variables from control panel 
	local rectWidth 	= InWidth:GetValue(req).Value
	local rectHeight   		 = InHeight:GetValue(req).Value
	local rounding    = InRounding:GetValue(req).Value
	local subdivs    = InSubdivs:GetValue(req).Value
	local linetype   = math.floor(InLineType:GetValue(req).Value + 0.5) + 1
	local thickness  = InThickness:GetValue(req).Value
	local blur       = InSoftness:GetValue(req).Value
	local filter     = math.floor(InFilter:GetValue(req).Value + 0.5) + 1
	local gain_r     = InRed:GetValue(req).Value
	local gain_g     = InGreen:GetValue(req).Value
	local gain_b     = InBlue:GetValue(req).Value
	local gain_a     = InAlpha:GetValue(req).Value
	local onblack    = (InOnBlack:GetValue(req).Value > 0.5)
	local outlinetypes = {"OLT_Solid", "OLT_Dash", "OLT_Dot", "OLT_DashDot", "OLT_DashDotDot",}
	local blurfilters  = {"BT_Box", "BT_Bartless", "BT_MultiBox", "BT_Gaussian", }

	
	local imageHeight = img.Height
	local imageWidth = img.Width
	local centreX = (imageWidth/2)/imageWidth
	local centreY = ((imageHeight/2)/imageHeight)*aspect
	local halfWidth = rectWidth/2
	local halfHeight = (rectHeight/2)*aspect
	
	local cornerRadius = halfHeight
	
	if halfWidth < halfHeight then cornerRadius = halfWidth end
	
	local correctedRadius = cornerRadius * rounding
	
	local bottomRightCorner = Point(centreX+halfWidth,centreY-halfHeight)
	local topRightCorner = Point(centreX+halfWidth,centreY+halfHeight)
	local topLeftCorner = Point(centreX-halfWidth,centreY+halfHeight)
	local bottomLeftCorner = Point(centreX-halfWidth,centreY-halfHeight)
	
	local p1 = Point(bottomRightCorner.X-correctedRadius,bottomRightCorner.Y)
	local p2 = Point(bottomRightCorner.X,bottomRightCorner.Y+correctedRadius)
	local p3 = Point(topRightCorner.X,topRightCorner.Y-correctedRadius)
	local p4 = Point(topRightCorner.X-correctedRadius,topRightCorner.Y)
	local p5 = Point(topLeftCorner.X+correctedRadius,topLeftCorner.Y)
	local p6 = Point(topLeftCorner.X,topLeftCorner.Y-correctedRadius)
	local p7 = Point(bottomLeftCorner.X,bottomLeftCorner.Y+correctedRadius)
	local p8 = Point(bottomLeftCorner.X+correctedRadius,bottomLeftCorner.Y)
	
	-- Create a Shape object and initialize it.
	local line1 = Shape()	
	line1:MoveTo(p1.X,p1.Y)
	
	local anglePerStep = (math.pi/2)/subdivs
	-- Bottom Right Corner
	local cornerCenterX = p1.X
	local cornerCenterY = p2.Y
	
	for i=1,(subdivs-1) do
		newX = cornerCenterX + (correctedRadius*math.sin(anglePerStep*i))
		newY = cornerCenterY - (correctedRadius*math.cos(anglePerStep*i))
		line1:LineTo(newX,newY)
	end
	
	
	
	
	line1:LineTo(p2.X,p2.Y)
	line1:LineTo(p3.X,p3.Y)
	
	-- Top Right Corner
	cornerCenterX = p4.X
	cornerCenterY = p3.Y
	
	for i=1,(subdivs-1) do
		newX = cornerCenterX + (correctedRadius*math.cos(anglePerStep*i))
		newY = cornerCenterY + (correctedRadius*math.sin(anglePerStep*i))
		line1:LineTo(newX,newY)
	end
	
	
	
	line1:LineTo(p4.X,p4.Y)
	line1:LineTo(p5.X,p5.Y)
	
	-- Top Left Corner
	cornerCenterX = p5.X
	cornerCenterY = p6.Y
	
	for i=(subdivs-1),1,-1 do
		newX = cornerCenterX - (correctedRadius*math.cos(anglePerStep*i))
		newY = cornerCenterY + (correctedRadius*math.sin(anglePerStep*i))
		line1:LineTo(newX,newY)
	end
	
	
	
	
	line1:LineTo(p6.X,p6.Y)
	line1:LineTo(p7.X,p7.Y)
	
	-- Bottom Left Corner
	cornerCenterX = p8.X
	cornerCenterY = p7.Y
	
	for i=(subdivs-1),1,-1 do
		newX = cornerCenterX - (correctedRadius*math.sin(anglePerStep*i))
		newY = cornerCenterY - (correctedRadius*math.cos(anglePerStep*i))
		line1:LineTo(newX,newY)
	end
	
	line1:LineTo(p8.X,p8.Y)
	line1:LineTo(p1.X,p1.Y)

	

 	
	line1 = line1:OutlineOfShape(thickness, outlinetypes[linetype], "OJT_Round", (req:IsQuick() and 8 or 16))

	
	-- put shape to image
	local ic = ImageChannel(out, 8)
	local fs = FillStyle()
	ic:SetStyleFill(fs)
	ic:ShapeFill(line1)
	local cs = ChannelStyle()
	cs.Color = Pixel({R = gain_r, G = gain_g, B = gain_b, A = gain_a})
	cs.BlurType = blurfilters[filter]
	cs.SoftnessX = blur
	cs.SoftnessY = blur
	if self.Status == "OK" then
		ic:PutToImage("CM_Merge", cs)
	end
	
	
	OutImage:Set(req, out)
end
It's made by picking apart Bryan Ray's Bezier fuse in a language that I don't really understand properly, but it does seem to work.(I know swift and objective C better)

Is there a way of getting the actual shape out of this as a path that I could then use in the paint node to draw on/off ? Alternatively is there a way of adding the Draw On/Off control ?

Thanks.

Re: Rounded Rectangle Fuse

Posted: Fri Nov 08, 2019 3:14 pm
by Midgardsormr
I haven't found a way to interact with a real Polyline yet. TaperedShapes does a write on/off feature by multiplying the range control by the subdivs when initializing the loops:

for i=writeOnStart*subdivs+1,writeOnEnd*subdivs do

In order for that to work for what you're doing, you'd need to subdivide the straight sides of your rectangle, and I guess work out how to distribute the range control across the segments of your shape, based on its dimensions. That is, you'd need to know the ratios between the horizontal and vertical lines, and also the perimeter of the rounded corners. Tricky problem.

Re: Rounded Rectangle Fuse

Posted: Sat Nov 09, 2019 12:09 am
by ShadowMaker SdR
Thanks for all the work. This should make it a little easier to save and use it.
Code: [Select all] [Expand/Collapse] [Download] (RoundedRect.fuse)
  1. --[[--
  2. RoundedRect.Fuse
  3.  
  4. A rounded rectangle tool, butchered from Bryan Ray's Bezier Fuse
  5.  
  6. Not elegant in any way.
  7.  
  8. version 0.1
  9. 8/11/19
  10. Steve Watson
  11.  
  12.  
  13. --]]--
  14.  
  15.  
  16. FuRegisterClass("RoundRect", CT_SourceTool, {
  17.     REGS_Name = "RoundRect",
  18.     REGS_Category = "Creator",
  19.     REGS_OpIconString = "Rrec",
  20.     REGS_OpDescription = "Rectangle Drawing Path.",
  21.    
  22.     REGS_Company = "SteveWatson",
  23.     REGS_URL = "",
  24.    
  25.     REG_Source_GlobalCtrls = true,
  26.     REG_Source_SizeCtrls = true,
  27.     REG_Source_AspectCtrls = true,
  28.     REG_Source_DepthCtrls = true,
  29.     })
  30.    
  31.  
  32. function Create()
  33.     InWidth = self:AddInput("RectWidth", "RectWidth", {
  34.         LINKID_DataType = "Number",
  35.         INPID_InputControl = "SliderControl",
  36.         INP_MinAllowed = 0,
  37.         INP_MaxScale = 1,
  38.         INP_Default = 0.5,
  39.        
  40.     })
  41.  
  42.     InHeight = self:AddInput("RectHeight", "RectHeight", {
  43.         LINKID_DataType = "Number",
  44.         INPID_InputControl = "SliderControl",
  45.         INP_MinAllowed = 0,
  46.         INP_MaxScale = 1,
  47.         INP_Default = 0.5,
  48.        
  49.     })
  50.     InRounding = self:AddInput("CornerRounding", "CornerRounding", {
  51.         LINKID_DataType = "Number",
  52.         INPID_InputControl = "SliderControl",
  53.         INP_MinAllowed = 0,
  54.         INP_MaxScale = 1,
  55.         INP_Default = 0.5,
  56.        
  57.     })
  58.    
  59.         InSubdivs = self:AddInput("Smoothness", "Smoothness", {
  60.         LINKID_DataType = "Number",
  61.         INPID_InputControl = "SliderControl",
  62.         INP_MinAllowed = 1,
  63.         INP_MaxScale = 50,
  64.         INP_Default = 20,
  65.         INP_Integer = true,
  66.     })
  67.    
  68.         InThickness = self:AddInput("Thickness", "Thickness", {
  69.         LINKID_DataType    = "Number",
  70.         INPID_InputControl = "SliderControl",
  71.         INP_MinAllowed     = 0,
  72.         INP_MaxScale       = 0.1,
  73.         INP_Default        = 0.01,
  74.         })
  75.        
  76.         self:BeginControlNest("Line Style", "LineStyle", false, {})
  77.     InLineType = self:AddInput("Type", "Type", {
  78.         LINKID_DataType    = "Number",
  79.         INPID_InputControl = "MultiButtonControl",
  80.         INP_Default        = 0.0,
  81.         MBTNC_ShowName     = false,
  82.         {MBTNC_AddButton   = "Solid", MBTNCD_ButtonWidth = 0.33, },
  83.         {MBTNC_AddButton   = "Dash",  MBTNCD_ButtonWidth = 0.34, },
  84.         {MBTNC_AddButton   = "Dot",   MBTNCD_ButtonWidth = 0.33, },
  85.         {MBTNC_AddButton   = "Dash Dot",     MBTNCD_ButtonWidth = 0.5, },
  86.         {MBTNC_AddButton   = "Dash Dot Dot", MBTNCD_ButtonWidth = 0.5, },
  87.         })
  88.  
  89.     InThickness = self:AddInput("Thickness", "Thickness", {
  90.         LINKID_DataType    = "Number",
  91.         INPID_InputControl = "SliderControl",
  92.         INP_MinAllowed     = 0,
  93.         INP_MaxScale       = 0.1,
  94.         INP_Default        = 0.01,
  95.         })
  96.  
  97.     InFilter = self:AddInput("Filter", "Filter", {
  98.         LINKID_DataType    = "Number",
  99.         INPID_InputControl = "MultiButtonControl",
  100.         INP_Default        = 3.0,
  101.         {MBTNC_AddButton   = "Box",       MBTNCD_ButtonWidth = 0.25, },
  102.         {MBTNC_AddButton   = "Bartlett",  MBTNCD_ButtonWidth = 0.25, },
  103.         {MBTNC_AddButton   = "Multi-box", MBTNCD_ButtonWidth = 0.25, },
  104.         {MBTNC_AddButton   = "Gaussian",  MBTNCD_ButtonWidth = 0.25, },
  105.         })
  106.  
  107.     InSoftness = self:AddInput("Softness", "Softness", {
  108.         LINKID_DataType    = "Number",
  109.         INPID_InputControl = "SliderControl",
  110.         INP_MinAllowed     = 0.0,
  111.         INP_MaxScale       = 100,
  112.         INP_Default        = 0.0,
  113.         ICD_Center         = 10,
  114.         })
  115.  
  116.     -- color wheel
  117.     InRed = self:AddInput("Red", "Red", {
  118.         ICS_Name            = "Line Color",
  119.         LINKID_DataType     = "Number",
  120.         INPID_InputControl  = "ColorControl",
  121.         INP_Default         = 1.0,
  122.         INP_MaxScale        = 1.0,
  123.         CLRC_ShowWheel      = false,
  124.         IC_ControlGroup     = 1,
  125.         IC_ControlID        = 0,
  126.         })
  127.     InGreen = self:AddInput("Green", "Green", {
  128.         LINKID_DataType     = "Number",
  129.         INPID_InputControl  = "ColorControl",
  130.         INP_Default         = 1.0,
  131.         IC_ControlGroup     = 1,
  132.         IC_ControlID        = 1,
  133.         })
  134.     InBlue = self:AddInput("Blue", "Blue", {
  135.         LINKID_DataType     = "Number",
  136.         INPID_InputControl  = "ColorControl",
  137.         INP_Default         = 1.0,
  138.         IC_ControlGroup     = 1,
  139.         IC_ControlID        = 2,
  140.         })
  141.     InAlpha = self:AddInput("Alpha", "Alpha", {
  142.         LINKID_DataType     = "Number",
  143.         INPID_InputControl  = "ColorControl",
  144.         INP_Default         = 1.0,
  145.         IC_ControlGroup     = 1,
  146.         IC_ControlID        = 3,
  147.         })
  148.  
  149.     InOnBlack = self:AddInput("Draw lines on black image", "OnBlack", {
  150.         LINKID_DataType     = "Number",
  151.         INPID_InputControl  = "CheckboxControl",
  152.         INP_Default         = 0,
  153.         })
  154.     self:EndControlNest()
  155.        
  156.        
  157.     -- outputs
  158.     OutImage = self:AddOutput("Output", "Output", {
  159.         LINKID_DataType = "Image",
  160.         LINK_Main = 1,
  161.         })
  162.  
  163. end
  164.  
  165.  
  166.  
  167.  
  168. -- Alternate method of the above. Calculates only once instead of on-demand.
  169. function calcAspect(ref_img)
  170.     return (ref_img.Height * ref_img.YScale) / (ref_img.Width * ref_img.XScale)
  171. end
  172.  
  173. function Process(req)
  174. -- Standard set up for Creator tools
  175.     local realwidth = Width;
  176.     local realheight = Height;
  177.    
  178.     -- We'll handle proxy ourselves
  179.     Width = Width / Scale
  180.     Height = Height / Scale
  181.     Scale = 1
  182.    
  183.     -- Attributes for new images
  184.     local imgattrs = {
  185.         IMG_Document = self.Comp,
  186.         { IMG_Channel = "Red", },
  187.         { IMG_Channel = "Green", },
  188.         { IMG_Channel = "Blue", },
  189.         { IMG_Channel = "Alpha", },
  190.         IMG_Width = Width,
  191.         IMG_Height = Height,
  192.         IMG_XScale = XAspect,
  193.         IMG_YScale = YAspect,
  194.         IMAT_OriginalWidth = realwidth,
  195.         IMAT_OriginalHeight = realheight,
  196.         IMG_Quality = not req:IsQuick(),
  197.         IMG_MotionBlurQuality = not req:IsNoMotionBlur(),
  198.         }
  199.        
  200.     if not req:IsStampOnly() then
  201.         imgattrs.IMG_ProxyScale = 1
  202.     end
  203.    
  204.     if SourceDepth ~= 0 then
  205.         imgattrs.IMG_Depth = SourceDepth
  206.     end
  207.  
  208.     -- Set up image
  209.     local img = Image(imgattrs)
  210.     local out = img:CopyOf()
  211.     local p = Pixel({R=0,G=0,B=0,A=0})
  212.     img:Fill(p) -- Clear the image so the next frame doesn't contain the previous one.
  213.     out:Fill(p)
  214.  
  215.     local aspect = calcAspect(img)
  216.  
  217. -- Import variables from control panel
  218.     local rectWidth     = InWidth:GetValue(req).Value
  219.     local rectHeight         = InHeight:GetValue(req).Value
  220.     local rounding    = InRounding:GetValue(req).Value
  221.     local subdivs    = InSubdivs:GetValue(req).Value
  222.     local linetype   = math.floor(InLineType:GetValue(req).Value + 0.5) + 1
  223.     local thickness  = InThickness:GetValue(req).Value
  224.     local blur       = InSoftness:GetValue(req).Value
  225.     local filter     = math.floor(InFilter:GetValue(req).Value + 0.5) + 1
  226.     local gain_r     = InRed:GetValue(req).Value
  227.     local gain_g     = InGreen:GetValue(req).Value
  228.     local gain_b     = InBlue:GetValue(req).Value
  229.     local gain_a     = InAlpha:GetValue(req).Value
  230.     local onblack    = (InOnBlack:GetValue(req).Value > 0.5)
  231.     local outlinetypes = {"OLT_Solid", "OLT_Dash", "OLT_Dot", "OLT_DashDot", "OLT_DashDotDot",}
  232.     local blurfilters  = {"BT_Box", "BT_Bartless", "BT_MultiBox", "BT_Gaussian", }
  233.  
  234.    
  235.     local imageHeight = img.Height
  236.     local imageWidth = img.Width
  237.     local centreX = (imageWidth/2)/imageWidth
  238.     local centreY = ((imageHeight/2)/imageHeight)*aspect
  239.     local halfWidth = rectWidth/2
  240.     local halfHeight = (rectHeight/2)*aspect
  241.    
  242.     local cornerRadius = halfHeight
  243.    
  244.     if halfWidth < halfHeight then cornerRadius = halfWidth end
  245.    
  246.     local correctedRadius = cornerRadius * rounding
  247.    
  248.     local bottomRightCorner = Point(centreX+halfWidth,centreY-halfHeight)
  249.     local topRightCorner = Point(centreX+halfWidth,centreY+halfHeight)
  250.     local topLeftCorner = Point(centreX-halfWidth,centreY+halfHeight)
  251.     local bottomLeftCorner = Point(centreX-halfWidth,centreY-halfHeight)
  252.    
  253.     local p1 = Point(bottomRightCorner.X-correctedRadius,bottomRightCorner.Y)
  254.     local p2 = Point(bottomRightCorner.X,bottomRightCorner.Y+correctedRadius)
  255.     local p3 = Point(topRightCorner.X,topRightCorner.Y-correctedRadius)
  256.     local p4 = Point(topRightCorner.X-correctedRadius,topRightCorner.Y)
  257.     local p5 = Point(topLeftCorner.X+correctedRadius,topLeftCorner.Y)
  258.     local p6 = Point(topLeftCorner.X,topLeftCorner.Y-correctedRadius)
  259.     local p7 = Point(bottomLeftCorner.X,bottomLeftCorner.Y+correctedRadius)
  260.     local p8 = Point(bottomLeftCorner.X+correctedRadius,bottomLeftCorner.Y)
  261.    
  262.     -- Create a Shape object and initialize it.
  263.     local line1 = Shape()  
  264.     line1:MoveTo(p1.X,p1.Y)
  265.    
  266.     local anglePerStep = (math.pi/2)/subdivs
  267.     -- Bottom Right Corner
  268.     local cornerCenterX = p1.X
  269.     local cornerCenterY = p2.Y
  270.    
  271.     for i=1,(subdivs-1) do
  272.         newX = cornerCenterX + (correctedRadius*math.sin(anglePerStep*i))
  273.         newY = cornerCenterY - (correctedRadius*math.cos(anglePerStep*i))
  274.         line1:LineTo(newX,newY)
  275.     end
  276.    
  277.    
  278.    
  279.    
  280.     line1:LineTo(p2.X,p2.Y)
  281.     line1:LineTo(p3.X,p3.Y)
  282.    
  283.     -- Top Right Corner
  284.     cornerCenterX = p4.X
  285.     cornerCenterY = p3.Y
  286.    
  287.     for i=1,(subdivs-1) do
  288.         newX = cornerCenterX + (correctedRadius*math.cos(anglePerStep*i))
  289.         newY = cornerCenterY + (correctedRadius*math.sin(anglePerStep*i))
  290.         line1:LineTo(newX,newY)
  291.     end
  292.    
  293.    
  294.    
  295.     line1:LineTo(p4.X,p4.Y)
  296.     line1:LineTo(p5.X,p5.Y)
  297.    
  298.     -- Top Left Corner
  299.     cornerCenterX = p5.X
  300.     cornerCenterY = p6.Y
  301.    
  302.     for i=(subdivs-1),1,-1 do
  303.         newX = cornerCenterX - (correctedRadius*math.cos(anglePerStep*i))
  304.         newY = cornerCenterY + (correctedRadius*math.sin(anglePerStep*i))
  305.         line1:LineTo(newX,newY)
  306.     end
  307.    
  308.    
  309.    
  310.    
  311.     line1:LineTo(p6.X,p6.Y)
  312.     line1:LineTo(p7.X,p7.Y)
  313.    
  314.     -- Bottom Left Corner
  315.     cornerCenterX = p8.X
  316.     cornerCenterY = p7.Y
  317.    
  318.     for i=(subdivs-1),1,-1 do
  319.         newX = cornerCenterX - (correctedRadius*math.sin(anglePerStep*i))
  320.         newY = cornerCenterY - (correctedRadius*math.cos(anglePerStep*i))
  321.         line1:LineTo(newX,newY)
  322.     end
  323.    
  324.     line1:LineTo(p8.X,p8.Y)
  325.     line1:LineTo(p1.X,p1.Y)
  326.  
  327.    
  328.  
  329.    
  330.     line1 = line1:OutlineOfShape(thickness, outlinetypes[linetype], "OJT_Round", (req:IsQuick() and 8 or 16))
  331.  
  332.    
  333.     -- put shape to image
  334.     local ic = ImageChannel(out, 8)
  335.     local fs = FillStyle()
  336.     ic:SetStyleFill(fs)
  337.     ic:ShapeFill(line1)
  338.     local cs = ChannelStyle()
  339.     cs.Color = Pixel({R = gain_r, G = gain_g, B = gain_b, A = gain_a})
  340.     cs.BlurType = blurfilters[filter]
  341.     cs.SoftnessX = blur
  342.     cs.SoftnessY = blur
  343.     if self.Status == "OK" then
  344.         ic:PutToImage("CM_Merge", cs)
  345.     end
  346.    
  347.    
  348.     OutImage:Set(req, out)
  349. end

Re: Rounded Rectangle Fuse

Posted: Sat Nov 09, 2019 2:43 am
by SteveWatson
Hi,

thanks for the info and for formatting the code. I'm going to look at the write on/off today trying the methods you described.

A probably stupid question, is it possible to edit a fuse without having to edit it it BBedit, save it then quit and restart resolve so it reloads new one ?

Re: Rounded Rectangle Fuse

Posted: Sat Nov 09, 2019 8:01 am
by Midgardsormr
Are there not Edit and Reload buttons at the top of the Fuse's control panel in Resolve? You should be able to just hit "Reload" to get an update. It won't work for changes to FuRegisterClass(), but it should for everything outside of that.

Re: Rounded Rectangle Fuse

Posted: Sat Nov 09, 2019 9:24 am
by SteveWatson
Midgardsormr wrote:
Sat Nov 09, 2019 8:01 am
Are there not Edit and Reload buttons at the top of the Fuse's control panel in Resolve? You should be able to just hit "Reload" to get an update. It won't work for changes to FuRegisterClass(), but it should for everything outside of that.
Hi,

I did say it was a stupid question, I didn't notice those buttons. Thanks.

Re: Rounded Rectangle Fuse

Posted: Sun Nov 10, 2019 6:15 am
by SteveWatson
Hi,

thanks to Bryan's tips I have got the start/end and offset working(basically just ripped them from his Tapered Shape fuse). They key was putting each subdivision in an array(why Lua calls them tables I'm not sure), then drawing them as required by start/end and offset.

Code: Select all

--[[--
RoundedRect.Fuse

A rounded rectangle tool, butchered from Bryan Ray's Bezier Fuse and Tapered Shape

Not elegant in any way. 

version 0.2
8/11/19
Steve Watson


--]]--


FuRegisterClass("RoundRect", CT_SourceTool, {
	REGS_Name = "RoundRect",
	REGS_Category = "Creator",
	REGS_OpIconString = "Rrec",
	REGS_OpDescription = "Rectangle Drawing Path.",
	
	REGS_Company = "SteveWatson",
	REGS_URL = "",
	
	REG_Source_GlobalCtrls = true,
	REG_Source_SizeCtrls = true,
	REG_Source_AspectCtrls = true,
	REG_Source_DepthCtrls = true,
	})
	

function Create()
	InWidth = self:AddInput("RectWidth", "RectWidth", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})

	InHeight = self:AddInput("RectHeight", "RectHeight", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})
	InRounding = self:AddInput("CornerRounding", "CornerRounding", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})
	
		InSubdivs = self:AddInput("Smoothness", "Smoothness", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 1,
		INP_MaxScale = 50,
		INP_Default = 20,
		INP_Integer = true,
	})
	
		InThickness = self:AddInput("Thickness", "Thickness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0,
		INP_MaxScale       = 0.1,
		INP_Default        = 0.01,
		})
		
		self:BeginControlNest("Line Style", "LineStyle", false, {})
	InLineType = self:AddInput("Type", "Type", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "MultiButtonControl",
		INP_Default        = 0.0,
		MBTNC_ShowName     = false,
		{MBTNC_AddButton   = "Solid", MBTNCD_ButtonWidth = 0.33, },
		{MBTNC_AddButton   = "Dash",  MBTNCD_ButtonWidth = 0.34, },
		{MBTNC_AddButton   = "Dot",   MBTNCD_ButtonWidth = 0.33, },
		{MBTNC_AddButton   = "Dash Dot",     MBTNCD_ButtonWidth = 0.5, },
		{MBTNC_AddButton   = "Dash Dot Dot", MBTNCD_ButtonWidth = 0.5, },
		})
		
	InEnd = self:AddInput("LineEnd", "LineEnd", {
		LINKID_DataType = "Number",
		INPID_InputControl = "MultiButtonControl",
		{ MBTNC_AddButton = "Round", },
		{ MBTNC_AddButton = "Square", },
		MBTNC_StretchToFit = true,
		INP_Default = 0,
		INP_DoNotifyChanged = true,
	})

	InThickness = self:AddInput("Thickness", "Thickness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0,
		INP_MaxScale       = 0.1,
		INP_Default        = 0.01,
		})

	InFilter = self:AddInput("Filter", "Filter", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "MultiButtonControl",
		INP_Default        = 3.0,
		{MBTNC_AddButton   = "Box",       MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Bartlett",  MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Multi-box", MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Gaussian",  MBTNCD_ButtonWidth = 0.25, },
		})

	InSoftness = self:AddInput("Softness", "Softness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0.0,
		INP_MaxScale       = 100,
		INP_Default        = 0.0,
		ICD_Center         = 10,
		})

	-- color wheel
	InRed = self:AddInput("Red", "Red", {
		ICS_Name            = "Line Color",
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		INP_MaxScale        = 1.0,
		CLRC_ShowWheel      = false,
		IC_ControlGroup     = 1,
		IC_ControlID        = 0,
		})
	InGreen = self:AddInput("Green", "Green", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 1,
		})
	InBlue = self:AddInput("Blue", "Blue", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 2,
		})
	InAlpha = self:AddInput("Alpha", "Alpha", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 3,
		})

	InOnBlack = self:AddInput("Draw lines on black image", "OnBlack", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "CheckboxControl",
		INP_Default         = 0,
		})
	self:EndControlNest()
	
	InWriteOnStart = self:AddInput("Write On Start", "WriteOnStart", {
		LINKID_DataType = "Number",
--		INP_Integer = true,
		INPID_InputControl = "RangeControl",
--		ICS_ControlPage = "Controls",
		IC_ControlGroup = 1,
--		IC_ControlID = 0,
		LINKS_Name = "Start",
		RNGCS_MidName = "Write On",
		INP_MinScale = -0,
		INP_MaxScale = 1.0,
--		RNGCD_LowOuterLength = "0.1",
		INP_Default = 0,
	})
	
	InWriteOnEnd = self:AddInput("Write On End", "WriteOnEnd", {
		LINKID_DataType = "Number",
--		INP_Integer = true,
		INPID_InputControl = "RangeControl",
--		ICS_ControlPage = "Controls",
		IC_ControlGroup = 1,
		IC_ControlID = 1,
		LINKS_Name = "End",	
		INP_MinScale = 0,
		INP_MaxScale = 1,
--		RNGCD_HighOuterLength = "0.1",
		INP_Default = 1,
	})	
		
	InOffset = self:AddInput("Offset", "Offset", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ScrewControl",
		INP_Default = 0,
		INP_MaxScale = 5,
	})
		
		
	-- outputs
	OutImage = self:AddOutput("Output", "Output", {
		LINKID_DataType = "Image",
		LINK_Main = 1,
		})

end




-- Alternate method of the above. Calculates only once instead of on-demand.
function calcAspect(ref_img)
	return (ref_img.Height * ref_img.YScale) / (ref_img.Width * ref_img.XScale)
end

function Process(req)
-- Standard set up for Creator tools
	local realwidth = Width;
	local realheight = Height;
	
	-- We'll handle proxy ourselves
	Width = Width / Scale
	Height = Height / Scale
	Scale = 1
	
	-- Attributes for new images
	local imgattrs = {
		IMG_Document = self.Comp,
		{ IMG_Channel = "Red", },
		{ IMG_Channel = "Green", },
		{ IMG_Channel = "Blue", },
		{ IMG_Channel = "Alpha", },
		IMG_Width = Width,
		IMG_Height = Height,
		IMG_XScale = XAspect,
		IMG_YScale = YAspect,
		IMAT_OriginalWidth = realwidth,
		IMAT_OriginalHeight = realheight,
		IMG_Quality = not req:IsQuick(),
		IMG_MotionBlurQuality = not req:IsNoMotionBlur(),
		}
		
	if not req:IsStampOnly() then
		imgattrs.IMG_ProxyScale = 1
	end
	
	if SourceDepth ~= 0 then
		imgattrs.IMG_Depth = SourceDepth
	end

	-- Set up image
	local img = Image(imgattrs)
	local out = img:CopyOf()
	local p = Pixel({R=0,G=0,B=0,A=0})
	img:Fill(p) -- Clear the image so the next frame doesn't contain the previous one.
	out:Fill(p)

	local aspect = calcAspect(img)
 
-- Import variables from control panel 
	local rectWidth 	= InWidth:GetValue(req).Value
	local rectHeight   		 = InHeight:GetValue(req).Value
	local rounding    = InRounding:GetValue(req).Value
	local subdivs    = InSubdivs:GetValue(req).Value
	local linetype   = math.floor(InLineType:GetValue(req).Value + 0.5) + 1
	local lineEnd 		= InEnd:GetValue(req).Value
	local thickness  = InThickness:GetValue(req).Value
	local blur       = InSoftness:GetValue(req).Value
	local filter     = math.floor(InFilter:GetValue(req).Value + 0.5) + 1
	local gain_r     = InRed:GetValue(req).Value
	local gain_g     = InGreen:GetValue(req).Value
	local gain_b     = InBlue:GetValue(req).Value
	local gain_a     = InAlpha:GetValue(req).Value
	local onblack    = (InOnBlack:GetValue(req).Value > 0.5)
	local outlinetypes = {"OLT_Solid", "OLT_Dash", "OLT_Dot", "OLT_DashDot", "OLT_DashDotDot",}
	local blurfilters  = {"BT_Box", "BT_Bartless", "BT_MultiBox", "BT_Gaussian", }
	local lineEnds = {"OJT_Round","OJT_Square"}

	local writeOnStart 		= InWriteOnStart:GetValue(req).Value
	local writeOnEnd		= InWriteOnEnd:GetValue(req).Value
	local offset 		 	= InOffset:GetValue(req).Value
	
	local imageHeight = img.Height
	local imageWidth = img.Width
	local centreX = (imageWidth/2)/imageWidth
	local centreY = ((imageHeight/2)/imageHeight)*aspect
	local halfWidth = rectWidth/2
	local halfHeight = (rectHeight/2)*aspect
	if not subdivs then subdivs = 40 end
	local cornerRadius = halfHeight
	
	-- choose radius based on shortest between width and height
	
	if halfWidth < halfHeight then cornerRadius = halfWidth end
	
	local correctedRadius = cornerRadius * rounding
	
	-- calculate corner centres
	local bottomRightCorner = Point(centreX+halfWidth,centreY-halfHeight)
	local topRightCorner = Point(centreX+halfWidth,centreY+halfHeight)
	local topLeftCorner = Point(centreX-halfWidth,centreY+halfHeight)
	local bottomLeftCorner = Point(centreX-halfWidth,centreY-halfHeight)
	
	-- calculate points that join corners
	
	local p1 = Point(bottomRightCorner.X-correctedRadius,bottomRightCorner.Y)
	local p2 = Point(bottomRightCorner.X,bottomRightCorner.Y+correctedRadius)
	local p3 = Point(topRightCorner.X,topRightCorner.Y-correctedRadius)
	local p4 = Point(topRightCorner.X-correctedRadius,topRightCorner.Y)
	local p5 = Point(topLeftCorner.X+correctedRadius,topLeftCorner.Y)
	local p6 = Point(topLeftCorner.X,topLeftCorner.Y-correctedRadius)
	local p7 = Point(bottomLeftCorner.X,bottomLeftCorner.Y+correctedRadius)
	local p8 = Point(bottomLeftCorner.X+correctedRadius,bottomLeftCorner.Y)
	
	
	
	local line = {} -- Table to hold line segment Shape objects
	
	local segmentCountTotal = subdivs * 8
	
	local segmentCounter = 0
	

	local anglePerStep = (math.pi/2)/subdivs
	
	
	
	
	-- Bottom Right Corner
	local cornerCenterX = p1.X
	local cornerCenterY = p2.Y
	
	local newX = p1.X
	local newY = p1.Y
	
	for i=1,(subdivs) do
		line[segmentCounter] = Shape()
		line[segmentCounter]: MoveTo(newX,newY)
		nextX = cornerCenterX + (correctedRadius*math.sin(anglePerStep*i))
		nextY = cornerCenterY - (correctedRadius*math.cos(anglePerStep*i))
		line[segmentCounter]:LineTo(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
			
		

		
	end
	
	
	-- calculate each segment length
	
	local segmentLengthY = (p3.Y-p2.Y)/subdivs
	
	-- Right vertical side

	
	newX = p2.X
	newY = p2.Y
	 
		
	for i=1,subdivs do
		
		line[segmentCounter] = Shape()
		line[segmentCounter]:MoveTo(newX,newY)
		nextY = (newY + segmentLengthY)
		line[segmentCounter]:LineTo(newX,nextY)
		newY = nextY
		segmentCounter = segmentCounter + 1
		
		
	end
	

		
	-- Top Right Corner
	cornerCenterX = p4.X
	cornerCenterY = p3.Y
	
	newX = p3.X
	newY = p3.Y
	
	for i=1,(subdivs) do
		line[segmentCounter] = Shape()
		line[segmentCounter]: MoveTo(newX,newY)
		nextX = cornerCenterX + (correctedRadius*math.cos(anglePerStep*i))
		nextY = cornerCenterY + (correctedRadius*math.sin(anglePerStep*i))
		line[segmentCounter]:LineTo(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
		

		
	end
	
	-- Top Edge
	local segmentLengthX = (p4.X-p5.X)/subdivs
	newX = p4.X
	newY = p4.Y
	
	for i=1,subdivs do
		
		line[segmentCounter] = Shape()
		line[segmentCounter]:MoveTo(newX,newY)
		nextX = (newX - segmentLengthX)
		line[segmentCounter]:LineTo(nextX,newY)
		newX = nextX
		segmentCounter = segmentCounter + 1
		
		
	end
	
	-- Top Left Corner
	cornerCenterX = p5.X
	cornerCenterY = p6.Y
	
	newX = p5.X
	newY = p5.Y
	
	for i=1,(subdivs) do
		line[segmentCounter] = Shape()
		line[segmentCounter]: MoveTo(newX,newY)
		newAngle = (math.pi/2)-(anglePerStep*i)
		nextX = cornerCenterX - (correctedRadius*math.cos(newAngle))
		nextY = cornerCenterY + (correctedRadius*math.sin(newAngle))
		line[segmentCounter]:LineTo(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
		

		
	end
	
	-- Left edge
	
	newX = p6.X
	newY = p6.Y
	 
		
	for i=1,subdivs do
		
		line[segmentCounter] = Shape()
		line[segmentCounter]:MoveTo(newX,newY)
		nextY = (newY - segmentLengthY)
		line[segmentCounter]:LineTo(newX,nextY)
		newY = nextY
		segmentCounter = segmentCounter + 1
		
		
	end
	
	-- Bottom Left Corner
	
	cornerCenterX = p8.X
	cornerCenterY = p7.Y
	
	newX = p7.X
	newY = p7.Y
	
	for i=1,(subdivs) do
		line[segmentCounter] = Shape()
		line[segmentCounter]: MoveTo(newX,newY)
		newAngle = (math.pi/2)-(anglePerStep*i)
		nextX = cornerCenterX - (correctedRadius*math.sin(newAngle))
		nextY = cornerCenterY - (correctedRadius*math.cos(newAngle))
		line[segmentCounter]:LineTo(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
		

		
	end
	
	-- Bottom Edge
	
	newX = p8.X
	newY = p8.Y
	
	for i=1,subdivs do
		
		line[segmentCounter] = Shape()
		line[segmentCounter]:MoveTo(newX,newY)
		nextX = (newX + segmentLengthX)
		line[segmentCounter]:LineTo(nextX,newY)
		newX = nextX
		segmentCounter = segmentCounter + 1
		
		
	end
	
	
	
	
	
	
	-- Construct the Line if start and end are not both 0 or 1, inelegant way of doing this
	
	if not((writeOnStart == 0 and writeOnEnd == 0) or (writeOnStart == 1 and writeOnEnd == 1)) then
		
	
	
		print("New")
		for i=math.floor(writeOnStart*(segmentCountTotal-1)),math.floor(writeOnEnd*(segmentCountTotal-1)) do
		
			local j = i
			local normOffset = math.floor(offset * subdivs*8)
			j = ((j + normOffset)%(subdivs*8))
			
			print(j)
			
			line[j] = line[j]:OutlineOfShape(thickness, outlinetypes[linetype], "OJT_Round", (req:IsQuick() and 8 or 16))
		end
	end
	

	

 	
	

	
	-- put shape to image
	local ic = ImageChannel(out, 8)
	local fs = FillStyle()
	ic:SetStyleFill(fs)
	
	local cs = ChannelStyle()
	cs.Color = Pixel({R = gain_r, G = gain_g, B = gain_b, A = gain_a})
	cs.BlurType = blurfilters[filter]
	cs.SoftnessX = blur
	cs.SoftnessY = blur
	for i,segmentCountTotal in pairs(line) do
		ic:ShapeFill(line[i])
		if self.Status == "OK" then
			ic:PutToImage("CM_Merge", cs)
		end
	end
	
	
	OutImage:Set(req, out)
end
It's almost there, the line style is pretty redundant (ie dots/dashes etc) because as each line is made up of lots of individual lines which are drawn separately the dots/dashes etc are drawn per individual segment as opposed to the whole line shape. I left it in because there are some possibilities with the style.

I've added a dropdown to select the end of the line, to try and get a square end(it's there in the paint node when you use a poly line), but that doesn't seem to work, using an array of {"OJT_Round","OJT_Square",} and selecting that in the drawing routing makes no difference. I've checked the fusion reference guide and there is no list of the drawing styles.

My only issue is some graphical glitches every now and then, I don't know what's causing them. You can see them in this video. This is the same fusion comp copied and pasted a few time in a timeline and the glitches are not consistent across each copy(ie the glitches appear in a frame of one copy but not at the exact frame in another)


Re: Rounded Rectangle Fuse

Posted: Sun Nov 10, 2019 8:40 am
by Midgardsormr
SteveWatson wrote:
Sun Nov 10, 2019 6:15 am
They key was putting each subdivision in an array(why Lua calls them tables I'm not sure),
Because a table is a bit more than an array. In Lua, there's only one collective data type—the table. This can be an array, as you have noted, but it can also be what Python calls a dictionary—a table with tables as entries, or even as keys. Some languages make a distinction between an array and a list (ordered vs unordered). A Lua table can be either, depending on how it's set up.

One important thing to be aware of is that Lua generally indexes its tables starting at 1 instead of 0. It doesn't have to, but if you do something like for i, item in ipairs(table) do, your first value for i is almost always going to be 1, which might cause some confusion if you're not ready for it.
using an array of {"OJT_Round","OJT_Square",} and selecting that in the drawing routing makes no difference. I've checked the fusion reference guide and there is no list of the drawing styles.
I think those may be the only two end-cap styles. When I get back to work tomorrow, I'll try to remember to look at my own documentation to see if there are more. I learned most of what I know about the Shape API from tearing apart Dunn Lewis' Fuses, and he always leaves it locked to OJT_Round. I wonder if that's because that parameter doesn't work for some reason? I've never experimented with it.
My only issue is some graphical glitches every now and then, I don't know what's causing them. You can see them in this video. This is the same fusion comp copied and pasted a few time in a timeline and the glitches are not consistent across each copy(ie the glitches appear in a frame of one copy but not at the exact frame in another)
Nothing jumps out at me in your code, but I haven't looked at it very deeply, either. I never saw anything that looked like those artifacts when I was developing TaperedShapes (though I certainly saw quite a few other artifacts).

Re: Rounded Rectangle Fuse

Posted: Mon Nov 11, 2019 9:07 am
by SteveWatson
Hi,

thanks again to Bryan I think I cracked it. I looked at the fuses by Dunn Lewis(which are astounding) and the key was instead of drawing each segment individually(which could amount to drawing hundreds of individual lines) I just create a single shape made up of the line segments points, so the drawing operation is only called once.

Code: Select all

--[[--
RoundedRect.Fuse

A rounded rectangle tool, butchered from Bryan Ray's Bezier Fuse and Tapered Shape

Not elegant in any way. 

version 0.3
11/11/19
Steve Watson


--]]--


FuRegisterClass("RoundRect", CT_SourceTool, {
	REGS_Name = "RoundRect",
	REGS_Category = "Creator",
	REGS_OpIconString = "Rrec",
	REGS_OpDescription = "Rectangle Drawing Path.",
	
	REGS_Company = "SteveWatson",
	REGS_URL = "",
	
	REG_Source_GlobalCtrls = true,
	REG_Source_SizeCtrls = true,
	REG_Source_AspectCtrls = true,
	REG_Source_DepthCtrls = true,
	})
	

function Create()
	InWidth = self:AddInput("RectWidth", "RectWidth", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})

	InHeight = self:AddInput("RectHeight", "RectHeight", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})
	InRounding = self:AddInput("CornerRounding", "CornerRounding", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 0,
		INP_MaxScale = 1,
		INP_Default = 0.5,
		
	})
	
		InSubdivs = self:AddInput("Smoothness", "Smoothness", {
		LINKID_DataType = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed = 1,
		INP_MaxScale = 50,
		INP_Default = 20,
		INP_Integer = true,
	})
	
		InThickness = self:AddInput("Thickness", "Thickness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0,
		INP_MaxScale       = 0.1,
		INP_Default        = 0.01,
		})
		
		self:BeginControlNest("Line Style", "LineStyle", false, {})
	InLineType = self:AddInput("Type", "Type", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "MultiButtonControl",
		INP_Default        = 0.0,
		MBTNC_ShowName     = false,
		{MBTNC_AddButton   = "Solid", MBTNCD_ButtonWidth = 0.33, },
		{MBTNC_AddButton   = "Dash",  MBTNCD_ButtonWidth = 0.34, },
		{MBTNC_AddButton   = "Dot",   MBTNCD_ButtonWidth = 0.33, },
		{MBTNC_AddButton   = "Dash Dot",     MBTNCD_ButtonWidth = 0.5, },
		{MBTNC_AddButton   = "Dash Dot Dot", MBTNCD_ButtonWidth = 0.5, },
		})
		
	

	InThickness = self:AddInput("Thickness", "Thickness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0,
		INP_MaxScale       = 0.1,
		INP_Default        = 0.01,
		})

	InFilter = self:AddInput("Filter", "Filter", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "MultiButtonControl",
		INP_Default        = 3.0,
		{MBTNC_AddButton   = "Box",       MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Bartlett",  MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Multi-box", MBTNCD_ButtonWidth = 0.25, },
		{MBTNC_AddButton   = "Gaussian",  MBTNCD_ButtonWidth = 0.25, },
		})

	InSoftness = self:AddInput("Softness", "Softness", {
		LINKID_DataType    = "Number",
		INPID_InputControl = "SliderControl",
		INP_MinAllowed     = 0.0,
		INP_MaxScale       = 100,
		INP_Default        = 0.0,
		ICD_Center         = 10,
		})

	-- color wheel
	InRed = self:AddInput("Red", "Red", {
		ICS_Name            = "Line Color",
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		INP_MaxScale        = 1.0,
		CLRC_ShowWheel      = false,
		IC_ControlGroup     = 1,
		IC_ControlID        = 0,
		})
	InGreen = self:AddInput("Green", "Green", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 1,
		})
	InBlue = self:AddInput("Blue", "Blue", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 2,
		})
	InAlpha = self:AddInput("Alpha", "Alpha", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "ColorControl",
		INP_Default         = 1.0,
		IC_ControlGroup     = 1,
		IC_ControlID        = 3,
		})

	InOnBlack = self:AddInput("Draw lines on black image", "OnBlack", {
		LINKID_DataType     = "Number",
		INPID_InputControl  = "CheckboxControl",
		INP_Default         = 0,
		})
	self:EndControlNest()
	
	InWriteOnStart = self:AddInput("Write On Start", "WriteOnStart", {
		LINKID_DataType = "Number",
--		INP_Integer = true,
		INPID_InputControl = "RangeControl",
--		ICS_ControlPage = "Controls",
		IC_ControlGroup = 1,
--		IC_ControlID = 0,
		LINKS_Name = "Start",
		RNGCS_MidName = "Write On",
		INP_MinScale = -0,
		INP_MaxScale = 1.0,
--		RNGCD_LowOuterLength = "0.1",
		INP_Default = 0,
	})
	
	InWriteOnEnd = self:AddInput("Write On End", "WriteOnEnd", {
		LINKID_DataType = "Number",
--		INP_Integer = true,
		INPID_InputControl = "RangeControl",
--		ICS_ControlPage = "Controls",
		IC_ControlGroup = 1,
		IC_ControlID = 1,
		LINKS_Name = "End",	
		INP_MinScale = 0,
		INP_MaxScale = 1,
--		RNGCD_HighOuterLength = "0.1",
		INP_Default = 1,
	})	
		
	InOffset = self:AddInput("Offset", "Offset", {
		LINKID_DataType = "Number",
		INPID_InputControl = "ScrewControl",
		INP_Default = 0,
		INP_MaxScale = 5,
	})
		
		
	-- outputs
	OutImage = self:AddOutput("Output", "Output", {
		LINKID_DataType = "Image",
		LINK_Main = 1,
		})

end




-- Alternate method of the above. Calculates only once instead of on-demand.
function calcAspect(ref_img)
	return (ref_img.Height * ref_img.YScale) / (ref_img.Width * ref_img.XScale)
end

function Process(req)
-- Standard set up for Creator tools
	local realwidth = Width;
	local realheight = Height;
	
	-- We'll handle proxy ourselves
	Width = Width / Scale
	Height = Height / Scale
	Scale = 1
	
	-- Attributes for new images
	local imgattrs = {
		IMG_Document = self.Comp,
		{ IMG_Channel = "Red", },
		{ IMG_Channel = "Green", },
		{ IMG_Channel = "Blue", },
		{ IMG_Channel = "Alpha", },
		IMG_Width = Width,
		IMG_Height = Height,
		IMG_XScale = XAspect,
		IMG_YScale = YAspect,
		IMAT_OriginalWidth = realwidth,
		IMAT_OriginalHeight = realheight,
		IMG_Quality = not req:IsQuick(),
		IMG_MotionBlurQuality = not req:IsNoMotionBlur(),
		}
		
	if not req:IsStampOnly() then
		imgattrs.IMG_ProxyScale = 1
	end
	
	if SourceDepth ~= 0 then
		imgattrs.IMG_Depth = SourceDepth
	end

	-- Set up image
	local img = Image(imgattrs)
	local out = img:CopyOf()
	local p = Pixel({R=0,G=0,B=0,A=0})
	img:Fill(p) -- Clear the image so the next frame doesn't contain the previous one.
	out:Fill(p)

	local aspect = calcAspect(img)
 
-- Import variables from control panel 
	local rectWidth 	= InWidth:GetValue(req).Value
	local rectHeight   		 = InHeight:GetValue(req).Value
	local rounding    = InRounding:GetValue(req).Value
	local subdivs    = InSubdivs:GetValue(req).Value
	local linetype   = math.floor(InLineType:GetValue(req).Value + 0.5) + 1
	
	local thickness  = InThickness:GetValue(req).Value
	local blur       = InSoftness:GetValue(req).Value
	local filter     = math.floor(InFilter:GetValue(req).Value + 0.5) + 1
	local gain_r     = InRed:GetValue(req).Value
	local gain_g     = InGreen:GetValue(req).Value
	local gain_b     = InBlue:GetValue(req).Value
	local gain_a     = InAlpha:GetValue(req).Value
	local onblack    = (InOnBlack:GetValue(req).Value > 0.5)
	local outlinetypes = {"OLT_Solid", "OLT_Dash", "OLT_Dot", "OLT_DashDot", "OLT_DashDotDot",}
	local blurfilters  = {"BT_Box", "BT_Bartless", "BT_MultiBox", "BT_Gaussian", }
	

	local writeOnStart 		= InWriteOnStart:GetValue(req).Value
	local writeOnEnd		= InWriteOnEnd:GetValue(req).Value
	local offset 		 	= InOffset:GetValue(req).Value
	
	local imageHeight = img.Height
	local imageWidth = img.Width
	local centreX = (imageWidth/2)/imageWidth
	local centreY = ((imageHeight/2)/imageHeight)*aspect
	local halfWidth = rectWidth/2
	local halfHeight = (rectHeight/2)*aspect
	if not subdivs then subdivs = 40 end
	local cornerRadius = halfHeight
	
	-- choose radius based on shortest between width and height
	
	if halfWidth < halfHeight then cornerRadius = halfWidth end
	
	local correctedRadius = cornerRadius * rounding
	
	-- calculate corner centres
	local bottomRightCorner = Point(centreX+halfWidth,centreY-halfHeight)
	local topRightCorner = Point(centreX+halfWidth,centreY+halfHeight)
	local topLeftCorner = Point(centreX-halfWidth,centreY+halfHeight)
	local bottomLeftCorner = Point(centreX-halfWidth,centreY-halfHeight)
	
	-- calculate points that join corners
	
	local p1 = Point(bottomRightCorner.X-correctedRadius,bottomRightCorner.Y)
	local p2 = Point(bottomRightCorner.X,bottomRightCorner.Y+correctedRadius)
	local p3 = Point(topRightCorner.X,topRightCorner.Y-correctedRadius)
	local p4 = Point(topRightCorner.X-correctedRadius,topRightCorner.Y)
	local p5 = Point(topLeftCorner.X+correctedRadius,topLeftCorner.Y)
	local p6 = Point(topLeftCorner.X,topLeftCorner.Y-correctedRadius)
	local p7 = Point(bottomLeftCorner.X,bottomLeftCorner.Y+correctedRadius)
	local p8 = Point(bottomLeftCorner.X+correctedRadius,bottomLeftCorner.Y)
	
	
	
	local lineStartPoints = {} -- Array of Start Points
	local lineEndPoints = {} -- Array of EndPoints Points
	
	local segmentCountTotal = subdivs * 8
	
	local segmentCounter = 0
	

	local anglePerStep = (math.pi/2)/subdivs
	
	
	
	
	-- Bottom Right Corner
	local cornerCenterX = p1.X
	local cornerCenterY = p2.Y
	
	local newX = p1.X
	local newY = p1.Y
	
	for i=1,(subdivs) do
	
		lineStartPoints[segmentCounter] = Point(newX,newY)
		nextX = cornerCenterX + (correctedRadius*math.sin(anglePerStep*i))
		nextY = cornerCenterY - (correctedRadius*math.cos(anglePerStep*i))
	
		lineEndPoints[segmentCounter] = Point(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
			
		

		
	end
	
	
	-- calculate each segment length
	
	local segmentLengthY = (p3.Y-p2.Y)/subdivs
	
	-- Right vertical side

	
	newX = p2.X
	newY = p2.Y
	 
		
	for i=1,subdivs do
		

		lineStartPoints[segmentCounter] = Point(newX,newY)
		nextY = (newY + segmentLengthY)
		
		lineEndPoints[segmentCounter] = Point(newX,nextY)
		newY = nextY
		segmentCounter = segmentCounter + 1
		
		
	end
	

		
	-- Top Right Corner
	cornerCenterX = p4.X
	cornerCenterY = p3.Y
	
	newX = p3.X
	newY = p3.Y
	
	for i=1,(subdivs) do
	
		lineStartPoints[segmentCounter] = Point(newX,newY)
		nextX = cornerCenterX + (correctedRadius*math.cos(anglePerStep*i))
		nextY = cornerCenterY + (correctedRadius*math.sin(anglePerStep*i))
	
		lineEndPoints[segmentCounter] = Point(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
		

		
	end
	
	-- Top Edge
	local segmentLengthX = (p4.X-p5.X)/subdivs
	newX = p4.X
	newY = p4.Y
	
	for i=1,subdivs do
		
	
		lineStartPoints[segmentCounter] = Point(newX,newY)
		nextX = (newX - segmentLengthX)
	
		lineEndPoints[segmentCounter] = Point(nextX,newY)
		newX = nextX
		segmentCounter = segmentCounter + 1
		
		
	end
	
	-- Top Left Corner
	cornerCenterX = p5.X
	cornerCenterY = p6.Y
	
	newX = p5.X
	newY = p5.Y
	
	for i=1,(subdivs) do
	
		lineStartPoints[segmentCounter] = Point(newX,newY)
		newAngle = (math.pi/2)-(anglePerStep*i)
		nextX = cornerCenterX - (correctedRadius*math.cos(newAngle))
		nextY = cornerCenterY + (correctedRadius*math.sin(newAngle))
	
		lineEndPoints[segmentCounter] = Point(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
		

		
	end
	
	-- Left edge
	
	newX = p6.X
	newY = p6.Y
	 
		
	for i=1,subdivs do
		
	
		lineStartPoints[segmentCounter] = Point(newX,newY)
		nextY = (newY - segmentLengthY)
		
		lineEndPoints[segmentCounter] = Point(newX,nextY)
		newY = nextY
		segmentCounter = segmentCounter + 1
		
		
	end
	
	-- Bottom Left Corner
	
	cornerCenterX = p8.X
	cornerCenterY = p7.Y
	
	newX = p7.X
	newY = p7.Y
	
	for i=1,(subdivs) do
		
		lineStartPoints[segmentCounter] = Point(newX,newY)
		newAngle = (math.pi/2)-(anglePerStep*i)
		nextX = cornerCenterX - (correctedRadius*math.sin(newAngle))
		nextY = cornerCenterY - (correctedRadius*math.cos(newAngle))
		
		lineEndPoints[segmentCounter] = Point(nextX,nextY)
		newX = nextX
		newY = nextY
		segmentCounter = segmentCounter + 1
		

		
	end
	
	-- Bottom Edge
	
	newX = p8.X
	newY = p8.Y
	
	for i=1,subdivs do
		

		lineStartPoints[segmentCounter] = Point(newX,newY)
		nextX = (newX + segmentLengthX)
		
		lineEndPoints[segmentCounter] = Point(nextX,newY)
		newX = nextX
		segmentCounter = segmentCounter + 1
		
		
	end
	
	
	
	
	local fullShape = Shape()
	
	-- Construct the Line if start and end are not both 0 or 1, inelegant way of doing this
	
	if not((writeOnStart == 0 and writeOnEnd == 0) or (writeOnStart == 1 and writeOnEnd == 1)) then
		
	
	
		
		for i=math.floor(writeOnStart*(segmentCountTotal-1)),math.floor(writeOnEnd*(segmentCountTotal-1)) do
		
			local j = i
			local normOffset = math.floor(offset * subdivs*8)
			j = ((j + normOffset)%(subdivs*8))
			
			startPoint = lineStartPoints[j]
			endPoint = lineEndPoints[j]
			fullShape:MoveTo(startPoint.X,startPoint.Y)
			fullShape:LineTo(endPoint.X,endPoint.Y)
			
			
			
		end
	end
	
	fullShape = fullShape:OutlineOfShape(thickness, outlinetypes[linetype], "OJT_Round", (req:IsQuick() and 8 or 16))
	

 	
	

	
	-- put shape to image
	local ic = ImageChannel(out, 8)
	local fs = FillStyle()
	ic:SetStyleFill(fs)
	
	local cs = ChannelStyle()
	cs.Color = Pixel({R = gain_r, G = gain_g, B = gain_b, A = gain_a})
	cs.BlurType = blurfilters[filter]
	cs.SoftnessX = blur
	cs.SoftnessY = blur

		ic:ShapeFill(fullShape)
		if self.Status == "OK" then
			ic:PutToImage("CM_Merge", cs)
		end
	
	
	OutImage:Set(req, out)
end


It's not the nicest code, in another language I'd create a custom class to hold the line segments instead of an array of start points and an array of end points, but looking into lua it's not the easiest thing to do(well without reading a lot more).

Thanks for all the help.

Re: Rounded Rectangle Fuse

Posted: Mon Nov 11, 2019 11:03 am
by Midgardsormr
Yeah, class-based Lua, while possible, is a bit of a pain.

Dunn's Fuses are a much better place to learn the Shapes API than mine. TaperedShapes is definitely a hack-job trying to get something that the API was never designed to do. To really do it right I'd probably need to dip into making an actual plug-in.

I promised to dig into my notes...

There are three OutlineJoinTypes: OJT_Bevel, OJT_Miter, and OJT_Round.

OutLineTypes: OLT_Solid, OLT_Dash, OLT_Dot, OLT_DashDot, OLT_DashDotDot, OLT_Custom

I suspect OLT_Custom is only available for SDK plug-ins.

There's also a FillOverlap property, which I haven't encountered yet, but has these possibilities: FO_Solid, FO_Transparent, FO_Composite

Re: Rounded Rectangle Fuse

Posted: Mon Nov 11, 2019 11:46 am
by Dunn
Hey @SteveWatson, you nailed it ! Nice fuse 8-)