Scott Hanselman

The Weekly Source Code 18 - Deep Zoom (Seadragon) Silverlight 2 MultiScaleImage Mouse Wheel Zooming and Panning Edition

March 7, '08 Comments [25] Posted in Mix | Silverlight | Source Code
Sponsored By

Silverlight Project Test Page - Windows Internet Explorer (4)Dear Reader, I present to you eighteenth in a infinite number of posts of "The Weekly Source Code." Here's some source I was reading - and writing - this week at Mix.

I have been checking out Deep Zoom in Silverlight 2, but I thought it was a bummer that there wasn't (yet) a "Hello DeepZoom World!" example that includes panning, zooming (mouse wheel) support out of the box. The Vertigo example behaves exactly as I'd want my stuff to behave. You can see my working Deep Zoom example here or by clicking the picture at right.

Adding Mouse Wheel support to Silverlight can happen a couple of ways. Mouse Wheel events are sourced by the browser, not by Silverlight itself (which is the way you'd want it as Silverlight lives inside the browser, it shouldn't replace its behaviors, IMHO).

So, you could use the Javascript code from Adomas along with the work that Jeff Prosise did to reach into Silverlight and call methods. The events would be handled in JavaScript and the Zoom method would be called via JavaScript over the bridge into Silverlight managed code.

However, you can also reach out from inside managed code and set managed handlers for DOM (JavaScript events) like Pete Blois does with his Mouse Wheel Helper class. I use this class directly by downloading it from Pete's blog and adding it to my project. This is significant because it doesn't require ANY external JavaScript files. All the events are handled by managed code.

if (HtmlPage.IsEnabled) {
  HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel);
  HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel);
  HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel);
}

I took this along with snippets from Yasser Makram and John posting in Yasser's blog's comments got me mostly what I needed.

I've seen some basic examples using either mouse clicking or key-downs to get the zooming effect, but I wanted to support mouse wheel events as well, just like the stuff shown off at Mix.

This more complete example gives you:

  • Drag to pan
  • Click to zoom in, Shift Click to zoom out
  • Mouse wheel to zoom in and out
  • No JavaScript dependency at all - everything is in managed code.

First, start with the output of the DeepZoom composer (I just used the Windows Wallpapers I had on this machine to compose a DeepZoom image in the editor) and copy the resulting exported folder structure somewhere (I put it under bin/debug for ease, but you can put it wherever as long as the source attribute lines up in your XAML:

<UserControl
	xmlns="http://schemas.microsoft.com/client/2007"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	x:Class="SilverlightApplication1.Page"
	Width="800" Height="600" >
  <Grid
    x:Name="LayoutRoot"
    Background="Gray">
    <MultiScaleImage 
      x:Name="msi"
      ViewportWidth="1.0"
      Source="http://www.yourdomain.com/foo/items.bin" />
  </Grid>
</UserControl>

RANDOM NOTE: Here's a cool switch you can set on MultiScaleImage. It's UseSprings="false" and it'll turn off the zooming animation. Why would you want to do this?  Well, that very zoom-in/zoom-out animation gives DeepZoom an opportunity to perform its "visual slight of hand" and transition between images. When the animation happens, you are less likely to notice the transition between tiles (a good thing). Of course, I want to get my head around how it all works so I liked seeing the transitions.

Keep in mind it's four in the morning, so this code is a little wonky and not at all thought-out and I've only been at it for an hour. I'm REALLY interested in what you, Dear Reader, can do with it and make it better so we all have a canonical example to start from. This is NOT that example, I'm actually kind of reticent to post it here because it's so hacked together, but that's half the fun, right? It works OK, I think.

One thing to point out, note that the name of the control is "msi," set in the XAML above via x:name="msi" so you'll see me referencing properties like msi.thisandthat in the managed XAML code-behind below. Also, the "using akadia" below is Pete's MouseHandler code's namespace referenced from my page.

My code hooks up a bunch of events from the constructor using anonymous delegates, and those work together to call a single Zoom() helper method.

using System; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Documents; 
using System.Windows.Ink; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Animation; 
using System.Windows.Shapes; 
using System.Windows.Threading; 
using akadia; 

namespace SilverlightApplication1 
{ 
    public partial class Page : UserControl 
    { 
        Point lastMousePos = new Point(); 
        double _zoom = 1; 
        bool mouseButtonPressed = false; 
        bool mouseIsDragging = false; 
        Point dragOffset; 
        Point currentPosition; 

        public double ZoomFactor 
        { 
            get { return _zoom; } 
            set { _zoom = value; } 
        } 

        public Page() 
        { 
            this.InitializeComponent(); 

            this.MouseMove += delegate(object sender, MouseEventArgs e) 
            { 
                if (mouseButtonPressed) 
                { 
                    mouseIsDragging = true; 
                } 
                this.lastMousePos = e.GetPosition(this.msi);   
            }; 

            this.MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e) 
            { 
                mouseButtonPressed = true; 
                mouseIsDragging = false; 
                dragOffset = e.GetPosition(this); 
                currentPosition = msi.ViewportOrigin; 
            }; 

            this.msi.MouseLeave += delegate(object sender, MouseEventArgs e) 
            { 
                mouseIsDragging = false; 
            }; 

            this.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e) 
            { 
                mouseButtonPressed = false; 
                if (mouseIsDragging == false) 
                { 
                    bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift; 

                    ZoomFactor = 2.0; 
                    if(shiftDown) ZoomFactor = 0.5; //back out when shift is down 
                    Zoom(ZoomFactor, this.lastMousePos); 
                } 
                mouseIsDragging = false; 
            }; 

            this.MouseMove += delegate(object sender, MouseEventArgs e) 
            { 
                if (mouseIsDragging) 
                { 
                    Point newOrigin = new Point(); 
                    newOrigin.X = currentPosition.X - (((e.GetPosition(msi).X - dragOffset.X) / msi.ActualWidth) * msi.ViewportWidth); 
                    newOrigin.Y = currentPosition.Y - (((e.GetPosition(msi).Y - dragOffset.Y) / msi.ActualHeight) * msi.ViewportWidth); 
                    msi.ViewportOrigin = newOrigin; 
                } 
            }; 

            new MouseWheelHelper(this).Moved += delegate(object sender, MouseWheelEventArgs e) 
            { 
                e.Handled = true; 
                if (e.Delta > 0) 
                    ZoomFactor = 1.2; 
                else 
                    ZoomFactor = .80; 

                Zoom(ZoomFactor, this.lastMousePos); 
            }; 
        } 

        public void Zoom(double zoom, Point pointToZoom) 
        { 
            Point logicalPoint = this.msi.ElementToLogicalPoint(pointToZoom); 
            this.msi.ZoomAboutLogicalPoint(zoom, logicalPoint.X, logicalPoint.Y); 
        } 
    } 
}

