There were some parts of the puzzle that needed to be addressed before a similar experience could be created:
- Mouse and keyboard handlers, including Mouse Wheel support, for panning and zooming the Deep Zoom image
- Figuring out how to manipulate the Deep Zoom images as a collection instead of a single large image
- Arranging the images in a grid, where the height of all images in a row is the same but can change across rows. This gives a clean look to the images
- Animating the images so that they appear to slide into position
- Adding extra information to each image so that they can be filtered on
I figured out Part 2 myself but noticed that the Expression Team already posted a solution for this. The DeepZoom composer provides a way to export the images as a collection. The images can then be accessed using the MultiScaleImage.SubImages collection.
The Expression Team has provided a partial solution to Part 3. They arranged the images in a grid but did not arrange it such that the height of all images in a row remain the same. I will attempt to provide an algortihm for this in this post and maybe provide some code snippet in the next post. I do not have access to Visual Studio 2008 at this time and hence am using notepad++ to write code. I am actually working off Hanselman's source code so I will wait until I create my own project before posting the entire source code. In the meantime, I will try to provide some code snippets...
Once again, the Expression Team has provided a very good sample for animating images, which addresses Part 4. This is one area I was struggling with since I lack animation skills. I am learning more everyday but for now I am happy to borrow this part of the code from the Expression Team!
I haven't reached Part 5 yet but I suspect that the Name/Tag property on the MultiScaleSubImage can be used to store additional information regarding the image. At the very least, a pointer of some sort can be stored in the Tag property which can then be used to retrieve more information on the image.
Now for the algorithm for Part 3 (arrange images in a grid, where the height of all images in a row is the same but can change across rows)
- Infer the MultiScaleImage aspect ratio - MultiScaleImage.Width / MultiScaleImage.Height
- Capture the normalized width of each image - What I mean by this is that the height of each image should remain the same. Assuming that we want the height to be a fixed '1', the width would equal the aspect ratio of the image (not the MultiScaleImage aspect ratio but the aspect ratio of the SubImage itself)
- Capture the total normalized width of all images - This is the sum of the normalized widths of each image
- Calculate the number of rows required to display the images - For this we need to normalize the heights of all images within the MultiScaleImage container. This can be achieved by dividing the total normalized width of images by the MultiScaleImage aspect ratio: Number of rows = Sqrt of [total image width (3.) / MultiScaleImage aspect ratio (1.)]
- Capture the width per row - total images width (3.) / Number of rows (4.)
- For each row, capture all the images that will fit in that row. This is based on the width of each image (2.) and the max width per row (5.)
- At this point in time, each row will contain a set of images which may or may not take up the width allocated to the entire row. Scale the rows by a factor of MultiScaleImage aspect ratio (1.) / total normalized width of images in a row. This will ensure that images in a row will fill up the width of the entire row. This will also normalize the height of all rows such that the sum of height of all rows will not exceed '1' (the normalized height of the MultiScaleImage)
- After step 8, all the rows should contain images that fill up the width of the entire row but the row height between rows may differ. By this we ensure that the images will fill the width of the container (MultiScaleImage) but may not fill up the height. Hence we need to calculate the top and bottom margin for display purposes
- Total Margin = 1 - total normalized height of rows. Top and bottom margin = Total margin / 2
- Un-normalize the images by scaling them back to the actual container size and display them - SubImage.ViewportWidth = MultiScaleImage aspect ratio / SubImage.width, SubImage.ViewportWidth = Point(X / SubImage.width, Y / SubImage.width) where X and Y are calculated based on the row/col value and the prior images width
A few tips (I discovered along the way...)
- SubImage can be hidden/shown by controlling the Opacity property. This is useful for implementing filter functionality
- Instead of setting the MultiScaleImage Source property in the Xaml, a better place to set it is in the MultiScaleImage.Loaded event. This way the entire html page with the Silverlight control will be loaded and then the MultiScaleImage control will try to load the images. This enhances the overall user experience