Pages

Monday, July 20, 2009

Loading SVG from a Silverlight Application

The problem:
I want to use SVG files as the underlying graphics format for my Silverlight game.

Why?

First why do I want to use vector graphics for all my game graphics assets? Since vector graphics are resolution independent this allows me to create the content once and scale it to the required resolution depending on the target platform without any loss of quality(which is not the case with bitmap image formats like PNG). The same graphic asset can then be used today on the web, tomorrow on some lower resoluton mobile device and later on a full screen view on a HDTV.

What is SVG? In short, SVG is an open file format that describes a vector graphics image. There are free and excellent tools like Inkscape that lets us create images in the SVG format.

What is XAML? XAML is the basic application markup language for Silverlight and is also very good at describing vector graphics. Then why don't I just use XAML for my vector graphics? The main reason is that I want to keep the original format of my game assets to be portable to other platforms.

So thats the problem in short - I want to be able to create my game graphics in Inkscape as SVG and load it into my Silverlight Application.

The solutions that didn't work
The solution path is obvious - Convert SVG to XAML which Silverlight understands and load it into the Silverlight application. Either I can do the conversion during build time or do it dynamically at runtime. The solution which can load at runtime would get extra bonus points.

So I looked at various existing conversion tools for converting from SVG to XAML.

Here is a list of the ones that I tried and rejected.

1) Inkscape XAML exporter - The current version on Windows has known bug and crashes. Also it is difficult to integrate this with the build process.
2) The commercial ViwerSVG - Allows to dynamically load SVG into a WPF application(Silverlight also I would assume). I gave it a quick spin and not very happy with the conversions. And the price tag was too heavy.
3) Next I tried the excellent XamlTune. The conversions from SVG to XAML were great and it had a command line utility which I could use as part of the build process. I saw the live version of XamlTune and was even more excited. The only problem was that the output from XamlTune works with WPF but is not exactly friendly towards the subset of XAML supported by Silverlight. I needed to find a better solution.

THE REAL SOLUTION : SVG2XAML.XSL
Enter svg2xaml.xsl. This is a XSL transform written originally by one Toine de Greef. It basically provides the rules to convert a SVG file to a XAML file and it supports Silverlight XAML! As it turns out Inkscape uses this same XSL to actually do the XAML export So I got the XSL from Inkscape since I thought it would have some extra bug fixes to it.

So now I could use a command line XSL processor like msxsl.exe and use SVG2XAML.xsl to convert SVG to XAML and it worked really well with the sample set of SVG files I had.

Now only the bonus question remained. Can I use svg2xaml.xsl to dynamically load SVG into my silverlight application? I had seen the live version of XamlTune. It seems to be using server processing for converting SVG to XAML. But that seems to be a waste of server resources. If I wrote a silverlight game I would like to avoid using sever resources since it costs money for the server CPU and bandwidth.

Can I do it entirely on the client side? As it turned out I can! Eventhough Silverlight itself doesn't support doing XSL Transforms it can call into javascript code which can do the XSL transform and return back the transformed XML.

Using client-side Javascript to convert SVG to XAML
The following is the Javascript code I used to do the XSL transforms from SVG to XAML. The script loads svg2xaml.xsl which in turn refers to colors.xml. I made some extra fixes to svg2xaml which allowed me to succesfully convert few more SVG files succesfully - You can get the updated svg2xaml.xsl and colors.xml.

You can use the following Javascript code in the page that loads the silverlight control.

function loadXMLDoc(xml, isFileName) {

var xmlDoc = null;

// code for IE
if (window.ActiveXObject) {
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;

if (isFileName) {
xmlDoc.load(xml);
}
else {
xmlDoc.loadXML(xml);
}
}
// code for Mozilla, Firefox, Opera, etc.
else if (document.implementation && document.implementation.createDocument) {

if (isFileName) {
xmlDoc = document.implementation.createDocument("", "", null);
xmlDoc.async = false;
xmlDoc.load(xml);
}
else {
parser = new DOMParser();
xmlDoc = parser.parseFromString(xml, "text/xml");
}
}
else {
return null;
}

return (xmlDoc);
}

