Tuesday, March 25, 2008

Dissecting Hard Rock Memorabilia and Silverlight Deep Zoom - Part 1

I have been following some of the Silverlight presentations on Mix 2008 and was totally blown away with the Hard Rock Memorabilia demo. Vertigo had created a truly compelling site! I was hoping that they would put some of their source code online, but since this did not happen, I decided to play around with Silverlight and Deep Zoom myself and attempt to create a similar experience.

There were some parts of the puzzle that needed to be addressed before a similar experience could be created:
  1. Mouse and keyboard handlers, including Mouse Wheel support, for panning and zooming the Deep Zoom image
  2. Figuring out how to manipulate the Deep Zoom images as a collection instead of a single large image
  3. 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
  4. Animating the images so that they appear to slide into position
  5. Adding extra information to each image so that they can be filtered on
Part 1 is easily solved since a lot of people have already provided solutions for the mouse and keyboard handlers. Some of the contributors who caught my attention: Scott Hanselman, Soul Solutions Blog, and the Expression Team at Microsoft.

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)
  1. Infer the MultiScaleImage aspect ratio - MultiScaleImage.Width / MultiScaleImage.Height
  2. 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)
  3. Capture the total normalized width of all images - This is the sum of the normalized widths of each image
  4. 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.)]
  5. Capture the width per row - total images width (3.) / Number of rows (4.)
  6. 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.)
  7. 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)
  8. 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
  9. Total Margin = 1 - total normalized height of rows. Top and bottom margin = Total margin / 2
  10. 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
That should do it! I will try to post some code snippet in subsequent posts.

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

4 comments:

George said...

I hadn't thought of the tag item to store content...what I did was created a list < T > where T is a custom class with different properties like Artist, Location, Date and importantly the ID of the picture (which I based off the ZOrder in the XML file used by the photo generator)...and then used LINQ to query those items...basically the query gets run, and then I iterate on those items and make them visible, then run the inverse of the query (NOT IN)...and hide those items.

Anonymous said...
This comment has been removed by a blog administrator.
Hrvoje said...

Hi,

I have one question regarding Deep Zoom/Silverlight.
Is there a way of marking a particular picture area (given by coordinates) which would stay marked/highlighted while zooming in/out? I would use it for marking search words on scanned documents. (OCR tools can recognize words and store box coordinates surrounding the word.)

I'm new with Silverlight so any kind of advice/help will be more than welcome.

Wilfred Pinto said...

hrvoje,

It might be possible but is definitely not trivial. I was thinking of attempting something like this but was not sure if there was any interest for it.

The problem currently is that the highlight is layered on top of MultiScaleImage and hence is treated independent of the MultiScaleImage. Instead of using the inbuilt MultiScaleImage zoom functionality for zooming, you might want to use Storyboard animation and viewport manipulation to achieve the same effect. Then you have control over both - MultiScaleImage and highlight.

Hope this helps!

Wilfred Pinto