【问题标题】:MeasureString and DrawString differenceMeasureString 和 DrawString 的区别
【发布时间】:2020-11-05 03:18:20
【问题描述】:

为什么我必须将MeasureString() 结果宽度增加 21% size.Width = size.Width * 1.21f;DrawString() 中逃避自动换行?

我需要一个解决方案来获得准确的结果。

两个函数使用相同的字体、相同的字符串格式、相同的文本。


来自 OP 的回答:

  SizeF size = graphics.MeasureString(element.Currency, Currencyfont, new PointF(0, 0), strFormatLeft);
  size.Width = size.Width * 1.21f;
  int freespace = rect.Width - (int)size.Width;
  if (freespace < ImageSize) { if (freespace > 0) ImageSize = freespace; else ImageSize = 0; }
  int FlagY = y + (CurrencySize - ImageSize) / 2;
  int FlagX = (freespace - ImageSize) / 2;
  graphics.DrawImage(GetResourseImage(@"Flags." + element.Flag.ToUpper() + ".png"), 
         new Rectangle(FlagX, FlagY, ImageSize, ImageSize));
  graphics.DrawString(element.Currency, Currencyfont, Brushes.Black, 
       new Rectangle(FlagX + ImageSize, rect.Y, (int)(size.Width), CurrencySize), strFormatLeft);

我的代码。

【问题讨论】:

  • 显示代码。你做错了什么,乘数应该是 1.0

标签: c# .net string graphics


【解决方案1】:

MeasureString() 方法存在一些问题,尤其是在绘制非 ASCII 字符时。请改用 TextRenderer.MeasureText()。

【讨论】:

  • @ChocapicSz:说实话,我还没有找到单一可靠的字符串边界测量方法,无论编程环境如何......有趣的是,相同的文本,相同的字体,但不同的字体大小可能会产生完全不同的结果...我曾经比较过英语与翻译字符串的比率,结果很有趣:对于 Arial 10,翻译后的文本更宽,但对于 Arial 18,它比英语文本窄... C'est la vie .
【解决方案2】:

Graphics.MeasureString、TextRenderer.MeasureText 和 Graphics.MeasureCharacterRanges all 返回一个大小,其中包括字形周围的空白像素以适应升序和降序。

换句话说,它们返回“a”的高度与“d”(上升)或“y”(下降)的高度相同。如果您需要字形的真实大小,唯一的方法是绘制字符串并计算像素:

Public Shared Function MeasureStringSize(ByVal graphics As Graphics, ByVal text As String, ByVal font As Font) As SizeF

    ' Get initial estimate with MeasureText
    Dim flags As TextFormatFlags = TextFormatFlags.Left + TextFormatFlags.NoClipping
    Dim proposedSize As Size = New Size(Integer.MaxValue, Integer.MaxValue)
    Dim size As Size = TextRenderer.MeasureText(graphics, text, font, proposedSize, flags)

    ' Create a bitmap
    Dim image As New Bitmap(size.Width, size.Height)
    image.SetResolution(graphics.DpiX, graphics.DpiY)

    Dim strFormat As New StringFormat
    strFormat.Alignment = StringAlignment.Near
    strFormat.LineAlignment = StringAlignment.Near

    ' Draw the actual text
    Dim g As Graphics = graphics.FromImage(image)
    g.SmoothingMode = SmoothingMode.HighQuality
    g.TextRenderingHint = Drawing.Text.TextRenderingHint.AntiAliasGridFit
    g.Clear(Color.White)
    g.DrawString(text, font, Brushes.Black, New PointF(0, 0), strFormat)

    ' Find the true boundaries of the glyph
    Dim xs As Integer = 0
    Dim xf As Integer = size.Width - 1
    Dim ys As Integer = 0
    Dim yf As Integer = size.Height - 1

    ' Find left margin
    Do While xs < xf
        For y As Integer = ys To yf
            If image.GetPixel(xs, y).ToArgb <> Color.White.ToArgb Then
                Exit Do
            End If
        Next
        xs += 1
    Loop
    ' Find right margin
    Do While xf > xs
        For y As Integer = ys To yf
            If image.GetPixel(xf, y).ToArgb <> Color.White.ToArgb Then
                Exit Do
            End If
        Next
        xf -= 1
    Loop
    ' Find top margin
    Do While ys < yf
        For x As Integer = xs To xf
            If image.GetPixel(x, ys).ToArgb <> Color.White.ToArgb Then
                Exit Do
            End If
        Next
        ys += 1
    Loop
    ' Find bottom margin
    Do While yf > ys
        For x As Integer = xs To xf
            If image.GetPixel(x, yf).ToArgb <> Color.White.ToArgb Then
                Exit Do
            End If
        Next
        yf -= 1
    Loop

    Return New SizeF(xf - xs + 1, yf - ys + 1)

