文号
t72660
阅读
15797
回复
2
收藏
2
VC提高GDI绘图速率的方法
2015/07/18
IP:四川
GDI绘图效率不高的主要解决方法:

1.不要想着用GDI或GDI+在绘图期间(比如OnPaint)做一些耗时动作,如高斯模糊等,这样会导致界面严重闪烁!

高斯模糊等耗时操作应该进行预处理,并在绘图时简单地转化为位图映射这种迅速高效的绘图操作,而不是在每次绘图时都处理一遍。
// 错误的做法
void OnPaint()
{
     using namespace Gdiplus;
     CPaintDC dc(this);
     Graphics graphics(dc);
     Image myImage(L"img1.jpg");
               
     REAL srcWidth = (REAL)myImage.GetWidth();
     REAL srcHeight = (REAL)myImage.GetHeight();
     RectF srcRect(0.0f, 0.0f, srcWidth, srcHeight);
     Matrix myMatrix(1.0f, 0.0f, 0.0f, 1.0f, 200.0f, 20.0f);
               
     BlurParams myBlurParams;
     myBlurParams.expandEdge = TRUE;
     myBlurParams.radius = 3;
               
     Blur myBlur;
     myBlur.SetParameters(&myBlurParams);
               
     // Draw the image without the blur effect.
     graphics.DrawImage(&myImage, 20.0, 20.0, srcWidth, srcHeight);
               
     // Draw the image with the blur effect.
     graphics.DrawImage(&myImage, &srcRect, &myMatrix, &myBlur, NULL, UnitPixel);
}
                       
// 正确的做法
void OnPaint()
{
    using namespace Gdiplus;
    CPaintDC dc(this);
    Graphics graphics(dc);
    Image myImage(L"img1.jpg");
                       
    // 画出没有模糊效果的图片
    graphics.DrawImage(&myImage, 20, 20);
                       
    static Bitmap *g_bmp = NULL;
    if (g_bmp == NULL) { // 如果图片还没有被模糊处理过,那么就先处理它,否则直接显示
                        
        REAL srcWidth = (REAL)myImage.GetWidth();
        REAL srcHeight = (REAL)myImage.GetHeight();
                
        BlurParams myBlurParams;
        myBlurParams.expandEdge = TRUE;
        myBlurParams.radius = 3;
                       
        Blur myBlur;
        myBlur.SetParameters(&myBlurParams);
                       
        g_bmp = ::new Bitmap(srcWidth, srcHeight, &graphics);
        Graphics graphics2(g_bmp);
                     
        // 在内存位图上画出高斯模糊过的图片
        graphics2.DrawImage(&myImage, NULL, NULL, &myBlur, NULL, UnitPixel);
    }
                       
    // 画出已经处理过的图片
    graphics.DrawImage(g_bmp, 500, 20);
}

2.如果界面仍有闪烁,那么最好把OnEraseBkgnd重写成空函数,改在OnPaint中填充背景颜色,因为消息之间可能有延时。
BOOL CChildView::OnEraseBkgnd(CDC* pDC)
{
    // 抗闪烁 - 不自动清空背景,务必在OnPaint中清空背景,否则易出现显示不正确!
    return TRUE;
    // 默认 - 自动清空背景,但是在某些情况下可能会闪烁
    //return CWnd::OnEraseBkgnd(pDC);
}

3.在OnPaint代码中加入这个CFastDC类,实现缓冲绘图,并自动清空背景:
struct CFastDC : public CDC
{
    CDC *m_phydc;
    CBitmap m_bitmap;
    CRect m_rc;
                                                      
    CFastDC() {
        m_phydc = NULL;
    }
                                                      
    CFastDC(CDC *phyDC, CRect rc, COLORREF cr = RGB(255, 255, 255)) {
        m_phydc = NULL;
        BeginFastDC(phyDC, rc, cr);
    }
                                                      
    ~CFastDC() {
        if (m_phydc != NULL)
            EndFastDC();
    }
                                                      
    CDC *BeginFastDC(CDC *phyDC, CRect rc, COLORREF cr = RGB(255, 255, 255))
    {
        ASSERT(m_phydc == NULL);
        m_phydc = phyDC;
        m_rc = rc;
        CreateCompatibleDC(phyDC); // 新建兼容DC
        m_bitmap.CreateCompatibleBitmap(phyDC, m_rc.right-m_rc.left, m_rc.bottom-m_rc.top);
        SelectObject(m_bitmap); // 新建承载图像的兼容位图并附着在兼容DC上
        FillColor(cr);
        return this;
    }
                                                      
    void FillColor(COLORREF cr)
    {
        CRect rc2;
        rc2.top = rc2.left = 0;
        rc2.right = m_rc.right - m_rc.left;
        rc2.bottom = m_rc.bottom - m_rc.top;
        FillSolidRect(rc2, cr); // 填充背景
    }
                                                      
    BOOL EndFastDC()
    {
        ASSERT(m_phydc != NULL);
        return m_phydc->BitBlt(m_rc.left, m_rc.top,
            m_rc.right-m_rc.left, m_rc.bottom-m_rc.top,
            this, 0, 0, SRCCOPY);
    }
};

在OnPaint中需要绘图时,首先使用GetClientRect(rc)获得窗口客户区大小,然后用CFastDC包装住实体DC(如CClientDC、CPaintDC等),即可实现缓冲绘图。
void OnPaint() {
    CRect rc;
    GetClientRect(rc); // 获取窗口大小
    CPaintDC dc(this); // 获取绘图DC
    CFastDC memdc(&dc, rc, GetSysColor(COLOR_3DFACE)); // 新建一个指定背景色和大小的内存DC
                                                  
    // TODO: 使用memdc而不是dc作图
    // ...
                                                  
    // 当CFastDC析构时,它会自动尝试将自己的图像映射到物理DC上
}

CFastDC会建立一对兼容DC/Bitmap来承载绘图结果,并在析构时将图像映射到实体DC上,经过测试,使用这种方法很大程度上可以提高绘图速率。
[ 修改于11年5个月前 - 2015/07/18 19:34:16 ]
acmilan作者
11年5个月前IP:四川
779697
1楼
无论是GDI、GDI+还是DirectDraw还是Direct2D,如果不注意细节的话,都非常容易闪屏。
引用
评论
笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的