SVG vs. Canvas on Trivial Drawing Application

Table of Contents

Pixel drawing application
Pixels with Canvas
Pixels with SVG
Winner of pixels
Vector drawing application
Vectors with SVG
Vectors with Canvas
Winner of Vectors
Combining the best techniques
Canvas to SVG
SVG to Canvas
Putting it all together
Combining existing components: SVG-Edit and CanvasPaint
Definition of "Web Graphics"
Browser Compatibility
The Speed Situation
Need for Scene Graph
No vs. Situation!
The Political vs. Situation

Recent popularity on Canvas development and support has made the need for SVG somewhat vague. As SVG is still not widely adopted and Canvas has recently acquired lot of support from developers and browsers, some might question the role of SVG in future web. However, this is not the case and there is a strong need for SVG. To illustrate this, we will create a small a) vector drawing application with SVG and b) pixel drawing application with Canvas. Both are easy and straightforward to develop. The assumption is, that these are the typical applications for both techniques.

What will be interesting, though, is when we swap the standard and the target application. Now the assumption is, that each takes considerably more work. We show this to be true with a c) pixel drawing application with SVG, and a d) vector drawing application with Canvas.

This experiment gives insight on the limits of each standard. If it would turn out, that either of these "wrong" approaches works better than the other one, that would suggest the concerning standard to be "better", or at least to be more limited in general.

SVG's native "file format" is naturally SVG (XML), while Canvas' is bitmap, such as PNG. It will also be interesting to experiment on transferring data between them. This would be required for the standards to be attached into one useful drawing application.

We'll then attempt to layer and combine the SVG vector drawing application with the Canvas pixel drawing application. This way we can bring the best of both standards into one small standards-compliant (and non-Flash) web drawing application. Here the assumption is, though, that it's easier to stay in one standard, as there is no useful interface between SVG and Canvas. This is partially proved wrong, as we will show possible workarounds to combine both standards.

First we'll create the trivial drawing applications, by hand-written JavaScript (with one exception) in effort to really find the limits of each standard. Then we'll see which standard is the "winner", and investigate on ways to exchange data between them.

We conclude that there is no vs. situation between SVG and Canvas. Instead, both are needed and useful for future web, as both have their strong points and there's no reason to degeneralize those into just one standard.

Browser requirements for demos presented in the paper: Firefox 3.0+, Safari 4.0.

Pixels are no doubt the easier setting, so we even implemented some extra features! The features of our pixel drawing applications are:

  • Draw rectangle-shaped pixels

  • Three selectable brush sizes

  • Four selectable colors

As there are no pixels in SVG, we need to emulate pixels by drawing SVG objects. This will look exactly the same as real pixels. However, each pixel is a new SVG shape added to the DOM, meaning that the DOM grows as drawing continues. Modifying DOM with raw JavaScript is quite verbose, too.

Strangely, it's not possible to get the rendered view out of web browser. SVG lacks the basic toDataURL() function. Thus, the resulting image is not exportable as bitmap without server-side support, exactly which will be done in the section called “SVG to Canvas”.

Implemention notes:

Canvas. You guessed it.

As assumed, pixels are the natural environment for Canvas. Canvas has a basic API for drawing primitive shapes, which is all we need. Canvas is a low-level "framebuffer" for drawing pixels, and in that buffer, you can do anything imaginable, you just need to do it by hand. So in addition to drawing application, there are lot of traditional applications and games, like Wolfenstein 3D clones, that benefit from Canvas.

For SVG, each "pixel" is a separate SVG shape, bloating the SVG DOM. This slows down rendering, takes a lot of memory and results in a huge DOM tree. So for any real work, emulating pixels with SVG shapes is no good. Also, SVG doesn't work as a frame buffer, there's a big overhead in any implementation which tries to emulate this. SVG is for higher-level shapes, not for pixels.

Vectors are more interesting, especially for Canvas, since it doesn't support them natively. Also, being bitmap data, Canvas pixelates when scaling up. So we need to find a non-pixelating solution for Canvas.

For vector drawing, there are two parts: 1) rendering vector shapes on screen, and 2) allowing interaction with the shapes. So the features of our vector drawing applications are:

  • Insert random-colored circles with 40 pixel radius

  • Move the circles around (drag-n-drop)

  • Scale the circles (with right-click-drag)

  • Re-stack the circles on the top when clicked

