Dynamically creating DeepZoom composition
 
Published: 22 May 2009
Unedited - Community Contributed
Abstract
Explore the process of creating the files to support DeepZoom composition (image pyramids and dz xml files). Assumes a basic understanding of deepzoom. The foundation for the article can be seen on my blog: problog.jamespritz.com.
by James Pritz
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 15726/ 51

Introduction

Admittedly, the need to create an image pyramid in code is not often encountered in system requirements; unless, of course, one is developing a composer for DeepZoom or any other zooming technology.  Microsoft's DeepZoom composer has the designer aspect covered, so this article actually assumes the road less traveled, hypothesizing that one could imagine a need to create image pyramids, and consequently "image collections" and "compositions" on the fly.  Two examples immediately come to mind, the first of which inspired this article. 

Writing from experience, an automobile body shop quality assurance department is required to review, often many, high resolution images of vehicle damage; these images are uploaded from the body shop, and typically loaded onto a viewing webpage at full resolution, which becomes increasingly tedious.  An alternative is to use DeepZoom, which requires a process to dynamically create these necessary DeepZoom files, following the upload of the original images, so that they can be presented in a zoom-able thumbnail viewer on demand.   As a second example, this process could also be used in a photo sharing application, allowing users to upload high resolution pictures without requiring the viewer to download huge image files.

Target Audience

The reader should have a basic understanding of zooming technology, not necessarily of Microsoft's implementation (DeepZoom).  Most importantly, the reader should know what an image pyramid is.  Silverlight experience is helpful, as it will help the reader visualize the implementation; however, this article focuses mainly on the code used to create an image pryamid, which primarily involves using objects from the System.Drawing namespace and some IO.  Reader should also have a moderate understanding of modern programming languages and concepts; this article uses C#, but can be clearly understood.

Scope

This article focuses primarily on the process of creating an image pyramid.

The Process

Because this article focuses only on one step of a larger process, yet uses code created for the larger process, it is challenging to give code examples that do not intimate elements of the larger process.  Therefore, before diving in, perhaps a high level overview will help with any abstractions.

1.    Output.  Yes, output first.  Because we are dealing with high-res, memory consuming images, we must put these images to rest as soon as they are processed.  Output, therefore occurs throughout the process.

2.    Input.  Iterate through collection of images, and process individually.

·         Retrieve Images.  Obvious, yes, but none-the-less complicated.  This code was created for a real-world implementation, so the implementation has to be somewhat more professional then the generic 'hello world' example that seems to prevail these types of articles.  The code does not assume the source of the image; it allows for a custom provider that must only implement a basic reader interface.

·         [Abstract] Compose the images for display.  Not so obvious!  Remember, the point is display multiple high res images on a viewer; at some point, we need to determine the position and scaling of these images in the viewer.  Because we are doing this 'on the fly', our compositions have to be pre-calculated early in the process, and often generic.  The code in the download, for example, creates 100 pixel width thumbnails, positioned horizontally with a 10 pixel gap. 

3.    Process

·         For each image, create the image pyramid and output to the file system.  For simplicity, it is fortunate that DeepZoom can only access these artifacts if they are local; therefore we can assume that the output will ultimately be the local file system. 

·         [Abstract] At the completion of each image, the process will register its output with a DZ file manager that will ultimately write the necessary DZ file manifests at the end of the process, thus creating the collection.

·         [Abstract] After all images are processed into pyramids, the process will create a composition pyramid, and write the final DZ file manifest that is ultimately fed to the Viewer.

Output

DeepZoom can only access source files from a local file system, and those files, of course, must adhere to a defined structure.  Abstracting the implementation, this process uses a class that implements the following contract.  This class can be extended to customize the root folder for the output, without allowing for corruption of the underlying folder structure as needed by DeepZoom.

public interface IDZFileWriter
{
void WritePyramidImage(IDZImage image, int layer, int column, int row);
void WriteImageDZFile(IDZImage image);
//remaining methods left out intentionally
}

