Using Vector (SVG) Graphics in C# .NET

Introduction

Scalable vector graphics(SVG), has been widely popular currently in terms of rendering web graphics. When compared with Raster images, Vector images (SVG image types) have the following advantages.

  • As the name implies Vector SVG graphics are scaleable and do not pixelate at higher zoom levels.
  • Vector image scaled
    Vector image scaled

    Raster image pixelated after resizing
    Raster image pixelated after resizing
  • Vector graphics are formed using basic shapes, mathematical paths and lines. Because of this it is easier to understand sub parts that make up the vector image.
  • SVG files are XML files therefore customizing them and making changes is easier than manipulating binary raster images.

Practical Usages of Vector Images

  • To represent dynamic charts
  • SVG graphics are been used for dynamic interactive charts. This is mainly due to its ease of scalability and the ease in creating interactive graphics. Since as above mentioned, SVG graphics allows to identify sub parts of the graphic therefore allows to customize a graph and its sub components easily.

  • To represent icons in a Map or a floorplan
  • When in a scenario where you need to create 100 to 1000s of icons annotating a map and to allow scaling and zooming into these icons the best option is to use SVG images. also these allow to dynamically color the icons to annotate regions of a map and to toggle on/off the icons on a map with ease.

Using Vector images in C#

Lets now check simple manipulation of SVG images using C#. Even though the .NET framework does not consist of out of the box implementations to manipulate SVG images, we could make use of a more usable framework known as SVG Render Engine

SVG Render Engine

Creating a sample application in C#

To identify the features explained above lets start of with a simple .NET Windows application. The application would consists of the following features.

  • Loading and converting a SVG graphic to a raster image and drawing it in C# forms controls.
  • Using recursive approaches to identify paths in the SVG graphic consisting of a given fill color.
  • Identifying the fill colors and replacing fill colors.
  • Scaling a SVG graphics based on limit boundaries.

Start off by creating a new C# Forms application and then install the SVG Render library as reference using the Nuget Package Manager.
Right click on the Project from the Solutions Explorer and select Manage Nuget Packages from the online section in the left panel start searching for SVG Render Library

Installing the SVG Render Library

Once the package has been installed create a basic form as below. The controls used in the form are mostly text boxes and buttons. The bottom part of the form consists of a canvas area, a panel containing a picturebox. This picturebox would be used to render out the rendered SVG image. The Pick Color control is used as a toggle button, a checkbox with a flat button view to see if its clicked or not. This button toggles a state where you could pick colors from a loaded SVG image.

Other than the basic components, a ColorDialog and a OpenFileDialog is used to load a color picker and to help in loading a SVG file into the canvas.

SVG Sample Form

Usage of the application

The following screenshots show the usage of the functions of the sample application to get a more clearer idea.

Instructions Screen

Initially once the application has been launched, use the browse button to load a sample SVG image into the picturebox.

After loading a SVG Image
After loading a SVG Image

Click the Pick Color to start color selection mode. To select a color move the cursor over a desired pixel on the canvas area below (the cursor pointer would turn to a cross) click once you come to the desired area. (In this instance the green color of the eyes have been selected).Next click on the destination color swatch to load the color picker and pick the desired replacement color.

Picking a source color from the picture box
Picking a source color/Destination color
Click Replace Color to replace the selected source color with the destination color. Note that all areas with the source color as fill color would be replaced in the image.
Replacing the source color picked with a destination color
Replacing the source color picked with a destination color
Scaling the image is also possible using the Scale up/scale down controls. Notice how the image is not blurry during the scaling process and remains sharp and crisp.
Scaling SVG images
Scaling SVG images

When using the SVG render library, the starting point is to load the SVG image into a SvgDocument. Once the SvgDocument object is created there would be SvgElement objects. SvgElement is the super type of all of the other specific SVG element types such as SvgPath, SvgText, SvgCircle, SvgEcllipse. When traversing through the SvgDocument these elements can be extracted based on their type.

The application contains a separate class to hold the SVG manipulation code. Since the picture box is of a fixed size the SvgParser consists of proportionately resizing the SVG image to properly fit the picturebox.

SVGSample.svg.SVGParser.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Svg;

namespace SVGSample.svg
{
    /// <summary>
    /// Class containg code for manipulating SVG graphics.
    /// </summary>
    public class SVGParser
    {
        /// <summary>
        /// The maximum image size supported.
        /// </summary>
        public static Size MaximumSize { get; set; }