Three One thing I am having trouble with, but I haven't run this under a debugger yet as I haven't installed the single Silverlight 2 Beta 1 Tools Installer (Troubleshooting Silverlight 2 Beta 1 Tools Installer). I just built it in Expression Blend 2.5 and Notepad2. I was enjoying myself so much I didn't want to stop and install anything. ;)

  • One, if you scroll WHILE the "spring zoom" animation is happening, something goes wrong and you'll continue zooming in, no matter what direction you're scrolling.
  • Second, more subtlety, if you scroll in, stop, then start scrolling out, it'll scroll in a step, then start scrolling out, so there's clearly a state issue I'm goofing up. Stated another way, if a managed event comes in WHILE the animation is happening, the direction it's currently zooming just keeps going. (Maybe a subtle bug, or my code needs to be debounced because it seems like the mouse wheel events are coming in too fast.) In my example, you have to let the animation "settle down" in order to zoom the other way. Of course, if you just F5 to Refresh you can get back to home and reset the zoom.
  • I figured it out, I was using a relative calculated ZoomFactor when I should have used an absolute factor.
  • Third, I want to prevent the image from panning outside the viewable area (meaning, I don't want folks to get lost) and I'd like to stop the zooming in and out at some reasonable max/min.
  • Don't forget to setup .xap files as the mime type application/x-silverlight-app on on your hoster's IIS instance.

Very cool and fairly easy considering all that's going on. I'll look into other ways this can be exploited in the morning.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Deep Zoom (Seadragon) Silverlight 2 MultiScaleImages at Mix

March 6, '08 Comments [22] Posted in Mix | Silverlight
Sponsored By

outrun One of the cool things seen at Mix 08 today was the Deep Zoom technology. This was originally called Seadragon and came out of the Live Labs research.

You can see it live at the Hard Rock Cafe Memorabilia site. This Silverlight application includes a tiled montage of 512 many-megapixel images, some 10+megapixels, while some are stitched together to sizes upwards of 40-50 megapixels, it seems.

Remember the first 3D games like Sega's OutRun? They seemed 3D, but basically bitmaps were being scaled as you "drove forward" and each graphical asset, like a house, for example, would have a number of pre-rendered versions. Another example are .ico files that have 16x16, 32x32, etc, versions. As the viewer there is one image, but there's multiple resolutions depending on how far in you want to zoom. This is the basic idea behind "multi-scale images."

You can see examples of exceedingly large images, often called "giga-pixel" images all over the web. A great example of zooming using Flash is at Harlem 13-Gigapixels.com. The large Harlem image is 2,045 individual images of 12 megapixels each. The rendering when zooming in is a lot like Google Earth. You zoom into an image of a specific resolution then that first image transitions into a tile of a second image rendered another resolution. You can create these kinds of images with Panorama stitching software like AutoPano.net, and PanoramaFactory. I took a large 50+ megapixel panorama of Ngorongoro Crater in Tanzania that I think would make a good example for this kind of thing. I'll dig it up and make a Deep Zoom example project.

DeepZoom includes an optimized local Silverlight Renderer and XAML control that makes creating these fairly easy. Try it out over at the HardRock Memorabilia site and take specific notice of this guitar I've circled in the picture below.

Hardrock Memorabilia

Zoom in...

image

And more...into the screw knob in the lower left corner...

image

You can actually see a reflection of the bench and the camera and Scott Stanfield from Vertigo who's taking the shot of this guitar, reflected in the shiny knob. Vertigo wrote the Hardrock site and they also have a great Deep Zoom Gallery.

Project Name

You can go download the Preview of the Deep Zoom Composer now. You can see its heritage when you make a new Project and it puts it in a folder called "Seadragon Projects." Here's the Deep Zoom Composer User Guide.

You place a number of pictures (or a large one) on the surface and export the collection. There will be a scene graph XML file created indicating location, size, z-order and aspect ratio of the images.

SparseImageSceneGraph.xml - Notepad

Then an output directory will be created with folders for each image and, depending on the relative largeness of the source images, a series of tiles generated a number of zoom levels. Here's an example directory structure:

11

Silverlight Project Test Page - Windows Internet Explorer (2)See how that image at zoom level 11 has 20 different tiles? They've got to balance many factors to make it all seamless. They're considering how many tiles are needed for that resolution as it relates to a zoom level's need for memory, as well as considering a reasonable number of requests to the server. There's the extreme need for a good user-experience so you want the images to come fast and furious, be easily cached and quickly shown to give the perception of speed. You don't have to sweat any of this stuff, as the multi-resolution tiles stitched together automatically by the client rendered.

After you've exported a project into these files, you create a MultiScale image in XAML like this, referencing a bin file manifest that knows contains info about the directory structure and files:

<MultiScaleImage x:Name="deepZoomObject" Source="foo/info.bin"/>

This gets you the image rendered. To zoom a MultiScaleImage, you call ZoomAboutLogicalPoint:

public void ZoomAboutLogicalPoint(double zoomIncrementFactor, double zoomCenterLogicalX, double zoomCenterLogicalY)

But there's a decent amount of state management that needs to be done as well as deciding how you want things animated, whether or not your image contains sub-images, etc. In the morning I'll take a look at how to hook up JavaScript events like Mouse Wheel Scrolls and do some zooming and panning. Hopefully Vertigo will release their hard-won source code for the canonical example to the community. It's pretty slick.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

ASP.NET MVC Preview 2 Screencast Tutorials

March 5, '08 Comments [44] Posted in ASP.NET | ASP.NET MVC | Learning .NET | Microsoft | Mix | Screencasts
Sponsored By

ASP.NET MVC Preview 2 released today and you can head over to http://www.asp.net/mvc to get the new stuff. I put together four new screencasts for this release.

I really feel that a well-done screencast is a great learning tool. Last month I did a survey of you all, Dear Reader, and got 1000 responses and published the results. I took all the comments, stats and general feedback and tried to improve these screencasts. As a result, I am more judicious in my use of PIP (Picture in Picture) and the screencasts are shorter and more to the point.

If you watched the original MVC Screencast, some of the content in videos #1 and #2 will be repeated as it's been updated for Preview 2, but I hope it doesn't waste your time. However, there's new things sprinkled around and a number of changes are called out. Additionally, videos 3 and 4 are completely new content. Video #4 talks about how to use a Mocking Framework to to TDD with MVC and #3 touches on a number of advanced techniques that you can practice at the Mix HOL (Hands-On-Labs).


#1 | MVC Preview - Part 1 - Displaying Data

16 minutes, 41 seconds

#2 | MVC Preview - Part 2 - Editing Data and Making Forms

18 minutes, 38 seconds

#3 | MVC Preview - Part 3 - Advanced Techniques

12 minutes, 56 seconds


#4 | MVC Preview - Part 4 - Testing

23 minutes, 20 seconds

UPDATE: There's only a template for MSTest in the new File | New MVC Project wizard right now because we're reaching out to the big Unit Testing libraries to get them to help create templates. If you have a Unit Testing Library, you can get into the MVC File | New Project dialog by packaging up your templates like this. I'd guess/hope that the installers for the major libraries would just include their templates in their next versions.

imageI'll work with Phil to get all the source to the apps in these videos posted soon. I hope you enjoy them as much as I enjoyed making them.

Note that while the player on the http://www.asp.net site is Silverlight, you can download versions on the same page in WMV format, as well as ones customized for the Zune, iPod, PSP, and MP4 and 3GP.

One other important new feature on the ASP.NET site is that Videos now accept comments and those comments include RSS feeds. Do feel free to leave comments on this blog, but you can also comment on videos and screencast tutorials all over the ASP.NET site. We all read these comments (including my boss) so your feedback IS paid attention to.

Technorati Tags: ,

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Heading to Mix08 and Tweeting along the way

March 4, '08 Comments [4] Posted in Mix | Speaking
Sponsored By

image In the morning I'm heading to Mix. I'm going to be using Twitter as a microblog during my time at Mix. I'll post where I am and what I'm up to. You can sign up and "follow" me on Twitter, and select optional notifications via SMS or IM.

Scott Watermasysk has a great post called "Why Twitter" and I've been "tweeting" more and more of late. It's a great way to communicate, kind of like IRC except with permalinks and angle brackets. ;) Twitter is a lot more casual and there's a lot more off-the-cuff random discussions.