This is what SVG is all about. Vector shapes can be simply added to the SVG DOM, and they appear rendered on the browser window. It's just like adding the emulated pixels we already did.

SVG also handles high-level mouse events, telling us which element was clicked or dragged. The low-level core of user interaction needs to be done by hand with JavaScript. That's quite basic scripting, though. Using any SVG library might make it even easier, but we chose do it manually.

Implementation notes:

As Canvas is just a bitmap and supports those functions well, we need to implement a scene graph on top of Canvas with JavaScript/DOM, in order to keep track of the state of all the vector shapes, and implementing tests on which shapes are under mouse cursor. That's a huge overhead (which SVG natively handles), and we won't be going down that road.

So we decided to take a shortcut by using the CakeJS library. This library provides scene graph implementation as required to support vector shapes that can be drawn and dragged on Canvas. Adding shapes becomes almost as easy as drawing pixels, and mouse interaction with shapes is as easy as with SVG. However, CakeJS is a huge 200+ KB library, adding a lot of extra code to the implementation.

Implemention notes:

  • CakeJS library is not very well documented, and one needs to search source and example code for basic API information. For example, the Cake functionality cannot be neatly added to an existing Canvas: new Canvas(HTMLCanvasElement). When done that way, the mouse events do not work. Instead, Cake needs to create the Canvas element by itself, and append it to a placeholder element: new Canvas(HTMLPlaceholderElement, width, height).

  • Now the mouse event listeners need to be added to each shape, and in contrast to SVG, Cake doesn't relay the top-most shape clicked/dragged via the events target.

  • As we are only drawing circles here, we ended up doing transformations (translate and scale) as "raw". That is, we simply change the x, y and radius attributes of the circle shapes.

As can be seen from the previous experiments, SVG is strong and robust with built-in shape handling, while Canvas is fast and efficient for pixel manipulation. For a complete drawing application, we need layered bitmaps on top of each other, something that would resemble Adobe Photoshop with layer ordering and simple shapes. For this, common interfaces are required between Canvas and SVG.

Both standards allow accessing their data through JavaScript, but there are no shared interfaces to utilize co-operation. SVG gives its data as (serialized) XML, while Canvas as base64-coded PNG.

We came up with two reasonable solutions: Including Canvas as foreignObject into SVG, or to import Canvas bitmap data as image element into SVG. foreignObject allows SVG to be extended with anything the browser can render.

However, SVG doesn't know that included element is basically a bitmap. This makes rasterization of the composed vector image hard, as the component that makes the rasterization should also be able to render HTML Canvas element (or in fact any HTML element). As our purpose is to rasterize all image data into one single image, that is not an option. foreignObject is a good solution for example when including interactive features into SVG, but it is not good for our purposes right here.

Canvas supports toDataUrl("image/png") function that returns a data URI representation of current Canvas bitmap encoded in base64. This image data can be set as source for HTML img element or image element in SVG. We use this method to import Canvas to SVG with JavaScript.

Now we have successfully imported Canvas bitmap into SVG as a separate element. This element can be easily transformed like any SVG element. Transparency is preserved as well.

Figure 3. Canvas to SVG using toDataUrl("image/png")

If SVG would also support toDataUrl() function like Canvas, this would be trivial. However, that is not the case, and even stranger is that the Internet gives us no clues as to why is SVG missing this basic functionality. After all, the browser internally renders SVG as a raster bitmap, so why not provide that data for JavaScript?

So we need to implement full SVG parsing and rendering in Canvas or do SVG conversion to bitmap server-side. Some libraries, like CakeJS, have implemented SVG parsing. This method seems to be a bit experimental, and is once again re-implementing browser's native functionality with JavaScript.

Server-side conversion is a stable way rasterize SVG to bitmap. This can be done for example with ImageMagick, Batik or Inkscape. If we would have selected foreignObject as our "Canvas to SVG" -approach, we would not be able to fully rasterize SVG with these utilities.

Dynamically modified SVG can be serialized to string with JavaScript and sent to conversion with Ajax.

When we combine SVG vector drawing application with server-side conversion, we have a working method of transferring dynamically modified SVG to our Canvas bitmap.

Figure 4. SVG to Canvas using server-side conversion and Ajax