        /// <summary>
        /// Converts an SVG file to a Bitmap image.
        /// </summary>
        /// <param name="filePath">The full path of the SVG image.</param>
        /// <returns>Returns the converted Bitmap image.</returns>
        public static Bitmap GetBitmapFromSVG(string filePath)
        {
            SvgDocument document = GetSvgDocument(filePath);
         
            Bitmap bmp=document.Draw();
            return bmp;
        }

        /// <summary>
        /// Gets a SvgDocument for manipulation using the path provided.
        /// </summary>
        /// <param name="filePath">The path of the Bitmap image.</param>
        /// <returns>Returns the SVG Document.</returns>
        public static SvgDocument GetSvgDocument(string filePath)
        {
            SvgDocument document=SvgDocument.Open(filePath);
            return AdjustSize(document);
        }

        /// <summary>
        /// Makes sure that the image does not exceed the maximum size, while preserving aspect ratio.
        /// </summary>
        /// <param name="document">The SVG document to resize.</param>
        /// <returns>Returns a resized or the original document depending on the document.</returns>
        private static SvgDocument AdjustSize(SvgDocument document)
        {
            if (document.Height > MaximumSize.Height)
            {
                document.Width = (int)((document.Width / (double)document.Height) * MaximumSize.Height);
                document.Height = MaximumSize.Height;
            }
            return document;
        }

    }

}


Next lets take a look at the event bindings of the form. There are events for all of the button controls, A mouse down event for the Picturebox to track the pixel color under the current mouse position. Notice the usage of the GDI raster graphics classes found in .NET to get the pixel color using GetPixel. A validation method is used to check if an SVG image has been loaded prior to using the other functions, this is the ValidateFormControls.

ChangeFill is a recursive method, which traverses through the entire XML SVG document, from element to element and checks the fill color whether it matches a given color, if so replaces with the provided replace color. It is important to note that we pick the color from the raster image in the Picturebox as the source and matches it against the svg document for matching fill colors. This shows that the raster image consists of the same colors without a quality loss when converting from SVG.

