在反转的Y轴上绘制文本

本文关键字:绘制 文本 | 更新日期: 2023-09-27 18:24:44

我正在编写一个可视化应用程序:它从XML加载建筑几何图形并在屏幕上绘制。这座建筑由长方形的房间组成,其中很多房间——所以我想在上面画上它们的名字。

我使用本教程来翻转表单中的Y轴,因为建筑数据存储在笛卡尔坐标中。并将它们全部转换为经典的Windows";y向下生长";绘图时的系统看起来很奇怪。
此外,我需要缩放和翻译我的";场景";到左下角
而且,在我最后的痛苦中,我需要再次翻转我的文本——因为它也会翻转!

正如教程所说,我需要:

  1. 翻转Y轴,缩放场景并将其移动到所需位置
  2. 在笛卡尔坐标系中绘制建筑几何图形(仅为矩形)
  3. 返回到";Y向下生长";系统,缩放并再次移动
  4. 在此"中绘制文本;经典的";系统

但是文本的坐标无效
它们从正确的位置向下移动:(

这就是我的问题-如何正确计算Windows窗体对象中的新文本坐标

void VisualizerForm_Paint(object sender, PaintEventArgs e)
{
    // Setup graphics output settings
    var g = e.Graphics;
    g.Clear(Color.White);
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.PageUnit = GraphicsUnit.Pixel;
    
    // Move coordinate system center to the bottom left corner,
    // scale it to user-defined scale value and flip Y axis (mul it scale to -1)
    g.ScaleTransform(m_scale, -m_scale, MatrixOrder.Append);
    g.TranslateTransform(50, Height - 50, MatrixOrder.Append);
    
    // ... Draw some complex building geometry ...
    
    // Draw building room
    var customPen = new Pen(Color.Black, 1.0f / g.DpiX);
    var rect = new RectangleF(box.X1, box.Y1, (box.X2 - box.X1), (box.Y2 - box.Y1));
    g.DrawRectangle(customPen, box.X1, box.Y1, (box.X2 - box.X1), (box.Y2 - box.Y1));
    GraphicsState gs = g.Save();
    
    // First reset transform matrix
    g.ResetTransform();
    // Then again scale and move scene, but now with classic down-incresed Y axis
    g.ScaleTransform(m_scale, m_scale, MatrixOrder.Append);
    g.TranslateTransform(50, Height - 50, MatrixOrder.Append);
    
    // All Y coords now must be inverted :/ *sigh*
    box.Y1 *= -1.0f;
    box.Y2 *= -1.0f;
    rect = new RectangleF(box.X1, box.Y1, Math.Abs(box.X2 - box.X1), Math.Abs(box.Y2 - box.Y1));
    // FIXME: This text is drawing in incorrect place
    var fnt = new Font("Arial", 40f / g.DpiX, FontStyle.Bold, GraphicsUnit.Pixel);
    g.DrawString("ID: " + box.Id, fnt, Brushes.Black, rect, stringFormat);
    g.Restore(gs);
}

在反转的Y轴上绘制文本

终于拿到了
下面的类在Y反转的场景中绘制一个中心有指定文本的矩形
文本自动缩放以适应实际矩形大小。享受:)

class RectangleWithText
{
    RectangleF m_extent = new RectangleF();
    string m_text = "";
    Font m_textFont = null;
    RectangleF m_textRect = new RectangleF();
    public RectangleWithText( RectangleF extent, string text )
    {
        m_extent = extent;
        m_text = text;
    }
    public void Draw( Graphics g )
    {
        var dashedGrayPen = new Pen( Color.Gray, 1.0f / g.DpiX ) { DashStyle = DashStyle.Dash };
        var brownPen = new Pen( Color.Brown, 1.0f / g.DpiX );
        // Draw rectangle itself
        g.DrawRectangle( brownPen, m_extent.X, m_extent.Y, m_extent.Width, m_extent.Height );
        // Draw text on it
        var extentCenter = new PointF( ( m_extent.Left + m_extent.Right ) / 2, ( m_extent.Bottom + m_extent.Top ) / 2 );
        DrawText( g, m_text, extentCenter, m_extent );
        }
    }
    private void DrawText( Graphics g, string text, PointF ptStart, RectangleF extent )
    {
        var gs = g.Save();
        // Inverse Y axis again - now it grow down;
        // if we don't do this, text will be drawn inverted
        g.ScaleTransform( 1.0f, -1.0f, MatrixOrder.Prepend );
        if ( m_textFont == null )
        {
            // Find the maximum appropriate text size to fix the extent
            float fontSize = 100.0f;
            Font fnt;
            SizeF textSize;
            do
            {
                fnt = new Font( "Arial", fontSize / g.DpiX, FontStyle.Bold, GraphicsUnit.Pixel );
                textSize = g.MeasureString( text, fnt );
                m_textRect = new RectangleF( new PointF( ptStart.X - textSize.Width / 2.0f, -ptStart.Y - textSize.Height / 2.0f ), textSize );
                var textRectInv = new RectangleF( m_textRect.X, -m_textRect.Y, m_textRect.Width, m_textRect.Height );
                if ( extent.Contains( textRectInv ) )
                    break;
                fontSize -= 1.0f;
                if ( fontSize <= 0 )
                {
                    fontSize = 1.0f;
                    break;
                }
            } while ( true );
            m_textFont = fnt;
        }
        // Create a StringFormat object with the each line of text, and the block of text centered on the page
        var stringFormat = new StringFormat()
        {
            Alignment = StringAlignment.Center,
            LineAlignment = StringAlignment.Center
        };
        g.DrawString( text, m_textFont, Brushes.Black, m_textRect, stringFormat );
        g.Restore( gs );
    }
}

