Creating SparseImages for Pivot
- by John Conwell
Learning how to programmatically make collections for Microsoft Live Labs Pivot has been a pretty interesting ride. There are very few examples out there, and the folks at MS Live Labs are often slow on any feedback. But that is what Reflector is for, right?
Well, I was creating these InfoCard images (similar to the Car images in the "New Cars" sample collection that that MS created for Pivot), and wanted to put a Tag Cloud into the info card. The problem was the size of the tag cloud might vary in order for all the tags to fit into the tag cloud (often times being bigger than the info card itself). This was because the varying word lengths and calculated font sizes.
So, to fix this, I made the tag cloud its own separate image from the info card. Then, I would create a sparse image out of the two images, where the tag cloud fit into a small section of the info card. This would allow the user to see the info card, but then zoom into the tag cloud and see all the tags at a normal resolution. Kind'a cool.
But...I couldn't find one code example (not one!) of how to create a sparse image. There is one page on the SeaDragon site (http://www.seadragon.com/developer/creating-content/deep-zoom-tools/) that gives over the API for creating images and collections, and it sparsely goes over how to create a sparse image, but unless you are familiar with the API already, the documentation doesn't help very much.
The key is the Image.ViewportWidth and Image.ViewportOrigin properties of the image that is getting super imposed on the main image. I'll walk through the code below. I've setup a couple Point structs to represent the parent and sub image sizes, as well as where on the parent I want to position the sub image. Next, create the parent image. This is pretty straight forward. Then I create the sub image. Then I calculate several ratios; the height to width ratio of the sub image, the width ratio of the sub image to the parent image, the height ratio of the sub image to the parent image, then the X and Y coordinates on the parent image where I want the sub image to be placed represented as a ratio of the position to the parent image size.
After all these ratios have been calculated, I use them to calculate the Image.ViewportWidth and Image.ViewportOrigin values, then pass the image objects into the SparseImageCreator and call Create.
The key thing that was really missing from the API documentation page is that when setting up your sub images, everything is expressed in a ratio in relation to the main parent image. If I had known this, it would have saved me a lot of trial and error time. And how did I figure this out? Reflector of course! There is a tool called Deep Zoom Composer that came from MS Live Labs which can create a sparse image. I just dug around the tool's code until I found the method that create sparse images. But seriously...look at the API documentation from the SeaDragon size and look at the code below and tell me if the documentation would have helped you at all. I don't think so!
public static void WriteDeepZoomSparseImage(string mainImagePath, string subImagePath, string destination)
{
Point parentImageSize = new Point(720, 420);
Point subImageSize = new Point(490, 310);
Point subImageLocation = new Point(196, 17);
List<Image> images = new List<Image>();
//create main image
Image mainImage = new Image(mainImagePath);
mainImage.Size = parentImageSize;
images.Add(mainImage);
//create sub image
Image subImage = new Image(subImagePath);
double hwRatio = subImageSize.X/subImageSize.Y; // height width ratio of the tag cloud
double nodeWidth = subImageSize.X/parentImageSize.X; // sub image width to parent image width ratio
double nodeHeight = subImageSize.Y / parentImageSize.Y; // sub image height to parent image height ratio
double nodeX = subImageLocation.X/parentImageSize.X; //x cordinate position on parent / width of parent
double nodeY = subImageLocation.Y / parentImageSize.Y; //y cordinate position on parent / height of parent
subImage.ViewportWidth = (nodeWidth < double.Epsilon) ? 1.0 : (1.0 / nodeWidth);
subImage.ViewportOrigin = new Point(
(nodeWidth < double.Epsilon) ? -1.0 : (-nodeX / nodeWidth),
(nodeHeight < double.Epsilon) ? -1.0 : ((-nodeY / nodeHeight) / hwRatio));
images.Add(subImage);
//create sparse image
SparseImageCreator creator = new SparseImageCreator();
creator.Create(images, destination);
}