C ++ Draw a line in GDI with arrow filled at the end

Can someone please help me with some ideas (preferably with a code) on how to draw a line with a filled arrow at the end?

The arrow head should be correctly oriented in the direction of the line. I want to do this in C ++.

+3
source share
1 answer

Small geometry is required to determine the angles of a triangle that form the arrowhead.

Suppose the line goes from P0 to P1 and that we want the tip of the arrow on P1. To find the "back corners" of the arrowhead, we want to move backward along the line from the tip, and then turn left and right so that the arrow is a little wide.

It would be simple if the line were aligned with the x or y axis. To process the lines at any angle, we can build a coordinate system whose axes are parallel and perpendicular to the original line. We will call these axes u and v, and u is indicated in the direction of the line and v is perpendicular to it.

Now we can start with P0 and go to the corners, stepping in the directions defined by u and v, scaled for any length and width of the arrow that we want.

In code:

constexpr int Round(float x) { return static_cast<int>(x + 0.5f); }

// Draws a line from p0 to p1 with an arrowhead at p1.  Arrowhead is outlined
// with the current pen and filled with the current brush.
void DrawArrow(HDC hdc, POINT p0, POINT p1, int head_length, int head_width) {
    ::MoveToEx(hdc, p0.x, p0.y, nullptr);
    ::LineTo(hdc, p1.x, p1.y);

    const float dx = static_cast<float>(p1.x - p0.x);
    const float dy = static_cast<float>(p1.y - p0.y);
    const auto length = std::sqrt(dx*dx + dy*dy);
    if (head_length < 1 || length < head_length) return;

    // ux,uy is a unit vector parallel to the line.
    const auto ux = dx / length;
    const auto uy = dy / length;

    // vx,vy is a unit vector perpendicular to ux,uy
    const auto vx = -uy;
    const auto vy = ux;

    const auto half_width = 0.5f * head_width;

    const POINT arrow[3] =
        { p1,
          POINT{ Round(p1.x - head_length*ux + half_width*vx),
                 Round(p1.y - head_length*uy + half_width*vy) },
          POINT{ Round(p1.x - head_length*ux - half_width*vx),
                 Round(p1.y - head_length*uy - half_width*vy) }
        };
    ::Polygon(hdc, arrow, 3);
}

And a demo (using WTL):

LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL &) {
    PAINTSTRUCT ps;
    BeginPaint(&ps);

    RECT rc;
    GetClientRect(&rc);

    const auto origin = POINT{rc.left + (rc.right - rc.left)/2,
                                rc.top + (rc.bottom - rc.top)/2 };
    const auto pi = 3.1415926f;
    const auto tau = 2.0f*pi;
    const auto cxInch = ::GetDeviceCaps(ps.hdc, LOGPIXELSX);
    const auto radius = 2.0f * cxInch;
    const auto size = Round(0.333f * cxInch);
    for (float theta = 0.0f; theta < tau; theta += tau/12.0f) {
        const auto p1 =
            POINT{Round(origin.x + radius * std::cos(theta)),
                    Round(origin.y + radius * std::sin(theta))};
        DrawArrow(ps.hdc, origin, p1, size, size/3);
    }
    EndPaint(&ps);
    return 0;
}
+2
source

Source: https://habr.com/ru/post/1774542/


All Articles