Rendering SVGs with RSCL

The RiverSoftAVG SVG Component Library (RSCL) makes it incredibly easy to incorporate scalable vector graphics (SVGs) into your VCL and FMX Delphi applications for Windows, OSX, iOS, and Android.  However, the RSCL provides many choices of how you want to display SVGs within your apps, and it can get confusing to know which is better.  This blog post will discuss the choices and tradeoffs for drawing SVGs with different classes and components in the RSCL.

The RSCL provides 5 methods to display and interact with SVGs, each with advantages and disadvantages:

I’ve listed the 5 choices generally from the simplest to the most complex to code.

Load an SVG into a TBitmap/TGraphic

This is one of the simplest ways to display an SVG in your programs.  The RSCL hooks itself into the TPicture/TGraphic architecture of VCL and the TBitmap codecs of FMX.  In practice, this means you just have to the include RSImaging.SVGImage.pas (VCL) or FMX.RS.SVGCodec.pas (FMX) unit.  Then, you need to read the SVG into the TPicture/TGraphic or TBitmap.

To load an SVG into a TImage control, the code is really easy:

var
  Image1: TImage;
[...]
// VCL
Image1.Picture.LoadFromFile('c:\mysvg.svg'); 
// FMX
Image1.Bitmap.LoadFromFile('c:\mysvg.svg');

To load an SVG into a bitmap is easy as well.  There are no changes to do this in FMX, though with VCL it is slightly more involved:

var
 Bitmap: TBitmap;
 Picture: TPicture;
begin
 Bitmap := TBitmap.Create;
 try
  Picture := TPicture.Create;
  try
   Picture.LoadFromFile('c:\mysvg.svg');
   Picture.Graphic.SetSize(1000,1000);
   Bitmap.Assign(Picture.Graphic);
   PaintBox1.Canvas.Draw(0, 0, Bitmap);
  finally
   Picture.Free;
  end;
 finally
  Bitmap.Free;
 end;
end;

Incredibly simple, though you give up a lot of control.  Everything is rendered using defaults.  The size of the bitmap is determined from the SVG (SVGs may optionally specify their intended width and height or the RSCL will calculate the size).  Additionally, what you get is a bitmap, you cannot change colors of individually elements, detect elements being clicked, or anything else.  The result is a simple bitmap.

The VCL TGraphic architecture is more powerful in this case.  The TGraphic architecture allows you to change the size of the graphic after it is loaded.  The RSCL will redraw the SVG in the new size to keep everything looking sharp and perfect.

However, in FMX, this is not true.  If the specified or calculated size of the SVG is small, you can easily see jaggies as the bitmap is scaled up.  In addition, it should be noted that on mobile platforms, the FMX text output to bitmaps is poor.

Use a TRSSVGImageList to manage multiple SVGs

A variation of the first method is to use the TRSSVGImageList (VCL/FMX) control.  The TRSSVGImageList component provides a powerful means to manage a list of SVGs in your application.  It embeds every SVG within the image list (simplifying deployment) and re-renders the SVGs as needed.  Like a traditional TImageList, multiple controls can use the same TImageList, either different items or the same item, and it is all managed automatically for you.

In VCL, you can change the TRSSVGImageList.Width and Height at any time and the SVGs will be re-rendered.

With the FMX platform, the TRSSVGImageList is even more powerful.  It defines a TImageList composed entirely of SVGs to enable compact, graphical support of different screen resolutions and densities.

As you know, FireMonkey is a multi-platform framework (Windows, iOS, Mac OS X, and Android), and therefore FireMonkey applications work on devices having different screen densities and resolutions. Embarcadero recommends using FireMonkey multi-resolution bitmaps so that your images and icons in FireMonkey applications will appear correctly in different densities and resolutions (Windows, iOS, Mac OS X, and Android). However, a multi-resolution bitmap is a collection of bitmaps providing the same image (probably), each designed for a different scale to provide the same look irregardless of the resolution of the device the app is running on. This means that multiple copies of the same image can be embedded in your app (Embarcadero recommends at least two scales for iOS, regular and retina, and four or more on Android) – most of these copies are unused as they are intended for a different scale than the one your app is running on – potentially wasting lots of disk and memory space.

