今天心情很嗨皮,原因是花了5块钱买了一张刮刮彩,给中了100快,喜悦感可谓是油然而生吧,好了进入正题吧。关于GDI+的介绍在这里就不谈了,总之相比GDI确实方便了太多,比如首先GDI仅仅支持bmp格式的位图,对其他格式比如jpg,png,gif.....都很吃力了,而用GDI+确全部支持,而且用起来十分顺手,比如放置一幅jpg图片:
Image image(L"Texture1.jpg"); graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());
当然还需要构造一个graphics对象,也很方便:
Graphics graphics(hdc);
Microsoft Windows GDI+包含大约40个类、50个枚举和6个结构体。同时也有少数几个函数不属于任何类。Graphics类是整个GDI+接口的核心;它是实际进行线条、曲线、图形、图象和文本绘制的类。
多数类和Graphics类配合使用。例如,Graphics::DrawLine方法接受一个点传给一个Pen对象,该对象保存了即将绘制的线条的属性(颜色、宽度、虚线类型及其他)。Graphics::FillRectangle方法接受一个点传给LinearGradientBrush对象,该对象协同Graphics对象实现矩形的渐变色填充。Font和StringFormat对象影响到Graphics对象绘制文本的方式。Matrix对象用于存储和生成一个Graphics对象的世界变换矩阵,用于旋转、缩放和翻转图象。
至于想要知道更具体的就去找你度娘跟谷歌吧,初次接触GDI+让我兴奋的是他的线性渐变跟路径渐变,图像旋转图像平移,用这些弄出来的画面十分好看的,而且操作也十分容易。。。。。。。拍马屁的话不多说了,也不是我的风格,大家接触了就明了了。
说到支持gif,我赶紧就去测试了一把,就把上面代码中的jpg换做gif就OK了,如果gif是静态的,那完全满足要求,如果是动态的,那运行结果有点失望,现实的是静态的,说准确点就是该gif图片的第一个帧,哪如何加载动态gif呢,首先想到的是分析gif格式,网上也能够找到一大堆资料,不过看着还蛮费劲的,看过之后分析,知道gif实际上是有很多歌帧组成的,我们可以一个接一个的来显示出来,从而整体看起来就是一个符合要求的结果。
首先判断gif有多少个帧,用到如下几个API:
Image *image=new Image(L"123.gif"); UINT count=0; count=image->GetFrameDimensionsCount(); GUID *pDimensionIDs=(GUID*)new GUID[count]; image->GetFrameDimensionsList(pDimensionIDs,count); WCHAR strGuid[39]; StringFromGUID2(pDimensionIDs[0],strGuid,39); UINT frameCount=image->GetFrameCount(&pDimensionIDs[0]); delete []pDimensionIDs;
得到帧的数量之后,其实根据数量我们也能判断出来这个gif是否是动态的,是否大于1就是了,数量判断完毕接着就该判断帧的时间了吧,所谓的延迟。
// 假设图像具有属性条目 PropertyItemEquipMake. // 获取此条目的大小. int nSize = GetPropertyItemSize(PropertyTagFrameDelay); // 为属性条目分配空间. m_pPropertyItem = (PropertyItem*) malloc(nSize); GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem); m_pPropertyItem->value 是一个长整形数组, 每个长整形代表每帧的延时。由于获取的属性不同,GetPropertyItem会获得不同大小的对象, 因此要由用户来获得的对象大小,开辟与删除 GetPropertyItem相关的内存。对象的大小是通过GetPropertyItemSize 获取的,其参数是你所感兴趣的属性条目。 一旦获取了帧的数量与延时,我们就可生成一个线程来调用 DrawFrameGIF()来显示。
起初我没有用到线程,我认为用线程不好控制(个人对线程的应用少之又少),反倒是写了一个单独的函数放到了WM_PAINT中,大概是这样的:
void showimage(HDC hdc) { Image *image=new Image(L"123.gif"); UINT count=0; count=image->GetFrameDimensionsCount(); GUID *pDimensionIDs=(GUID*)new GUID[count]; image->GetFrameDimensionsList(pDimensionIDs,count); WCHAR strGuid[39]; StringFromGUID2(pDimensionIDs[0],strGuid,39); UINT frameCount=image->GetFrameCount(&pDimensionIDs[0]); delete []pDimensionIDs; int size=image->GetPropertyItemSize(PropertyTagFrameDelay); PropertyItem* pItem=NULL; pItem=(PropertyItem*)malloc(size); image->GetPropertyItem(PropertyTagFrameDelay,size,pItem); UINT fcount=0; GUID Guid=FrameDimensionTime; while(true) { Graphics graphics(hdc); graphics.DrawImage(image,0,0,image->GetWidth(),image->GetHeight()); image->SelectActiveFrame(&Guid,fcount++); if(fcount==frameCount) fcount=0; long lPause=((long*)pItem->value)[fcount]*10; Sleep(lPause); } }
放到了WM_PAINT消息中,但是运行发现动态gif确实显示出来了,但是当程序窗口失去焦点进而发生重绘的时候发现图片就不会动了,最小最大化也是如此,可能就是那个死循环从中作怪吧,此时又想到了多线程来完成,于是改动了一下代码,应该是这样的:
void __cdecl showimage(LPVOID) { HDC hdc=GetDC(g_hWnd); Image *image=new Image(L"123.gif"); UINT count=0; count=image->GetFrameDimensionsCount(); GUID *pDimensionIDs=(GUID*)new GUID[count]; image->GetFrameDimensionsList(pDimensionIDs,count); WCHAR strGuid[39]; StringFromGUID2(pDimensionIDs[0],strGuid,39); UINT frameCount=image->GetFrameCount(&pDimensionIDs[0]); delete []pDimensionIDs; int size=image->GetPropertyItemSize(PropertyTagFrameDelay); PropertyItem* pItem=NULL; pItem=(PropertyItem*)malloc(size); image->GetPropertyItem(PropertyTagFrameDelay,size,pItem); UINT fcount=0; GUID Guid=FrameDimensionTime; while(true) { Graphics graphics(hdc); graphics.DrawImage(image,0,0,image->GetWidth(),image->GetHeight()); image->SelectActiveFrame(&Guid,fcount++); if(fcount==frameCount) fcount=0; long lPause=((long*)pItem->value)[fcount]*10; Sleep(lPause); } ReleaseDC(g_hWnd,hdc); }
case WM_PAINT: hdc = BeginPaint(hWnd, &ps); // TODO: Add any drawing code here... RECT rt; GetClientRect(hWnd, &rt); _beginthread(showimage,0,0); //showimage(hdc); EndPaint(hWnd, &ps); break;
运行发现此时上述的那个重绘时遇到的图片假死已经不复存在了,这个时候表明看起来没什么问题了,但是仔细看发现gif图片交换的频率好像快了不少(跟原来的123.gif进行了肉眼对比),显的跟原版图片还是有点差距的,总之是不太理想,原因大致查了一下,可能是线程还没执行玩就 EndPaint(hWnd, &ps);了
最终跟网友交流方法,说到了定时器的思路,大体一想,确实定时器可以做到,思路:
开一个定时器。然后取gif当前帧到下一帧的时间间隔,做为定时器的间隔(最小100ms)。(定时器只用一次)。每次定时器到了。就把活动帧设到下一帧,然后触发窗口重绘。
剩下的。你只需要在wm_paint里面,把gif的当前帧画出来就好了.
大概流程:
1.取帧间隔,开定时器。
2.定时器到。杀掉定时器,选中下一帧,再取帧间隔。
3.触发窗口重绘。
按照这个思路终于算是比较完美的实现了gif动态图片的加载,主要代码大致这样的:
//定义2个ID #define TIMER_FIR 1 #define TIMER_SEC 2 //核心工作 case WM_CREATE: SetTimer(hWnd,TIMER_FIR,0,NULL); break; case WM_TIMER: { switch(wParam) { case TIMER_FIR: { hdc=GetDC(hWnd); image=new Image(L"123.gif"); count=0; count=image->GetFrameDimensionsCount(); pDimensionIDs=(GUID*)new GUID[count]; image->GetFrameDimensionsList(pDimensionIDs,count); StringFromGUID2(pDimensionIDs[0],strGuid,39); frameCount=image->GetFrameCount(&pDimensionIDs[0]); delete []pDimensionIDs; size=image->GetPropertyItemSize(PropertyTagFrameDelay); //PropertyItem* pItem=NULL; // pItem=(PropertyItem*)malloc(size); pItem=(PropertyItem*)new PropertyItem[size]; image->GetPropertyItem(PropertyTagFrameDelay,size,pItem); fcount=0; Guid=FrameDimensionTime; Graphics graphics(hdc); graphics.DrawImage(image,0,0); image->SelectActiveFrame(&Guid,fcount++); if(fcount==frameCount) fcount=0; lPause=((long*)pItem->value)[fcount]*10; //ReleaseDC(hWnd,hdc); KillTimer (hWnd, TIMER_FIR) ; SetTimer(hWnd,TIMER_SEC,lPause,NULL); InvalidateRect (hWnd, NULL, FALSE) ; break; } case TIMER_SEC: { image->SelectActiveFrame(&Guid,fcount++); if(fcount==frameCount) fcount=0; lPause=((long*)pItem->value)[fcount]*10; KillTimer(hWnd,TIMER_SEC); SetTimer(hWnd,TIMER_SEC,lPause,NULL); InvalidateRect (hWnd, NULL, FALSE) ; } } }
主要是分享一下这个问题的解决思路,免得后面的人又绕弯路,这样就不太好了。
贴一张运行效果图: