如何在使用 WPF 渲染的 jpeg 图像上获取两点之间的精确像素数。我的解决方案不同于 GIMP
How to get exact pixel count between two points on jpeg image which is rendered with WPF. My solution differs from GIMP
我正在尝试获取 jpeg 图像上两点之间的精确像素数。为此,我使用 Line class 在 WPF 应用程序中使用用户绘制的线。我的解决方案是计算这条线的两个端点之间的距离似乎很远,它 returns 显然不是像素数的小数。这是画线的代码:
// 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
。
我正在尝试获取 jpeg 图像上两点之间的精确像素数。为此,我使用 Line class 在 WPF 应用程序中使用用户绘制的线。我的解决方案是计算这条线的两个端点之间的距离似乎很远,它 returns 显然不是像素数的小数。这是画线的代码:
// 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
。