ソフトウェア開発 Win32プログラミング

半透明のウィンドウを作成する

戻る


[スライム]

右のような透過/半透明のウィンドウを作成する方法について解説する。

透過/半透明のウィンドウを作成する方法は、次の3通りある。

このページでは、3番目の方法について解説する。

Windows 2000以降では、「レイヤードウィンドウ」と呼ばれる透過/半透明のウィンドウを作成できる。 レイヤードウィンドウは、WS_EX_LAYERED拡張スタイルを持ったウィンドウでSetLayeredWindowAttributesか UpdateLayeredWindowを呼ぶことで実現できる。ただし、このような新しいAPIを使うには、#include <windows.h>の前に、#define _WIN32_WINNT 0x0500が必要だ。

レイヤードウィンドウでは次の組み合わせにより、ウィンドウの一部をくりぬいたり、半透明にしたりできる。

UpdateLayeredWindowを使う場合、次のような手順で半透明のウィンドウを作成できる。

  1. WS_EX_LAYERED拡張スタイルを指定してウィンドウを作成する。
  2. 32BPPのDIBを用意する。
  3. DIBをpremultiplied(積算済み)形式に変換する。
  4. UpdateLayeredWindowを呼ぶ。

積算済み形式は、他の形式と比較してアルファブレンドの計算量を削減することができることが長所だが、 元のピクセルデータが破壊されてしまうのが欠点である。

最初にWS_EX_LAYEREDを指定して次のように普通にウィンドウを作成する。

    if (!m_slimeWnd.CreateEx(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW,
        mzcAppName, WS_POPUP, CW_USEDEFAULT, 0, 300, 300))
    {
        return FALSE;
    }
 
    m_slimeWnd.ShowWindow(mzcApp->m_nCmdShow);
    m_slimeWnd.UpdateWindow();

タイトルバーや「閉じる」ボタンは必要ないので、スタイルはWS_POPUPのみとし、拡張スタイルにはWS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOWを指定する。 常に手前に表示したいので、WS_EX_TOPMOSTを指定する。タスクバーにボタンを表示したくないので、WS_EX_TOOLWINDOWも指定する。

次に、32BPPのDIBを用意する。これは、IllustratorやPhotoshop、GIMPなどを使って作成したアルファチャンネル付きの画像を 32BPPのビットマップファイルとして保存して、前節で説明したLoadBitmapFromFileなどを使ってそのまま読み込めばいい(※注1)。

そして、32BPP DIBをpremultiplied(積算済み)形式に変換するわけだが、これはMZCライブラリの最新版にある「MzcPremultiplyDIB関数」を使えばいい。 MzcPremultiplyDIBの実装は次のようになっている。

MZC_PORT VOID MZCAPI MzcPremultiplyDIB(HBITMAP hbm32bpp)
{
    BITMAP bm;
    VERIFY(::GetObject(hbm32bpp, sizeof(bm), &bm));
    ASSERT(bm.bmBitsPixel == 32);
 
    DWORD cdw = bm.bmWidth * bm.bmHeight;
    LPBYTE pb = (LPBYTE) bm.bmBits;
    BYTE alpha;
    while(cdw--)
    {
        alpha = pb[3];
        pb[0] = (BYTE) ((DWORD) pb[0] * alpha / 255);
        pb[1] = (BYTE) ((DWORD) pb[1] * alpha / 255);
        pb[2] = (BYTE) ((DWORD) pb[2] * alpha / 255);
        pb += 4;
    }
}

さらに、メモリDCと初期化したBLENDFUNCTION構造体を用意し、メモリDCで積算済みDIBを選択してUpdateLayeredWindowを呼ぶ。 BLENDFUNCTIONの初期化には、MzcGetBlendFunctionという関数を使うとよい。

BOOL CSlimeWnd::SetImageAndPos(CBitmap& bmp, CPoint& pt)
{
    CMemoryDC dcMem;
    CPoint ptSrc(0, 0);
    HDC hdc = ::GetDC(m_hWnd);
    if (hdc == NULL)
        return FALSE;
    BLENDFUNCTION bf = MzcGetBlendFunction();
    HBITMAP hbmOld = dcMem.SelectBitmap(bmp);
    BOOL bOK = ::UpdateLayeredWindow(m_hWnd, hdc, &pt, &m_siz,
        dcMem, &ptSrc, 0, &bf, ULW_ALPHA);
    dcMem.SelectBitmap(hbmOld);
    ::ReleaseDC(m_hWnd, hdc);
    return bOK;
}

このようにUpdateLayeredWindowを呼ぶと、位置とサイズも更新されるので注意する。

これで半透明のウィンドウが実現できる。イメージを更新したい場合は、積算済み形式のDIBを指定して、前述のCSlimeWnd::SetImageAndPosを呼ぶとよい。

#include "stdafx.h"

BEGIN_MESSAGE_MAP(CSlimeWnd, CWnd)
    ON_WM_TIMER()
    ON_WM_NCRBUTTONUP()
    ON_WM_NCHITTEST()
    ON_WM_CREATE()
    ON_WM_DESTROY()
END_MESSAGE_MAP()
 
CSlimeWnd::CSlimeWnd()
{
}
 
