I wanted to finish this side project during Women’s History Month to honor the Amazing Grace Hopper and her contributions to the field of Computer Science. I found this interesting SVG of Grace Hopper that wanted to figure out how to render in Delphi using the FireMonkey TPath, but it also looked like it should be animated somehow . . . .
I’ve rendered some simple SVG graphics with the TPath component before, but this one was more complicated. It has multiple colors and variable opacity. This requires multiple TPath instances to handle each variation. It was a simple matter of loading in the SVG file using an IXMLDocument, then parsing the elements, and creating a TPath for each one. For fun I included a variable sleep between each draw. Also, to make sure all the paths have the same relative size I added a couple MoveTo calls to define the client area.
var XmlSvg: IXMLDocument; val: String; vals: TArray<String>; node: IXMLNode; path: TPath; begin tabControl.ActiveTab := TabItem2; // This removes the encoded carriage returns XmlSvg := LoadXMLData(StringReplace(memo1.Text, '
', '', [rfReplaceAll])); if XmlSvg.DocumentElement.HasAttribute('viewBox') then begin val := XmlSvg.DocumentElement.Attributes['viewBox']; vals := val.Split([' ']); SVGLayout.Width := vals.ToInteger - vals.ToInteger; SVGLayout.Height := vals.ToInteger - vals.ToInteger; end; for var idx := 0 to XmlSvg.DocumentElement.ChildNodes.Count - 1 do begin node := XmlSvg.DocumentElement.ChildNodes[idx]; if (node.NodeName = 'path') and (node.HasAttribute('d')) then begin path := TPath.Create(svgLayout); path.Parent := svgLayout; path.WrapMode := TPathWrapMode.Stretch; path.Align := TAlignLayout.Contents; path.Data.Data := node.Attributes['d']; path.Data.MoveTo(TPointF.Zero); path.Data.MoveTo(TPointF.Create(SVGLayout.Width, SVGLayout.Height)); if node.HasAttribute('opacity') then path.Opacity := StrToFloat(node.Attributes['opacity']); if node.HasAttribute('fill') and (node.Attributes['fill'] <> 'none') and (node.Attributes['fill'] <> '') then path.Fill.Color := TAlphaColorRec.Alpha or StringToAlphaColor(node.Attributes['fill']); end; Sleep(Trunc(TrackBar1.Value)); svgLayout.Repaint; Application.ProcessMessages; end;
This is by no means a complete implementation of the SVG standard, but it is getting closer! Close enough for some simple SVG images though, and possibly a useful basis for more complicated ones.
The animations was just a matter of assigning a TFloatAnimation to each TPath that adds some random movement. I included both slight scales and rotations. I could have done both on each, but was afraid that might be too much movement.
var dance: TFloatAnimation; path: TPath; I: Integer; begin for I := 0 to pred(SVGLayout.ChildrenCount) do begin if SVGLayout.Children[I] is TPath then begin path := SVGLayout.Children[I] as TPath; dance := TFloatAnimation.Create(nil); dancers.Add(dance); dance.Parent := Path; dance.AutoReverse := True; dance.Loop := True; dance.StartFromCurrent := True; case random(4) of 0: begin case random(2) of 0: dance.PropertyName := 'Scale.X'; 1: dance.PropertyName := 'Scale.Y'; end; case random(2) of 0: dance.StopValue := 1.01; 1: dance.StopValue := 0.99; end; end; 1: begin case random(2) of 0: begin dance.PropertyName := 'Position.X'; case random(2) of 0: dance.StopValue := Path.Position.X - random - 0.01; 1: dance.StopValue := Path.Position.X + random + 0.01; end; end; 1: begin dance.PropertyName := 'Position.Y'; case random(2) of 0: dance.StopValue := Path.Position.Y - random - 0.01; 1: dance.StopValue := Path.Position.Y + random + 0.01; end; end; end; end; 2..3: begin dance.PropertyName := 'RotationAngle'; path.RotationCenter.X := random; path.RotationCenter.Y := random; case random(2) of 0: dance.StopValue := random + 0.01; 1: dance.StopValue := -1 * random - 0.01; end; end; end; dance.Enabled := True; end; end;
And we end up with something like this . . .. (Down scaled and lower FPS)
I’m posting my code if you want to play with it some more. The source SVG is embedded in a memo instead of reading it from a file. It was written with Delphi 10.3.1 Rio.
One reply on “Animated Path Graphics of Grace Hopper”
Great, but just one adaptation to your code. when i ran on my Delphi french version I ran into an conversion error line 80.
I made a change
if node.HasAttribute('opacity') then
path.Opacity := StrToFloat(node.Attributes['opacity'],FmtSettings);
where FMtSettings is a TFormatSettings with decimal separator := '.'