Click here to Skip to main content
6,595,854 members and growing! (18,784 online)
Email Password   helpLost your password?
Web Development » Silverlight » General     Intermediate License: The Code Project Open License (CPOL)

DeepZoom

By Sacha Barber

An article showing how to do DeepZoom in Silverlight 2.0
C# (C# 3.0), .NET (.NET 3.5), ASP.NET, Dev, Design
Version:2 (See All)
Posted:13 May 2008
Updated:24 Feb 2009
Views:34,836
Bookmarked:72 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
35 votes for this article.
Popularity: 7.46 Rating: 4.83 out of 5

1
2 votes, 5.7%
2
1 vote, 2.9%
3
1 vote, 2.9%
4
31 votes, 88.6%
5

Introduction

Since I wrote my first article on Silverlight (Silverlight 1.1 Fun and Games) which was based on the Silverlight 1.1 Alpha, things have a changed a bit, and now a Silverlight 2.0 Beta is available.

There have been some real improvements since the 1.1 Alpha, for example within the 1.1 Alpha there were no input/layout controls to speak of. Ok there was Canvas and that was about it. So I for one am quite pleased with the new controls that the Silverlight 2.0 Beta has available.

For example if I use Expression Blend Beta 2.5, to create a new Silverlight 2.0 Beta project, I get to use all of the following controls (believe it or not there never used to be Buttons in 1.1, you had to write those yourself...Ouch)

Now this is all cool, but I am still not that interested in writing another Silverlight article just to show these new controls. What does interest me (and has interested me) enough to write this article is the new MultiScaleImage which doesn't exist in the full WPF toolset.

A Bit Of History

The new MultiScaleImage is a strange thing. From what I can tell from my googling, this owes its existance to an old product called Sea Dragon, which Microsoft bought and rebranded DeepZoom.

What Does DeepZoom Do For Me?

As the name suggested the MultiScaleImage more than likely allows multiple scale images to be viewed in a single image container. This is the case. The DeepZoom (aka Sea Dragon) allows a composition to be made where the image (or more typically collage of images) have various different stages of information stored which allows the MultiScaleImage to work correctly. In order to create the required information there is a DeepZoom composer which aids in the design of DeepZoom compositions. If you want to see an online version have a look at Vertigos hardrock.com site

Prerequisites

Before we proceed, I'll just list what I think youll need to construct Silverlight 2.0 apps that use DeepZoom

Oh and Visual Studio 2008 is always required

Creating a DeepZoom Enabled Silverlight App

I am going to outline the steps that I followed in order to get DeepZoom working (I hope this helps, cos I had to jump through a few hoops)

Step 1 : Creating a DeepZoom Composition Project

The 1st step is to create a composer project, so once you have downloaded and installed the Deep Zoom Composer, simply create a new project, and import and compose some images.

The only thing that I had to do here was give the Export a name, and press the export button.

I called the export "composedoutput" so I got (oh and I didnt click create collection)

OutputSdi

  • composedoutput.sdi
  • composedoutput folder
    • Lots of subfolders
    • info.bin
    • info.xml
  • SparseImageSceneGraph.xml

Step 2 : Creating a Silverlight 2.0 App In Visual Studio 2008

I then started VS2008 and created a new Silverlight project

And chose to let VS2008 create a new web site for the Silverlight App.

I then cleaned up the web project a bit, to remove unwanted files. The ones below with lines through them I didnt need.

I then built the Silverlight app, which created a ClientBin directory in the web project.

Also of note is the single .xap file. This is actually a compiled Silverlight file (you can rename this .Zip and examine its contents), and it should contain the referenced Dlls, and the actual App Dll and a manifest file.

So far it's all cool. At this point you should be able to run the web app and see the Silverlight app embedded in the web page just fine.

Ok so now comes some code.

Step 3 : Adding DeepZoom To The Silverlight App

So we have a nice Silverlight app, but this article is about DeepZoom. So we need to make some changes to our Silverlight app to DeepZoom'ify it. Lets look at the XAML required to do this.

