Error processing SSI file
LEADTOOLS Controls (Leadtools.Topics.Controls assembly)

Show in webframe

The ImageViewer class supports unlimited number of items through the user of the view layouts (Refer to Image Viewer Layouts). With this support, the viewer can be used to load a document with large amount of pages and large physical image sizes. Naturally, there's a hardware limit on how many of these images can be kept in the physical memory at any time and the Image Viewer provides the Virtualizer mechanism to support loading/unloading image data on demand.

For an example, the image viewer is using a vertical layout with continuous scroll. And the user is trying to load a PDF file with 1000 pages in it with each page is a typical document size at 8.5 by 11 inches. Assuming the pages are at 24 bits per pixel, that's a pixel size of 2550 by 3300 and a physical size of 24MB for each page of uncompressed data and a total of 24MB * 1000 = 24GB of physical memory required to hold all these pages in memory.

In the vertical layout above, the pages (each contained in an ImageViewerItem object) will not be all visible on the screen at the same time. In fact, most of them will stay invisible till they come into view when the user scrolls the viewer or zooms it out. The viewer needs the information on the number of items and the physical size of each for layout and scrollbar calculation. But the image data itself is not needed unless the item is visible. A better approach would be to not load the image data till the item is about to become visible into the view. And to discard the image data when the item goes out of view and is invisible. The ImageViewerVirtualizer performs exactly that. It is an abstract class that allows you to easily load and unload the items data on demand with full control for rendering place-holders and controlling the number of items to cache in memory.

Example

Test Document

The first thing we need is a document with large number of pages. Here is code that uses LEADTOOLS to create such a document:


             private static void CreateTestDocument()
             {
                // Create a 400 pages PDF file. Each page is 8.5 by 11 inches
                var width = 8.5;
                var height = 11.0;
                var resolution = 300;
            
                var pixelWidth = (int)(width * resolution + 0.5);
                var pixelHeight = (int)(height * resolution + 0.5);
            
                var pageCount = 400;
                var fileName = string.Format(@"C:\Users\Public\Documents\LEADTOOLS Images\{0}Pages.pdf", pageCount);
                if (File.Exists(fileName))
                   File.Delete(fileName);
            
                using (var codecs = new RasterCodecs())
                {
                   var pageMode = CodecsSavePageMode.Overwrite;
                   for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)
                   {
                      using (var rasterImage = CreatePageImage(pixelWidth, pixelHeight, resolution, pageNumber))
                      {
                         codecs.Save(rasterImage, fileName, RasterImageFormat.RasPdfLzw, 24, 1, 1, -1, pageMode);
                         pageMode = CodecsSavePageMode.Append;
                      }
                   }
                }
             }
            
             private static RasterImage CreatePageImage(int pixelWidth, int pixelHeight, int resolution, int pageNumber)
             {
                Pen[] pens =
                {
                   Pens.Red,
                   Pens.Green,
                   Pens.Blue,
                };
            
                Brush[] brushes =
                {
                   Brushes.Red,
                   Brushes.Green,
                   Brushes.Blue
                };
            
                Pen pen = pens[pageNumber % 3];
                Brush brush = brushes[pageNumber % 3];
            
                var rasterImage = RasterImage.Create(pixelWidth, pixelHeight, 24, resolution, RasterColor.FromKnownColor(RasterKnownColor.White));
                var hdc = RasterImagePainter.CreateLeadDC(rasterImage);
                using (var graphics = Graphics.FromHdc(hdc))
                {
                   var rc = new Rectangle(0, 0, pixelWidth, pixelHeight);
                   graphics.DrawRectangle(pen, rc.X, rc.Y, rc.Width - 2, rc.Height - 2);
                   graphics.DrawRectangle(pen, rc.X + 1, rc.Y + 1, rc.Width - 3, rc.Height - 3);
            
                   var text = string.Format("Page {0}", pageNumber);
                   using (var font = new Font("Arial", 72, FontStyle.Regular))
                   using (var sf = new StringFormat())
                   {
                      sf.Alignment = StringAlignment.Center;
                      sf.LineAlignment = StringAlignment.Center;
            
                      var textSize = graphics.MeasureString(text, font);
                      float y = 10;
                      float x = 10;
                      while (y < pixelHeight)
                      {
                         while (x < pixelWidth)
                         {
                            graphics.DrawString(text, font, brush, x, y);
                            x += textSize.Width + 80;
                         }
            
                         y += textSize.Height + 80;
                         x = 10;
                      }
                   }
                }
                RasterImagePainter.DeleteLeadDC(hdc);
                return rasterImage;
             }
             

