メタファイル形式でコピーする


Imageコンポーネントに何らかの画像があり, これをビットマップ形式でクリップボードにコピーするのは簡単である.

procedure TForm1.CopyAsBmpBtnClick(Sender: TObject);
begin
   Clipboard.Assign( Image1.Picture.Bitmap );
end;

と書くだけだ.

写真やペイント系のソフトで描いた絵ならこの方法で問題ないが, GDIやCanvasのメソッドを使って描いたグラフや図形の場合は, ビットマップではなくベクトルフォーマットでコピーしたいものである. ベクトルフォーマットでコピーした画像は,コピー先で拡大/縮小しても劣化が少ない.

ベクトルフォーマットでコピーするのはビットマップでコピーするほど簡単ではないが, TMetafileのSaveToClipboardFormatメソッドを使えば,拡張メタファイル形式(EMF形式)で クリップボードにコピーするために必要なデータが得られる. このメソッドのプロトタイプは,

procedure SaveToClipboardFormat( var AFormat: Word; var AData: THandle; var APalette: HPALETTE );

である. AFormat,ADataおよびAPaletteはすべてvarであり, それぞれに適当な変数を渡してやると必要な情報が格納されて戻ってくる. これらの結果をTClipboardのSetAsHandleメソッドに渡せば, 画像をEMF形式でコピーすることができる.

例えば以下のように使う.

procedure TForm1.CopyAsEMFBtnClick(Sender: TObject);
var
  Metafile: TMetafile;
  MetafileCanvas: TMetafileCanvas;
  DispW, DispH, MMPerPixelW, MMPerPixelH: Integer;
  Format: Word;
  Data: THandle;
  Palette: HPALETTE;
begin
   DispW := GetDeviceCaps( Canvas.Handle, HORZSIZE );
   DispH := GetDeviceCaps( Canvas.Handle, VERTSIZE );
   MMPerPixelW := Round( DispW * 100 / Screen.Width );
   MMPerPixelH := Round( DispH * 100 / Screen.Height );
    Metafile := TMetafile.Create;
    try
      with Metafile do
      begin
         Enhanced := True;
         MMWidth := Image1.Width * MMPerPixelW;
         MMHeight := Image1.Height * MMPerPixelH;
         Width := Image1.Width;
         Height := Image1.Height;
      end;
      MetafileCanvas := TMetafileCanvas.Create( Metafile, Canvas.Handle );
      DrawPicture( MetafileCanvas, Metafile.Width, Metafile.Height );
      MetafileCanvas.Free;
      Metafile.SaveToClipboardFormat( Format, Data, Palette );
      Clipboard.SetAsHandle( Format, Data );
    finally
      Metafile.Free;
    end;
end;

ここでは,MetafileオブジェクトのWidthおよびHeightを MetafileCanvasを割り当てる前と後に2回設定しなければならないように書いたが, MMWidthおよびMMHeightを設定すればその必要はないようだ. これらのプロパティはメタファイルの幅および高さを0.01mm単位で指定するものであり, ディスプレイ上の1ピクセルあたりの長さに,画像のWidthおよびHeightに乗じた値になる. 1ピクセルあたりの長さは, ディスプレイの横方向と縦方向で異なるのが普通なので 別々に求めなければならない(上のコードのMMPerPixelWおよびMMPerPixelH).

DrawPicture手続きでMetafileCanvasに適当な絵を描画した後, MetafileCanvasを解放してMetafileに画像を転送する. その後,SaveToClipboardFormatでコピーに必要なデータを取得し, SetAsHandleに渡す.

VCLのソースを見てみると,SaveToClipboardFormatはFormatにCF_ENHMETAFILEを設定し, DataにはCopyEnhMetafile APIで作成したメモリメタファイルのハンドルを設定している. また,Paletteには常に0が返ってくる.

SetAsHandleはSetClipboardData APIにDataパラメータで渡された メタファイルのハンドルを渡す. 一旦,ハンドルをクリップボードに渡すと,ハンドルの所有権がOS側に移るので, コピーが終了した後アプリケーション側でメタファイルのハンドルを解放することはない.

