This article is compatible with the latest version of Silverlight.
Introduction
The MultiscaleImage is a really great control that allows us to do amazing things in Silverlight. That is why I decided to write a series of articles about the nice things that can be done using it. This is the first one and it is focused on the SubImages collection. It explains how to select an image from the collection and then fit it to the size of the control. If you're new to this control read my previous article about it - Using the MultiscaleImage control. Before going ahead you should also be familiar with the DeepZoom Composer and its latest changes. Note that in this article I've replaced the "Double-Click" zoom with “One-Click” zoom.
Live demo | Source code
The SubImage collection
The MultiscaleImage control has a property that contains a collection of the Images that are used in the composition. In order to use it you should export your DeepZoom Composer project as a collection. Now let's see how we can get an image from the collection when it's clicked with the mouse for example:
private int GetSelectedImage( Point p )
{
for( int i = 0; i < MyMultiscaleImage.SubImages.Count; i++ )
{
MultiScaleSubImage subImage = MyMultiscaleImage.SubImages[ i ];
double scaleBy = 1 / subImage.ViewportWidth;
Rect rect = new Rect( -subImage.ViewportOrigin.X * scaleBy,
-subImage.ViewportOrigin.Y * scaleBy,
1 * scaleBy,
( 1 / subImage.AspectRatio ) * scaleBy );
if( rect.Contains( p ) )
return i;
}
return -1;
}
We have clicked on one of the images in the collection. In order to find the clicked image we have to iterate throughout the whole collection, of images calculate scale factor, which is reciprocal to the ViewportWidth of the SubImage. Than we create a rectangle that has the same coordinates as the SubImage in the MultiscaleImage control. The Rectangle class has a method Contains that takes a Point as an argument (in this case it's a logical point) and checks if that point is contained within the bounds of the rectangle. If the rectangle contains the point, then the SubImage also contains it. We return the found index, but if no image is clicked we return -1 as default value.
Fitting the image to the control
Since we can get the selected image out of the collection, let's now try to fit it to the MultiscaleImage control. Here is the method:
private void FitImageToScreen( int index )
{
if( index != -1 )
{
MultiScaleSubImage subImage = MyMultiscaleImage.SubImages[ index ];
this.parentViewportWidth = 1 / subImage.ViewportWidth;
MyMultiscaleImage.ViewportWidth = this.parentViewportWidth;
double scaleBy = 1 / subImage.ViewportWidth;
MyMultiscaleImage.ViewportOrigin = new Point( -subImage.ViewportOrigin.X * scaleBy,
-subImage.ViewportOrigin.Y * scaleBy );
}
}
If the index is different then -1, than we have something that can be fitted. We get the image out of the SubImages using the index. The ViewportWidth property of the SubImage returns the ViewportWidth of the image when the VieweportWidth of the control is 1. So it's easy to calculate how much the ViewportWidth of the control must be so the SubImage has a ViewportWidth of 1. Than we calculate the new ViewportOrigin in dependence of the ViewportOrigin of the SubImage and the scale factor. Finally we save the new ViewportWidth of the control, in order to use it later.
Putting these methods in use
I do the zooming and the fitting in the handler for the MouseLeftButtonUp event of the MultiscaleImage control:
private void MyMultiscaleImage_MouseLeftButtonUp( object sender, MouseButtonEventArgs e )
{
if( !dragInProgress )
{
Point p = e.GetPosition( MyMultiscaleImage );
Point zoomPosition = MyMultiscaleImage.ElementToLogicalPoint( new Point( p.X, p.Y ) );
if( ( Keyboard.Modifiers & ModifierKeys.Alt ) == ModifierKeys.Alt )
{
this.Zoom( 0.9, zoomPosition );
}
else
{
this.Zoom( 1.1, zoomPosition );
int index = this.GetSelectedImage( MyMultiscaleImage.ElementToLogicalPoint(
e.GetPosition( MyMultiscaleImage ) ) );
if( index == selectedImageIndex )
{
if( ( float )this.parentViewportWidth < ( float )MyMultiscaleImage.ViewportWidth
|| MyMultiscaleImage.ViewportWIdth == 1)
{
this.FitImageToScreen( index );
}
}
else
{
this.FitImageToScreen( index );
}
selectedImageIndex = index;
}
}
dragInProgress = false;
mouseLeftButtonClicked = false;
}
The logic here is the following - if the image is not fitted and is smaller we fit it, if it's not fitted, but is bigger we don't fit it, so we can zoom it further. If another image is clicked it's fitted to the screen.
Summary
That is all for now and as you can see it isn’t complicated at all. Hope you weren’t scared by the spiders. Here is a link to the live demo and a link to the source code of this example. Stay tuned for the next article, because the things are going to become more and more impressive step by step.