使用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動圖。