如何在千兆像素位图上实现平移/缩放?

在我的项目中,我正在使用(未压缩的16位灰度)千兆像素图像,这些图像来自高分辨率扫描仪,用于测量目的。 由于这些位图无法加载到内存中(主要是由于内存碎片),我正在使用磁贴(以及磁盘上的平铺TIFF)。 (请参阅StackOverflow主题 )

我需要以Google Maps或DeepZoom的方式实现平移/缩放。 我必须在将它呈现在屏幕上之前动态应用图像处理,因此我不能使用直接访问图像文件的预先烹饪的库。 对于缩放,我打算在我的文件(金字塔存储)中保留多分辨率图像。 最有用的步骤似乎是+ 200%,50%并且显示全部。

我的代码库目前是C#和.NET 3.5。 目前我假设表格类型,除非WPF在这方面给我很大的优势。 我有一个方法可以返回底层图像的任何(已处理)部分。

具体问题:

  • 关于如何通过按需生成图像部件来实现此平移/缩放的提示或参考
  • 任何可用作基础的代码(最好是商业版或LGPL / BSD版许可证)
  • 可以使用DeepZoom(也就是说有一种方法可以提供一个函数来为当前的缩放级别提供正确的分区?)(我还需要像素精确的寻址)

此CodeProject文章:生成… DeepZoom图像集合可能是一个有用的阅读,因为它讨论生成DeepZoom图像源。

这篇MSDN文章有一个动态深度缩放部分:在运行时提供图像像素,并链接到这个Mandelbrot资源管理器 ,它有点’听起来类似于你想要做的事情(即他正在生成mandelbrot集的特定部分 -需求;您希望按需检索千兆像素图像的特定部分。

我认为答案“可以使用DeepZoom吗?” 可能是“是”,但是因为它只在Silverlight中可用,所以如果需要WinForms / WPF客户端应用程序,则必须使用嵌入式Web浏览器控件做一些技巧。

对不起,我无法提供更具体的答案 – 希望这些链接有所帮助。

ps我不确定Silverlight是否支持TIFF图像 – 除非你转换为另一种格式,否则这可能是一个问题。

我决定自己尝试一下。 我想出了一个简单的GDI +代码,它使用了我已经获得的磁贴。 我只是过滤掉与当前剪切区域相关的部分。 它就像魔术一样! 请在下面找到我的代码。 (表单设置双缓冲以获得最佳效果)

protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); Graphics dc = e.Graphics; dc.ScaleTransform(1.0F, 1.0F); Size scrollOffset = new Size(AutoScrollPosition); int start_x = Math.Min(matrix_x_size, (e.ClipRectangle.Left - scrollOffset.Width) / 256); int start_y = Math.Min(matrix_y_size, (e.ClipRectangle.Top - scrollOffset.Height) / 256); int end_x = Math.Min(matrix_x_size, (e.ClipRectangle.Right - scrollOffset.Width + 255) / 256); int end_y = Math.Min(matrix_y_size, (e.ClipRectangle.Bottom - scrollOffset.Height + 255) / 256); // start * contain the first and last tile x/y which are on screen // and which need to be redrawn. // now iterate trough all tiles which need an update for (int y = start_y; y < end_y; y++) for (int x = start_x; x < end_x; x++) { // draw bitmap with gdi+ at calculated position. dc.DrawImage(BmpMatrix[y, x], new Point(x * 256 + scrollOffset.Width, y * 256 + scrollOffset.Height)); } } 

为了测试它,我创建了一个80x80的256个瓦片(420 MPixel)的矩阵。 当然,我必须在现实生活中添加一些延迟加载。 如果尚未加载,我可以将瓷砖留空(空)。 事实上,我已经让我的客户在他的机器上坚持使用8 GByte,所以我不必太在乎性能。 一旦加载的瓷砖可以留在内存中。

 public partial class Form1 : Form { bool dragging = false; float Zoom = 1.0F; Point lastMouse; PointF viewPortCenter; private readonly Brush solidYellowBrush = new SolidBrush(Color.Yellow); private readonly Brush solidBlueBrush = new SolidBrush(Color.LightBlue); const int matrix_x_size = 80; const int matrix_y_size = 80; private Bitmap[,] BmpMatrix = new Bitmap[matrix_x_size, matrix_y_size]; public Form1() { InitializeComponent(); Font font = new Font("Times New Roman", 10, FontStyle.Regular); StringFormat strFormat = new StringFormat(); strFormat.Alignment = StringAlignment.Center; strFormat.LineAlignment = StringAlignment.Center; for (int y = 0; y < matrix_y_size; y++) for (int x = 0; x < matrix_x_size; x++) { BmpMatrix[y, x] = new Bitmap(256, 256, PixelFormat.Format24bppRgb); // BmpMatrix[y, x].Palette.Entries[0] = (x+y)%1==0?Color.Blue:Color.White; using (Graphics g = Graphics.FromImage(BmpMatrix[y, x])) { g.FillRectangle(((x + y) % 2 == 0) ? solidBlueBrush : solidYellowBrush, new Rectangle(new Point(0, 0), new Size(256, 256))); g.DrawString("hello world\n[" + x.ToString() + "," + y.ToString() + "]", new Font("Tahoma", 8), Brushes.Black, new RectangleF(0, 0, 256, 256), strFormat); g.DrawImage(BmpMatrix[y, x], Point.Empty); } } BackColor = Color.White; Size = new Size(300, 300); Text = "Scroll Shapes Correct"; AutoScrollMinSize = new Size(256 * matrix_x_size, 256 * matrix_y_size); } 

原来这是容易的部分。 在后台完成异步multithreadingi / o操作非常困难。 不过,我已经按照这里描述的方式工作了。 要解决的问题是与此主题相关的更多.NET / Formmultithreading。

在伪代码中,它的工作方式如下:

 after onPaint (and on Tick) check if tiles on display need to be retrieved from disc if so: post them to an async io queue if not: check if tiles close to display area are already loaded if not: post them to an async io/queue check if bitmaps have arrived from io thread if so: updat them on screen, and force repaint if visible 

结果:我现在拥有自己的自定义控件,它使用大约50 MByte来快速访问任意大小(平铺)的TIFF文件。

我想您可以按照以下步骤解决此问题:

  1. 图像生成:

    • 将您的图像分割成小分辨率的多个子图像(图块),例如500×500。 这些图像深度为0
    • 组合一系列深度为0(4×4或6×6)的图块,调整组合大小,生成一个深度为500×500像素的新图块1。
    • 继续这种方法,直到只使用几个瓷砖获得整个图像。
  2. 图像可视化

    • 从最高深度开始
    • 当用户拖动图像时,动态加载切片
    • 当用户缩放图像的区域时,减小深度,以更高的分辨率加载该区域的图块。

最终结果与Google地图类似。