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

c#DrawString-测量每个字符的边界框,c#,gdi+,C#,Gdi+,在我将字符串传递给Graphics.DrawString()之前,我想知道每个字符的确切位置——不仅是偏移量和宽度,还有它的精确绑定框,其中显示了上升/下降,这样我就可以在字符级别执行碰撞检测,而不是使用Graphics.MeasureString()提供的整个行高 我似乎找不到任何能准确返回每个字符的边界框的例子。如何在c#/GDI+中实现这一点 理想情况下,我希望得到下图中的每个灰色矩形: 我认为在GDI+中不可能精确测量字符串中的字符。即使您忘记了亚像素定位和紧排等,这意味着绘制的字符串中

在我将字符串传递给Graphics.DrawString()之前,我想知道每个字符的确切位置——不仅是偏移量和宽度,还有它的精确绑定框,其中显示了上升/下降,这样我就可以在字符级别执行碰撞检测,而不是使用Graphics.MeasureString()提供的整个行高

我似乎找不到任何能准确返回每个字符的边界框的例子。如何在c#/GDI+中实现这一点

理想情况下,我希望得到下图中的每个灰色矩形:


我认为在GDI+中不可能精确测量字符串中的字符。即使您忘记了亚像素定位和紧排等,这意味着绘制的字符串中的字符将根据其在字符串中的上下文和在屏幕上的位置以及ClearType设置而具有不同的大小,MeasureString也有添加填充和模糊因子的习惯,因此您无法轻松从中获得所需的信息

sortof工作的一种方法是测量第一个角色,然后是前两个角色,然后是前三个角色,依此类推,这样您就可以大致了解在一次点击中绘制它们时,它们将在何处结束。然后您可能需要再次测量每个字符以确定其高度。如果MeasureString不提供字符高度,而是提供字体高度,则可能需要将每个字符渲染为屏幕外位图,然后扫描像素以确定字符的真实高度

在这一点上,您可能会发现最好将p/invoke关闭到一个较低级别的字体API,该API直接从字体中提供所需的信息。但是,您可能需要使用GDO+以外的其他工具来渲染它,因为它与您计算出的结果对齐的可能性可能不高


(你能告诉我GDI+字体渲染器的质量有问题吗?

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

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

我同意杰森·威廉姆斯的观点。“填充和捏造因素”会稍微扭曲结果。

如果性能不是关键因素,我认为您可以通过将字符转换为路径并检查这些路径来获得最准确的结果。我不确定处理紧排问题的最佳方法,但对于没有连字的字体,我们可能可以单独计算字符的边界框,相对于字符的起点进行测量,然后确定紧排字符串中每个字符的起点。对于带有连字的字体,单个字符可能并不总是具有不同的字形。例如,如果字体有“ffi”连字,“offing”一词可能只包含四个字形:“o”、“ffi”、“n”和“g”;询问“i”的边界框是没有意义的。

这是密码

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;
    }
private void saykaPanel_Paint(对象发送方,PaintEventArgs e)
{
字符串saykaName=“Sayka”;
字体usedFont=新字体(“Tahoma”,46);
int lastPoint=0;
for(int i=0;i=0;i--)
对于(int j=0;j=0;i--)
对于(int j=0;j

从左侧测量第一个黑点,然后是frm右侧,然后是frm向上和frm底部。。
这是我使用的解决方案,基于@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);
    }

}
pub