/*virtual*/ CSlimeWnd::~CSlimeWnd()
{
}
 
mzc_msg INT CSlimeWnd::OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
{
    if (!m_abmp[0].LoadBitmapFromFile(_T("slime1.bmp")) ||
        !m_abmp[1].LoadBitmapFromFile(_T("slime2.bmp")))
    {
#ifdef JAPAN
        MsgBox(_T("slime1.bmpとslime2.bmpをEXEと同じ場所に置いてください。"),
            MB_ICONERROR);
#else
        MsgBox(_T("Put slime1.bmp and slime2.bmp to the same folder of the EXE."),
            MB_ICONERROR);
#endif
        return -1;
    }
 
    MzcPremultiplyDIB(m_abmp[0]);
    MzcPremultiplyDIB(m_abmp[1]);
 
    m_abmp[0].GetSize(&m_siz);
    MzcGetWorkArea(&m_rcWorkArea);
 
    MzcSRand();
    m_pt.x = MzcRandInt(m_rcWorkArea.left, m_rcWorkArea.right - m_siz.cx);
    m_pt.y = MzcRandInt(m_rcWorkArea.top, m_rcWorkArea.bottom - m_siz.cy);
 
    if (!SetImageAndPos(m_abmp[0], m_pt))
    {
        MsgBox(_T("Failed on SetImageAndPos"), MB_ICONERROR);
        return -1;
    }
    m_nDirection = MzcRandInt(0, 1);
    m_nImage = 0;
    SetTimer(999, 120);
    return 0;
}
 
mzc_msg VOID CSlimeWnd::OnDestroy()
{
    KillTimer(999);
    ::PostQuitMessage(0);
}
 
mzc_msg UINT CSlimeWnd::OnNcHitTest(CPoint pt)
{
    UINT n = CWnd::OnNcHitTest(pt);
    if (n == HTCLIENT || n == HTBORDER)
        return HTCAPTION;
    return n;
}
 
mzc_msg VOID CSlimeWnd::OnNcRButtonUp(UINT /*nHitTest*/, CPoint /*pt*/)
{
    SendMessage(WM_CLOSE);
}
 
BOOL CSlimeWnd::SetImageAndPos(CBitmap& bmp, CPoint& pt)
{
    CMemoryDC dcMem;
    CPoint ptSrc(0, 0);
    HDC hdc = ::GetDC(m_hWnd);
    if (hdc == NULL)
        return FALSE;
    BLENDFUNCTION bf = MzcGetBlendFunction();
    HBITMAP hbmOld = dcMem.SelectBitmap(bmp);
    BOOL bOK = ::UpdateLayeredWindow(m_hWnd, hdc, &pt, &m_siz,
        dcMem, &ptSrc, 0, &bf, ULW_ALPHA);
    dcMem.SelectBitmap(hbmOld);
    ::ReleaseDC(m_hWnd, hdc);
    return bOK;
}
 
mzc_msg VOID CSlimeWnd::OnTimer(UINT nEventID, TIMERPROC /*fnTimerProc*/)
{
    static INT s_nCount = 0;
 
    if (::GetCapture() == m_hWnd || nEventID != 999)
        return;
 
    CRect rc;
    CPoint pt;
    GetWindowRect(&rc);
    ::GetCursorPos(&pt);
    if (rc.PtInRect(pt) && ::GetAsyncKeyState(VK_RBUTTON) < 0)
        return;
 
    m_pt = MzcGetWindowPos(m_hWnd);
 
    m_pt.y += 10;
    if (m_pt.y > m_rcWorkArea.bottom - m_siz.cy)
        m_pt.y = 0;
    switch(m_nDirection)
    {
    case 0:
        m_pt.x -= 5;
        if (m_pt.x < m_rcWorkArea.left)
            m_nDirection = 1;
        break;
 
    case 1:
        m_pt.x += 5;
        if (m_pt.x > m_rcWorkArea.right - m_siz.cx)
            m_nDirection = 0;
        break;
    }
 
    s_nCount++;
    if (s_nCount > 1)
    {
        m_nImage = (m_nImage + 1) % 2;
        s_nCount = 0;
    }
    VERIFY(SetImageAndPos(m_abmp[m_nImage], m_pt));
}

MzcGetBlendFunctionの実装は次のようになっている。

    MZC_INLINE MZC_INLINE_PORT
    BLENDFUNCTION MZCAPI MzcGetBlendFunction(
        BYTE bSCA/* = 255*/, BYTE bFormat/* = AC_SRC_ALPHA*/)
    {
        BLENDFUNCTION bf;
        bf.BlendOp = AC_SRC_OVER;
        bf.BlendFlags = 0;
        bf.SourceConstantAlpha = bSCA;
        bf.AlphaFormat = bFormat;
        return bf;
    }

※注1: BCC55などの場合、「Invalid bitmap format」などのエラーメッセージが表示されて、 32BPPのビットマップをリソースに埋め込めないことがある。その場合は、アルファチャンネル付きのPNG画像を埋め込むとよい。

ソース: クラスライブラリMZC最新版のサンプルLayeredSampleを参照。


戻る

©片山博文MZ
katayama.hirofumi.mz@gmail.com

inserted by FC2 system