Gif在闪烁

Gif is flickering

我正在用 c++win32 api 制作一个 window。我正在使用 gdi+ 显示 gif。这个 gif 是从资源加载并制作 IStream*。但是当我不使用 gif 的路径,并使用 IStream 创建 GDIPlus::Image,并显示它时,gif 开始闪烁。 P.S。 window 是 SW_SHOWMAXIMIZED

我已经尝试 return 1 in WM_ERASEBKGND message, making NULL WNDCLASSEX.hbrBackGround, use InvalidateRect(hwnd,&rc, FALSE) P.S。 rc 是

RECT rc;
GetWindowRect(hwnd,&rc);

但是没有用;

case WM_TIMER:
        if (wParam == DRAW_ANIM)
        {
            pImg->SelectActiveFrame(&FrameDimensionTime, nFrm);
            const Rect DRC(0, 0, pImg->GetWidth(), pImg->GetHeight());
            pGphcs->Clear(Color(128, 128, 128));

            pGphcs->DrawImage(pImg, DRC);
            RECT rt;
            GetWindowRect(hwnd, &rt);
            InvalidateRect(hwnd, &rt, FALSE);


            if (nFrm < (nFrmCnt - 1)) nFrm++; else nFrm = 0;

            InvalidateRect(hwnd, &rt, FALSE);
        }

        break;

hMWDC = GetDC(hWnd);
    pGphcs = new Graphics(hMWDC);

    HMODULE hMod = GetModuleHandle(NULL);
    HRSRC hRes = FindResource(hMod, MAKEINTRESOURCEW(MY_GIF_ID), RT_RCDATA);
    if (!hRes) MessageBox(NULL, L"hRes!!", L"ERROR", 0);
    HGLOBAL hGlobal = LoadResource(hMod, hRes);
    if (!hGlobal)MessageBox(NULL, L"hGlobal!!", L"ERROR", 0);
    void* pResData = LockResource(hGlobal);
    if (!pResData) MessageBox(NULL, L"pResData!!", L"ERROR", 0);

    DWORD dwResData = SizeofResource(hMod, hRes);

    IStream* pStream = SHCreateMemStream((BYTE*)pResData, dwResData);
    if (!pStream) MessageBox(NULL, L"pStream!!", L"ERROR", 0);

    pImg = new Image(pStream,1);
    pStream->Release();


    nFrmCnt = pImg->GetFrameCount(&FrameDimensionTime);
    SetTimer(hWnd, DRAW_ANIM, 500, NULL);

我预计 gif 可以正常显示,但它在闪烁。

这里说动画无效 gif 或不是动画 gif

代码:


#include <shlwapi.h>
#include "Resource.h"
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <algorithm>
#include <windows.h>
#include <objidl.h>
#include <GdiPlus.h>
#include <gdiplusimaging.h>
using namespace std;
using namespace Gdiplus;
#pragma comment (lib, "gdiplus.lib")
#pragma comment (lib, "shlwapi.lib")
#define DRAW_ANIM   1

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


static HFONT s_hFont = NULL;
static HWND hWnd;

static HDC hMWDC;

static Graphics* pGphcs = NULL;
static Image* pImg = NULL;
static unsigned int nFrm = 0, nFrmCnt = 0;