End Function

【讨论】:

  • 听起来效率低下,但确实如此:无法使用这两个函数,它们都包含填充。也将使用位图。谢谢!
【解决方案3】:

如果对任何人有帮助,我会将答案从 smirkingman 转换为 C#,修复内存错误(使用 - Dispose)和外部循环中断(无 TODO)。我还在图形(和字体)上使用了缩放,所以我也添加了这一点(否则不起作用)。它返回 RectangleF,因为我想精确定位文本(使用 Graphics.DrawText)。

不完美但足够满足我的目的源代码:

static class StringMeasurer
{
    private static SizeF GetScaleTransform(Matrix m)
    {
        /*
         3x3 matrix, affine transformation (skew - used by rotation)
         [ X scale,     Y skew,      0 ]
         [ X skew,      Y scale,     0 ]
         [ X translate, Y translate, 1 ]

         indices (0, ...): X scale, Y skew, Y skew, X scale, X translate, Y translate
         */
        return new SizeF(m.Elements[0], m.Elements[3]);
    }

    public static RectangleF MeasureString(Graphics graphics, Font f, string s)
    {
        //copy only scale, not rotate or transform
        var scale = GetScaleTransform(graphics.Transform);

        // Get initial estimate with MeasureText
        //TextFormatFlags flags = TextFormatFlags.Left | TextFormatFlags.NoClipping;
        //Size proposedSize = new Size(int.MaxValue, int.MaxValue);
        //Size size = TextRenderer.MeasureText(graphics, s, f, proposedSize, flags);
        SizeF sizef = graphics.MeasureString(s, f);
        sizef.Width *= scale.Width;
        sizef.Height *= scale.Height;
        Size size = sizef.ToSize();

        int xLeft = 0;
        int xRight = size.Width - 1;
        int yTop = 0;
        int yBottom = size.Height - 1;

        // Create a bitmap
        using (Bitmap image = new Bitmap(size.Width, size.Height))
        {
            image.SetResolution(graphics.DpiX, graphics.DpiY);

            StringFormat strFormat = new StringFormat();
            strFormat.Alignment = StringAlignment.Near;
            strFormat.LineAlignment = StringAlignment.Near;

            // Draw the actual text
            using (Graphics g = Graphics.FromImage(image))
            {
                g.SmoothingMode = graphics.SmoothingMode;
                g.TextRenderingHint = graphics.TextRenderingHint;
                g.Clear(Color.White);
                g.ScaleTransform(scale.Width, scale.Height);
                g.DrawString(s, f, Brushes.Black, new PointF(0, 0), strFormat);
            }
            // Find the true boundaries of the glyph

            // Find left margin
            for (;  xLeft < xRight; xLeft++)
                for (int y = yTop; y <= yBottom; y++)
                    if (image.GetPixel(xLeft, y).ToArgb() != Color.White.ToArgb())
                        goto OUTER_BREAK_LEFT;
        OUTER_BREAK_LEFT: ;

            // Find right margin
            for (; xRight > xLeft; xRight--)
                for (int y = yTop; y <= yBottom; y++)
                    if (image.GetPixel(xRight, y).ToArgb() != Color.White.ToArgb())
                        goto OUTER_BREAK_RIGHT;
        OUTER_BREAK_RIGHT: ;

            // Find top margin
            for (; yTop < yBottom; yTop++)
                for (int x = xLeft; x <= xRight; x++)
                    if (image.GetPixel(x, yTop).ToArgb() != Color.White.ToArgb())
                        goto OUTER_BREAK_TOP;
        OUTER_BREAK_TOP: ;

            // Find bottom margin
            for (; yBottom > yTop; yBottom-- )
                for (int x = xLeft; x <= xRight; x++)
                    if (image.GetPixel(x, yBottom).ToArgb() != Color.White.ToArgb())
                        goto OUTER_BREAK_BOTTOM;
        OUTER_BREAK_BOTTOM: ;
        }

        var pt = new PointF(xLeft, yTop);
        var sz = new SizeF(xRight - xLeft + 1, yBottom - yTop + 1);
        return new RectangleF(pt.X / scale.Width, pt.Y / scale.Height,
            sz.Width / scale.Width, sz.Height / scale.Height);
    }
}

