Tuesday, April 8, 2008

Dissecting Hard Rock Memorabilia and Silverlight Deep Zoom - Part 8

For completion sake, I decided to post the animation and randomize code here. The code to do the actual animation is lifted as is from the Expression Team blog but I did make a few subtle changes -
  1. Fixed the memory leak issue by getting rid of the Storyboard after it has achieved its purpose
  2. Used a single storyboard to animate multiple images. I don't know if this is better than multiple storyboards (one for each image) so it would be nice to hear some opinions

Code to animate the image

private void AnimateImage(MultiScaleSubImage currentImage, Point futurePosition)
{
// Create Keyframe
SplinePointKeyFrame endKeyframe = new SplinePointKeyFrame();
endKeyframe.Value = futurePosition;
endKeyframe.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1));

KeySpline ks = new KeySpline();
ks.ControlPoint1 = new Point(0, 1);
ks.ControlPoint2 = new Point(1, 1);
endKeyframe.KeySpline = ks;

// Create Animation
PointAnimationUsingKeyFrames moveAnimation = new PointAnimationUsingKeyFrames();
moveAnimation.KeyFrames.Add(endKeyframe);

Storyboard.SetTarget(moveAnimation, currentImage);
Storyboard.SetTargetProperty(moveAnimation, "ViewportOrigin");

// Add the animation to the Storyboard
_moveStoryboard.Children.Add(moveAnimation);
}

Code to invoke the animation - I decided against animating the hidden images (like Hard Rock) since my goal is to work with large amount of images and I didn't want to incur the additional performance overhead

private Storyboard _moveStoryboard;

public void InitStoryboard()
{
_moveStoryboard = new Storyboard();
msi.Resources.Add(_moveStoryboard);

_moveStoryboard.Completed += (sender, e) => msi.Resources.Remove((Storyboard)sender);
}

public void ArrangeImages()
{
InitStoryboard();

...

// Then display the displayable images to scale
for (int i=0; i<_imagesToShow.Count; i++) {
double X = rows[subImages[i].RowNum].CalcX(subImages[i].ColNum);
double Y = margin;
for (int j=0; j<subImages[i].RowNum; j++)
Y += spaceBetweenImages + rows[j].Height;

_imagesToShow[i].ViewportWidth = containerAspectRatio / subImages[i].Width;
AnimateImage(_imagesToShow[i], new Point(-(X / subImages[i].Width), -(Y / subImages[i].Width)));
_imagesToShow[i].Opacity = 1.0;
}

// Play Storyboard
_moveStoryboard.Begin();
}

Code to Randomize the images. The code for the RandomizeListOfImages is lifted from the Expression Team blog

buttonRandomize.Click += (sender, e) => RandomizeAndArrange();

private void RandomizeAndArrange()
{
_imagesToShow = RandomizedListOfImages();
ArrangeImages();
}

private List<MultiScaleSubImage> RandomizedListOfImages()
{
List<MultiScaleSubImage> imageList = new List<MultiScaleSubImage>();
Random ranNum = new Random();

// Store List of Images
_imagesToShow.ForEach(subImage => imageList.Add(subImage));

// Randomize Image List
int numImages = imageList.Count;
for(int i=0; i<numImages; i++) {
MultiScaleSubImage tempImage = imageList[i];
imageList.RemoveAt(i);

int ranNumSelect = ranNum.Next(imageList.Count);

imageList.Insert(ranNumSelect, tempImage);
}
return imageList;
}

6 comments:

Anonymous said...

This is great stuff. Is there any chance you can post the source code for the complete project?

Jessica D said...

I am hoping you can help me out...
I am trying to do a Deep Zoom with thumbnails on the side, and when you click on the thumbnails, it zooms in on that picture in the deep zoom. However I want the picture to basically fill up the deep zoom screen. I am having the issue of it not centering correctly, so what I have it do is center AFTER the zooming is done, but it isn't as smooth as I want it to be, and takes longer than I want it to be, do you have any ideas?
You can see an example here:
http://people.rit.edu/~jld4460/Silverlight/DeepZoomGalleryTestPage.html

Thanks!
-Jess
P.S your blog has been very helpful !

Wilfred Pinto said...

Jessica,

I see what you mean. I will look into it when I get some time, hopefully sometime this week. In the meantime, if you do find a solution, please post it here.

Thanks

Wilfred

Wilfred Pinto said...

Jessica,

Tried this and it seems to work fine. Let me know if it works for you.

public void ZoomAndCenterImage(int subImageIndex, double zoomFactor)
{
Rect subImageRect = GetSubImageRect(subImageIndex);
DisplaySubImageCentered(subImageIndex);
msi.ZoomAboutLogicalPoint(zoomFactor, (subImageRect.Left+subImageRect.Right)/2, (subImageRect.Top+subImageRect.Bottom)/2);
}

Jess said...

Yes! That worked perfectly, thank you. I also added a line of code so that no matter what the zoom level is at, it will always zoom in to the max zoom level:

double zoomFactor = _maxZoomIn / _currentTotalZoom;
ZoomAndCenterImage(img.returnNum(), zoomFactor);

public void ZoomAndCenterImage(int subImageIndex, double zoomFactor)
{
if ((_currentTotalZoom * zoomFactor) < MaxZoomOut ||
(_currentTotalZoom * zoomFactor) > MaxZoomIn)
return;
Rect subImageRect = GetSubImageRect(subImageIndex);
DisplaySubImageCentered(subImageIndex);
msi.ZoomAboutLogicalPoint(zoomFactor, (subImageRect.Left + subImageRect.Right) / 2, (subImageRect.Top + subImageRect.Bottom) / 2);
_currentTotalZoom *= zoomFactor;
}
Thanks again!
-Jessica

Wilfred Pinto said...

Jessica,

Thanks for sharing your code. This works great in a scenario where all images are of the same dimensions.

I am working with different size images and hence needed a more generic solution. I figured out how to do this and decided to dedicate a post to it, since there might be others who might want a similar solution.

Thanks

Wilfred