两端画线偏移
Drawline offset at both ends
我正在尝试使用 DrawLine
方法绘制虚线。然而,它在两端增加了轻微的偏移,使线条变得难看。如何防止在末端添加偏移量?
protected override void OnPaint (PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawLine (Dot, new Point (10, 50), new Point (70, 50));
g.DrawLine (Dot, new Point (70, 50), new Point (70, 100));
}
Pen Dot = new Pen (Brushes.Black, 2) { DashStyle = DashStyle.Dot };
输出
预期结果
您提供的坐标是指向结果线左上角位置的点。因此,当您绘制一条两像素宽的线时,您应该计算起点和端点以包括该线的宽度。
在这种情况下,您可能希望将垂直线稍微向左和顶部偏移(准确地说是 borderwith
)。
因此,通过添加(或减去)等于线宽的偏移量,结果是这样的:
protected override void OnPaint (PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawLine (Dot, new Point (10, 50), new Point (70, 50));
g.DrawLine (Dot, new Point (69, 49), new Point (69, 100));
}
Pen Dot = new Pen (Brushes.Black, 2) { DashStyle = DashStyle.Dot };
目标简单合理:
- 我们希望我们的行以点开始结束。
而且游戏规则也比较简单:
- 默认情况下,点和间隙都是正方形。
- 所有线条都用
PenAlignment.Center
绘制。
不幸的是,组合的结果相当复杂,所以请耐心等待...
我们先来看第一个规则;让我们忽略其他 DashStyles
,继续使用 DashStyle.Dot
。默认情况下,每个点和每个间隙的两侧都有 Pen.Width
像素。这直接导致第一期:
- 如果我们的线条宽度不能被
Pen.Width
整除,我们就有麻烦了。
- 要以点开始和结束,我们需要
n
个点和 n-1
个间隔。
还有更多,但让我们接下来看看第二条规则;为了说明这一点,我画了这张放大了 10 倍的图像:
这是创建彩色部分的代码:
g.FillRectangle(Brushes.Yellow, 15, 15, 10, 10);
g.DrawRectangle(Pens.Orange, 10, 10, 10, 10);
g.DrawLine(Pens.OrangeRed, 10, 5, 40, 5);
using (Pen pen = new Pen(Color.Red, 2f) { DashStyle = DashStyle.Dot })
g.DrawLine(pen, 10, 30, 48, 30);
using (Pen pen = new Pen(Color.Crimson, 2f))
g.DrawLine(pen, 10, 40, 48, 40);
using (Pen pen = new Pen(Color.DarkMagenta, 3f))
g.DrawLine(pen, 10, 50, 48, 50);
仔细看看线条是怎么画的!
(旁白:您可能还想看看 DrawRectangle
和 FillRectangle
的区别!)
- 水平线在正确的坐标处开始和结束,但它们会向下扩展(如果它们的 Pen.Width = 1)或在 y 坐标上方和下方扩展。
- 垂直线当然也有同样的作用。
问题是它们无法在(外)边缘拼接在一起。
那我们能做什么呢?我认为 DashOffset
没有帮助。但是还有另一个调整 Pen
的选项:我们可以将其 DashPattern
设置为使用 自定义值 .
我们需要的值是两个 floats
,包含点和间隙的 缩放 。默认情况下,这两个值都是 1f
。我决定让点保持正方形,只修改间隙。这是一个通过
解决问题的函数
- 在两侧将线宽扩大半个笔宽,使外边缘相交
- 根据需要扩大间隙以适合行长度
这里是画线功能;它需要 Graphics
对象,一个 Pen
,两端 Points
和一个 byte
来告诉我们这条线是独立的还是 connect 到其他线路,就像在我们的示例中一样。
为了建立一个良好的连接,这将与半透明笔刷一起很好地工作,我们需要能够跳过一个点开头或结尾,甚至两者兼而有之,例如当我们想要插入一条对角线时,如下面的测试所示。
跳过值是 0
跳过 none,1 or 2
跳过第一个或最后一个点,3
跳过两个点。当然,您可能想改用 enumeration
。
void DrawDottedLine(Graphics g, Pen pen_, Point pa_, Point pb, byte skipDots)
{
float pw = pen_.Width;
float pw2 = pen_.Width / 2f;
Pen pen = (Pen)pen_.Clone();
// find out directions:
int sigX = Math.Sign(pb_.X - pa_.X);
int sigY = Math.Sign(pb_.Y - pa_.Y);
// move the end points out a bit:
PointF pa = new PointF(pa_.X - pw2 * sigX, pa_.Y - pw2 * sigY);
PointF pb = new PointF(pb_.X + pw2 * sigX, pb_.Y + pw2 * sigY);
// find line new length:
float lw = (float)(Math.Abs(pb.X - pa.X));
float lh = (float)(Math.Abs(pb.Y - pa.Y));
float ll = (float)(Math.Sqrt(lw * lw + lh * lh));
// dot length:
float dl = ll / pw;
// dot+gap count: round to nearest odd int:
int dc = (int)(2 * Math.Round((dl + 1) / 2) - 1);
// gap count:
int gc = dc / 2 ;
// dot count:
dc = gc + 1;
// gap scaling
float gs = (ll - dc * pw) / (pw * gc);
// our custom dashpattern
pen.DashPattern = new float[] { 1, gs };
// maybe skip 1st and/or last dots:
if (skipDots % 2 == 1) pa = new PointF(pa_.X + pw * sigX, pa_.Y + pw * sigY);
if (skipDots > 1) pb = new PointF(pb_.X - pw * sigX, pb_.Y - pw * sigY);
// finally we can draw the line:
g.DrawLine(pen, pa, pb);
// dispose of pen clone
pen.Dispose();
}
经过一些明显的准备后,我将点移出一点,然后计算垂直或水平线的点数和间隙数。然后我计算修改后的间隙比例。
这是放大 4 倍的结果,绘制四条线形成一个矩形,笔宽从 1/3 - 10/3
:
这是我用的测试平台;注意使用半透明黑色来说明角是如何正确绘制的,即不重叠:
Pen Dot = new Pen(Color.Black, 1f);
Point pa = new Point(10, 50);
Point pb = new Point(70, 50);
Point pc = new Point(70, 100);
Point pd = new Point(10, 100);
for (int i = 1; i < 10; i++ )
{
Dot = new Pen(Color.FromArgb(128, Color.Black), i / 3f){ DashStyle = DashStyle.Dot };
g.TranslateTransform(10, 10);
DrawDottedLine(g, Dot, pa, pb, 2);
DrawDottedLine(g, Dot, pb, pc, 2);
DrawDottedLine(g, Dot, pc, pd, 2);
DrawDottedLine(g, Dot, pd, pa, 2);
DrawDottedLine(g, Dot, pd, pb, 3);
}
我真的希望可以通过简单地使用 DrawLines
来避免连接问题,但这没有用,在找出这个解决方案后我并不感到惊讶它没有..
我正在尝试使用 DrawLine
方法绘制虚线。然而,它在两端增加了轻微的偏移,使线条变得难看。如何防止在末端添加偏移量?
protected override void OnPaint (PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawLine (Dot, new Point (10, 50), new Point (70, 50));
g.DrawLine (Dot, new Point (70, 50), new Point (70, 100));
}
Pen Dot = new Pen (Brushes.Black, 2) { DashStyle = DashStyle.Dot };
输出
预期结果
您提供的坐标是指向结果线左上角位置的点。因此,当您绘制一条两像素宽的线时,您应该计算起点和端点以包括该线的宽度。
在这种情况下,您可能希望将垂直线稍微向左和顶部偏移(准确地说是 borderwith
)。
因此,通过添加(或减去)等于线宽的偏移量,结果是这样的:
protected override void OnPaint (PaintEventArgs e) {
Graphics g = e.Graphics;
g.DrawLine (Dot, new Point (10, 50), new Point (70, 50));
g.DrawLine (Dot, new Point (69, 49), new Point (69, 100));
}
Pen Dot = new Pen (Brushes.Black, 2) { DashStyle = DashStyle.Dot };
目标简单合理:
- 我们希望我们的行以点开始结束。
而且游戏规则也比较简单:
- 默认情况下,点和间隙都是正方形。
- 所有线条都用
PenAlignment.Center
绘制。
不幸的是,组合的结果相当复杂,所以请耐心等待...
我们先来看第一个规则;让我们忽略其他 DashStyles
,继续使用 DashStyle.Dot
。默认情况下,每个点和每个间隙的两侧都有 Pen.Width
像素。这直接导致第一期:
- 如果我们的线条宽度不能被
Pen.Width
整除,我们就有麻烦了。 - 要以点开始和结束,我们需要
n
个点和n-1
个间隔。
还有更多,但让我们接下来看看第二条规则;为了说明这一点,我画了这张放大了 10 倍的图像:
这是创建彩色部分的代码:
g.FillRectangle(Brushes.Yellow, 15, 15, 10, 10);
g.DrawRectangle(Pens.Orange, 10, 10, 10, 10);
g.DrawLine(Pens.OrangeRed, 10, 5, 40, 5);
using (Pen pen = new Pen(Color.Red, 2f) { DashStyle = DashStyle.Dot })
g.DrawLine(pen, 10, 30, 48, 30);
using (Pen pen = new Pen(Color.Crimson, 2f))
g.DrawLine(pen, 10, 40, 48, 40);
using (Pen pen = new Pen(Color.DarkMagenta, 3f))
g.DrawLine(pen, 10, 50, 48, 50);
仔细看看线条是怎么画的!
(旁白:您可能还想看看 DrawRectangle
和 FillRectangle
的区别!)
- 水平线在正确的坐标处开始和结束,但它们会向下扩展(如果它们的 Pen.Width = 1)或在 y 坐标上方和下方扩展。
- 垂直线当然也有同样的作用。
问题是它们无法在(外)边缘拼接在一起。
那我们能做什么呢?我认为 DashOffset
没有帮助。但是还有另一个调整 Pen
的选项:我们可以将其 DashPattern
设置为使用 自定义值 .
我们需要的值是两个 floats
,包含点和间隙的 缩放 。默认情况下,这两个值都是 1f
。我决定让点保持正方形,只修改间隙。这是一个通过
- 在两侧将线宽扩大半个笔宽,使外边缘相交
- 根据需要扩大间隙以适合行长度
这里是画线功能;它需要 Graphics
对象,一个 Pen
,两端 Points
和一个 byte
来告诉我们这条线是独立的还是 connect 到其他线路,就像在我们的示例中一样。
为了建立一个良好的连接,这将与半透明笔刷一起很好地工作,我们需要能够跳过一个点开头或结尾,甚至两者兼而有之,例如当我们想要插入一条对角线时,如下面的测试所示。
跳过值是 0
跳过 none,1 or 2
跳过第一个或最后一个点,3
跳过两个点。当然,您可能想改用 enumeration
。
void DrawDottedLine(Graphics g, Pen pen_, Point pa_, Point pb, byte skipDots)
{
float pw = pen_.Width;
float pw2 = pen_.Width / 2f;
Pen pen = (Pen)pen_.Clone();
// find out directions:
int sigX = Math.Sign(pb_.X - pa_.X);
int sigY = Math.Sign(pb_.Y - pa_.Y);
// move the end points out a bit:
PointF pa = new PointF(pa_.X - pw2 * sigX, pa_.Y - pw2 * sigY);
PointF pb = new PointF(pb_.X + pw2 * sigX, pb_.Y + pw2 * sigY);
// find line new length:
float lw = (float)(Math.Abs(pb.X - pa.X));
float lh = (float)(Math.Abs(pb.Y - pa.Y));
float ll = (float)(Math.Sqrt(lw * lw + lh * lh));
// dot length:
float dl = ll / pw;
// dot+gap count: round to nearest odd int:
int dc = (int)(2 * Math.Round((dl + 1) / 2) - 1);
// gap count:
int gc = dc / 2 ;
// dot count:
dc = gc + 1;
// gap scaling
float gs = (ll - dc * pw) / (pw * gc);
// our custom dashpattern
pen.DashPattern = new float[] { 1, gs };
// maybe skip 1st and/or last dots:
if (skipDots % 2 == 1) pa = new PointF(pa_.X + pw * sigX, pa_.Y + pw * sigY);
if (skipDots > 1) pb = new PointF(pb_.X - pw * sigX, pb_.Y - pw * sigY);
// finally we can draw the line:
g.DrawLine(pen, pa, pb);
// dispose of pen clone
pen.Dispose();
}
经过一些明显的准备后,我将点移出一点,然后计算垂直或水平线的点数和间隙数。然后我计算修改后的间隙比例。
这是放大 4 倍的结果,绘制四条线形成一个矩形,笔宽从 1/3 - 10/3
:
这是我用的测试平台;注意使用半透明黑色来说明角是如何正确绘制的,即不重叠:
Pen Dot = new Pen(Color.Black, 1f);
Point pa = new Point(10, 50);
Point pb = new Point(70, 50);
Point pc = new Point(70, 100);
Point pd = new Point(10, 100);
for (int i = 1; i < 10; i++ )
{
Dot = new Pen(Color.FromArgb(128, Color.Black), i / 3f){ DashStyle = DashStyle.Dot };
g.TranslateTransform(10, 10);
DrawDottedLine(g, Dot, pa, pb, 2);
DrawDottedLine(g, Dot, pb, pc, 2);
DrawDottedLine(g, Dot, pc, pd, 2);
DrawDottedLine(g, Dot, pd, pa, 2);
DrawDottedLine(g, Dot, pd, pb, 3);
}
我真的希望可以通过简单地使用 DrawLines
来避免连接问题,但这没有用,在找出这个解决方案后我并不感到惊讶它没有..