<UserControl x:Class="DeepZoomApp.Page"
    xmlns="http://schemas.microsoft.com/client/2007" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="800" Height="420">
    <Grid x:Name="LayoutRoot" Background="White">
        <MultiScaleImage x:Name="image" UseSprings="False" 
                         ViewportWidth="1.0"
                         Loaded="image_Loaded" 
                         MotionFinished="image_InitialMotionFinished" />
    </Grid>
</UserControl>

And here is the FULL code behind for this Silverlight page.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DeepZoomApp
{
    /// <summary>
    /// A very simple silverlight control demonstrating how to wire up mouse 
    /// interactions to a MultiScaleImage instance.
    /// </summary>
    public partial class Page : UserControl
    {
        /// <summary>
        /// Stores an instance of the wrapper class that handles mouse interactions 
        /// (panning, zooming, etc)
        /// </summary>
        private MultiScaleImageWrapper mouseInteractionWrapper;

        /// <summary>
        /// Indicates if the first motion has completed yet 
        /// (the initial zoom behavior is disabled)
        /// </summary>
        private bool isInitialMotionFinished;

        public Page()
        {
            InitializeComponent();

            // initialize the wrapper class to enable mouse interactions
            mouseInteractionWrapper = new MultiScaleImageWrapper(image);
        }

        /// <summary>
        /// Wires up an absolute Uri to the MultiScaleImage collection path 
        /// (for some reason, relative values are not currently supported). 
        /// This should make this example work, no matter what port is auto-assigned
        /// by Cassini.
        /// </summary>
        /// <param name="sender">The MultiScaleImage instance.</param>
        /// <param name="e">Unused RoutedEvent arguments.</param>
        private void image_Loaded(object sender, RoutedEventArgs e)
        {
            Uri collectionUri;

            if (Uri.TryCreate(App.Current.Host.Source, 
                "OutputSdi/composedoutput/info.bin", out collectionUri))
                image.Source = collectionUri;
        }

        /// <summary>
        /// Handles the "MotionFinished" event fired by the MultiScaleImage, 
        /// but only re-enables the "UseSprings" property after the first 
        /// motion completes (a little trick to properly bypass the initial "zoom in
        /// from nowhere" animation when first loading)
        /// </summary>
        /// <param name="sender">The MultiScaleImage instance.</param>
        /// <param name="e">Unused RoutedEvent arguments.</param>
        void image_InitialMotionFinished(object sender, RoutedEventArgs e)
        {
            if (!isInitialMotionFinished)
            {
                isInitialMotionFinished = true;
                image.UseSprings = true;
            }
        }
    }
}

The more eagle eyed amongst you may notice that there is a class used here called "MultiScaleImageWrapper". I can take no credit what so ever for this class. Thats all down to the most excellent folk at vertigo, who were kind enough to create a codeplex page with this class. This class allows the users to use the MouseScrollWheel in a browser to interact with the DeepZoom MultiScaleImage. This is done by hooking into the hosting element events. Lets have a look at the code shall we.

using System;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;

namespace DeepZoomApp
{
    /// <summary>
    /// Provides mouse panning and zooming support for a MultiScaleImage.
    /// </summary>
    public class MultiScaleImageWrapper
    {
        /// <summary>
        /// Specifies the zoom factor when the MultiScaleImage receives a mouse click event.
        /// </summary>
        private const double ZOOM_FACTOR_CLICK = 2;

        /// <summary>
        /// Specifies the zoom factor when the MultiScaleImage receives a mouse wheel event.
        /// </summary>
        private const double ZOOM_FACTOR_WHEEL = 1.25;

        /// <summary>
        /// Listens to mouse wheel events raised by the HTML document.
        /// </summary>
        private static MouseWheelListener mouseWheelListener = new MouseWheelListener();

        /// <summary>
        /// Indicates whether or not the mouse is over the MultiScaleImage.
        /// </summary>
        private bool isMouseOver;

        /// <summary>
        /// Indicates whether or not the left mouse button is pressed for the MultiScaleImage.
        /// </summary>
        private bool isMouseDown;

