SVG Rendering
For the past few weeks I've been working on writing and testing an object oriented encapsulation of the free open source SDL and NanoVG libraries. Below is a short video clip of one of my demo programs highlighting realtime symbolic vector graphics (SVG) parsing and rendering.
This demo is a Free Pascal program which dynamically reads in SVG document files and renders them on screen. The SVG images are rendered in realtime using vector parsed data and converted to OpenGL vertex data with the help of compute shaders. Note the listing at the end of this page uses only 156 lines of code to render an SVG file!
This program has been tested and runs well on Windows, Linux, Mac, and Raspberry Pi
Video
Here is a screen recording of the program viewing various SVG files.
Video: SVG Rendering
Source Code
The following is a listing of the entire unit defining this SVG rendering portion of my system. Comments are provided to explain how it is written and how it works.
unit Tiny.Graphics.Svg.Renderer;
{$i tiny.svg}
interface
uses
Tiny.System,
Tiny.Types,
Tiny.Graphics,
Tiny.Graphics.Svg;
{ TSvgRender }
type
TSvgRender = class
private
FCanvas: ICanvas;
FDoc: TSvgDocument;
public
constructor Create(Canvas: ICanvas);
destructor Destroy; override;
procedure Parse(const Xml: string);
procedure ParseFile(const FileName: string);
procedure Draw;
end;
implementation
{ TSvgRender }
constructor TSvgRender.Create(Canvas: ICanvas);
begin
inherited Create;
FCanvas := Canvas;
FDoc := TSvgDocument.Create;
end;
destructor TSvgRender.Destroy;
begin
FDoc.Free;
inherited Destroy;
end;
procedure TSvgRender.Parse(const Xml: string);
begin
FDoc.Parse(Xml);
end;
procedure TSvgRender.ParseFile(const FileName: string);
begin
Parse(FileLoadText(FileName));
end;
procedure TSvgRender.Draw;
procedure RenderPath(Path: TSvgPath);
var
C: TSvgCommand;
I: Integer;
begin
for I := 0 to Path.Commands.Length - 1 do
begin
C := Path.Commands[I];
case C.Action of
svgMove: FCanvas.MoveTo(C.X, C.Y);
svgLine: FCanvas.LineTo(C.X, C.Y);
svgCubic: FCanvas.BezierTo(C.X1, C.Y1, C.X2, C.Y2, C.X, C.Y);
svgQuadratic: FCanvas.QuadTo(C.X1, C.Y1, C.X, C.Y);
svgClose: FCanvas.ClosePath;
end;
end;
end;
procedure Polyline(N: TSvgPolyline);
var
P: TPointF;
I: Integer;
begin
for I := 0 to N.Points.Length - 1 do
begin
P := N.Points[I];
if I = 0 then
FCanvas.MoveTo(P.X, P.Y)
else
FCanvas.LineTo(P.X, P.Y);
end;
if N is TSvgPolygon then
FCanvas.ClosePath;
end;
procedure Render(N: TSvgNode; Opacity: Float);
var
L: TSvgLine absolute N;
C: TSvgCircle absolute N;
E: TSvgEllipse absolute N;
R: TSvgRect absolute N;
P: TSvgPath absolute N;
G: TSvgGroup absolute N;
S: TSvgNode;
T: TSvgTransform;
M: IMatrix;
begin
M := nil;
if N.Transform <> '' then
begin
N.BuildTransform(T);
M := NewMatrix;
M.Copy(T[0], T[1], T[2], T[3], T[4], T[5]);
M.Transform(FCanvas.Matrix);
FCanvas.Matrix.Push;
FCanvas.Matrix := M;
end;
if N is TSvgLine then
begin
FCanvas.MoveTo(L.X1, L.Y1);
FCanvas.LineTo(L.X2, L.Y2);
end
else if N is TSvgPolyLine then
Polyline(TSvgPolyline(N))
else if N is TSvgCircle then
FCanvas.Circle(C.X, C.Y, C.R)
else if N is TSvgEllipse then
FCanvas.Ellipse(E.X, E.Y, E.W, E.H)
else if N is TSvgRect then
if R.R > 0 then
FCanvas.RoundRect(R.X, R.Y, R.W, R.H, R.R)
else
FCanvas.Rect(R.X, R.Y, R.W, R.H)
else if N is TSvgPath then
RenderPath(P)
else if N is TSvgGroup then
begin
for S in G do
Render(S, Opacity * G.Opacity);
if M <> nil then
FCanvas.Matrix.Pop;
Exit;
end;
if not (N is TSvgLine) then
if R.Style.Fill <> 0 then
FCanvas.Fill(BuildBrush(R, Opacity * R.Opacity), True);
if R.Style.Stroke <> 0 then
FCanvas.Stroke(BuildPen(R, Opacity * R.Opacity), True);
FCanvas.BeginPath;
if M <> nil then
FCanvas.Matrix.Pop;
end;
var
N: TSvgNode;
begin
for N in FDoc do
Render(N, 1);
end;
end.