代码之家  ›  专栏  ›  技术社区  ›  Adriaan

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

  •  7
  • Adriaan  · 技术社区  · 15 年前

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

    我需要像谷歌地图或Deepzoom那样实现平移/缩放。在将图像显示在屏幕上之前,我必须动态地应用图像处理,因此我不能使用直接访问图像文件的预编好的库。对于缩放,我打算在我的文件(金字塔存储)中保留多分辨率图像。最有用的步骤似乎是+200%,50%和全部显示。

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

    具体问题:

    • 关于如何通过按需生成图像部件来实现这种平移/缩放的提示或参考
    • 可以用作基础的任何代码(最好是商业或类似于LGPL/BSD的许可证)
    • Deepzoom是否可以用于此目的(即,是否有一种方法可以提供一个功能,以在当前缩放级别的正确恢复时提供一个图块?)(我还需要像素精确寻址)
    3 回复  |  直到 12 年前
        1
  •  3
  •   Conceptdev    15 年前

    这个 CodeProject article: Generate...DeepZoom Image Collection 可能是一个有用的阅读,因为它谈到了生成deepzoom图像源。

    这个 MSDN article 有一节 动态深度缩放:在运行时提供图像像素 链接到这个 Mandelbrot Explorer 这听起来有点像你想做的(例如,他正在生成Mandelbrot点播集的特定部分;你想检索你的千兆像素点播图像的特定部分)。

    我想“Deepzoom能用吗?”可能是“是”,但由于它仅在Silverlight中可用,因此如果需要WinForms/WPF客户端应用程序,则必须对嵌入的Web浏览器控件执行一些技巧。

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

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

        2
  •  3
  •   Adriaan    15 年前

    我决定自己试试。我想出了一个简单的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块(420mpixel)组成的矩阵。当然,我必须在现实生活中添加一些延迟加载。如果瓷砖还没有装好,我可以把它们放在外面(空的)。事实上,我已经要求我的客户在他的机器上安装8G字节,这样我就不必为性能操心太多。加载后的磁贴可以保留在内存中。

    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兆字节快速访问任意大小(平铺)的TIFF文件。

        3
  •  3
  •   Andrew Barber Eric Lafortune    12 年前

    我想你可以按照以下步骤来解决这个问题:

    1. 图像生成:

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

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

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