        /// <summary>
        /// Indicates whether or not the mouse is being dragged over the MultiScaleImage.
        /// </summary>
        private bool isMouseDrag;

        /// <summary>
        /// Specifies the initial mouse drag origin for the MultiScaleImage.
        /// </summary>
        private Point dragOrigin;

        /// <summary>
        /// Specifies the initial mouse drag position for the MultiScaleImage.
        /// </summary>
        private Point dragPosition;

        /// <summary>
        /// Specifies the current mouse cursor position for the MultiScaleImage.
        /// </summary>
        private Point cursorPosition;

        /// <summary>
        /// Gets the wrapped MultiScaleImage instance. 
        /// </summary>
        public MultiScaleImage Image { get; private set; }

        /// <summary>
        /// Initializes a new instance of the MultiScaleImageWrapper class to provide mouse panning and zooming support for the specified MultiScaleImage.
        /// </summary>
        /// <param name="image">The MultiScaleImage instance to wrap.</param>
        public MultiScaleImageWrapper(MultiScaleImage image)
        {
            Image = image;
            Image.MouseEnter += Image_MouseEnter;
            Image.MouseLeave += Image_MouseLeave;
            Image.MouseMove += Image_MouseMove;
            Image.MouseLeftButtonDown += Image_MouseLeftButtonDown;
            Image.MouseLeftButtonUp += Image_MouseLeftButtonUp;
            mouseWheelListener.MouseWheel += Image_MouseWheel;
        }

        /// <summary>
        /// Handles the MouseEnter event for the MultiScaleImage. Sets the isMouseOver flag to true and saves the currentPosition of the cursor.
        /// </summary>
        /// <param name="sender">The wrapped MultiScaleImage instance.</param>
        /// <param name="e">The mouse event arguments.</param>
        private void Image_MouseEnter(object sender, MouseEventArgs e)
        {
            isMouseOver = true;
            cursorPosition = e.GetPosition(Image);
        }

        /// <summary>
        /// Handles the MouseLeave event for the MultiScaleImage. Sets the isMouseOver flag to false.
        /// </summary>
        /// <param name="sender">The wrapped MultiScaleImage instance.</param>
        /// <param name="e">The mouse event arguments.</param>
        private void Image_MouseLeave(object sender, MouseEventArgs e)
        {
            isMouseOver = false;
        }

        /// <summary>
        /// Handles the MouseMove event for the MultiScaleImage. Saves the currentPosition of the cursor and pans when the mouse is dragged.
        /// </summary>
        /// <param name="sender">The wrapped MultiScaleImage instance.</param>
        /// <param name="e">The mouse event arguments.</param>
        private void Image_MouseMove(object sender, MouseEventArgs e)
        {
            cursorPosition = e.GetPosition(Image);

            if (isMouseDown)
            {
                isMouseDrag = true;

                Point origin = new Point();
                origin.X = dragOrigin.X - (cursorPosition.X - dragPosition.X) / Image.ActualWidth * Image.ViewportWidth;
                origin.Y = dragOrigin.Y - (cursorPosition.Y - dragPosition.Y) / Image.ActualWidth * Image.ViewportWidth;

                Image.ViewportOrigin = origin;
            }
        }

        /// <summary>
        /// Handles the MouseLeftButtonDown event for the MultiScaleImage. Saves the initial dragOrigin and dragPosition in case the user begins to pan.
        /// </summary>
        /// <param name="sender">The wrapped MultiScaleImage instance.</param>
        /// <param name="e">The mouse button event arguments.</param>
        private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Image.CaptureMouse();
            mouseWheelListener.IsEnabled = false;

            isMouseDown = true;
            isMouseDrag = false;

            dragOrigin = Image.ViewportOrigin;
            dragPosition = e.GetPosition(Image);
        }

