お知らせ

電子会議

ライブラリ

パレット

Delphi FAQ検索

Delphi FAQ一覧

サンプル蔵





FDelphi FAQ
16番会議室「玉石混淆みんなで作るSample蔵」に寄せられたサンプル

"ドロップしたビットマップにフォームを変形"






【タイトル】ドロップしたビットマップに合わせてフォームを変形します


 エクスプローラからビットマップファイルをドロップすると、ビット
マップ透過色部分を抜いた形にフォームを変形して、ビットマップを表示
します。

 下記のソースコードは Delphi4 用です。SetRgnFromBitmap 関数の最後
の引数(Repaint: Boolean)に設定してある‘デフォルト値’の設定を削
除して関連部分を調整すれば Delphi3 等でも使えるはずです。

 ビットマップからリージョンを作成する部分については、当会議室
(Sample蔵)の #195(市丸 剛氏)と #330(YSS氏)のコードを参考に
させていただいています。両氏に感謝いたします。


【サンプルの実行例】
 メニューの [ファイル(F)][アプリケーションの新規作成(T)] で雛形を
作成して、メインフォームに TImage、TPopupMenu を1個ずつ置きます。

 Image1 にデフォルトのビットマップ(左下隅が透過色)をロード(し
なくても大丈夫ですが)します。

 PopupMenu1 にメニューデザイナーで [終了(&eXit)] の項目を追加して
その OnClick イベントハンドラ Form1.eXit1Click)を作成します。

 オブジェクトインスペクタで下記のプロパティの設定をします。
  Image1.Visible = FALSE
 Form1.PopupMenu = PopupMenu1

 後は、下記のサンプルコードをコピーして実行([F9])します。

 実行中にビットマップをドロップすると、左下を透過色にしてフォー
ムが変形して、ビットマップを表示します。


// ここから Unit1.pas の丸写しです。
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ShellApi, Menus;

type
  TForm1 = class(TForm)
    Image1: TImage;
    PopupMenu1: TPopupMenu;
    eXit1: TMenuItem;
    procedure eXit1Click(Sender: TObject);
  private
    FPoint: TPoint;
    procedure WMEraseBkgnd(var Message: TWMEraseBkgnd);
      message WM_ERASEBKGND;
    procedure WMDropFiles(var Message: TWMDropFiles);
      message WM_DROPFILES;
  protected
    procedure CreateWnd; override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
  public
    procedure UpdateRegionByBitmap;
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

// ビットマップからリージョンを作成します
function CreateRgnFromBitmap(Src: TBitmap;
   TransparentColor: TColor): HRGN;
var
  Stream: TMemoryStream;
var
  Bitmap: TBitmap;
  pLine: PWORD;
  x, y, StartPos: Integer;
  R: TRect;
begin
  Result := HRGN(0);
  Bitmap := TBitmap.Create;
  try
    // 元のビットマップから複製を作ってマスク化します
    Bitmap.Assign(Src);
    if not Bitmap.Empty then
    begin
      Stream := TMemoryStream.Create;
      try
        Stream.SetSize(sizeof(TRGNDATAHEADER));
        with PRgnDataHeader(Stream.Memory)^ do
        begin
          dwSize := sizeof(TRGNDATAHEADER);
          iType := RDH_RECTANGLES;
          nCount := 0;
          nRgnSize := 0;
          rcBound := RECT(0, 0, Bitmap.Width, Bitmap.Height);
        end;
        Stream.Position := sizeof(TRGNDATAHEADER);

        // マスク(モノクロ)にします
        Bitmap.Mask(TransparentColor);

        // ScanLine からデータが取りやすいように 2bytes/pixel にします
        Bitmap.PixelFormat := pf15bit;

        for y := 0 to Bitmap.Height - 1 do
        begin
          pLine := Bitmap.ScanLine[y];
          StartPos := -1;
          for x := 0 to Bitmap.Width - 1 do
          begin
            if(StartPos < 0)and(pLine^ = 0)then
              StartPos := x;
            if(StartPos >= 0)then
            begin
              if(pLine^ <> 0)then
              begin
                R.Left := StartPos;
                R.Right := x;
                R.Top := y;
                R.Bottom := R.Top + 1;

                Stream.Write(R, sizeof(TRect));
                Inc(PRgnDataHeader(Stream.Memory)^.nCount);

                StartPos := -1;
              end
              else if x = (Bitmap.Width - 1)then
              begin
                R.Left := StartPos;
                R.Right := x + 1;
                R.Top := y;
                R.Bottom := R.Top + 1;

                // RECT を書き込むたびにメモリストリームのリサイズが
                // 行われるので、その分、非効率的ですが Delphi4 では
                // 実用スピードです(たぶん Delphi3 でも大丈夫)
                Stream.Write(R, sizeof(TRect));
                Inc(PRgnDataHeader(Stream.Memory)^.nCount);

                StartPos := -1;
              end;
            end;
            Inc(pLine);
          end;
        end; // for y := ...
        Result := ExtCreateRegion(nil, Stream.Size, 
                                  PRgnData(Stream.Memory)^);
        if Result = 0 then
          Result := CreateRectRgn(0, 0, Bitmap.Width, Bitmap.Height);
      finally
        Stream.Free;
      end;
    end;
  finally
    Bitmap.Free;
  end;