【讨论】:

  • 处理位图是一个很好的建议,但所有这些 GOTO 都散发着“初学者”的味道。最好将 VB 代码粘贴到 converter.telerik.com 并为位图添加“使用”。
  • @smirkingman GOTOs 如果它们用于无条件跳转,则很臭'begginer',但如果它们用于多级循环中断(与 return 语句相同),它们是可以的。例如,如果我将这段代码重写为 Java,它不会是“goto”而是“break”关键字。阅读更多en.wikipedia.org/wiki/Goto#Common_usage_patterns_of_Goto
  • @peenut 我永远不会忘记阅读 Dijkstra 的著名论文 homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf 当我还是个小伙子时,它给我留下了深刻的印象 >;-),但 SO 不是辩论的地方跨度>
  • 对于那些喜欢 C# 的人来说,一个更简单的解决方案是将 VB 粘贴到 converter.telerik.com - 它会自动添加所有分号、大括号和诸如此类的东西,而无需任何 GOTOs
【解决方案4】:

This codeproject 上的文章提供了两种方法来获取由 DrawString 呈现的字符的确切大小。

【讨论】:

  • 不,只有宽度,它对高度没有帮助。无论如何,这是错误的文章,因为 Graphics.MeasureCharacterRanges 确实添加了填充!
  • 简单地使用带有StringFormat 参数的Graphics.MeasureString 重载(即传递StringFormat.GenericTypographic)就足以满足我的简单、仅宽度需求 - 感谢您链接到那篇文章!
  • 呃,谢谢@Thracx。我想知道为什么 MeasureString 如此不准确并查看所有这些解决方案,但完全错过了我忽略了 StringFormat 参数。
【解决方案5】:

就个人而言,最有效的方式和我推荐的方式一直是:

 const TextFormatFlags _textFormatFlags = TextFormatFlags.NoPadding | TextFormatFlags.NoPrefix | TextFormatFlags.PreserveGraphicsClipping;
    
 // Retrieve width
 int width = TextRenderer.MeasureText(element.Currency, Currencyfont, new Size(short.MaxValue, short.MaxValue), _textFormatFlags).Width + 1;

 // Retrieve height
 int _tempHeight1 = TextRenderer.MeasureText("_", Currencyfont).Height;
 int _tempHeight2 = (int)Math.Ceiling(Currencyfont.GetHeight());
 int height = Math.Max(_tempHeight1, _tempHeight2) + 1;

【讨论】:

    【解决方案6】:

    您可能需要在StringFormat 标志中添加以下内容:

    StringFormatFlags.FitBlackBox
    

    【讨论】:

    • 需要精确测量琴弦,不允许超出定位区域
    【解决方案7】:

    试试这个解决方案:http://www.codeproject.com/Articles/2118/Bypass-Graphics-MeasureString-limitation (在https://stackoverflow.com/a/11708952/908936找到它)

    代码:

    static public int MeasureDisplayStringWidth(Graphics graphics, string text, Font font)
    {
        System.Drawing.StringFormat format  = new System.Drawing.StringFormat ();
        System.Drawing.RectangleF   rect    = new System.Drawing.RectangleF(0, 0, 1000, 1000);
        var ranges  = new System.Drawing.CharacterRange(0, text.Length);
        System.Drawing.Region[] regions = new System.Drawing.Region[1];
    
        format.SetMeasurableCharacterRanges (new[] {ranges});
    
        regions = graphics.MeasureCharacterRanges (text, font, rect, format);
        rect    = regions[0].GetBounds (graphics);
    
        return (int)(rect.Right + 1.0f);
    }
    

    【讨论】:

      猜你喜欢
      • 2021-10-14
      • 2013-03-23
      • 2010-09-12
      • 1970-01-01
      • 2013-08-07
      • 2011-10-20
      • 2020-01-23
      • 1970-01-01
      • 2011-12-08
      相关资源
      最近更新 更多