Silverlight Deep Zoom Sample Code

johnWeeGo.jpgAfter seeing the amazing demo at Mix08 this week I decided I needed to play with this. Checkout my collection of photos from our trip to Thailand last year: http://thailand.soulclients.com

step6.jpg

So what do you need to do to make this?

Visual Studio 2008

Silverlight2 Tools for vs2008

Deep Zoom Composer

Essentially I put together a bunch of code from other people’s posts and the Mix08 code and fixed a few bugs. Thanks to Mike, Yasser and Mike for their posts.

Step 1, make the tiles from your set of images.

step1.jpg

This guide is great. Essentially you import your uber resolutions images (Windows Live Photo Gallery stitches awesome panoramas btw) and lay them out how you want it to be displayed. Tip, you can draw a box around a group of images, shift click the primary image (light blue border) and then use the tools to align, match size, shrink and grow and evenly space.

Finally your export either as a single image or as a collection. The later opens up some interesting options I will explore later as each image is exported separately.

When complete open the export folder as you need to copy this into your project.

Step 2, create a new silverlight2 application in vs2008.

Create a new project, under “Silverlight” you will find a “Siverlight Application” template. On the next screen I opted for web project to host the control but you could just use a html page.

step2.jpg

Scott Guthrie explains more here.

Step 3, drop a MultiScaleImage on the canvas, set path to your exported files

The control to use is the “MultiScaleImage”. Drop one onto the page.xaml inside the default grid. You need to copy the output from step 1 into a folder in the “clientBin” of the web site. We need to supply this path to the control:


<MULTISCALEIMAGE Source="thai2007/items.bin" x:Name="deepZoomObject" />

If you run this up you get the image but no interactivity.

Step 4, wire up some events

This is pretty much the point where all the examples out there differ. I want to be able to use several key combinations, the mouse to pan and zoom, be nice a smooth and not break if you go off the application.

I choose to use the following:

  • up,down,left,right to pan, i (in) and o (out) to zoom. Plus and minus are not listed – help me out someone?
  • AWSD to pan, c and space to zoom.
  • Mouse click and drag to pan
  • Mouse wheel to zoom in and out on the current mouse position

First up the key events and mouse leave are on the control while the other mouse events are on the MultiScaleImage. I also removed the height and width so the control will fill the whole page:


<UserControl x:Class="SoulSolutions.DeepZoom.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    KeyDown="UserControl_KeyDown" MouseLeave="UserControl_MouseLeave">
    <Grid x:Name="LayoutRoot" Background="Black">
        <MultiScaleImage x:Name="deepZoomObject" Source="thai2007/items.bin" 
        MouseLeftButtonDown="deepZoomObject_MouseLeftButtonDown" 
        MouseLeftButtonUp="deepZoomObject_MouseLeftButtonUp" 
        MouseMove="deepZoomObject_MouseMove"/>
    </Grid>
</UserControl>

In the code behind – page.xaml.cs we can hookup the events like so:
Key press:


private void UserControl_KeyDown(object sender, KeyEventArgs e)
{
    Point p = deepZoomObject.ElementToLogicalPoint(new Point((deepZoomObject.Width / 2),
 	((deepZoomObject.Width / deepZoomObject.AspectRatio) / 2))); 
    switch (e.Key)
    {
        case Key.I:
        case Key.C:
        case Key.Add:
            deepZoomObject.ZoomAboutLogicalPoint(1.1, p.X, p.Y);
            break;
        case Key.O:
        case Key.Space:
        case Key.Subtract:
            deepZoomObject.ZoomAboutLogicalPoint(0.9, p.X, p.Y);
            break;
        case Key.Left:
        case Key.A:
            deepZoomObject.ViewportOrigin = 
                new Point(deepZoomObject.ViewportOrigin.X - 0.1,
                deepZoomObject.ViewportOrigin.Y);
            break;
        case Key.Right:
        case Key.D:
            deepZoomObject.ViewportOrigin = 
                new Point(deepZoomObject.ViewportOrigin.X + 0.1,
                deepZoomObject.ViewportOrigin.Y);
            break;
        case Key.Up:
        case Key.W:
            deepZoomObject.ViewportOrigin = new Point(deepZoomObject.ViewportOrigin.X,
              deepZoomObject.ViewportOrigin.Y - 0.1);
            break;
        case Key.Down:
        case Key.S:
            deepZoomObject.ViewportOrigin = new Point(deepZoomObject.ViewportOrigin.X,
              deepZoomObject.ViewportOrigin.Y + 0.1);
            break;
        default:
            break;
    } 
} 

Mouse drag and drop:


bool dragInProgress = false;
Point dragOffset;
Point currentPosition;

private void deepZoomObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    dragInProgress = true;
    dragOffset = e.GetPosition(this);
    currentPosition = deepZoomObject.ViewportOrigin;
} 

private void deepZoomObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    dragInProgress = false;
} 

private void deepZoomObject_MouseMove(object sender, MouseEventArgs e)
{
    if (dragInProgress)
    {
        Point newOrigin = new Point();
        newOrigin.X = currentPosition.X - 
            (((e.GetPosition(deepZoomObject).X - dragOffset.X) 
		    / deepZoomObject.ActualWidth) * deepZoomObject.ViewportWidth);
        newOrigin.Y = currentPosition.Y - 
            (((e.GetPosition(deepZoomObject).Y - dragOffset.Y) 
		    / deepZoomObject.ActualHeight) * deepZoomObject.ViewportWidth);
        deepZoomObject.ViewportOrigin = newOrigin;
    }
} 

Just ensure we stop our drag and drop when you leave the application otherwise you get that nasty effect of it panning all all mouse movements.


private void UserControl_MouseLeave(object sender, MouseEventArgs e)
{
    dragInProgress = false;
}

Step 5, embed some JavaScript for mouse wheel event

Unfortunately the mouse wheel is not exposed to us so we have to work around this by capturing the event in JavaScript and communicating this back to the control. Mike has a helper class and the necessary javascript, so we simply include the scrollhelper.cs class and the scroller.js file. You must go to the properties of the javascript file and set “Build Action” =  “Embedded Resource”.

Then we only need to attach to the event and Zoom about the point of the mouse cursor:


public Page()
{
    InitializeComponent();
    ScrollHelper scroller = new ScrollHelper();
    scroller.ScrollChanged += scroller_ScrollChanged;
} 

void scroller_ScrollChanged(object sender, ScrollEventArgs e)
{
    Point logicalPoint = deepZoomObject.ElementToLogicalPoint(new Point(e.X, e.Y));
    if (e.ScrollDelta < 0)
    {
        deepZoomObject.ZoomAboutLogicalPoint(.66, logicalPoint.X, logicalPoint.Y);
    }
    else
    {
        deepZoomObject.ZoomAboutLogicalPoint(1.33, logicalPoint.X, logicalPoint.Y);
    }
}

Step 6, Deploy

http://thailand.soulclients.com/

step6.jpg

So you can run this and check that it all works but now you really want to put this on the web and share it. Well its pretty simple if you just want the whole page, essentially the web project can simply be copied to your server to run. Your other option is to now use the control in an existing page.

In order to run a Silverlight 2 application on your web server you must set the mime type for ".xap" to "application/x-silverlight".

As this is a .net 3.5 project I also had to install the .net3.5 framework on my server.

You can download the source code, it only missing the images from the deep zoom composer here:

http://thailand.soulclients.com/SoulSolutions.DeepZoom.zip (12KB)