Image Viewer

Next, create an image viewer with a vertical view layout:


             // Create a new image viewer instance with a vertical layout
             ImageViewerViewLayout viewLayout = new ImageViewerVerticalViewLayout();
             ImageViewer imageViewer = new ImageViewer(viewLayout);
             // Set some properties
             imageViewer.Dock = DockStyle.Fill;
             imageViewer.BackColor = Color.Bisque;
             // Add a border (need some padding as well)
             imageViewer.ImageBorderThickness = 1;
             imageViewer.ItemPadding = new Padding(imageViewer.ImageBorderThickness);
             // Take into consideration the resolution image when viewing, so an 8.5 by 11 inch image size
             // will take 8.5 by 11 inches on screen when zoom is 1:1
             imageViewer.UseDpi = true;
             // Add it to the form
             this.Controls.Add(imageViewer);
             imageViewer.BringToFront();
             // Add pan/zoom interactive mode. Click and drag to pan the image and ctrl-click
             // and drag to zoom in/out
             imageViewer.InteractiveModes.Add(new ImageViewerPanZoomInteractiveMode());
             

Adding Pages without Virtualizer

Let us try to load this document in the viewer as is. This code will add all the pages into the document as items:


             var fileName = @"C:\Users\Public\Documents\LEADTOOLS Images\400Pages.pdf";
             using (var codecs = new RasterCodecs())
             {
                codecs.Options.RasterizeDocument.Load.Resolution = 300;
            
                // Do not update till we have all the pages
                imageViewer.BeginUpdate();
            
                // Get the number of pages
                int pageCount = codecs.GetTotalPages(fileName);
                for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)
                {
                   var item = new ImageViewerItem();
                   item.Image = codecs.Load(fileName, pageNumber);
                   imageViewer.Items.Add(item);
                }
            
                imageViewer.EndUpdate();
             }
             

Try to run this code. It will work on 64-bit systems after taking a large amount of time to load all the pages and most probably fail on 32-bit systems with an Out Of Memory exception. The system cannot load all this image data in memory at once. Clearly a better approach is needed.

Adding Pages with Size Information Only

As described in Image Viewer Items, the image viewer supports creating items without image data. All the viewer needs is the number of items as well their sizes (pixel and resolution). So let us modify the code to add the items without the image data:


             var fileName = @"C:\Users\Public\Documents\LEADTOOLS Images\400Pages.pdf";
             using (var codecs = new RasterCodecs())
             {
                codecs.Options.RasterizeDocument.Load.Resolution = 300;
             
                // Do not update till we have all the pages
                imageViewer.BeginUpdate();
             
                // Get the number of pages and the size of each page
                int pageCount;
                LeadSize imageSize;
                LeadSizeD resolution;
             
                using (var info = codecs.GetInformation(fileName, true))
                {
                   // Get number of pages
                   pageCount = info.TotalPages;
                   // Get size in pixels
                   imageSize = LeadSize.Create(info.Width, info.Height);
                   // Get resolution
                   resolution = LeadSizeD.Create(info.XResolution, info.YResolution);
                }
             
                for (var pageNumber = 1; pageNumber <= pageCount; pageNumber++)
                {
                   var item = new ImageViewerItem();
                   // Set the members needed for the viewer to create the correct layout
                   item.ImageSize = imageSize;
                   item.Resolution = resolution;
                   // Add it
                   imageViewer.Items.Add(item);
                }
             
                imageViewer.EndUpdate();
             }
             

