FormクラスのTransparencyKeyプロパティを使い、その色を透過色として用いたBitmapをFormクラスのBackgroundImageプロパティに指定すれば、カラーキーを使った複雑な形状のウィンドウを作ることができる。 一方、不定形のリージョン(Region)を作成し、FormクラスのRegionプロパティに指定することでも、リージョンに合わせた形状のウィンドウを作ることができる。

ただし、RegionクラスBitmapクラスには画像からリージョンを作成するメンバが用意されていないため、自前で実装する必要がある。

以下のコードでは、αチャンネルを含む画像(透過PNG)を読み込み、α値が25%未満の部分を透明とみなしてリージョンを作成している。 また作成したリージョンをFormに割り当て、画像の半透明部分に合わせた形状のウィンドウとなるようにしている。 この例ではαチャンネルをもとにリージョンを作成しているが、カラーキーを使うように実装する事も可能。

実装

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

public class Form1 : Form {
  static void Main()
  {
    Application.Run(new Form1());
  }

  protected override void OnLoad(EventArgs e)
  {
    var image = new Bitmap("region.png");

    // ウィンドウを枠線なしにする
    this.FormBorderStyle = FormBorderStyle.None;

    // 背景に読み込んだイメージを割り当てる
    this.BackgroundImage = image;

    // 読み込んだイメージから作成したリージョンを割り当てる
    this.Region = CreateRegionFromImage(image);
  }

  // 画像のαチャンネルを元にリージョンを作成する
  private static Region CreateRegionFromImage(Bitmap image)
  {
    var region = new Region();

    // リージョンを一旦空領域にする
    region.MakeEmpty();

    BitmapData locked = null;

    // 画像の各ピクセルを読み取り、α値が25%(0x40)未満の部分を透明とみなしてリージョンとする
    try {
      locked = image.LockBits(new Rectangle(0, 0, image.Width, image.Height),
                              ImageLockMode.ReadOnly,
                              PixelFormat.Format32bppArgb);

      unsafe {
        for (var y = 0; y < image.Height; y++) {
          var opaque = new Rectangle(0, y, 0, 1); // 連続する不透明ピクセル列の領域
          var transparentPrev = false;
          var transparentCurr = false;
          var pixel = (byte*)locked.Scan0 + y * locked.Stride;

          for (var x = 0; x < image.Width; x++) {
            // このピクセルは透明かどうか
            transparentCurr = pixel[3] < 0x40;
            //transparentCurr = image.GetPixel(x, y).A < 0x40;

            if (transparentCurr) {
              if (transparentPrev) {
                // 透明領域のまま
              }
              else {
                // このピクセルで透明領域に変わった
                if (0 < opaque.Width)
                  // これまでの不透明ピクセル列の領域をリージョンに結合
                  region.Union(opaque);
              }
            }
            else {
              if (transparentPrev) {
                // このピクセルで不透明領域に変わった
                // 不透明ピクセル列の開始点を初期化
                opaque.X = x;
                opaque.Width = 1;
              }
              else {
                // 不透明領域のまま
                // 不透明ピクセル列の幅を更新
                opaque.Width = x - opaque.X + 1;
              }
            }

            // 次のピクセルへ進む
            pixel += 4;

            // 直前のピクセルの透明/不透明の値を保存
            transparentPrev = transparentCurr;
          }

          // 最後のピクセルが不透明領域だった場合
          if (!transparentPrev && !transparentCurr)
            // これまでの不透明ピクセル列の領域をリージョンに結合
            region.Union(opaque);
        }
      }

      return region;
    }
    finally {
      if (locked != null)
        image.UnlockBits(locked);
    }
  }
}
Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class Form1
  Inherits Form

  Public Shared Sub Main()
    Application.Run(New Form1())
  End Sub

  Protected Overrides Sub OnLoad(ByVal e As EventArgs)
    Dim image As New Bitmap("region.png")

    ' ウィンドウを枠線なしにする
    Me.FormBorderStyle = FormBorderStyle.None

    ' 背景に読み込んだイメージを割り当てる
    Me.BackgroundImage = image

    ' 読み込んだイメージから作成したリージョンを割り当てる
    Me.Region = CreateRegionFromImage(image)
  End Sub

  ' 画像のαチャンネルを元にリージョンを作成する
  Private Shared Function CreateRegionFromImage(ByVal image As Bitmap) As Region
    Dim region As New Region()

    ' リージョンを一旦空領域にする
    region.MakeEmpty()

    Dim locked As BitmapData = Nothing

    ' 画像の各ピクセルを読み取り、α値が25%(0x40)未満の部分を透明とみなしてリージョンとする
    Try
      locked = image.LockBits(New Rectangle(0, 0, image.Width, image.Height), _
                              ImageLockMode.ReadOnly, _
                              PixelFormat.Format32bppArgb)

      For y As Integer = 0 To image.Height - 1
        Dim opaque As New Rectangle(0, y, 0, 1) ' 連続する不透明ピクセル列の領域
        Dim transparentPrev As Boolean = False
        Dim transparentCurr As Boolean = false

        For x As Integer = 0 To image.Width - 1
          ' このピクセルは透明かどうか
          transparentCurr = Marshal.ReadByte(locked.Scan0, y * locked.Stride + x * 4 + 3) < &h40
          'transparentCurr = image.GetPixel(x, y).A < &h40

          If transparentCurr Then
            If transparentPrev Then
              ' 透明領域のまま
            Else
              ' このピクセルで透明領域に変わった
              ' これまでの不透明ピクセル列の領域をリージョンに結合
              If 0 < opaque.Width Then region.Union(opaque)
            End If
          Else
            If transparentPrev Then
              ' このピクセルで不透明領域に変わった
              ' 不透明ピクセル列の開始点を初期化
              opaque.X = x
              opaque.Width = 1
            Else
              ' 不透明領域のまま
              ' 不透明ピクセル列の幅を更新
              opaque.Width = x - opaque.X + 1
            End If
          End If

          ' 直前のピクセルの透明/不透明の値を保存
          transparentPrev = transparentCurr

          ' 次のピクセルへ進む
        Next

        ' 最後のピクセルが不透明領域だった場合
        If Not transparentPrev AndAlso Not transparentCurr Then
          ' これまでの不透明ピクセル列の領域をリージョンに結合
          region.Union(opaque)
        End If
      Next

      Return region
    Finally
      If locked IsNot Nothing Then image.UnlockBits(locked)
    End Try
  End Function
End Class

実行例

使用した画像。

使用した画像

上記の画像を使用した実行結果。

実行結果