SVGSample.frmSVGWindow.cs

        /// <summary>
        /// The file path of the SVG image selected.
        /// </summary>
        private string selectedPath;

        /// <summary>
        /// Instance reference for the svgDocument used and updated throughout the manipulation of the image.
        /// </summary>
        private Svg.SvgDocument svgDocument;
        
        /// <summary>
        /// Form window constructor.
        /// </summary>
        public frmSVGWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Destination Color Selection
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void btnDestinationColor_Click(object sender, EventArgs e)
        {
            DialogResult result = colorPicker.ShowDialog();
            if (result == DialogResult.OK)
            {
                btnDestinationColor.BackColor = colorPicker.Color;
            }
        }

        /// <summary>
        /// Action responsible to allow the user select a SVG image.
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void btnBrowse_Click(object sender, EventArgs e)
        {
            DialogResult selectResult = filePicker.ShowDialog();
            if (selectResult == System.Windows.Forms.DialogResult.OK)
            {
                svg.SVGParser.MaximumSize = new Size(pictConvertedImage.Width, pictConvertedImage.Height);
                
                selectedPath = filePicker.FileName;
                txtSelectedFile.Text = selectedPath;
                svgDocument = svg.SVGParser.GetSvgDocument(selectedPath);
               
                txtWidth.Text = svgDocument.Width.Value.ToString();
                txtHeight.Text = svgDocument.Height.Value.ToString(); 
                
                pictConvertedImage.Image = svg.SVGParser.GetBitmapFromSVG(selectedPath);
            }
        }

        /// <summary>
        /// Action responsible to allow the user pick a source color. 
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void btnSourceColor_Click(object sender, EventArgs e)
        {
            colorPicker.ShowDialog();
        }

        /// <summary>
        /// Action responsible to replace the color of the original image.
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void btnReplaceColor_Click(object sender, EventArgs e)
        {
            if (!ValidateFormControls())
            return;

            foreach (Svg.SvgElement item in svgDocument.Children)
            {
                ChangeFill(item, btnSourceColor.BackColor, btnDestinationColor.BackColor);
            }
            pictConvertedImage.Image = svgDocument.Draw();

        }

        /// <summary>
        ///  Recursive fill function to change the color of a selected node and all of its children.
        /// </summary>
        /// <param name="element">The current element been resolved.</param>
        /// <param name="sourceColor">The source color to search for.</param>
        /// <param name="replaceColor">The color to be replaced the source color with.</param>
        private void ChangeFill(SvgElement element, Color sourceColor, Color replaceColor)
        {
            if (element is SvgPath)
            {
                if (((element as SvgPath).Fill as SvgColourServer).Colour.ToArgb() == sourceColor.ToArgb())
                {
                    (element as SvgPath).Fill = new SvgColourServer(replaceColor);
                }
            }

            if (element.Children.Count > 0)
            {
                foreach (var item in element.Children)
                {
                    ChangeFill(item, sourceColor, replaceColor);
                }
            }

        }
                     
        /// <summary>
        /// Action used to pick the color from a pixel from the rasterized picture.
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void pictConvertedImage_MouseDown(object sender, MouseEventArgs e)
        {
            if (togglePickColor.Checked)
            {
                if (!ValidateFormControls())
                    return;


                if (e.Button == System.Windows.Forms.MouseButtons.Left)
                {
                    Bitmap bmp = pictConvertedImage.Image as Bitmap;
                    btnSourceColor.BackColor = bmp.GetPixel(e.X, e.Y);

                }
            }
        }

        /// <summary>
        /// Action used to scale down an SVG image. (in inrements of 10)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnScaleDown_Click(object sender, EventArgs e)
        {
            if (!ValidateFormControls())
                return;

            int W =int.Parse(txtWidth.Text);
            int H = int.Parse(txtHeight.Text);

            if (W - 10 > 0 &&  H- 10 > 0)
            {
                W -= 10;
                txtWidth.Text = W.ToString();

                H -= 10;
                txtHeight.Text = H.ToString();

                svgDocument.Width = W;
                svgDocument.Height = H;

                pictConvertedImage.Image = svgDocument.Draw();
            }
        }

        /// <summary>
        /// Action used to scale up an SVG image. (in inrements of 10)
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void btnScaleUp_Click(object sender, EventArgs e)
        {
            if (!ValidateFormControls())
                return;

            int W = int.Parse(txtWidth.Text);
            int H = int.Parse(txtHeight.Text);

            if (W + 10 < pictConvertedImage.Width && H + 10 < pictConvertedImage.Height)
            {
                W += 10;
                txtWidth.Text = W.ToString();

                H += 10;
                txtHeight.Text = H.ToString();

                svgDocument.Width = W;
                svgDocument.Height = H;

                pictConvertedImage.Image = svgDocument.Draw();
            }
        }

        /// <summary>
        /// Checks if there is an image selected.
        /// </summary>
        /// <returns>Returns the boolean results whether an image is selected.</returns>
        private bool ValidateFormControls()
        {
            if (svgDocument == null || pictConvertedImage.Image == null)
            {
                MessageBox.Show("Please select a SVG image to continue");
                return false;
            }
            return true;
        }

        /// <summary>
        /// Action performed to indicate whether picking color from the image is available.
        /// </summary>
        /// <param name="sender">The source calling the event.</param>
        /// <param name="e">The arguments passed to the event.</param>
        private void togglePickColor_CheckedChanged(object sender, EventArgs e)
        {
            if (togglePickColor.Checked)
            {
                togglePickColor.BackColor = Color.LightPink;
                pictConvertedImage.Cursor = Cursors.Cross;
            }
            else
            {
                togglePickColor.BackColor = Color.Gainsboro;
                pictConvertedImage.Cursor = Cursors.Default;
            }
        }

Image Reference – Blackicemedia.com awesome tiger.
To download the code sample use the link below
Download Sample Code

That concludes this brief introduction to SVG manipulation using the SVG Render Engine Library. Do check out more at Codeplex
for other functionality supported in the library.

Advertisements

32 thoughts on “Using Vector (SVG) Graphics in C# .NET