The WritePyramidImage method will write the scaled image to the appropriate folder, using the supplied column and row for the file naming convention.

The WriteImageDZfile will create the DZ file that instructs DeepZoom where, and at what scale to render the associated image in the viewer.

Input

Abstracting the source of the images, this example uses and interface for which the implementation can be customized to draw its images from a local file system, a web service, or any other imaginable source. 

public interface IDZImageReader
{
System.Collections.Generic.IEnumerable<IDZImage> Images(IDZComposer composer);
int ImageCount { get; }
}

The implementation is responsible for the underlying mechanics of reading the image from its defined source, processing each image through a custom composer, then returning them iteratively to the caller as a custom object which contains both the image as well as elements that indicate where the image will reside in the composition (as calculated by the composer).  For this article, it is necessary only to know that the image is returned in a System.Drawing.Image class.  Coincidentally, DeepZoom will only support image formats that the Image class can support; another fortunate simplification.  At this point, the process has no knowledge of the source of the image.

Process

For each image in the collection, create a pyramid.  It is very useful to know that this process will yield files that amount to in size approximately 1.33 percent of the original file.  The main method takes in an implementation of IDZImage, and of IDZFileWriter which is responsible for managing the output.

public void ProcessImagePyramid(IDZImage image, IDZFileWriter writer)
{

On a personal note, I believe that all variables used in an example should be visible, so that the reader does not have to assume.

    //number of pyramid layers, using 256x256 tiles
    int numberOfLevels;
    int currentLevel;
    int columns, rows, column, row;
    int tileWidth, tileHeight;
    int imageWidth, imageHeight;
    int x, y;
    int padLeft = 0, padRight = 0, padTop = 0, padBottom = 0;
    Rectangle coords;
    Bitmap tile;
 
    Bitmap bm256 = null;
 

1.    Determine the number of levels in the pyramid.  This can be determined using the Log base 2 of the larger of the image's width and height.  Therefore, a factor between 257 and 512 would yield 9 levels; (129 and 256) à 8 levels; (65 and 128) à 7 levels… and so on.  The 0'th level is always the image scaled to 1 pixel.  Obviously, we would not do this for an image that is only 512 pixels.

    numberOfLevels = (int)Math.Ceiling(Math.Log(Math.Max
        (image.Image.Width, image.Image.Height), 2));
 

2.    Write the DZ file that instructs DeepZoom how and where to render the image on the viewer.  Remember, although abstracted, the IDZImage that is passed includes the coordinates and size of the image as it should be rendered.

    writer.WriteImageDZFile(image);
 
 
    /* process each level of pyramid
     */
    for (currentLevel = numberOfLevels; currentLevel >= 0; currentLevel--)
    {
 
        imageWidth = image.Image.Width;
        imageHeight = image.Image.Height;
 
 

3.    Tile each layer.  If the image at the current level is larger than 256 pixels, we need to chop it up into max 256x256 tiles, with a 1 pixel overlap.  Interestingly, this algorithm is slightly different from the one used in the current version of DeepZoom composer.  Including the overlap, this algorithm will not yield any tile larger than 256 width or height, whereas Microsoft's algorithm will yield tiles that are 258 pixels in width or height.  This modification was made on the advice of Ben Vanik (www.noxa.org), who asserts that this is more efficient.  Both will run, and I am inclined to take his advice.  Furthermore, I understand that Microsoft is aware of the inefficiency and has addressed it in the next version.  The TILESIZE constant is defined elsewhere to equal 256, OVERLAP = 1.

        if ((image.Image.Width <= (TILESIZE + 2)) && 
            (image.Image.Height <= (TILESIZE + 2))
            )
        {
            if (bm256 == null) bm256 = image.Image;
            writer.WritePyramidImage(image, currentLevel, 0, 0);
        }
        else
        {
            /* Chop it up into max(256x256) tiles, overlapping by the value 
             * of OVERLAP constant.  
             */
            decimal tileSize = (decimal)(TILESIZE);
 

4.    Process each tile, column then row, using a 1 pixel overlap.

            columns = (int)Math.Ceiling(image.Image.Width / tileSize);
            rows = (int)Math.Ceiling(image.Image.Height / tileSize);
 
            for (column = 0; column < columns; column++)
            {
 
                padLeft = (column == 0) ? 0 : 1;
                padRight = (column == (columns - 1)) ? 0 : 1;
 
                x = (column * TILESIZE) - column;
                for (row = 0; row < rows; row++)
                {
 
                    padTop = (row == 0) ? 0 : 1;
                    padBottom = (row == (rows - 1)) ? 0 : 1;
 
                    y = (row * TILESIZE) - row;
 
                    //if the remaining column is less than 256, 
                    //return the difference
                    tileWidth = ((imageWidth - x) < TILESIZE) 
                        ? (imageWidth - x) 
                        : TILESIZE;
                    tileHeight = ((imageHeight - y) < TILESIZE) 
                        ? (imageHeight - y) 
                        : TILESIZE;
 
                    coords = new Rectangle(x, y, 
                        tileWidth + padLeft + padRight, 
                        tileHeight + padTop + padBottom);
 
                    tile = new Bitmap(tileWidth + padLeft + padRight, 
                        tileHeight + padTop + padBottom);
                    Graphics.FromImage(tile).DrawImage(
                        image.Image, 
                        new Rectangle(0, 0, tileWidth + padLeft + padRight, 
                            tileHeight + padTop + padBottom), 
                            coords, GraphicsUnit.Pixel);
 
                    DZImage tileImage = new DZImage(image.Name, tile);

5.    Write each file to disk as soon as it is created, therefore not holding it in memory.

                    writer.WritePyramidImage(tileImage, currentLevel, 
                        column, row);
 
                }
            }
        }
 
        if (currentLevel >= 0)
        {
            //again, thanx to Ben Vanik.... 
            imageHeight = (int)Math.Ceiling((double)imageHeight / 2);
            imageWidth = (int)Math.Ceiling((double)imageWidth / 2);
 
            tile = new Bitmap(imageWidth, imageHeight);
            Graphics.FromImage(tile).DrawImage(
                image.Image,
                new Rectangle(0, 0, imageWidth, imageHeight),
                new Rectangle(0, 0, image.Image.Width, image.Image.Height),
                GraphicsUnit.Pixel);
            image.Image = tile;
        }
    }
 
    image.Image = bm256;
}
Conclusion

