Monday, March 31, 2008

Dissecting Hard Rock Memorabilia and Silverlight Deep Zoom - Part 5

Trying to find out which image is clicked? Maybe this code snippet will help...

msi.MouseLeftButtonUp += delegate(object sender, MouseButtonEventArgs e)
{
Point p = this.msi.ElementToLogicalPoint(e.GetPosition(this.msi));
int subImageIndex = SubImageHitTest(p);
if (subImageIndex >= 0)
// look up a custom corresponding data structure or xml and return the metadata associated with this image
}

int SubImageHitTest(Point p)
{
for (int i=0; i<msi.SubImages.Count; i++) {
MultiScaleSubImage subImage = msi.SubImages[i];

double scaleBy = 1 / subImage.ViewportWidth;
Rect subImageRect = new Rect(-subImage.ViewportOrigin.X * scaleBy, -subImage.ViewportOrigin.Y * scaleBy,
1 * scaleBy, (1 / subImage.AspectRatio) * scaleBy);
if (subImageRect.Contains(p))
return i;
}

return -1;
}

8 comments:

ccchai said...

Very nice...I will try the code snippet tonight....

Marco Silva said...

Hello Wilfred, great posts, you solved some problems that took me quite some time to figure out. And this hit testing was one that I got stuck until I read your post. But I still have a problem I think you could have the answer to. The think is, I am trying to create a photo album using Deep Zoom, and I want to have a on click event associated to each photo, so that when I click on a photo the application zooms to the image and centres the image in the screen. The think is I got it to go to the image, but I cant understand the msi.ViewportWidth property and how it relates to the subImage.ViewportWidth property... Can you point me to were I can read something about these properties or about the functionality I am trying to implement?
Thanks for all your help.

Marco Silva said...

Congratulations on your blog Wilfred, you are building a great work here. I was completely stuck with the mouse hit function and you helped me a lot!
I still have a problem you probably can help me with, I am trying to make a photo album that when I click on a photo the application zooms to the image centring it in the screen. My problem is in understanding the way the ViewportOrigin property of the MultiScaleImage and MultiScaleSubImage objects work and relate to each other... The code I'm using is this but it doesen't make the effect I was hopping it would...

private void CenterImage(int imageIndex)
{
Point newOrigin = msi.SubImages[imageIndex].ViewportOrigin;
newOrigin.X /= -msi.SubImages[imageIndex].ViewportWidth;
newOrigin.Y /= -msi.SubImages[imageIndex].ViewportWidth;
msi.ViewportOrigin = newOrigin;

msi.ViewportWidth = msi.SubImages[imageIndex].ViewportWidth;
}

I appreciate all your time and help.

Wilfred Pinto said...

Thanks for the kind words Marco! I can't claim to have a very good understanding of the Viewport as it relates to the MultiScaleImage but a lot of trial and error and a good amount of logic has helped me made some progress.

I was planning to try out what you just asked as my next step, since this is part of the Hard Rock functionality. I have one question for you: do you want to zoom the image so that it fills the entire screen or do you want it to behave similar to the Hard Rock site?

This exercise has really made me appreciate what Vertigo has done! Not so much on the actual implementation but more on the look and feel and usability.

Wilfred Pinto said...

Marco,

This will do the trick

void DisplaySubImageCentered(int indexSubImage)
{
if (indexSubImage < 0 || indexSubImage >= msi.SubImages.Count)
return;

Rect subImageRect = GetSubImageRect(indexSubImage);
double msiAspectRatio = msi.ActualWidth / msi.ActualHeight;

Point newOrigin = new Point(subImageRect.X - (msi.ViewportWidth / 2) + (subImageRect.Width / 2),
subImageRect.Y - ((msi.ViewportWidth / msiAspectRatio) / 2) + (subImageRect.Height / 2));

msi.ViewportOrigin = newOrigin;
}

Rect GetSubImageRect(int indexSubImage)
{
if (indexSubImage < 0 || 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 will post this on the blog after I figure out how to link it with the zoom functionality.

Anonymous said...

Ran into a problem with the snippet when trying to layer smaller images on top of larger images to create "clickable zones" within a large image.

Even when all of the small images are on top, and the large image is sent to back, it only detects the index of the large image, regardless of where you click.

Wilfred Pinto said...

Anonymous (a name would be nice!),

The issue is due to the fact that the SubImageHitTest function returns only the first match. In your case, there will be more than 1 match - the big image and the small image. This function should be modified to return a array of matches, something like this -

int[] SubImageHitTest(Point p)
{
List<int> images = new List<int>();

for (int i=0; i<msi.SubImages.Count; i++) {
MultiScaleSubImage subImage = msi.SubImages[i];

double scaleBy = 1 / subImage.ViewportWidth;
Rect subImageRect = new Rect(-subImage.ViewportOrigin.X * scaleBy, -subImage.ViewportOrigin.Y * scaleBy,
1 * scaleBy, (1 / subImage.AspectRatio) * scaleBy);

if (subImageRect.Contains(p))
images.Add(i);
}

return images.ToArray();
}

This will return both the small and big image in an array.

Wilfred

Chris said...

Wilfred, this is great stuff.
I'm trying to get my head round the SubImageHitTest code. I'm wondering why you make make the x and y parameters of the Rect subImageRect negatives of the subImage.ViewPortOrigin.X and .Y ?
I'm not saying this is wrong at all, but my measly brain cells are letting me down here.

Thanks for all your help.