使用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动图。

猜你喜欢

转载自blog.csdn.net/zhuiyuanqingya/article/details/80815801