end;

// フォームをビットマップに合わせて変形します
function SetRgnFromBitmap(Form: TForm; Bitmap: TBitmap;
  Repaint: Boolean = TRUE): Boolean;
var
  hrgnNew: HRGN;
  R: TRect;
begin
  Result := FALSE;
  // ビットマップからリージョンを作って
  hrgnNew := CreateRgnFromBitmap(Bitmap, Bitmap.TransparentColor);
  if Integer(hrgnNew) = 0 then Exit;
  if GetRgnBox(hrgnNew, R) <> NULLREGION then
  begin
    // リージョンをクライアント領域にずらします
    R.TopLeft := Form.ClientToScreen(Point(-Form.Left, -Form.Top));
    OffsetRgn(hrgnNew, R.Left, R.Top);

    // リージョンをフォームに適用します
    Result := SetWindowRgn(Form.Handle, hrgnNew, Repaint);
  end
  else
  begin
    DeleteObject(hrgnNew);
  end;
end;

{ TForm1 }

// エクスプローラからのドロップ受け入れの設定と、デフォルトのビット
// マップが Image1 にある場合にフォームを変形します
procedure TForm1.CreateWnd;
begin
  inherited CreateWnd;
  DragAcceptFiles(Handle, TRUE);
  UpdateRegionByBitmap;
end;

// フォームのキャプションが見えなくなるので、別なクリック&移動手段
// を提供するため(だけ)です
procedure TForm1.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited MouseDown(Button, Shift, X, Y);
  FPoint := Point(X, Y);
end;

// フォームのキャプションが見えなくなるので、別なクリック&移動手段
// を提供するため(だけ)です
procedure TForm1.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseMove(Shift, X, Y);
  if GetKeyState(VK_LBUTTON) < 0 then
    SetBounds(Left + X - FPoint.x, Top + Y - FPoint.y, Width, Height);
end;

procedure TForm1.UpdateRegionByBitmap;
begin
  with Image1.Picture do
  if Bitmap.Empty then
    SetWindowRgn(Handle, HRGN(0), TRUE)
  else
  begin
    if not SetRgnFromBitmap(Self, Bitmap) then
      SetWindowRgn(Handle, HRGN(0), TRUE);
    ClientWidth := Bitmap.Width;
    ClientHeight:= Bitmap.Height;
    Repaint;
  end;
end;

// エクスプローラからのドロップを受けるメッセージハンドラです
procedure TForm1.WMDropFiles(var Message: TWMDropFiles);
var
  Count: Integer;
  pFileName: PChar;
begin
  try
    Count := DragQueryFile(Message.Drop, $ffffffff, nil, 0);
    if Count > 1 then
      ShowMessage('ドロップするファイルは1個にしてください。')
    else
    begin
      Count := DragQueryFile(Message.Drop, 0, nil, 0) + 1;
      pFileName := AllocMem(Count);
      try
        DragQueryFile(Message.Drop, 0, pFileName, Count);
        Image1.Picture.Bitmap.LoadFromFile(pFileName);
        UpdateRegionByBitmap;
      finally
        FreeMem(pFileName);
      end;
    end;
  finally
    DragFinish(Message.Drop);
  end;
end;

// ビットマップを表示する部分です
// Paint メソッドをオーバーライドしても良いのですが、チラツキを
// 少し減らすための措置です
procedure TForm1.WMEraseBkgnd(var Message: TWMEraseBkgnd);
begin
  with Image1.Picture do
  if Bitmap.Empty then
    inherited
  else
    // クライアント領域の大きさをビットマップに合わせているので
    // これで充分のはずです。
    // 極端に小さいビットマップでは隙間ができる場合も有り得ます。
    BitBlt(Message.DC, 0, 0, Bitmap.Width, Bitmap.Height,
      Bitmap.Canvas.Handle, 0, 0, SRCCOPY);
end;

// キャプションが見えなくなるので、マウスによる終了手段をポップアップ
// メニューで作っています。[Alt]+[F4] や [Alt]+[Space] で良ければこの
// 部分は要りません。
procedure TForm1.eXit1Click(Sender: TObject);
begin
  Close;
end;

end.
// ここまでが Unit1.pas です。

                1999/01/31、河邦 正(GCC02240@nifty.ne.jp)

Original document by 河邦 正         氏 ID:(GCC02240)


ここにあるドキュメントは NIFTY SERVEの Delphi Users' Forum の16番会議室「玉石混淆みんなで作るSample蔵」に投稿されたサンプルです。これらのサンプルはボーランド株式会社がサポートする公式のものではありません。また、必ずしも動作が検証されているものではありません。これらのサンプルを使用したことに起因するいかなる損害も投稿者、およびフォーラムスタッフはその責めを負いません。使用者のリスクの範疇でご使用下さい。

Copyright 1996-2002 Delphi Users' Forum