Not only do you not even need to generate all these extra resolution images with the TRSSVGImageList component, but you don’t need to store them either.  The TRSSVGImageList component solves this problem by creating a TImageList composed entirely of SVGs. For each SVG, you specify the sizes that the SVG should take at each screen scale. The TRSSVGImageList component generates, at run-time, bitmaps only for the current screen scale, potentially saving lots of space.  Because the SVG is buffered as a bitmap for the current screen scale, refreshes are extremely fast after the initial setup.

Like all good things, there are limitations.  You cannot combine SVGs and regular images in the same imagelist.  Customization and interaction is limited, especially compared to the following methods.  While the SVGs are available to edit, you do not have OnDrawing/OnDrawn events to hook into and customize the appearance of elements.  There are no OnElementClicked events to hook into to see which element has been clicked.  At the level of the visual controls like TSpeedButton, the SVGs are again just bitmaps.

Finally, it is generally not recommended to use SVGs with text on non-Windows platforms unless the specified Width and Height are large. The FMX library provides poor text quality when writing to offscreen bitmaps, which is what the TRSSVGImageList does, on these platforms.

Use a TRSSVGImage to display SVGs

In most ways, the TRSSVGImage (VCL/FMX) control is the simplest means to display and interact with SVGs in the RSCL library.  Everything can be done at design-time.  I won’t repeat the help topic here, but to summarize: just drop a TRSSVGDocument (VCL)/TRSFmxSVGDocument (FMX) component on your form, set its Filename property to your SVG, drop a TRSSVGImage control on your form, and set its SVGDocument property.

Use a TRSSVGImage and TRSSVGDocument/TRSFmxSVGDocument to display SVGs quickly and easily at design-time

Use a TRSSVGImage and TRSSVGDocument (VCL) / TRSFmxSVGDocument (FMX) to display SVGs quickly and easily at design-time

The TRSSVGDocument/TRSSVGImage combination is incredibly powerful.  Using the TRSSVGDocument properties, you can change the default options for displaying and interacting with your SVG.  The SVG is always available for modification on re-rendering.  At run-time, use the TRSSVGDocument.SVG property to programmatically access the entire SVG document hierarchy.  Any changes are automatically propagated to the TRSSVGImage control.

The TRSSVGImage control can draw directly to the control’s canvas, or, for performance, set the Buffered property and let the TRSSVGImage control create a backbuffer.  As the TRSSVGImage control size changes, the SVG is always redrawn to maintain the highest resolution needed.  You can also hook into the TRSSVGDocument.OnDrawing and OnDrawn events to customize the painting of elements.

The TRSSVGImage control even allows you to capture mouse-clicks and see what element in the SVG was under the mouse through its OnClickElement event.

The TRSSVGImage/TRSSVGDocument combination provides a good combination of power, performance and ease of use.  It provides a high degree of compatibility with SVGs that the RSCL supports.

Use a TRSSVGPanel to generate controls from SVGs

One of the most interesting ways to render SVGs is to use the TRSSVGPanel control.  The TRSSVGPanel provides a panel for generating design-time or run-time FMX controls (or VCL, though the compatibility is not as good) for a SVG. Like the TRSSVGImage control, you drop a TRSSVGDocument / TRSFmxSVGDocument on your form and hook it up to the TRSSVGPanel.  Unlike the TRSSVGImage control, the TRSSVGPanel control does not draw the SVG using canvas operations. Rather, it generates the FMX (or VCL) controls, which then draw the appearance of the SVG.

