Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

Direct2DとWindows Imaging Component(WIC)で透過PNGを表示

スポンサードリンク

Direct2Dを使ってPNG画像を表示するいい方法はないか模索していたところ、
Windows Imaging Component(WIC)というのを見つけましたが、しばらくこれどうやって使うの?
状態が続いており、いろんなサイトを読んでみるも挫折……
と思ったところ神!!!といえるようなサイトを発見し、そこの解説を読んで、
一応一つのコードにまとめました。長いんでGistに貼り付けました。
作り方はWin32プロジェクト作って変更していくスタイルです。
ひな形をVisual Studioに作らせてADDとかMODとかDELとか書いているところを変更しただけです。
Windows APIの最低限の知識がないと意味不明です。
あとちょっとだけでもCOM知ってないとなんじゃこりゃですw

作成環境

Windows 8.1
Visual Studio 2013 Professional
※ExpressだとATLが使えないので、CComPtrが使えないですので、代替の_com_ptr_tを使えばいいそうです。
※_com_ptr_tは古いのでMicrosoft::WRL::ComPtrのほうが良いです。

作成方法

ファイル > 新規作成 > プロジェクト
Visual C++のWin32 プロジェクトを選択し
プロジェクト名をD2DTest01とかにしておく。
デフォルト設定のままプロジェクトを作成。
プロジェクトを右クリックしてプロパティで
Debug/Releaseともに
構成プロパティのリンカー > 入力 > 追加の依存ファイル
先頭に以下を追加して下さい
d2d1.lib;WindowsCodecs.lib;
あとはD2DTest01.cppのWinMainがあるソースを下記のように変更する。

ソース

// D2DTest01.cpp : アプリケーションのエントリ ポイントを定義します。
//
// NOTE :   構成プロパティのリンカー > 入力 > 追加の依存ファイル
//          先頭に以下を追加して下さい
//          d2d1.lib;WindowsCodecs.lib;

#include "stdafx.h"
#include "D2DTest01.h"
// ADD ======================== >>>
#include <d2d1.h>               // Direct2D
#include <d2d1helper.h>         // Direct2D
#include <atlbase.h>            // Direct2D ATL依存
#include <wincodec.h>           // WIC
#include <wincodecsdk.h>        // WIC
// ADD ======================== <<<

#define MAX_LOADSTRING 100

// グローバル変数:
HINSTANCE hInst;                                // 現在のインターフェイス
TCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
TCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名
// ADD ======================== >>>
CComPtr<ID2D1Factory>           gFactory;
CComPtr<ID2D1HwndRenderTarget>  gTarget;
CComPtr<ID2D1Bitmap>            gBitmap;
CComPtr<IWICImagingFactory>     gImagingFactory;
// ADD ======================== <<<

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
// ADD ======================== >>>
HRESULT             createResource(HWND hWnd);
void                discardResource();
// ADD ======================== <<<

int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: ここにコードを挿入してください。
    // ADD ======================== >>>
    CoInitialize(NULL);
    if (FAILED(CoCreateInstance(
            CLSID_WICImagingFactory,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_IWICImagingFactory,
            reinterpret_cast<void **>(&gImagingFactory)
            ))
        )
    {
        MessageBox(NULL, _T("ImagingFactory作成に失敗"), NULL, MB_OK);
        return -1;
    }
    HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &gFactory);
    if (FAILED(hr))
    {
        MessageBox(NULL, _T("D2Dファクトリ作成に失敗"), NULL, MB_OK);
        return -1;
    }
    // ADD ======================== <<<

    MSG msg;
    HACCEL hAccelTable;

    // グローバル文字列を初期化しています。
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadString(hInstance, IDC_D2DTEST01, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーションの初期化を実行します:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_D2DTEST01));

    // MOD ======================== >>>
    // メイン メッセージ ループ:
    //while (GetMessage(&msg, NULL, 0, 0))
    //{
    //  if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    //  {
    //      TranslateMessage(&msg);
    //      DispatchMessage(&msg);
    //  }
    //}
    do
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
        {
            BOOL ret = GetMessage(&msg, NULL, 0, 0);
            if (ret == 0 /*WM_QUIT */
                || ret == -1 /* ERROR */)
            {
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    } while (msg.message != WM_QUIT);

    gImagingFactory.Release();
    CoUninitialize();
    // MOD ======================== <<<

    return (int) msg.wParam;
}


//
//  関数: MyRegisterClass()
//
//  目的: ウィンドウ クラスを登録します。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D2DTEST01));
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDC_D2DTEST01);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassEx(&wcex);
}