int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    hInstance = GetModuleHandle(NULL);

    MSG msg;
    WNDCLASSEX wc;

    ULONG_PTR gdipToken;
    GdiplusStartupInput gdipStartupInput;

    wc.cbClsExtra = 0;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbWndExtra = 0;
    //wc.hbrBackground = (HBRUSH)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_BITMAP1), IMAGE_BITMAP, 0, 0,
        //LR_CREATEDIBSECTION);

    wc.hbrBackground = NULL;
    wc.hCursor = LoadCursor(0, IDC_HAND);
    wc.hIcon = LoadIcon(0, IDI_QUESTION);
    wc.hIconSm = LoadIcon(0, IDI_INFORMATION);

    wc.hInstance = hInstance;
    wc.lpfnWndProc = WndProc;
    wc.lpszClassName = L"GIF";
    wc.lpszMenuName = 0;
    wc.style = 0;
    wc.cbClsExtra = 0;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbWndExtra = 0;
    //wc.hbrBackground = (HBRUSH)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDB_BITMAP1), IMAGE_BITMAP, 0, 0,
        //LR_CREATEDIBSECTION);



    if (!RegisterClassEx(&wc))
    {
        MessageBoxA(0, "FAILED MESSSAGE", "FAILED", MB_OK);
    }

    GdiplusStartup(&gdipToken, &gdipStartupInput, 0);


    HMODULE hMod = GetModuleHandle(NULL);
    HRSRC hRes = FindResource(hMod, MAKEINTRESOURCEW(MY_GIF_ID), RT_RCDATA);
    if (!hRes) MessageBox(NULL, L"hRes!!", L"ERROR", 0);
    HGLOBAL hGlobal = LoadResource(hMod, hRes);
    if (!hGlobal)MessageBox(NULL, L"hGlobal!!", L"ERROR", 0);
    void* pResData = LockResource(hGlobal);
    if (!pResData) MessageBox(NULL, L"pResData!!", L"ERROR", 0);

    DWORD dwResData = SizeofResource(hMod, hRes);

    IStream* pStream = SHCreateMemStream((BYTE*)pResData, dwResData);
    if (!pStream) MessageBox(NULL, L"pStream!!", L"ERROR", 0);

    pImg = new Image(pStream, 1);
    pStream->Release();


    hWnd = CreateWindow(

        L"GIF",
        L"",
        WS_EX_TOPMOST | WS_CLIPCHILDREN,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance,
        &pImg);

    if (hWnd == NULL) {
        MessageBoxA(0, "FAILED HWND", "FAILED", MB_OK);
    }




    ShowWindow(hWnd, SW_SHOWMAXIMIZED);
    UpdateWindow(hWnd);



    while (GetMessage(&msg, NULL, 0, 0) > 0) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);


    }

    GdiplusShutdown(gdipToken);
    return msg.wParam;
}

std::vector<unsigned int> LoadGifFrameInfo(Image* image)
{
    // I think animated gifs will always only have 1 frame dimension...
    // the "dimension" being the frame count, but I could be wrong about this
    int count = image->GetFrameDimensionsCount();
    if (count != 1)
        return std::vector<unsigned int>();

    GUID guid;
    if (image->GetFrameDimensionsList(&guid, 1) != 0)
        return std::vector<unsigned int>();
    int frame_count = image->GetFrameCount(&guid);

    auto sz = image->GetPropertyItemSize(PropertyTagFrameDelay);
    if (sz == 0)
        return std::vector<unsigned int>();

    // copy the frame delay property into the buffer backing an std::vector
    // of bytes and then get a pointer to its value, which will be an array of 
    // unsigned ints
    std::vector<unsigned char> buffer(sz);
    PropertyItem* property_item = reinterpret_cast<PropertyItem*>(&buffer[0]);
    image->GetPropertyItem(PropertyTagFrameDelay, sz, property_item);
    unsigned int* frame_delay_array = (unsigned int*)property_item[0].value;

    // copy the delay values into an std::vector while converting to milliseconds.
    std::vector<unsigned int> frame_delays(frame_count);
    std::transform(frame_delay_array, frame_delay_array + frame_count, frame_delays.begin(),
        [](unsigned int n) {return n * 10; }
    );

    return frame_delays;
}

void GenerateFrame(Bitmap* bmp, Image* gif)
{
    Graphics dest(bmp);

    SolidBrush white(Color::White);
    dest.FillRectangle(&white, 0, 0, bmp->GetWidth(), bmp->GetHeight());

    if (gif)
        dest.DrawImage(gif, 0, 0);
}