For example, instead of drawing a rectangle, circle or path SVG element, it creates a TRectangle, TCircle, or TPath control for each element and configures the control to make it appear similar to the drawn SVG element.  Generally, there is one control for each SVG element.  The exception is the text elements; every text element and its child text elements (tspan, textpath, tref) are combined into one TRSSVGText object.

If you right click the TRSSVGPanel at design-time and select “Generate Controls”.  The TRSSVGPanel will generate the controls permanently and they will be streamed with the form.  At this point, you can delete the TRSSVGDocument / TRSFmxSVGDocument component and not even deploy an SVG with your application.

Configure SVG controls with the Object Inspector. For example, adding a TShadowEffect and changing the color of a control.

Configure SVG controls with the Object Inspector. For example, adding a TShadowEffect and changing the color of a control.

Generating the controls permanently is what makes this method interesting.  You can now interact with the SVG elements as normal Delphi controls.  The controls have the typical Delphi events (e.g., OnClick, OnMouseOver, etc) and the typical properties (e.g., Visible, Enabled, Width, Height, etc), which you can customize.  With FMX, you can even apply Filter Effects to any control to change its appearance.

The TRSSVGPanel control is the least compatible and most limited way to display SVGs, especially in the VCL version. The FMX TRSSVGPanel does do a very good job with compatibility.  However, because of the way that the SVG elements must be rendered, hundreds of controls can be generated for one SVG document, with many of them overlapping.  In VCL, there can be problems with clipping and with gradients.  In addition, the TRSSVGPanel only creates controls at the aspect ratio of the SVG, i.e., the SVG is not stretched to fit the panel (you can modify the scale of the created controls by setting the TRSSVGPanel.ScaleOriginal property).

What is an advantage on one hand, the ability to permanently generate controls and decouple the controls from the SVG, is also a disadvantage on the other hand.  Many SVGs share resources, such as “use”-ing another element (or elements) in a different location or sharing linear and radial gradient elements for fill and stroke properties.  Every SVG element that references a linear gradient element shares the same element.  Update the linear gradient and all SVG elements that reference it get updated.  With the TRSSVGPanel controls, every control that uses that linear gradient gets its own copy in its the Fill or Stroke property.  If you have deleted the SVG document and edit one control’s Fill property, no other element gets updated.

Finally, this method consumes the most memory (all of those controls can be expensive) and is the slowest to generate the SVG display the first time as it needs to create perhaps 100s of controls for the SVG document.  However, subsequent refreshes can be relatively fast as it is just controls updating.

The TRSSVGPanel control is an interesting way to render an SVG and gives you some flexibility not available through other methods.  This method can be very useful if used judiciously.  However, you sacrifice flexibility in a lot of other ways.  And finally, note that the VCL TRSSVGPanel version is the slowest, least flexible, and least compatible way to display SVGs in the RSCL.

Draw to a TCanvas directly using a TSVGDocument class

The final method to render an SVG is the most powerful and one of the fastest (though there is no buffering).  Every SVG loaded by the RSCL creates a TSVGDocument and its hierarchy of TSVGxxxElement classes. The TSVGDocument class (and actually all TSVGGraphicElement descendants) exposes a number of Draw methods for drawing the SVG to any canvas, including a printer TCanvas.  You can specify the rectangle the SVG is drawn in and provide any matrix transformation you want to affect the rendering (such as rotating or skewing).

Like the TRSSVGImage method, there are events for OnDrawing and OnDrawn that you can hook into to customize the appearance of SVG elements.  You can edit any TSVGxxxElement object and change its appearance, including inheriting (or not) style attributes from parents.  There is an ElementAtPos method you can call to determine which element is under a mouse pointer.  You can customize the TSVGDocument.Options as you like.  Also, you can specify a DefaultAspectRatio to control if the SVG is rendered proportionally or not.

