Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SVG Artiste - An SVG Editor

0.00/5 (No votes)
3 Aug 2010 1  
A Vector based tool to create and edit SVG images

Introduction

SVG Artiste is a vector editor based on SVG.

SVGArtisteModelling.jpg

It is a refinement of SVGPaint by Alexandr Shokhin. I'm also more than happy to give due credit to Cristinel Mazarine and Bill Seddon for the DockToolbar and Ruler control which adorn SVG Artiste with their respective functionalities.

Background

For one of my commercial projects, I was required to draw a vector image, which in a way represents the data I have and allows users to manipulate the vector image, and then the data needed to be updated accordingly. It was a WinForms application so I didn't have the option of using XAML over there (neither data templates :)). So then I searched over Google and found SVG Paint. I had to spend considerable effort on the curve of learning the project, then customizing and implementing it. As I mentioned, some levels of customization were required to fit my requirements and I am carrying it through to the project which is uploaded here.

The challenges I faced were to bring interactivity to the objects based on user actions or data, and to manipulate the graphics frequently based on UI design feedbacks. I had to spend a considerable amount of time and effort on perfecting the approach I used, but I should say that the end result was very satisfactory.

My project got completed and I wanted to perfect the tools and techniques used in the due course. But now that I have shifted myself to WPF, I would say my creativity has got a stronger platform on the positive side. I had to rewrite SVG Artiste to render as XAML instead of WinForms GDI. But I feel it is worthwhile sharing the idea here as the core challenges still remain the same for any vector editor.

Concepts

Vector Graphics - It is an indispensable part of any visual presentation which will be scaled unpredictably. Raster will lose its quality, vectors won't. Each vector is represented by its co-ordinates and each graphics is a group of basic vector elements. Examples of vector elements can be circle, rectangle etc.

SVG - It is a standard brought forward by Adobe. Now Microsoft is also joining the community, which shows its strength. SVG is an extension of XML, so it is bound to be platform independent. <rectangle/> is <rectangle/> in Linux, Windows or Mac. SVG Artiste 2.0 uses SVG as a method of serialization and transportation of graphics created out of it.

Editors - A typical graphical editor would have a toolbar, a drawing area, and other visual objects that help edit the graphic on screen. So for vectors, it has got to be tools that help edit each element on the drawing area.

Vectors with Apps - When it comes to presenting the vector graphics in an application, present it as a drawing in a control which is capable of reading (and probably writing) them and converting them to the form in which the application can understand and represent them as pixel points on the display system.

SVG Artiste 2.0

OK, we have had enough background now. We will move on to how the SVG Artiste 2.0 is engineered (SVG Artiste 1.0 is a basic tool with very few variations from SVG Paint). So basically, the shell is an SDI form here even though the application supports multiple graphic documents at the same time. Here's how it is done. The diagram below should better explain it (it was created with SVG Artiste 2.0).

SVGArtisteScreenLayout.jpg

The shell has a dockable toolbar inside it. The dockable toolbar has:

  • Toolbar - All tools reside here.
  • Properties - Properties of the selected SVG element on screen.
  • WorkArea - Where the graphic creation and editing takes place.
  • WorkSpace - Resides inside the WorkArea and holds each drawing. A WorkArea is basically a collection of WorkSpaces. Each WorkSpace can be edited and saved independent of each other.
  • Controlbox - Helps users customize the properties of a WorkSpace.

Each of these parts are loaded on to the dockable toolbar in the main form. The process will be explained in detail, later in this article.

The main application is going to depend on the following projects for doing its job:

  1. Dockable toolbar - Manages the layout onscreen.
  2. Draw - Manages the functionality of the draw objects or graphic elements.
  3. SVGLib - Takes care of serialization and deserialization to SVG.

Each of this is a bigger topic, but you can get the details from their respective authors.

P.S.: I have made certain customizations for my requirement.

The important functionalities supported, as of the time of writing this article, are listed here:

  • Creation/Modification of rectangle, line, ellipse, polygon and path
  • Zoom/UnZoom
  • Multiple Undo/Redo
  • Cut/Copy/Paste of elements

Introducing Core Components

