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

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

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

§1 実装

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);
    }
  }
}

§2 実行例

使用した画像。

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