GDI+でアニメPNGの表示
環境はWinXP SP3 と VC++2010です。
あるウィンドウにアニメーションPNGを表示したいと考えているのですが、
Gdiplus::Image::SelectActiveFrame()やGdiplus::Image::DrawImage()のタイミングで
メモリアクセス違反(0xC0000005)となり、困っています。
ファイルによって大丈夫だったりエラーになったりします。
アニメじゃない画像の場合も正常に動きます。
エラーにならないアニメPNG:https://si0.twimg.com/profile_images/1256677017/GLOOMY_battle_normal.gif
エラーになるアニメPNG:https://si0.twimg.com/profile_images/1256677017/GLOOMY_battle_bigger.gif
この問題とは別のようで、
低い頻度で、開発中のアプリの別の部分で実際にメモリが破壊されたようなバグり方をします。
http://support.microsoft.com/kb/961889/ja
コードは以下です。
元のコードは長いので検証用に小さくまとめました。
このコードで同様のバグの再現を確認しています。
WM_CREATEで_threadstartexでスレッドを立ち上げ
WM_DELETEでThreaadParam::finをtrueにしてスレッドの終了を待機ます。
/***************************************************
* ここから
**************************************************/
struct ThreadParams{
HWND hWnd;
bool fin;
IStream* stream;
Gdiplus::Image* image;
ThreadParams(HWND hWnd):hWnd(hWnd),fin(false),stream(NULL),image(NULL){}
};
bool InputFile(const TCHAR* infile,void** buf,DWORD* size){
DWORD filesize;
HANDLE hFile = CreateFile(infile,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if(hFile!=INVALID_HANDLE_VALUE){
filesize = GetFileSize(hFile,NULL);
*buf = new UCHAR[filesize+1];
// 読み込み
ReadFile(hFile,*buf,filesize,size,NULL);
FlushFileBuffers(hFile);
// 末尾NULL
(*((UCHAR**)buf))[filesize] = 0;
// ファイルハンドルの開放
CloseHandle(hFile);
return true;
}else{
*buf=NULL;
*size=0;
return false;
}
}
bool CreateByteStream(IStream** stream){
if(stream == NULL){
return false;
}
HRESULT hr = CreateStreamOnHGlobal(NULL,TRUE,stream);
if(SUCCEEDED(hr)){
return true;
}else{
*stream = NULL;
return false;
}
}
bool CreateByteStream(IStream** stream,const void* buf,DWORD size){
if(CreateByteStream(stream)){
if(buf!=NULL){
DWORD wsize;
(*stream)->Write(buf,size,&wsize);
if(size==wsize){
return true;
}
}
}
if(*stream!=NULL){
(*stream)->Release();
*stream = NULL;
}
return false;
}
bool CreateByteStream(IStream** stream,const TCHAR* filename){
bool ret = false;
UCHAR *buf;
DWORD fsize;
if(InputFile(filename,(void**)&buf,&fsize)){
if(CreateByteStream(stream,buf,fsize)){
ret = true;
}
delete[] buf;
}
return ret;
}
static UINT GetFrameCount(Gdiplus::Image* image){
UINT count = image->GetFrameDimensionsCount();
GUID* pDimensionIDs = new GUID[count];
image->GetFrameDimensionsList(pDimensionIDs, count);
count = image->GetFrameCount(&pDimensionIDs[0]);
delete [] pDimensionIDs;
return count;
}
unsigned int __stdcall thread(void* _params){
ThreadParams* params = (ThreadParams*)_params;
HDC hdc = GetDC(params->hWnd);
IStream* ist = NULL;
CreateByteStream(&ist,IMAGE_PATH);
Gdiplus::Graphics g(hdc);
Gdiplus::Image *image = new Gdiplus::Image(ist);
params->stream = ist;
params->image = image;
// GUID
Gdiplus::PropertyItem* pItem = NULL;
Gdiplus::Status status = Gdiplus::Ok;
// フレーム数
UINT frm_cnt = GetFrameCount(image);
// アニメ
if(frm_cnt>1){
// 時間間隔
UINT pItemSize = image->GetPropertyItemSize(PropertyTagFrameDelay);
pItem = (Gdiplus::PropertyItem*)(new BYTE[pItemSize]);
status = image->GetPropertyItem(PropertyTagFrameDelay, pItemSize, pItem);
UINT w = image->GetWidth();
UINT h = image->GetHeight();
Gdiplus::SolidBrush brush(Gdiplus::Color(255,255,255));
for(UINT frm=0;!params->fin;frm++){
if(frm>=frm_cnt)frm = 0;
// Frame切り替え
Gdiplus::Status status = image->SelectActiveFrame(&Gdiplus::FrameDimensionTime, frm);
for(int i=0;status==Gdiplus::ObjectBusy;i++){
Sleep(10);
status = image->SelectActiveFrame(&Gdiplus::FrameDimensionTime, frm);
}
// 描画
g.FillRectangle(&brush,0,0,w,h);
g.DrawImage(image,0,0);
// Sleep
Sleep(((long*)pItem->value)[frm] * 10);
}
}
// 非アニメ
else{
// 描画
g.DrawImage(image,0,0);
}
if(ist!=NULL)ist->Release();
ReleaseDC(params->hWnd,hdc);
return 0;
}