• GDI双缓冲


    GDI双缓冲

    翻译自Double buffering,原作者Dim_Yimma_H

    语言:C

    (原文写的是C++,实际上是纯C)

    推荐知识:

    • 构建程序
    • 函数
    • 结构体
    • 变量和条件语句
    • switch语句
    • 循环
    • 指针
    • 创建窗口

    教程

    为了构建这个应用,你需要链接这两个库:User32.lib, Gdi32.lib。

    你也需要把开发环境中的字符集设定为多字节,因为如果字符集是Unicode的话,MessageBox和CreateWindow都会有不同的定义。如果你在用Visual C++ 2005,你可以这样做:打开菜单"Project",点击"Properties",点开"Configuration Properties",选择"General",看到"Character Set"右边,把它从"Use Unicode Character Set"改成"Use Multi-Byte Charater Set",点击"OK"。

    双缓冲的优势是消除闪烁(flickering)。当持续的直接绘制东西到一个窗口上,并且在绘制完成前就更新窗口,导致当前一次更新后有一部分画面不可见,就产生了闪烁。在下一次更新期间,另一部分又会不可见。为了得到一个更稳定的更新,我们把需要绘制的全部内容都绘制到一个备用缓冲(backbuffer)上,然后backbuffer会被绘制到窗口上。闪烁问题于是被解决:因为不再有部分画面不可见的问题了。同时也带来了另一个问题:水平撕裂(horizontal tearing)。当窗口的一半是现在更新的,而另一半是上次更新的,就出现了horizontal tearing。不过这通常来说是可以接受的。

    本教程包括基本的Win32图形编程(Win32 graphics programming)以及如何使用backbuffer。我会分段解释相关代码,文末则给出完整代码。其中一部分是Win32 API,它的目的是处理图形设备(graphics devices) - GDI (graphics device interface)。每个窗口有一个设备上下文(device context),它描述了窗口属性(例如像素格式)。在调用绘制函数时,device context也被用作窗口和位图的标识符。因此,如果我们想在窗口中绘制像素、形状或者位图,我们需要先找到它的device context。通过调用GetDC函数,它返回的是一个HDC(handle to a device context),可以得到这个窗口的device context。GetDC函数需要的参数:一个HWND,也就是我们想要获取context device的那个窗口的HWND。在我们的函原型部分,我们会声明一个函数,后面会用到它,现在则不用想太多。我们同时定义两个宏,表示窗口的宽度和高度,后面用起来会很方便。

    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480
    
    // function prototype - PaintRectangle
    int PaintRectangle(SHORT left,
        SHORT top,
        USHORT width,
        USHORT height,
        COLORREF color,
        HDC hDstDC);
    

    我们的backbuffer是一个位图。CreateCompatibleBitmap()函数返回一个HBITMAP,因此通过调用CreateCompatibleBitmap()来获取位图。CreateCompatibleBitmap 函数有三个参数:第一个是HDC类型,描述了我们想要bitmap兼容到的窗口。另外两个参数是位图的宽度和高度。我们现在声明这些变量,以及一个rectangle,这个rectangle后续会被backbuffer绘制出来。

    // GDI related variables
    HWND hWnd; // handle to the main window
    HBITMAP hBackbuffer; // handle to the backbuffer bitmap
    HDC hWndDC; // handle to the device context of the main window
    HDC hBackbufferDC; // handle to the device context of the backbuffer
    
    // rectangle variables
    int left = 0, top = 0; // upper left corner
    USHORT width=32, height=24; // size of the rectangle
    COLORREF color=RGB(255, 255, 255); // create a 32-bit color using the RGB macro
    

    我们将对主窗口类的style成员做一点小的调整:要求这个窗口指定唯一的device context。

    wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    

    当窗口被创建后,一个好的做法是获取它的device context,并创建一个backbuffer和相应的device context。这个backbuffer位图并不会自动有相应的device context,而是要通过CreateCompatibleDC来创建。然后通过调用SelectObject()来描述这个backbuffer。SelectObject()函数的第一个参数是这个HDC,第二个参数是一个GDI对象,例如HBITMAP。

    /*
    Retrieve the window device context,
    and create a device compatible with it.
    Then select it to describe hBackbuffer.
    */
    hWndDC = GetDC(hWnd);
    hBackbuffer = CreateCompatibleBitmap(hWndDC, WINDOW_WIDTH, WINDOW_HEIGHT);
    hBackbufferDC = CreateCompatibleDC(hWndDC);
    SelectObject(hBackbufferDC, hBackbuffer);
    

    为了真的用上double buffering,我们的程序将会是一个实时应用(real time application)。因而,需要修改我们的消息循环相应代码:GetMessage()会休眠,直到获取到一个message;我们换用PeekMessage(),它如果没有得到一个message,就立即返回。注意到也有可能一连串message被一起发送过来,因此Windows有过一个消息队列(message queue)。我们在调用PeekMessage()时指定最后一个参数为PM_REMOVE,作用是删除那些已经被接收到的消息,而不是处理它们两次。

    // message loop
    while (true)
    {
        // peek messages for all windows belonging to this thread
        // PM_REMOVE specifies that
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message === WM_QUIT) // the window has been destroyed
                break;
    
            DispatchMessage(&msg); // send the message to the window procedure
        }
        else
        {
            // idle process - do the drawing here
    

    消息循环的这一部分被叫做idle process(空闲进程),因为当前没有消息被接收到。我们知道,窗口最上面的像素数量最少,越往下越多。第一个if条件语句判断的是,我们创建的矩形(前面有提到过一次)是否为挨着窗口右边,如果是的话就随机生成一个位置、尺寸和颜色;而如果矩形还在窗口内,则通过不断的增加“left”变量的值来让矩形向右滑动。

            if (left >= WINDOW_WIDTH)
            {
                // randomize the color of the rectangle
                color = RGB(rand() % 255, rand() % 255, rand() % 255);
    
                // randomize the width and height of the rectangle
                width = rand() % WINDOW_WIDTH
                height = rand() % WINDOW_HEIGHT
    
                // randomize the positioon of the rectangle
                left = - width;
                top = rand() % (WINDOW_HEIGHT - height);
            }
            else
            {
                left += 2; // move rectangle to the right
            }
    
            // clear the backbuffer by painting a blank (0) rectangle over it
            PaintRectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, hBackbufferDC);
            // paint the moving rectangle onto the backbuffer
            PaintRectangle(left, top, width, height, color, hBackbufferDC);
    

    GDI提供了一个叫做BitBlt的函数,它做的是bit-block transfer(拷贝一个bitmap到另一个)。我们要做的是拷贝backbuffer到窗口,BitBlt函数对此来说是完美的。BitBlt的函数参数如下:目标device context,left, top, width, height, 源device context, 源left, 源top, 光栅操作标识符(raster operation flag)。我们把光栅操作标识符设定为SRCCOPY来表示说我们只想把源BITMAPk(backbuffer)拷贝到窗口。此外还有其他的光栅操作标识符类型,都是使用位操作来结合位图的像素,从而创建不同的效果。

            // copy the whole backbuffer to the main window
            BitBlt(hWndDC, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
                    hBackbufferDC, 0, 0,
                    SRCCOPY);
    
            // drawing is complete, pause the program for 30 ms by calling Sleep
            // thie limits the speed and lowers the CPU usage
            Sleep(30);
        }
        // end of message loop
    }
    

    我们创建的对象,一旦用完就应该删掉,因此当结束程序之前我们删掉backbuffer和相应的device context,因为它们是我们手动创建的。窗口的device context则应该被释放而不是删除,因为它是被找到的(retrieved)而不是被创建的(created)。

        // this should be done before exiting the program:
        ReleaseDC(hWnd, hWndDC); // retrieved device contexts are just released
        DeleteDC(hBackbufferDC); // created device contexts must be deleted
        DeleteObject(hBackbuffer); // created objects must be deleted
    

    最后,我们过一遍PaintRectangle函数。之所以现在才看这个函数,是因为觉得对应该对device context有一个整体的印象。PaintRectangle函数有两个位置参数:left和top。如果它们俩都等于0,那么会在左上角绘制。width和height则表示了矩形框的尺寸,颜色参数表示整个框的颜色;hDstDC参数则是目标device context的句柄。因此如果它是backbuffer的device context的话,那么就绘制矩形框到backbuffer上。使用了两种新的数据类型:一个是RECT,是矩形的left、top、right、bottom的简单封装的结构体;另一个是HBRUSH,它是brash的句柄。brush意思是刷子,是按指定的颜色来填充指定区域的。HBRUSH是一个GDI对象,我们通过CreateSolidBrush来创建,而当用完的时候,通过DeleleObject来释放它。为了绘制真正的矩形,需要调用FillRect函数。FillRect函数的第一个参数是目标device context,第二个参数则是RECT的地址,RECT表示了需要绘制的区域;最后一个参数则是brush句柄。FillRect返回了一个int变量,随后被PaintRectangle返回,如果成功的话它的值非0。

    int PaintRectangle(SHORT left, SHORT top, USHORT width, USHORT height, COLORREF color, HDC hDstDC)
    {
        if (hDstDC == NULL)
        {
            return -1; // no device context specified - return
        }
        int returnValue;
    
        // RECT struct instance to paint
        RECT rect = {left, top, left+width, top+height};
        HBRUSH hBrush;
    
        hBrush = CreateSolidBrush(color);
    
        returnValue = FillRect(hDstDC, &rect, hBrush);
    
        DeleteObject(hBrush);
    
        return returnValue;
    }
    

    搞定!完整代码如下

    (简单起见,我把入口函数从WINMAIN改成main,因此直接创建VS的控制台程序即可)

    //from http://www.geocities.ws/dim_yimma_h/cdoublebuffering.htm
    
    #include <stdio.h>
    #include <stdbool.h>
    #include <windows.h>
    
    #define WINDOW_WIDTH 640
    #define WINDOW_HEIGHT 480
    
    //function prototype - PaintRectangle
    int PaintRectangle(SHORT left,
    	SHORT top,
    	USHORT width,
    	USHORT height,
    	COLORREF color,
    	HDC hDstDC);
    
    //declare window procedure
    LRESULT WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
    
    int main()
    {
    	HINSTANCE hInstance = GetModuleHandle(0);
    
    	MSG msg;	//a message struct instance
    	WNDCLASS wc;	//a window class instance
    
    	//GDI related variables
    	HWND hWnd;	//handle to the main window
    	HBITMAP hBackbuffer;	//handle to the backbuffer bitmap
    	HDC hWndDC;	//handle to the device context of the main window
    	HDC hBackbufferDC;	//handle to the device context of the backbuffer
    
    	//rectangle variables
    	int left = 0, top = 0;	//upper left corner
    	USHORT width = 32, height = 24;	//size of the rectangle
    	COLORREF color = RGB(255, 255, 255);	//create a 32-bit color using the RGB macro
    
    	//set the member variables of the main window class instance
    	wc.lpszClassName = "MainWindowClass";	//string identifier for this class instance
    	wc.lpfnWndProc = MainWndProc;	//the name (address) of the window procedure
    
    	/*	CS_HREDRAW and CS_VREDRAW specifies the window should be redrawn,
    		both when resized horizontally and vertically.
    		hInstance specifies program instance a created window belongs to.	*/
    	wc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
    	wc.hInstance = hInstance;
    
    	/*	Load the icon displayed in the corner of the window,
    		the first argument specifies the HINSTANCE of the icon resource,
    		since IDI_WINLOGO is a standard icon ID no HINSTANCE is specified.
    		Also load the mouse cursor displayed when hovering over the window.	*/
    	wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    
    	//brushes are used to specify fill color (in this case the background)
    	//the the color of a window is explicitly converted into a HBRUSH
    	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    	wc.lpszMenuName = NULL;	//name of window menu, we won't be creating one so it's NULL
    	//the following two members specifies extra bytes to allocate for the window instance
    	wc.cbClsExtra = 0;
    	wc.cbWndExtra = 0;
    
    	if (RegisterClass(&wc) == 0)
    	{
    		//we have no window yet thus the HWND for the messagebox is set to NULL
    		//MB_ICONERROR specifies an error icon should be displayed in the messagebox
    		MessageBox(NULL, "RegisterClass failed.", "Double buffering", MB_ICONERROR);
    		return 0;	//no window has been created so return without destroying it
    	}
    
    	/*	create the window using the registered class,
    		the first message for this window is sent now,
    		it was the WM_CREATE message (see MainWndProc)	*/
    	hWnd = CreateWindow("MainWindowClass",	//our window class identifier
    		"Double buffering",	//caption
    		WS_OVERLAPPEDWINDOW | WS_VISIBLE,
    		0,		//left edge position
    		0,		//top edge position
    		WINDOW_WIDTH,	//width of the window
    		WINDOW_HEIGHT,	//height of the window
    		NULL,	//parent HWND is NULL since this is the only window
    		NULL,	//handle to the menu of the window
    		hInstance,	//specifies which program instance window shall belong to
    		NULL);
    	//the last parameter can be a pointer to be sent with the WM_CREATE message
    
    //test whether the window failed to be created - in that case return 0
    	if (hWnd == NULL)	//if no handle was recieved
    	{
    		MessageBox(NULL, "CreateWindow failed.", "Double buffering", MB_ICONERROR);
    		return 0;
    	}
    
    
    	/*	Retrieve the window device context,
    		and create a device context compatible with it.
    		Then select it to describe hBackbuffer.	*/
    	hWndDC = GetDC(hWnd);
    	hBackbuffer = CreateCompatibleBitmap(hWndDC, WINDOW_WIDTH, WINDOW_HEIGHT);
    	hBackbufferDC = CreateCompatibleDC(hWndDC);
    	SelectObject(hBackbufferDC, hBackbuffer);
    
    	//message loop
    	while (true)
    	{
    		//peek messages for all windows belonging to this thread
    		//PM_REMOVE specifies that
    		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    		{
    			if (msg.message == WM_QUIT)	//the window has been destroyed
    				break;
    
    			DispatchMessage(&msg); //send the message to the window procedure
    		}
    		else
    		{
    			//idle process - do the drawing here
    			if (left >= WINDOW_WIDTH)
    			{
    				//randomize the color of the rectangle
    				color = RGB(rand() % 255, rand() % 255, rand() % 255);
    
    				//randomize the width and height of the rectangle
    				width = rand() % WINDOW_WIDTH;
    				height = rand() % WINDOW_HEIGHT;
    
    				//randomize the position of the rectangle
    				left = -width;
    				top = rand() % (WINDOW_HEIGHT - height);
    			}
    			else
    			{
    				left += 2;	//move the rectangle to the right
    			}
    
    			//clear the backbuffer by painting a black (0) rectangle over it
    			PaintRectangle(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, hBackbufferDC);
    			//paint the moving rectangle onto the backbuffer
    			PaintRectangle(left, top, width, height, color, hBackbufferDC);
    			//copy the whole backbuffer to the main window
    			BitBlt(hWndDC, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT,
    				hBackbufferDC, 0, 0,
    				SRCCOPY);
    
    			//drawing is complete, pause the program for 30 ms by calling Sleep
    			//this limits the speed and lowers the CPU usage
    			Sleep(20);
    		}
    	}	//end of message loop
    
    	//this should be done before exiting the program:
    	ReleaseDC(hWnd, hWndDC); //retrieved device contexts are just released
    	DeleteDC(hBackbufferDC); //created device contexts must be deleted
    	DeleteObject(hBackbuffer); //created objects must be deleted
    
    	//return the last handled message to the caller (exit the program)
    	return msg.wParam;
    }	//end of WinMain
    
    LRESULT WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
    	//switch statement to determine the current message
    	switch (uMsg)
    	{
    	case WM_CLOSE:
    		DestroyWindow(hWnd);	//destroys this window and send WM_DESTROY message
    		return 0;
    
    	case WM_DESTROY:
    		PostQuitMessage(0);	//send WM_QUIT message with the return code 0
    		return 0;
    	}
    
    	//send any unhandled messages to the default window procedure by calling DefWindowProc
    	return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    
    int PaintRectangle(SHORT left, SHORT top, USHORT width, USHORT height, COLORREF color, HDC hDstDC)
    {
    	if (hDstDC == NULL)
    		return -1;	//no device context specified - return
    
    	int returnValue;
    
    	//RECT struct instance to paint
    	RECT rect = { left, top, left + width, top + height };
    	HBRUSH hBrush;
    
    	hBrush = CreateSolidBrush(color);
    
    	returnValue = FillRect(hDstDC, &rect, hBrush);
    
    	DeleteObject(hBrush);
    
    	return returnValue;
    }
    
  • 相关阅读:
    RSA算法
    Windows-caffe配置
    python 下 excel,csv 文件的读写
    python 网络通讯 服务器端代码demo,能够同时处理多个客户端的连接请求
    python 下串口数据的读取,解析,和保存-
    XML字符串和JAVA对象之间的转化
    MySQL的join on和 where 的执行顺序和区别,以及各种连接说明
    全国各行政区行政编码
    计数器+打卡+习惯+目标APP推荐
    安卓计数器类APP推荐
  • 原文地址:https://www.cnblogs.com/zjutzz/p/10925397.html
Copyright © 2020-2023  润新知