So, we combined everything we learned from our experiments: Canvas based pixel drawing application, SVG based Vector drawing application and linked them together with canvas.toDataUrl() and server-side conversion. We are now consistently transferring bitmap data between Canvas and SVG.

In this example SVG and Canvas areas are separated to follow previous layouts. For a complete and more advanced drawing application a better layout would be needed. However, this demo is interesting as both of the drawing components can be replaced with any existing components and bind together with the interfaces described in our solution.

By embedding two pre-made non-trivial components and using toDataUrl() and server-side conversion a complete drawing application could be achieved. We quickly tried this with SVG-Edit and CanvasPaint. Implementation is not stable as we did not modify the codebases of the components. This demonstration is just to illustrate the potential of SVG-Canvas data exchange. It also stresses how Canvas is more suitable for "MSPaint" and SVG is more suitable for "Inkscape".

During all the implementation and research work for this paper, we have seen many relating trends on the web. Here are some observations on those that we'd like to point out.

If we take a look at graphics on the web, there is a clear need for vector graphics. W3C suggests that SVG would be used where needed. Generally, web graphics are naively defined as "72 dpi raster image, preferably JPEG". Even when source image is from vector origin, the image is rasterized for web, because of the lacking support / belief for vector graphics in web.

Situation might be even worse if graphics from vector origin, natively fit for SVG, should be done in Canvas. SVG provides nice addressable representation of vector graphics and is supported with various tools and editors. In XHTML specification the trend seems to be that structure is being generalized, like <h1>..<h6> into nested <h> elements, and even <img> into <object>. So why not <bitmap> and <vector> elements instead of <img>, <svg> and <canvas>?

To be truly able to make a transition from print media to web, various shapes and scalability of graphics has to be provided by the browsers in an addressible manner. This is where consistent and good SVG support is required.

Browser compatibility is always a problem, especially when introducing new elements to HTML. Even though SVG is a much older standard than Canvas, it's less supported in browsers. That's partly because SVG is such a huge standard, covering everything from foreignObjects to animation.

As discussed in the section called “Pixels with SVG”, getting mouse coordinates relative to an element's top-left corner is still really complicated. JavaScript libraries try to handle all the quirks involved, but it's a mess. The new getBoundingClientRect() brought by IE looks promising, but for SVG, there are still issues interpreting W3C's specs.

There are many libraries which try to make SVG easier to use with JavaScript, but we thought the library situation is somewhat messy, and for this paper it was more beneficial to do everything by hand. Most libraries handle adding SVG shapes with Canvas-like drawing commands, as this seems to be what web developers prefer.

It seems like for interactive applications a scene graph support is needed.

Browser already provides a "scene graph". It keeps information of elements and provides required events for interaction. Layered elements are possible to implement with HTML and JavaScript. This method does not provide easy export of the resulting image. A demo of this approach is illustrated below.

Figure 7. Using browsers scene graph

For interactive Canvas applications, a popular way seems to be implementing SVG-like scene graph on Canvas. Why not just use SVG? It's a huge overhead to implement SVG with Canvas, an overhead that the browsers already handle quite efficiently. There's no way of ever implementing SVG as fast with JavaScript than the browser does natively.

However, if Canvas would have a native scene graph implementation, then things could change. Interesting quote from W3C specification about canvas.toDataUrl(): “The possible values are MIME types with no parameters, for example image/png, image/jpeg, or even maybe image/svg+xml if the implementation actually keeps enough information to reliably render an SVG image from the Canvas.” This would actually mean, that Canvas sort of implements SVG, lending even more confusion for their co-existence.

As Apple introduced Canvas, there was some controversy for Apple's decision to create a new proprietary element instead of supporting SVG, which still hadn't achieved broad web developer acceptance. [Wikipedia: Canvas] However, as stated its our believe that there is need for both standars, and all that's needed is stable support for both of them.

Sadly, Internet Explorer never started supporting SVG because of their VML effort, and Adobe stopped supporting their IE SVG plugin (maybe because of acquiring Macromedia and Flash?). This leaves SVG stranded, as it doesn't seem probable for SVG to ever appear on IE, which would be required for widespread support. There are, however, libraries which bring both SVG (SVG Web) and Canvas (Explorer Canvas) support for IE, using IE's native VML capability or Adobe Flash plugin.

We chose not to use the Docbook bibliography, as it does not render correctly. Instead, we are using inline links, which do not render in typical print output.