Let me explain each of the above mentioned core components briefly here.

1. Toolbar

The Toolbar provides the users a choice to select a tool. No twists here. Coming to the implementation, all tools are derived from the base class Tool.

/// <summary>
/// Base class for all drawing tools
 /// </summary>
 public abstract class Tool 
 {
    #region Fields
    /// <summary>
    /// If false the tool is not yet completed
    /// </summary>
    public Boolean IsComplete;
    #endregion Fields
    #region Methods
    /// <summary>
    /// Left mouse button is pressed
    /// </summary>
    /// <param name="drawArea"></param>
    /// <param name="e"></param>
    public virtual void OnMouseDown(DrawArea drawArea, MouseEventArgs e)
    {
    }

    /// <summary>
    /// Mouse is moved, left mouse button is pressed or none button is pressed
    /// </summary>
    /// <param name="drawArea"></param>
    /// <param name="e"></param>
    public virtual void OnMouseMove(DrawArea drawArea, MouseEventArgs e)
    {
    }

    /// <summary>
    /// Left mouse button is released
    /// </summary>
    /// <param name="drawArea"></param>
    /// <param name="e"></param>
    public virtual void OnMouseUp(DrawArea drawArea, MouseEventArgs e)
    {
    }

    public virtual void ToolActionCompleted()
    {
    }
    #endregion Methods
}

The derived classes, obviously, are expected to implement the features presented. For example, if we are dealing with a rectangle tool, a mouse down should mark the starting points of the rectangle, a mouse move should track the resizing of the intermediary stages of the rectangle, and a mouseup should create the final rectangle with the starting point and ending point as per user selection. You can refer to any of the tools implementation to get the full feel of it. It really is a very simple implementation which is used here, trust me. Adding a new tool is just a piece of cake once you get a feel of it.

2. Properties

I have probably overly simplified the job in hand by making use of the properties grid provided by the .NET framework. Since each of the shapes onscreen are typical objects as in OOP, simply assigning them to the property grid does the trick. If multiple items are selected, only the common properties are shown. SVG Artiste also follows the same religion. Another approach is to use a custom property control for each graphic element, which will give better intuitiveness to the application, but of course, at the price of more effort.

3. WorkArea

WorkArea is a place which holds WorkSpaces. A WorkArea is basically a tabbed view as provided by the dockable toolbar kit. To a WorkArea, we will add WorkSpace(s).

4. WorkSpace

This is the place where the drawings are done. Here I have done a two step approach. The workspace resides inside a WorkSpaceHolder (both being user controls). I have followed the below approach for two simple reasons:

  • To allow scrolling
  • To add in the ruler control

The WorkSpace holds the DrawArea control, whose jobs are:

  • Render the shapes on screen
  • Take user inputs (depending on the tool selected)
  • Hold the current state of the graphic (I am referring to the graphic list)

5. ControlBox

This is the place where the various aspects of the WorkSpace can be controlled. Presently there are three of them.

  • Zoom
  • Grid
  • Height/Width and Description of SVG Document

The Internals

Now that we have the recipe, let's cook the SVG Artiste. I will follow the below mentioned use cases to better explain the internals of SVG Artise:

  • User starts up SVG Artiste.
  • User opens an SVG file and SVG Artiste loads it for him/her onscreen.
  • User creates a new file and he/she draws a rectangle, does some basic operations, and then saves it.

The First UseCase

Okay. Let's start with the first use case. What this portion will cover is how the application makes use of a dockable toolbar to present itself to the user and how other components are presented on the main shell. I, being a .NET developer, am bound to be a penchant to think of an interface like Visual Studio. So it might not be surprising that, amongst other alternatives, I decided to make use of the Dockable Windows Toolkit by Cristinel Mazarine, which would give my application a familiar look and feel. The main SDI application holds an instance of DockContainer. The process here is just like adding any other control to a window. Then I created and added each of the core components explained earlier. The following code snippet explains the process:

//Work are is created here
_svgMainFiles = new WorkArea();  
_svgMainFiles.PageChanged += OnPageSelectionChanged;
_svgMainFiles.ToolDone += OnToolDone;
_svgMainFiles.ItemsSelected += SvgMainFilesItemsSelected;

//The toolbar is added here
_toolBox = new ToolBox {Size = new Size(113, 165)};
_toolBox.ToolSelectionChanged += ToolSelectionChanged;
_infoToolbar = _docker.Add(_toolBox, zAllowedDock.All, 
               new Guid("a6402b80-2ebd-4fd3-8930-024a6201d002"));
_infoToolbar.ShowCloseButton = false;

//Workspace control box is created here
_svgProperties = new WorkSpaceControlBox();
_svgProperties.ZoomChange += OnZoomChanged;
_svgProperties.GridOptionChange += GridOptionChaged;
_svgProperties.WorkAreaOptionChange += SvgPropertiesWorkAreaOptionChange;

//The Workarea is created here
_infoFilesMain = _docker.Add(_svgMainFiles, zAllowedDock.Fill, 
   new Guid("a6402b80-2ebd-4fd3-8930-024a6201d001")); 
_infoFilesMain.ShowCloseButton = false;

//Document properties are added here
_infoDocumentProperties = _docker.Add(_svgProperties, zAllowedDock.All, 
   new Guid("a6402b80-2ebd-4fd3-8930-024a6201d003"));
_infoDocumentProperties.ShowCloseButton = false;

//The properties of shapes are created here
_shapeProperties = new shapeProperties();
_shapeProperties.PropertyChanged += ShapePropertiesPropertyChanged;
_infoShapeProperties = _docker.Add(_shapeProperties, zAllowedDock.All, 
   new Guid("a6402b80-2ebd-4fd3-8930-024a6201d004"));
_infoShapeProperties.ShowCloseButton = false;

Good, we have created all the parts. Let's put it all together onto the main docker control in our main shell. The below mentioned snippet from the function SvgMainShown explains it. I decided to do it once the form is loaded completely and hence the choice of the shown event.

_docker.DockForm(_infoToolbar, DockStyle.Left, zDockMode.Inner);
_docker.DockForm(_infoFilesMain, DockStyle.Fill, zDockMode.Inner);
_docker.DockForm(_infoDocumentProperties, DockStyle.Right, zDockMode.Inner);
_docker.DockForm(_infoShapeProperties,_infoToolbar, DockStyle.Bottom, zDockMode.Outer);

A few points I would like to elaborate on this section are:

  • The GUID is a unique number used to identify each of the forms added to the docker. This is the convention used by the dockable toolbar and I have simply adopted it. It works fine for me, so I didn't grumble too much.
  • I have turned off all the close buttons just to avoid the complexity of rearranging them. But may be later on, I can enable this, adding additional flexibility.
  • The windows are completely re-arrangable as in Visual Studio. Simple click the titlebar of the toolbar you want to move around and put it into the visual cue shown on the course of the drag. It's simply up to the creativity of the user to put the toolboxes wherever he wants them to be. The following screenshots show jut a few ways you could arrange your toolboxes. This will really be handy once a lot more toolboxes are added. The flexibility in terms of arranging the onscreen window is really cool with the dockable toolkit.

    Arrangement1.jpg

    Arrangement2.jpg

    Arrangement3.jpg

  • I used the basic event mechanism in .NET for the communication between each of the forms within the application. The core of the SVG Artiste application, I should say, is the draw area, which is the "happening place" in terms of drawings. This is how I have done it.

The Second UseCase

Now that we have a skeleton, let us move onto the second use case. I have taken out the doc toolkit, which was the framework which SVG Paint used to handle document management, just to make it simple. Later on, I will be adding it, which will not only add additional features like recent list but also keep the application structured.

Coming back to the implementation in SVG Artiste, the DrawArea is capable of handling file operations. The function LoadFromXml does the job.

public bool LoadFromXml(XmlTextReader reader)
 {
    _graphicsList.Clear();
    var svg = new SvgDoc();

    if (!svg.LoadFromFile(reader))
        return false;

    SvgRoot root = svg.GetSvgRoot();

    if (root == null)
        return false;

    try
    {
        SizePicture = new SizeF(
          DrawObject.ParseSize(root.Width,DrawObject.Dpi.X),
          DrawObject.ParseSize(root.Height,DrawObject.Dpi.Y));
    }
    catch
    {
    } 

    SvgElement ele = root.getChild(); 

    if (ele != null)
        _graphicsList.AddFromSvg(ele); 

    return true;
}

The DrawArea internally makes uses of SVGLib to read and write its files. So I will give a brief idea about SVGLib.

SVGLib, basically, is a framework which can read an SVG file, parse out the shapes in the document, identify its parameters, and convert them into SVG Elements. An SVG Element is a basic SVG Shape, for example, an Ellipse. SVGLib has a list of supported shapes, and if you wish to add more, it can be done by simply extending any of the primitive shapes. Another term which is worth mentioning is the SVG Attribute. It can be thought of as a property of an SVG Element, and each element is associated to a list of attributes. So basically, for SVG Artiste, what we need to know is that the SVGLib converts the SVG document into a list of SVG Elements, each of which is associated wiht a set of attributes.

Now coming to DrawArea, it can be though of as a SVG-GDI bridge, which means, to render the SVG Elements on screen, we need to convert it to graphics.rectanlgle() or graphics.ellipse() first. The DrawArea internally has an ArrayList which holds the list of DrawObjects. Each of the DrawObject is created by iterating through SVGLib's internal element list and converting it into a DrawObject. The LoadFromXml function does just this.

The details on how the conversion happens is revealed by examining the AddFromSvg function from the GraphicsList class.

public void AddFromSvg(SvgElement ele)
{ 
    while (ele != null)
    {
        DrawObject o = CreateDrawObject(ele);
        if (o != null)
        Add(o);
        SvgElement child = ele.getChild();
        while (child != null)
        {
            AddFromSvg(child);
            child = child.getNext();
        }
        ele = ele.getNext();
    }
}

CreateDrawObject handles the DrawObject creation which completes the cycle.

DrawObject CreateDrawObject(SvgElement svge)
{ 
    DrawObject o = null;
    switch (svge.getElementType()) 
    {
        case SvgElement._SvgElementType.typeLine:
             o = DrawLine.Create((SvgLine )svge);
             break;
        case SvgElement._SvgElementType.typeRect:
             o = DrawRectangle.Create((SvgRect )svge);
             break;
        case SvgElement._SvgElementType.typeEllipse:
             o = DrawEllipse.Create((SvgEllipse )svge);
             break;
        case SvgElement._SvgElementType.typePolyline:
             o = DrawPolygon.Create((SvgPolyline )svge);
             break;
        case SvgElement._SvgElementType.typeImage:
             o = DrawImage.Create((SvgImage )svge);
             break;
        case SvgElement._SvgElementType.typeText:
             o = DrawText.Create((SvgText )svge);
             break;
        case SvgElement._SvgElementType.typeGroup:
             o = CreateGroup((SvgGroup )svge);
             break;
        case SvgElement._SvgElementType.typePath:
             o = DrawPath.Create((SvgPath)svge);
             break;
        case SvgElement._SvgElementType.typeDesc:
             Description = ((SvgDesc)svge).Value;
             break;
        default:
        break; 
    }
    return;
}

The above code snippet shows how the object creation is done based on the element type of the SVGLib object (can see an opportunity for Factory pattern).

To summarize, the flow is as follows:

  1. DrawArea makes use of SVGLib to load the SVG file
  2. SVGLib parses the file and converts it into a list of SVGElements
  3. GraphicsList makes use of SVGLib's list to populate its internal list with Graphics objects
  4. DrawArea makes use of GraphicsList to render each DrawObject on screen

A few points worth mentioning in this context are:

  • Each shape is derived from DrawObject, and DrawObject has a Draw method. So, predictably, the rendering method of DrawArea iterates through the GraphicsList and calls the Draw method to get its job done
  • Any generic operation on the SVG operation involves iterating the GraphicsList and manipulating the DrawObjects in it

The Third UseCase

The idea of this section is to explain the basic operations which are performed on the shapes, or rather how SVG Artiste handles it. When the application is opened, it presents a new blank document by default. If the user wants to make a new one, he can click the New Document icon on the toolbar or use the menu option.