Add yours

  1. You can also check the Ab2d.ReaderSvg library – http://www.ab4d.com/ReaderSvg.aspx. It is not free but has some advanced features like possibility to read as WPF Shapes (for better manipulation) or GeometryDrawing (for better performance), optimize read objects, transform objects to custom size, read objects metadata and layer properties for svg files created in Visio, etc. The library comes with WPF samples and also with WinForms sample and a sample SvgToPngConverter console application.

  2. System.ArgumentOutOfRangeException was unhandled
    HResult=-2146233086
    Message=Index was out of range. Must be non-negative and less than the size of the collection.
    Parameter name: index
    Source=mscorlib
    ParamName=index
    StackTrace:
    at System.ThrowHelper.ThrowArgumentOutOfRangeException()
    at System.Collections.Generic.List`1.get_Item(Int32 index)
    at Svg.SvgGradientServer.GetColourBlend(SvgVisualElement owner, Single opacity) in C:\Dev\vvvv\SVG\Source\Painting\SvgGradientServer.cs:line 120
    at Svg.SvgRadialGradientServer.GetBrush(SvgVisualElement renderingElement, Single opacity) in C:\Dev\vvvv\SVG\Source\Painting\SvgRadialGradientServer.cs:line 71
    at Svg.SvgVisualElement.RenderFill(SvgRenderer renderer) in C:\Dev\vvvv\SVG\Source\Basic Shapes\SvgVisualElement.cs:line 130
    at Svg.SvgVisualElement.Render(SvgRenderer renderer) in C:\Dev\vvvv\SVG\Source\Basic Shapes\SvgVisualElement.cs:line 108
    at Svg.SvgElement.RenderChildren(SvgRenderer renderer) in C:\Dev\vvvv\SVG\Source\SvgElement.cs:line 543
    at Svg.SvgGroup.Render(SvgRenderer renderer) in C:\Dev\vvvv\SVG\Source\Document Structure\SvgGroup.cs:line 65
    at Svg.SvgElement.RenderChildren(SvgRenderer renderer) in C:\Dev\vvvv\SVG\Source\SvgElement.cs:line 543
    at Svg.SvgElement.Render(SvgRenderer renderer) in C:\Dev\vvvv\SVG\Source\SvgElement.cs:line 531
    at Svg.SvgDocument.Draw(Bitmap bitmap) in C:\Dev\vvvv\SVG\Source\SvgDocument.cs:line 394
    at Svg.SvgDocument.Draw() in C:\Dev\vvvv\SVG\Source\SvgDocument.cs:line 367
    at SVGSample.svg.SVGParser.GetBitmapFromSVG(String filePath) in c:\Users\504523\Desktop\SVGSample\SVGSample\SVGSample\svg\SVGParser.cs:line 33
    at SVGSample.frmSVGWindow.btnBrowse_Click(Object sender, EventArgs e) in c:\Users\504523\Desktop\SVGSample\SVGSample\SVGSample\frmSVGWindow.cs:line 61
    at System.Windows.Forms.Control.OnClick(EventArgs e)
    at System.Windows.Forms.Button.OnClick(EventArgs e)
    at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
    at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
    at System.Windows.Forms.Control.WndProc(Message& m)
    at System.Windows.Forms.ButtonBase.WndProc(Message& m)
    at System.Windows.Forms.Button.WndProc(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
    at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
    at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
    at System.Windows.Forms.Application.Run(Form mainForm)
    at SVGSample.Program.Main() in c:\Users\504523\Desktop\SVGSample\SVGSample\SVGSample\Program.cs:line 19
    at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
    at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
    at System.Threading.ThreadHelper.ThreadStart()
    InnerException:

  3. Hi, nice work. I was wandering, is it possible to access individual path or object in the SVG file like getting events?

    1. Hi. Really good. Just wondering if sol got an answer. I’m looking to access the svg element on mousedown and doesn’t seem to allow in bitmap.
      Any help much appreciated.

  4. I am looking to so the same as Ben, i am looking to select an element on click and return the x/y coor. Any tips?

    1. Hi Sol, Ben, Patrick,

      You could make use of the SVGVisualElement Bounds property to identify the region as well as the location of an element. Iterating through the elements and identifying which element was clicked could be done this way.

  5. Would there be a way to use what you have developed and pull the svg from a table’s column instead of a directory file?

  6. Hi,
    I can’t find the example code on “https://dl.dropboxusercontent.com/u/4380280/SVGSample.7z”. The web page said, the source possibly has been moved or changed. Could you send me the code to me? my email: ichbinlinia@msn.cn

    Thanks so much,
    Jing

  7. Hi,
    I can’t find the example code on “https://dl.dropboxusercontent.com/u/4380280/SVGSample.7z”. The web page said, the source possibly has been moved or changed. Could you send me the code to me? my email: wodhr_s2@naver.com

    Thanks so much,
    Hwang

  8. I need to combine two or more svg contiguous files to into one by rotating them clockwise 90-degrees. I would really appreciate if you could help me with some hints and tips on how to combine svg files. Also, it would be useful to know how to trim these svg images.

    Thanks in advance

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Powered by WordPress.com.

Up ↑

%d bloggers like this: