I'm trying to get exact pixel count between two points on jpeg image. To do so i use user drawn line in WPF application using Line class. My solution is to calculate distance between two end points of this line seems to be pretty off and it returns fractional number which obviously not pixel count. Here is the code for drawing line:
// 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
And here is the XAML for the control containing image:
<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>
There is a Win32 GDI call (LineDDA
) that will enumerate the points (pixels) between two points.
Getting the count of points will give you the length of the line in pixels.
This answer has C# code that you can use. I tried it with your sample code and was able to get a proper pixel length.
Here is the code for reference:
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);
Note, that the GetPointsOnLine
method using System.Drawing.Point
instead of System.Windows.Point
.