在反转的Y轴上绘制文本
本文关键字:绘制 文本 | 更新日期: 2023-09-27 18:24:44
我正在编写一个可视化应用程序:它从XML加载建筑几何图形并在屏幕上绘制。这座建筑由长方形的房间组成,其中很多房间——所以我想在上面画上它们的名字。
我使用本教程来翻转表单中的Y轴,因为建筑数据存储在笛卡尔坐标中。并将它们全部转换为经典的Windows";y向下生长";绘图时的系统看起来很奇怪。
此外,我需要缩放和翻译我的";场景";到左下角
而且,在我最后的痛苦中,我需要再次翻转我的文本——因为它也会翻转!
正如教程所说,我需要:
- 翻转Y轴,缩放场景并将其移动到所需位置
- 在笛卡尔坐标系中绘制建筑几何图形(仅为矩形)
- 返回到";Y向下生长";系统,缩放并再次移动
- 在此"中绘制文本;经典的";系统
但是文本的坐标无效
它们从正确的位置向下移动:(
这就是我的问题-如何正确计算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反转的场景中绘制一个中心有指定文本的矩形
文本自动缩放以适应实际矩形大小。享受:)
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;
}