• 关于双缓冲绘图之二


    小结:主要的思想是,如果要显示图形,直接把该图形当作一个document,在改写document的时候,实际上就是改写这个位图。然后在View中显示的时候,直接把这个位图显示出来就可以了。这需要两个DC,一个DC是台面上的显示用的,一个DC是在内存中绘图用的。两者的关联点就在于这个bitmap对象。
    需要明确几个概念,由于MFC的类封装了windows对象,所以由MFC类创建出来的对象就叫做“实例”,以与windows“对象”区分开来。

    document/view的经典办法是将图形的数据存储在document类里面,view类只是根据这些数据绘图。比如你要画个圆,只是将圆心和半径存在document里面,view类根据这个里面的数据在屏幕上面重新绘制。那么,我们只需要随机产生一次数据就可以了。

    这样还是存在性能的问题,于是我们开始考虑另外的解决方法。我们知道,将内存中的图片原样输出到屏幕是很快的,这也是我们在dos时代经常做的事情,能不能在windows也重新利用呢?答案就是内存缓冲绘图,我们今天的主题。

    我们还是回到DC上来,既然DC是绘图对象,我们也就可以自己来在内存里面造一个,让它等于我们想要的图,图(CBitmap)可以存储在document 类里面,每一次刷新屏幕都只是将这个图输出到屏幕上面,每一次作图都是在内存里面绘制,保存在document的图里面,必要时还可以将图输出到外存保存。这样既保证了速度,也解决了随机的问题,在复杂作图的情况下对内存的开销也不大(总是一副图片的大小)。这是一个很好的解决办法,现在让我们来实现它们。

    我们在document类里面保存一个图片

    CBitmap m_bmpBuf;//
    这里面保存了我们做的图,存在于内存中

    view类里面我们需要将这个图拷贝到屏幕上去
    位于OnDraw(CDC *pDC)函数中:

     下面这一部分就是CView负责的工作,仅仅需要的是显示该bitmap

    CDC dcMem;//以下是输出位图的标准操作
    CBitmap *pOldBitmap = NULL;
    dcMem.CreateCompatibleDC(NULL);
    pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);
    BITMAP bmpinfo;
    pDoc->m_bmpBuf.GetBitmap(&bmpinfo);
    pDC->BitBlt(0,0,bmpinfo.bmWidth,bmpinfo.bmHeight,&dcMem,0,0,SRCCOPY);
    dcMem.SelectObject(pOldBitmap);
    dcMem.DeleteDC();

    在我们需要画图的函数里面,我们完成绘图工作
    下面就是设置Doc
    CBmpDrawDoc *pDoc = GetDocument(); //
    得到document中的bitmap对象
    CDC *pDC = GetDC();//
    这个GetDC将得到当前客户区的DC,也就是说,现在windowsDC对象已经创建了,并且由pDC同一负责
    CDC dcMem;//
    前面已经讲过,用CDC创建DC实例,不会创建任何的windows DC对象。所以这个dcMem现在还没有任何的windows对象句柄
    dcMem.CreateCompatibleDC(NULL);//
    随后建立与屏幕显示兼容的内存显示设备。这个时候dcMem就有了windows对象的句柄了
    注意此时m_bmpBuf还没有跟任何windows对象相关联。
    pDoc->m_bmpBuf.DeleteObject();
    pDoc->m_bmpBuf.CreateCompatibleBitmap(pDC,100,100);//依附DC创建bitmap
    执行了上面这句话之后,bitmap实例就依附于一个windowsbitmap对象了。那么为什么要用pDC作为参数,而不用&dcMem呢?下面来看看MSDN关于该函数的解释:

     CBitmap::CreateCompatibleBitmap
    BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );
    Return Value
    Nonzero if successful; otherwise 0.
    Parameters
    pDC
    Specifies the device context.
    nWidth
    Specifies the width (in pixels) of the bitmap.
    nHeight
    Specifies the height (in pixels) of the bitmap.
    Remarks
    Initializes a bitmap that is compatible with the device specified by pDC. The bitmap has the same number of color planes or the same bits-per-pixel format as the specified device context. It can be selected as the current bitmap for any memory device that is compatible with the one specified by pDC.
    If pDC is a memory device context, the bitmap returned has the same format as the currently selected bitmap in that device context. A "memory device context" is a block of memory that represents a display surface. It can be used to prepare images in memory before copying them to the actual display surface of the compatible device.
    When a memory device context is created, GDI automatically selects a monochrome stock bitmap for it.
    Since a color memory device context can have either color or monochrome bitmaps selected, the format of the bitmap returned by the CreateCompatibleBitmap function is not always the same; however, the format of a compatible bitmap for a nonmemory device context is always in the format of the device.
    When you finish with the CBitmap object created with the CreateCompatibleBitmap function, first select the bitmap out of the device context, then delete the CBitmap object.

    也就是说,我们现在需要的是把我们的bitmap显示在屏幕上,因此需要和现在的客户区的DC(也就是Pdc来负责的)关联起来,这样bitmap的一些色彩属性才会和pDC的一样。但是内存DC的色彩没有那么多,这样的话,绘制这个bitmap实际上只有几种颜色了。可以作个实验,如果这里改成&menDC,那么绘出来的方块就是黑色的,而用pDC就是红色的,也是我们设想的。
    CBitmap *pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);//
    调入了我们bitmap对象

    dcMem.FillSolidRect(0,0,100,100,RGB(255,255,255));//
    这些时绘图操作,随便你^_^
    dcMem.TextOut(0,0,"Hello,world!");
    dcMem.Rectangle(20,20,40,40);
    dcMem.FillSolidRect(40,40,50,50,RGB(255,0,0));

    pDC->BitBlt(0,0,100,100,&dcMem,0,0,SRCCOPY);//
    第一次拷贝到屏幕
    dcMem.SelectObject(pOldBitmap);
    dcMem.DeleteDC();


    全部的过程就是这样,很简单吧。以此为例子还可以实现2个缓冲或者多个缓冲等等,视具体情况而定。当然在缓冲区还可以实现很多高级的图形操作,比如透明,合成等等,取决于具体的算法,需要对内存直接操作(其实就是当年dos怎么做,现在还怎么做)。

    再来解释一下前面说的为什么不能用全局变量保存DC问题。其实DC也是用句柄来标识的,所以也具有句柄的不确定性,就是只能随用随取,不同时间两次取得的是不同的(使用过文件句柄地话,应该很容易理解的)。那么我们用全局变量保存的DC就没什么意义了,下次使用只是什么也画不出来。(这一点的理解可以这样: DC需要占用一定的内存,那么在频繁的页面调度中,位置难免改变,于是用来标志指针的句柄也就不同了)。
     
  • 相关阅读:
    OpenGL入门1.3:着色器 GLSL
    OpenGL入门1.2:渲染管线简介,画三角形
    C++回调,函数指针
    JavaScript 比较和逻辑运算符
    JS 运算符
    JS 函数
    JS 对象
    JS 数据类型
    JS 变量
    JS 注释
  • 原文地址:https://www.cnblogs.com/rainbowzc/p/2422171.html
Copyright © 2020-2023  润新知