.net c#滚动放大大的图片时如何防止图形卡住

How to prevent Graphical stutter when scrolling a large zoomed out picture
2021-02-23
  •  译文(汉语)
  •  原文(英语)

我有一幅大的tiff图片(5.9 Mb,13k X 16k分辨率),用户将其加载到可滚动面板中,然后他可以放大/缩小,滚动和标记点,区域等.

对于可滚动的双缓冲面板,我使用的是鲍勃·鲍威尔(Bob Powell)令人敬畏的ZoomPicBox 的修改版.该面板仅显示当前查看的图片部分.

缩小时滚动图像时会发生结结巴巴(即使interpolationMode设置为low)

有什么可以做的(最好没有硬件加速)?

面板的绘画事件:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        if (_image == null)
        {
            base.OnPaintBackground(e);
            return;
        }

        //scale
        System.Drawing.Drawing2D.Matrix ScaleMat = new System.Drawing.Drawing2D.Matrix(_zoom, 0, 0, _zoom, 0, 0);
        //move to position of scrollbas
        ScaleMat.Translate(this.AutoScrollPosition.X / (_zoom), this.AutoScrollPosition.Y / (_zoom));


        e.Graphics.Transform = ScaleMat;
        e.Graphics.InterpolationMode = _interpolationMode;

        e.Graphics.DrawImage(_image, new Rectangle(0, 0, _image.Width, _image.Height), 0, 0, _image.Width, _image.Height, GraphicsUnit.Pixel);


        base.OnPaint(e);
    }
  • _zoom,_image和_interpolationMode是控件的私有字段

构造函数:

 public PicBoxPlus()
        {
            MouseMove += PicBoxPlus_MouseMove;
            KeyDown += PicBoxPlus_KeyDown;
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);

            this.AutoScroll = true;
        }

编辑:尝试缓存

我尝试实现Sinatr的代码,但出了点问题,因为我得到的只是一张黑色的图片(大小合适).任何人都知道有什么问题吗?

