How I built the Paint 4 Kids Windows Store using only HTML5 and SVG
Paint 4 Kids is a Windows Store app, specifically designed for kids. A simple app for coloring and drawing. You can read of the consumer’s features directly from the Windows Store site, where you can also see some screen shots. For this article, you can simply think of an app that has several drawings and you can interact with them.
This article is about to discuss the use of Scalable Vector Graphics (SVG) in Paint 4 Kids, starting from some requirements of the project, going to address them and some pitfalls using SVG. I hope that some of these considerations could apply to your apps as well.
Requirements and Why we use SVG
Windows Store app gives you a great opportunity in terms of markets where you can distribute and sell your apps and in terms of devices where your app can run. Think that your app can be used on devices with different screen sizes, different screen resolutions and pixel densities. This must be considered and looking through this article gives you a very good technical understanding on how to scale your app to different screens and how to test your app using the Windows Simulator.
One of the requirement is that we want to have a single drawing that will show well at different resolutions. We ended to use SVG that is a vector image format for two-dimensional graphics. Our approach is to create a drawing at a specific resolution 2560x1440, see later for details, scaling down to the current user’s resolution. Another advantage is that is very simple and fast to fill a path with a color in SVG, that it is one of the main feature of the app. When a user tap the screen it is simple to intercept the corresponding portion of the drawing and fill the path with a specific color.
Another requirement is that we want to reuse some drawings made in XAML and have a good tooling support so that we can create and add new drawings to the app in a simple and fast way. Inkscape suits very well for this purpose. You can import a XAML drawing and export it into an SVG file and because the two vector based formats are very similar the export ends quite always successfully.
One disadvantage of using SVG is that the DOM manipulation can become slow with the increase in the number of objects added to the DOM, so some performance test and optimizations are often necessary. You can read a good table with pro and con of using SVG vs. Canvas from this Internet Explorer blog post to help deciding when using Canvas, SVG or both in the same app. Another Paint 4 Kids’ requirement is that the user can be able to save his drawing. Unfortunately, it is not possible, at the time of writing, to create an image from an SVG file, so we ended up converting the SVG file into a Canvas object.
An alternative approach could be to use Canvas instead of the SVG. With Canvas you have to create different raw imagines for different resolution, at least if you want that your drawing look very well at different resolutions, otherwise the lines will look pixelate. Another consideration is how to fill a shape in a drawing. While you are using Canvas with touch points, you are dealing with a raw image, made only of pixels; you cannot deal with shapes like a rectangle, circle and path in an easy way. If you want to feel a shape bounded by a black color line, you have to implement a flood fill (or seed fill) algorithm to accomplish the purpose and if the area to fill is big (relatively to screen size) you will see the area while is filled, whereas using SVG the fill effect is quite instantaneous.
Adapting the Drawing to Different Form Factors: Viewport and ViewBox
As outlined, we start our SVG drawing with a specific resolution in pixel of 2560 x 1440 and every drawing is loaded dynamically into the DOM from a file inside the Windows Store app. This specific value has been chosen for convenience, you can start fixing others as well. In Paint 4 Kids we want use a single drawing and make it scale at different resolutions. Look at the following images of the same drawing running in the Windows Simulator at different resolutions.
The first one is taken simulating a 10-inch monitor at the resolution of 1024 x 768 pixels and the second one on a 27-inch monitor at the resolution of 2560 x 1440.
This fixed resolution give us a sort of virtual space coordinate system. In SVG we can set this coordinate system using the viewBox attribute. If you use a tool to draw using this coordinate system, all the graphical elements are relatively to this coordinate system. Now if you want to scale down (or up) from this resolution to a specific one, suppose for example that your tablet has a resolution of 1366 x 768, you have only to set the width and height attributes on the SVG element that contains your drawing. These last two attributes define the viewport of the SVG, that is the actual resolution of our device in this scenario.
The value of the viewBox consists of four numbers that represent the minimum x-coordinate, minimum y-coordinate, width and height of the coordinate system that you want to map and scale on the viewport. So combining the viewBox, width and height attributes give us the expected result.
The following is the root SVG element of every drawing, a simple XML file. The coordinate system starts at the top, left corner or the screen, as you may expect if you have already worked with SVG.
<svg viewbox="0 0 2560 1440">...
When the drawing is loaded into DOM that happens when the user select a drawing to color, we can set the viewport attributes.
To find the root of an unnamed SVG element we use document.querySelector API, using a pseudo-class selector to find the first SVG element. Because this API is called only once, when user select an element to draw, we can overlook any performance lag.
svgd = document.querySelector("svg:first-of-type");
The code sample also use the window object and its properties inner* to get the actual resolution in pixel at runtime.
Another consideration when dealing with viewport and viewBox is aspect ratio. If the two coordinate systems have different ratio of width and height, sometime you want that the resulting image fit non-uniformly. In other scenarios, you may want that the aspect ratio is preserved and the image scaled uniformly. SVG use the preserveAspectRatio attribute to decide if the image have to be scaled uniformly or not. We will discuss this later when we speak about “stamps”. For the drawings the default behavior that scale the viewBox uniformly to fit the viewport just works as we want.
How to Fill a Path with Colors and Images
Fill a SVG shape, like a path, a rectangle or others is a very easy step and it is quite instantaneous, because it is like setting a style property in CSS, so you don’t need to write the code to find every single pixel surrounded by a line. You can look at the code below that it is the call back function when a tap event is fired inside the SVG area.
var el = document.elementFromPoint(e.x, e.y);
var selectedColor = "255,0,0,1";
el.setAttribute("style", "fill:rgba(" + selectedColor + "); stroke-width:3;);
In the code above the e is an object of type MSPointerEvent. This object is very important, you can get this object if you subscribe to some of the MSPointer* events (like MSPointerDown). With a single line of code you can subscribe to an event that comes from mouse, touch and even from a pen! Also inside the MSPointerEvent you can, if needed, read the pointerType property that give you the detailed information of which kind of device generate the event. If you are interested on this topic, you can read this blog post on the APIs for the touch input on IE 10 and Windows Store apps.
Back to the code, the e object here is only used to get the x, y point coordinates from the input device, using the elementFromPoint API. el now is the specific shape we want to fill with a color, and it is of type SVGPathElement. The rest of the code is straightforward, setting the fill color and the line stroke width of the SVGPathElement, regardless of the actual shape that it really is.
Instead of using the setAttirbute API you could also directly set the fill and strokeWidth property on the SVGPathElement, avoiding the string parsing can give you more performance; even though will not be perceived by the user in this scenario.
In the sample, the color is a standard solid RGBA color that the user can chose from a colors’ palette, but you can also fill the shape with a path coming from an image or using gradients. Paint 4 Kids defines not only a common set of colors, but also some images, like stones, grass etc. To do that in SVG you can define some patterns as in the code below:
<pattern id="imgPatterns_1" patternUnits="userSpaceOnUse" width="128" height="128">
<image xlink:href="../images/BRICK.PNG" width="128" height="128" />
Two thinks to note in the code: first, we are defining a SVGPatternElement that it based on the imaged contained. Second, you can define the patternUnits property that define how to fill a shape with the pattern itself. The userSpaceOnuse simply repeat the image more and more times without padding between them. Once the pattern is defined, we can use in the fill property used above using the following syntax:
var selectedColor = "url(#imgPatterns_1)";
el.setAttribute("style", "fill:" + selectedColor + "; stroke-width:3;);
Looking at the code, you can notice that the fill property it is now a url that use the id of the pattern element defined above, preceded by the # symbol (e.g. #imgPattern_1).
See the images below to see some pattern effects in use.