        /// <summary>
        /// Handles the MouseLeftButtonUp event for the MultiScaleImage. Zooms in if the user is not completing a pan and resets the mouse.
        /// </summary>
        /// <param name="sender">The wrapped MultiScaleImage instance.</param>
        /// <param name="e">The mouse button event arguments.</param>
        private void Image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (!isMouseDrag && isMouseDown)
            {
                bool isShiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
                double factor = isShiftDown ? 1 / ZOOM_FACTOR_CLICK : ZOOM_FACTOR_CLICK;
                Zoom(factor, cursorPosition);
            }

            isMouseDown = false;
            isMouseDrag = false;

            Image.ReleaseMouseCapture();
            mouseWheelListener.IsEnabled = true;
        }

        /// <summary>
        /// Handles the MouseWheel event for the MultiScaleImage. Zooms in or out depending on the mouse wheel direction.
        /// </summary>
        /// <param name="sender">The MouseWheelWrapper instance.</param>
        /// <param name="e">The mouse wheel event arguments.</param>
        private void Image_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (!isMouseOver || e.Delta == 0)
                return;

            double factor = e.Delta > 0 ? ZOOM_FACTOR_WHEEL : 1 / ZOOM_FACTOR_WHEEL;
            Zoom(factor, cursorPosition);
            e.Handled = true;
        }

        /// <summary>
        /// Zooms in or out of the MultiScaleImage.
        /// </summary>
        /// <param name="factor">The zoom factor.</param>
        /// <param name="point">The zoom center point.</param>
        public void Zoom(double factor, Point point)
        {
            Point logicalPoint = Image.ElementToLogicalPoint(point);
            Image.ZoomAboutLogicalPoint(factor, logicalPoint.X, logicalPoint.Y);
        }

        /// <summary>
        /// Provides data for a mouse wheel event.
        /// </summary>
        private class MouseWheelEventArgs : EventArgs
        {
            /// <summary>
            /// Gets a value that specifies the delta and direction of the mouse wheel event. This value is normalized across browsers.
            /// </summary>
            public double Delta { get; private set; }

            /// <summary>
            /// Gets or sets a value that indicates whether or not the mouse wheel event has been handled.
            /// </summary>
            public bool Handled { get; set; }

            /// <summary>
            /// Initializes a new instance of the MouseWheelEventArgs class with the specified delta.
            /// </summary>
            /// <param name="delta">The delta calculated for the mouse wheel event.</param>
            public MouseWheelEventArgs(double delta)
            {
                Delta = delta;
            }
        }

        /// <summary>
        /// Provides cross-browser support for the mouse wheel.
        /// </summary>
        private class MouseWheelListener
        {
            /// <summary>
            /// Indicates whether or not the mouse wheel is enabled.
            /// </summary>
            public bool IsEnabled { get; set; }

            /// <summary>
            /// Occurs when a mouse wheel event is fired.
            /// </summary>
            public event EventHandler<MouseWheelEventArgs> MouseWheel;

            /// <summary>
            /// Initializes a new instance of the MouseWheelListener class that listens to mouse wheel events fired by the HTML document. 
            /// </summary>
            public MouseWheelListener()
            {
                if (HtmlPage.IsEnabled)
                {
                    HtmlPage.Document.AttachEvent("DOMMouseScroll", Plugin_MouseWheelFirefox);
                    HtmlPage.Document.AttachEvent("onmousewheel", Plugin_MouseWheelOther);
                    IsEnabled = true;
                }
            }

            /// <summary>
            /// Handles mouse wheel events for Firefox.
            /// </summary>
            /// <param name="sender">The HTML element for the plug-in.</param>
            /// <param name="e">The HTML event arguments.</param>
            private void Plugin_MouseWheelFirefox(object sender, HtmlEventArgs e)
            {
                if (!IsEnabled)
                {
                    e.PreventDefault();
                    return;
                }

                double delta = (double)e.EventObject.GetProperty("detail") / -3;
                MouseWheelEventArgs args = new MouseWheelEventArgs(delta);
                MouseWheel(this, args);

                if (args.Handled)
                    e.PreventDefault();
            }

            /// <summary>
            /// Handles mouse wheel events for browsers other than Firefox.
            /// </summary>
            /// <param name="sender">The HTML element for the plug-in.</param>
            /// <param name="e">The HTML event arguments.</param>
            private void Plugin_MouseWheelOther(object sender, HtmlEventArgs e)
            {
                if (!IsEnabled)
                {
                    e.EventObject.SetProperty("returnValue", false);
                    return;
                }

                double delta = (double)e.EventObject.GetProperty("wheelDelta") / 120;
                MouseWheelEventArgs args = new MouseWheelEventArgs(delta);
                MouseWheel(this, args);

                if (args.Handled)
                    e.EventObject.SetProperty("returnValue", false);
            }
        }
    }
}

