AsuYuHomepage
トップページ サイトの説明 子供の成長記録 JM BAR Delphi リンク集
Home>>プログラミング>>Tips&Tricks>>Bitmapから画像の形のリージョンを得る
Delphi Tips
Delphi Win32API
ダウンロード

Counter

Delphi Tips & Tricks

Bitmapから画像の形のリージョンを得る

 当Tipsの「ウィンドウを四角以外の形にする」のページでも少し触れましたが、ビットマップの形状に合わせて変形させるサンプルです。
 もちろんフォームだけでなく、ボタン等にも適用できる為、アイデア次第でなにかおもしろい利用方法があるかもしれません。
(ちなみに拙作のJ's Launchでも、この方法を使用してウィンドウを変形させています。)
※自分で1から書こうと思ったのですが、結局どこかで見かけたサンプルと同じようになってしまいました…。

このサンプルを試すにはフォームにTImageを貼り付けておいてください
//与えられたビットマップからリージョンを作成する関数
function CreateRgnFromBitmap(SrcBM :TBitmap; TransColor :TColor):HRGN;
var
  BM         :TBitmap;
  ms         :TMemoryStream;
  R          :TRect;
  x,y,StartX :integer;
  pScan      :PWordArray;
  RgnHeader  :TRgnDataHeader;
begin
  Result :=HRGN(0);
  //ストリーム内にリージョン情報を格納していきます
  ms :=TMemoryStream.Create;
  try
   with RgnHeader do begin
       dwSize   :=Sizeof(TRgnDataHeader);
       iType    :=RDH_RECTANGLES;
       nCount   :=0;
       nRgnSize :=0;
       rcBound  :=Rect(0, 0, SrcBM.Width, SrcBM.Height);
    end;
    //まずリージョンデータヘッダーを書き込み
    ms.WriteBuffer(RgnHeader,Sizeof(TRgnDataHeader));

    BM :=TBitmap.Create;
    try
      BM.Assign(SrcBM);
      if BM.Empty then Exit;
      //モノクロ化します。非透過色が黒
      BM.Mask(TransColor);
      //ScanLineしやすいようにピクセル形式を直します
      BM.PixelFormat :=pf15bit;

      //ここからScanLineによる解析とTRectデータの作成 ・・・@
      for y :=0 to BM.Height-1 do begin
        pScan :=BM.ScanLine[y];
        StartX :=-1;
        for x :=0 to BM.Width-1 do begin
          //非透過色(黒)なら
          if pScan[x] =0 then begin
           if StartX =-1 then
              StartX :=x;
          end
          //透過色なら
          else begin
         if StartX <> -1 then
          begin
              R.Left   :=StartX;
              R.Top    :=y;
              R.Right  :=x;
              R.Bottom :=y+1;
              //Rectをストリームに書き込む
              ms.WriteBuffer(R, Sizeof(TRect));
              Inc(RgnHeader.nCount);
              StartX :=-1;
             end;
          end;
        end; //for x...
        //ビットマップの右端スキャンして StartX <>-1 のままなら
        //(ビットマップの右端が非透過色なら)
        if StartX <> -1 then
        begin
          R.Left   :=StartX;
          R.Top    :=y;
          R.Right  :=BM.Width;
          R.Bottom :=y +1;

          ms.WriteBuffer(R,Sizeof(TRect));
          Inc(RgnHeader.nCount);
        end;
       end; //for y...
       //ストリーム内ヘッダーの矩形数のパラメータを加算
       PRgnDataHeader(ms.Memory).nCount :=RgnHeader.nCount;
       //ストリーム内のデータからリージョンを作成
       Result :=ExtCreateRegion(nil, ms.Size,
                                PRgnData(ms.Memory)^);
     finally
      BM.Free;
     end;
  finally
   ms.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Rgn :HRGN;
  R   :TRect;
  BM  :TBitmap;
begin
  if OpenDialog1.Execute then begin
  if LowerCase(ExtractFileExt(OpenDialog1.FileName)) ='.bmp' then
    BM :=TBitmap.Create;
    try
    BM.LoadFromFile(OpenDialog1.FileName);
     if not BM.Empty  then
     begin
       Rgn :=CreateRgnFromBitmap(BM, BM.TransparentColor);
       try
      if Rgn <> 0 then begin
         //ビットマップをTimageに読み込みます
         with image1 do begin
          AutoSize :=true;
          Picture.LoadFromFile(OpenDialog1.FileName);
          Left :=0; Top  :=0;
         end;
         //リージョンをクライアント領域に平行移動します ・・・A
         //Form のBorderStyleが bsNoneなら不要です
         R.TopLeft := ClientToScreen(Point(-Left, -Top));
         OffsetRgn(Rgn, R.Left, R.Top);
         //リージョンをウィンドウに適用します
         SetWindowRgn(Handle, Rgn, True);
       end;
       finally
        DeleteObject(Rgn); //リージョンを解放
       end;
     end;
    finally
     BM.Free;
    end;
   end;
  end;
end;

 Windowsにはリージョンという機能があり、ウィンドウを変形させたり、描画の際のクリッピングを行えます。Win32APIには任意のリージョンを作成する関数に円形のリージョンを作るCreateEllipticRgn関数、矩形のリージョンを作るCreateRectRgn関数などがあります。
 それに加えもっと自由な形のリージョンを作成する為に用意されたExtCreateRegion関数というものがあります。これは指定されたリージョンデータと座標変換データに基づきリージョンを作成するというものです。
これを使ってビットマップの形状をリージョン化しています。
 ExtCreateRgn関数に渡すデータは、


Region Rect上のように1つのTRgnDataHeaderレコードと、複数の矩形情報です。1つの矩形とはどこの長方形を表しているのかというと、右図のように1行ごとに焦点を当て1行内にある長方形の数だけTRectを含んでいます。
 右図の画像では最終的に計20個のTRectのデータが出来上がるはずです。

このコード内で一番わかりにくいのが、ScanLineをしながらTRectを組み上げストリームに書き込む部分です。(コード内では@からの部分)
丁寧に説明しますと、ここでやっていることは「矩形のスタートするX座標を確保しておき、矩形の終わりの場所でTRectを組み上げてストリームに書き込む」ことです。これをすべての行に対して行います。

 最初に矩形のスタート座標を格納する StartXに-1 を代入しておきます。StartX は現在の位置がは矩形の内部であるかどうかというフラグも兼ねています。(-1のときは矩形内部ではない)
 次に現在のピクセルが透過したい色かどうかをみて、

非透過色でStartX =-1(まだ矩形内部でない)なら、現在の位置を矩形の左座標になるので、StartX に現在のX座標を代入しておきます。StartX <>-1(矩形内部である) なら何もしません。

透過色で StartX <> -1 (前のピクセルが非透過色であった)なら、矩形のスタートX座標である StartXと現在の座標を使って、TRectを組み上げストリームに書き込みます。この際リージョンヘッダー矩形数を1つ増やし、StartXに再度 -1 を代入し初期化しておきます。StratX =-1の場合は何もしません。

1行分のForループ(For x :=0 to BM.Width-1 do)を終えた時点で、まだ StartX が -1以外であれば、その行の最後のピクセルが非透過色であったということなので、StartX とBM.Widthの値を使ってTRectを組み上げストリームに書き込みます。
Region アルゴリズム
 スキャンラインによる解析が終わったら、TRgnDataHeader内のnCountの値を、ストリーム内のnCountの位置へ代入し、こうしてできたリージョンデータを ExtCreateRgn関数へ渡して返り値にリージョンを得ます。

 後はこのリージョンハンドルを使ってウィンドウをビットマップの形に変形します。TImageをフォームの左端にセットしておくことによって、あたかもウィンドウがTImage上の画像によって切り取られたのようになります。
 作成されるリージョンをタイトルバーを含めたウィンドウの左端からのものです。トップレベルウィンドウに適用する際に、クライアント領域の座標に直す必要があります。(コード中Aの部分)
※このプログラムを実行すると、タイトルバー消える為「×」ボタンでの終了ができません。何かの手段を用意するか、タスクバーの右クリックメニューから終了してください。

1