C++中使用GDI绘制带填充箭头的线段

3

请问有没有人能够帮我提供一些关于如何绘制一条带填充箭头结尾的线段的思路(最好有代码)?

箭头必须正确地朝向线段的方向。我想在C++中实现这个功能。


Windows GDI?您觉得应该使用windows标签在这里获取帮助。 - Elemental
1
你尝试过 Pen::SetEndCap() 吗? - Hans Passant
2个回答

4
需要翻译的内容:

需要一些几何知识才能确定组成箭头的三角形的角落。

假设线段从P0到P1,并且我们希望箭头的尖端在P1处。为了找到箭头的“后角”,我们要沿着线从尖端向后移动,然后向左和向右转弯以使箭头具有一定宽度。

如果该线与x或y轴对齐,则这将是简单的。为了处理任意角度的线条,我们可以构建一个坐标系,其轴平行于和垂直于原始线条。我们将称这些轴为u和v,其中u指向线的方向,v垂直于它。

现在,我们可以从P0开始,通过按比例缩放所需的箭头长度和宽度来沿着由u和v确定的方向移动到角落处。

代码如下:

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);
}

以下是使用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;
}

0

我知道这是一个老问题,但我一段时间前找到了一个简单的方法,所以想分享一下...

这仅适用于左、右、上和下,但非常快速和简单。 根据您的需要进行调整并添加行 :)

void DrawArrow(CDC* pDC, CPoint ArrowTip)
{
    CPoint      ptDest;
    LOGPEN      logPen;                             // to get the color

    pDC->GetCurrentPen()->GetLogPen(&logPen);
    pDC->SetPixel(ArrowTip, logPen.lopnColor);      //....x....

    ArrowTip -= CPoint(1, 1);                       // move up one line (or down to suit need)
    pDC->MoveTo(ArrowTip);
    ptDest = ArrowTip;
    ptDest += CPoint(3, 0);
    pDC->LineTo(ptDest);                            //...xxx... 

    ArrowTip -= CPoint(1, 1);
    pDC->MoveTo(ArrowTip);
    ptDest = ArrowTip;
    ptDest += CPoint(5, 0);
    pDC->LineTo(ptDest);                            //..xxxxx.. 

    ArrowTip -= CPoint(1, 1);
    pDC->MoveTo(ArrowTip);
    ptDest = ArrowTip;
    ptDest += CPoint(7, 0);
    pDC->LineTo(ptDest);                            //.xxxxxxx. 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接