C++ Draw a line in GDI with a filled arrow head at the end
Could someone please help me with some ideas (preferably with code) on how to draw a line with a filled arrowhead at the end?
The arrow head must be correctly oriented with the direction of the line. I wan开发者_开发知识库t to do this in C++.
It takes a little geometry to figure out the corners of the triangle that form the arrowhead.
Suppose the line goes from P0 to P1, and that we want the tip of the arrow at P1. To find the "back corners" of the arrowhead, we want to move from the tip back along the line and then turn left and right to give the arrow some width.
This would be simple if the line were aligned with the x or y axis. To handle lines at any angle, we can construct a coordinate system whose axes are parallel to and perpendicular to the original line. We'll call these axes u and v, with u pointing in the direction of the line and v perpendicular to it.
Now we can start at P0, and move to the corners by stepping in the directions determined by u and v, scaled for whatever arrowhead length and width 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;
}
I know this is an old Q, but I found a simple way to do this awhile ago, so thought I would share...
This only works for left, right, up and down, but is very quick and simple. Adjust to your need and add in the line :)
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.
}
精彩评论