//
//   関数: InitInstance(HINSTANCE, int)
//
//   目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します。
//
//   コメント:
//
//        この関数で、グローバル変数でインスタンス ハンドルを保存し、
//        メイン プログラム ウィンドウを作成および表示します。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd;

    hInst = hInstance; // グローバル変数にインスタンス処理を格納します。

    hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if (!hWnd)
    {
        return FALSE;
    }

    ShowWindow(hWnd, nCmdShow);
    // DEL ======================== >>>
    //UpdateWindow(hWnd);
    // DEL ======================== <<<

    return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウの描画
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    // DEL ======================== >>>
    //PAINTSTRUCT ps;
    // DEL ======================== <<<
    HDC hdc;

    switch (message)
    {
    case WM_COMMAND:
        wmId    = LOWORD(wParam);
        wmEvent = HIWORD(wParam);
        // 選択されたメニューの解析:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;
    // ADD ======================== >>>
    case WM_SIZE:
        {
        if (gTarget)
        {
            RECT rect;
            GetClientRect(hWnd, &rect);
            gTarget->Resize(
                D2D1::Size(
                    static_cast<UINT>(rect.right - rect.left),
                    static_cast<UINT>(rect.bottom - rect.top)
                    )
                );
        }
        }
        break;
    case WM_DISPLAYCHANGE:
        {
        /*解像度、色深度が変わった時は再描画*/
        InvalidateRect(hWnd, NULL, FALSE);
        }
        break;
    // ADD ======================== <<<
    case WM_PAINT:
        // MOD ======================== >>>
        {
        if (SUCCEEDED(createResource(hWnd)))
        {
            PAINTSTRUCT ps;
            hdc = BeginPaint(hWnd, &ps);    // 無効領域を有効領域にする
            EndPaint(hWnd, &ps);
            if (!(
                gTarget->CheckWindowState()
                & D2D1_WINDOW_STATE_OCCLUDED))
            {
                gTarget->BeginDraw();
                gTarget->Clear(D2D1::ColorF(D2D1::ColorF::Green));
                D2D1_SIZE_F size = gBitmap->GetSize();
                RECT rect;
                GetClientRect(hWnd, &rect);
                int     width = rect.right - rect.left;
                int     height = rect.bottom - rect.top;
                // センタリング
                float   x = (width - size.width) / 2.0f;
                float   y = (height - size.height) / 2.0f;
                gTarget->DrawBitmap(gBitmap,
                    D2D1::Rect<float>(x, y, x + size.width, y + size.height));
                // デバイスロストの場合
                if (gTarget->EndDraw() == D2DERR_RECREATE_TARGET)
                {
                    discardResource();
                    InvalidateRect(hWnd, NULL, FALSE);
                }
            }
        }
        }
        break;
        // MOD ======================== <<<
    // ADD ======================== >>>
    // Direct2Dが自前で背景の描画を行うので、このメッセージを無効に
    case WM_ERASEBKGND:
        break;
    // ADD ======================== <<<
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// バージョン情報ボックスのメッセージ ハンドラーです。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

/// ADD ======================== >>>
HRESULT createResource(HWND hWnd)
{
    HRESULT hr;
    if (!gTarget)
    {
        // レンダーターゲットの作成
        RECT rect;
        GetClientRect(hWnd, &rect);
        hr = gFactory->CreateHwndRenderTarget(
            D2D1::RenderTargetProperties(),
            D2D1::HwndRenderTargetProperties(
            hWnd,
            D2D1::Size(
            static_cast<UINT>(rect.right - rect.left),
            static_cast<UINT>(rect.bottom - rect.top)
            )
            ),
            &gTarget
            );
        if (FAILED(hr))
        {
            MessageBox(NULL, _T("レンダーターゲット作成に失敗"), NULL, MB_OK);
            return E_FAIL;
        }
        // PNGの読込み
        // ファイルからデコーダを作成
        // 画像 http://www.gamecradle.net/document/main/content/material/index.html よりDLして使用
        CComPtr<IWICBitmapDecoder> dec;
        hr = gImagingFactory->CreateDecoderFromFilename(
            _T("pond_1_l.png"),
            NULL,
            GENERIC_READ,
            WICDecodeMetadataCacheOnLoad,
            &dec);
        if (FAILED(hr))
        {
            MessageBox(NULL, _T("デコーダー作成に失敗"), NULL, MB_OK);
            return E_FAIL;
        }
        // フレーム取得
        CComPtr<IWICBitmapFrameDecode> frame;
        hr = dec->GetFrame(0, &frame);
        if (FAILED(hr))
        {
            MessageBox(NULL, _T("フレーム取得に失敗"), NULL, MB_OK);
            return E_FAIL;
        }
        // コンバータでDirect2D用フォーマットに変換
        CComPtr<IWICFormatConverter> converter;
        hr = gImagingFactory->CreateFormatConverter(&converter);
        if (FAILED(hr))
        {
            MessageBox(NULL,
                _T("Direct2Dフォーマット変換に失敗(CreateFormatConverter)"),
                NULL,
                MB_OK);
            return E_FAIL;
        }
        hr = converter->Initialize(
            frame,
            GUID_WICPixelFormat32bppPBGRA,
            WICBitmapDitherTypeNone,
            NULL,
            0.f,
            WICBitmapPaletteTypeMedianCut);
        if (FAILED(hr))
        {
            MessageBox(NULL,
                _T("Direct2Dフォーマット変換に失敗(Initialize)"),
                NULL,
                MB_OK);
            return E_FAIL;
        }
        // Direct2D用ビットマップを作成
        hr = gTarget->CreateBitmapFromWicBitmap(converter, NULL, &gBitmap);
        if (FAILED(hr))
        {
            MessageBox(NULL,
                _T("Direct2Dビットマップ作成に失敗"),
                NULL,
                MB_OK);
            return E_FAIL;
        }
    }
    return S_OK;
}

void discardResource()
{
    gTarget.Release();
}
/// ADD ======================== <<<

2014/11/14追記
COM用のスマートポインタにCComPtrとCComQIPtrとを不適切に使っていた箇所がありました。
今回の場合はCComPtrだけで用が足りますので修正しておきました。
なお、CComPtrとCComQIPtrの違いはこのあたりが参考になるかと……
http://msdn.microsoft.com/ja-jp/library/hh279683.aspx
visual c++ - What is the use of CComPtr over CComQIPtr in COM? - Stack Overflow

実行結果

f:id:fa11enprince:20140310095536p:plain
それにしても初見殺しなコード……WICちゃんと理解しないと…いろいろオプションとか機能ありすぎてわけわかりません(;´・ω・)

参考にさせていただいたサイト 感謝!

http://www.tkzdev.net/

使わせていただいた画像

※リンク先が切れていましたので、トップページのURLを載せています。