以上で問題なく画像をEMF形式でコピーできるのだが,ここではちょっと回りくどいことをしてみよう. SetClipboardDataのプロトタイプは

function SetClipboardData( uFormat: UINT; hMem: THandle ): THandle; stdcall;

uFormatはコピーするデータの種類であり,hMemはそれぞれの種類に応じたデータのハンドルである. APIのヘルプには,hMemがメモリハンドルの場合,それはグローバルヒープに存在しなければならないと 書かれている. CopyEnhMetafileはメタファイルのコピーをヒープ上に作成するのだろうが(だから上記のコードで コピーができるのである),このAPIを使わずにグローバルヒープ上にメタファイルのコピーを作ってみよう.

手順としては,

1. メタファイルのデータを格納するのに必要なメモリサイズを取得
2. 1で取得したサイズの領域をグローバルヒープ上に確保
3. データを2で確保した領域にコピー
4. コピーしたデータからメモリメタファイルのハンドルを生成
5. クリップボードにコピー

という具合になる. メタファイルデータのサイズ取得およびコピーはGetEnhMetaFileBits APIを用いる. このAPIのプロトタイプは

function GetEnhMetaFileBits( p1: HENHMETAFILE; p2: UINT; p3: PByte ): UINT; stdcall;

p1は拡張メタファイルのハンドルであり,p2はコピーする領域のサイズ,p3は その領域のアドレスである. p3にnilを指定すればコピーに必要な領域のサイズが取得できる. 一方,グローバルヒープに領域を確保するにはGlobalAlloc APIを用いる. こちらのプロトタイプは

function GlobalAlloc( uFlags: UINT; dwBytes: DWORD ): HGLOBAL; stdcall;

であり,uFlagsに領域の属性を,dwBytesに領域のサイズをそれぞれ指定する. クリップボードに渡すデータを格納する場合は,uFlagsにGMEM_MOVEABLEおよびGMEM_DDESHAREを 設定しなければならない. GlobalAllocはメモリの確保に成功すると,確保されたメモリオブジェクトのハンドルを返す. これは確保されたメモリ領域の先頭アドレスではないことに注意しなければならない. メモリオブジェクトのハンドルをGlobalLock APIに渡せば実際のアドレスを取得することができる.

また,コピーしたメタファイルのデータからメモリメタファイルのハンドルを生成するには SetEnhMetafileBits APIを用いる. プロトタイプは

function SetEnhMetaFileBits( p1: UINT; p2: PChar ): HENHMETAFILE; stdcall;

であり,p1にメタファイルデータを含む領域のサイズを,p2にそのアドレスを渡す. ハンドルの生成に成功するとそのハンドルが返ってくる.

具体的なコードは以下のようになる.

procedure SaveToClipAsEMF( Metafile: TMetafile );
var
  hMetafilePict: HGLOBAL;
  h: HENHMETAFILE;
  Size: Integer;
  Buffer: Pointer;
begin
   Size := GetEnhMetaFileBits( Metafile.Handle, 0, nil );
   hMetafilePict := GlobalAlloc( GMEM_MOVEABLE or GMEM_DDESHARE, Size );
   try
     Buffer := GlobalLock( hMetafilePict );
     GetEnhMetaFileBits( Metafile.Handle, Size, Buffer );
     h := SetEnhMetafileBits( Size, Buffer );
     GlobalUnlock( hMetafilePict );
     Clipboard.SetAsHandle( CF_ENHMETAFILE, h );
   except
     GlobalFree( hMetafilePict );
   end;
end;

上でも書いたように,クリップボードにコピーした後のデータの所有権はOSにあるので, GlobalAllocで確保した領域を開放する必要はない. ここでは,try...except...を使ってエラーが発生したときのみ領域を開放するようにしている.


お問い合わせはメールにて: akasaka@klc.ac.jp

戻る
SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送