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;
}
GDI双缓冲的更多相关文章
- C# GDI+双缓冲技术
我想有很多搞图形方面的朋友都会用到双缓冲技术的时候,而且有的时候她的确是个头疼的问题.最近我也要用双缓冲技术,程序怎么调试都不合适,当要对图形进行移动时,总是会出现闪烁抖动.在网上找了些资料,说得都不 ...
- (转载)GDI+双缓冲
双缓冲在GDI+里可以有效的提高描画效率.改善显示的质量. 下面的代码是一个最简单的双缓冲的模板.可以根据需要,做简单的修改即可. Bitmap CacheImage( [Width], [Heigh ...
- 简单的GDI+双缓冲的分析与实现
为什么要使用双缓冲绘制 在进行多图元绘制的时候: 因为是要一个一个画上去,所以每画一个图元,系统就要做一次图形的绘制操作,图形的重绘是很占用资源的,特别当需要重绘的图形数量很多的时候,所造成的消耗就特 ...
- .net WINFORM的GDI双缓冲的实现
有时候在窗体中执行不断的GDI+操作的时候会出现闪速的状况,除了修改窗体的参数,更应该解决刷新本身的问题,双缓冲可能就是这样来的. 方法1: 用GDI绘制在位图上,然后再重新生成位图 Bitmap b ...
- GDI+ 双缓冲实现
早前曾为此问题在CSDN发帖求助(GDI+ 如何使用双缓冲绘制图像),得到了一个GDI+下较可行的方法,虽然绘制效果比直接绘制要好一些,不过还不能跟GDI的双缓冲方式比肩. 现在,我终于找到了一个 ...
- GDI双缓冲绘图
一.简介 在进行复杂图形绘制时,若直接在屏幕DC上进行绘制,则会出现明显的闪烁.闪烁产生的原因是当绘制的图形较为 复杂时,图形绘制过程中就被刷新到屏幕上,导致结果断断续续地显示出来.双缓冲绘图的原理是 ...
- VC GDI双缓冲机制绘图防屏幕闪烁实现步骤
在OnDraw(CDC* pDC) 中添加如下代码 CDC MemDC; //首先定义一个显示设备对象 CBitmap MemBitmap;//定义一个位图对象 //随后建立与屏幕显示兼容的内存显示设 ...
- GDI+ 双缓冲字体模糊
只是记录自己的UI库,对其他估计没什么帮助 void CListCtrlUI::ReFillRect(HDC hdc){ if (!m_pImage) { Graphics gs(hdc); int ...
- Duilib的双缓冲实现,附带GDI、WTL的双缓冲实现
前言: 闪烁问题,之前的经验是使用双缓冲,借此机会,把双缓冲的研究心得总结下. 双缓冲的含义: 缓冲这个词,相信大家都不陌生,Cache.主要是为了解决上下游(或者模块.或者系统)等性能不匹配问题.如 ...
随机推荐
- WPF之图片处理系列(19/590)
https://www.cnblogs.com/Big-Head/p/12068230.html
- 金钱数友好显示 php版本
2019年6月28日16:35:10 此方法可扩展性较好 /* * 吧金额数字转成可视化的方便读的汉字表述 */ function amountConversion(float $amount = 0 ...
- js:如何在iframe重载前执行特定动作
问题说明: 点击左侧菜单时,右侧页面中的iframe加载菜单内容,在iframe加载的页面A中使用了websocket.点击其它菜单时,无法主动关闭websocket, 可能会造成websocket链 ...
- 【NPDP笔记】第一章 新产品开发战略
1.1 战略很重要 1.2 战略定义 使命/愿景/核心价值观:成为领导者 公司/经营战略:市场份额扩大10% 创新战略:强调技术,外部合作 职能战略:IT战略,人力资源战略 1.3明确组织方向 组织身 ...
- mvn-dependencies-vs-dependencyManagement
dependencyManagement里只是声明依赖,并不实现引入,因此子项目需要显式的声明需要用的依赖. dependencies 相对于dependencyManagement,所有声明在dep ...
- mysql 连接远程阿里云数据库
一.修改mysql 数据库的远程访问权限 use mysql; SELECT HOST,user,PASSWORD FROM USER; -- 查询用户信息 UPDATE USER SET HOST= ...
- Node.js实现PC端类微信聊天软件(一)
Github StackChat 技术栈 写这个软件StackChat的主要目的是巩固练习Node和对React的实践,也是为了学习东西,所以选用了这些自己还没在项目里使用过的技术,边学变写 Elec ...
- jq同一页面内容切换
$(function() { //选择标题显示 初始显示内容及样式 $('.right-content .right-item').eq(0).addClass('showcontent') $('. ...
- myeclipse的ctrl+f搜索面板功能详解
1.查找/替换方向:Direction Forward:向前 Backward:向后 2.范围:Scope All:全部(当前文件) Selected lines:选中的几行 3.选项:Options ...
- redis源码分析(五)--cluster(集群)结构
Redis集群 Redis支持集群模式,集群中可以存在多个master,每个master又可以拥有多个slave.数据根据关键字映射到不同的slot,每一个master负责一部分的slots,数据被存 ...