Tuesday, May 26, 2009

Disecting the disection of HardRock Cafe Memorabilia - Part 1

It all started when a friend showed me the Hardrock Memorabilia site. "That's cool," I thought, "I could build myself a nice photo website like that." So then I set about investigating how it worked. 

A quick search on the web led me to Wilfred Pinto's blog and his fantastic recreation of some of the Hardrock functionality. Thanks, Wilfred!

I downloaded the source and, fairly soon, I'd got it working with some of my own pictures. However, there were a couple of parts of the code that I just didn't understand. Armed with a brain made of porridge, I attempted to solve the problems.

Here's the first part that I've been tearing my hair out over:

1. How are the coordinates, width and height calculated for the new Rect object in GetSubImageRect?

Here's the code (thanks, Wilfred P): 

Rect GetSubImageRect(int indexSubImage)
   if (indexSubImage <>= msi.SubImages.Count)
return Rect.Empty;
MultiScaleSubImage subImage = msi.SubImages[indexSubImage];
double scaleBy = 1 / subImage.ViewportWidth;
return new Rect(-subImage.ViewportOrigin.X * scaleBy,-subImage.ViewportOrigin.Y * scaleBy,
1 * scaleBy, (1 / subImage.AspectRatio) * scaleBy);

I created a new DeepZoom project that contained only the functionality I was interested in and worked it out.

We want to create a rectangle the coordinates, width and height of which are "logical" or normalized to find out if the rectangle contains the logical point we're testing. For a definition of "logical", read MSDN.

I read Gerhard Lutz's blog. This guy knows his stuff, but it was too much for my peanut brain and I couldn't quite get it straight. If anyone thinks they can explain it to me in terms I'd understand, please get in touch. 

Luckily, Jaime Rodriguez had written something about subimages, which really helped. 

This is what I found out,it seems to work and it makes sense to me:

1. The SubImage.ViewPortWidth is relative to the the width of the MultiScaleImage control. 

For example, if SubImage.ViewPortWidth = 1 then it takes up the whole of the multiscale image width. If SubImage.ViewPortWidth = 2 then it takes up 1/2 the multiscale image width.

2. The SubImage.ViewPortOrigin.X and .Y values are relative to the logical width of the SubImage with the added twist that coordinates within the MultiScaleImage are negative.

Here's an example of a MultiScaleImage control containing 10 subimages. The images are numbered 0 to 9 from top left to bottom right. I added the numbers after I'd taken a snapshot of the page. Click on the image to see a bigger version.

Here's some info about each image. Click on the image to see a bigger version:

So, for image 0, ViewPortOrigin = (0,0). This means the top left hand corner of the SubImage (the ViewPortOrigin) is in the top left hand corner of the MultiScaleImage.

Let's create the rectangle for image 6.

Image 6 has ViewPortOrigin = (-2.5,-1.5).  The minus sign tells us the ViewPortOrigin coordinate is to the right and down of (0,0). But how much? Let's work it out.

As mentioned above, the ViewPortOrigin is relative to the logical width of the SubImage. The logical width of SubImage = 1/SubImage.ViewPortWidth. 

In the code example:  ScaleBy = 1/SubImage.ViewPortWidth.

In this case, ScaleBy = 1/5.5 = 0.181818181

So, SubImage.ViewPortOrigin.X as a logical point along the X axis of the MultiScaleImage is:

-1 * -2.5 * 0.181818181 = 0.4545454.

(We multiply by -1 otherwise the coordinates will be outside the MultiScaleImage. The negative coordinates are only used in the ViewPortOrigin of the SubImage.)

The Y coordinate of the rectangle will be:

-1 * -1.5 * 0.181818181 = 0.27272727

The width will be simply 1/SubImage.ViewPortWidth = ScaleBy = .181818181

The logical height is a little more complicated.

AspectRatio = Width/Height, so Height = Width/AspectRatio.

Logical Width = ScaleBy,
So, Height  = ScaleBy /AspectRatio  = 0.1818181/0.666666 = 0.272727

If you want the pixel coordinates (again, see MSDN for the definition), you can multiply the results of the above by the actual width of the MultiscaleImage.

For example, the pixel X coordinate of the rectange would be:

 0.4545454 * 400 = 181.818181