Using Silverlight DeepZoom for a Document Viewer
August 27, 2009
Roscoe, N.Y.
Earlier this summer I wrote a custom document viewer for Silverlight to display the pages of an 82-page document created in 1983. I originally wrote this document on an Osborne 1 using WordStar, so it seemed impractical to go back to the original file. Moreover, although the daisy-wheel-printed copy I had was in fine shape, it also contained diagrams that I had added by hand, so it was not really a good candidate for converting back to ASCII using optical-character-recognition software. It became very obvious to me that I needed to display the 82 pages as bitmaps.
(The document is part of a collection of stuff that I hope to post soon relating to my hobby in electronic music between 1974 and 1982. The posted material will also include about ½ hour of music and about 3 hours of "music.")
Because I was intending to display this document in a Silverlight application as 8½ × 11" pages, I scanned the 82 pages at 96 DPI with gray-shades so they have a nice paper-like look to them. Each resultant PNG file is 816 × 1056 pixels, and an average of about 450K in size, for a total of 36.5M.
Obviously the Silverlight app shouldn't download all 36.5M to the user when the application starts up — particularly considering that this document is so dull that most readers will abandon it after a paragraph or two. So I wrote a little WPF program to create 82 thumbnails from these images: 74 × 96 pixels, and about 6K each.
When the document-viewer application starts up, it downloads all the thumbnails and displays them in a grid the size of the paper page. You can run the program here:
DocumentViewerWithThumbnails.html
Each of the thumbnails is an Image element. When you click one, several animations are initiated that scale the Image element and move it to the upper-left corner, which results in an enlarged and very blurry thumbnail. But at the same time, the program creates a WebClient object to download the full-size bitmap asynchronously. When that bitmap is available, it replaces the blurry expanded thumbnail. Although the program will eventually also include Page-Forward and Page-Back buttons, I was generally pleased with the results.
Here's the source code, but excluding the images themselves. The DocumentViewerWithThumbnails.Web/ClientBin/Document directory contains an XML file the program loads to help access the files, and you can use that to download the actual images if you want to experiment with them at home.
Until recently I hadn't even thought about using DeepZoom for this application. To me DeepZoom was all about panning and zooming through large high-resolution bitmaps like on the Hard Rock Memorabilia site. But after reading some blog entries about the July 2009 Deep Zoom Composer release, it seemed like DeepZoom might provide a better transition between the blurry enlarged thumbnail and the full-size image.
The Deep Zoom technology is a way of chopping up bitmap images to make them cheaper to download when displayed at small sizes, and to allow gradations up in size. Consider a large bitmap: The Deep Zoom Composer chops that bitmap into 256 × 256 pixel tiles, or smaller for the edges if the original horizontal and vertical pixel dimensions are not multiples of 256. It then reduces the original bitmap by halving the height and width (so one-quarter the number of pixels) and chops that into 256 × 256 pixel tiles, and then again and again until the original is less than one tile size. The resultant bitmaps — divided into subdirectories and referenced with XML files — allow any part of this bitmap to be displayed at any size with a minimum of downloading.
Customarily, you use the Deep Zoom Composer to assemble a bunch of different bitmaps into a composite image. However, that's not what I wanted. I wanted to treat each page of the document fairly independently. It seemed as if I could get this by using a Deep Zoom collection, as described in Kirupa Chinnathambi's blog on Improved Collection Support in Deep Zoom Composer.
Following Kirupa's instructions, after creating a new project in the Deep Zoom Composer, I clicked "Add image..." to load the 82 bitmaps (but not the thumbnails). I then went straight for the Export button. I selected the Custom tab, clicked the radio button to "Export as a collection," chose the "Tag Browser" template, and a PNG format.
What I got was a Silverlight app (which I ignored), and a 72.4M collection of directories and bitmaps, all referenced with a file named dcz_output.xml. I wrote a second document viewer using DeepZoom which you can run here:
DocumentViewerWithDeepZoomCollection.html
In the DocumentViewerWithThumbnails application, each of the thumbnails is a separate Image element. When the app loads, you can actually see them being created and filled with the images. In the DeepZoom version, all the images in the program are displayed with a single MultiScaleImage element with its Source property set to dcz_output.xml. The blurry images come up immediately but then come into better view as more data is loaded.
Like Image, MultiScaleImage derives from FrameworkElement, so it can be part of layout and get user input events. When using a DeepZoom collection (as I'm doing here), the SubImages property of MultiScaleImage contains a collection of MultiScaleSubImage objects (82 in my case).
MultiScaleSubImage contains two crucial properties named ViewportWidth (of type double) and ViewportOrigin (of type Point) that let you position these sub-images relative to the MultiScaleImage. (There is no ViewportHeight property to accompany ViewportWidth; DeepZoom always maintains the correct aspect ratio so the second property is not needed.) These properties might be backwards from how you would normally think of them. For example, if you want a sub-image to be 1/10th the width and height of the MultiScaleImage in which it appears, set ViewportWidth to 10. The ViewportOrigin property is a relative coordinate based on the rendered width of the sub-image. For example, if you want your sub-image positioned 2 widths to the right and 3 widths below the upper-left corner of the MultiScaleImage, set ViewportOrigin to (–2, –3). It takes a little while getting accustomed to this math to dynamically lay out the sub-images.
The first thing I discovered was that the Deep Zoom Composer did not order this SubImage collection in any discernable way, and certainly not alphabetically by filename. There is apparently no way to match up MultiScaleSubImage objects with filenames programmatically, so I had to manually edit the dcz_output.xml file to order the images numerically.
Although MultiScaleImage derives from FrameworkElement, MultiScaleSubImage does not, which means that these individual sub-images cannot get their own mouse input. Any hit-testing needs to be handled "manually" at the MultiScaleImage level. Again, there's some math involved but it's not very hard. Here's the source code but again without the actual bitmaps (or supporting files).
I'm not sure which version I prefer. The DeepZoom version certainly has some advantages: I like having all the little images come up immediately, and it's more satisfying seeing a smooth fade from blurry to sharp rather than the instant transition in the non-DeepZoom version.
However, for this particular application, the DeepZoom version has some performance issues. In DocumentViewerWithThumbnails, I clocked an average of about 1.1 seconds between the mouse click and the snap into the full image. With DocumentViewerWithDeepZoomCollection, about 4.3 seconds were required to completely load the highest resolution image. (I think it must be going through all the intermediate steps, which means that it's downloading many more bitmaps and some superfluous bitmap data.) However, the page became readable before the final view, and certainly looked better than the enlarged thumbnail. (Times are based on a RoadRunner broadband connection provided by Time Warner Cable of Hudson Valley.)
Unless I'm missing something, the documentation of DeepZoom and the Deep Zoom Composer seems quite sparse, so I'm left with several questions. For example, it is my understanding that DeepZoom itself allows tile sizes other than 256 × 256, but I couldn't see any way to persuade the Deep Zoom Composer to break up tiles into other sizes. In this application, wouldn't it make more sense to base the tile size on integral divisors of the bitmap dimensions?
And what if I had a bunch of Deep-Zoomable images that I wanted to use throughout the application and not positioned side-by-side? Icon-like images, for example? I'm not sure a DeepZoom collection would be the right approach, but this seems to be the only way to persuade the Deep Zoom Composer to operate in a "batch" mode.
I suspect that most applications of DeepZoom (other than the most trivial) will want to bypass the Deep Zoom Composer and chop up the bitmaps and generate the XML files in ways that Deep Zoom Composer doesn't allow. Yet there don't seem to be any tools for doing this.
NOTE: Due to some weirdly archaic disk space limitations set by the company currently hosting my web site (EarthLink), some of the data for the last couple pages of the DocumentViewerWithDeepZoomCollection are missing. These pages will not become clear enough to read. I will be removing my site from EarthLink as soon as I can.