试试这个:

void DrawDigonalString(Graphics G, string S, Font F, Brush B, PointF P, int Angle)
{
    SizeF MySize = G.MeasureString(S, F);
    G.TranslateTransform(P.X + MySize.Width / 2, P.Y + MySize.Height / 2);
    G.RotateTransform(Angle);
    G.DrawString(S, F, B, new PointF(-MySize.Width / 2, -MySize.Height / 2));
    G.RotateTransform(-Angle);
    G.TranslateTransform(-P.X - MySize.Width / 2, -P.Y- MySize.Height / 2);
}

我最近遇到了这个线程,当时我想做与OP完全相同的事情。尝试将所有东西拼凑在一起很令人沮丧,尤其是因为上面的例子用DPI划分字体大小有点人为,尤其是在DPI更高的新屏幕上。

因此,我制作了自己的例子,并粘贴在下面。要使用此示例,请创建一个Windows窗体应用程序,并为"ResizeEnd"answers"Paint"生成处理程序("ResizeEnd"处理程序不是真正必要的,但它是通过稍微调整Form1窗口的大小来重新运行程序的一种方便方式)。然后用下面的代码替换Paint和resize处理程序的内容,并将两个辅助函数"ConvertRectangleToPointFArray()"answers"TextInsideRectangle()"复制粘贴到项目中。正如所写的那样,示例程序将在表单底部附近绘制一个坐标系,并标记";X〃;以及";Y";轴正确(这需要在绘制"Y"字符串之前将其反转。如果"verbose"bool变量设置为"true",则一些诊断信息将被写入"输出"窗口。享受吧!

