Using Vector (SVG) Graphics in C# .NET
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
| 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
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
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.
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
|
|
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/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
|
| 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
|
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
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.
Thanks for this article. I want tout know if this library allows to find the coordinates of a point in a path by using the distance on that path. Like in some js libraries for svg there is the getPointAtLength method.