使用gdiplus顯示gif圖片

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章