Dynamically creating DeepZoom composition
page 2 of 3
by James Pritz
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 20083/ 37
Article Contents:

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;
}

View Entire Article

User Comments

No comments posted yet.

Product Spotlight
Product Spotlight 





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


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-29 2:52:20 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search