C# 绘制自定义字体精灵时内存不足

C# 绘制自定义字体精灵时内存不足,c#,out-of-memory,draw,C#,Out Of Memory,Draw,我一直在用Visual Studio 2013 Express用C#从头开始制作一个游戏 一切都进展顺利,直到我在运行此代码时遇到内存不足错误: public static class ExtraGraphics { static string[] chars = { " ", "!", "\"", "#", "$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4"

我一直在用Visual Studio 2013 Express用C#从头开始制作一个游戏

一切都进展顺利,直到我在运行此代码时遇到内存不足错误:

 public static class ExtraGraphics
{

    static string[] chars = { " ", "!", "\"", "#", "$", "%", "&", "\'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "¬", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~" };
    static List<string> charList = chars.ToList<string>();

    public static void DrawFont(this Graphics g, Bitmap image, int width, int height, string str)
    {
        for (int i = 0; i < str.Length; i++)
        {
            g.DrawImage(image.Clone(new Rectangle(new Point(charList.IndexOf(str.Substring(i)) * width, 0), new Size(width, height)), System.Drawing.Imaging.PixelFormat.DontCare), new Point(i * width, 0)); //Line that errors.
        }
    }
}
这意味着要从图像(自定义字体)中获取一条字符,然后根据输入字符串将其拆分并显示不同的字符,但是当str参数包含多个字符时,我会得到该错误


这是我正在使用的精灵。

这里的问题是每次调用
DrawFont
方法时都在使用内存。这是因为您每次都在创建一个新的
位图
对象,该对象会持续使用更多内存。解决此问题的最简单方法是更改绘制字体例程,使用语句在
中创建新的
位图
对象

using (var temp = new Bitmap(Image.FromFile("Assets\\font.gif"))) {
    ExtraGraphics.DrawFont(canvas, temp, sliceWidth, sliceHeight, text);
}
为了完全修复内存泄漏,您可能还必须对克隆采取相同的策略。祝你好运

注意/编辑:注释中提到的一个重要项目是,如果可能,您希望尝试加载图像一次-可能是在表单加载时或在初始化过程中。这有两个积极的影响。第一,您不必担心内存泄漏,第二,您将显著加快渲染循环(从磁盘加载图像很慢)


最后要考虑的一件事是使用
矩阵
来绘制您正在寻找的图像部分,而不是尝试克隆您想要绘制的像素。您还将看到这也带来了巨大的性能优势。

下面是一段从字体图像中绘制字符图示符的代码

它首先准备了一个字典,该字典将保留所有glyph位图,以便以后重用。键是每个标志符号的字符

当您不再需要字典或要加载其他字体图像文件时,还有一种方法可以正确地处理字典

private void Form1_Load(object sender, EventArgs e)
{
    prepareFont(fontImageFileName);
}


Dictionary<char, Bitmap> theFont = new Dictionary<char, Bitmap>();

string fontImageFileName = @"D:\yourfontimage.png";
string fontCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

int fontCharsPerLine = 7;
int fontCharsLines = 5;
int fontCharPixelWidth = 63;
int fontCharPixelHeight = 87;



void prepareFont(string fontImageFile)
{
    Rectangle destRect = new Rectangle(0, 0, fontCharPixelWidth, fontCharPixelHeight );

    using (Bitmap bmp = new Bitmap(fontImageFileName))
        for (int y = 0; y < fontCharsLines; y++)
        for (int x = 0; x < fontCharsPerLine; x++)
        {
            if (x + y * fontCharsPerLine < fontCharacters.Length)
            {
               char c = fontCharacters[x + y * fontCharsPerLine];
               Bitmap glyph = new Bitmap(fontCharPixelWidth, fontCharPixelHeight);
               using (Graphics G = Graphics.FromImage(glyph))
               {
                   G.DrawImage(bmp, destRect, fontCharPixelWidth * x, fontCharPixelHeight * y,
                        fontCharPixelWidth, fontCharPixelHeight, GraphicsUnit.Pixel);

                   theFont.Add(c, glyph);
               }
            }
        }
}


void disposeFont()
{
    foreach (char c in theFont.Keys) theFont[c].Dispose();
    theFont.Clear();
}

void drawFontString(Graphics G, string text, Point location)
{
    int offset = 0;
    foreach (char c in text)
    {
        if (theFont.Keys.Contains(c))
        {
            G.DrawImage(theFont[c], new Point(location.X + offset, location.Y));
            offset += fontCharPixelWidth;
        }
    }
}