Using the DrawBeforeElement, DrawElement, and DrawAfterElement methods, you can implement your own buffering scheme for buffering any part of the SVG document hierarchy.  The DrawBeforeElement method can draw in place the “older”/before siblings and ancestors of an element.  The DrawElement method will draw the specified element, in place, and its children but not ancestors or siblings.  Finally, the DrawAfterElement method will draw all siblings after the specified element.  By using these three methods, you can buffer background and foreground SVG elements to bitmaps.  As one element changes, the buffers can be drawn quickly without re-rendering SVG elements.  The included SVG Editor demo in the RSCL uses this scheme to provide excellent response as a user edits an element.

The TSVGDocument provides the most flexibility for specifying exactly what you render as well as where you render.

Differences between VCL and FMX

Besides the TRSSVGPanel and TRSSVGImageList differences, the VCL and FMX versions of the RSCL are as similar as possible and share most of the same code base.

In the VCL version, the RSCL uses the GDI+ library for software-based rendering of SVGs.  Because it is software-based, the VCL version is slower than the FMX version.  The quality of radial gradients is slightly worse in the VCL version (if you use the patch for the FMX version).  On the positive side, the GDI+ library provides irregular clipping regions, enabling generally better compatibility and sometimes faster rendering when large regions are clipped.  The GDI+ library provides much more text metrics than FMX (i.e., ascent, descent, fonts installed, etc), allowing much better text placement and font choice.

In the FMX version, the RSCL will use whichever TCanvas you choose (DirectX, OpenGL), including the hardware-based GPU TCanvas.  This means that the FMX RSCL is faster.  However, the FMX classes do not provide irregular clipping regions and minimal font and text metrics, sacrificing SVG fidelity somewhat.  The FMX version also tends to not render text well to off-screen bitmaps, especially on the mobile platforms.  On mobile (iOS and Android), or if you use the Windows FMX GPU Canvas, the RSCL must fill text instead of creating path-based representations of the text.

Differences between Path-based Text and Fill Text

In the section above, I mentioned fill text and path-based text.  The RSCL provides two methods for rendering text: path-based text and fill text. The default method is path-based text on desktop platforms and fill text on mobile platforms.

In path-based text, the text is converted to paths, cached for later use, and then drawn using the paths. It has the advantage that text can be both filled and stroked, and allows pixel level precision for selection. The path-based text method is slower than fill text for the initial draw call.  However, subsequent draw calls can be faster with complex text SVGs as all of the rotations, kerning, etc has already been performed.  Path-based text also consumes more memory than filling text.  However, path-based text rendering provides greatest compatibility with SVGs text elements on desktop platforms.

With fill text, the text is drawn directly to the canvas using fill text calls. It is faster for the first Draw call and uses much less memory.  Fill text also does a better job with small text.  It has better compatibility with non-Western fonts. However, filling text does not use the Pen/Stroke and so cannot both fill and stroke text.  In FMX, you cannot use gradients for filling text.  With complex text with lots of placement and rotation per character, fill text does not perform center and right alignment.  Selections of SVG text elements rendered with fill text use the bounding box of the text, which is much less accurate; for text paths, the bounding box is also incorrect.  Finally, rendering decorative fonts (underlined, overlined, strike-out) is not as accurate across words and characters as path-based text rendering.

Whether path-based text or fill text is used on desktop platforms depends on if soUseFillText is in the TSVGDocument.Options property.  On mobile platforms (iOS and Android), or if you use the FMX GPU Canvas (FMX.Types.GlobalUseGPUCanvas), text is always filled and not converted to paths.

Wrap-up

This was an incredibly long blog post.  Kudos to all of you who are still reading it.  🙂  However, I thought that a post like this would help explain the power as well as the tradeoffs required when using the RiverSoftAVG SVG Component Library.  The final v2.0 release of the RSCL will be within a month.  Until release, you can buy the RSCL for 30% off, and you will be able to access both the v1.x release and through our Early Experience Program the current v2.0 beta release.

That’s all for now.  Happy CodeSmithing!

 

Leave a Reply

Your email address will not be published. Required fields are marked *