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[2].ToInteger - vals[0].ToInteger;
SVGLayout.Height := vals[3].ToInteger - vals[1].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
[code]
if node.HasAttribute('opacity') then
path.Opacity := StrToFloat(node.Attributes['opacity'],FmtSettings);
[code]
where FMtSettings is a TFormatSettings with decimal separator := '.'