std::unique_ptr<Bitmap> CreateBackBuffer(HWND hWnd)
{
    RECT r;
    GetClientRect(hWnd, &r);
    return std::make_unique<Bitmap>(r.right - r.left, r.bottom - r.top);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

    static Image* animated_gif;
    static std::unique_ptr<Bitmap> back_buffer;
    static std::vector<unsigned int> frame_delays;
    static int current_frame;

    switch (msg) {

    case WM_CREATE:
    {
        animated_gif = reinterpret_cast<Image*>(
            reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams
            );

        if (!animated_gif || animated_gif->GetLastStatus() != 0) {
            MessageBox(hWnd, L"Unable to load animated gif", L"error", MB_ICONERROR);
            return 0;
        }

        // Create a bitmap the size of the window's clent area
        back_buffer = CreateBackBuffer(hWnd);

        // get the frame delays and thereby test that this is really an animated gif
        frame_delays = LoadGifFrameInfo(animated_gif);
        if (frame_delays.empty()) {
            MessageBox(hWnd, L"Invalid gif or not an animated gif", L"error", MB_ICONERROR);
            return 0;
        }

        current_frame = 0;
        animated_gif->SelectActiveFrame(&FrameDimensionTime, current_frame);

        GenerateFrame(back_buffer.get(), animated_gif);

        SetTimer(hWnd, DRAW_ANIM, frame_delays[0], nullptr);
        InvalidateRect(hWnd, nullptr, FALSE);


    }break;

    case WM_TIMER:
    {
        KillTimer(hWnd, DRAW_ANIM);
        current_frame = (current_frame + 1) % frame_delays.size();
        animated_gif->SelectActiveFrame(&FrameDimensionTime, current_frame);
        GenerateFrame(back_buffer.get(), animated_gif);
        SetTimer(hWnd, DRAW_ANIM, frame_delays[current_frame], nullptr);
        InvalidateRect(hWnd, nullptr, FALSE);


    }break;
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        Graphics g(hdc);
        g.DrawImage(back_buffer.get(), 0, 0);
        EndPaint(hWnd, &ps);
    } break;

    case WM_SIZE: {
        back_buffer = CreateBackBuffer(hWnd);
        GenerateFrame(back_buffer.get(), animated_gif);
    } break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:


        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

您不应该直接在计时器处理程序中绘制;在 WM_PAINT 中绘画。

但除此之外,您还想 double-buffer 完全无闪烁。使 window class 没有背景画笔,使屏幕外位图的大小与 window 的客户区相同,并在框架更改时为 [=23 绘制您想要的任何背景=] 首先,然后将当前动画帧绘制到屏幕外位图 "the back buffer",然后 WM_PAINT 处理程序所要做的就是将后台缓冲区中的任何内容绘制到屏幕上。 WM_PAINT 处理程序不需要知道任何关于动画状态等的信息。

下面的最小代码:

#include <memory>
#include <vector>
#include <algorithm>
#include <windows.h>
#include <objidl.h>
#include <GdiPlus.h>
#include <gdiplusimaging.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

#define TIMER_ID 101

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    ULONG_PTR m_gdiplusToken;
    GdiplusStartupInput gdiplusStartupInput;
    GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

    Image gif((lpCmdLine) ? lpCmdLine : _T("sample.gif"));

    MSG msg = { 0 };
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.hbrBackground = NULL; // <= Do not provide a background brush.
    wc.lpszClassName = L"anim_gif_player";
    if (!RegisterClass(&wc))
        return -1;

    if (!CreateWindow(wc.lpszClassName,
        L"Animated GIF player",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        0, 0, 640, 480, 0, 0, hInstance, &gif))
        return -2;

    while (GetMessage(&msg, NULL, 0, 0) > 0)
        DispatchMessage(&msg);

    return 0;
}

