使用gdiplus显示gif图片
需求
- 在没有MFC上下文的windows环境下实现gif图片的显示;
- 可以根据文件名来显示gif图片;
- gif图片集成到可执行程序中。
实现思路
windows api并不支持gif图片的显示,不过从XP之后,windows自带的库gdi++支持gif、png等各种格式的图片。因此,这里使用了gdi++的库来实现。
想要把gif图片集成到可执行程序中,可以借助windows的资源文件实现。这样在编译时gif图片被集成到exe文件中,exe文件可以独立执行。
在将gif图片作为资源文件导入时,资源类型选择Bitmap,这样导入到rc文件后,资源文件增加的内容如下:
IDB_BITMAP1 GIF "360setup.gif"
代码
gif.cpp
这个文件包含两个函数,一个是实现gif图片显示的函数ShowImage,另一个函数是LoadImageFromIDResource 实现从资源文件加载gif图片,如果只需要根据文件名读取gif图片,可以不需要这个函数。
#include <gdiplus.h>
using namespace Gdiplus;
BOOL LoadImageFromIDResource(UINT nID, Image* & pImg)
{
HRSRC hRsrc = ::FindResource(NULL, MAKEINTRESOURCE(nID), L"GIF"); // type
if (!hRsrc)
return FALSE;
// load resource into memory
DWORD len = SizeofResource(NULL, hRsrc);
BYTE* lpRsrc = (BYTE*)LoadResource(NULL, hRsrc);
if (!lpRsrc)
return FALSE;
// Allocate global memory on which to create stream
HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len);
BYTE* pmem = (BYTE*)GlobalLock(m_hMem);
memcpy(pmem, lpRsrc, len);
IStream* pstm;
CreateStreamOnHGlobal(m_hMem, FALSE, &pstm);
// load from stream
pImg = Gdiplus::Image::FromStream(pstm);
// free/release stuff
GlobalUnlock(m_hMem);
pstm->Release();
FreeResource(lpRsrc);
return TRUE;
}
void _cdecl ShowImage(HWND hWnd)
{
HDC hdc = GetDC(hWnd);
Image *image; // = new Image(L"360setup.gif");
ImageFromIDResource(IDB_BITMAP1, image);
UINT 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, 20, 20, image->GetWidth(), image->GetHeight());
image->SelectActiveFrame(&Guid, fcount++);
if (fcount == frameCount)
fcount = 0;
long lPause = ((long*)pItem->value)[fcount] * 10;
Sleep(lPause);
}
ReleaseDC(hWnd, hdc);
}
main_window.h
这个文件封装了利用windows api构建窗口的操作,其中初始化gdi++库的操作放在了窗口的构造函数中,关闭gdi++库的操作放在了窗口的析构函数中。
std::shared_ptr<InstallMainWindow> g_main_window;
class InstallMainWindow
{
public:
static InstallMainWindow * GetMainWindow();
int Execute(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
~InstallMainWindow();
HWND GetMainWindowHwnd() const;
InstallMainWindow(const InstallMainWindow& other);
InstallMainWindow& operator=(const InstallMainWindow& other);
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
private:
InstallMainWindow();
private:
HWND main_window_;
ULONG_PTR gdiplusToken_;
Gdiplus::GdiplusStartupInput gdiplusStartupInput_;
};
main_window.cpp
InstallMainWindow类的实现,采用了单例模式封装主窗口的构造过程,在窗口响应过程函数中,增加对WM_SIZE消息的响应,目的是实现去除窗口的边框和标题栏。
在WM_PAINT消息中增加SetLayeredWindowAttributes的目的是实现对窗口透明的控制,需要配合属性WS_EX_LAYERED使用。
#include "main_window.h"
#include "interface_constants.h"
#include "gif.h"
#include <thread>
InstallMainWindow * InstallMainWindow::GetMainWindow()
{
static InstallMainWindow* main_window = new InstallMainWindow();
return main_window;
}
InstallMainWindow::InstallMainWindow()
{
Gdiplus::GdiplusStartup(&gdiplusToken_, &gdiplusStartupInput_, NULL);
}
InstallMainWindow::~InstallMainWindow()
{
Gdiplus::GdiplusShutdown(gdiplusToken_);
}
HWND InstallMainWindow::GetMainWindowHwnd() const
{
return main_window_;
}
int InstallMainWindow::Execute(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wcex.lpszMenuName = NULL;
// If forget to set this attribute, the program will throw an exception.
wcex.lpszClassName = kWindowClassName;
wcex.hIconSm = NULL;
// Register the window
::RegisterClassEx(&wcex);
DWORD main_window_style = WS_OVERLAPPEDWINDOW;
main_window_ = ::CreateWindow(L"main_window", L"", main_window_style, 200, 200,
680, 400, NULL, NULL, hInstance, NULL);
if (!main_window_)
return FALSE;
::ShowWindow(main_window_, nCmdShow);
::UpdateWindow(main_window_);
LONG nRet = ::GetWindowLong(main_window_, GWL_EXSTYLE);
nRet = nRet | WS_EX_LAYERED;
::SetWindowLong(main_window_, GWL_EXSTYLE, nRet);
MSG msg;
while (::GetMessage(&msg, 0, 0, 0))
{
::TranslateMessage(&msg); // Translate keyboard message
::DispatchMessage(&msg); // Dispatch message to the corresponding window
}
return (int)msg.wParam;
}
LRESULT CALLBACK InstallMainWindow::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hDC;
static bool first_gif_flag = true;
static HBRUSH hBrush;
switch (message)
{
case WM_SIZE:
{
LONG_PTR Style = ::GetWindowLongPtr(hWnd, GWL_STYLE);
Style = Style & ~WS_CAPTION &~WS_SYSMENU &~WS_SIZEBOX;
::SetWindowLongPtr(hWnd, GWL_STYLE, Style);
return 0;
}
case WM_PAINT:
{
::SetLayeredWindowAttributes(g_main_window->GetMainWindowHwnd(), 0, 123, LWA_ALPHA);
hDC = ::BeginPaint(g_main_window->GetMainWindowHwnd(), &ps);
if (first_gif_flag) {
first_gif_flag = false; // To avoid too many threads.
std::thread gif_thread(showimage, g_main_window->GetMainWindowHwnd());
gif_thread.detach();
}
::EndPaint(g_main_window->GetMainWindowHwnd(), &ps);
return 0;
}
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
}
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
InstallMainWindow::InstallMainWindow(const InstallMainWindow& other)
{
main_window_ = other.main_window_;
}
InstallMainWindow& InstallMainWindow::operator=(const InstallMainWindow& other)
{
if (this != &other)
{
main_window_ = other.main_window_;
}
return *this;
}
main.cpp
这里加入了两个编译控制行,第一个是为了实现禁止命令行窗口显示;第二个是使用gdi++库所必须的。
#include "main_window.h"
#include "interface_constants.h"
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
#pragma comment(lib,"GdiPlus.lib")
int main()
{
HINSTANCE h_instance = 0;
install_interface::g_main_window.reset(install_interface::InstallMainWindow::GetMainWindow());
install_interface::g_main_window->Execute(h_instance, h_instance, NULL, SW_SHOWNORMAL);
return 0;
}
实现过程中遇到的问题
1. GdiplusTypes.h(479,22): error: use of undeclared identifier ‘min’
解决办法:
#ifndef min
#define min
#endif
#ifndef max
#define max
#endif
#include <gdiplus.h>
在声明gdiplus.h之前增加对min和max的宏处理,需要注意在每一处声明该头文件的地方都需要这样处理。
2. std::numeric_limits::max()
解决办法:
#ifndef min
#define min
#endif
#ifndef max
#define max
#endif
#include <gdiplus.h>
#undef min
#undef max
在声明gdiplus.h之后,取消对max和min的宏处理。
3. gif图片加载后只显示第一页
问题原因:当把gif加入到VS的资源文件夹后,文件大小由900多kb变为只有40多kb,丢失了大量的数据。
解决办法:用正常的gif文件替换损坏的gif文件,重新编译可正常显示gif动图。