Month: November 2008

Programmatically Create Deep Zoom Collections

The Expression team have released a new version of Deep Zoom Composer, and at last they’ve supplied a .NET assembly for creating Deep Zoom images. This has always been possible, using the SparseImageTool.exe that was supplied as part of Composer, but that was never a very good solution, particularly for large collections of images.

We have a potential use case where we’d want to create millions of deep zoom images, and expose these as collections, but without necessarily knowing which images were in a collection ahead of time. Or we might want to add new images to a large collection. Until now, that has always been impossible just using SparseImageTool, but using the new DLL it’s easy. Here’s some sample code which I just threw together to see how the API worked. You need two classes. The ImageCreator class creates individual Deep Zoom images, which was always easy with SparseImageTool. But the CollectionCreator can then take these images, and build a composition of all those images.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.DeepZoomTools;
using Path = System.IO.Path;

namespace DZCtest
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        private void Create_Click(object sender, RoutedEventArgs e)
        {
            ImageCreator creator = new ImageCreator();
            creator.TileFormat = ImageFormat.Jpg;
            creator.TileOverlap = 1;
            creator.TileSize = 256;

            List<string> files = new List<string>()
            {
                @"D:\Users\Pictures\DadsArmy\hi000350807.jpg",
                @"D:\Users\Pictures\DadsArmy\hi000350820.jpg",
                @"D:\Users\Pictures\DadsArmy\hi000360009.jpg"
            };
            string root = @"D:\Users\James\Visual Studio 2008\Projects\DeepZoomProjects\DeepZoomTests_Web\ClientBin\GeneratedImages\";
            List<string> dzi = new List<string>();
            foreach (var name in files)
            {
                string output = Path.Combine(root, Path.GetFileNameWithoutExtension(name) + ".dzi");
                dzi.Add(output);
                creator.Create(name, output);
            }

            CollectionCreator ccreator = new CollectionCreator();
            ccreator.TileFormat = ImageFormat.Jpg;
            ccreator.TileOverlap = 1;
            ccreator.TileSize = 256;
            ccreator.Create(dzi, Path.Combine(root, "da.dzc"));
        }
    }
}

You’ll have to add a Reference to DeepZoomTools.dll which is installed as part of Deep Zoom Composer. All this code does is take three hardcoded image paths, generates individual deep zoom images for them, then creates an overall collection of all three.

One thing there doesn’t seem to be is any way to lay out the collection other than having all the images on top of one another, but since any code which makes use of a collection would probably handle its own layout in code, this probably isn’t much of a drawback. And there might well another way to do that – I’ve literally only just thrown this code together, so I haven’t delved very far into what’s exposed.

It’ll be interesting to see what the performance is like. In a perfect world, I’d be able to do a search across millions of images and dynamically generate a collection of a few hundred images in response to a user request, but that might be asking a bit too much.

UPDATE: 9 minutes to process 305 images. 20 seconds to build the collection from those images. 20 seconds is probably a bit more than you’d want for a completely dynamic search, for example, but it’s probably absolutely fine if you’re uploading images to a more static collection like a picture album. 2 seconds to process a typical digital camera image is really rather good. Next test will be some of the really large images that DZC choked on last time I tried it.

Why does LayoutUpdated pass a null sender?

Because it does not mean what you think it means.

As Dave Relyea explains, LayoutUpdated is fired every time layout did anything in the tree so it’s not specific to a particular object, and you can’t use the sender to tell you what you’re interested in.

What you need is the SizeChanged event. LayoutUpdated is going to get fired an awful lot, while SizeChanged gets fired when the object you’re interested in has its size changed. It won’t get fired if the position of the object changes, but that’s probably what you want most of the time.

My particular code had a UserControl in a popup, and the UserControl had a variable size. I wanted it to always remain on screen, intelligently positioning itself so that it’s fully on screen (like a menu would) but when it’s first created (and indeed, laid out) its actual size is {0,0} because (I believe) that it hasn’t been added to the visual tree yet because the parent Popup is closed. I needed to wait until the size of the object was fully calculated before I positioned the popup, and I thought LayoutUpdated would do the trick, so I had some code like this:

void infoPane_LayoutUpdated(object sender, EventArgs e)
{
WalkInfoPane pane = sender as WalkInfoPane;
if (pane != null && pane.ActualHeight > 0 && pane.ActualWidth > 0)
{
Point anchor = PopupAnchor.TransformToVisual(null).Transform(new Point());
if (anchor.Y < 240) { PanePopup.VerticalOffset = -240 + (240 - anchor.Y); } else { PanePopup.VerticalOffset = -240; } pane.MakeVisible(); } } [/sourcecode] (PanePopup is the parent Popup. I was expecting the sender to point at the child control, but it gives null.) Not surprisingly, this code was never getting called because sender is always null. Using the same code in a SizeChanged event works perfectly. It's a shame MSDN doesn't cover details like this.

