7

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

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

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

具体问题:

  • 关于如何通过按需生成图像部分来实现此平移/缩放的提示或参考
  • 任何可用作基础的代码(最好是商业或 LGPL/BSD 之类的许可证)
  • 可以使用 DeepZoom 吗(即有没有一种方法可以提供一种功能来为当前缩放级别提供正确分辨率的图块?)(我仍然需要像素精确的寻址)
4

3 回答 3

3

我决定自己尝试一下。我想出了一个简单的 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 GB,这样我就不必太在意性能了。加载后的图块可以保留在内存中。

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

原来这是最容易的部分。在后台完成异步多线程 i/o 更难实现。尽管如此,我仍然按照这里描述的方式工作。要解决的问题更多是与本主题相关的 .NET/Form 多线程。

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

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 文件。

于 2010-02-12T14:26:34.257 回答
3

这篇CodeProject 文章:Generate...DeepZoom Image Collection可能是一本有用的读物​​,因为它讨论了生成 DeepZoom 图像源。

这篇MSDN 文章有一节动态深度缩放:在运行时提供图像像素和指向这个Mandelbrot Explorer的链接,这“有点”听起来类似于你正在尝试做的事情(即他正在生成 mandelbrot 集合的特定部分 -需求;您想按需检索千兆像素图像的特定部分)。

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

抱歉,我无法提供更具体的答案 - 希望这些链接有所帮助。

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

于 2010-02-10T08:13:09.873 回答
3

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

  1. 图像生成:

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

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

最终结果类似于谷歌地图。

于 2012-11-19T17:07:25.767 回答