【问题标题】:Lowering memory cost of drawing in C#降低在 C# 中绘图的内存成本
【发布时间】:2018-03-28 11:36:37
【问题描述】:

我将图像存储在位图中。当我调整对象大小时,我的图像也需要调整大小。我已经制作了一个将图像调整为所需大小的功能。

我已经只在绝对需要调整图像大小时才调用这个函数,但是我的内存成本不断地随着这个图像的大小而迅速从 50 MB 跃升至超过 1 GB。 (当我的鼠标按钮被按住时,这个方法需要每毫秒调用一次。)

我在using 中有我的Graphics,但在这种情况下这似乎没什么帮助。 如果我在它上面调用GC.Collect(2),当然会有所帮助,但它会极大地减慢 GUI,如果我将其设置为非阻塞,那么它根本没有多大帮助。

有没有办法释放最后一个调用该函数的实例仍未使用的内存,或者我是否需要更改我的代码并以另一种方式执行此操作?如果有更好的方法,我将不胜感激,因为似乎 GDI 有漏洞或者我的方法效率很低。

我的代码:

public int getWidth()
{
    return width;
}

public int getHeight()
{
    return height;
}

public void createScaledImage()
{
    var cr = new c_returnGraphicSettings();
    if (getWidth() > 20 && getHeight() > 20)
    {
        scaledImage = new Bitmap(getWidth(), getHeight());
        using (Graphics g = Graphics.FromImage(scaledImage))
        {
            /*
            g.InterpolationMode = cr.getIM();
            g.PixelOffsetMode = cr.getPOM();
            g.SmoothingMode = cr.getSM();
            /*--*/

            g.DrawImage(image, new Rectangle(new Point(0, 0), scaledImage.Size), new Rectangle(new Point(0, 0), image.Size), GraphicsUnit.Pixel);
            g.Dispose();
        }

        //GC.Collect(2, GCCollectionMode.Forced, true);

    }
}

public Bitmap getScaledImage()
{
    //createScaledImage();
    return scaledImage;
}

public void resizeImage()
{
    if(image != null)
    {
        createScaledImage();
    }
}

主窗体上的代码:

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    //TODO: PAINT!!
    organizeImageList();
    foreach (c_ImageHolder c in Limages)
    {
        if (c != null)
        {
            e.Graphics.DrawImage(c.getScaledImage(), c.Position);
            ...
        }
    }
}


private void f_Screen_MouseDown(object sender, MouseEventArgs e)
{
    Limages.Sort(new intComparerDesc());
    foreach (c_ImageHolder c in Limages)
    {
        if (renhan.pointInPosition(e.Location, new Rectangle(c.Position, c.Size)))
        {
            c.select();
            selectedImage = c;
            imageDragPoint = new Point(e.X - c.Left, e.Y - c.Top);
            if (!c.isOverAnEdge(imageDragPoint) && !c.isOverACorner(imageDragPoint)) 
            { 
                resize = false; 
                cResizer = null; 
                ed = edges.none; 
                cor = corners.none; 
            }
            else 
            { 
                resize = true; 
                cResizer = c; 
                if (c.isOverAnEdge(imageDragPoint))
                { 
                    ed = c.overWhichEdge(imageDragPoint); 
                    cor = corners.none; 
                } 
                else 
                { 
                    cor = c.overWhichCorner(imageDragPoint); 
                    ed = edges.none; 
                } 
            }
            mdown = true;
            break;
        }
    }
    Invalidate();
}


解释:

我正在使用我创建的一个类来存储位图图像,其中包含一些关于它的大小和位置的数据,然后我使用它将该图像绘制到一个存储该类实例的屏幕上。

由于透明度问题,我没有为此使用控件(图片框等...)。

每个实例都包含不同的图像。我还需要能够调整这些图像的大小。出于这个原因,我有第二个位图,用于存储原始位图的调整大小版本。

我使用不同的位图来存储调整大小的图像,这样如果我需要再次调整大小,我不会调整已经修改的图像的大小,而是调整原始图像的大小。 (保持质量)

调整大小是这样处理的: 我在图像边缘按住鼠标按钮。当我在每次调整大小时按住按钮时移动鼠标时,该类会生成一个新图像,将原始图像拉伸到新大小。然后该图像返回到无效的主屏幕,并在绘制时将新图像绘制到屏幕背景上。

问题是,因为我需要将此图像绘制到背景上,所以我无法处理它。我可以复制并处理原件,但我认为,那时我会遇到同样的问题。

当我按住鼠标按钮并不断地重新绘制图像时,内存迅速增加。我可以在完成后处理,但只要调整图像大小足够长的时间就会导致程序崩溃。

我制作了一个更小更简单的程序来演示我的问题以及一个简短的视频,您可以在这里找到它们:Video and link to program

在视频下的 rar 文件中,我的演示程序的链接存储在 dropbox 中。

我希望这个解释和视频/演示有助于更好地解释我的问题。

感谢所有努力提供帮助的人!

【问题讨论】:

  • 你在左右泄漏东西。在旧位图上调用 Dispose()。而using 已经在g 上调用了Dispose(),无需手动操作。如果您的代码正确,则根本不需要调用GC.Collect()
  • 为什么要手动调整位图大小?在视图上让控件调整大小,当您完成调整大小时(释放鼠标单击后),生成并存储新的位图。
  • 但是当他们决定一次重做某事时,您需要丢弃以前的版本,因为该代码总是会创建一个全新的scaledImage。我们不知道该代码是如何使用的,所以我们没有上下文。听起来你肯定没有处理任何东西
  • @WolfyD 因此,如果 scaledImage 不为空,则需要处理它。另一方面,为什么不简单地将原始图像投影到画布上呢?当您创建 scaledImage (将矩形作为目标)时,您似乎做得很准确,为什么在绘图期间不这样做呢?如果您使用Graphics.DrawImage-Methode: (Image, RectangleF, RectangleF, GraphicsUnit) 方法,则无需创建 scaledImage
  • @WolfyD 你有没有机会提供一个minimal reproducible example 来证明你的问题?无需向我们提供所有代码,而只是让我们大致了解您要完成的工作?我目前的想象力有限,但您似乎正在尝试创建某种图像浏览器。它可能对解决您的问题最有帮助。另外,在调整大小时你真的需要实时更新吗?对于大多数用户场景来说,这似乎有点矫枉过正

标签: c# memory graphics gdi drawimage


【解决方案1】:

感谢所有试图提供帮助的人。我终于意识到我错在哪里了。

当我在控件中管理图像时,我保留了原始代码中的一个函数。

使用我的新代码,我可以在不将其写入新位图的情况下管理调整大小。

我不知道为什么在 5 个人告诉我同样的事情之后我不明白。

再次感谢。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多