ScottWater has also setup a series of Hashtags such that any "tweets" on Twitter that include "#mix" "#mix08" in them will show up on this realtime feed. This is a great way to keep up on the hour by hour Mixiness that'll be going on, whether you're at the conference or not.

Also, if you haven't done your Mix Schedule, check out this ClickOnce WPF app that does Mix Scheduling. I'll be speaking on ASP.NET MVC on Thursday at 4:15 in Lando 4201 and Phil Haack and I will be hosting a Q&A in the Mix Open Spaces area immediately following.

I'll be looking to do a number of technical podcasts while in Vegas so if there's certain cool or obscure (or both) questions you want answered, leave them here and I'll try to chase them down. There's also a Mix category on my blog so posts while I'm there will be tagged as well.

See you in Vegas. Man, I hate this town.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

2008 Window Scripting Games - Advanced PowerShell Event 7

March 4, '08 Comments [25] Posted in Learning .NET | PowerShell | Ruby
Sponsored By

Olympic_flameIn a few days the 2008 Scripting Games will come to an end. This is a yearly event that the Script Center does. There's a beginner and an advanced division and a bunch of deceptively hard problems. I was selected to be on of the "Guest Commentators (list here)" which really means they wanted me to solve one of the problems and provide the solution as an example. I'm not sure my solution is the best way, but it did solve the problem they assigned me.

