如果是使用VB,也許這個話題是多余的,因為VB有一個圖象控件可以非常容易地實現各種格式的圖象顯示功能,然而對於VC卻沒有一個象樣的控件可以達到這種效果,怎麼辦?經過一段日子的研究,發現只需要實現兩步工作,就可以在VC中實現如同VB中一樣的gif動態效果。
本文將介紹的兩部分是IPicture接口的使用和gif的儲存格式,好象一聽到儲存格式,讀者就不想再看下去了!其實不然,這裡只須用到其最基本的一部分,請讀者耐心地往下看。
一.IPicture接口
IPicture接口是一個com類,其成員函數可參見微軟的MSDN,這裡只需用到以下幾個函數:
get_Width 返回當前圖像的寬度 get_Height 返回當前圖像的高度 Render 在指定的位置、設備上下文上繪制指定的圖像
IPicture的使用不需要CoCreateInstance函數,而只需要使用OleLoadPicture,鑒於此接口在許多文章雜志上均有介紹,這裡略去(因為不是本文的重點)。
二.Gif儲存格式
gif儲存格式是一個非常復雜的內容,如果要講透徹可以寫很多篇文章,慶幸的是要實現本文的主題只需要知道其中的一個圖象儲存結構就可以了,這裡定義該圖象結構為gifImage:
typedef struct gifImage{
在該結構中,
WORD logX;
WORD logY;
WORD width;
WORD height;
struct flag{
BYTE d:3;
BYTE c:1;
BYTE b:3;
BYTE a:1;
}Flag;
}GifImage,*PGifImage;
logX為圖象相對於邏輯屏幕左上角的x坐標,常為0;
logY為圖象相對於邏輯屏幕左上角的y坐標,常為0;
width為圖象的寬度;
height為圖象的高度;
Flag為一個標志,a指是否存在局域性調色板,b為存儲方式(與本主題無關),c為RGB值是否經過排序(無關),d為調色板的大小,為3*2^(d+1);
最後想提一下,因為每副圖象都以0x2c開頭,並且0x2c前面必為0,故在儲存格式中要找到圖象的起始位置,只需查找0x2c並且前面一個值為0(具體請看下面代碼),其次,一副圖象可能儲存為多個數據塊,每個數據快都是以數據長度為起始的,這一點很重要。即其圖象儲存為:
0x2c 圖象開頭 gifImage 圖象頭結構 BYTE Number Number為一個跟gif壓縮有關的數字,可以不理踩。 第一副圖象的大小 ...... 圖象存儲內容 第二副圖象大小 ...... ...... ...... 0x00 數據塊結束
好,介紹完了主要的兩大部分也該進入今天的主題了。由於使用IPicture接口可以非常輕松地顯示gif的第一副圖畫,那麼我們就是利用這一點,利用gif的圖象格式,把第二,三。。。圖畫逐一拷貝到第一副圖畫的位置,再使用IPicture進行讀取,便可以形成一副十分連續的gif動畫了,接下來就讓我來給大家展示以下:
HINSTANCE handle = ::AfxGetResourceHandle(); //首先獲得資源句柄
HRSRC hrsrc= ::FindResource(handle,MAKEINTRESOURCE(IDR_IMAGE2),"IMAGE");
DWORD word = ::SizeofResource(handle,hrsrc);
BYTE* lpBy = (BYTE*)LoadResource(handle,hrsrc); //獲得圖象的首地址
BYTE* pByte[20]; //用來儲存gif每幅圖象的地址
DWORD nu[20]; //用來儲存每幅圖象的大小
int num = 0; //用來計算有幾副圖象
DWORD firstLocation = 0; //第一副圖象的位置,用來替換
for(DWORD j=0;j<word;j++)
{
if(lpBy[j]==0x2c) //圖象開頭
{
if(lpBy[j-1]==0x00) //確認是否圖象開頭
{
if(num==0)
{
firstLocation = j; //得到第一副圖象位置
}
PGifImage nowImage = (PGifImage)&lpBy[j+1];
if(nowImage->Flag.a==0) //a為0時指圖象不存在局部調色板
{
DWORD number = 1+sizeof(GifImage);
while(lpBy[j+number]!=0)
{
number = number+(DWORD)lpBy[j+number]+1;
} //算得圖象大小
number++; //把最後一個0x00加上
pByte[num] = new BYTE[number];
for(DWORD n=0;n<number;n++)
{
*(BYTE*)(pByte[num]+n) = lpBy[j+n];
} //將圖象儲存起來。
nu[num] = number;
j = j+number-1; //跳過圖象
num++;
}
else
{ //當a為1時需要加上局部調色板的大小,其他與a為0時一樣
int number = 1+sizeof(GifImage)+3*(int)floor(pow(2,nowImage->Flag.d));
while(lpBy[j+number]!=0)
{
number = number+(DWORD)lpBy[j+number]+1;
} //算得圖象大小
number++; //把最後一個0x00加上
pByte[num] = new BYTE[number];
for(DWORD n=0;n<number;n++)
{
*(BYTE*)(pByte[num]+n) = lpBy[j+n];
} //將圖象儲存起來。
nu[num] = number;
j = j+number-1; //跳過圖象
num++;
}
}
}
}
int working= 1;
while(working)
{
for(int m=0;m<num;m++)
{
CBrush brush(RGB(255,255,255));
pdc->FillRect(CRect(0,0,500,500),&brush);
DWORD DDD;
VirtualProtect(lpBy,word,PAGE_READWRITE,&DDD);
//修改頁面的保護屬性,以進行寫操作
for(DWORD n=0;n<nu[m];n++)
{
lpBy[firstLocation+n] = *(BYTE*)(pByte[m]+n);
}
VirtualProtect(lpBy,word,DDD,NULL);
//因為IPicture必須把圖象存成流的形式才能工作,所以有下面一段函數
CMemFile file(lpBy,word);
CArchive ar(&file,CArchive::load|CArchive::bNoFlushOnDelete);
CArchiveStream arcstream(&ar);
CComQIPtr<IPicture> m_picture;
HRESULT hr = OleLoadPicture((LPSTREAM)&arcstream,0,false,
IID_IPicture,(void**)&m_picture);
long a,b;
m_picture->get_Width(&a);
m_picture->get_Height(&b);
CSize sz(a,b);
pdc->HIMETRICtoDP(&sz); //時OLE格式的大小轉化為正常大小
CRect rect;
m_picture->Render(*pdc,0,0,sz.cx,sz.cy,0,b,a,-b,&rect);
Sleep(100); //停止一段時間。
}
}
結尾語:本程序最好放在一個線程中進行工作,對於最後Sleep的毫秒數就讀者喜好進行修改,其實gif儲存格式中有一個圖象間隔的毫秒數,但是,筆者認為在此沒有必要,還是隨讀者的喜好較好。由於筆者時間較緊,沒有把程序整理成原代碼供讀者下載,深表歉意,並且本文中出現的小錯誤,希望讀者批評指正。