Tuesday, April 1, 2008

Dissecting Hard Rock Memorabilia and Silverlight Deep Zoom - Part 6

The filter example is finally here!

Update Oct 14, 2008: Removed the example from this post to reduce load time of this page. Please use the example from this post, DeepZoom sample ported to Silverlight 2 Beta 2, to try the functionality.


Click on the Filter button to filter the images based on the tag entered in the text box to the left of the filter button. The following tags will yield some result: lilies, tulip, rose, hibiscus, iris, blue, white, yellow, red, peach, pink, purple. The flower tag will return all the images. The current limitation is that only one tag can be used at a time.

Behind the scenes...

This was actually pretty easy. I used the method suggested by George (see the first comment in this post ) instead of storing a pointer in the Tag property of the MultiScaleSubImage control.

This is what I did to accomplish basic filter functionality:
1. Modified the Deep Zoom generated SparseImageSceneGraph.xml file to include some tag information
?xml version="1.0"?>
<SceneGraph version="1">
<SceneNode>
<FileName>tigerlilies_cr.jpg</FileName>
<ZOrder>1</ZOrder>
<Tags>
<Tag>lilies</Tag>
<Tag>flower</Tag>
</Tags>
</SceneNode>
<SceneNode>
<FileName>tulip_bluebase-1_cr.jpg</FileName>
<ZOrder>5</ZOrder>
<Tags>
<Tag>tulip</Tag>
<Tag>flower</Tag>
<Tag>blue</Tag>
</Tags>
</SceneNode>
<SceneNode>
<FileName>2redandwhitelilies_cr.jpg</FileName>
<ZOrder>8</ZOrder>
<Tags>
<Tag>lilies</Tag>
<Tag>flower</Tag>
<Tag>red</Tag>
<Tag>white</Tag>
</Tags>
</SceneNode>
...
</SceneGraph>

2. Loaded the SparseImageSceneGraph.xml asynchronously into the control. I could have simplified this by loading the metadata file synchronously or by embedding it in the xap package itself but I wanted to learn the asynchronous download feature provided by Silverlight

using System.Net;
using System.Linq;
using System.Xml.Linq;

XElement _xmlImageMetadata; // to store the metadata associated with the images

msi.Loaded += delegate(object sender, RoutedEventArgs e)
{
// Now set the source property.
// This ensures that the html page with the silverlight control is loaded completely before loading the multiscaleimage.
// This is to enhance the overall user experience.
msi.Source = new Uri("http://thepintospatronus.com/deepzoom/test/items.bin");

// Download the metadata associated with the above uri
WebClient _downloader = new WebClient();
_downloader.DownloadStringCompleted += new DownloadStringCompletedEventHandler(imageMetadata_DownloadStringCompleted);
_downloader.DownloadStringAsync(new Uri("http://thepintospatronus.com/deepzoom/test/SparseImageSceneGraph.xml"));
};

void imageMetadata_DownloadStringCompleted(Object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null) {

// Convert the string xml representation to a valid XML document
_xmlImageMetadata = XElement.Parse(e.Result);

// The filter button is disabled by default. Enable it when the metadata is available
buttonFilter.IsEnabled = true;
}
}

3. Used Linq to filter the dataset

buttonFilter.Click += delegate(object sender, RoutedEventArgs e)
{
IEnumerable<int> ZOrders =
from sceneNode in _xmlImageMetadata.Elements("SceneNode")
where
(from tag in sceneNode.Elements("Tags").Elements("Tag")
where
((string)tag.Value).ToUpper() == txtFilter.Text.ToUpper()
select tag)
.Any()
select ((int)sceneNode.Element("ZOrder")-1);

_imagesToShow.Clear();
_imagesToHide.Clear();
for (int i=0; i<msi.SubImages.Count; i++) {
if (ZOrders.Contains(i))
_imagesToShow.Add(msi.SubImages[i]);
else
_imagesToHide.Add(msi.SubImages[i]);
}
ArrangeImages();
};

TODO - Better animation.

Comments are welcome!

16 comments:

Anonymous said...

Awesome!

