【问题标题】:How to perform smooth image zoom and pan?如何进行平滑的图像缩放和平移?
【发布时间】:2012-11-17 09:07:13
【问题描述】:

我正在编写一个显示地图的程序,并在其顶部显示相机位置及其观察方向的另一层。地图本身可以缩放和平移。问题是地图文件很大,缩放不顺畅。

我创建了class ZoomablePictureBox : PictureBox 来添加缩放和平移功能。我尝试了不同的方法,来自这个论坛和其他论坛,用于缩放和平移,最终得到以下结果,触发 ZoomablePictureBoxOnPaint 事件:

  private void DrawImgZoomed(PaintEventArgs e)
    {
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            if (imgZoomed != null)
                e.Graphics.DrawImage(imgZoomed, new Rectangle(-ShiftX, -ShiftY, imgZoomed.Width, imgZoomed.Height), 0, 0, imgZoomed.Width, imgZoomed.Height, GraphicsUnit.Pixel);

    }

ShiftX 和 ShiftY 提供正确的地图平移(计算与此问题无关)。

imgZoomed 是每次缩放变化时在 BackgroundWorker 中计算的原始地图的缩放版本:

private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
    {

        Bitmap workerImage = e.Argument as Bitmap;
        Bitmap result;

        result = new Bitmap(workerImage, new Size((int)(workerImage.Width * Zoom), (int)(workerImage.Height * Zoom)));

        e.Result = result;
    }

所以当前的方法是,每次用户滚动鼠标滚轮时,都会根据当前缩放计算新的imgZoomed。地图大小约为 30 MB,这可能需要 0.5 秒,这很烦人,但平移运行平稳。

我意识到这可能不是最好的主意。在以前的方法中,我没有在每次滚动鼠标时创建缩放图像副本,而是这样做:

e.Graphics.DrawImage(Image, new Rectangle(-ShiftX, -ShiftY, (int)(this._image.Width * Zoom), (int)(this._image.Height * Zoom)), 0, 0, Image.Width, Image.Height, GraphicsUnit.Pixel);

缩放更平滑,因为据我了解,它只是拉伸了原始图像。另一方面,平移正在大量跳过。

我没想到:

  • 为每次放大内存/硬盘驱动器创建原始地图副本 - 这会占用太多内存/硬盘空间
  • 为下一个/实际/上一个缩放创建原始地图的副本,以便我 有更多时间来计算下一步 - 如果用户 一次滚动不止一个步骤

我还尝试了矩阵变换 - 没有真正的性能提升,而且计算 pan 真的很痛苦。

我在这里兜圈子,不知道该怎么做。如果我在默认的 Windows 图片查看器中打开地图,则缩放和平移很流畅。他们是怎么做到的?

如何同时实现平滑的缩放和平移?

【问题讨论】:

  • 我最近实现了类似的东西,经过多次测试,到目前为止我获得的最流畅的性能是使用StretchBlt 和缓存的GraphicsHBitmap 对象实现的。即使在 7000X7000 的图像上,这也帮助我实现了超级流畅的显示。

标签: c# winforms zooming


【解决方案1】:

抱歉,篇幅太长,我只留下了重要的部分。

大部分 p/invoke 内容取自 pinvoke.net

我的大图像平滑平移解决方案涉及使用StretchBlt gdi 方法而不是Graphics.DrawImage。我创建了一个静态类,将所有本地 blitting 操作分组。

另一件有很大帮助的事情是缓存BitmapHBitmapGraphics 对象。

public class ZoomPanWindow
{
    private Bitmap map;
    private Graphics bmpGfx;
    private IntPtr hBitmap;

    public Bitmap Map
    {
        get { return map; }
        set 
        {
            if (map != value)
            {
                map = value;
                //dispose/delete any previous caches
                if (bmpGfx != null) bmpGfx.Dispose();
                if (hBitmap != null) StretchBltHelper.DeleteObject(hBitmap);
                if (value == null) return;
                //cache the new HBitmap and Graphics.
                bmpGfx = Graphics.FromImage(map);
                hBitmap = map.GetHbitmap();
             }
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (map == null) return;
        //finally, the actual painting!
        Rectangle mapRect = //whatever zoom/pan logic you implemented.
        Rectangle thisRect = new Rectangle(0, 0, this.Width, this.Height);
        StretchBltHelper.DrawStretch(
            hBitmap,
            bmpGfx,
            e.Graphics,
            mapRect,
            thisRect);
    }
}

public static class StretchBltHelper
{
    public static void DrawStretch(IntPtr hBitmap, Graphics srcGfx, Graphics destGfx,
        Rectangle srcRect, Rectangle destRect)
    {
        IntPtr pTarget = destGfx.GetHdc();
        IntPtr pSource = CreateCompatibleDC(pTarget);
        IntPtr pOrig = SelectObject(pSource, hBitmap);
        if (!StretchBlt(pTarget, destRect.X, destRect.Y, destRect.Width, destRect.Height,
            pSource, srcRect.X, srcRect.Y, srcRect.Width, srcRect.Height,
            TernaryRasterOperations.SRCCOPY))
        throw new Win32Exception(Marshal.GetLastWin32Error());

        IntPtr pNew = SelectObject(pSource, pOrig);
        DeleteDC(pSource);
        destGfx.ReleaseHdc(pTarget);
    }

    [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
    public static extern System.IntPtr SelectObject(
        [In()] System.IntPtr hdc,
        [In()] System.IntPtr h);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    static extern bool DeleteDC(IntPtr hdc);

    [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool DeleteObject(
        [In()] System.IntPtr ho);

    [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)]
    static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    [DllImport("gdi32.dll")]
    static extern bool StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest,
        int nWidthDest, int nHeightDest,
        IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,
        TernaryRasterOperations dwRop);

    public enum TernaryRasterOperations : uint
    {
        SRCCOPY = 0x00CC0020
        //there are many others but we don't need them for this purpose, omitted for brevity
    }
}

【讨论】:

  • 您好。您能否展示如何在 Visual Studio 2019 中使用它?非常感谢。
猜你喜欢
  • 2019-01-15
  • 2010-11-25
  • 1970-01-01
  • 2010-10-19
  • 2016-07-22
  • 1970-01-01
  • 1970-01-01
  • 2011-09-10
  • 1970-01-01
相关资源
最近更新 更多