Gif is flickering

2019-08-09 13:08发布

问题:

I'm making a window in c++win32 api. And I'm showing gif using gdi+. And this gif is loaded from resource and making IStream*. But when I used not path to gif, and used IStream for creating GDIPlus::Image, and showed it, the gif started to flick. P.S. The window is SW_SHOWMAXIMIZED

I have tried to return 1 in WM_ERASEBKGND message, making NULL WNDCLASSEX.hbrBackGround, use InvalidateRect(hwnd,&rc, FALSE) P.S. rc is

RECT rc;
GetWindowRect(hwnd,&rc);

But it didn't worked;

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);

I expected that gif will show without problem, but it's flickering.

And here it says that animated Invalid gif or not an animated gif

Code:


#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;
}

回答1:

You should not draw directly in the timer handler; paint in WM_PAINT.

But beyond this to be totally flicker free you want to double-buffer. Make the window class not have a background brush, make an offscreen bitmap the size of the window's client area, and on frame changes paint whatever background you want for the window first then the current frame of animation to the offscreen bitmap, "the back buffer", then all the WM_PAINT handler has to do is paint whatever is in the back buffer to the screen. The WM_PAINT handler does not need to know anything about the state of animation etc.

Minimal code below:

#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;
}