Graphics.DrawImage速度

时间:2013-01-09 22:52:45

标签: c# performance bitmap gdi drawimage

在我的程序中,我正在编写一个基本的图像编辑器。部分内容允许用户绘制一个矩形区域,然后弹出一个显示区域,显示区域缩放3倍左右(可以用鼠标滚轮进一步调整)。如果他们右键单击并拖动此图像,它将在原始图像上移动缩放区域,基本上用作放大镜。

问题是,即使在相对较小的位图上,我也会看到一些严重的性能问题。如果显示缩放区域的位图大约为400x400,它仍然可以像鼠标移动那样快速更新并且非常平滑,但是如果我将鼠标滚轮缩放到450x450左右,它会立即开始分块,每秒只有2次更新,如果说。我不明白为什么这么小的增加导致如此巨大的性能问题...就像我已经达到一些内部内存限制或其他东西。它似乎与缩放的源位图的大小无关,只与缩放位图的大小有关。

问题是我正在使用Graphics.DrawImage和PictureBox。阅读本网站,我发现这两者的表现通常不是很好,但我对GDI的内部运作方式知之甚少,无法提高我的速度。我希望你们中的一些人可能知道我的瓶颈在哪里,因为我可能只是以糟糕的方式使用这些工具,或者不知道有更好的工具可供使用。

以下是我的鼠标事件和相关功能的一些片段。

private void pictureBox_MouseDown(object sender, MouseEventArgs e)
    {

        else if (e.Button == System.Windows.Forms.MouseButtons.Right)
        {
            // slide the zoomed part to look at a different area of the original image
            if (zoomFactor > 1)
            {
                isMovingZoom = true;
                // try saving the graphics object?? are these settings helping at all??
                zoomingGraphics = Graphics.FromImage(displayImage);
                zoomingGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
                zoomingGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
                zoomingGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
                zoomingGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
            }
        }
    }


private void pictureBox_MouseMove(object sender, MouseEventArgs e)
    {
        if (isMovingZoom)
        {
            // some computation on where they moved mouse ommitted here

            zoomRegion.X = originalZoomRegion.X + delta.X;
            zoomRegion.Y = originalZoomRegion.Y + delta.Y;
            zoomRegionEnlarged = scaleToOriginal(zoomRegion);

            // overwrite the existing displayImage to prevent more Bitmaps being allocated
            createZoomedImage(image.Bitmap, zoomRegionEnlarged, zoomFactor, displayImage, zoomingGraphics);
        }
    }

private void createZoomedImage(Bitmap source, Rectangle srcRegion, float zoom, Bitmap output, Graphics outputGraphics)
    {
        Rectangle destRect = new Rectangle(0, 0, (int)(srcRegion.Width * zoom), (int)(srcRegion.Height * zoom));

            outputGraphics.DrawImage(source, destRect, srcRegion, GraphicsUnit.Pixel);

        if (displayImage != originalDisplayImage && displayImage != output)
            displayImage.Dispose();
        setImageInBox(output);
    }

// sets the picture box image, as well as resizes the window to fit
    void setImageInBox(Bitmap bmp)
    {
        pictureBox.Image = bmp;
        displayImage = bmp;
        this.Width = pictureBox.Width + okButton.Width + SystemInformation.FrameBorderSize.Width * 2 + 25;
        this.Height = Math.Max(450, pictureBox.Height) + SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2 + 20;
    }

private void pictureBox_MouseUp(object sender, MouseEventArgs e)
    {
        else if (e.Button == System.Windows.Forms.MouseButtons.Right)
        {
            if (isMovingZoom)
            {
                isMovingZoom = false;
                zoomingGraphics.Dispose();
            }
        }
    }

正如你所看到的,我不会在每次想要绘制内容时声明一个新的Bitmap,我正在重用旧的Bitmap(以及Bitmap的图形对象,但我不知道是否有很多成本重复调用Graphics.FromImage)。我尝试添加Stopwatches来对我的代码进行基准测试,但我认为DrawImage将功能传递给另一个线程,因此该功能声称可以相对快速地完成。当我不使用它时,我正在尝试Dispose我的所有Bitmap和Graphics对象,并避免在MouseMove事件期间重复调用分配/释放资源。我正在使用PictureBox,但我认为这不是问题所在。

任何帮助加快此代码或教我在DrawImage中发生的事情表示赞赏!我修剪了一些多余的代码,使其更具代表性,但如果我不小心修剪了一些重要的东西,或者没有显示我是如何使用可能导致问题的东西,请告诉我,我会修改交。

1 个答案:

答案 0 :(得分:2)

我处理类似问题的方式是在接收Paint事件时,我将整个图像绘制到内存位图,然后将其BLT到窗口。 这样,所有视觉闪光都被消除,并且看起来快,即使它实际上不是。

为了更清楚,我不会在鼠标事件处理程序中进行任何绘制。 我只是设置了主要Paint处理程序所需的内容,然后执行Invalidate。 因此,绘画在鼠标事件完成后发生。


补充:要在评论中回答汤姆的问题,我就是这样做的。请记住,我并不认为它很快,只是看起来快,因为_e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));是瞬间出现的。它只是从一个图像到下一个图像。 用户没有看到窗口被清除然后再重新粉刷。 它具有与双缓冲相同的效果。

    Graphics grToDrawOn = null;
    Bitmap bmToDrawOn = null;

    private void DgmWin_Paint(object sender, PaintEventArgs _e){
        int w = ClientRectangle.Width;
        int h = ClientRectangle.Height;
        Graphics gr = _e.Graphics;

        // if the bitmap needs to be made, do so
        if (bmToDrawOn == null) bmToDrawOn = new Bitmap(w, h, gr);
        // if the bitmap needs to be changed in size, do so
        if (bmToDrawOn.Width != w || bmToDrawOn.Height != h){
            bmToDrawOn = new Bitmap(w, h, gr);
        }
        // hook the bitmap into the graphics object
        grToDrawOn = Graphics.FromImage(bmToDrawOn);
        // clear the graphics object before drawing
        grToDrawOn.Clear(Color.White);
        // paint everything
        DoPainting();
        // copy the bitmap onto the real screen
        _e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));
    }

    private void DoPainting(){
        grToDrawOn.blahblah....
    }