My problem was Event 7: Play Ball! and I was to write a script that schedules all the games for a round-robin baseball tournament. The complete scenario is here, but in a nutshell:

"In a round-robin baseball tournament (or any kind of round-robin tournament, for that matter), every team plays each of the other teams in the tournament one time and one time only. For example, suppose we have a tournament with three teams (teams A, B, and C). In that case, the tournament would consist of the following set of games:

  • A vs. B
  • A vs. C
  • B vs. C

See how that works? Team A plays Team B and Team C; Team B plays Team A and Team C; and Team C plays Teams A and B."

A few other wrinkles thrown in are that the games must be randomized, otherwise Team A will play too many in a row and you need to schedule six teams, A through F. Of course, to be clear, every team must pay every other team once and only once. Here's my solution, hacked together quickly.

#this only works with an even number of teams
cls
[array]$global:games = $nul
function rotateArray($a)
{
 $first, $rest = $a
 $a = $rest + $first
 return $a
}
function makeGames($a)
{
 $i = 0;
 while($i -lt $a.Length/2)
 {
  $global:games = $global:games + ($a[$i].ToString() + " vs. " + $a[$a.Length-1-$i].ToString())
  $i++
 }  
}
$a = "A","B","C","D","E","F"
$z = 0
while($z -lt $a.Length-1)
{
 makeGames($a)
 # hold on to the first one
 
 $first, $rest = $a
 #rotate the rest
 $rest = rotateArray($rest)
 $a = [array]$first + $rest
 $z++
}
#randomize games
$a = [collections.arraylist]$global:games
$r = $a.count..1 |% {$R = new-object random}{$R.next(0,$a.count) |%{$a[$_];$a.removeat($_)}}
$r

Doing this in PowerShell took my brain a while to get. Note the RotateArray method's use of multi-variable assignment to chop up he array into first and rest. That wasn't obvious to me as a C-guy for the last 15 years, but it made my solution simpler when I refactored and introduced it.

The solution (remember, it's randomized) will look something like this:

B vs. D
B vs. C
A vs. D
B vs. F
C vs. D
A vs. F
A vs. B
C vs. F
E vs. F
A vs. E
D vs. F
B vs. E
D vs. E
A vs. C
C vs. E

Enjoy. Here's the same solution in Perl from Jan Dubois and again in VBScript. Who wants to do the F# version and the Ruby version? What about just LINQ to Objects?

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.