private void button1_Click(object sender, EventArgs e)
{
    using (Graphics G = panel1.CreateGraphics())
    { drawFontString(G, textBox1.Text, Point.Empty); }
}
private void Form1\u加载(对象发送方,事件参数e)
{
prepareFont(fontImageFileName);
}
Dictionary theFont=新字典();
字符串fontImageFileName=@“D:\yourfontimage.png”;
字符串fontCharacters=“abcdefghijklmnopqrstuvxyz1234567890”;
int-fontCharsPerLine=7;
int-fontCharsLines=5;
int fontCharPixelWidth=63;
int fontCharPixelHeight=87;
void prepareFont(字符串fontImageFile)
{
矩形析取=新矩形(0,0,fontCharPixelWidth,fontCharPixelHeight);
使用(位图bmp=新位图(fontImageFileName))
对于(int y=0;y
您肯定有复制粘贴错误,如
g.(image.Clone)(新矩形…
不是有效的代码。你在调用
g
什么函数?为什么要使用一堆
字符串而不是
char
s?同样,这一行可能不是问题所在,但只是破坏骆驼的稻草。如果你没有正确处理你的对象,你在运行时会出现OutOfMemoryExceptionGDI句柄不足,或者您有太多未清理的非托管内存。完全同意此解决方案,但“更好”解决方法是加载一次位图并将其保存在内存中的某个位置,然后在代码中反复引用同一位图。每次屏幕重画都需要从持久存储中读取,这将大大降低游戏的速度。@ScottChamberlain我完全同意。提供的代码太少,无法提供这一点作为一个解决方案。无论如何,我会修改答案来说明这一点。谢谢,我正在尝试您的建议。似乎错误仍然存在,所以我将尝试使用矩阵。谢谢,尽管我已经找到了一种方法,让函数只画一个字符,然后让另一个函数运行它尽可能多的次数继承者是字符串中的字符。我不确定它是否比您的方法更有效,但这里有一个链接:我没有时间尝试您的代码,但看起来您仍然在泄漏
Bitmap newImage=image.Clone(…
IMO您应该在
g.DrawImage()之后添加
newImage.Dispose();
(newImage..
好的,我在中添加了这一点,它似乎提高了很多性能!不确定为什么它会提高性能,但如果您处于下一个内存不足的边缘,它很可能会提高性能。主要的一点是停止GDI对象(即位图、笔、画笔、图标和字体)防止泄漏。您可能需要在任务管理器中检查这些!!
private void Form1_Load(object sender, EventArgs e)
{
    prepareFont(fontImageFileName);
}


Dictionary<char, Bitmap> theFont = new Dictionary<char, Bitmap>();

string fontImageFileName = @"D:\yourfontimage.png";
string fontCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

int fontCharsPerLine = 7;
int fontCharsLines = 5;
int fontCharPixelWidth = 63;
int fontCharPixelHeight = 87;



void prepareFont(string fontImageFile)
{
    Rectangle destRect = new Rectangle(0, 0, fontCharPixelWidth, fontCharPixelHeight );

    using (Bitmap bmp = new Bitmap(fontImageFileName))
        for (int y = 0; y < fontCharsLines; y++)
        for (int x = 0; x < fontCharsPerLine; x++)
        {
            if (x + y * fontCharsPerLine < fontCharacters.Length)
            {
               char c = fontCharacters[x + y * fontCharsPerLine];
               Bitmap glyph = new Bitmap(fontCharPixelWidth, fontCharPixelHeight);
               using (Graphics G = Graphics.FromImage(glyph))
               {
                   G.DrawImage(bmp, destRect, fontCharPixelWidth * x, fontCharPixelHeight * y,
                        fontCharPixelWidth, fontCharPixelHeight, GraphicsUnit.Pixel);

                   theFont.Add(c, glyph);
               }
            }
        }
}


void disposeFont()
{
    foreach (char c in theFont.Keys) theFont[c].Dispose();
    theFont.Clear();
}

void drawFontString(Graphics G, string text, Point location)
{
    int offset = 0;
    foreach (char c in text)
    {
        if (theFont.Keys.Contains(c))
        {
            G.DrawImage(theFont[c], new Point(location.X + offset, location.Y));
            offset += fontCharPixelWidth;
        }
    }
}

private void button1_Click(object sender, EventArgs e)
{
    using (Graphics G = panel1.CreateGraphics())
    { drawFontString(G, textBox1.Text, Point.Empty); }
}