Gif闪烁 [英] Gif is flickering

查看:134
本文介绍了Gif闪烁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

我试图在WM_ERASEBKGND消息中返回1,使NULL WNDCLASSEX.hbrBackGround使用InvalidateRect(hwnd,& rc,FALSE) P.S. rc是

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

但是没有用;

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不会出现问题,但它会闪烁.

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

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

And here it says that animated Invalid gif or not an animated 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中绘画.

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

但是要完全消除闪烁,您还需要双缓冲.使窗口类不具有背景画笔,使窗口位图的大小与窗口工作区的大小相同,并在帧上更改,首先为窗口绘制所需的背景,然后将动画的当前帧绘制到屏幕外的位图,后缓冲区" ",那么WM_PAINT处理程序所要做的就是将屏幕后台缓冲区中的内容绘制出来. 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.

下面的最小代码:

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

这篇关于Gif闪烁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