All of the code for this article can be found here.  Although this article only focuses on the creation of an image pyramid, this code can be used to process several images, creating the image pyramids as well as the collection and basic thumbnail composition.  This download supplements the code in this article with the following.

·         Classes that represent, and can be serialized to (and conversely de-serialized from) the dz definition files for DeepZoom.  ** this article does not cover that process, but the reader should be able to reverse engineer from the code.

·         Classes that automate the creation of dz files and folder structure necessary to support DeepZoom.  Again, not covered in this article.

·         Classes that can be extended to create customized input and output, including customized compositions.

Reference

·         My Blog: http://problog.jamespritz.com

·         All of the code to create the pyramid, as well as the DZ files, was reverse engineered from the output generated by Microsoft's DeepZoom Composer, Preview 2 from October 2008.  This, as well as many other resources can be downloaded via silverlight.net.

·         A basic example of the output can be found on my personal website, at http://www.jamespritz.com/myfirst/deepzoom/viewer.aspx.  The images were created with the code in this article.

·         A comprehensive explanation of image pyramids and zooming technology can be found on msdn.microsoft.com, under Design Tools, Deep Zoom Composer.  This site was instrumental to my understanding of DeepZoom and Silverlight.

·         http://gasi.ch/blog/ - Daniel Gasienca's Blog, much of which focuses on Zooming, especially OpenZoom.

·         http://grou.ps/zooming - Zooming users group.



User Comments

No comments posted yet.

Product Spotlight
Product Spotlight 





Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2014 ASPAlliance.com  |  Page Processed at 10/26/2014 1:06:40 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search