2.3 裝入并顯示圖形文件
為了簡(jiǎn)明地說(shuō)明采用DirectDraw圖形文件的顯示技術(shù),我們以示例程序dx2介紹圖面、圖形文件裝入、圖形縮放、圖形在圖面上顯示等的初步概念和實(shí)現(xiàn)技術(shù)。
2.3.1 DirectDraw顯示圖形的技術(shù)
為了顯示圖象,DirectDraw必需首先擁有類(lèi)似畫(huà)布(canvas)的繪圖空間,DirectDraw并不向在DOS下那樣簡(jiǎn)單地將顯示緩存作為繪畫(huà)的對(duì)象,而是通過(guò)DirectDraw對(duì)象創(chuàng)建各種不同種類(lèi)的“圖面”(Suerface),圖面上的內(nèi)容可以被應(yīng)用程序自由地拷貝、組合,生成千變?nèi)f化的圖形。
2.3.1.1圖面分以下幾種類(lèi)型:
(1)主圖面(Primary圖面):即在屏幕上顯示出來(lái)的圖面,就是GDI用于繪制Windows用戶(hù)界面的圖面。每個(gè)DirectDraw對(duì)象只能有一個(gè)主圖面,主圖面的尺寸、位置和格式由系統(tǒng)當(dāng)前的顯示模式?jīng)Q定,不能改動(dòng)。
(2)后臺(tái)圖面(Off-screen圖面):此類(lèi)圖面不能被直接看到。一般來(lái)說(shuō),后臺(tái)圖面往往用于作為游戲精靈動(dòng)畫(huà)、背景圖形等部件的存儲(chǔ)緩沖區(qū)。后臺(tái)圖面的尺寸是可以調(diào)整的,且可以有多個(gè)后臺(tái)圖面,其大小根據(jù)實(shí)際情況調(diào)整,不要太大或太小。一種典型的例子是:有一個(gè)精靈的動(dòng)畫(huà)由4張128點(diǎn)陣圖形組成,那么可以將后臺(tái)圖面定義為256點(diǎn)的方陣,將這個(gè)動(dòng)畫(huà)序列存儲(chǔ)下來(lái)讀者可能認(rèn)為可以創(chuàng)建一個(gè)比主圖面大的后臺(tái)圖面以便保存游戲背景,這樣可以方便地實(shí)現(xiàn)滾屏,但是,DirectDraw限制后臺(tái)圖面的尺寸不能比主圖面大,除非系統(tǒng)的顯示卡支持。能否實(shí)現(xiàn)大的后臺(tái)圖面我們將在以后敘述。
(3)復(fù)合圖面(Complex圖面)和翻轉(zhuǎn)鏈(Flipping Chain):這種圖面主要用于生成平滑動(dòng)畫(huà)。有關(guān)技術(shù)待制作動(dòng)畫(huà)時(shí)介紹。
(4)覆蓋圖面(Overlay圖面):這是一種由硬件支持的圖面,DirectDraw不能仿真。有關(guān)技術(shù)在后面介紹。
DirectDraw可以把圖面創(chuàng)建在顯示內(nèi)存或系統(tǒng)內(nèi)存中,而顯示內(nèi)存又分為常規(guī)顯示內(nèi)存和AGP加速圖形接口內(nèi)存。由于顯示內(nèi)存容量是有限的,所以每個(gè)圖面具體應(yīng)該創(chuàng)建在哪部分存儲(chǔ)區(qū)域中應(yīng)該統(tǒng)籌規(guī)劃,一般將使用頻繁,需要硬件加速或?qū)崿F(xiàn)功能的圖面安排在顯示內(nèi)存。如果您不指定圖面創(chuàng)建的位置,DirectDraw將首先在常規(guī)顯示內(nèi)存創(chuàng)建圖面,當(dāng)常規(guī)顯示內(nèi)存不夠時(shí),若系統(tǒng)支持AGP內(nèi)存,則先使用AGP內(nèi)存,最終使用系統(tǒng)內(nèi)存。
2.3.1.2 圖形文件的裝入
圖形文件裝入到圖面并不象想象的那么簡(jiǎn)單,因?yàn)檠b入的圖形的點(diǎn)陣可能與、圖面的點(diǎn)陣不同,這就存在圖形的縮放。另外,圖形數(shù)據(jù)在內(nèi)存中的移動(dòng)、復(fù)制,也是需要處理的內(nèi)容。對(duì)于Windows的設(shè)備無(wú)關(guān)位圖,我們可以考慮使用Windows的功能實(shí)現(xiàn):
(1)采用LoadImage函數(shù)裝入圖形文件
(2)采用圖面的GetDC方法獲得圖面與GDI兼容的設(shè)備上下文
(3)采用BitBlt函數(shù)將圖形數(shù)據(jù)拷貝到圖面中
有關(guān)GDI編程請(qǐng)參看有關(guān)Windows編程資料,這里讀者只需要知道固定的用法就可以了。
2.3.1.3 圖面的丟失
在DirectDraw應(yīng)用程序被最小化、屏幕顯示方式改變或用戶(hù)按Alt+Tab鍵切換當(dāng)前應(yīng)用程序時(shí),圖面將會(huì)丟失,因此在重新回到DirectDraw應(yīng)用程序中時(shí),必需用Restore方法恢復(fù)圖面。遺憾的是,雖然圖面被恢復(fù)了,但其中圖形數(shù)據(jù)卻丟失了,需要重新繪制。
2.3.2 dx2運(yùn)行過(guò)程
啟動(dòng)dx2程序后,只有第一個(gè)“執(zhí)行”按鈕可以使用,按下該按鈕后,系統(tǒng)將創(chuàng)建DirectDraw對(duì)象,并設(shè)置為800*600全屏幕顯示方式;按順序按下“創(chuàng)建主圖面”、“創(chuàng)建
圖2.2 dx2 裝入并顯示圖形文件程序運(yùn)行界面
后臺(tái)圖面”按鈕,分別創(chuàng)建對(duì)應(yīng)屏幕顯示的主圖面和100*100點(diǎn)陣的后臺(tái)圖面;按下“后臺(tái)圖面裝入圖形”按鈕,則圖形文件view.bmp被一100*100點(diǎn)陣裝入到后臺(tái)圖面,屏幕上看不見(jiàn)圖形;再按下“主圖面裝入圖形”按鈕,view.bmp以200*100點(diǎn)陣縮放后裝入到主圖面(屏幕)的(0,0)位置,此時(shí)圖形顯示在屏幕左上角;繼續(xù)按“拷貝后臺(tái)圖面到主圖面”,將把后臺(tái)圖面的100*100圖形顯示在屏幕的(200,0)位置,我們可以看到兩副同樣的圖形以不同的縮放比例并排顯示在屏幕左上方;按下“圖面丟失”后,屏幕被設(shè)置成640*480的顯示方式,屏幕上顯示出的圖形消失了;用“恢復(fù)丟失的圖面”按鈕重新設(shè)置顯示方式為800*600(必需恢復(fù)顯示方式,否則圖面恢復(fù)將會(huì)失敗)并恢復(fù)圖面,此時(shí),失去的圖形在屏幕上仍然看不見(jiàn);最后,按“重新顯示圖形”來(lái)重新繪制view.bmp,屏幕重新展現(xiàn)原有的圖形。
2.3.3 dx2程序的編程實(shí)現(xiàn)
2.2.2 dx1編程實(shí)現(xiàn)
啟動(dòng)C++ Builder后在窗口Form1中設(shè)計(jì)如圖2.2的操作界面,各對(duì)象相關(guān)屬性設(shè)置如表2.3:
控件對(duì)象類(lèi)型
控件對(duì)象名稱(chēng)
相關(guān)屬性
屬性值
TForm
Form1
Caption
DirectX 練習(xí)程序1
TLabel
Label1
Caption
運(yùn)行狀態(tài):
TLabel
Label2
Caption
設(shè)備的枚舉
Tlabel
Label3
Caption
顯示模式DDraw2
TEdit
Edit1
Text
(空)
ReadOnly
True
TGroupBox
GroupBox1
Caption
狀態(tài)
TCheckBox
CheckBox6
Caption
DDSCL_NOWINDOWCHANGES
Checked
true
TButton
Button1
Caption
執(zhí)行
TButton
Button2
Caption
創(chuàng)建主圖面
Enabled
False
TButton
Button3
Caption
創(chuàng)建后臺(tái)圖面
Enabled
False
TButton
Button4
Caption
后臺(tái)圖面裝入圖形
Enabled
False
TButton
Button5
Caption
主圖面裝入圖形
Enabled
False
TButton
Button6
Caption
拷貝后臺(tái)圖面到主圖面
Enabled
False
TButton
Button7
Caption
圖面丟失:設(shè)置640*480方式
Enabled
False
TButton
Button8
Caption
恢復(fù)已丟失的圖面
Enabled
False
TButton
Button9
Caption
重新顯示圖形
Enabled
False
表2.3 dx2控件對(duì)象屬性設(shè)置一覽表
2.3.3.1 創(chuàng)建主圖面
用HRESULT IDirectDraw::CreateSurface來(lái)創(chuàng)建圖面:
lpDD2->CreateSurface(LPDDSURFACEDESC lpDDSurfaceDesc,
LPDIRECTDRAWSURFACE FAR *lpDDSurface,
Iunknown FAR *pUnkOuter)
(1)參數(shù)lpDDSurfaceDesc是一個(gè)志向DDSURFACEDESC結(jié)構(gòu)的指針,DDSURFACEDESC結(jié)構(gòu)的定義比較復(fù)雜,幸好一般只需要使用其中很少的一部分。結(jié)構(gòu)DDSURFACEDESC的部分描述如表2.4所示:
結(jié)構(gòu)成員
描述
DOWRD dwSize
DDSURFACE結(jié)構(gòu)的尺寸。在使用此結(jié)構(gòu)之前,此項(xiàng)數(shù)據(jù)必需用sizeof函數(shù)設(shè)置
DWORD dwFlags
控制標(biāo)志。主要可以設(shè)置的標(biāo)志為:
DDSD_CAPS、
DDSD_HEIGHT、
DDSD_WIDTH、
DDSD_BACKBUFFERCOUNT、
DDSD_PIXELFORMATDENG 等
DWORD dwHeight
圖面高度。主圖面不需要設(shè)置
DWORD dwWidth
圖面寬度。主圖面不需要設(shè)置
DDSCAPS ddsCaps
圖面能力。DDSCAPS也是一個(gè)結(jié)構(gòu),在創(chuàng)建圖面時(shí)需要設(shè)置其成員dwCaps的值,以便確定所建圖面的性質(zhì)。
DwCaps的取值主要有:
DDSCAPS_PRIMARYSURFACE:主圖面
DDSCAPS_OFFSCREENPLAIN:后臺(tái)圖面
DDSCAPS_COMPLEX:復(fù)合圖面
DDSCAPS_FLIP:圖面翻轉(zhuǎn)鏈
DDSCAPS_OVERLAY:覆蓋圖面
DDSCAPS_VIDEOMEMORY:圖面創(chuàng)建在顯示內(nèi)存
DDSCAPS_LOCALVIDMEM:使用常規(guī)顯示內(nèi)存
DDSCAPS_NONLOCALVIDMEM:使用AGP內(nèi)存
DDSCAPS_SYSTEMMEMORY:圖面創(chuàng)建在系統(tǒng)內(nèi)存
表2.4 DDSURFACEDESC結(jié)構(gòu)的部分成員說(shuō)明
(2)參數(shù)lpDDSurface返回一個(gè)指向所創(chuàng)建圖面的指針。
(3)參數(shù)pUnkOuter未使用,必需為NULL。
創(chuàng)建主圖面需要以下步驟:
(1)獲得并設(shè)置DDSURFACEDESC結(jié)構(gòu)的尺寸dwSize:ddsd.dwSize=sizeof(ddsd);
(2)簡(jiǎn)單地設(shè)置ddsd.dwFlags=DDSD_CAPS;
(3)設(shè)置主圖面標(biāo)志:ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;
(4)調(diào)用CreateSurface方法創(chuàng)建圖面。
2.3.3.2 創(chuàng)建后臺(tái)圖面
后臺(tái)圖面的創(chuàng)建與創(chuàng)建主圖面基本相同,只是在DDDURFACEDESC結(jié)構(gòu)中多給出一些信息。創(chuàng)建后臺(tái)圖面需要以下步驟:
(1) 獲得并設(shè)置DDSURFACEDESC結(jié)構(gòu)的尺寸dwSize:ddsd.dwSize=sizeof(ddsd);
(2) 設(shè)置ddsd.dwFlags=DDSD_CAPS|DDSD_HEIGHT|DDSD_WIDTH;
(3) 設(shè)置后臺(tái)表面的寬和高(dx2中設(shè)為100):ddsd.dwHeight=100; ddsd.dwWidth=100;
(4) 設(shè)置后臺(tái)圖面標(biāo)志:ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN;
(5)調(diào)用CreateSurface方法創(chuàng)建圖面。
2.3.3.3 圖形文件的裝入后臺(tái)圖面和圖形文件的裝入主圖面
圖形文件的裝入主要采用Windows的函數(shù),雖然使用C++Builder的TCavas對(duì)象打開(kāi)圖形文件要方便一些,但是在BitBlt時(shí)不夠穩(wěn)定,因此dx2還是選擇了前者。
dx2在實(shí)現(xiàn)圖形內(nèi)存數(shù)據(jù)復(fù)制時(shí)采用了GDI,在DirectDrawSurface對(duì)象中有GetDC和ReleaseDC兩個(gè)方法,以便取得HDC并調(diào)用GDI。
HRESULT IDirectDrawSurface::GetDC(HDC FAR *hdc)
HRESULT IDirectDrawSurface::ReleaseDC(HDC hdc)
參數(shù)hdc是一個(gè)設(shè)備句柄。
BitBlt雖然速度比較慢,但是兼容性好,能夠支持不同的顯示模式,而且能夠自動(dòng)進(jìn)行格式轉(zhuǎn)換。
2.3.3.4 后臺(tái)圖面圖形拷貝到主圖面顯示
這里同樣使用了BitBlt,將后臺(tái)圖面的數(shù)據(jù)復(fù)制到主圖面并顯示出來(lái)。
2.3.3.5 丟失圖面及恢復(fù)初始顯示方式和圖面
在dx2中我們演示了當(dāng)改變屏幕顯示方式時(shí),圖面丟失的現(xiàn)象,并且說(shuō)明了在圖面丟失后可以用HRESULT IDirectDrawSurface::Restore()方法來(lái)恢復(fù)圖面,同時(shí)必需重新繪制圖面上的圖形。
Restore方法沒(méi)有參數(shù),但是若要成功恢復(fù)已丟失的圖面,必需屏幕顯示方式重新恢復(fù)到其初始的狀態(tài)。
為了判斷圖面是否已經(jīng)丟失,也可以使用HRESULT IDirectDrawSurface::IsLost()方法來(lái)進(jìn)行檢測(cè),若返回值為DDERR_SURFACELOST則說(shuō)明圖面確實(shí)丟失了。在dx2中沒(méi)有進(jìn)行此判斷,讀者可以根據(jù)自己的理解修改dx2,實(shí)現(xiàn)恢復(fù)圖面前首先進(jìn)行圖面是否丟失的判斷。
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:oolsdx5sdksdkincddraw.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);
}