2.3 装入并显示图形文件
为了简明地说明采用DirectDraw图形文件的显示技术,我们以示例程序dx2介绍图面、图形文件装入、图形缩放、图形在图面上显示等的初步概念和实现技术。
2.3.1 DirectDraw显示图形的技术
为了显示图象,DirectDraw必需首先拥有类似画布(canvas)的绘图空间,DirectDraw并不向在DOS下那样简单地将显示缓存作为绘画的对象,而是通过DirectDraw对象创建各种不同种类的“图面”(Suerface),图面上的内容可以被应用程序自由地拷贝、组合,生成千变万化的图形。
2.3.1.1图面分以下几种类型:
(1)主图面(Primary图面):即在屏幕上显示出来的图面,就是GDI用于绘制Windows用户界面的图面。每个DirectDraw对象只能有一个主图面,主图面的尺寸、位置和格式由系统当前的显示模式决定,不能改动。
(2)后台图面(Off-screen图面):此类图面不能被直接看到。一般来说,后台图面往往用于作为游戏精灵动画、背景图形等部件的存储缓冲区。后台图面的尺寸是可以调整的,且可以有多个后台图面,其大小根据实际情况调整,不要太大或太小。一种典型的例子是:有一个精灵的动画由4张128点阵图形组成,那么可以将后台图面定义为256点的方阵,将这个动画序列存储下来读者可能认为可以创建一个比主图面大的后台图面以便保存游戏背景,这样可以方便地实现滚屏,但是,DirectDraw限制后台图面的尺寸不能比主图面大,除非系统的显示卡支持。能否实现大的后台图面我们将在以后叙述。
(3)复合图面(Complex图面)和翻转链(Flipping Chain):这种图面主要用于生成平滑动画。有关技术待制作动画时介绍。
(4)覆盖图面(Overlay图面):这是一种由硬件支持的图面,DirectDraw不能仿真。有关技术在后面介绍。
DirectDraw可以把图面创建在显示内存或系统内存中,而显示内存又分为常规显示内存和AGP加速图形接口内存。由于显示内存容量是有限的,所以每个图面具体应该创建在哪部分存储区域中应该统筹规划,一般将使用频繁,需要硬件加速或实现功能的图面安排在显示内存。如果您不指定图面创建的位置,DirectDraw将首先在常规显示内存创建图面,当常规显示内存不够时,若系统支持AGP内存,则先使用AGP内存,最终使用系统内存。
2.3.1.2 图形文件的装入
图形文件装入到图面并不象想象的那么简单,因为装入的图形的点阵可能与、图面的点阵不同,这就存在图形的缩放。另外,图形数据在内存中的移动、复制,也是需要处理的内容。对于Windows的设备无关位图,我们可以考虑使用Windows的功能实现:
(1)采用LoadImage函数装入图形文件
(2)采用图面的GetDC方法获得图面与GDI兼容的设备上下文
(3)采用BitBlt函数将图形数据拷贝到图面中
有关GDI编程请参看有关Windows编程资料,这里读者只需要知道固定的用法就可以了。
2.3.1.3 图面的丢失
在DirectDraw应用程序被最小化、屏幕显示方式改变或用户按Alt+Tab键切换当前应用程序时,图面将会丢失,因此在重新回到DirectDraw应用程序中时,必需用Restore方法恢复图面。遗憾的是,虽然图面被恢复了,但其中图形数据却丢失了,需要重新绘制。
2.3.2 dx2运行过程
启动dx2程序后,只有第一个“执行”按钮可以使用,按下该按钮后,系统将创建DirectDraw对象,并设置为800*600全屏幕显示方式;按顺序按下“创建主图面”、“创建
图2.2 dx2 装入并显示图形文件程序运行界面
后台图面”按钮,分别创建对应屏幕显示的主图面和100*100点阵的后台图面;按下“后台图面装入图形”按钮,则图形文件view.bmp被一100*100点阵装入到后台图面,屏幕上看不见图形;再按下“主图面装入图形”按钮,view.bmp以200*100点阵缩放后装入到主图面(屏幕)的(0,0)位置,此时图形显示在屏幕左上角;继续按“拷贝后台图面到主图面”,将把后台图面的100*100图形显示在屏幕的(200,0)位置,我们可以看到两副同样的图形以不同的缩放比例并排显示在屏幕左上方;按下“图面丢失”后,屏幕被设置成640*480的显示方式,屏幕上显示出的图形消失了;用“恢复丢失的图面”按钮重新设置显示方式为800*600(必需恢复显示方式,否则图面恢复将会失败)并恢复图面,此时,失去的图形在屏幕上仍然看不见;最后,按“重新显示图形”来重新绘制view.bmp,屏幕重新展现原有的图形。
2.3.3 dx2程序的编程实现
2.2.2 dx1编程实现
启动C++ Builder后在窗口Form1中设计如图2.2的操作界面,各对象相关属性设置如表2.3:
| 控件对象类型 | 控件对象名称 | 相关属性 | 属性值 |
| TForm | Form1 | Caption | DirectX 练习程序1 |
| TLabel | Label1 | Caption | 运行状态: |
| TLabel | Label2 | Caption | 设备的枚举 |
| Tlabel | Label3 | Caption | 显示模式DDraw2 |
| TEdit | Edit1 | Text | (空) |
| ReadOnly | True |
| TGroupBox | GroupBox1 | Caption | 状态 |
| TCheckBox | CheckBox6 | Caption | DDSCL_NOWINDOWCHANGES |
| Checked | true |
| TButton | Button1 | Caption | 执行 |
| TButton | Button2 | Caption | 创建主图面 |
| Enabled | False |
| TButton | Button3 | Caption | 创建后台图面 |
| Enabled | False |
| TButton | Button4 | Caption | 后台图面装入图形 |
| Enabled | False |
| TButton | Button5 | Caption | 主图面装入图形 |
| Enabled | False |
| TButton | Button6 | Caption | 拷贝后台图面到主图面 |
| Enabled | False |
| TButton | Button7 | Caption | 图面丢失:设置640*480方式 |
| Enabled | False |
| TButton | Button8 | Caption | 恢复已丢失的图面 |
| Enabled | False |
| TButton | Button9 | Caption | 重新显示图形 |
| Enabled | False |
表2.3 dx2控件对象属性设置一览表
2.3.3.1 创建主图面
用HRESULT IDirectDraw::CreateSurface来创建图面:
lpDD2->CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc,
LPDIRECTDRAWSURFACE FAR *lpDDSurface,
Iunknown FAR *pUnkOuter)
(1)参数lpDDSurfaceDesc是一个志向DDSURFACEDESC结构的指针,DDSURFACEDESC结构的定义比较复杂,幸好一般只需要使用其中很少的一部分。结构DDSURFACEDESC的部分描述如表2.4所示:
结构成员
描述
DOWRD dwSize
DDSURFACE结构的尺寸。在使用此结构之前,此项数据必需用sizeof函数设置
DWORD dwFlags
控制标志。主要可以设置的标志为:
DDSD_CAPS、
DDSD_HEIGHT、
DDSD_WIDTH、
DDSD_BACKBUFFERCOUNT、
DDSD_PIXELFORMATDENG 等
DWORD dwHeight
图面高度。主图面不需要设置
DWORD dwWidth
图面宽度。主图面不需要设置
DDSCAPS ddsCaps
图面能力。DDSCAPS也是一个结构,在创建图面时需要设置其成员dwCaps的值,以便确定所建图面的性质。
DwCaps的取值主要有:
DDSCAPS_PRIMARYSURFACE:主图面
DDSCAPS_OFFSCREENPLAIN:后台图面
DDSCAPS_COMPLEX:复合图面
DDSCAPS_FLIP:图面翻转链
DDSCAPS_OVERLAY:覆盖图面
DDSCAPS_VIDEOMEMORY:图面创建在显示内存
DDSCAPS_LOCALVIDMEM:使用常规显示内存
DDSCAPS_NONLOCALVIDMEM:使用AGP内存
DDSCAPS_SYSTEMMEMORY:图面创建在系统内存
表2.4 DDSURFACEDESC结构的部分成员说明
(2)参数lpDDSurface返回一个指向所创建图面的指针。
(3)参数pUnkOuter未使用,必需为NULL。
创建主图面需要以下步骤:
(1)获得并设置DDSURFACEDESC结构的尺寸dwSize:ddsd.dwSize=sizeof(ddsd);
(2)简单地设置ddsd.dwFlags=DDSD_CAPS;
(3)设置主图面标志:ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
(4)调用CreateSurface方法创建图面。
2.3.3.2 创建后台图面
后台图面的创建与创建主图面基本相同,只是在DDDURFACEDESC结构中多给出一些信息。创建后台图面需要以下步骤:
(1) 获得并设置DDSURFACEDESC结构的尺寸dwSize:ddsd.dwSize=sizeof(ddsd);
(2) 设置ddsd.dwFlags=DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH;
(3) 设置后台表面的宽和高(dx2中设为100):ddsd.dwHeight=100; ddsd.dwWidth=100;
(4) 设置后台图面标志:ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN;
(5)调用CreateSurface方法创建图面。
2.3.3.3 图形文件的装入后台图面和图形文件的装入主图面
图形文件的装入主要采用Windows的函数,虽然使用C++Builder的TCavas对象打开图形文件要方便一些,但是在BitBlt时不够稳定,因此dx2还是选择了前者。
dx2在实现图形内存数据复制时采用了GDI,在DirectDrawSurface对象中有GetDC和ReleaseDC两个方法,以便取得HDC并调用GDI。
HRESULT IDirectDrawSurface::GetDC(HDC FAR *hdc)
HRESULT IDirectDrawSurface::ReleaseDC(HDC hdc)
参数hdc是一个设备句柄。
BitBlt虽然速度比较慢,但是兼容性好,能够支持不同的显示模式,而且能够自动进行格式转换。
2.3.3.4 后台图面图形拷贝到主图面显示
这里同样使用了BitBlt,将后台图面的数据复制到主图面并显示出来。
2.3.3.5 丢失图面及恢复初始显示方式和图面
在dx2中我们演示了当改变屏幕显示方式时,图面丢失的现象,并且说明了在图面丢失后可以用HRESULT IDirectDrawSurface::Restore()方法来恢复图面,同时必需重新绘制图面上的图形。
Restore方法没有参数,但是若要成功恢复已丢失的图面,必需屏幕显示方式重新恢复到其初始的状态。
为了判断图面是否已经丢失,也可以使用HRESULT IDirectDrawSurface::IsLost()方法来进行检测,若返回值为DDERR_SURFACELOST则说明图面确实丢失了。在dx2中没有进行此判断,读者可以根据自己的理解修改dx2,实现恢复图面前首先进行图面是否丢失的判断。
2.3.4 dx2源程序
2.3.4.1 dx2主要文件的组成为:工程文件(dx2.bpr)、窗口文件(main.cpp)、头文件(main.h)、view.bmp。
2.3.4.2 头文件main.h
#ifndef mainH
#define mainH
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Dialogs.hpp>
#include "d:\tools\dx5sdk\sdk\inc\ddraw.h"
//---------------------------------------------------------------------------
char CR[]={13,10,0};
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
TLabel *Label1;
TGroupBox *GroupBox1;
TMemo *Memo1;
TButton *Button2;
TButton *Button3;
TButton *Button4;
TButton *Button5;
TButton *Button6;
TButton *Button7;
TButton *Button8;
TButton *Button9;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall Button3Click(TObject *Sender);
void __fastcall Button4Click(TObject *Sender);
void __fastcall Button5Click(TObject *Sender);
void __fastcall Button6Click(TObject *Sender);
void __fastcall Button7Click(TObject *Sender);
void __fastcall Button8Click(TObject *Sender);
void __fastcall Button9Click(TObject *Sender);
private: // User declarations
LPDIRECTDRAW FAR lpDD;
LPDIRECTDRAW2 FAR lpDD2;
DDSURFACEDESC ddsd;
LPDIRECTDRAWSURFACE FAR lpDDPrimary,lpDDOffScreen;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
2.3.4.3 程序文件main.cpp
#include <vcl.h>
#pragma hdrstop
#include "main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(!FAILED(DirectDrawCreate(NULL,&lpDD,NULL)))
{
if(!FAILED(lpDD->QueryInterface(IID_IDirectDraw2,(LPVOID *)&lpDD2)))
{
lpDD->Release();
if(!FAILED(lpDD2->SetCooperativeLevel(Handle,
DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN|DDSCL_NOWINDOWCHANGES)))
{
if(!FAILED(lpDD2->SetDisplayMode(800,600,16,0,0)))
{
Memo1->Lines->Text=Memo1->Lines->Text+"Create DirectDraw Object OK."+(String)CR;
Button1->Enabled=false;
Button2->Enabled=true;
return;
}
}
}
}
Memo1->Lines->Text=Memo1->Lines->Text+"Create DirectDraw Object Failed."+(String)CR;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
if(FAILED(lpDD2->CreateSurface(&ddsd,&lpDDPrimary,NULL)))
Memo1->Lines->Text=Memo1->Lines->Text+"Create Priamry Surface Failed."+(String)CR;
else
{
Memo1->Lines->Text=Memo1->Lines->Text+"Create Priamry Surface OK."+(String)CR;
Button2->Enabled=false;
Button3->Enabled=true;
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
lpDD2->Release();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH;
ddsd.dwHeight=100;
ddsd.dwWidth=100;
ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN;
if(FAILED(lpDD2->CreateSurface(&ddsd,&lpDDOffScreen,NULL)))
Memo1->Lines->Text=Memo1->Lines->Text+"Create OffScreen Surface Failed."+(String)CR;
else
{
Memo1->Lines->Text=Memo1->Lines->Text+"Create OffScreen Surface OK."+(String)CR;
Button3->Enabled=false;
Button4->Enabled=true;
}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
HDC hdc,hdcImage;
HBITMAP hbm;
hbm=(HBITMAP)LoadImage(NULL,"view.bmp",IMAGE_BITMAP,100,100,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
hdcImage=CreateCompatibleDC(NULL);
SelectObject(hdcImage,hbm);
if(FAILED(lpDDOffScreen->GetDC(&hdc)))
Memo1->Lines->Text=Memo1->Lines->Text+"Get DC of OffScreen Screen Surface Failed."+(String)CR;
else
{
Memo1->Lines->Text=Memo1->Lines->Text+"Get DC of OffScreen Screen Surface OK."+(String)CR;
if(BitBlt(hdc,0,0,100,100,hdcImage,0,0,SRCCOPY)==FALSE)
Memo1->Lines->Text=Memo1->Lines->Text+"OffScreen Screen BitBlt Failed."+(String)CR;
else
{
Memo1->Lines->Text=Memo1->Lines->Text+"OffScreen Screen BitBlt OK."+(String)CR;
Button4->Enabled=false;
Button5->Enabled=true;
}
lpDDOffScreen->ReleaseDC(hdc);
}
if(hdcImage) DeleteDC(hdcImage);
if(hbm) DeleteObject(hbm);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
HDC hdc,hdcImage;
HBITMAP hbm;
hbm=(HBITMAP)LoadImage(NULL,"view.bmp",IMAGE_BITMAP,200,100,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
hdcImage=CreateCompatibleDC(NULL);
SelectObject(hdcImage,hbm);
if(FAILED(lpDDPrimary->GetDC(&hdc)))
Memo1->Lines->Text=Memo1->Lines->Text+"Get DC of Primary Screen Surface Failed."+(String)CR;
else
{
Memo1->Lines->Text=Memo1->Lines->Text+"Get DC of Primary Screen Surface OK."+(String)CR;
if(BitBlt(hdc,0,0,200,100,hdcImage,0,0,SRCCOPY)==FALSE)
Memo1->Lines->Text=
Memo1->Lines->Text+"Primary Screen BitBlt Failed."+(String)CR;
else
{
Memo1->Lines->Text=Memo1->Lines->Text+"Primary Screen BitBlt OK."+(String)CR;
Button5->Enabled=false;
Button6->Enabled=true;
}
lpDDPrimary->ReleaseDC(hdc);
}
if(hdcImage) DeleteDC(hdcImage);
if(hbm) DeleteObject(hbm);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button6Click(TObject *Sender)
{
HDC hdcPrimary,hdcOffScreen;
lpDDPrimary->GetDC(&hdcPrimary);
lpDDOffScreen->GetDC(&hdcOffScreen);
BitBlt(hdcPrimary,200,0,100,100,hdcOffScreen,0,0,SRCCOPY);
lpDDPrimary->ReleaseDC(hdcPrimary);
lpDDOffScreen->ReleaseDC(hdcOffScreen);
Memo1->Lines->Text=Memo1->Lines->Text+"OffScreen To Primary OK."+(String)CR;
Button6->Enabled=false;
Button7->Enabled=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button7Click(TObject *Sender)
{
lpDD2->SetDisplayMode(640,480,16,0,0);
if(lpDDPrimary->IsLost()==DDERR_SURFACELOST&&lpDDOffScreen->IsLost()==DDERR_SURFACELOST)
Memo1->Lines->Text=Memo1->Lines->Text+"Surfaces are Lost."+(String)CR;
else
Memo1->Lines->Text=Memo1->Lines->Text+"Surfaces remain."+(String)CR;
Button7->Enabled=false;
Button8->Enabled=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button8Click(TObject *Sender)
{
lpDD2->SetDisplayMode(800,600,16,0,0);
lpDDPrimary->Restore();
lpDDOffScreen->Restore();
Memo1->Lines->Text=Memo1->Lines->Text+"Restore lost Surfaces OK."+(String)CR;
Button8->Enabled=false;
Button9->Enabled=true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button9Click(TObject *Sender)
{
Button4Click(Sender);
Button5Click(Sender);
Button6Click(Sender);
}