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(必需恢復顯示方式,否則圖面恢復將會失?。┎⒒謴蛨D面,此時,失去的圖形在屏幕上仍然看不見;最后,按“重新顯示圖形”來重新繪制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: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);
}