首页 文章

如何进行平滑的图像缩放和平移?

提问于
浏览
3

我正在编写一个显示 Map 的程序,并在其上面显示另一个图层,其中显示了摄像机的位置及其查看方向 . Map 本身可以缩放和平移 . 问题是 Map 文件的大小很大,缩放不顺畅 .

我创建了 class ZoomablePictureBox : PictureBox 以添加缩放和平移功能 . 我尝试了不同的方法,从这个和其他论坛,进行缩放和平移,最后得到以下内容,触发 OnPaint 事件的 OnPaint

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提供适当的 Map 平移(计算与此问题无关) .

imgZoomed 是每次缩放更改时在BackgroundWorker中计算的原始 Map 的缩放版本:

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 都是根据当前的缩放计算出来的 . Map 大小约为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);

变焦更平滑,因为从我的理解,它只是拉伸原始图像 . 另一方面,平移正在大量减少 .

我想的是:

  • 为内存/硬盘上的每个缩放创建orignial map的副本 - 它将占用太多的内存/硬盘空间

  • 为下一个/实际/上一个缩放创建orignial Map 的副本,这样我就有更多的时间来计算下一步 - 如果用户一次滚动多个步骤,它将无济于事

我也试过矩阵变换 - 没有真正的性能提升,而计算平移是一个真正的痛苦 .

我在这里跑圈子,不知道怎么做 . 如果我在默认的Windows图片浏览器中打开 Map ,则缩放和平移是平滑的 . 他们是怎么做到的?

我以什么方式同时实现平滑缩放和平移?

1 回答

  • 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
        }
    }
    

相关问题