Now that the new document is opened, the user selects a tool from the toolbox. When a tool is selected, the toolbox raises a ToolSelectionChanged event. This is handled by the main shell, which in turn propagates it to the DrawArea of the active document. Then the ActiveTool of the DrawArea is set to whatever the user has selected. Now the events that are going to happen on the DrawArea are going to be handled by the ActiveTool.

Say, for example, the user has selected a rectangle tool. The ActiveTool is set to Rectangle in the DrawArea. The user then draws a rectangle, which is a MouseDown-MouseMove-MouseUp transaction. Basically, the approach used in SVG Artiste is to create an object at mouse-down and then manipulate based on the mouse-move and complete the activity on mouse-up.

Now that the object is created, the GraphicsList will hold a Rectangle object. The Render function in DrawArea will call the Draw method of the Rectangle object, which renders the shape on the screen. Also the selected object(s) are set to the PropertyGrid so that the properties are shown. Changing a property will change the object's property and a redraw renders the shape with the new property.

What if I did a mistake here? I wanted to create an ellipse and not a rectangle. I would want to delete it. So Delete is the opposite of Create. Or in other words, Delete is undo for Create. Command pattern is typically used in these scenarios, and so did SVG Artiste. The ICommand interface has two methods: Execute and UnExecute.

namespace Draw.Command 
{
    public interface ICommand 

    { 
        void Execute(); 
        void UnExecute(); 
    }
}

To illustrate the functionality, let's take the example of CreateCommand:

class CreateCommand : ICommand
{
    private readonly ArrayList _graphicsList;
    private readonly DrawObject _shape;

    public CreateCommand(DrawObject shape, ArrayList graphicsList)
    {
        _shape = shape;
        _graphicsList = graphicsList;
    }

    //Disable default constructor
    private CreateCommand()
    {
    }

    public void Execute()
    {
        _graphicsList.Insert(0, _shape);
    }

    public void UnExecute()
    {
        _graphicsList.Remove(_shape);
    }
}

The code is self explanatory. Execute adds a shape, Unexecute deletes it. Whenever a command is executed, it is pushed to a stack. If the user decides to undo, SVG Artiste pops out the last command and calls its Unexecute method and pushes it to a redo stack. That's all about the undo-redo mechanism.

Now when the user has done with editing, he/she decides to save it. We have a list of DrawObjects in the GraphicsList. We are not going to go through SVGLib now, as we have to convert the DrawObject to an SVGElement and then recreate the structure and save. To make it simpler, each DrawObject can convert itself to an SVG string. So we will be iterating through the GraphicsList and converting each DrawObject to a string and then write it to a file.

public string GetXmlString(SizeF scale)
{
  string sXml = "";
  int n = _graphicsList.Count;
  for (int i = n - 1; i >= 0; i-- )
  {
    sXml += ((DrawObject)_graphicsList[i]).GetXmlStr(scale);
  }
  return sXml;
}

Append the header to sXml and we have the final SVG image document.

Points of Interest

What I would like to present here is also how we can make a functional application based on a group of disparate contributions from other contributors. I really admire them for their skills and this is just a way of acknowledging them - by making use of their work. I would say XAML is very similar to SVG. And SVG came first. It's a very powerful but underused standard. I would say not much tools came around it that empowers it. Basically, whatever can be done with XAML could have been achieved with SVG if there were sufficient tools around it. There are quite good tools which do make use of SVG, like InkScape. But the integration with development environments is still lacking. There are tons of enhancements to me made on SVG Artiste. I hope the community is going to help me with it through valuable suggestions, code contributions, and so on.

Possible Enhancements

  • More options for each tool
  • Keyboard shortcuts
  • Better communication between components (right now, it is through events and direct function calls)
  • Logging
  • Plug-in based architecture
  • Multi-touch support
  • Gesture identification
  • [the list is non-exhaustive]

History

  • 31st July, 2010: Article updated
    • Migrated to Visual Studio Express 2010
    • Bug Fix for pencil tool copy

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here