我正在尝试获取jpeg图像上两点之间的精确像素数。为此,我使用Line类在WPF应用程序中使用用户绘制的线。我的解决方案是计算这条线的两个端点之间的距离似乎很差,并且返回的分数显然不是像素数。这是画线的代码:
// The "size" of an object for mouse over purposes.
private const int ObjectRadius = 3;
// We're over an object if the distance squared
// between the mouse and the object is less than this.
private const int OverDistSquared = ObjectRadius * ObjectRadius;
// The line we're drawing or moving.
private Line _selectedLine;
private List<Line> _lines = new List<Line>();
// True if we're moving the line's first starting end point.
private bool _movingStartEndPoint = false;
// The offset from the mouse to the object being moved.
private double _offsetX, _offsetY;
// Save the trash can dimensions.
private double _trashWidth, _trashHeight;
// The mouse is up. See whether we're over an end point or segment.
private void canDrawing_MouseMove_NotDown(object sender, MouseEventArgs e)
{
Cursor newCursor = Cursors.Cross;
// See what we're over.
Point location = e.MouseDevice.GetPosition(canDrawing);
if (MouseIsOverEndpoint(location, out _selectedLine, out _movingStartEndPoint))
newCursor = Cursors.Arrow;
else if (MouseIsOverLine(location, out _selectedLine))
newCursor = Cursors.Hand;
// Set the new cursor.
if (canDrawing.Cursor != newCursor)
canDrawing.Cursor = newCursor;
}
// See what we're over and start doing whatever is appropriate.
private void canDrawing_MouseDown(object sender, MouseButtonEventArgs e)
{
// See what we're over.
Point location = e.MouseDevice.GetPosition(canDrawing);
if (MouseIsOverEndpoint(location, out _selectedLine, out _movingStartEndPoint))
{
// Start moving this end point.
canDrawing.MouseMove -= canDrawing_MouseMove_NotDown;
canDrawing.MouseMove += canDrawing_MouseMove_MovingEndPoint;
canDrawing.MouseUp += canDrawing_MouseUp_MovingEndPoint;
// Remember the offset from the mouse to the point.
Point hitPoint;
if (_movingStartEndPoint)
hitPoint = new Point(_selectedLine.X1, _selectedLine.Y1);
else
hitPoint = new Point(_selectedLine.X2, _selectedLine.Y2);
_offsetX = hitPoint.X - location.X;
_offsetY = hitPoint.Y - location.Y;
}
else if (MouseIsOverLine(location, out _selectedLine))
{
// Start moving this segment.
canDrawing.MouseMove -= canDrawing_MouseMove_NotDown;
canDrawing.MouseMove += canDrawing_MouseMove_MovingSegment;
canDrawing.MouseUp += canDrawing_MouseUp_MovingSegment;
// Remember the offset from the mouse
// to the segment's first end point.
_offsetX = _selectedLine.X1 - location.X;
_offsetY = _selectedLine.Y1 - location.Y;
}
else
{
// Start drawing a new segment.
canDrawing.MouseMove -= canDrawing_MouseMove_NotDown;
canDrawing.MouseMove += canDrawing_MouseMove_Drawing;
canDrawing.MouseUp += canDrawing_MouseUp_Drawing;
_selectedLine = new Line
{
Stroke = Brushes.Red,
X1 = location.X,
Y1 = location.Y,
X2 = location.X,
Y2 = location.Y
};
canDrawing.Children.Add(_selectedLine);
}
}
#region Distance Methods
// See if the mouse is over an end point.
private bool MouseIsOverEndpoint(Point mousePt, out Line hitLine, out bool startEndpoint)
{
foreach (object obj in canDrawing.Children)
{
// Only process Lines.
if (obj is Line)
{
Line line = obj as Line;
// Check the starting point.
Point point = new Point(line.X1, line.Y1);
if (FindDistanceToPointSquared(mousePt, point) < OverDistSquared)
{
// We're over this point.
hitLine = line;
startEndpoint = true;
return true;
}
// Check the end point.
point = new Point(line.X2, line.Y2);
if (FindDistanceToPointSquared(mousePt, point) < OverDistSquared)
{
// We're over this point.
hitLine = line;
startEndpoint = false;
return true;
}
}
}
hitLine = null;
startEndpoint = false;
return false;
}
// See if the mouse is over a line segment.
private bool MouseIsOverLine(Point mousePt, out Line hitLine)
{
foreach (object obj in canDrawing.Children)
{
// Only process Lines.
if (obj is Line)
{
Line line = obj as Line;
// See if we're over this line.
Point closest;
Point pt1 = new Point(line.X1, line.Y1);
Point pt2 = new Point(line.X2, line.Y2);
if (FindDistanceToSegmentSquared(
mousePt, pt1, pt2, out closest)
< OverDistSquared)
{
// We're over this segment.
hitLine = line;
return true;
}
}
}
hitLine = null;
return false;
}
// Calculate the distance squared between two points.
private double FindDistanceToPointSquared(Point pt1, Point pt2)
{
double dx = pt1.X - pt2.X;
double dy = pt1.Y - pt2.Y;
return dx * dx + dy * dy;
}
// Calculate the distance squared between
// point pt and the segment p1 --> p2.
private double FindDistanceToSegmentSquared(Point pt, Point p1, Point p2, out Point closest)
{
double dx = p2.X - p1.X;
double dy = p2.Y - p1.Y;
if ((dx == 0) && (dy == 0))
{
// It's a point not a line segment.
closest = p1;
dx = pt.X - p1.X;
dy = pt.Y - p1.Y;
return dx * dx + dy * dy;
}
// Calculate the t that minimizes the distance.
double t = ((pt.X - p1.X) * dx + (pt.Y - p1.Y) * dy) / (dx * dx + dy * dy);
// See if this represents one of the segment's
// end points or a point in the middle.
if (t < 0)
{
closest = new Point(p1.X, p1.Y);
dx = pt.X - p1.X;
dy = pt.Y - p1.Y;
}
else if (t > 1)
{
closest = new Point(p2.X, p2.Y);
dx = pt.X - p2.X;
dy = pt.Y - p2.Y;
}
else
{
closest = new Point(p1.X + t * dx, p1.Y + t * dy);
dx = pt.X - closest.X;
dy = pt.Y - closest.Y;
}
return dx * dx + dy * dy;
}
private double FindDistanceToPoint(Point pt1, Point pt2)
{
double dx = pt1.X - pt2.X;
double dy = pt1.Y - pt2.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
#endregion Distance Methods
#region Moving End Point
// We're moving an end point.
private void canDrawing_MouseMove_MovingEndPoint(object sender, MouseEventArgs e)
{
// Move the point to its new location.
Point location = e.MouseDevice.GetPosition(canDrawing);
if (_movingStartEndPoint)
{
_selectedLine.X1 = location.X + _offsetX;
_selectedLine.Y1 = location.Y + _offsetY;
}
else
{
_selectedLine.X2 = location.X + _offsetX;
_selectedLine.Y2 = location.Y + _offsetY;
}
}
// Stop moving the end point.
private void canDrawing_MouseUp_MovingEndPoint(object sender, MouseEventArgs e)
{
// Reset the event handlers.
canDrawing.MouseMove += canDrawing_MouseMove_NotDown;
canDrawing.MouseMove -= canDrawing_MouseMove_MovingEndPoint;
canDrawing.MouseUp -= canDrawing_MouseUp_MovingEndPoint;
}
#endregion Moving End Point
#region Drawing
// We're drawing a new segment.
private void canDrawing_MouseMove_Drawing(object sender, MouseEventArgs e)
{
// Update the new line's end point.
Point location = e.MouseDevice.GetPosition(canDrawing);
_selectedLine.X2 = location.X;
_selectedLine.Y2 = location.Y;
}
// Stop drawing.
private void canDrawing_MouseUp_Drawing(object sender, MouseEventArgs e)
{
_selectedLine.Stroke = Brushes.DeepPink;
// Reset the event handlers.
canDrawing.MouseMove -= canDrawing_MouseMove_Drawing;
canDrawing.MouseMove += canDrawing_MouseMove_NotDown;
canDrawing.MouseUp -= canDrawing_MouseUp_Drawing;
// If the new segment has no length, delete it.
if ((_selectedLine.X1 == _selectedLine.X2) && (_selectedLine.Y1 == _selectedLine.Y2))
canDrawing.Children.Remove(_selectedLine);
else
{
_lines.Add(_selectedLine);
var point1 = new Point(_selectedLine.X1, _selectedLine.Y1);
var point2 = new Point(_selectedLine.X2, _selectedLine.Y2);
PixelsInMillimeterTextBox.Text = FindDistanceToPoint(point1, point2).ToString(CultureInfo.InvariantCulture);
}
}
#endregion Drawing
#region "Moving Segment"
// We're moving a segment.
private void canDrawing_MouseMove_MovingSegment(object sender, MouseEventArgs e)
{
// Find the new location for the first end point.
Point location = e.MouseDevice.GetPosition(canDrawing);
double newX1 = location.X + _offsetX;
double newY1 = location.Y + _offsetY;
// See how far we are moving that point.
double dx = newX1 - _selectedLine.X1;
double dy = newY1 - _selectedLine.Y1;
// Move the line.
_selectedLine.X1 = newX1;
_selectedLine.Y1 = newY1;
_selectedLine.X2 += dx;
_selectedLine.Y2 += dy;
}
// Stop moving the segment.
private void canDrawing_MouseUp_MovingSegment(object sender, MouseEventArgs e)
{
// Reset the event handlers.
canDrawing.MouseMove += canDrawing_MouseMove_NotDown;
canDrawing.MouseMove -= canDrawing_MouseMove_MovingSegment;
canDrawing.MouseUp -= canDrawing_MouseUp_MovingSegment;
// See if the mouse is over the trash can.
Point location = e.MouseDevice.GetPosition(canDrawing);
if ((location.X >= 0) && (location.X < _trashWidth) &&
(location.Y >= 0) && (location.Y < _trashHeight))
{
if (MessageBox.Show("Delete this segment?",
"Delete Segment?", MessageBoxButton.YesNo)
== MessageBoxResult.Yes)
{
// Delete the segment.
canDrawing.Children.Remove(_selectedLine);
}
}
}
#endregion // Moving End Point
这是包含图像的控件的XAML:
<Grid>
<Border Name="Border" BorderThickness="1" BorderBrush="#34558b" Margin="10,44,10,10">
<utility:ZoomBorder x:Name="border" ClipToBounds="True" Background="Gray">
<Canvas Name="canDrawing"
MouseMove="canDrawing_MouseMove_NotDown"
MouseDown="canDrawing_MouseDown">
<Image Stretch="None" Name="ReferenceImage" Canvas.Left="0" Canvas.Top="0"/>
</Canvas>
</utility:ZoomBorder>
</Border>
</Grid>
有一个Win32 GDI调用(LineDDA
),它将枚举两个点之间的点(像素)。
获取点数将为您提供以像素为单位的线的长度。
此answer具有您可以使用的C#代码。我用您的示例代码进行了尝试,并能够获得适当的像素长度。
这里是参考代码:
public static List<Point> GetPointsOnLine(System.Drawing.Point point1, System.Drawing.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);
注意,GetPointsOnLine
方法使用System.Drawing.Point
而不是System.Windows.Point
。