新的油漆事件:

 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            if (mCachedImage == null)
            {
                base.OnPaintBackground(e);
                return;
            }

            //scale
            System.Drawing.Drawing2D.Matrix ScaleMat = new System.Drawing.Drawing2D.Matrix(mZoom, 0, 0, mZoom, 0, 0);
            //move to position of scrollbas
            ScaleMat.Translate(this.AutoScrollPosition.X / (mZoom), this.AutoScrollPosition.Y / (mZoom));

            try
            {
                if (mCachedImage == null)
                {
                    mCachedImage = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
                    using (var cacheGraphics = Graphics.FromImage(mCachedImage))
                    {
                        cacheGraphics.Transform = ScaleMat;
                        cacheGraphics.InterpolationMode = _interpolationMode;
                        cacheGraphics.DrawImage(mCachedImage, new Rectangle(0, 0, mCachedImage.Width, mCachedImage.Height), 0, 0, mCachedImage.Width, mCachedImage.Height, GraphicsUnit.Pixel);

                    }

                    e.Graphics.DrawImage(mCachedImage, Point.Empty);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            base.OnPaint(e);
        }

图像和缩放属性:

public Bitmap Image
    {
        get { return mCachedImage; }
        set
        {
            mCachedImage = value;
            UpdateScaleFactor();
            this.Invalidate();
        }
    }

public Single Zoom
{
    get { return mZoom; }
    set
    {
        if (value <= 0||value < 0.001)
        {
            value = 0.001f;
        }

        mZoom = value;
        UpdateScaleFactor();
        ResetCache(); // Sinatr's function
        this.Invalidate();
    }
}

从主窗体加载图像:

panelMap.Image = (Bitmap)Image.FromFile("pic.tiff");
速聊1:
如果重绘成本很高(发生断断续续),则必须对其进行优化.例如,尝试缓存转换后的图像(可以Graphics从位图创建),并在更改大小/缩放比例时销毁缓存.
速聊2:
您能否提供一个图像缓存示例?
解决过程1

没有经过测试,但是应该给出一个想法.

Bitmap _cached = null;

override void OnPaint(PaintEventArgs e)
{
    if(_cached == null)
    {
        _cached = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
        using(var graphics = Graphics.FromImage(_cached)
        {
             // draw into this graphics once -> it will be cached in _cached bitmap
        }
    }
    e.Graphics.DrawImage(_cached, Point.Empty);
}

// call this if _zoom or ClientSize is changed
void ResetCache()
{
    _cache = null;
    this.Invalidate(); // mandatory for _zoom change
}

另外,我不知道如何显示放大的图片,但是通常会有偏移,以便您可以移动(平移)图像.

速聊1:
感谢您提供示例!一定是个点子吗?我想我将主要使用.tiff和.png类型的图像
速聊2:
嗯...似乎对我不起作用.加载图片后面板为空,但是它的确会改变尺寸...
速聊3:
我将ResetCache()函数移至Zoom属性.现在我得到一个尺寸合适但黑色的面板
速聊4:
我将其添加到原始帖子.
速聊5:
为什么base.OnPaint()在绘画结束时打电话?

I have a large tiff picture (5.9 Mb , 13k X 16k reolution) that the user loads into a scrollable panel that he then can zoom in/out of, scroll and mark points, regions etc on.

for the scrollable double buffered panel I am using a modification of Bob Powell's awesome ZoomPicBox The panel displays only the part of the picture currently in view.

The stutter occurs when scrolling the image when zoomed out (even if the interpolationMode is set to low)

Is there anything that can be done about it (preferably without hardware acceleration)?

The paint event of the panel :

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
    {
        if (_image == null)
        {
            base.OnPaintBackground(e);
            return;
        }

        //scale
        System.Drawing.Drawing2D.Matrix ScaleMat = new System.Drawing.Drawing2D.Matrix(_zoom, 0, 0, _zoom, 0, 0);
        //move to position of scrollbas
        ScaleMat.Translate(this.AutoScrollPosition.X / (_zoom), this.AutoScrollPosition.Y / (_zoom));


        e.Graphics.Transform = ScaleMat;
        e.Graphics.InterpolationMode = _interpolationMode;

        e.Graphics.DrawImage(_image, new Rectangle(0, 0, _image.Width, _image.Height), 0, 0, _image.Width, _image.Height, GraphicsUnit.Pixel);


        base.OnPaint(e);
    }
  • _zoom ,_image and _interpolationMode are private fields of the control

The constructor:

 public PicBoxPlus()
        {
            MouseMove += PicBoxPlus_MouseMove;
            KeyDown += PicBoxPlus_KeyDown;
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer, true);

            this.AutoScroll = true;
        }

EDIT : Attempting Caching

I tried implementing Sinatr's code but something is wrong, because all I get is a black image (of the right size). Anyone has an idea what could be wrong?

The new paint event:

 protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            if (mCachedImage == null)
            {
                base.OnPaintBackground(e);
                return;
            }

            //scale
            System.Drawing.Drawing2D.Matrix ScaleMat = new System.Drawing.Drawing2D.Matrix(mZoom, 0, 0, mZoom, 0, 0);
            //move to position of scrollbas
            ScaleMat.Translate(this.AutoScrollPosition.X / (mZoom), this.AutoScrollPosition.Y / (mZoom));

            try
            {
                if (mCachedImage == null)
                {
                    mCachedImage = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
                    using (var cacheGraphics = Graphics.FromImage(mCachedImage))
                    {
                        cacheGraphics.Transform = ScaleMat;
                        cacheGraphics.InterpolationMode = _interpolationMode;
                        cacheGraphics.DrawImage(mCachedImage, new Rectangle(0, 0, mCachedImage.Width, mCachedImage.Height), 0, 0, mCachedImage.Width, mCachedImage.Height, GraphicsUnit.Pixel);

                    }

                    e.Graphics.DrawImage(mCachedImage, Point.Empty);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            base.OnPaint(e);
        }

The Image and zoom properties:

public Bitmap Image
    {
        get { return mCachedImage; }
        set
        {
            mCachedImage = value;
            UpdateScaleFactor();
            this.Invalidate();
        }
    }

public Single Zoom
{
    get { return mZoom; }
    set
    {
        if (value <= 0||value < 0.001)
        {
            value = 0.001f;
        }

        mZoom = value;
        UpdateScaleFactor();
        ResetCache(); // Sinatr's function
        this.Invalidate();
    }
}

Loading the image from the main form:

panelMap.Image = (Bitmap)Image.FromFile("pic.tiff");
Talk1:
If redrawing is costly (stutter occurs), then you have to optimize it. Try, to example, cache transformed image (you could create Graphics from bitmap) and destroy cache if size/zoom is changed.
Talk2:
Could you please provide an example for image caching?
Solutions1

Is not tested, but should give an idea.

Bitmap _cached = null;

override void OnPaint(PaintEventArgs e)
{
    if(_cached == null)
    {
        _cached = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
        using(var graphics = Graphics.FromImage(_cached)
        {
             // draw into this graphics once -> it will be cached in _cached bitmap
        }
    }
    e.Graphics.DrawImage(_cached, Point.Empty);
}

// call this if _zoom or ClientSize is changed
void ResetCache()
{
    _cache = null;
    this.Invalidate(); // mandatory for _zoom change
}

Also, I don't know how to do you present your zoomed-in picture, but usually there is an offset so that you can move (pan) image.

Talk1:
Thank you for providing an example! Does it have to be a bitmpap? I think I will be working mostly with .tiff and .png type images
Talk2:
Hmm... It doesn't seem to work for me. The panel is empty after loading a picture, it does change size though...
Talk3:
I moved the ResetCache() function to the Zoom property. Now I just get a panel of the right size but black
Talk4:
I added it to the original post.
Talk5:
Why do you call base.OnPaint() at the end of painting?
转载于:https://stackoverflow.com/questions/23733405/how-to-prevent-graphical-stutter-when-scrolling-a-large-zoomed-out-picture

本人是.net程序员,因为英语不行,使用工具翻译,希望对有需要的人有所帮助
如果本文质量不好,还请谅解,毕竟这些操作还是比较费时的,英语较好的可以看原文

留言回复
我们只提供高质量资源,素材,源码,坚持 下了就能用 原则,让客户花了钱觉得值
上班时间 : 周一至周五9:00-17:30 期待您的加入