function svg2xaml(svgString) {
var xml = loadXMLDoc(svgString, false);
var xsl = loadXMLDoc("svg2xaml.xsl", true);

if ((xml != null) && (xsl != null)) {

// code for IE
if (window.ActiveXObject) {
result = xml.transformNode(xsl);
return result;
}
// code for Mozilla, Firefox, Opera, etc.
else {
xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsl);
resultDocument = xsltProcessor.transformToDocument(xml)

var serializer = new XMLSerializer();
var xaml = serializer.serializeToString(resultDocument.documentElement);

return xaml;
}

}
else {
return null;
}
}


Then you can call this javascript function from your silverlight code to convert the SVG file to XAML which can then be loaded in the silverlight application. The following code prompts the user to select a SVG file on the local computer's file system and converts into XAML by calling into the javascript code using HtmlPage.Window.Invoke. It then displays it so that it fits the current silverlight control dimensions. "mapCanvas" in the code below is the just the main Canvas object that displays the loaded SVG image.

...
using System.IO;
using System.Windows.Browser;
using System.Xml;
using System.Windows.Markup;
...


OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Filter = "Graphics files (*.svg)|*.svg";

if (fileDialog.ShowDialog() == true)
{
string svg = null;
using (StreamReader reader = fileDialog.File.OpenText())
{
// Store file content in 'text' variable
svg = reader.ReadToEnd();
}

try
{
// Call the Javascript XSLT processor to convert SVG to XAML
Object returnVal = HtmlPage.Window.Invoke("svg2xaml", svg);
if (returnVal != null)
{
string xaml = (string)returnVal;

Canvas canvas = (Canvas)XamlReader.Load(xaml);

double scale = Math.Min(640.0 / canvas.ActualWidth, 480.0 / canvas.ActualHeight);

ScaleTransform scaleTransform = new ScaleTransform();
scaleTransform.ScaleX = scaleTransform.ScaleY = scale;
canvas.RenderTransform = scaleTransform;
canvas.RenderTransformOrigin = new Point(0, 0);

canvas.SetValue(Canvas.LeftProperty, (mapCanvas.ActualWidth - (canvas.ActualWidth * scale)) / 2);
canvas.SetValue(Canvas.TopProperty, (mapCanvas.ActualHeight - (canvas.ActualHeight * scale)) / 2);

mapCanvas.Children.Clear();
mapCanvas.Children.Add(canvas);
}
}
catch (Exception ex)
{
mapCanvas.Children.Clear();
TextBlock text = new TextBlock();
text.Text = ex.Message;

mapCanvas.Children.Add(text);
}
}


Demo:
You want to see something cool? Here is the Live version of the silverlight application served via Silverlight streaming. You can download the following test SVG files locally and then load them into the live application by clicking on "Load Graphics" and selecting the local SVG file (Wikipedia has tonnes of public SVG graphics).




Caveats
The XSL transform is not perfect. Some SVG files might error out during conversion. It works well for most SVG files created with Inkscape which stick to mostly Paths and use linear or radial gradients.

Also, even though the above javascript code tries to be cross-browser it does not work very well outside IE and FireFox. IE works the best with this code. Even Firefox doesn't work completely because the XSL transform applied is slightly different than IE. The XSL transform behavior is so different in Opera that many of the sample SVGs above don't even work. Safari didn't even come to the transformation part of the javascript.

Please consider this as a proof of concept. I will be posting updates to the SVG2XAML XSL and the Javascript code as I figure out how to fix these problems.

Final Thoughts
So there we have it. The ability to load SVG graphic files from a Silverlight application and that too dynamically at runtime. What use is it to load the SVG file dynamically at runtime by doing conversions in the client side? Think Live Game Map Editors...

No comments:

Post a Comment