One Tweet by Oliver Sturm (http://twitter.com/olivers) the other day got me to thinking. He was asking about the possibility to host graphics for a web site in XAML format, and the best way to render it.
Currently, there are 3 possible ways that I know of to render XAML in a web browser
- Loading the XAML file directly in an IFRAME will trigger Windows Presentation Foundation on the client (if available). The IFRAME can be positioned in the page so the it "blends" with the HTML page. That's only for Windows (IE and Firefox) and requires .NET 3.0 (or preferably 3.5/3.5 SP1) installed on the client. I wouldn't recommend that solution.
- Silverlight can load loose XAML files. However, Silverlight XAML is less powerful than WPF XAML. Not all controls and features are supported. Also, it requires a plug-in to be installed on the client. The plug-in is available for a number of platforms, but still. More lightweight than solution 1, but still not ideal to render static images.
- Rendering the XAML on the web server. This solution has the advantage to require installation of the .NET framework only on the web server (a controlled environment). WPF can render in memory, which is very convenient. No need for a visual, you can just load the XAML in a stream, and render as a bitmap image it in a memory stream, which in turn can be passed to the Response. Multiple bitmap formats are supported. In this example, we will use PNG.
I really want to write a more in-depth article about that, but for now I already post source code demonstrating 3 possible uses for this technique. To install the demo, follow the steps below.
Note: I am having some issues installing this on my web server, and trying to clear things up now. It works fine locally however.
- Download the source code (Zip)
- Unzip to a folder on your PC.
- Open the Solution file in Visual Studio 2008. Note: This is targetting .NET 3.0.
- Make sure that the web application "GalaSoft.Web.ConvertXamlToPng" is set as startup project.
- Make sure that the file "index.html" is set as StartUp page.
- Run the application by pressing Ctrl-F5.
The demo has 3 parts:
- Localizing an image: In this sample, some strings are fetched from the resources (using a standard .NET resource manager) and set in the XAML scene after it has been loaded in memory. This solves a recurrent problem for web site managers: How can we design pictures in various languages without having to create (and store) one picture per supported language. In this demo, the same XAML file is localized in 3 different languages, and rendered to PNG.
- Scaling an image: WPF and XAML are great for flow layout and element composition. Using tools like Expression Design and Expression Blend, it is easy to create compelling graphics that resize neatly. The second sample demonstrates this and allows the use to specify any size for an image. The XAML is loaded and rendered to that size directly on the server. Note that if you choose a very large size (thousands of pixels), the image appears pixellated. I am not sure yet what is the reason for it.
- Customizing an image. In this last sample, you can parameterize multiple elements of the XAML scene. It's all a matter of finding the right element after the XAML has been loaded (FindName helps), finding the right DependencyPropertyand reflection are needed) and then setting the right object.
Just a word on the technology
One thing that WPF requires when it is being rendered (visually or in memory) is a STA (single threaded apartment). However, ASP.NET pages are typically rendered in MTA (multi threaded appatment). To solve this, you can either force an ASPX web page to run in STA (by using the <%@ ASPCOMPAT="true" %>directive in the Page) but that will degrade the performances. A much better way is to create a worker thread and to render the XAML in this thread.
One problem with that is that WPF is very particular with threads: To put it simply, a visual element created in a thread may not be used in another thread. We run into that problem when we parameterize the SolidColorBrush used by the last sample. In that sample, the replacement brush is created on the main thread, but used in the worker thread which creates an Exception. To avoid this issue, we "freeze" the SolidColorBrush after it is created. Frozen objects cannot be modified anymore, but they can be shared between threads.
Some things are still missing to this demo. I really would like to get it to work on my web server, but I am getting some issues. My understanding so far is that some assemblies are missing on the web server, which creates exceptions.
Another thing I would love to support is zoomability. Since XAML elements are vector graphics, and since ScaleTransform is supported, it is rather easy to add zoomability to an image. I experimented with that already and with good results, but it's not ripe for distribution yet. The problem here is that dimensions get all kind of wacky when you zoom an image (a 500 pixels wide image zoomed with a factor of 200% is suddenly 1000 pixels wide) and I am still looking for the best way to implement this in a generic way.
The XamlToPngConverter class
This is the main component used for the conversion. I won't write too much about it now, but it's implemented in a generic way, so that XAML can be loaded from any stream, and rendered to any stream. This allows for saving static images to a file, for example, rendering to a web response like here, etc... More details will follow.
I hope you like this demo and that it is useful. While it requires .NET 3.0 on the web server (and while it's not as straightforward on remote web servers as I was hoping for), it's a useful technique that I will be playing with more in the close future.
| posted on Friday, October 10, 2008 12:48 AM