Reputation: 41845
I want to draw a lot of lines in the WM_PAINT message handler with the following code.
//DrawLine with double buffering
LRESULT CALLBACK CMyDoc::OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
std::vector<Gdiplus::Point> points;
std::vector<Gdiplus::Point>::iterator iter1, iter2;
HDC hdc, hdcMem;
HBITMAP hbmScreen, hbmOldBitmap;
PAINTSTRUCT ps;
RECT rect;
hdc = BeginPaint(hWnd, &ps);
//Create memory dc
hdcMem = CreateCompatibleDC(hdc);
GetClientRect(hWnd, &rect);
hbmScreen = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
hbmOldBitmap = (HBITMAP)SelectObject(hdcMem, hbmScreen);
//Fill the rect with white
FillRect(hdcMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
//Draw the lines
Gdiplus::Graphics graphics(hdcMem);
Gdiplus::Pen blackPen(Gdiplus::Color(255, 0, 0));
points = m_pPolyLine->GetPoints();
for (iter1 = points.begin(); iter1 != points.end(); iter1++) {
for (iter2 = iter1 + 1; iter2 != points.end(); iter2++)
graphics.DrawLine(&blackPen, *iter1, *iter2);
}
//Copy the bitmap from memory dc to the real dc
BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);
//Clean up
SelectObject(hdcMem, hbmOldBitmap);
DeleteObject(hbmScreen);
DeleteDC(hdcMem);
EndPaint(hWnd, &ps);
return 0;
}
However, if the size of points exceed 20, the client rect just flicker. I think the reason is that Gdiplus::DrawLines is too slow.
Is there any method to solve the flicker problem? Thanks.
Upvotes: 1
Views: 5923
Reputation: 473
If your lines happen to extend outside the bounds of the DC (Graphics), Win32/GDI+ is painfully slow at clipping. Like, up to two orders of magnitude slower than rolling your own clipping function. Here is some C# code that implements Liang/Barsky - I scrounged this up from an old library that was originally in C++ 20 years ago. Should be easy enough to port back.
If your lines can extend beyond the client rectangle, call ClipLine(rect, ...) on your points before handing them off to Graphics::DrawLine.
private static bool clipTest(double dp, double dq, ref double du1, ref double du2)
{
double dr;
if (dp < 0.0)
{
dr = dq / dp;
if (dr > du2)
{
return false;
}
else if (dr > du1)
{
du1 = dr;
}
}
else
{
if (dp > 0.0)
{
dr = dq / dp;
if (dr < du1)
{
return false;
}
else if (dr < du2)
{
du2 = dr;
}
}
else
{
if (dq < 0.0)
{
return false;
}
}
}
return true;
}
public static bool ClipLine(Rectangle clipRect, ref int x1, ref int y1, ref int x2, ref int y2)
{
double dx1 = (double)x1;
double dx2 = (double)x2;
double dy1 = (double)y1;
double dy2 = (double)y2;
double du1 = 0;
double du2 = 1;
double deltaX = dx2 - dx1;
double deltaY;
if (clipTest(-deltaX, dx1 - clipRect.Left, ref du1, ref du2))
{
if (clipTest(deltaX, clipRect.Right - dx1, ref du1, ref du2))
{
deltaY = dy2 - dy1;
if (clipTest(-deltaY, dy1 - clipRect.Top, ref du1, ref du2))
{
if (clipTest(deltaY, clipRect.Bottom - dy1, ref du1, ref du2))
{
if (du2 < 1.0)
{
x2 = DoubleRoundToInt(dx1 + du2 * deltaX);
y2 = DoubleRoundToInt(dy1 + du2 * deltaY);
}
if (du1 > 0.0)
{
x1 = DoubleRoundToInt(dx1 + du1 * deltaX);
y1 = DoubleRoundToInt(dy1 + du1 * deltaY);
}
return x1 != x2 || y1 != y2;
}
}
}
}
return false;
}
Upvotes: 0
Reputation: 2349
The flickering may be caused by slow painting as well as by other things. In general, try/ensure the following:
Try to not rely on WM_ERASEBKGND
message, i.e. return non-zero, or specify NULL
in WNDCLASS::hbrBackground
if possible. Often the paint method paints all the background of the dirty region, then there is no need to do the erasing.
If you need the erasing, it can often be optimized so that WM_ERASEBKGND
returns non-zero, and the paint method then ensures the "erasing" by also painting areas not covered by the regular painted contents, if PAINTSTRUCT::fErase
is set.
If reasonably possible, write the paint method so that it does not repaint the same pixels in one call. E.g. to make blue rect with red border, do not FillRect(red)
, then repainting inner part of it with FillRect(blue)
. Try to paint each pixel once as much as reasonably possible.
For complex controls/windows, the paint method may often be optimized to easily skip a lot of painting outside the dirty rect (PAINTSTRUCT::rcPaint
) by proper organizing the control data.
When changing the control state, invalidate only the minimal required region of the control.
If it is not top-level window, consider using CS_PARENTDC
. If your paint method does not rely on system setting clipping rectangle to the client rect of the control, this class style will lead to a somewhat better performance.
If you see the flickering on control/window resizing, consider to not using CS_HREDRAW
and CS_VREDRAW
. Instead invalidate the relevant parts of the control in WM_SIZE
manually. This often allows to invalidate only smaller parts of the control.
If you see the flickering on control scrolling, do not invalidate whole client, but use ScrollWindow()
and invalidate only small area which exposes the new (scrolled-in) content.
If everything above fails, then use double buffering.
Upvotes: 6
Reputation: 5025
Use double buffer. It is an offen problem with Win32 C++ application and specifically with OnPaint function and DC facilities.
Here is a few links to help you check if everything is fine with YOUR implementation of double buffer: Flicker Free Drawing In MFC and SO question "Reduce flicker with GDI+ and C++"
Upvotes: 3