簡體   English   中英

如何獲得使用 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 類在 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 ) 將枚舉兩點之間的點(像素)。

獲取點數將為您提供線的長度(以像素為單位)。

答案包含您可以使用的 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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM