C# 计算直线的精确像素
假设我想试着画一条直线,尽管有任何角度C# 计算直线的精确像素,c#,.net,winforms,gdi+,C#,.net,Winforms,Gdi+,假设我想试着画一条直线,尽管有任何角度 public class Line : Control { public Point start { get; set; } public Point end { get; set; } public Pen pen = new Pen(Color.Red); protected override void OnPaint(PaintEventArgs e) { e.Graphics.DrawLin
public class Line : Control
{
public Point start { get; set; }
public Point end { get; set; }
public Pen pen = new Pen(Color.Red);
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawLine(pen, start, end);
base.OnPaint(e);
}
}
此行是在自定义控件上生成的
现在,我如何计算生成线条的确切像素,以便使用
MouseMove
实现命中测试有Win32调用,用于枚举使用GDI调用绘制的线条的像素。我相信这是你想要完成的最好的技巧。请参阅及其关联的回调
下面是您将如何从C#使用它。请注意,根据LineDDA的文档,输出中不包括端点
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Runtime.InteropServices;
public static List<Point> GetPointsOnLine(Point point1, Point point2)
{
var points = new List<Point>();
var handle = GCHandle.Alloc(points);
try
{
LineDDA(point1.X, point1.Y, point2.X, point2.Y, GetPointsOnLineCallback, GCHandle.ToIntPtr(handle));
}
finally
{
handle.Free();
}
return points;
}
private static void GetPointsOnLineCallback(int x, int y, IntPtr lpData)
{
var handle = GCHandle.FromIntPtr(lpData);
var points = (List<Point>) handle.Target;
points.Add(new Point(x, y));
}
[DllImport("gdi32.dll")]
private static extern bool LineDDA(int nXStart, int nYStart, int nXEnd, int nYEnd, LineDDAProc lpLineFunc, IntPtr lpData);
// The signature for the callback method
private delegate void LineDDAProc(int x, int y, IntPtr lpData);
使用系统;
使用System.Collections.Generic;
使用系统图;
使用System.Runtime.InteropServices;
公共静态列表GetPointsOnLine(点1、点2)
{
var points=新列表();
变量句柄=GCHandle.Alloc(点);
尝试
{
LineDDA(point1.X、point1.Y、point2.X、point2.Y、GetPointsOnLineCallback、GCHandle.ToIntPtr(handle));
}
最后
{
handle.Free();
}
返回点;
}
私有静态void GetPointsOnLineCallback(intx、inty、IntPtr lpData)
{
var handle=GCHandle.FromIntPtr(lpData);
var points=(List)handle.Target;
点。添加(新点(x,y));
}
[DllImport(“gdi32.dll”)]
私有静态外部bool-LineDDA(int-nXStart、int-nXStart、int-nXEnd、int-nYEnd、LineDDAProc-lpLineFunc、IntPtr-lpData);
//回调方法的签名
私有委托void LineDDAProc(int x、int y、IntPtr lpData);
您应该看看哪个提供了一些代码来计算从一个点到具有起点和终点的给定线段的距离。它提供了C++和JavaScript版本,它们都非常接近于C语言。我将向您的Line类添加一个使用该代码的方法:
public class Line : Control
{
public Point start { get; set; }
public Point end { get; set; }
public Pen pen = new Pen(Color.Red);
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawLine(pen, start, end);
base.OnPaint(e);
}
public float DistanceToLine(Point x)
{
// do your distance calculation here based on the link provided.
}
}
然后检查距离是否小于2像素。如果您真的想这样做,请绘制两次控件:
在屏幕外,您可以关闭抗锯齿功能,以便能够准确地读取写入的颜色值。现在您可以简单地从位图中读取。如果需要点击测试多行,请将索引值置于颜色中 有更复杂的方法可以做到这一点,但简单的方法只是处理自定义控件的单击事件。换句话说,为由
控件
基类引发的添加处理程序。这样,Windows会为您完成所有的命中测试
如果用户单击控件上的任何位置,将引发MouseClick
事件,您可以根据需要对其进行处理。否则,不会引发任何事件。简单的缩影
在MouseClick
事件处理程序中,您将在客户端坐标中获得一个点(e.Location
),这意味着该位置相对于客户端控件的左上角
出于测试目的,我刚刚在空表单中添加了一个标签
控件,关闭了自动调整大小
,并将背景色
设置为红色。然后我把它做成一行,并为MouseClick
事件添加了一个处理程序。处理程序如下所示:
private void redLabel_MouseClick(object sender, MouseEventArgs e)
{
// Fired whenever the control is clicked; e.Location gives the location of
// the mouse click in client coordinates.
Debug.WriteLine("The control was clicked at " + e.Location);
}
var hitTest = new LineIntersectionChecker(p1, p2);
if (hitTest.IsOnLine(p))
...
这种简单的命中测试方法依赖于这样一个事实:就Windows而言,控件的物理边界与其逻辑边界相同。因此,要使其与自定义控件配合使用,您需要确保将其
Size
属性设置为其实际逻辑尺寸(即线条的宽度和厚度)。如果您只想查看鼠标是否靠近线段,你不需要知道像素的确切位置-你只需要知道它们是否在一定的距离内
这里有一个我自己设计的小班。它只使用直线的标准公式y=mx+c
计算任何特定点是否在直线的特定距离(公差)内
给定两个点,p1
和p2
,这两个点是要命中测试的线的端点的坐标,您可以这样初始化它:
private void redLabel_MouseClick(object sender, MouseEventArgs e)
{
// Fired whenever the control is clicked; e.Location gives the location of
// the mouse click in client coordinates.
Debug.WriteLine("The control was clicked at " + e.Location);
}
var hitTest = new LineIntersectionChecker(p1, p2);
if (hitTest.IsOnLine(p))
...
然后检查另一个点,p
是否在该行上,如下所示:
private void redLabel_MouseClick(object sender, MouseEventArgs e)
{
// Fired whenever the control is clicked; e.Location gives the location of
// the mouse click in client coordinates.
Debug.WriteLine("The control was clicked at " + e.Location);
}
var hitTest = new LineIntersectionChecker(p1, p2);
if (hitTest.IsOnLine(p))
...
类实现:
public sealed class LineIntersectionChecker
{
private readonly PointF _p1;
private readonly PointF _p2;
private readonly double _slope;
private readonly double _yIntersect;
private readonly double _tolerance;
private readonly double _x1;
private readonly double _x2;
private readonly double _y1;
private readonly double _y2;
private readonly bool _isHorizontal;
private readonly bool _isVertical;
public LineIntersectionChecker(PointF p1, PointF p2, double tolerance = 1.0)
{
_p1 = p1;
_p2 = p2;
_tolerance = tolerance;
_isVertical = (Math.Abs(p1.X - p2.X) < 0.01);
_isHorizontal = (Math.Abs(p1.Y - p2.Y) < 0.01);
if (_isVertical)
{
_slope = double.NaN;
_yIntersect = double.NaN;
}
else // Useable.
{
_slope = (p1.Y - p2.Y)/(double) (p1.X - p2.X);
_yIntersect = p1.Y - _slope * p1.X ;
}
if (_p1.X < _p2.X)
{
_x1 = _p1.X - _tolerance;
_x2 = _p2.X + _tolerance;
}
else
{
_x1 = _p2.X - _tolerance;
_x2 = _p1.X + _tolerance;
}
if (_p1.Y < _p2.Y)
{
_y1 = _p1.Y - _tolerance;
_y2 = _p2.Y + _tolerance;
}
else
{
_y1 = _p2.Y - _tolerance;
_y2 = _p1.Y + _tolerance;
}
}
public bool IsOnLine(PointF p)
{
if (!inRangeX(p.X) || !inRangeY(p.Y))
return false;
if (_isHorizontal)
return inRangeY(p.Y);
if (_isVertical)
return inRangeX(p.X);
double expectedY = p.X*_slope + _yIntersect;
return (Math.Abs(expectedY - p.Y) <= _tolerance);
}
private bool inRangeX(double x)
{
return (_x1 <= x) && (x <= _x2);
}
private bool inRangeY(double y)
{
return (_y1 <= y) && (y <= _y2);
}
}
所有这些听起来很像是你在重新发明轮子。忘了winforms使用WPF,WPF已经完成了这一切。你想看看鼠标是否在这条随机线的X像素范围内吗?@Tombala是的,我可以处理所有其他事情。我只需要知道如何计算2之间的像素points@WinCoder-这取决于你所关注的“线”的抽象。例如,您可能希望将该行看作是“0”宽度的“数学”线,并想知道所选像素是否与该理论线相交。或者,您可能关心的是其颜色被
e.Graphics.DrawLine(画笔、开始、结束)
命令更改的实际像素,这将取决于抗锯齿等设置。为此,您应该使用数学,计算直线点的法线。法线长度是否小于1,或者您希望的精度是多少,我们称之为碰撞。或者超级简单,计算两个距离(从鼠标位置到线条的每个点)。如果这些距离的总和接近线的长度,你也会有冲突,但我对C非常陌生,更不用说Win32了,因此我对这方面真的一无所知。不管怎样,谢谢给了我另一种方法,如果其他东西都枯竭了,我可以用它。再次感谢你的帮助。考虑接受这个答案,或者调整你的问题的标题,以更好地表明这是一个热门的测试问题(然后接受别人的答案)。这将帮助其他人稍后找到答案