65.9K
CodeProject 正在变化。 阅读更多。
Home

使用GDI+添加GIF动画

2002 年 1 月 30 日

3分钟阅读

viewsIcon

485870

downloadIcon

9539

Norm演示了如何强制GDI+显示动画GIF。

动机

有多少开发者在使用 GDI+?我估计,没多少。作为一名经验丰富的 Windows C++ 程序员,为桌面应用程序开发,我一直在定期使用古老的 GDI。自从 GDI+ 发布以来(参见 (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp)),我一直在新应用程序中使用 GDI+,并且也将现有应用程序转换为使用 GDI+。有很多令人信服的理由说明您应该使用 GDI+,这里列出了一些

  • GDI 的后继者
  • 与 .NET 兼容
  • 优化了许多 GDI 的功能
  • 支持:渐变画笔、独立路径对象、变换和矩阵对象、可缩放区域、Alpha 混合以及对多种图像格式的支持
    • BMP
    • GIF
    • JPEG
    • Exif
    • PNG
    • TIFF
    • ICON
    • WMF
    • EMF

在开发基于 Windows GUI 的 GDI+ 应用程序时,我遇到了需要显示动画 GIF 的情况,虽然 GDI+ 不直接支持显示动画 GIF,但只需编写少量代码即可实现。

实现

首先,让我们看看使用 GDI+ 绘制一个简单的图像。

void CMyWnd::OnPaint()
{
	CPaintDC dc(this);
	
	Graphics graphics(&dc); // Create a GDI+ graphics object

	Image image(L"Test.Gif"); // Construct an image
	
	graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());	
		
}

我们立刻可以看到使用 GDI 对象的简化 C++ 接口。这确保了使用 GDI 对象的一种方式。无需使用 SelectObject 来选择进出设备上下文的 GDI 对象。

对于不熟悉动画 GIF 格式的读者来说,动画 GIF 实际上是一系列 GIF 帧,每个帧都有一个相关的延迟时间,因此每个帧可以有不同的延迟时间。

我的实现封装了动画 GIF 的功能,从 GDI+ 的 Image 类派生出一个名为 ImageEx 的类。第一步是确定 GIF 是否为动画类型。以下代码演示了这一点

bool ImageEx::TestForAnimatedGIF()
{
	UINT count = 0;
	count = GetFrameDimensionsCount();
	GUID* pDimensionIDs = new GUID[count];

	// Get the list of frame dimensions from the Image object.
	GetFrameDimensionsList(pDimensionIDs, count);

	// Get the number of frames in the first dimension.
	m_nFrameCount = GetFrameCount(&pDimensionIDs[0]);

	// Assume that the image has a property item of type PropertyItemEquipMake.
	// Get the size of that property item.
	int nSize = GetPropertyItemSize(PropertyTagFrameDelay);

	// Allocate a buffer to receive the property item.
	m_pPropertyItem = (PropertyItem*) malloc(nSize);

	GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem);


	delete pDimensionIDs;

	return m_nFrameCount > 1;
}

m_pPropertyItem->value 实际上是指向一个长整型数组的指针,每个长整型代表一个延迟时间,对应一个 GIF 帧。因为 GetPropertyItem 返回的大小取决于您感兴趣的属性,所以需要一个 Size,并且程序员有责任分配和释放与 GetPropertyItem 关联的内存。大小是通过调用 GetPropertyItemSize 来确定的,提供您感兴趣的属性标记。

一旦从图像中检索到帧数和延迟时间,就会创建一个线程,该线程调用 DrawFrameGIF 直到对象被销毁。请参见下面的 DrawFrameGIF

bool ImageEx::DrawFrameGIF()
{

	::WaitForSingleObject(m_hPause, INFINITE);

	GUID pageGuid = FrameDimensionTime;

	long hmWidth = GetWidth();
	long hmHeight = GetHeight();

	HDC hDC = GetDC(m_hWnd);
	if (hDC)
	{
		Graphics graphics(hDC);
		graphics.DrawImage(this, m_rc.left, m_rc.top, hmWidth, hmHeight);
		ReleaseDC(m_hWnd, hDC);
	}

	SelectActiveFrame(&pageGuid, m_nFramePosition++); 

	if (m_nFramePosition == m_nFrameCount)
		m_nFramePosition = 0;


	long lPause = ((long*) m_pPropertyItem->value)[m_nFramePosition] * 10;

	DWORD dwErr = WaitForSingleObject(m_hExitEvent, lPause);

	return dwErr == WAIT_OBJECT_0;
}

这个类的另一个有趣的方面是它能够直接从可执行文件将图像作为资源加载。我通常将我的 GIF 导入到项目中,并为其提供 "GIF" 资源类型,然后将资源 ID 从数字常量重命名为字符串(请参见示例代码)。

结论

如果您熟悉 GDI 的概念和编程,GDI+ 提供了一些高级功能,并且接口几乎镜像了 .NET GDI 命名空间。示例应用程序和代码包括用于在您的代码应用程序中实现动画 GIF 的完整列表。对于代码参考,只需在代码中搜索 GDI+ 即可。我这篇文章中没有包括的一件事是启动和关闭 GDI+ 子系统。

© . All rights reserved.