当前位置:C++技术网 > 精选软件 > 绘图技术的闪烁原因探究2:实验研究找绘图闪烁原因

绘图技术的闪烁原因探究2:实验研究找绘图闪烁原因

更新时间:2016-04-21 23:36:38浏览次数:1+次

    在文章《绘图技术的闪烁原因探究1》中做了一个开篇。主要是矫正一下心态,更多的是告诉这种学习方法。下面就开始来讲述一下研究过程。
    我们是不知道确切的闪烁的原因的,网上有些说法,但是我觉得并不完全正确,或者没有说到点子上去,也就有了我这一个专题。
    那么网上有人说,闪烁是因为有画面颜色反差,是有一定道理的。不过我想过了之后,觉得还缺少些东西。或者说,只是有反差,并不一定会闪烁。我想全面的研究一下,从而彻底了解闪烁的原因,这样就可以灵活的解决闪烁问题。
    绘图时的闪烁问题,是UI编程最基本而又最重要的问题。可能你搞清楚了觉得没有什么,然而没有搞清楚就不行了。
    我开始也不知道为什么,只是有一个模糊的概念而已。事实的真相,只有实验才能揭晓,光靠说靠想是不够的,况且计算机编程做实验也就写几句代码的问题,成本极低。
    从印象来看,闪烁好像在大量画面绘制、高频率绘制、卡顿等情况发生,有些人就会想,那么我只输出几个字,应该就不会闪烁了吧。毕竟几个字而已,绘画的量如此之小。然而,我们不能凭想象来猜测。
    我们就先简单的输出几个字观察一下。我在MFC的对话框程序中来验证。给确定按钮添加一个单击响应,响应函数如下:
void Ct1Dlg::OnBnClickedOk()
{
    SetTimer(1,10,0);
}
    我们这里就是简单的创建一个计时器,设置第二个参数即计时器执行的间隔时间为10ms。第一个参数为计时器ID,随便设置一个就可以。第三个参数填0或者NULL,表示我们不单独提供计时器处理函数,使用标准的计时器消息。然后我们给窗口添加一个计时器消息处理函数。这个可以使用向导轻松完成,在向导的消息栏中找到WM_TIMER,然后双击WM_TIMER即可添加响应消息。具体的过程我就不在这里说了,熟悉的一看就知道,不熟悉的就多熟悉一下。
    下面是计时器的代码:
void Ct1Dlg::OnTimer(UINT_PTR nIDEvent)
{
    static int i=0;
    CDC * pDC = GetDC();
    //设置字体和大小
    CFont font;
    font.CreatePointFont(300,_T("黑体"));
    pDC->SelectObject(font.m_hObject);
    CString str;
    str.Format(_T("C++技术网www.cjjjs.com:%d"),i++);
    pDC->TextOut(0,0,str);
    pDC->DeleteDC();
}

    计时器中就是简单的输出【C++技术网www.cjjjs.com:数字】,数字不断的增加。而这个计时器执行的时间间隔非常小,所以执行频率非常高。下面是执行的效果:

计时器快速执行输出文字

【计时器快速执行输出文字】
   虽然我们这里只简单的输出了一段文字,然而却很明显可以看到了闪烁。这里你可能会想到,高频率的显示会造成闪烁。你可以将计时器的时间设置长一些,然后你会发现,好像间隔变长后,闪烁消失了。但是你仔细观察,也会发现偶尔有那么一下闪烁。只是总体来看,似乎没有什么闪烁了。然而,随着间隔时间的变短,闪烁越来越明显。你可以自己动手改变。那么这里得出一个结论:闪烁的几率和刷新的时间间隔成反比,与刷新的频率成正比。刷新间隔越短,频率越高,闪烁的几率就越大。
    我这里用的词是几率,是概率的意思,也就不是百分百的会出现闪烁。所以这个现象不能说明什么问题。那么我们继续试验。
    下面我们多输出几行文字,计时器间隔时间可以调长一些。间隔变长后,输出一行文字的几乎不闪烁,那么多输出几行,会是什么情况呢?下面是计时器代码:
