vc6.0中用GDIPlus实现加载动态gif图片(非MFC实现)

来源:互联网 发布:培训java费用 编辑:程序博客网 时间:2024/05/16 11:19
 

今天心情很嗨皮,原因是花了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) ;    }      }    }

主要是分享一下这个问题的解决思路,免得后面的人又绕弯路,这样就不太好了。

贴一张运行效果图:


完整的工程已经上传到了CSDN资源区了,但是不晓得什么原因现在还找不到地址,稍后会附上附近的下载地址(等CSDN的消息吧)。最后欢迎大家交流学习。
原创粉丝点击