最終更新:2015-04-06 (月) 14:59:54 (3307d)  

UpdateLayeredWindow
Top / UpdateLayeredWindow

32ビットのARGBビットマップ(透過PNGとか)からレイヤードウィンドウを生成

CreateWindowEx 関数を使ってウィンドウを作成するとき、WS_EX_LAYERED フラグをセットすると、作成されるウィンドウはレイヤードウィンドウになります。

もしくはSetWindowLongで変える。

http://blogs.wankuma.com/youryella/archive/2007/10/15/102151.aspx

定義

  • BOOL UpdateLayeredWindow(
      HWND hwnd,             // レイヤードウィンドウのハンドル
      HDC hdcDst,            // 画面のデバイスコンテキストのハンドル
      POINT *pptDst,         // 画面の新しい位置
      SIZE *psize,           // レイヤードウィンドウの新しいサイズ
      HDC hdcSrc,           // サーフェスのデバイスコンテキストのハンドル
      POINT *pptSrc,         // レイヤの位置
      COLORREF crKey,        // カラーキー
      BLENDFUNCTION *pblend, // ブレンド機能
      DWORD dwFlags          // フラグ
    );

メモ

  • UpdateLayeredWindow 関数を呼び出したために現れる、レイヤーウィンドウの下のウィンドウは再描画する必要がありません。システムによって再描画されるためです。そのため、レイヤードウィンドウのシームレスなアニメーションが可能です。
  • The UpdateLayeredWindow function maintains the window's appearance on the screen. The windows underneath a layered window do not need to be repainted when they are uncovered due to a call to UpdateLayeredWindow, because the system will automatically repaint them. This permits seamless animation of the layered window.

C♯

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Runtime.InteropServices;


// UpdateLayeredWindow用ユーティリティ
class ULW
{
  public enum Bool
  {
    False= 0,
    True
  };

  [StructLayout(LayoutKind.Sequential)]
  public struct Point
  {
    public Int32 x;
    public Int32 y;
    public Point(Int32 x, Int32 y) { this.x= x; this.y= y; }
  }

  [StructLayout(LayoutKind.Sequential)]
  public struct Size {
    public Int32 cx;
    public Int32 cy;
    public Size(Int32 cx, Int32 cy) { this.cx= cx; this.cy= cy; }
  }

  [StructLayout(LayoutKind.Sequential, Pack=1)]
  struct ARGB
  {
    public byte Blue;
    public byte Green;
    public byte Red;
    public byte Alpha;
  }

  [StructLayout(LayoutKind.Sequential, Pack=1)]
  public struct BLENDFUNCTION
  {
    public byte BlendOp;
    public byte BlendFlags;
    public byte SourceConstantAlpha;
    public byte AlphaFormat;
  }

  public const Int32 ULW_COLORKEY = 0x00000001;
  public const Int32 ULW_ALPHA    = 0x00000002;
  public const Int32 ULW_OPAQUE   = 0x00000004;

  public const byte AC_SRC_OVER  = 0x00;
  public const byte AC_SRC_ALPHA = 0x01;

  public static int GWL_EXSTYLE   = -20;
  public static int WS_EX_LAYERED = 0x80000;
  public static int LWA_ALPHA     = 0x2;