void Ct1Dlg::OnTimer(UINT_PTR nIDEvent)
{
    static int i=0;
    CDC * pDC = GetDC();
    //设置字体和大小
    CFont font;
    font.CreatePointFont(300,_T("黑体"));
    pDC->SelectObject(font.m_hObject);
    CString str;
    str.Format(_T("C++技术网www.cjjjs.com:%d"),i++);
    pDC->TextOut(0,0,str);
    pDC->TextOut(0,60,str);
    pDC->TextOut(0,120,str);
    pDC->TextOut(0,180,str);
    pDC->TextOut(0,240,str);
    pDC->TextOut(0,300,str);
    pDC->TextOut(0,360,str);
    pDC->DeleteDC();
}
    此时我将计时器间隔设置为100ms,单行文字输出几乎不怎么闪烁了。然而输出了7行之后,发现有了明显的闪烁。当刷新频率不变,输出的内容越多的时候,闪烁也就越明显。这里我们又可以得到一个结论:闪烁的几率和输出的内容成正比,内容越多,闪烁几率越大。
    到这里,很多人就下定论了,认为闪烁和刷新频率和输出内容有关。请你再用脚趾头想想,如果这样,我们还怎么防止闪烁,难道仅仅通过降低频率和减少输出内容吗?这不科学!
    当你到这里时,你可能也不知道下一步怎么试验了。我们在之前了解过,有一种说法就是有颜色反差。比如黑色和白色交替显示,也就出现了颜色反差,只要两次显示的内容的颜色不同,就会出现颜色反差。这种反差可以造成闪烁。这个说法是有道理的。如果两次绘制的颜色都是一样的,不管绘制多慢或者多快,人眼看到的颜色都是一样的,还怎么闪烁呢?比如不停的在同一个地方画白色,你始终看到的都是白色,不存在闪烁。
    既然如此,我们看看我们输出文字到底做了些什么。我们在输出文字的时候,先输出了文字的背景。我们可以看到文字后面是白色的一块。这个白色的地方就是文字的背景。也就是说,输出文字会先将输出文字的位置用背景颜色刷一遍,然后再输出文字。那么这个输出文字的过程就执行了两遍绘制过程,即画背景和画文字。而背景和文字颜色不一样,也就产生了颜色反差。
    那么我们可以将背景颜色去掉,不画背景,这样就不存在两次绘制的颜色反差了。我们来看看这样还会不会闪烁。禁止文字背景绘制的函数为SetBkMode,传入标志TRANSPARENT即可禁止背景绘制,这样文字就没有背景了,此时输出文字也就只会输出文字,没有交替两次绘制了。代码如下:
void Ct1Dlg::OnTimer(UINT_PTR nIDEvent)
{
    static int i=0;
    CDC * pDC = GetDC();
    //设置字体和大小
    CFont font;
    font.CreatePointFont(300,_T("黑体"));
    pDC->SelectObject(font.m_hObject);
    CString str;

    //修改其他属性
    pDC->SetBkMode(TRANSPARENT);
    str.Format(_T("C++技术网www.cjjjs.com:%d"),i++);
    pDC->TextOut(0,0,str);
    pDC->TextOut(0,60,str);
    pDC->TextOut(0,120,str);
    pDC->TextOut(0,180,str);
    pDC->TextOut(0,240,str);
    pDC->TextOut(0,300,str);
    pDC->TextOut(0,360,str);
    pDC->DeleteDC();
}

    通过试验发现,确实不闪烁了。效果图如下:

禁止文字背景绘制,不闪烁了

【禁止文字背景绘制,不闪烁了】
    不过从图中可以看到,数字部分的文字,成了一团黑色。不要惊慌。因为我们禁止文字背景即让文字背景透明了。所以就不会先用背景色刷掉前面一次输出的文字,然后这次又在同样的位置输出文字,这样多次输出的文字也就叠加了,这样叠加的越来越多,也就成了一团黑。不过你可以看到,确实不闪烁了。这个可以说明一个问题,绘画颜色反差是闪烁的必要条件。
    也就是说,多次连续绘制,如果多次绘制的内容在颜色上有反差,也就可能出现闪烁。如果多次绘制不存在颜色反差,则一定不会产生闪烁。
    到这里,似乎大家得到了满意的答案。然而真的如此吗?难道我们绘图就不用反差画面来避免闪烁吗?这个不现实哦。

    我们到这里,就知道了闪烁的一个必要条件,即多次连续绘制的颜色反差。我想这个是稍微有些研究的人知道的答案,很多人就此止步,以为就仅此而已了。然而,这个和双缓冲绘图有毛线关系?网上很多文章也就到此为止了。实际上,只了解到这个程度,有一定的进步,然而却模模糊糊的,实际上还没有搞懂。使用双缓冲技术也只是知道这个技术可以避免闪烁,然而为什么,实际上还是不知道。你要问如何避免闪烁,他会用双缓冲来处理。为什么呢,他不知道。

    限于篇幅,我将在下一篇文章详细讲解更多闪烁的深入原因细节。精彩不可错过哦。不过在看下一篇文章前,请消化本文的问题。