【问题标题】:Drawing (too slow) in WM_PAINT causes flicker?在 WM_PAINT 中绘图(太慢)会导致闪烁?
【发布时间】:2012-06-20 01:21:53
【问题描述】:

我想用下面的代码在 WM_PAINT 消息处理程序中画很多线。

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

但是,如果点的大小超过 20,则客户端 rect 会闪烁。我认为原因是 Gdiplus::DrawLines 太慢了。

有什么方法可以解决闪烁问题吗? 谢谢。

【问题讨论】:

  • 它不应该与最终BitBlt导致的绘制线条的“速度”有关,除非hdcMem直接绑定到hdc。也许还有其他事情发生? (例如 BitBlt 中的“眼泪”)

标签: winapi gdi+ gdi


【解决方案1】:

使用双缓冲。这是 Win32 C++ 应用程序,特别是 OnPaint 函数和 DC 工具的问题。

这里有几个链接可以帮助您检查您的双缓冲实现是否一切正常:Flicker Free Drawing In MFC 和 SO question "Reduce flicker with GDI+ and C++"

【讨论】:

  • 但这在最后使用了 BitBlt……这不是有效的双缓冲吗? (也就是说,这个答案需要更多的扩展和/或支持。)
  • 是的,我明白了。目前我无法给出更详细的答案。但几年前,我写了一个应用程序(C++、MFC、GDI+),它可以很好地处理大约 10000 点或更多点的线图(以防用户选择更大的时间段)
【解决方案2】:

问题是我没有自己处理 WM_ERASEBKGND 消息。

【讨论】:

    【解决方案3】:

    闪烁可能是由于绘制缓慢以及其他原因造成的。一般来说,尝试/确保以下几点:

    • 尽量不要依赖WM_ERASEBKGND消息,即返回非零值,或者如果可能的话,在WNDCLASS::hbrBackground中指定NULL。往往paint方法会绘制脏区的所有背景,那么就不需要进行擦除了。

    • 如果您需要擦除,通常可以对其进行优化,使WM_ERASEBKGND 返回非零值,然后paint 方法通过还绘制常规绘制内容未覆盖的区域来确保“擦除”,如果PAINTSTRUCT::fErase 已设置。

    • 如果可能,请编写paint 方法,使其不会在一次调用中重新绘制相同的像素。例如。用红色边框制作蓝色矩形,不要FillRect(red),然后用FillRect(blue)重新绘制它的内部。尝试尽可能多地为每个像素绘制一次。

    • 对于复杂的控件/窗口,通常可以优化绘制方法,通过适当组织控件数据轻松跳过脏矩形 (PAINTSTRUCT::rcPaint) 之外的大量绘制。

    • 更改控件状态时,仅使控件所需的最小区域无效。

    • 如果不是顶级窗口,考虑使用CS_PARENTDC。如果您的绘制方法不依赖于系统将剪切矩形设置为控件的客户端矩形,则此类样式将带来更好的性能。

    • 如果您在控件/窗口大小调整时看到闪烁,请考虑不使用 CS_HREDRAWCS_VREDRAW。而是手动使WM_SIZE 中控件的相关部分无效。这通常只允许使控件的较小部分无效。

    • 如果您看到控件滚动时闪烁,请不要使整个客户端无效,而是使用ScrollWindow() 并仅使暴露新(滚动)内容的小区域无效。

    • 如果以上都失败了,那么使用双缓冲。

    【讨论】:

      【解决方案4】:

      如果您的线条碰巧超出了 DC(图形)的边界,Win32/GDI+ 的剪切速度会非常缓慢。比如,比滚动你自己的裁剪函数慢两个数量级。这是一些实现 Liang/Barsky 的 C# 代码——我从一个 20 年前最初使用 C++ 的旧库中搜罗到了它。应该很容易移植回来。

      如果您的线条可以延伸到客户矩形之外,请在您的点上调用 ClipLine(rect, ...),然后再将它们交给 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;
      }
      

      【讨论】:

        猜你喜欢
        • 2013-12-30
        • 1970-01-01
        • 1970-01-01
        • 2015-05-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-05
        • 1970-01-01
        相关资源
        最近更新 更多