  [DllImport("user32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern Bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pprSrc, Int32 crKey, ref BLENDFUNCTION pblend, Int32 dwFlags);

  [DllImport("user32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern IntPtr GetDC(IntPtr hWnd);

  [DllImport("user32.dll", ExactSpelling=true)]
  public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern IntPtr CreateCompatibleDC(IntPtr hDC);

  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern Bool DeleteDC(IntPtr hdc);

  [DllImport("gdi32.dll", ExactSpelling=true)]
  public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

  [DllImport("gdi32.dll", ExactSpelling=true, SetLastError=true)]
  public static extern Bool DeleteObject(IntPtr hObject);

  [DllImport("user32")]
  public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
  [DllImport("user32")]
  public static extern int GetWindowLong(IntPtr hWnd, int nIndex);


  /// <para>Changes the current bitmap.</para>
  static public void SetULW(Form form, Bitmap bitmap)
  {
    SetULW(form,bitmap, 255);
  }


  /// <para>Changes the current bitmap with a custom opacity level.  Here is where all happens!</para>
  static public void SetULW(Form form,Bitmap bitmap, byte opacity)
  {
    if (bitmap.PixelFormat != PixelFormat.Format32bppArgb)
      throw new ApplicationException("The bitmap must be 32ppp with alpha-channel.");

    // The ideia of this is very simple,
    // 1. Create a compatible DC with screen;
    // 2. Select the bitmap with 32bpp with alpha-channel in the compatible DC;
    // 3. Call the UpdateLayeredWindow.

    IntPtr screenDc = GetDC(IntPtr.Zero);
    IntPtr memDc = CreateCompatibleDC(screenDc);
    IntPtr hBitmap = IntPtr.Zero;
    IntPtr oldBitmap = IntPtr.Zero;

    try {
      hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));  // grab a GDI handle from this GDI+ bitmap
      oldBitmap = SelectObject(memDc, hBitmap);

      Size size = new Size(bitmap.Width, bitmap.Height);
      Point pointSource = new Point(0, 0);
            Point topPos = new Point(form.Left, form.Top);
      BLENDFUNCTION blend = new BLENDFUNCTION();
      blend.BlendOp             = AC_SRC_OVER;
      blend.BlendFlags          = 0;
      blend.SourceConstantAlpha = opacity;
      blend.AlphaFormat         = AC_SRC_ALPHA;

      SetWindowLong(form.Handle, GWL_EXSTYLE, GetWindowLong(form.Handle, GWL_EXSTYLE) | WS_EX_LAYERED);

      UpdateLayeredWindow(form.Handle, screenDc, ref topPos, ref size, memDc, ref pointSource, 0, ref blend, ULW_ALPHA);
    }
    finally {
      ReleaseDC(IntPtr.Zero, screenDc);
      if (hBitmap != IntPtr.Zero) {
        SelectObject(memDc, oldBitmap);
        //Windows.DeleteObject(hBitmap); // The documentation says that we have to use the Windows.DeleteObject... but since there is no such method I use the normal DeleteObject from ULWUtility GDI and it's working fine without any resource leak.
        DeleteObject(hBitmap);
      }
      DeleteDC(memDc);
    }
  }

}
 

VB.NET

Imports System.Drawing
Imports System.Runtime.InteropServices

Public Class LayeredForm

#Region " UpdateLayerdWindow 関連 API "

    <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Public Shared Function CreateCompatibleDC(ByVal hDC As IntPtr) As IntPtr
    End Function

    <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Public Shared Function DeleteDC(ByVal hdc As IntPtr) As Boolean
    End Function

    <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function DeleteObject(ByVal hObject As IntPtr) As Boolean
    End Function

    <DllImport("gdi32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function SelectObject(ByVal hDC As IntPtr, ByVal hObject As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function GetDC(ByVal hWnd As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Integer
    End Function

    <DllImport("user32.dll", ExactSpelling:=True, SetLastError:=True)> _
    Private Shared Function UpdateLayeredWindow( _
        ByVal hwnd As IntPtr, _
        ByVal hdcDst As IntPtr, _
        <System.Runtime.InteropServices.In()> _
        ByRef pptDst As Point, _
        <System.Runtime.InteropServices.In()> _
        ByRef psize As Size, _
        ByVal hdcSrc As IntPtr, _
        <System.Runtime.InteropServices.In()> _
        ByRef pptSrc As Point, _
        ByRef crKey As Integer, _
        <System.Runtime.InteropServices.In()> _
        ByRef pblend As BLENDFUNCTION, _
        ByVal dwFlags As Integer _
        ) As Boolean
    End Function

    <StructLayout(LayoutKind.Sequential, Pack:=1)> _
    Private Structure BLENDFUNCTION
        Public BlendOp As Byte
        Public BlendFlags As Byte
        Public SourceConstantAlpha As Byte
        Public AlphaFormat As Byte
    End Structure

    Private Const WS_EX_LAYERED As Integer = &H80000
    Private Const WS_BORDER As Integer = &H800000
    Private Const WS_THICKFRAME As Integer = &H40000
    Private Const AC_SRC_OVER As Byte = 0
    Private Const AC_SRC_ALPHA As Byte = 1
    Private Const ULW_ALPHA As Integer = 2

#End Region

#Region " コンストラクタ "

    Public Sub New(ByVal bmp As Bitmap)

        ' この呼び出しは、Windows フォーム デザイナで必要です。 
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。 
        Me.SetBackground(bmp)
    End Sub

#End Region

#Region " オーバーライド "

    Protected Overrides ReadOnly Property CreateParams() As CreateParams
        Get
            ' レイヤードウィンドウスタイルを適用 
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or WS_EX_LAYERED
            cp.Style = cp.Style And (Not WS_BORDER)
            cp.Style = cp.Style And (Not WS_THICKFRAME)
            Return cp
        End Get
    End Property

#End Region

#Region " Public メソッド "

    Public Sub SetBackground(ByVal srcBitmap As Bitmap)

        ' デバイスコンテキストを取得 
        Dim screenDc As IntPtr = GetDC(IntPtr.Zero)
        Dim memDc As IntPtr = CreateCompatibleDC(screenDc)
        Dim hBitmap As IntPtr = IntPtr.Zero
        Dim hOldBitmap As IntPtr = IntPtr.Zero
        Try
            hBitmap = srcBitmap.GetHbitmap(Color.FromArgb(0))
            hOldBitmap = SelectObject(memDc, hBitmap)

            ' BLENDFUNCTION を初期化 
            Dim blend As New BLENDFUNCTION
            blend.BlendOp = AC_SRC_OVER
            blend.BlendFlags = 0
            blend.SourceConstantAlpha = 255
            blend.AlphaFormat = AC_SRC_ALPHA

            ' レイヤードウィンドウを更新 
            Dim r As Boolean = UpdateLayeredWindow( _
                Me.Handle, screenDc, Me.Location, New Size(srcBitmap.Width, srcBitmap.Height), _
                memDc, New Point(0, 0), 0, blend, ULW_ALPHA _
            )

        Finally
            ReleaseDC(IntPtr.Zero, screenDc)
            If hBitmap <> IntPtr.Zero Then
                SelectObject(memDc, hOldBitmap)
                DeleteObject(hBitmap)
            End If
            DeleteDC(memDc)

        End Try

    End Sub

#End Region

End Class

Dim f As New LayeredForm(New Bitmap("hoge.png"))
f.Show()
 

Delphi

interface
function UpdateLayeredWindow(_hwnd:HWND; dstHDC:HDC; pptDst:PPoint;
  ASize:PSize; srcHDC:HDC; pptSrc:PPoint; crKey:COLORREF;
  var bf : BLENDFUNCTION; dwFlag:DWORD):BOOL; stdcall;
function SetULW(Handle:HWND;BMP:TBitMap;Alpha:Integer):boolean;
const
  WS_EX_LAYERED = $80000;
  LWA_COLORKEY  = 1;
  LWA_ALPHA     = 2;
  ULW_COLORKEY  = 1;
  ULW_ALPHA     = 2;
  ULW_OPAQUE    = 4;
  AC_SRC_ALPHA  = 1;
implementation


function UpdateLayeredWindow;
  external 'user32.dll' name 'UpdateLayeredWindow';
function SetULW(Handle:HWND;BMP:TBitMap;Alpha:Integer):boolean;
var
  bf : TBlendFunction;
  zerop : TPoint;
  formsz : TSize;
begin
  result:=false;
  WITH bf DO BEGIN
    BlendOp:=AC_SRC_OVER;
    BlendFlags:=0;
    SourceConstantAlpha:=Alpha; // 完全にALPHAをBITMAPに依存する場合
    AlphaFormat:=AC_SRC_ALPHA;
  END;
  zerop.x:=0; zerop.y:=0;
  formsz.cx:=BMP.Width; formsz.cy:=BMP.height;
  SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE)Xor WS_EX_LAYERED);
  SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED);


  IF NOT UpdateLayeredWindow(
        handle, 0,
        nil, // palleteを気にしないならnilでよろし
        @formsz, // フォームの大きさの指定 : 必須!
        BMP.canvas.handle, // サーフェイスを定義するDC
        @zerop, // サーフェイスを定義する画像の開始点
        0, bf, ULW_ALPHA) THEN BEGIN
    ShowMessage(SysErrorMessage(GetLastError));
    Exit;
  END;
  result:=true;
end;

Windows 7 におけるULWの挙動

Windows 7 での滑らかなアニメーションの実現

メモ

  • 裏に入ったウインドウの描画まで平均的に遅くなる
  • 完全に裏に入っていなくても不可視領域にカスっただけで遅くなる
  • 大元の資料にすら「Layered Window はできるだけ面積を小さくして下さい」等とえらく弱気な説明が書かれている
  • http://usada.sakura.vg/contents/specification2.html