Render animated SVG to GIF, AVI or APNG

This example Delphi application demonstrates how to render an animated SVG to a GIF, AVI or animated PNG.
The application makes use of the following libraries:For the SVG it uses the SVG control package or Для просмотра ссылки Войди
For the animated PNG, theДля просмотра ссылки Войди
For the AVI, Для просмотра ссылки Войди
For GIF, no external library is needed because it is part of the Delphi VCL.
The source code of the application can be found on Для просмотра ссылки Войди
The SVG used in this example is “Для просмотра ссылки Войди
Application layout
On the top left of the application form there is a TValueListEditor control where you can set the SVG input file, width en height and some other settings.
On the bottom left there is a TTabControl with another TValueListEditor where you can select the output format and some settings for the output.
Then on the top you can specify the “duration” for the animation in milliseconds and the “frames per second”.
The “duration” is something you have to find out yourself by inspecting the SVG file. SVG animations can be very complex, because parts of the animation can be triggered by events. In simple cases you can figure out the duration by looking at the “Для просмотра ссылки Войди
For CSS animations, you look for the “Для просмотра ссылки Войди
Of course, increasing the “frames per second” will result in a smoother animation but a larger output file.
The center pane of the application will display a preview of each frame while it is recorded. You start the recording with the button “Record”.
At the end of the recording, the application will save the output file to the file specified in the output settings.
Stepping through the SVG animation
For stepping through the SVG animation I have created a class TSVGFrameRenderer. Because we do not have to display the SVG animation in real time, the animation timer is not needed.
The total number of frames is calculated by:
FrameCount := Duration * FPS div 1000;
Where FPS is the “Frames per second”.
And the time in milliseconds between each frame is:
DeltaTime := 1000 div FPS;
The input for the frame renderer is a “SVG root object”, specified by the interface ISVGRoot. The root object contains the parsed SVG file.
The root object also implements interface ISVGAnimationTimerTarget. This interface can be used to step through the SVG animation, for example:
Код:
var
Target: ISVGAnimationTimerTarget;
...
// Get the animation target interface
if not Supports(SVGRoot, ISVGAnimationTimerTarget, Target) then
Exit;
...
// Start the animation
Target.DoAnimationTimerStart;
...
// Advance the animation "Delay" milliseconds
Target.DoAnimationAdvanceFrame(Delay);
The full code of the frame renderer can be found Для просмотра ссылки Войди
Rendering to an animated PNG
For rendering the animated PNG we use theДля просмотра ссылки Войди
At the moment we use only one output parameter for the PNG animation: “AnimationLoops”. This specifies how many times the animation will repeat itself. A value of 0 means endless repetition.
procedure TSVGConvTargetApng.Convert(aFrameRenderer: TSVGFrameRenderer);
Код:
var
DataArray: TDynImageDataArray;
Meta: TMetadata;
Format: TPNGFileFormat;
Index: Integer;
begin
// https://github.com/galfar/imaginglib
SetLength(DataArray, 0);
try
if aFrameRenderer.RenderFirst then
begin
// Create an array to store the frame images
SetLength(DataArray, aFrameRenderer.StepCount);
try
repeat
// Convert the bitmap from the frame rendere to an image
// and store it in the array
ConvertBitmapToData(aFrameRenderer.Bitmap, DataArray[aFrameRenderer.Step]);
until not aFrameRenderer.RenderNext;
finally
aFrameRenderer.RenderClose;
end;
end;
// Create a meta object for PNG settings
Meta := TMetadata.Create;
try
// Set "AnimationLoops" in the meta object
Meta.SetMetaItemForSaving(SMetaAnimationLoops, AnimatedLoops);
// Set the frame delay on each image
for Index := 0 to Length(DataArray) - 1 do
Meta.SetMetaItemForSaving(SMetaFrameDelay, 1000 / aFrameRenderer.FPS, Index);
// Save the array to file in animated PNG format
Format := TPNGFileFormat.Create(Meta);
try
Format.SaveToFile(FileName, DataArray);
finally
Format.Free;
end;
finally
Meta.Free;
end;
finally
FreeImagesInArray(DataArray);
end;
end;
Для просмотра ссылки Войди
The imaginglib doesn’t have components for displaying an animated PNG in Delphi (or Freepascal). Below is a very simplistic way to display the animated PNG based on a timer:
Код:
uses
...
Imaging,
ImagingComponents,
ImagingNetworkGraphics
...
TForm1 = class(TForm)
...
private
FFrame: Integer;
FDynImageDataArray: TDynImageDataArray;
public
procedure LoadPng(const aFilename: string);
...
procedure TForm1.FormCreate(Sender: TObject);
begin
SetLength(FDynImageDataArray, 0);
FFrame := 0;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Finalize(FDynImageDataArray);
end;
procedure TForm1.LoadPng(const aFilename: string);
var
PngFormat: TPNGFileFormat;
DelayVar: Variant;
begin
PngFormat := TPNGFileFormat.Create(nil);
try
// Load PNG in image array
PngFormat.LoadFromFile(OpenDialog1.Filename, FDynImageDataArray);
// Get delay from global meta
DelayVar := GlobalMetadata.MetaItems[SMetaFrameDelay];
if not VarIsNull(DelayVar) then
Timer1.Interval := DelayVar;
finally
PngFormat.Free;
end;
// Start timer
FFrame := 0;
Timer1.Enabled := True;
end;
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
Bitmap: TBitmap;
begin
if FFrame < Length(FDynImageDataArray) then
begin
Bitmap := TBitmap.Create;
try
ConvertDataToBitmap(FDynImageDataArray[FFrame], Bitmap);
PaintBox1.Canvas.Draw(0, 0, Bitmap);
finally
Bitmap.Free;
end;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
begin
PaintBox1.Repaint;
if FFrame < Length(FDynImageDataArray) - 1 then
Inc(FFrame)
else
FFrame := 0;
end;
In this example we use the GIF functionality that is part of the Delphi VCL, but you could also use the Для просмотра ссылки Войди
procedure TSVGConvTargetGif.Convert(aFrameRenderer: TSVGFrameRenderer);
Код:
var
GIFImage: TGifImage;
GifFrame: TGIFFrame;
GCE: TGIFGraphicControlExtension;
LoopExt: TGIFAppExtNSLoop;
begin
GIFImage := TGifImage.Create;
try
if aFrameRenderer.RenderFirst then
begin
try
repeat
// Add the bitmap to the gif image list
GifFrame := GifImage.Add(aFrameRenderer.Bitmap);
// Loop extension must be the first extension in the first frame
if GifImage.Images.Count = 1 then
begin
LoopExt := TGIFAppExtNSLoop.Create(GifFrame);
// Number of loops (0 = forever)
LoopExt.Loops := AnimatedLoops;
end;
// Add Graphic Control Extension
GCE := TGIFGraphicControlExtension.Create(GifFrame);
// Delay is in hundreds of a second
GCE.Delay := aFrameRenderer.Delay div 10;
if Transparent then
begin
GCE.Transparent := True;
GCE.TransparentColorIndex := GifFrame.Pixels[0, aFrameRenderer.Height - 1];
GCE.Disposal := dmBackground;
end;
until not aFrameRenderer.RenderNext;
finally
aFrameRenderer.RenderClose;
end;
end;
// Optimize Color map...
if OptimizeColorMap then
GifImage.OptimizeColorMap;
// Optimize aGifImage frames...
if OptimizeOptions <> [] then
GifImage.Optimize(OptimizeOptions, rmNone, dmNearest, 0);
if not GifImage.Empty then
GifImage.SaveToFile(Filename);
finally
GifImage.Free;
end;
end;
Для просмотра ссылки Войди
Rendering to AVI
For rendering to AVI, we use the library Для просмотра ссылки Войди
The code for rendering to AVI is very simple:
Код:
procedure TSVGConvTargetAvi.Convert(aFrameRenderer: TSVGFrameRenderer);
var
Avi: TAviFromBitmaps;
CompressionTag: FOURCC;
begin
// http://francois-piette.blogspot.com/2013/07/creating-avi-file-from-bitmaps-using.html
// Compression will only work if you have installed the necessary codecs
// XVID compressor, download the setup from http://www.xvid.org
CompressionTag := MKFOURCC('D', 'I', 'B', ' ');
case Compression of
acXVID: CompressionTag := MKFOURCC('x', 'v', 'i', 'd');
end;
Avi := TAviFromBitmaps.CreateAviFile(
nil,
Filename,
CompressionTag,
Cardinal(aFrameRenderer.FPS),
1);
try
if aFrameRenderer.RenderFirst then
begin
try
repeat
// Add the bitmap to the avi
Avi.AppendNewFrame(aFrameRenderer.Bitmap.Handle);
until not aFrameRenderer.RenderNext;
finally
aFrameRenderer.RenderClose;
end;
end;
finally
Avi.Free;
end;
end;
Simple SVG animations are usually not a problem, but In SVG you can animate almost every attribute, also for example attributes on filters. Rendering SVG filters can be very expensive in terms of computing power, so it might not be possible to render frames fast enough for a smooth animation.
Also you might be less depended on specific fonts or other resources that are needed on the host computer for rendering the SVG.
On the other hand, the SVG file is much smaller. In this example the PNG file is 602KB, the AVI (uncompressed) is 17583KB and the GIF is 492KB. The SVG file is only 13KB.