2025-11-28 00:35:46 +09:00

1426 lines
47 KiB
C++

// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved
// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER // Allow use of features specific to Windows XP or later.
#define WINVER 0x0501 // Change this to the appropriate value to target other versions of Windows.
#endif
#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later.
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target other versions of Windows.
#endif
#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later.
#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
#endif
#ifndef _WIN32_IE // Allow use of features specific to IE 6.0 or later.
#define _WIN32_IE 0x0600 // Change this to the appropriate value to target other versions of IE.
#endif
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <wincodec.h>
#include <commdlg.h>
#include <d2d1.h>
#include "WICAnimatedGif.h"
const UINT DELAY_TIMER_ID = 1; // Global ID for the timer, only one timer is used
// Utility inline functions
template <typename T>
inline void SafeRelease(T *&pI)
{
if (NULL != pI)
{
pI->Release();
pI = NULL;
}
}
inline LONG RectWidth(RECT rc)
{
return rc.right - rc.left;
}
inline LONG RectHeight(RECT rc)
{
return rc.bottom - rc.top;
}
// Gif Animation Overview
// In order to play a gif animation, raw frames (which are compressed frames
// directly retrieved from the image file) and image metadata are loaded
// and used to compose the frames that are actually displayed in the animation
// loop (which we call composed frames in this sample). Composed frames have
// the same sizes as the global gif image size, while raw frames can have their own sizes.
//
// At the highest level, a gif animation contains a fixed or infinite number of animation
// loops, in which the animation will be displayed repeatedly frame by frame; once all
// loops are displayed, the animation will stop and the last frame will be displayed
// from that point.
//
// In each loop, first the entire composed frame will be initialized with the background
// color retrieved from the image metadata. The very first raw frame then will be loaded
// and directly overlaid onto the previous composed frame (i.e. in this case, the frame
// cleared with background color) to produce the first composed frame, and this frame
// will then be displayed for a period that equals its delay. For any raw frame after
// the first raw frame (if there are any), the composed frame will first be disposed based
// on the disposal method associated with the previous raw frame. Then the next raw frame
// will be loaded and overlaid onto the result (i.e. the composed frame after disposal).
// These two steps (i.e. disposing the previous frame and overlaying the current frame) together
// 'compose' the next frame to be displayed. The composed frame then gets displayed.
// This process continues until the last frame in a loop is reached.
//
// An exception is the zero delay intermediate frames, which are frames with 0 delay
// associated with them. These frames will be used to compose the next frame, but the
// difference is that the composed frame will not be displayed unless it's the last frame
// in the loop (i.e. we move immediately to composing the next composed frame).
/******************************************************************
* *
* WinMain *
* *
* Application entrypoint *
* *
******************************************************************/
int WINAPI wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR pszCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(pszCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
{
DemoApp app;
hr = app.Initialize(hInstance);
if (SUCCEEDED(hr))
{
// Main message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
CoUninitialize();
}
return 0;
}
/******************************************************************
* *
* DemoApp::DemoApp constructor *
* *
* Initializes member data *
* *
******************************************************************/
DemoApp::DemoApp()
:
m_hWnd(NULL),
m_pD2DFactory(NULL),
m_pHwndRT(NULL),
m_pFrameComposeRT(NULL),
m_pRawFrame(NULL),
m_pSavedFrame(NULL),
m_pIWICFactory(NULL),
m_pDecoder(NULL)
{
}
/******************************************************************
* *
* DemoApp::~DemoApp destructor *
* *
* Tears down resources *
* *
******************************************************************/
DemoApp::~DemoApp()
{
SafeRelease(m_pD2DFactory);
SafeRelease(m_pHwndRT);
SafeRelease(m_pFrameComposeRT);
SafeRelease(m_pRawFrame);
SafeRelease(m_pSavedFrame);
SafeRelease(m_pIWICFactory);
SafeRelease(m_pDecoder);
}
/******************************************************************
* *
* DemoApp::Initialize *
* *
* Creates application window and device-independent resources *
* *
******************************************************************/
HRESULT DemoApp::Initialize(HINSTANCE hInstance)
{
// Register window class
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DemoApp::s_WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = NULL;
wcex.lpszMenuName = MAKEINTRESOURCE(IDR_WICANIMATEDGIF);
wcex.lpszClassName = L"WICANIMATEDGIF";
wcex.hIconSm = NULL;
HRESULT hr = (RegisterClassEx(&wcex) == 0) ? E_FAIL : S_OK;
if (SUCCEEDED(hr))
{
// Create D2D factory
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
}
if (SUCCEEDED(hr))
{
// Create WIC factory
hr = CoCreateInstance(
CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_pIWICFactory));
}
if (SUCCEEDED(hr))
{
// Create window
m_hWnd = CreateWindow(
L"WICANIMATEDGIF",
L"WIC Animated Gif Sample",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
this);
hr = (m_hWnd == NULL) ? E_FAIL : S_OK;
}
if (SUCCEEDED(hr))
{
hr = SelectAndDisplayGif();
if (FAILED(hr))
{
DestroyWindow(m_hWnd);
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::CreateDeviceResources *
* *
* Creates a D2D hwnd render target for displaying gif frames *
* to users and a D2D bitmap render for composing frames. *
* *
******************************************************************/
HRESULT DemoApp::CreateDeviceResources()
{
HRESULT hr = S_OK;
RECT rcClient;
if (!GetClientRect(m_hWnd, &rcClient))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
if (m_pHwndRT == NULL)
{
// Create a D2D hwnd render target
D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties
= D2D1::RenderTargetProperties();
// Set the DPI to be the default system DPI to allow direct mapping
// between image pixels and desktop pixels in different system DPI
// settings
renderTargetProperties.dpiX = DEFAULT_DPI;
renderTargetProperties.dpiY = DEFAULT_DPI;
D2D1_HWND_RENDER_TARGET_PROPERTIES hwndRenderTragetproperties
= D2D1::HwndRenderTargetProperties(m_hWnd,
D2D1::SizeU(RectWidth(rcClient), RectHeight(rcClient)));
hr = m_pD2DFactory->CreateHwndRenderTarget(
renderTargetProperties,
hwndRenderTragetproperties,
&m_pHwndRT);
}
else
{
// We already have a hwnd render target, resize it to the window size
D2D1_SIZE_U size;
size.width = RectWidth(rcClient);
size.height = RectHeight(rcClient);
hr = m_pHwndRT->Resize(size);
}
}
if (SUCCEEDED(hr))
{
// Create a bitmap render target used to compose frames. Bitmap render
// targets cannot be resized, so we always recreate it.
SafeRelease(m_pFrameComposeRT);
hr = m_pHwndRT->CreateCompatibleRenderTarget(
D2D1::SizeF(
static_cast<FLOAT>(m_cxGifImage),
static_cast<FLOAT>(m_cyGifImage)),
&m_pFrameComposeRT);
}
return hr;
}
/******************************************************************
* *
* DemoApp::OnRender *
* *
* Called whenever the application needs to display the client *
* window. *
* *
* Renders the pre-composed frame by drawing it onto the hwnd *
* render target. *
* *
******************************************************************/
HRESULT DemoApp::OnRender()
{
HRESULT hr = S_OK;
ID2D1Bitmap *pFrameToRender = NULL;
// Check to see if the render targets are initialized
if (m_pHwndRT && m_pFrameComposeRT)
{
if (SUCCEEDED(hr))
{
// Only render when the window is not occluded
if (!(m_pHwndRT->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED))
{
D2D1_RECT_F drawRect;
hr = CalculateDrawRectangle(drawRect);
if (SUCCEEDED(hr))
{
// Get the bitmap to draw on the hwnd render target
hr = m_pFrameComposeRT->GetBitmap(&pFrameToRender);
}
if (SUCCEEDED(hr))
{
// Draw the bitmap onto the calculated rectangle
m_pHwndRT->BeginDraw();
m_pHwndRT->Clear(D2D1::ColorF(D2D1::ColorF::Black));
m_pHwndRT->DrawBitmap(pFrameToRender, drawRect);
hr = m_pHwndRT->EndDraw();
}
}
}
}
SafeRelease(pFrameToRender);
return hr;
}
/******************************************************************
* *
* DemoApp::GetFileOpen *
* *
* Creates an open file dialog box and returns the filename *
* of the file selected(if any). *
* *
******************************************************************/
BOOL DemoApp::GetFileOpen(WCHAR *pszFileName, DWORD cchFileName)
{
pszFileName[0] = L'\0';
OPENFILENAME ofn;
RtlZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFilter = L"*Gif Files\0*.gif\0";
ofn.lpstrFile = pszFileName;
ofn.nMaxFile = cchFileName;
ofn.lpstrTitle = L"Select an image to display...";
ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
// Display the Open dialog box.
return GetOpenFileName(&ofn);
}
/******************************************************************
* *
* DemoApp::OnResize *
* *
* If the application receives a WM_SIZE message, this method *
* will resize the render target appropriately. *
* *
******************************************************************/
HRESULT DemoApp::OnResize(UINT uWidth, UINT uHeight)
{
HRESULT hr = S_OK;
if (m_pHwndRT)
{
D2D1_SIZE_U size;
size.width = uWidth;
size.height = uHeight;
hr = m_pHwndRT->Resize(size);
}
return hr;
}
/******************************************************************
* *
* DemoApp::s_WndProc *
* *
* Static window message handler used to initialize the *
* application object and call the object's member WndProc *
* *
******************************************************************/
LRESULT CALLBACK DemoApp::s_WndProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
DemoApp *pThis = NULL;
LRESULT lRet = 0;
if (uMsg == WM_NCCREATE)
{
LPCREATESTRUCT pcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = reinterpret_cast<DemoApp *>(pcs->lpCreateParams);
SetWindowLongPtr(hWnd, GWLP_USERDATA, PtrToUlong(pThis));
lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
}
else
{
pThis = reinterpret_cast<DemoApp *>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (pThis)
{
lRet = pThis->WndProc(hWnd, uMsg, wParam, lParam);
}
else
{
lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
return lRet;
}
/******************************************************************
* *
* DemoApp::WndProc *
* *
* Window message handler *
* *
******************************************************************/
LRESULT DemoApp::WndProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
HRESULT hr = S_OK;
switch (uMsg)
{
case WM_COMMAND:
{
// Parse the menu selections
switch (LOWORD(wParam))
{
case IDM_FILE:
hr = SelectAndDisplayGif();
if (FAILED(hr))
{
MessageBox(hWnd, L"Load gif file failed. Exiting application.", L"Error", MB_OK);
PostQuitMessage(1);
return 1;
}
break;
case IDM_EXIT:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
}
break;
case WM_SIZE:
{
UINT uWidth = LOWORD(lParam);
UINT uHeight = HIWORD(lParam);
hr = OnResize(uWidth, uHeight);
}
break;
case WM_PAINT:
{
hr = OnRender();
ValidateRect(hWnd, NULL);
}
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect(hWnd, NULL, FALSE);
}
break;
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
break;
case WM_TIMER:
{
// Timer expired, display the next frame and set a new timer
// if needed
hr = ComposeNextFrame();
InvalidateRect(hWnd, NULL, FALSE);
}
break;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// In case of a device loss, recreate all the resources and start playing
// gif from the beginning
//
// In case of other errors from resize, paint, and timer event, we will
// try our best to continue displaying the animation
if (hr == D2DERR_RECREATE_TARGET)
{
hr = RecoverDeviceResources();
if (FAILED(hr))
{
MessageBox(hWnd, L"Device loss recovery failed. Exiting application.", L"Error", MB_OK);
PostQuitMessage(1);
}
}
return 0;
}
/******************************************************************
* *
* DemoApp::GetGlobalMetadata() *
* *
* Retrieves global metadata which pertains to the entire image. *
* *
******************************************************************/
HRESULT DemoApp::GetGlobalMetadata()
{
PROPVARIANT propValue;
PropVariantInit(&propValue);
IWICMetadataQueryReader *pMetadataQueryReader = NULL;
// Get the frame count
HRESULT hr = m_pDecoder->GetFrameCount(&m_cFrames);
if (SUCCEEDED(hr))
{
// Create a MetadataQueryReader from the decoder
hr = m_pDecoder->GetMetadataQueryReader(
&pMetadataQueryReader);
}
if (SUCCEEDED(hr))
{
// Get background color
if(FAILED(GetBackgroundColor(pMetadataQueryReader)))
{
// Default to transparent if failed to get the color
m_backgroundColor = D2D1::ColorF(0, 0.f);
}
}
// Get global frame size
if (SUCCEEDED(hr))
{
// Get width
hr = pMetadataQueryReader->GetMetadataByName(
L"/logscrdesc/Width",
&propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
m_cxGifImage = propValue.uiVal;
}
PropVariantClear(&propValue);
}
}
if (SUCCEEDED(hr))
{
// Get height
hr = pMetadataQueryReader->GetMetadataByName(
L"/logscrdesc/Height",
&propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
m_cyGifImage = propValue.uiVal;
}
PropVariantClear(&propValue);
}
}
if (SUCCEEDED(hr))
{
// Get pixel aspect ratio
hr = pMetadataQueryReader->GetMetadataByName(
L"/logscrdesc/PixelAspectRatio",
&propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI1 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
UINT uPixelAspRatio = propValue.bVal;
if (uPixelAspRatio != 0)
{
// Need to calculate the ratio. The value in uPixelAspRatio
// allows specifying widest pixel 4:1 to the tallest pixel of
// 1:4 in increments of 1/64th
FLOAT pixelAspRatio = (uPixelAspRatio + 15.f) / 64.f;
// Calculate the image width and height in pixel based on the
// pixel aspect ratio. Only shrink the image.
if (pixelAspRatio > 1.f)
{
m_cxGifImagePixel = m_cxGifImage;
m_cyGifImagePixel = static_cast<UINT>(m_cyGifImage / pixelAspRatio);
}
else
{
m_cxGifImagePixel = static_cast<UINT>(m_cxGifImage * pixelAspRatio);
m_cyGifImagePixel = m_cyGifImage;
}
}
else
{
// The value is 0, so its ratio is 1
m_cxGifImagePixel = m_cxGifImage;
m_cyGifImagePixel = m_cyGifImage;
}
}
PropVariantClear(&propValue);
}
}
// Get looping information
if (SUCCEEDED(hr))
{
// First check to see if the application block in the Application Extension
// contains "NETSCAPE2.0" and "ANIMEXTS1.0", which indicates the gif animation
// has looping information associated with it.
//
// If we fail to get the looping information, loop the animation infinitely.
if (SUCCEEDED(pMetadataQueryReader->GetMetadataByName(
L"/appext/application",
&propValue)) &&
propValue.vt == (VT_UI1 | VT_VECTOR) &&
propValue.caub.cElems == 11 && // Length of the application block
(!memcmp(propValue.caub.pElems, "NETSCAPE2.0", propValue.caub.cElems) ||
!memcmp(propValue.caub.pElems, "ANIMEXTS1.0", propValue.caub.cElems)))
{
PropVariantClear(&propValue);
hr = pMetadataQueryReader->GetMetadataByName(L"/appext/data", &propValue);
if (SUCCEEDED(hr))
{
// The data is in the following format:
// byte 0: extsize (must be > 1)
// byte 1: loopType (1 == animated gif)
// byte 2: loop count (least significant byte)
// byte 3: loop count (most significant byte)
// byte 4: set to zero
if (propValue.vt == (VT_UI1 | VT_VECTOR) &&
propValue.caub.cElems >= 4 &&
propValue.caub.pElems[0] > 0 &&
propValue.caub.pElems[1] == 1)
{
m_uTotalLoopCount = MAKEWORD(propValue.caub.pElems[2],
propValue.caub.pElems[3]);
// If the total loop count is not zero, we then have a loop count
// If it is 0, then we repeat infinitely
if (m_uTotalLoopCount != 0)
{
m_fHasLoop = TRUE;
}
}
}
}
}
PropVariantClear(&propValue);
SafeRelease(pMetadataQueryReader);
return hr;
}
/******************************************************************
* *
* DemoApp::GetRawFrame() *
* *
* Decodes the current raw frame, retrieves its timing *
* information, disposal method, and frame dimension for *
* rendering. Raw frame is the frame read directly from the gif *
* file without composing. *
* *
******************************************************************/
HRESULT DemoApp::GetRawFrame(UINT uFrameIndex)
{
IWICFormatConverter *pConverter = NULL;
IWICBitmapFrameDecode *pWicFrame = NULL;
IWICMetadataQueryReader *pFrameMetadataQueryReader = NULL;
PROPVARIANT propValue;
PropVariantInit(&propValue);
// Retrieve the current frame
HRESULT hr = m_pDecoder->GetFrame(uFrameIndex, &pWicFrame);
if (SUCCEEDED(hr))
{
// Format convert to 32bppPBGRA which D2D expects
hr = m_pIWICFactory->CreateFormatConverter(&pConverter);
}
if (SUCCEEDED(hr))
{
hr = pConverter->Initialize(
pWicFrame,
GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone,
NULL,
0.f,
WICBitmapPaletteTypeCustom);
}
if (SUCCEEDED(hr))
{
// Create a D2DBitmap from IWICBitmapSource
SafeRelease(m_pRawFrame);
hr = m_pHwndRT->CreateBitmapFromWicBitmap(
pConverter,
NULL,
&m_pRawFrame);
}
if (SUCCEEDED(hr))
{
// Get Metadata Query Reader from the frame
hr = pWicFrame->GetMetadataQueryReader(&pFrameMetadataQueryReader);
}
// Get the Metadata for the current frame
if (SUCCEEDED(hr))
{
hr = pFrameMetadataQueryReader->GetMetadataByName(L"/imgdesc/Left", &propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
m_framePosition.left = static_cast<FLOAT>(propValue.uiVal);
}
PropVariantClear(&propValue);
}
}
if (SUCCEEDED(hr))
{
hr = pFrameMetadataQueryReader->GetMetadataByName(L"/imgdesc/Top", &propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
m_framePosition.top = static_cast<FLOAT>(propValue.uiVal);
}
PropVariantClear(&propValue);
}
}
if (SUCCEEDED(hr))
{
hr = pFrameMetadataQueryReader->GetMetadataByName(L"/imgdesc/Width", &propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
m_framePosition.right = static_cast<FLOAT>(propValue.uiVal)
+ m_framePosition.left;
}
PropVariantClear(&propValue);
}
}
if (SUCCEEDED(hr))
{
hr = pFrameMetadataQueryReader->GetMetadataByName(L"/imgdesc/Height", &propValue);
if (SUCCEEDED(hr))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
m_framePosition.bottom = static_cast<FLOAT>(propValue.uiVal)
+ m_framePosition.top;
}
PropVariantClear(&propValue);
}
}
if (SUCCEEDED(hr))
{
// Get delay from the optional Graphic Control Extension
if (SUCCEEDED(pFrameMetadataQueryReader->GetMetadataByName(
L"/grctlext/Delay",
&propValue)))
{
hr = (propValue.vt == VT_UI2 ? S_OK : E_FAIL);
if (SUCCEEDED(hr))
{
// Convert the delay retrieved in 10 ms units to a delay in 1 ms units
hr = UIntMult(propValue.uiVal, 10, &m_uFrameDelay);
}
PropVariantClear(&propValue);
}
else
{
// Failed to get delay from graphic control extension. Possibly a
// single frame image (non-animated gif)
m_uFrameDelay = 0;
}
if (SUCCEEDED(hr))
{
// Insert an artificial delay to ensure rendering for gif with very small
// or 0 delay. This delay number is picked to match with most browsers'
// gif display speed.
//
// This will defeat the purpose of using zero delay intermediate frames in
// order to preserve compatibility. If this is removed, the zero delay
// intermediate frames will not be visible.
if (m_uFrameDelay < 90)
{
m_uFrameDelay = 90;
}
}
}
if (SUCCEEDED(hr))
{
if (SUCCEEDED(pFrameMetadataQueryReader->GetMetadataByName(
L"/grctlext/Disposal",
&propValue)))
{
hr = (propValue.vt == VT_UI1) ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
m_uFrameDisposal = propValue.bVal;
}
}
else
{
// Failed to get the disposal method, use default. Possibly a
// non-animated gif.
m_uFrameDisposal = DM_UNDEFINED;
}
}
PropVariantClear(&propValue);
SafeRelease(pConverter);
SafeRelease(pWicFrame);
SafeRelease(pFrameMetadataQueryReader);
return hr;
}
/******************************************************************
* *
* DemoApp::GetBackgroundColor() *
* *
* Reads and stores the background color for gif. *
* *
******************************************************************/
HRESULT DemoApp::GetBackgroundColor(
IWICMetadataQueryReader *pMetadataQueryReader)
{
DWORD dwBGColor;
BYTE backgroundIndex = 0;
WICColor rgColors[256];
UINT cColorsCopied = 0;
PROPVARIANT propVariant;
PropVariantInit(&propVariant);
IWICPalette *pWicPalette = NULL;
// If we have a global palette, get the palette and background color
HRESULT hr = pMetadataQueryReader->GetMetadataByName(
L"/logscrdesc/GlobalColorTableFlag",
&propVariant);
if (SUCCEEDED(hr))
{
hr = (propVariant.vt != VT_BOOL || !propVariant.boolVal) ? E_FAIL : S_OK;
PropVariantClear(&propVariant);
}
if (SUCCEEDED(hr))
{
// Background color index
hr = pMetadataQueryReader->GetMetadataByName(
L"/logscrdesc/BackgroundColorIndex",
&propVariant);
if (SUCCEEDED(hr))
{
hr = (propVariant.vt != VT_UI1) ? E_FAIL : S_OK;
if (SUCCEEDED(hr))
{
backgroundIndex = propVariant.bVal;
}
PropVariantClear(&propVariant);
}
}
// Get the color from the palette
if (SUCCEEDED(hr))
{
hr = m_pIWICFactory->CreatePalette(&pWicPalette);
}
if (SUCCEEDED(hr))
{
// Get the global palette
hr = m_pDecoder->CopyPalette(pWicPalette);
}
if (SUCCEEDED(hr))
{
hr = pWicPalette->GetColors(
ARRAYSIZE(rgColors),
rgColors,
&cColorsCopied);
}
if (SUCCEEDED(hr))
{
// Check whether background color is outside range
hr = (backgroundIndex >= cColorsCopied) ? E_FAIL : S_OK;
}
if (SUCCEEDED(hr))
{
// Get the color in ARGB format
dwBGColor = rgColors[backgroundIndex];
// The background color is in ARGB format, and we want to
// extract the alpha value and convert it in FLOAT
FLOAT alpha = (dwBGColor >> 24) / 255.f;
m_backgroundColor = D2D1::ColorF(dwBGColor, alpha);
}
SafeRelease(pWicPalette);
return hr;
}
/******************************************************************
* *
* DemoApp::CalculateDrawRectangle() *
* *
* Calculates a specific rectangular area of the hwnd *
* render target to draw a bitmap containing the current *
* composed frame. *
* *
******************************************************************/
HRESULT DemoApp::CalculateDrawRectangle(D2D1_RECT_F &drawRect)
{
HRESULT hr = S_OK;
RECT rcClient;
// Top and left of the client rectangle are both 0
if (!GetClientRect(m_hWnd, &rcClient))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
if (SUCCEEDED(hr))
{
// Calculate the area to display the image
// Center the image if the client rectangle is larger
drawRect.left = (static_cast<FLOAT>(rcClient.right) - m_cxGifImagePixel) / 2.f;
drawRect.top = (static_cast<FLOAT>(rcClient.bottom) - m_cyGifImagePixel) / 2.f;
drawRect.right = drawRect.left + m_cxGifImagePixel;
drawRect.bottom = drawRect.top + m_cyGifImagePixel;
// If the client area is resized to be smaller than the image size, scale
// the image, and preserve the aspect ratio
FLOAT aspectRatio = static_cast<FLOAT>(m_cxGifImagePixel) /
static_cast<FLOAT>(m_cyGifImagePixel);
if (drawRect.left < 0)
{
FLOAT newWidth = static_cast<FLOAT>(rcClient.right);
FLOAT newHeight = newWidth / aspectRatio;
drawRect.left = 0;
drawRect.top = (static_cast<FLOAT>(rcClient.bottom) - newHeight) / 2.f;
drawRect.right = newWidth;
drawRect.bottom = drawRect.top + newHeight;
}
if (drawRect.top < 0)
{
FLOAT newHeight = static_cast<FLOAT>(rcClient.bottom);
FLOAT newWidth = newHeight * aspectRatio;
drawRect.left = (static_cast<FLOAT>(rcClient.right) - newWidth) / 2.f;
drawRect.top = 0;
drawRect.right = drawRect.left + newWidth;
drawRect.bottom = newHeight;
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::RestoreSavedFrame() *
* *
* Copys the saved frame to the frame in the bitmap render *
* target. *
* *
******************************************************************/
HRESULT DemoApp::RestoreSavedFrame()
{
HRESULT hr = S_OK;
ID2D1Bitmap *pFrameToCopyTo = NULL;
hr = m_pSavedFrame ? S_OK : E_FAIL;
if(SUCCEEDED(hr))
{
hr = m_pFrameComposeRT->GetBitmap(&pFrameToCopyTo);
}
if (SUCCEEDED(hr))
{
// Copy the whole bitmap
hr = pFrameToCopyTo->CopyFromBitmap(NULL, m_pSavedFrame, NULL);
}
SafeRelease(pFrameToCopyTo);
return hr;
}
/******************************************************************
* *
* DemoApp::ClearCurrentFrameArea() *
* *
* Clears a rectangular area equal to the area overlaid by the *
* current raw frame in the bitmap render target with background *
* color. *
* *
******************************************************************/
HRESULT DemoApp::ClearCurrentFrameArea()
{
m_pFrameComposeRT->BeginDraw();
// Clip the render target to the size of the raw frame
m_pFrameComposeRT->PushAxisAlignedClip(
&m_framePosition,
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
m_pFrameComposeRT->Clear(m_backgroundColor);
// Remove the clipping
m_pFrameComposeRT->PopAxisAlignedClip();
return m_pFrameComposeRT->EndDraw();
}
/******************************************************************
* *
* DemoApp::DisposeCurrentFrame() *
* *
* At the end of each delay, disposes the current frame *
* based on the disposal method specified. *
* *
******************************************************************/
HRESULT DemoApp::DisposeCurrentFrame()
{
HRESULT hr = S_OK;
switch (m_uFrameDisposal)
{
case DM_UNDEFINED:
case DM_NONE:
// We simply draw on the previous frames. Do nothing here.
break;
case DM_BACKGROUND:
// Dispose background
// Clear the area covered by the current raw frame with background color
hr = ClearCurrentFrameArea();
break;
case DM_PREVIOUS:
// Dispose previous
// We restore the previous composed frame first
hr = RestoreSavedFrame();
break;
default:
// Invalid disposal method
hr = E_FAIL;
}
return hr;
}
/******************************************************************
* *
* DemoApp::OverlayNextFrame() *
* *
* Loads and draws the next raw frame into the composed frame *
* render target. This is called after the current frame is *
* disposed. *
* *
******************************************************************/
HRESULT DemoApp::OverlayNextFrame()
{
// Get Frame information
HRESULT hr = GetRawFrame(m_uNextFrameIndex);
if (SUCCEEDED(hr))
{
// For disposal 3 method, we would want to save a copy of the current
// composed frame
if (m_uFrameDisposal == DM_PREVIOUS)
{
hr = SaveComposedFrame();
}
}
if (SUCCEEDED(hr))
{
// Start producing the next bitmap
m_pFrameComposeRT->BeginDraw();
// If starting a new animation loop
if (m_uNextFrameIndex == 0)
{
// Draw background and increase loop count
m_pFrameComposeRT->Clear(m_backgroundColor);
m_uLoopNumber++;
}
// Produce the next frame
m_pFrameComposeRT->DrawBitmap(
m_pRawFrame,
m_framePosition);
hr = m_pFrameComposeRT->EndDraw();
}
// To improve performance and avoid decoding/composing this frame in the
// following animation loops, the composed frame can be cached here in system
// or video memory.
if (SUCCEEDED(hr))
{
// Increase the frame index by 1
m_uNextFrameIndex = (++m_uNextFrameIndex) % m_cFrames;
}
return hr;
}
/******************************************************************
* *
* DemoApp::SaveComposedFrame() *
* *
* Saves the current composed frame in the bitmap render target *
* into a temporary bitmap. Initializes the temporary bitmap if *
* needed. *
* *
******************************************************************/
HRESULT DemoApp::SaveComposedFrame()
{
HRESULT hr = S_OK;
ID2D1Bitmap *pFrameToBeSaved = NULL;
hr = m_pFrameComposeRT->GetBitmap(&pFrameToBeSaved);
if (SUCCEEDED(hr))
{
// Create the temporary bitmap if it hasn't been created yet
if (m_pSavedFrame == NULL)
{
D2D1_SIZE_U bitmapSize = pFrameToBeSaved->GetPixelSize();
D2D1_BITMAP_PROPERTIES bitmapProp;
pFrameToBeSaved->GetDpi(&bitmapProp.dpiX, &bitmapProp.dpiY);
bitmapProp.pixelFormat = pFrameToBeSaved->GetPixelFormat();
hr = m_pFrameComposeRT->CreateBitmap(
bitmapSize,
bitmapProp,
&m_pSavedFrame);
}
}
if (SUCCEEDED(hr))
{
// Copy the whole bitmap
hr = m_pSavedFrame->CopyFromBitmap(NULL, pFrameToBeSaved, NULL);
}
SafeRelease(pFrameToBeSaved);
return hr;
}
/******************************************************************
* *
* DemoApp::SelectAndDisplayGif() *
* *
* Opens a dialog and displays a selected image. *
* *
******************************************************************/
HRESULT DemoApp::SelectAndDisplayGif()
{
HRESULT hr = S_OK;
WCHAR szFileName[MAX_PATH];
RECT rcClient = {};
RECT rcWindow = {};
// If the user cancels selection, then nothing happens
if (GetFileOpen(szFileName, ARRAYSIZE(szFileName)))
{
// Reset the states
m_uNextFrameIndex = 0;
m_uFrameDisposal = DM_NONE; // No previous frame, use disposal none
m_uLoopNumber = 0;
m_fHasLoop = FALSE;
SafeRelease(m_pSavedFrame);
// Create a decoder for the gif file
SafeRelease(m_pDecoder);
hr = m_pIWICFactory->CreateDecoderFromFilename(
szFileName,
NULL,
GENERIC_READ,
WICDecodeMetadataCacheOnLoad,
&m_pDecoder);
if (SUCCEEDED(hr))
{
hr = GetGlobalMetadata();
}
if (SUCCEEDED(hr))
{
rcClient.right = m_cxGifImagePixel;
rcClient.bottom = m_cyGifImagePixel;
if (!AdjustWindowRect(&rcClient, WS_OVERLAPPEDWINDOW, TRUE))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (SUCCEEDED(hr))
{
// Get the upper left corner of the current window
if (!GetWindowRect(m_hWnd, &rcWindow))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (SUCCEEDED(hr))
{
// Resize the window to fit the gif
MoveWindow(
m_hWnd,
rcWindow.left,
rcWindow.top,
RectWidth(rcClient),
RectHeight(rcClient),
TRUE);
hr = CreateDeviceResources();
}
if (SUCCEEDED(hr))
{
// If we have at least one frame, start playing
// the animation from the first frame
if (m_cFrames > 0)
{
hr = ComposeNextFrame();
InvalidateRect(m_hWnd, NULL, FALSE);
}
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::ComposeNextFrame() *
* *
* Composes the next frame by first disposing the current frame *
* and then overlaying the next frame. More than one frame may *
* be processed in order to produce the next frame to be *
* displayed due to the use of zero delay intermediate frames. *
* Also, sets a timer that is equal to the delay of the frame. *
* *
******************************************************************/
HRESULT DemoApp::ComposeNextFrame()
{
HRESULT hr = S_OK;
// Check to see if the render targets are initialized
if (m_pHwndRT && m_pFrameComposeRT)
{
// First, kill the timer since the delay is no longer valid
KillTimer(m_hWnd, DELAY_TIMER_ID);
// Compose one frame
hr = DisposeCurrentFrame();
if (SUCCEEDED(hr))
{
hr = OverlayNextFrame();
}
// Keep composing frames until we see a frame with delay greater than
// 0 (0 delay frames are the invisible intermediate frames), or until
// we have reached the very last frame.
while (SUCCEEDED(hr) && m_uFrameDelay == 0 && !IsLastFrame())
{
hr = DisposeCurrentFrame();
if (SUCCEEDED(hr))
{
hr = OverlayNextFrame();
}
}
// If we have more frames to play, set the timer according to the delay.
// Set the timer regardless of whether we succeeded in composing a frame
// to try our best to continue displaying the animation.
if (!EndOfAnimation() && m_cFrames > 1)
{
// Set the timer according to the delay
SetTimer(m_hWnd, DELAY_TIMER_ID, m_uFrameDelay, NULL);
}
}
return hr;
}
/******************************************************************
* *
* DemoApp::RecoverDeviceResources *
* *
* Discards device-specific resources and recreates them. *
* Also starts the animation from the beginning. *
* *
******************************************************************/
HRESULT DemoApp::RecoverDeviceResources()
{
SafeRelease(m_pHwndRT);
SafeRelease(m_pFrameComposeRT);
SafeRelease(m_pSavedFrame);
m_uNextFrameIndex = 0;
m_uFrameDisposal = DM_NONE; // No previous frames. Use disposal none.
m_uLoopNumber = 0;
HRESULT hr = CreateDeviceResources();
if (SUCCEEDED(hr))
{
if (m_cFrames > 0)
{
// Load the first frame
hr = ComposeNextFrame();
InvalidateRect(m_hWnd, NULL, FALSE);
}
}
return hr;
}