Scalable Vector Graphics on Java ME Blog


    JSR 226 is the Scalable 2D Vector Graphics API for low-end mobile devices. It is an optional API that mandates support for the SVG Tiny profile and is now available on a number of mobile handsets. Since Mobile Service Architecture (MSA)--the specification for the next wave of Java ME--makes support for JSR 226 mandatory, an increasing number of future handsets will implement this API. In this article, we look at the fundamentals of JSR 226.

    Scalable Vector Graphics (SVG) is a language for describing two-dimensional vector graphics, and SVG Tiny is a subset of SVG suitable for displaying vector graphics on small devices. SVG is based on XML and other applicable standards as described in its specification. This article does not intend to offer an insight into SVG itself. There are excellent tutorials available on the net that cover the basics of SVG and SVG Tiny. Links to some of these have been furnished in theResources section.

    I assume you are familiar with Java ME and the SVG Tiny specs. The applications (except the "Static Pointer Demo" and the "Moving Pointer Demo") used to examine the basic features of JSR 226 are drawn from the demos that come with the Sun Java Wireless Toolkit 2.5 for CLDC and are part of the "SVGDemo" project. In order to follow this article effectively, you'll need to download the Sun Java Wireless Toolkit and install it on your computer. I would also recommend that you download the JSR 226 API documentation for comprehensive information.

    Create an SVG Image from Scratch

    This demo application shows how to create an SVG image with inline code. The image is created by theCreateEmptyImageDemo MIDlet. The MIDlet creates an instance of SVGImageCanvas and passes the created image to it for rendering.

    In the world of JSR 226, ScalableGraphics is the class that takes care of all rendering. This is what the API documentation says about ScalableGrapics:

    This is the fundamental class for 2D rendering. TheScalableGraphics context class provides and handles all the rendering capability within this package. In other words, the rendering can only be achieved through the render method provided in this class. Note that the ScalableGraphicsinstance must be bound to the rendering target prior to calling the render method. The implementation must clip to the viewport boundaries.

    The other fundamental class isScalableImage, which is described in the API documentation as follows:

    This class models images in vector format, such as the Scalable Vector Graphics (SVG) image format. Therefore, it is required that all classes representing "scalable" images extend this class.

    So every JSR 226 based application will have instances of these two classes or of a subclass, if applicable. Accordingly, theCreateEmptyImageDemo MIDlet creates anSVGImage that is a subclass ofScalableImage:

    SVGImage svgImage = SVGImage.createEmptyImage(null); Document doc = svgImage.getDocument(); SVGSVGElement svg = (SVGSVGElement)doc.getDocumentElement(); 

    At this point we have an empty SVGImage and the child element corresponding to the topmost tag. This element is cast to SVGSVGElement, which represents the<svg> element in the (SVG) document tree. The next step is to define a viewBox(vb):

    // First, set the viewBox so that we can work in // a 100 by 100 area. SVGRect vb = svg.createSVGRect(); vb.setWidth(100); vb.setHeight(100); svg.setRectTrait("viewBox", vb); 

    The viewBox attribute ensures that the dimensions of image are calculated after taking into account the actual display area available at the time of rendering. This issue is elaborated upon later in the section An Animated Menu.

    We then go on to create the images: a rectangle and a circle within it. First we need to create an SVGElement:

    SVGElement r = (SVGElement)doc.createElementNS(SVG_NAMESPACE_URI, "rect"); 

    The SVGElement interface defines methods to set various attributes and properties required to define the specified element, which is rect in this case. Note that the specified name of the element must be one of those defined in the SVG Tiny specification. The API documentation lists the valid element names. Once the SVGElement has been created and the relevant attributes/properties have been set, it is appended to the SVGSVGElement already created. So theSVGElement now becomes a part of our (SVG) document tree:

    SVGRGBColor bkgColor = svg.createSVGRGBColor(99, 128, 147); r.setRGBColorTrait("fill", bkgColor); r.setFloatTrait("x", 25); r.setFloatTrait("y", 25); r.setFloatTrait("rx", 5); r.setFloatTrait("ry", 5); r.setFloatTrait("width", 50); r.setFloatTrait("height", 50); svg.appendChild(r); 

    Similarly, the circle is defined:

    SVGElement c = (SVGElement) doc.createElementNS(SVG_NAMESPACE_URI, "circle"); SVGRGBColor fgColor = svg.createSVGRGBColor(255, 255, 255); c.setRGBColorTrait("fill", fgColor); c.setFloatTrait("cx", 50); c.setFloatTrait("cy", 50); c.setFloatTrait("r", 20); svg.appendChild(c); 

    The image--svgImage--can now be rendered on a canvas. In this case, the rendering is performed by theSVGImageCanvas class.

    The SVGImageCanvas class works with two instance variables: the SVGImage that has to be rendered and aScalableGraphics context. The rendering of the image is done in the paint method. Once the display dimensions are determined and a white background is established, the MIDP Graphics object to be used by thepaint method has to be bound to theScalableGraphics context. The viewport dimensions are then set to cover the available display area and the image is rendered:

    class SVGImageCanvas extends Canvas { /** * The SVGImage painted by the canvas. */ protected SVGImage svgImage; /** * The ScalableGraphics used to paint into the midp * Graphics instance. */ protected ScalableGraphics sg = ScalableGraphics.createInstance(); /** * @param svgImage the SVGImage * this canvas should paint. */ protected SVGImageCanvas(final SVGImage svgImage) { this.svgImage = svgImage; } public void paint(Graphics g) { g.setColor(255, 255, 255); g.fillRect(0, 0, getWidth(), getHeight()); sg.bindTarget(g); svgImage.setViewportWidth(getWidth()); svgImage.setViewportHeight(getHeight()); sg.render(0, 0, svgImage); sg.releaseTarget(); } } 

    Note that it is important to invoke thereleaseTarget() method after the image is rendered. This method flushes the image and ensures that it becomes visible. Implementations do not guarantee that the rendered image will be visible unless this method is called.

    Static Pointer Demo

    The usual method of handling SVG code is to have one or more SVG files containing image information, and the application renders the images based on such information. By separating Java code from SVG, we can ensure that modifications in the image file will not affect the application; only the SVG file will have to be changed and the application will not have to be recompiled. In this section, we will see how images can be drawn from SVG files. The source code of this application is available in the Resources section.

    This is a simple application that draws a triangle at the center of the screen. The image information is available in theptr2.svg file, and the StaticPointer MIDlet creates an SVGImage from the information in the file. To do this, an InputStream is created with the SVG file as the parameter:

    //we'll work with the SVG file placed in the res folder public static final String POINTER_IMAGE = "/ptr2.svg"; . . . public void startApp() { InputStream is = getClass().getResourceAsStream(POINTER_IMAGE); . . . 

    The next step is to create an SVGImage from this stream:

    SVGImage pointerimage = (SVGImage)SVGImage.createImage(is, null); 

    StaticPointer then instantiates aStaticPointerCanvas object, which performs the actual rendering. The SVGImage, pointerimage, is passed to the StaticPointerCanvas object as a parameter. The StaticPointerCanvas class is very similar to the SVGImageCanvas already described.

    "Static Pointer Demo" draws a blue pointer in the middle of the screen, as shown in Figure 1.

    Display for StaticPointerDemo

    Figure 1. Display for Static Pointer Demo

    Moving Pointer Demo

    SVG not only supports rendering of static images, but also provides for animation in various ways. The simplest (and most fundamental) form of animation is, of course, the sequential display of a series of static images. This approach, shown in "Moving Pointer Demo," does not use the animation-oriented mechanisms built into the SVG specifications to simulate movement, but can be useful in creating motion when only simple, static image files are available. The source code of this application is available in the Resources section.

    In "Moving Pointer Demo," we deal with three SVG files each describing a pointer-like figure, as in Static Pointer Demo. The pointers are identical except in the following two respects:

    • The fill colors are different.
    • The location of each pointer is different.

    The code for the MovingPointer MIDlet is almost the same as that for StaticPointer. The only difference is that three SVGImages are created by theMovingPointer class and passed toMovingPointerCanvas for rendering:

    InputStream is1 = getClass().getResourceAsStream(POINTER_IMAGE1); if (is1 == null) { throw new Error("Could not load " + POINTER_IMAGE1); } InputStream is2 = getClass().getResourceAsStream(POINTER_IMAGE2); if (is2 == null) { throw new Error("Could not load " + POINTER_IMAGE2); } InputStream is3 = getClass().getResourceAsStream(POINTER_IMAGE3); if (is3 == null) { throw new Error("Could not load " + POINTER_IMAGE3); } try { System.out.print("Loading SVGImages .... "); SVGImage si1 = (SVGImage)SVGImage.createImage(is1, null); SVGImage si2 = (SVGImage)SVGImage.createImage(is2, null); SVGImage si3 = (SVGImage)SVGImage.createImage(is3, null); System.out.println(" ... Done"); movingpointercanvas = new MovingPointerCanvas(si1, si2, si3); . . . 

    The MovingPointerCanvas class implements theRunnable interface for repetitive rendering of the three pointers. In the run method, a switch statement checks the variable state and, depending on its value (0, 1, or 2), assigns the reference of one of the three SVGImages received fromMovingPointer to the imagetodrawvariable. The state variable is then set to the next value and the repaint method is called, which rendersimagetodraw in the same way as the paintmethod in SVGImageCanvas described earlier:

    switch(state) { case 0: imagetodraw = si1; state++; repaint(); break; case 1: imagetodraw = si2; state++; repaint(); break; case 2: imagetodraw = si3; state = 0; repaint(); } 

    After returning from repaint, the thread sleeps for 500 milliseconds and iterates the loop once more, creating a moving pointer.

    The SVGAnimator

    The SVG specification supports a variety of ways for animating images: different kinds of motion, change of color, zooming, panning, and so on. JSR 226 handles animation through theSVGAnimator class. The API documentation says:

    The SVGAnimator class handles automatic rendering of updates and animations in an SVGImage to a target user interface (UI) component.

    We'll look at a very nice demo application--"Play SVG Animation," which comes with WTK 2.5--to understand howSVGAnimator can be used.

    The entry point of this application is theGreetingCard class, which extends a MIDlet. Functionally, GreetingCard only initializes its superclass--the PlaySVGImageDemo MIDlet--by passing a reference to the animated SVG image file:

    public final class GreetingCard extends PlaySVGImageDemo { /** * The image that holds the animated SVG content. */ private static final String SVG_IMAGE = "/svg/Halloween.svg"; /** * The image that holds the splash screen image. */ private static final String SPLASH_IMAGE = "/images/GreetingCardHelp.png"; public GreetingCard() { super(SVG_IMAGE, SPLASH_IMAGE, true); } } 

    The real action takes place in the startApp method of PlaySVGImageDemo. After an SVGImage is built, it is used to create an SVGAnimator object:

    // Get input stream to the SVG image //stored in the MIDlet's jar. InputStream svgDemoStream = getClass().getResourceAsStream(svgImageName); // Build an SVGImage instance from the stream svgImage = null; if (svgDemoStream != null) { try { svgImage = (SVGImage)SVGImage.createImage (svgDemoStream, null); } catch (IOException io) { io.printStackTrace(); } } if (svgImage == null) { throw new Error(ERROR_COULD_NOT_LOAD_SVG + svgImageName); } // Build an SVGAnimator from the SVGImage. //The SVGAnimator will handle // all the animation details and run the SVG animation in a // Canvas instance. svgAnimator = SVGAnimator.createAnimator(svgImage); 

    The SVGAnimator class has a method to set the minimum time interval between two successive renderings of the image being animated. In this case, this time interval is set to 0.01 second (ten milliseconds), which means the image will be refreshed 100 times per second. If this value is set to, say,1.0f and the MIDlet is recompiled, the image will be refreshed once every second. Consequently the motions will be jerky:


    An SVGAnimator object has a platform-dependent user interface component associated with it, on which the image is rendered. Mobile devices support LCDUI and, for such devices, the target component associated with anSVGAnimator is a Canvas object. This object is obtained by calling the getTargetComponentmethod of the SVGAnimator class, and the dimensions of this canvas are used to set the width and height of the viewport:

    svgCanvas = (Canvas)svgAnimator.getTargetComponent(); svgImage.setViewportWidth(svgCanvas.getWidth()); svgImage.setViewportHeight(svgCanvas.getHeight()); 

    Of course, in addition to the steps required for showing the animated image, the MIDlet performs some other tasks that are needed for supporting activities like responding to commands and key presses.

    Finally, the animation is started with a call to theplay method of the SVGAnimator class:

    if (autoStart) {; . . 

    This application demonstrates how easy it is to show even complex animations using the JSR 226 API. Note that it was not necessary to get a ScalableGrphics context in this case, as SVGAnimator takes care of all that is needed to render the image.

    An Animated Menu

    "SVG Demo" is a suite of applications, one of which is "Optimized Menu." This demo shows how to use the information in an SVG file to convert an animated SVG image into a series of rasterized images and then display these images in a sequence to produce animation. Like many programming decisions, this too involves a trade-off. Every time an SVG image is displayed, a considerable amount of processing has to be done to convert the SVG code into a form suitable for display. By pre-rasterizing the SVG image, the processing is done only once and not every time the menu is displayed. However, additional memory will have to be allocated to hold the rasterized images.

    The technique used here is quite straightforward. Nine frames are created for each icon, with frame 0 representing the unselected state and frame 8 the selected one. All intervening frames represent animation in progress. When an icon is selected, the corresponding frames are displayed in quick succession from 0 to 8 showing the transition from the unselected to the selected state. For the icon that is deselected, the frames are stepped backwards from 8 to 0, thus creating a "reverse" animation. "Optimized Menu" displays a menu in which the icons are arranged in the form of a grid. Each icon is characterized by its position in the grid--a row number and a column number. Therefore, the entire set of rasterized images is structured as a three dimensional array:

    menuIcons[r][c][f] where r = row number for the icon (0 to 2) c = column number for the icon (0 to 2) f = frame number (0 to 8)

    The image at menuIcons[0][0][0], for example, represents the unselected frame for the icon at the top left corner.

    The steps to be followed for filling the array are:

    • Load all the icons into a vector.
    • Establish a viewBox for each icon.
    • Render frames for each icon and load them into the array.

    First Step

    Loading the icons into a vector is basically a matter of convenience so that subsequent handling becomes easier:

    // The input svgImage should have numRows * numCols //icons under the // root svg element. final int numIcons = numRows * numCols; Document doc = svgImage.getDocument(); SVGSVGElement svg = (SVGSVGElement)doc.getDocumentElement(); // Load all the icons in a Vector for future manipulation Vector iconVector = new Vector(); for (int i = 0; i < numIcons; i++) { SVGElement iconElt = (SVGElement)doc.getElementById("icon_" + i); if (iconElt == null) { throw new IllegalArgumentException ("The SVG Image does not have " + numIcons + " icons under the root svg element" + " icon_" + i + " does not exist in the document"); } if (!(iconElt instanceof SVGLocatableElement)) { throw new IllegalArgumentException ("The " + (i + 1) + "th icon under the " + "root svg element is not a <g;"); } // Hide all icons initially iconElt.setTrait("display", "none"); iconVector.addElement(iconElt); } 

    Second Step

    Before the viewBoxes can be established, the space available for each icon is determined. To set theviewBox parameters, dimensional data furnished in the SVG document is translated into device-dependent units (pixels) through transformation matrices--for the image (SVGSVGElement) as a whole, in conjunction with those for individual icons. The result of these transformations, the bounding boxes, are scaled to have a "padding" area around each icon and are then saved as an array of viewBoxes (iconViewBox):

    // Now, compute the size allocated to each icon. int width = getWidth(); int height = getHeight(); iconWidth = width / numCols; iconHeight = height / numRows; // Render each icon in a bitmap. svgImage.setViewportWidth(iconWidth); svgImage.setViewportHeight(iconHeight); final int numFrames = 1 + numFramesFocus; menuIcons = new Image[numRows][numCols][numFrames]; currentFrame = new int[numRows][numCols]; // calculate viewBox for each icon // svg -> screen SVGMatrix svgCTM = svg.getScreenCTM(); // screen -> svg SVGMatrix svgICTM = svgCTM.inverse(); SVGRect[] iconViewBox = new SVGRect[numIcons]; for (int i = 0; i < numIcons; ++i) { SVGLocatableElement icon = (SVGLocatableElement) iconVector.elementAt(i); // Get the user space bounding box for the icon SVGRect bbox = icon.getBBox(); if (bbox == null) { // If someone tampered with the //svg menu file, the bbox // could be null iconViewBox[i] = null; continue; } // icon -> svg -> screen SVGMatrix iconCTM = icon.getScreenCTM(); // icon -> svg SVGMatrix iconToSvg = svg.createSVGMatrixComponents(svgICTM.getComponent(0), svgICTM.getComponent(1), svgICTM.getComponent(2), svgICTM.getComponent(3), svgICTM.getComponent(4), svgICTM.getComponent(5)); iconToSvg.mMultiply(iconCTM); // get the icon bounding box in svg coordinates float x0 = bbox.getX(); float y0 = bbox.getY(); float x1 = x0 + bbox.getWidth(); float y1 = y0 + bbox.getHeight(); float[] pointsX = { x0, x0, x1, x1 }; float[] pointsY = { y0, y1, y0, y1 }; float minX = Float.MAX_VALUE; float minY = Float.MAX_VALUE; float maxX = -Float.MAX_VALUE; float maxY = -Float.MAX_VALUE; float a = iconToSvg.getComponent(0); float b = iconToSvg.getComponent(1); float c = iconToSvg.getComponent(2); float d = iconToSvg.getComponent(3); float e = iconToSvg.getComponent(4); float f = iconToSvg.getComponent(5); for (int j = 0; j < pointsX.length; ++j) { float nx = (a * pointsX[j]) + (c * pointsY[j]) + e; float ny = (b * pointsX[j]) + (d * pointsY[j]) + f; if (nx < minX) { minX = nx; } if (nx > maxX) { maxX = nx; } if (ny < minY) { minY = ny; } if (ny > maxY) { maxY = ny; } } bbox.setX(minX); bbox.setY(minY); bbox.setWidth(maxX - minX); bbox.setHeight(maxY - minY); iconViewBox[i] = pad(bbox); } 

    The getScreenCTM method of theSVGLocatableElement class returns transformation matrices that define affine transforms for mapping dimensional and transformation information from SVG files into actual display units applicable to the device.

    Third Step

    With the viewBox dimensions known, the frames can be rendered. As we can see from the code snippet below, the process of rendering is quite simple. For each frame an empty image is created, and then the rendering is done using theGraphics instance for that image. Note that the "animation time" for the image is incremented byframeLength (0.0625) seconds so that the next frame rendered is the one that matches the image to be shown as per animation settings given in the SVG document. In this case, we know that there are nine frames to be shown during the animation of an icon, and that the duration of animation as specified in theoptimizedSVGMenuG.svg file (located in the\WTK25\apps\SVGDemo\res\svg folder) is 0.5 second. So the time interval between two successive frames (theframeLength) is 0.5 divided by 8. This is shown below:

    // do the rendering int i = 0; for (int ri = 0; ri < numRows; ri++) { for (int ci = 0; ci < numCols; ci++, i++) { // Get the icon we want to draw SVGLocatableElement icon = (SVGLocatableElement)iconVector.elementAt(i); // Now, set the icon's display to 'inline' //before drawing // it to the offscreen. icon.setTrait("display", "inline"); // "zoom" the icon if (iconViewBox[i] != null) { svg.setRectTrait("viewBox", iconViewBox[i]); } // Create a bitmap to draw into svg.setCurrentTime(0); for (int fi = 0; fi < numFrames; fi++) { menuIcons[ri][ci][fi] = Image.createImage(iconWidth, iconHeight); // Get a Graphics instance //that we can draw into Graphics g = menuIcons[ri][ci][fi].getGraphics(); g.setColor(255, 255, 255); g.fillRect(0, 0, iconWidth, iconHeight); sg.bindTarget(g); sg.render(0, 0, svgImage); sg.releaseTarget(); svgImage.incrementTime(frameLength); } icon.setTrait("display", "none"); } } 

    Note that the tight bounding box returned by thegetBBox method is defined as "the smallest possible rectangle that includes the geometry of all contained graphics elements excluding stroke." Rendering an image as per the bounding box dimensions would display the smallest possible image and would not use the available screen area optimally. To ensure that the entire display area is properly filled up, the viewBoxattribute has to be set so that image will be stretched to occupy the entire viewport. To see what happens if this is not done, we can comment out the following code:

    /*if (iconViewBox[i] != null) { svg.setRectTrait("viewBox", iconViewBox[i]); }*/ 

    If we now build SVGDemo and run the application, the resulting display will be as shown in Figure 2.

    Figure 2

    Figure 2. Display without viewBox attribute

    The Animation

    Finally, it is time to see how the animation is performed. What we have is an array containing nine frames covering the entire animation sequence for each icon. We also have the following variables:

    • The row index of the selected icon (updated by key presses):focusRow.
    • The column index of the selected icon (updated by key presses):focusCol.
    • An array of indices of the frame being currently shown for each icon: currFrame[row][col].

    The animation is performed by a thread:

    // The following thread handles //animating the currently focused item. final long frameLengthMs = (long)(frameLength * 1000); Thread th = new Thread() { public void run() { long start = 0; long end = 0; long sleep = 0; boolean interrupted = false; while (!interrupted) { start = System.currentTimeMillis(); int cr = focusRow; int cc = focusCol; boolean needUpdate = false; for (int ri = 0; ri < numRows; ri++) { for (int ci = 0; ci < numCols; ci++) { // Process icon (ri, ci) // Frames are: // [0] : unselected // [1, numFramesFocusIn -1] : focusIn anim // [numFramesFocus] : focused int curFrame = currentFrame[ri][ci]; if((cr == ri)&&(cc == ci)) { // We are processing the focused icon. // If we are below the //focused frame, just //increase the frame index if (curFrame < numFramesFocus) { // Move towards focused //state on the focusIn animation curFrame += 1; needUpdate = true; } else { // Do nothing, we are in //the right frame already. } } else { // We are _not_ on the //focused frame. if (curFrame > 0) { curFrame -= 1; needUpdate = true; } } currentFrame[ri][ci] = curFrame; } } if (needUpdate) { repaint(); serviceRepaints(); } end = System.currentTimeMillis(); sleep = frameLengthMs - (end - start); if (sleep < 10) { sleep = 10; } try { sleep(sleep); } catch (InterruptedException ie) { interrupted = true; } } } }; th.start(); 

    Each time the run method is executed, the current frame for the selected icon is checked. If the current frame is less than 8, animation of the selected icon is in progress. Accordingly, the value of current frame is incremented by one and arepaint is called. For unselected icons, the value of the current frame should be 0. If it is higher than 0, then the animation for deselection is in progress. In this case, the value of the current frame is decremented by one and arepaint is called. The thread then sleeps (approximately) for the time period specified as the required interval between two frames before reiterating through the loop.

    The Canvas class has a repaint()method just like the Canvas class in AWT. Unlike the AWT version, however, Canvas here also has aserviceRepaints() method that forces immediate execution of a pending request for repaint and blocks until thepaint() method returns.


    SVG is an extremely powerful platform, and that makes JSR 226 a powerful API. All features of this combination cannot be covered in one article; what I have tried to do here is provide a glimpse into some of the possibilities. The Sun Java Wireless Toolkit for CLDC 2.5 comes with a number of MIDlets showing the capabilities of JSR 226. We have looked at only three of them. A study of the other demos will give you an understanding of many interesting features like zooming, panning, and layering.


    The classes for "Static Pointer Demo" and "Moving Pointer Demo" have been modelled on the CreateEmptyImageDemo andSVGImageCanvas classes.