std::vector<unsigned int> LoadGifFrameInfo(Image* image)
{
    // I think animated gifs will always only have 1 frame dimension...
    // the "dimension" being the frame count, but I could be wrong about this
    int count = image->GetFrameDimensionsCount();
    if (count != 1)
        return std::vector<unsigned int>(); 

    GUID guid;
    if (image->GetFrameDimensionsList(&guid, 1) != 0)
        return std::vector<unsigned int>();
    int frame_count = image->GetFrameCount(&guid);

    auto sz = image->GetPropertyItemSize(PropertyTagFrameDelay);
    if (sz == 0)
        return std::vector<unsigned int>();

    // copy the frame delay property into the buffer backing an std::vector
    // of bytes and then get a pointer to its value, which will be an array of 
    // unsigned ints
    std::vector<unsigned char> buffer(sz);
    PropertyItem* property_item = reinterpret_cast<PropertyItem*>(&buffer[0]);
    image->GetPropertyItem(PropertyTagFrameDelay, sz, property_item);
    unsigned int* frame_delay_array = (unsigned int*)property_item[0].value;

    // copy the delay values into an std::vector while converting to milliseconds.
    std::vector<unsigned int> frame_delays(frame_count);
    std::transform(frame_delay_array, frame_delay_array + frame_count, frame_delays.begin(), 
        [](unsigned int n) {return n * 10; } 
    );

    return frame_delays;
}

void GenerateFrame(Bitmap* bmp, Image* gif)
{
    Graphics dest(bmp);

    SolidBrush white(Color::White);
    dest.FillRectangle(&white, 0, 0, bmp->GetWidth(), bmp->GetHeight());

    if (gif)
        dest.DrawImage(gif, 0, 0);
}

std::unique_ptr<Bitmap> CreateBackBuffer(HWND hWnd)
{
    RECT r;
    GetClientRect(hWnd, &r);
    return std::make_unique<Bitmap>(r.right - r.left, r.bottom - r.top);
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    static Image* animated_gif;
    static std::unique_ptr<Bitmap> back_buffer;
    static std::vector<unsigned int> frame_delays;
    static int current_frame;

    switch (message) {
        case WM_CREATE: {
                animated_gif = reinterpret_cast<Image*>(
                    reinterpret_cast<CREATESTRUCT*>(lParam)->lpCreateParams
                );

                if (! animated_gif || animated_gif->GetLastStatus() != 0) {
                    MessageBox(hWnd, _T("Unable to load animated gif"), _T("error"), MB_ICONERROR);
                    return 0;
                }

                // Create a bitmap the size of the window's clent area
                back_buffer = CreateBackBuffer(hWnd);

                // get the frame delays and thereby test that this is really an animated gif
                frame_delays = LoadGifFrameInfo(animated_gif);
                if (frame_delays.empty()) {
                    MessageBox(hWnd, _T("Invalid gif or not an animated gif"), _T("error"),  MB_ICONERROR);
                    return 0;
                }

                current_frame = 0;
                animated_gif->SelectActiveFrame(&FrameDimensionTime, current_frame);

                GenerateFrame( back_buffer.get(), animated_gif );

                SetTimer(hWnd, TIMER_ID, frame_delays[0], nullptr);
                InvalidateRect(hWnd, nullptr, FALSE);
            }
            break;

        case WM_TIMER: {
            KillTimer(hWnd, TIMER_ID);
            current_frame = (current_frame + 1) % frame_delays.size();
            animated_gif->SelectActiveFrame(&FrameDimensionTime, current_frame);
            GenerateFrame(back_buffer.get(), animated_gif);
            SetTimer(hWnd, TIMER_ID, frame_delays[current_frame], nullptr);
            InvalidateRect(hWnd, nullptr, FALSE);
        } break;

        case WM_PAINT: {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            Graphics g(hdc);
            g.DrawImage(back_buffer.get(), 0, 0);
            EndPaint(hWnd, &ps);
        } break;

        case WM_SIZE: {
            back_buffer = CreateBackBuffer(hWnd);
            GenerateFrame(back_buffer.get(), animated_gif);
        } break;

        case WM_CLOSE:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}