c#DrawString - 测量每个字符的边界框

时间:2013-08-28 20:40:54

标签: c# gdi+

在我将一个字符串传递给Graphics.DrawString()之前,我想确切知道每个字符的确切位置 - 不仅仅是它的偏移和宽度,而是它的上升/下降的精确边界框,以便我可以在角色级别进行碰撞检测,而不是使用Graphics.MeasureString()给我的整个行高。

我似乎无法找到任何准确返回每个角色的边界框的示例。如何在c#/ GDI +中完成?

理想情况下,我想获得此图像中的每个灰色矩形:

enter image description here

6 个答案:

答案 0 :(得分:3)

我认为在GDI +中准确测量字符串中的字符是不可能的。即使您忘记了子像素定位和字距调整等,这意味着绘制的字符串中的字符将根据字符串中的上下文和屏幕上的位置以及ClearType设置而具有不同的大小,MeasureString习惯于添加填充和软糖因子,因此您不能轻易从中获得所需的信息。

排序的一种方法是测量第一个字符,然后测量前两个字符,然后测量前三个字符,依此类推,这样你就可以大致了解它们在一次点击中的最终结果。然后你可能需要再次测量每个焦炭以确定它的高度。如果MeasureString不提供字符高度而是给出字体高度,则可能不得不求助于将每个字符渲染到屏幕外位图,然后扫描像素以确定字符的真实高度。

此时你可能会发现最好p /调用较低级别的字体API,它只是直接从字体中提供所需的信息。但是你可能需要使用除GDO +之外的其他东西来渲染它,因为它与你所制定的东西排成一行可能并不好。

(你能说出我对GDI +字体渲染器的质量有信任吗?)

答案 1 :(得分:3)

编辑:这是我使用的解决方案,基于@ Sayka的示例和获取实际字符宽度的技巧:

如果您使用图形g1中的字体在X,Y处绘制字符串,则可以使用这些矩形绘制每个字符:

public IEnumerable<Rectangle> GetRectangles(Graphics g1)
{
    int left = X;
    foreach (char ch in word)
    {

        //actual width is the (width of XX) - (width of X) to ignore padding
        var size = g1.MeasureString("" + ch, Font);
        var size2 = g1.MeasureString("" + ch + ch, Font);

        using (Bitmap b = new Bitmap((int)size.Width + 2, (int)size.Height + 2))
        using (Graphics g = Graphics.FromImage(b))
        {
            g.FillRectangle(Brushes.White, 0, 0, size.Width, size.Height);
            g.TextRenderingHint = g1.TextRenderingHint;
            g.DrawString("" + ch, Font, Brushes.Black, 0, 0);
            int top = -1;
            int bottom = -1;

            //find the top row
            for (int y = 0; top < 0 && y < (int)size.Height - 1; y++)
            {
                for (int x = 0; x < (int)size.Width; x++)
                {
                    if (b.GetPixel(x, y).B < 2)
                    {
                        top = y;
                    }
                }
            }

            //find the bottom row
            for (int y = (int)(size.Height - 1); bottom < 0 && y > 1; y--)
            {
                for (int x = 0; x < (int)size.Width - 1; x++)
                {
                    if (b.GetPixel(x, y).B < 2)
                    {
                        bottom = y;
                    }
                }
            }
            yield return new Rectangle(left, Y + top, (int)(size2.Width - size.Width), bottom - top);
        }
        left += (int)(size2.Width - size.Width);
    }

}

看起来像这样:

enter image description here

答案 2 :(得分:2)

i made a method and got somethin like this

这是代码