Happy Birthday, Doctor

Today was the 45th anniversary of the first episode of Doctor Who. So to celebrate, we watched ‘An Unearthly Child’ which (TV technology notwithstanding) still holds up. I find it somehow remarkable that something made so long ago can have got so much right. That one episode set up everything that went after it, and it feels remarkably modern, even through the crusty black and white and lumbering cameras.

I also wanted to watch an episode from the latest incarnation, so I chose ‘School Reunion’ which reintroduced Sarah Jane Smith so beautifully. It’s an episode I can’t watch without getting a little emotional. In fact, being of a certain age, Sarah Jane was always my favourite companion, so when this episode was coming up, Doctor Who Magazine printed a full page picture of the Doctor and Sarah Jane:

*snif*

*snif*

And just seeing this picture made me cry. Yes, I’m really that sad.

I love Doctor Who. I love it because I watched it as a child, when it scared me and thrilled me. And I love it now because it scares me and thrills me, and it also moves me like few other TV shows can. But mostly I love it because it is the best TV series on television, and not to love it would be madness.

Problems with Popup

This one’s a bit odd. I’d built A UserControl which contained another UserControl inside a Popup control, as I wanted the second control to appear like a tooltip, but with specific positioning. That all worked fine when it was just appearing on the page, but I also wanted to have a nice ‘fade in’ effect, so I added a couple of storyboards, but when the control was created in the popup, the storyboard complained that it couldn’t resolve names of elements in the Xaml. I’m not sure quite why this was, but I assume it’s due to the way that the Popup is deferring the adding of the child and its elements to the visual tree until it’s made visible. But whatever the reason, it’s a pain.

So I had to build the storyboard in code in the constructor, and that all seemed to work properly. I triggered the fade in animation in the Loaded event handler so it gets triggered when the control is added to the visual tree. The fade out was less of a problem since that was always called when the control was fully built. I had to add an event to the control so that the calling control could be informed when the fade out had completed, so that it could set the popup IsOpen flag to false after the animation had finished.

But I still can’t help thinking I’m missing the ‘correct’ way to do this. There’s not much documentation about how Popup actually works, certainly none that I can find which discusses anything more complex than a static control.

“Value does not fall within the expected range.” when adding a UserControl to a Panel

This just took me a while to puzzle out. I’d built a custom UserControl which worked perfectly while I was initially testing it, but when I modified the application so that I was using several of them, all of a sudden any attempt to add the control to the panel was giving the error “Value does not fall within the expected range.” But it wasn’t giving any clue as to which value was the problem.

One reason it took me a little longer to find the solution is that as well as this custom control (which has a bunch of visual states) I was inserting it into a custom panel, so I wasn’t sure if my code in the panel was causing the problem. But in the end it was something fairly obscure.

My control had an x:Name attribute set on its UserControl root element, added by Blend as part of the visual transitions, and trying to add two copies of the control to the panel meant adding two things with the same name attribute, which is forbidden, and it’s this which was causing the error. If I removed the name attribute, all worked normally.

This does raise an issue, though. If you can’t set a name attribute, how do you declaratively define storyboards which affect properties on the root usercontrol itself? I still don’t know the answer to this. Luckily, I’d already refactored my control such that the transitions could be applied to the LayoutRoot container instead, but that might not be a solution for all scenarios.

Nice post about Deep Zoom positioning

From the Silverlight SDK:

http://blogs.msdn.com/silverlight_sdk/archive/2008/11/18/using-viewportorigin-and-viewportwidth-in-deep-zoom.aspx

Although we’ve all got this worked out already, there’s some good illustrations, which always help to visualise how it works.

It’s a pity that they don’t go further into collection handling – that adds a further layer to the way the geometry is handled, as each subimage has its own ViewportWidth and ViewportOrigin and the way these interact with the parent image isn’t always obvious. I certainly had to work it all out by trial and error.

ScottGu on Silverlight 3

Scott Guthrie, head of virtually everything developy at Microsoft, has just posted a blog on Silverlight, with some new information about Silverlight 3. For me, the most interesting items are:

  • Silverlight now on over 1 in 4 browsers in some form (1.0 and 2, I presume). This is the first concrete number we’ve had about Silverlight penetration. To be honest, I’ve no idea what constitutes a good number, since presumably anything less than Flash’s penetration would be seen as a negative, but at this stage it’s not a bad figure.
  • 3D support and GPU acceleration is coming in SL3 (along with H.264, which had already been announced). The GPU use is very good news for performance reasons. 3D support is a nice-to-have for me, but not killer. But it will make some UI features easier to do.
  • Richer data-binding and more controls. More controls are always nice, and let’s hope that some of WPF’s databinding abilities finally make it to Silverlight. Being able to bind controls to controls directly, for example, would make some things a lot easier.

Of course, we’ve got to wait for these new goodies, but it’s nice to see them coming. Hopefully a CTP or Beta will be around before too long.