8

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

我似乎找不到任何可以准确返回每个字符的边界框的示例。如何在 c#/GDI+ 中做到这一点?

理想情况下,我想得到每个灰色矩形,如下图所示:

在此处输入图像描述

4

6 回答 6

3

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

如果您使用 Graphics 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);
    }

}

看起来像这样:

在此处输入图像描述

于 2013-08-29T13:43:19.553 回答
3

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

一种可行的方法是测量第一个字符,然后是前两个字符,然后是前三个字符,依此类推,这样您就可以粗略地了解在一次点击中绘制它们时它们的最终位置。然后你可能需要再次测量每个字符以确定它的高度。如果 MeasureString 没有给出字符高度,而是给出了字体高度,您可能不得不求助于将每个字符渲染到屏幕外位图,然后扫描像素以确定字符的真实高度。

到那时,您可能会发现最好将 p/invoke 转到较低级别的字体 API,该 API 可以直接从字体中为您提供所需的信息。但是你可能需要使用 GDO+ 以外的东西来渲染它,因为它与你的工作结果对齐的机会可能并不好。

(你能说我对 GDI+ 字体渲染器的质量存在信任问题吗?)

于 2013-08-28T22:29:57.430 回答
2

我做了一个方法,得到了这样的东西

这是代码

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;
    }

. .

.

.

.

.

从左边测量第一个黑点,然后从右到右,然后从上到下。如果有帮助,请评分。

于 2013-08-28T23:16:40.813 回答
0

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

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

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

于 2013-08-28T22:57:33.127 回答
0

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

于 2013-08-28T23:10:49.460 回答
0

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

DrawString 例程针对在框中绘制(更多)文本进行了优化。如果你想更精确地绘制字符,那么 DrawString 是不够的。它在文本前后添加空格。

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

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

祝你好运!

于 2019-05-15T17:02:50.060 回答