Step 4 : Copying The DeepZoom Composer Output To The Web Site

I then copied the folder that contained the following files

  • composedoutput.sdi
  • composedoutput folder
    • Lots of subfolders
    • info.bin
    • info.xml
  • SparseImageSceneGraph.xml

So for me that was the whole OutputSdi folder, so my ClientBin folder now looked like this

Step 5: Changing Silverlight MultiScaleImage Uri

The very last step was to change the place the MultiScaleImage looked for its source. This is done by the line within the MultiScaleImage loaded event

if (Uri.TryCreate(App.Current.Host.Source, 
    "OutputSdi/composedoutput/info.bin", out collectionUri))
    image.Source = collectionUri;

And thats it. Job done.

History

v1.0 13/05/08

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sacha Barber


Member
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Occupation: Software Developer (Senior)
Location: United Kingdom United Kingdom

Other popular Silverlight articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 25 of 35 (Total in Forum: 35) (Refresh)FirstPrevNext
GeneralI have some errors Pinmemberrahul_shukla21:32 13 Apr '09  
Questionimage.Source PinmemberAlan Hastings7:46 19 Mar '09  
AnswerRe: image.Source PinmvpSacha Barber8:18 19 Mar '09  
GeneralRe: image.Source PinmemberAlan Hastings0:15 20 Mar '09  
GeneralRe: image.Source PinmvpSacha Barber1:24 20 Mar '09  
QuestionCannot download the source file Pinmembernmq17:59 23 Feb '09  
AnswerRe: Cannot download the source file PinmvpSacha Barber22:41 23 Feb '09  
AnswerRe: Cannot download the source file PinmvpSacha Barber10:29 24 Feb '09  
GeneralRe: Cannot download the source file Pinmembernmq21:42 25 Feb '09  
GeneralRe: Cannot download the source file PinmvpSacha Barber22:38 25 Feb '09  
Generalimage.Source = collectionUri; Pinmemberdefineconst16:55 16 Feb '09  
GeneralThank you PinmemberIndy20051:39 19 Jun '08  
GeneralRe: Thank you PinmvpSacha Barber1:59 19 Jun '08  
GeneralThis isn't anything I'm likely to use yet... PinmvpPete O'Hanlon11:33 26 May '08  
GeneralRe: This isn't anything I'm likely to use yet... PinmvpSacha Barber23:24 26 May '08  
GeneralRe: This isn't anything I'm likely to use yet... PinmvpPete O'Hanlon11:05 27 May '08  
GeneralRe: This isn't anything I'm likely to use yet... PinmvpSacha Barber21:53 27 May '08  
GeneralCool :) PinmemberDZaK8320:49 19 May '08  
GeneralRe: Cool :) PinmvpSacha Barber21:13 19 May '08  
GeneralGreate sample PinmemberValentin Billotte23:56 16 May '08  
GeneralRe: Greate sample PinmvpSacha Barber1:02 17 May '08  
General[Message Removed] PinmemberMojtaba Vali0:44 14 May '08  
GeneralRe: Blackboard system PinmvpSacha Barber1:13 14 May '08  
GeneralNice PinmemberApocwhen22:49 13 May '08  
GeneralRe: Nice [modified] PinmvpSacha Barber22:54 13 May '08  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 24 Feb 2009
Editor:
Copyright 2008 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2009
Web09 | Advertise on the Code Project