Sunday, February 24, 2013

SVG on Android

In my latest app I had a chance to use SVG images. I thought it might be useful for others to learn, how it can be done and what the main advantages/drawbacks of this approach are. Here's the whole story.


Motivation

I wanted to display a map of subway and suburban connections in Milan and let users zoom in and turn off some details they didn't need. However, the map image I was using was too big and I kept getting an OutOfMemoryError. I made the image smaller, but then the details became blurry and it looked bad when magnified. I could just settle on a compromise and load the biggest image that would fit into memory, but it wouldn't look perfect anyway and there would be a possibility, that some other data would cause an OutOfMemoryError. That's why I decided to give vector images a try.

What is SVG

Citing Wikipedia: "Scalable Vector Graphics (SVG) is an XML-based vector image format for two-dimensional graphics". It basically means, that images are not described by their pixels, but rather by shapes and objects they contain. This description is then saved as an XML file.
So, for example, instead of having:
pixel0: #ffa4c0, pixel1: #d49506, pixel2: #94ac3d, pixel3: #84850b, ..., pixel1000000: #de3a21
we might have:
line from x: 30, y: 20 to x:435, y: 847, color: #e93847, width: 4px
Of course it's not possible (or easily done), to transform all images into SVG. How would you describe a photograph by means of simple shapes? However, when creating logos, icons, buttons, backgrounds, maps or UI elements for your app you may choose to save them as SVG images. This way, they'll take up less space and remain infinitely scalable. If you don't believe that you can make them look as good, see the Inkspace's showcase.


Tools and Libraries

When you try to find some information about using SVG with Android, this post on Stack Overflow comes up, saying that only on Android's 3+ default browser can render this format. There is no native support for SVG images in apps though. That's not good news, but what do we have external libraries for?

After some searching, I managed to find svg-android library, but then it turned out, that it was last modified in May 2011 and therefore is slightly outdated. Fortunately, there's also a fork on github by Josef Pavlik fixing some bugs and adding extra features to it. The library only supports SVG Basic 1.1 specification, but it should be enough for most cases. You can see a sample image it can render on the left. Some more samples can be found here.

To make SVG images scalable, I used TouchImageView. I had to fix some bugs in it and add orientation change support, but otherwise, it did its job.

To create SVG images I first used Inkspace - a free open source graphic editor. I had some problems exporting files as SVG Basic 1.1 though, so I switched to Adobe Illustrator. You can download a 30-day trial version or buy one here.

Source Code

You might be surprised, how easy it is to use SVG images. To create a Drawable from an SVG file, all you need to do is:

SVG svg = SVGParser.getSVGFromResource(getResources(), R.raw.filename);
Drawable svgDrawable = svg.createPictureDrawable();
Then, you simply use it as a regular image:

ImageView myImageView = (ImageView) findViewById(R.id.myImageView);
imageView.setImageDrawable(svgDrawable);

Summary

SGV images are a good replacement for bitmaps in many cases. Although they are not supported natively by Android, they can be easily displayed by means of third party libraries. They take up much less space (especially the zipped version - svgz), scale indefinitely without losing any detail and don't require a separate version of an image for each screen density.

So why not use them in all cases? First of all, not all images can be transformed into SVG. As I said earlier, photographs or sophisticated images with shadows, transparency, gradients and reflections are much harder to represent in a vector format, because they are not just simple shapes.
Second, I noticed that large SVG images with thousands of objects (like the ones I'm using) take some time to load. For example, my map is stored in a 200kB svgz file and it needs a little more that a second to become ready for display. I have to load 4 versions of this map, so you can imagine, that it was taking so much time, I had to add a splash screen to my app, so users don't see a blank screen for 5 seconds or so. This might be a little extreme example, because you don't usually need so many objects in your image, but it's good to be aware of it. Regular bitmaps don't load longer if they contain more colors or more shapes, because it's the pixels that matter.
Finally, after publishing my subway map, I noticed an occurance of a weird exception:

java.lang.UnsupportedOperationException
  at android.view.GLES20Canvas.drawPicture(GLES20Canvas.java:911)
  at android.graphics.drawable.PictureDrawable.draw(PictureDrawable.java:73)
  at android.widget.ImageView.onDraw(ImageView.java:910)

After some digging on Google I found out that Canvas's drawPicture function is not supported while hardware acceleration is on. I certainly hadn't turned hardware acceleration on, so I kept searching and found out that on some versions of Android there's a bug which causes hardware acceleration to turn on by itself. I didn't have access to a device with Android 3.0 or one with hardware acceleration, so I couldn't reproduce this issue. All I managed to do was adding:

    android:hardwareAccelerated="false"
to my application manifest, just to make sure hardware acceleration was off. The exception has not reoccured since then, but it might as well be a coincidence...

All in all, SVG images turned out to be really useful, but it's definitely worth testing your app on multiple devices, to make sure everything works fine and doesn't produce any unexpected exceptions.


UPDATE
I just wrote another post on SVG images and using them in your apps without the need of any external libraries or code. Check it out:
http://androiddreamrevised.blogspot.it/2014/06/transforming-svg-images-into-android.html

10 comments:

  1. What about if someone want to create SVG Editor in android? Is there any library available to manage path, line attributes and svg file?

    ReplyDelete
    Replies
    1. I've never come across such a library. SVG is XML based though, so you can save vector images simply by transforming each shape into on adequate XML tag yourself. You can probably find people with more experience in this matter if you ask a question on stackoverflow.com. Good luck! If you find something interesting, feel free to share it here with other readers here.

      Delete
  2. Oh, thank you for this! I've spent two days trying to figure out why my SVG images stopped being displayed.

    ReplyDelete
  3. I'm not getting anything on display. It shows empty.

    ReplyDelete
    Replies
    1. There's a bug in the Android OS.

      Check out this StackOverflow thread:
      http://stackoverflow.com/questions/13987288/svg-image-not-visible-in-android-3-0-with-same-code

      In short, calling setLayerType on your ImageView like this should help:
      imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

      Delete
  4. Is it possible to locate any svg file in different location and display in imageView? I read about svg and I notice that you can only use SVG file inside your project area.

    ReplyDelete
    Replies
    1. You can use SVG images located in any place on the phone, on the SD card as well. Take a look at the AndroidSVG library and the SVG's fromInputStream() and fromString() methods:
      https://bigbadaboom.github.io/androidsvg/doc/reference/com/caverock/androidsvg/SVG.html
      Also, with Glide you can easily load images from the Internet.

      Delete