ccchai said...

I think this is the best DeepZoom example with source code.

I thought of using SparseImageSceneGraph.xml in this way too...using ZIndex to identify selected image sounds a bit hacky...not too sure whether this is the approach used by Hard Rock Memorabilia or not...

Anonymous said...

Saw the link I left in an earlier post of yours with a link to the .xap file?

Anonymous said...

Excellent, this looks great!

And of course you can generate the SparseImageSceneGraph.xml from the filesystem, mine grabs the tags from the windows vista tag system...so you can tag in Windows Live Image Gallery and the tags get loaded!

Thanks for the async code, I hadn't implemented that...I'm hoping to have my smaller collection online later tonite...I have a small one with 200,000 (2000 images) and a bigger one (1.1 million files with 10,000 images)...decided to upload the small one first...it's taken 3 days via ftp...ack...

Wilfred Pinto said...

Yuvi,

Being a pseudo hacker myself, I thought about taking this approach before embarking on this project. At that time I wanted to create a site that was similar to the Hard Rock site, to showcase my daughter's photos. But then my objective changed to learning Silverlight.

I know I could be reinventing the wheel but at the same time it would be nice to see alternate solutions to the same problem. Maybe if I get some time (I do have a full time job) I will look into that xap file and see how Vertigo's solution is same/different than the one in this blog. I will only do this after I am sure there are no legal inplications for doing this.

Anonymous said...

Could you please update to Beta2?

Thanks!

joaquin said...

Is there a way to load the SparseImageSceneGraph.xml file using a relative URL?

Thanks

joaquin said...

Found it:

http://www.kirupa.com/blend_silverlight/loading_xml_sl2_pg1.htm

MKing said...

This is a very comprehensive example, thanks. I'm trying to expand upon it slightly and having some trouble so I was hoping for a pointer in the right direction.

I'd like to dynamically generate the image collection instead of reading from a static XML file. I've tried inheriting from MultiScaleTileSource but cannot figure out how to make this provide SubImages on the MultiScaleImage control (always produces one composite image). Any thoughts?

Wilfred Pinto said...

MKing, Are you talking about something like this - http://www.codeproject.com/KB/silverlight/DecosDeepZoom.aspx

MKing said...

Not quite, Wilfred, but thanks for the reference. That is a demo for using the Deep Zoom Composer API to generate static XML files that can then be used as the source for MultiScaleImage controls. I want to be able to dynamically generate the source information (probably by deriving from MultiScaleTileSource) instead of using a pre-generated XML file.

Wilfred Pinto said...

How about this - http://soulsolutions.com.au/Blog/tabid/73/EntryId/471/Silverlight-Virtual-Earth-Part-2-MultiScaleTileSource.aspx

MKing said...

Nope, that does indeed use a custom MultiScaleTileSource but it only produces a single image, not a collection. Setting a break point at any point in this code's execution you can see msi.SubImages.Count == 0

I have found examples of using custom MultiScaleTileSources to produce a single image (like what you just referenced) or of using a static XML file to serve an image collection (like you referenced earlier) but never of dynamically producing a collection. It's certainly not for lack of searching! Thanks for looking around though.

Wilfred Pinto said...

It seems like MultiScaleTileSource was only designed to handle a single image and not a collection. The attributes just don't support a collection.

I don't know what your exact requirement is but one option could be to generate the xml dynamically on the server and pass that to the deep zoom control. This would work if you want to generate the source for the entire collection dynamically but would not help if you want to change the source of the individual items in the collection after rendering the collection.

MKing said...

Unfortunately the latter is exactly what I want to do. I want to be able to store the actual image files somewhere other than the server (Amazon S3). If I use an XML file (statically or dynamically generated) it just looks for the image files in sub directories relative to that file, I'd prefer they be drawn from a different URL entirely.

I guess I'm going to have to resort to URL redirection on the server. It's a little overhead I didn't want but should work OK. Thanks for all your research and help!

Anonymous said...

I want to attach click event to each images so that i can open new window corresponding to each images. Is this possible in deep zoom composer, how can i do ? please help........