ImageViewerクラスは、ビューレイアウト(画像ビューワのレイアウトに言及します)のユーザーを通して、項目の無制限の数をサポートします。このサポートで、ビューワはドキュメントに大量のページとかなりの物理的なイメージサイズをロードするのに用いられることができます。当然のことながら、これらのイメージのどれくらいがいつでも実メモリーで保持されることができるかに対するハードウェア制限があります、そして、画像ビューワはVirtualizerメカニズムをオンデマンドで画像データをロードしていて/アンロードしているサポートに提供します。
サンプルのために、画像ビューワは連続スクロールで垂直方向のレイアウトを使っています。ユーザーは、ページ数が1000で、各ページが一般文書サイズの8.5インチx11インチであるPDFファイルをロードしようとします。ページのビット数が24とすると、未圧縮データの各ページのピクセルサイズは2550 x 3300で実際のサイズは24MBとなり、すべてのページをメモリに保持するには、合計で24MB x 1000 = 24GBの物理メモリが必要になります。
上の垂直方向のレイアウトに、ページ(各々はImageViewerItemオブジェクトで格納しました)のすべては、同時に画面の上で見えないでしょう。実際、ユーザーがビューワをスクロールするか、それを外へズームするとき見えてくるまで、ほとんどは見えないままでいます。ビューワは、項目の数に関する情報と各々の物理サイズをレイアウトとスクロールバー計算のために必要とします。しかし、項目が表示状態にならない限り、画像データ自体は必要ありません。項目がビューに表示されるようになるまで画像データはロードしないのが最善策です。そして項目がビューから消えて見えなくなったときに画像データを破棄します。ImageViewerVirtualizerは、正確にそれを実行します。これは抽象クラスであり、オンデマンドで項目データを簡単にロードまたはアンロードできます。プレースホルダを描画したり多くの項目をメモリ内へキャッシュするよう制御したりとフルに制御できます。
必要とする最初のものは、かなりの頁数によるドキュメントです。ここにそのようなドキュメントを作成するためにLEADTOOLSを使うcodeがある。
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;
}
次に、垂直方向のビューレイアウトで画像ビューワを作成します:
// 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());
現状のままビューワでこのドキュメントをロードしようとしましょう。このcodeは、項目としてドキュメントにすべてのページを追加します:
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();
}
このcodeを実行しようとします。それは、すべてのページをロードして、メモリー不足の例外で32ビットシステムの上できっと失敗する大量の時間かかった後に、64ビットシステムの上で機能します。システムは、すぐにメモリ内のこのような画像データをロードすることができません。明らかに、より良好アプローチが必要です。
画像ビューワの項目に記載されているように、画像ビューワは画像データなしで項目を作成することをサポートします。よくサイズ(ピクセルと解像度)のように、ビューワが必要とするすべては項目の数です。それで、画像データなしで項目を追加するためにcodeを修正させます:
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();
}
このcodeを実行して、ビューワがほとんどすぐに稼働可能であることがわかります。正しいサイズによる空白ページは追加されます、そして、自由にimageのまわりでズームしてパンすることができます。
最終的な手順は、オンデマンドで画像データを値を入力することです。これは、ロードを処理するimage virtualizer派生クラスを作成して、オンデマンドで画像データを開放することによって、簡単に達成されます。
新しいクラスを作成して、以下の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);
}
}
最後に、ビューワでvirtualizerを設定します
// Add our virtualizer
imageViewer.Virtualizer = new MyVirtualizer(fileName);
このデモを実行して:はどうかについて気がつきます
ビューワは、すぐに稼働可能です
スクロールして、ズームするとき、virtualizerはロードして、ページを破棄するためにオンデマンドで呼び出されます
ImageViewerVirtualizerメソッドは、主なUIスレッドより別々のスレッドから呼び出されます。これは、割り込みなしでスムーズにパンし拡大縮小できるようにするため重要です。項目が削除される場合、またはビューワが破棄される場合(AutoDisposeImagesがtrueに設定される場合)にDeleteItemが呼び出され、リソースが確実に解放されるようにします。
RenderItemPlaceholderメソッドは、常にビューワを作成した同じスレッドで呼び出されます。