Run this code and notice that the viewer is up and running almost instantly. Blank pages with the correct size are added and you can zoom and pan around the image freely.

The Virtualizer

The final step is to populate the image data on demand. This is easily accomplished by creating an image virtualizer derived class that handles loading and freeing the image data on demand.

Create a new class and add the following code:


             // Class that derives from the abstract ImageViewerVirtualizer
             public class MyVirtualizer : ImageViewerVirtualizer
             {
                public MyVirtualizer() { }
            
                // The file containing the large image
                private string _fileName;
            
                public MyVirtualizer(string fileName) :
                   base()
                {
                   // Save the file
                   _fileName = fileName;
                   // Number of items to keep cached in memory, the default is 16. Changing to 4
                   this.MaximumItems = 4;
                }
            
                protected override object LoadItem(ImageViewerItem item)
                {
                   // This method is called when an item comes into view
                   // and is not cached in memory
            
                   // For this example, all we need is to load the image
                   // from the original file. But we can also load other
                   // state and data from a database or using deserialization.
            
                   // In this example, the item index is the page index
                   // However, we can use the item .Tag property or derive our
                   // own class to hold the data needed to load the page
            
                   // Index is 0-based, so add 1 to get the page number
                   var pageNumber = this.ImageViewer.Items.IndexOf(item) + 1;
            
                   // Load the page and return it
                   using (var codecs = new RasterCodecs())
                   {
                      codecs.Options.RasterizeDocument.Load.Resolution = 300;
                      return codecs.Load(_fileName, 0, CodecsLoadByteOrder.BgrOrGray, pageNumber, pageNumber);
                   }
                }
            
                protected override void SaveItem(ImageViewerItem item, object data)
                {
                   // This method is called when an item is about to be deleted
                   // from the cache. In this example, we do not have anything to do
                   // but you can modify the code if your application needs to serialize
                   // data to disk or a database for example
                }
            
                protected override void DeleteItem(ImageViewerItem item, object data)
                {
                   // This method is called when the item is no longer used
                   // In this example, we simply dispose the RasterImage we loaded
                   var image = data as RasterImage;
                   if (image != null)
                      image.Dispose();
                }
            
                protected override void RenderItemPlaceholder(ImageViewerRenderEventArgs e)
                {
                   // This method is called while an item is being loaded and give us a chance
                   // to offer a hint to the user
            
                   // Lets render a Loading ... message on the item
                   var transform = this.ImageViewer.GetItemImageTransform(e.Item);
            
                   var graphics = e.PaintEventArgs.Graphics;
                   var pt = LeadPointD.Create(0, 0);
                   pt = transform.Transform(pt);
                   graphics.DrawString("Loading...", this.ImageViewer.Font, Brushes.Black, (float)pt.X, (float)pt.Y);
                }
             }
             

Finally, set our virtualizer in the viewer


             // Add our virtualizer
             imageViewer.Virtualizer = new MyVirtualizer(fileName);
             

Run this demo and notice how:

Virtualizer and Multi-Threading Notes

The ImageViewerVirtualizer methods will be called from a separate thread than the main UI thread. This is important to ensure that the user can pan and zoom smoothly without interruptions. DeleteItem will be called when items are removed or the viewer is disposed (if AutoDisposeImages is set to true) to ensure the resources are freed.

The RenderItemPlaceholder method is always called in the same thread that created the viewer.

See Also

Reference

Using ImageViewer
Using Image Viewer with Windows.Forms
Image Viewer Appearance
Image Viewer Items
Image Viewer Bounds and Transform
Image Viewer Transformation
Image Viewer Layouts
Image Viewer Rendering
Image Viewer Scrolling
Image Viewer Interactive Modes
Image Viewer Other Operations
Image Viewer Single Item Mode
Image Viewer Drag and Drop

Error processing SSI file