让图形动起来
让图形动起来的办法就是不断定时清屏、重绘。像放电影那样。然而,直接向屏幕绘图是耗时的,在屏幕上画很多线,将多次调用绘图接口,这样,不但绘制效率低(慢),而且视觉上闪烁感明显。
解决的办法是:先在内存中将多条线的绘制结果准备好,再一次性调用向屏幕绘图的接口。这称为双缓冲法。即先向内存中的缓冲区作图,再将作图结果一次性拷贝到屏幕的显示区。
直接向屏幕作图
效果如下,视觉上闪烁感明显,不够好。
代码(64行):
#include<windows.h>
#include<stdio.h>
#include<math.h>
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"gdi32.lib")
const double pi=3.14159265;
void ClearWind(HDC hdc, int SCREEN_WIDTH,int SCREEN_HEIGHT);
int main(int argc,char *argv[])
{
TCHAR title[256];//控制台程序标题
GetConsoleTitle(title, 256);//获取控制台标题
//先通过FindWindow函数借助标题获取窗口句柄HWND,之后再利用GetDC函数借助HWND获取HDC
HWND hWnd=FindWindow(0, title);
HDC hdc = GetDC(hWnd);//HDC是设备描述表句柄(/获取屏幕显示DC)
int SCREEN_WIDTH = GetSystemMetrics(SM_CXSCREEN); //屏幕宽度
int SCREEN_HEIGHT =GetSystemMetrics(SM_CYSCREEN); //屏幕高度
double rho, theta, x, y;//极坐标和直角坐标
HPEN hPen1,hPen2,hPen3;
hPen1 = CreatePen(PS_SOLID, 0, RGB(255,155,0));//添加画笔属性
hPen2 = CreatePen(PS_SOLID, 0, RGB(0,255,0));//添加画笔属性
hPen3 = CreatePen(PS_SOLID, 0, RGB(0,0,255));//添加画笔属性
for(int j=0;j<1000; j++){
ClearWind(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);//清屏
for (int i=0; i<20000; i++)
{
theta=-pi+pi/10000.0*i;
rho=1+cos(7*(theta+j/100.0))+pow(1.5*(sin(3*(theta+j/100.0))),2);
x=400+50*rho*cos(theta);
y=300-50*rho*sin(theta);
//三支不同色的笔
if(theta<(-pi/3.0)){
SelectObject(hdc, hPen1);//画笔绑定到句柄
}
if(theta>-pi/3.0 )
{
SelectObject(hdc, hPen2);//画笔绑定到句柄
}
if(theta>pi/3.0)
{
SelectObject(hdc, hPen3);//画笔绑定到句柄
}
MoveToEx(hdc,400,300,NULL); //笔移到起点
LineTo(hdc,(int)x,(int)y); //画线作图
}
Sleep(10);//延时 ms
}
return 0;
}
void ClearWind(HDC hdc, int SCREEN_WIDTH,int SCREEN_HEIGHT)
{
HPEN hPen;
HBRUSH hBrush;
hBrush = CreateSolidBrush(RGB(0, 128, 128));
hPen = CreatePen(PS_SOLID, 0, RGB(0, 128, 128));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}
使用双缓冲技术
效果(实际运行效果比gif截图好):
代码(74行)
#include<windows.h>
#include<stdio.h>
#include<math.h>
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"gdi32.lib")
const double pi=3.14159265;
void ClearWind(HDC hdc, int SCREEN_WIDTH,int SCREEN_HEIGHT);
int main(int argc,char *argv[])
{
TCHAR title[256];//控制台程序标题
GetConsoleTitle(title, 256);//获取控制台标题
//先通过FindWindow函数借助标题获取窗口句柄HWND,之后再利用GetDC函数借助HWND获取HDC
HWND hWnd=FindWindow(0, title);
HDC hdc = GetDC(hWnd);//HDC是设备描述表句柄(获取屏幕显示DC)
int SCREEN_WIDTH = GetSystemMetrics(SM_CXSCREEN); //屏幕宽度
int SCREEN_HEIGHT =GetSystemMetrics(SM_CYSCREEN); //屏幕高度
//依据屏幕显示DC创建内存DC设备描述表句柄
HDC hdcMem = CreateCompatibleDC(hdc);
//依据屏幕显示DC创建一个bmp内存空间
HBITMAP hBmp = CreateCompatibleBitmap(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);
//将bmp内存空间分配给内存DC,
SelectObject(hdcMem,hBmp);
double rho, theta, x, y;//极坐标和直角坐标
HPEN hPen1,hPen2,hPen3;
hPen1 = CreatePen(PS_SOLID, 0, RGB(255,155,0));//添加画笔属性
hPen2 = CreatePen(PS_SOLID, 0, RGB(0,255,0));//添加画笔属性
hPen3 = CreatePen(PS_SOLID, 0, RGB(0,0,255));//添加画笔属性
for(int j=0;j<1000; j++){
ClearWind(hdcMem,SCREEN_WIDTH,SCREEN_HEIGHT);//清屏
for (int i=0; i<20000; i++)
{
theta=-pi+pi/10000.0*i;
rho=1+cos(7*(theta+j/100.0))+pow(1.5*(sin(3*(theta+j/100.0))),2);
x=400+50*rho*cos(theta);
y=300-50*rho*sin(theta);
//三支不同色的笔
if(theta<(-pi/3.0)){
SelectObject(hdcMem, hPen1);//画笔绑定到句柄
}
if(theta>-pi/3.0 )
{
SelectObject(hdcMem, hPen2);//画笔绑定到句柄
}
if(theta>pi/3.0)
{
SelectObject(hdcMem, hPen3);//画笔绑定到句柄
}
MoveToEx(hdcMem,400,300,NULL); //笔移到起点
LineTo(hdcMem,(int)x,(int)y); //画线作图
}
BitBlt(hdc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,hdcMem,0,0,SRCCOPY);
Sleep(10);//延时 ms
}
return 0;
}
void ClearWind(HDC hdc, int SCREEN_WIDTH,int SCREEN_HEIGHT)
{
HPEN hPen;
HBRUSH hBrush;
hBrush = CreateSolidBrush(RGB(0, 128, 128));
hPen = CreatePen(PS_SOLID, 0, RGB(0, 128, 128));
SelectObject(hdc, hBrush);
SelectObject(hdc, hPen);
Rectangle(hdc, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
}
双缓冲技术编程原理
传统的绘图方式实际上是一种单缓冲。在windows中每一种设备都在内存中有一个设备描述表与其对应,这个设备描述表实际上就是一个内存缓冲区。
传统的绘图中我们是将图形绘制在设备描述表缓冲区中,然后由gdi自动的将设备描述表中的图像拷贝到显存中进行显示。这样一个自动的拷贝过程屏蔽了传统的绘图方式是单缓冲的实质,使我们感觉到我们是在直接操纵显存一样。
双缓冲图形刷新是采用双缓存实现的。双缓冲图形刷新技术在内存中有两片缓存,除了设备描述表以外还有一个需要手动建立的与设备描述表缓冲区(前端缓冲区)相兼容的后备缓冲区。绘图过程中,首先将图形绘制在后备缓冲区中,然后在手动的将后备缓冲区中的图像拷贝到前端缓冲区中,再由gdi自动将前端缓冲区中的图像拷贝到显存完成图形的显示过程。
双缓冲图形刷新的实现步骤是:
1、创建与窗口设备描述表(前端缓冲区)兼容的内存设备描述表(后端缓冲区)
2、创建与内存设备描述表相兼容的位图并将该位图选入内存设备描述表中(没有位图的设备描述表是不能绘图的)
3、将图形绘制在内存设备描述表中
4、将内存设备描述表中的内容拷贝到窗口设备描述表
5、释放设备描述表句柄、位图等资源. (在我们的代码中省略了这步)
具体关键代码如下:
0、准备
先计算出显示区宽高像素备用:
int SCREEN_WIDTH = GetSystemMetrics(SM_CXSCREEN); //屏幕宽度
int SCREEN_HEIGHT =GetSystemMetrics(SM_CYSCREEN); //屏幕高度
通过
HDC hdc = GetDC(hWnd);
得到显示区句柄。
1、依据显示区句柄hdc
建立内存图形缓冲区,获得作图句柄hdcMem
。
//依据屏幕显示DC创建内存DC设备描述表句柄
HDC hdcMem = CreateCompatibleDC(hdc);
//依据屏幕显示DC创建一个bmp内存空间
HBITMAP hBmp = CreateCompatibleBitmap(hdc,SCREEN_WIDTH,SCREEN_HEIGHT);
//将bmp内存空间分配给内存DC,
SelectObject(hdcMem,hBmp);
//或用如下方法来保存以前的句柄以便完成作图后释放恢复。
//HGDIOBJ hOldSel = SelectObject(hdcMem,hBmp);
2、向缓冲区hdcMem
作图,而不是直接向屏幕显示区句柄hdc
作图。作图方法和函数与直接向屏幕显示区作图相同,仅句柄对象不同。
3、作图完成后,将缓冲区hdcMem
的内容拷贝到屏幕显示区。
BitBlt(hdc,0,0,SCREEN_WIDTH,SCREEN_HEIGHT,hdcMem,0,0,SRCCOPY);
4、释放资源和现场恢复
SelectObject(hdcMem,hOldSel);
DeleteDC(hdcMem);