=====================粘贴的代码==========================

  private void Form1_Paint(object sender, PaintEventArgs e)
  {
    //Step1: Change from Y-down to Y-up increasing using
    Graphics g = e.Graphics;
    g.PageUnit = GraphicsUnit.Millimeter; //  04/02/23 Changed Page units from pix to mm
    g.ScaleTransform(1.0f, -1.0f);//flip y
    Rectangle clientrect = this.ClientRectangle; //this is in pixels, NOT mm!
    PointF[] rectpoints = {new PointF(clientrect.X, clientrect.Y),new PointF(clientrect.X, clientrect.Bottom)};
    PointF[] ptf2 = { new PointF(clientrect.X, clientrect.Bottom) }; //have to have an array to use g.TransformPoints()
    g.TransformPoints(CoordinateSpace.Page, CoordinateSpace.Device, ptf2);
    g.TranslateTransform(ptf2[0].X, -ptf2[0].Y); //move origin to bottom-left corner
    g.TranslateTransform(20, 20);//move origin to right 20 and up 20 mm
    //Step2: draw coordinate system lines in new coordinate system
    g.DrawLine(new Pen(Color.Black, 1), 0, 0, 100, 0);
    g.DrawLine(new Pen(Color.Black, 1), 0, 0, 0, 100);
    //Step3: Save the transforms with g.Save()
    GraphicsState transState = g.Save(); //do this so can restore later
    //"Y" string.
    Rectangle Yrect = new Rectangle(-5,100, 10, 10); //this is in mm with origin at (20,20)mm from bottom left-hand corner of window
    StringFormat YdrawFormat = new StringFormat();
    YdrawFormat.Alignment = StringAlignment.Center;
    YdrawFormat.LineAlignment = StringAlignment.Center;
    //"X" string.
    Rectangle Xrect = new Rectangle(100,-5, 10, 10); //this is in mm with origin at (20,20)mm from bottom left-hand corner of window
    StringFormat XdrawFormat = new StringFormat();
    XdrawFormat.Alignment = StringAlignment.Center;
    XdrawFormat.LineAlignment = StringAlignment.Center;
    //now draw desired text
    //bool IsVerbose = true;
    bool IsVerbose = false;
    TextInsideRectangle(e, Yrect, "Y", YdrawFormat, IsVerbose);
    TextInsideRectangle(e, Xrect, "X", XdrawFormat, IsVerbose);
  }
  private void TextInsideRectangle(PaintEventArgs e, Rectangle rect, string text, StringFormat fmt, bool verbose)
  {
    //Purpose:  Place and size the given text inside the given rectangle
    //Inputs:
    //  PaintEventArgs e assumed to be set for y-up, origin at bottom left, mm scaling
    //  ptf = 2-element PointF array containing (rect.X, rect.Y), (rect.Width, rect.Height) in desired coord sys
    //  rect = Rectangle object in (x,y, width,height) format
    //  text = string object containing text to be displayed
    //Procedure:
    //  Step1: Save the current transform
    //  Step2: draw the given rectangle onto the Form surface (DEBUG only)
    //  Step3: Flip y axis so text is drawn correctly
    //  Step3: Draw the given text, centered in the given rectangle
    //  Step4: Iteratively modify the font size such that the given text fits inside the given rectangle
    //  Step5: Refresh the screen and redraw
    //Step1: Save the current transform
    Graphics g = e.Graphics;
    GraphicsState transState = g.Save(); //do this so can restore later
    //Step2: draw the given rectangle onto the Form surface (for debug only)
    if (verbose)
    {
        RectangleF mm_tgt_rect = rect;
        g.DrawRectangle(new Pen(Color.DarkCyan), Rectangle.Round(mm_tgt_rect));
    }
    //Step3: Flip y axis so text is drawn correctly
    g.ScaleTransform(1.0f, -1.0f, MatrixOrder.Prepend); //origin still at bottom left of screen
    //Step3: Draw the given text, centered in the given rectangle
    PointF rect_ctr = new PointF((rect.X + rect.Width/2), -rect.Y - rect.Height/2);
    // Create font and brush.
    int fontsize = 1;
    Font drawFont = new Font("Arial", fontsize);
    SolidBrush drawBrush = new SolidBrush(Color.Black);
    //Step4: Iteratively modify the font size such that the given text fits inside the given rectangle
    SizeF textSize = g.MeasureString(text, drawFont);
    if (verbose)
    {
        System.Diagnostics.Debug.WriteLine("text size is {0} x {1}, enclosing rect size is {2}, {3} ",
          textSize.Width, textSize.Height, rect.Width, rect.Height);
    }
    while (textSize.Width <= rect.Width && textSize.Height <= rect.Height)
    {
        if (verbose)
        {
          System.Diagnostics.Debug.WriteLine("text size of {0} x {1} is smaller than rect", textSize.Width, textSize.Height);
        }
        fontsize++;
        drawFont = new Font("Arial", fontsize);
        textSize = g.MeasureString(text, drawFont);
        e.Graphics.DrawString(text, drawFont, drawBrush, rect_ctr.X, rect_ctr.Y, fmt);//this is the 'inverted/reversed' text
    }
    if (verbose)
    {
        System.Diagnostics.Debug.WriteLine("text size of {0} x {1} is smaller than rect", textSize.Width, textSize.Height);
        System.Diagnostics.Debug.WriteLine("final text size = {0} x {1}: enclosing rect size is {2}, {3}",
          textSize.Width, textSize.Height, rect.Width, rect.Height);
    }
    //Step5: Refresh the screen and redraw
    e.Graphics.DrawString(text, drawFont, drawBrush, rect_ctr.X, rect_ctr.Y, fmt);
  }
  private void Form1_ResizeEnd(object sender, EventArgs e)
  {
    this.Invalidate();
  }
  private PointF[] ConvertRectangleToPointFArray(RectangleF rectF)
  {
    //Purpose: convert a Rectangle or RectangleF object into a 2-element array of PointF objects
    //Inputs:
    //  rectF = Rectangle or RectangleF object
    //Outputs:
    //  returns a 2-element array of PointF objects
    //  points[0] = PointF(rectF.X, rectF.Y)
    //  points[1] = PointF(rectF.Width,rectF.Height)
    PointF[] points = new PointF[2];
    points[0] = new PointF(rectF.X, rectF.Y);
    points[1] = new PointF(rectF.Width,rectF.Height);
    return points;
  }