private void saykaPanel_Paint(object sender, PaintEventArgs e)
    {
        string saykaName = "Sayka";
        Font usedFont = new Font("Tahoma", 46);

        int lastPoint = 0;
        for (int i = 0; i < saykaName.Length; i++)
        {
            Rectangle rect = measureAlphabet(saykaName[i], usedFont);
            e.Graphics.FillRectangle(Brushes.Gray, new Rectangle(lastPoint + rect.Left, rect.Top, rect.Width, rect.Height));
            e.Graphics.DrawString(saykaName[i].ToString(), usedFont, Brushes.Black, new PointF(lastPoint, 0));
            lastPoint += rect.Width;

        }        
    }

    Rectangle measureAlphabet(char alph, Font font)
    {
        SizeF size = CreateGraphics().MeasureString(alph.ToString(), font);
        Bitmap bmp = new Bitmap((int)size.Width, (int)size.Height);
        Graphics grp = Graphics.FromImage(bmp);
        grp.FillRectangle(Brushes.White, 0, 0, bmp.Width, bmp.Height);
        grp.DrawString(alph.ToString(), font, Brushes.Black, new PointF(0, 0));
        Bitmap img = (Bitmap)bmp;
        int x = 0, y = 0, width = 0, height = 0;

        for (int i = 0; i < img.Width;i++)
            for (int j = 0; j < img.Height;j++)
                if (img.GetPixel(i, j).B < 2)
                {
                    x = i;
                    goto jmp1;
                }

    jmp1: ;
        for (int i = img.Width - 1; i >= 0; i--)
            for (int j = 0; j < img.Height; j++)
                if (img.GetPixel(i, j).B < 2)
                {
                    width = i - x;
                    goto jmp2;
                }

    jmp2: ;
        for (int i = 0; i < img.Height; i++)
            for (int j = 0; j < img.Width; j++)
                if (img.GetPixel(j, i).B < 2)
                {
                    y = i;
                    goto jmp3;
                }

    jmp3: ;
        for (int i = img.Height - 1; i >= 0; i--)
            for (int j = 0; j < img.Width; j++)
                if (img.GetPixel(j, i).B < 2)
                {
                    height = i - y;
                    goto jmp4;
                }

    jmp4: ;
        Rectangle resultRectangle = new Rectangle(x, y, width, height);
        return resultRectangle;
    }

从左边开始测量第一个黑点,然后从右边开始测量,然后从顶部开始测量。 如果这有帮助,请评价..

答案 3 :(得分:0)

创建一个用于测量目的的不可见窗口,其大小和字体设置与“真实”窗口中的大小和字体设置完全相同。之后,您可以通过将更多数据混合在一起来扣除字符的各个边界框:

  • 选择单个字符的矩形,可以指示字符的左右边界。不幸的是,这个高度是行的全高
  • 来自MeasureCharacterRanges()或类似技术的数据的高度

我同意@Jason Williams的观点。 “填充和软糖因素”可能会稍微扭曲结果。

答案 4 :(得分:0)

如果性能不是很关键,我认为通过将字符转换为路径并检查它们,您可以获得最准确的结果。我不确定处理字距调整问题的最佳方法,但对于没有连字的字体,人们可能会找出孤立的字符边界框,相对于它们的起点测量,然后确定字符串中每个字符的起始点串。对于具有连字的字体,单个字符可能并不总是具有不同的字形。例如,如果字体具有“ffi”连字,则“offing”一词可能只包含四个字形:“o”,“ffi”,“n”和“g”;询问“我”的边界框是没有意义的。

答案 5 :(得分:0)

对我来说,解决方案是使用Graphics.DrawString 停止。我通过将字符添加到GraphicsPath并从GraphicsPath对象获取边界来测量字符。然后用Graphics.FillPath绘制字符。

DrawString例程经过优化,可以在一个盒子中绘制(更多)文本。如果要更精确地绘制字符,则DrawString是不够的。它在文本的前面和后面增加了空间。

如果您需要精确的字符绘制,请使用GraphicsPath进行测量,并使用FillPath进行绘制。与该线程和其他地方建议的查找像素相比,GraphicsPath操作非常快。

如果您还需要字距调整,请使用GetKerningPairs上的DllImport从字体加载字距调整表。为此,您还需要Win32友好的字体。为此,我建议使用CreateFont。

祝你好运!