Direct2D キャンバスの使用

提供: RAD Studio
移動先: 案内検索

描画用キャンバスの使用 への移動


Microsoft Windows 7 では、Direct2D 技術を使ってレンダリングや描画を行うキャンバス オブジェクトを作成するための、新しい機能が導入されています。独自に大量の描画をアプリケーションで行うような環境では、Direct2D を使用すると有益な場合があります。Direct2D は描画操作をすべて、CPU ではなく GPU に転送します。つまり、アプリケーションではより多くの処理能力を利用できることになります。このトピックでは、Delphi アプリケーションでこの新しい Direct2D キャンバスを利用する方法を説明します。


はじめに

Direct2D キャンバスをサポートしているのは Windows 7 だけです。そのため、Direct2D 対応のアプリケーションは必ず Windows 7 上で開発してください。Direct2D を使用するには、アプリケーションに次のユニットをインクルードする必要があります。

  • Vcl.Direct2DTDirect2DCanvas などの VCL ラッパー クラスを公開しています。
  • D2D1。Microsoft Direct2D API のヘッダー変換が含まれています。

不可欠なクラスは VCL で提供されていますが、Direct2D キャンバスを有益なものにしている拡張機能の多くを利用するには Direct2D インターフェイスを使用する必要があることに注意してください。

通常の VCL コントロールは、デフォルトでは Direct2D に対応していません。GDI ベースの従来の Vcl.Graphics.TCanvas オブジェクトを使用しています。新技術を使用するには、コントロールに Vcl.Direct2D.TDirect2DCanvas のサポートを明示的に追加する必要があります。

Direct2D キャンバスがサポートされているかの確認

便宜を図るため、TDirect2DCanvas では、アプリケーションを実行するオペレーティング システムが Direct2D キャンバスをサポートしているかどうかを判断するための Supported というクラス メソッドをサポートしています。それでも、状況によっては Direct2D キャンバスの作成中に例外が送出される可能性があります。Direct2D が利用可能かどうかの確認は、Direct2D キャンバスを作成する直前に行うのが最善です。

 function Create2D2Canvas(): Boolean;
 begin
  Result := false;
 
  if TDirect2DCanvas.Supported then
  begin
    try
      FD2DCanvas := TDirect2DCanvas.Create(Handle);
      Result := true;
    except
    end;
  end;
 
 end;

Direct2D キャンバスの使用

Direct2D を利用する現実的な方法は 2 つあります。

  • 従来の GDI キャンバスと新しい Direct2D キャンバスとで描画面を共有する。
  • Direct2D キャンバスだけを使用する。

それぞれに長所と短所があるため、どちらの方法を使用すべきかは時々に判断する必要があります。


Direct2D キャンバスを GDI キャンバスと併用する

Direct2D を使い始める最も簡単な方法は、自分のフォームやコントロール上で独自の描画を実装することです。そのためには、OnPaint イベント ハンドラを記述するか、Paint メソッドをオーバーライドします。

 procedure T2D2Form.FormPaint(Sender: TObject);
 var
   LCanvas: TDirect2DCanvas;
 begin
   LCanvas := TDirect2DCanvas.Create(Canvas, ClientRect);
   LCanvas.BeginDraw;
 
   try
     { Drawing goes here }
     LCanvas.Brush.Color := clRed;
     LCanvas.Pen.Color := clBlue;
     LCanvas.Rectangle(100, 100, 200, 200);
 
   finally
     LCanvas.EndDraw;
     LCanvas.Free;
   end;
 end;

上記の例では、フォームの OnPaint イベントに割り当てられたイベント ハンドラ FormPaint をフォームで画面の描画が必要になるたびに、OnPaint が発生します。再描画のたびに新しい TDirect2DCanvas インスタンスが作成されることに注意してください。これが必要なのは、そのたびにフォームに関連付けられた実際のデバイス コンテキスト(HDC)が変わって、前に作成された TDirect2DCanvas インスタンスが無効になる可能性があるためです。

この方法は非常に単純ではありますが、実際には採用できない可能性があります。再描画のたびに Direct2D リソースを取得し解放しなければならないからです。関連する PenBrushFont のオブジェクトは、そのオブジェクトを作成した元の TDirect2DCanvas インスタンスと一緒でないと利用できないため、解放する必要があります。この方法の長所は、従来の TCanvasTDirect2DCanvas と一緒に使えることです。この 2 つのキャンバス オブジェクトは同じ描画面に対して描画を行いますが、同時に使用してもあまり問題は発生しません。

Direct2D キャンバスだけを使用する

もう 1 つの方法は、その所有者であるコンポーネントと存続期間が同じである TDirect2DCanvas のインスタンスを 1 つだけ作成したい場合に有効です。この場合、WM_PAINT および WM_SIZE の Windows メッセージを捕捉して、独自の描画ルーチンを実行しなければなりません。

 type
   T2D2Form = class(TForm)
   private
     FCanvas: TDirect2DCanvas;
 
   protected
     procedure CreateWnd; override; 
 
     procedure WMPaint(var Message: TWMPaint); message WM_PAINT; 
     procedure WMSize(var Message: TWMSize); message WM_SIZE;
   public
     property Canvas: TDirect2DCanvas read FCanvas;
 
   end;
 
 implementation
 
 {$R *.dfm}
 
 procedure T2D2Form.CreateWnd;
 begin
   inherited;
   FCanvas := TDirect2DCanvas.Create(Handle);
 end;
 
 procedure T2D2Form.WMPaint(var Message: TWMPaint);
 var
   PaintStruct: TPaintStruct;
 begin
   BeginPaint(Handle, PaintStruct);
 
   try
     FCanvas.BeginDraw;
     try
       Paint;
     finally
       FCanvas.EndDraw;
     end;
   finally
     EndPaint(Handle, PaintStruct); 
   end; 
 end;
 
 procedure T2D2Form.WMSize(var Message: TWMSize);
 begin
   if Assigned(FCanvas) then
     ID2D1HwndRenderTarget(FCanvas.RenderTarget).Resize(D2D1SizeU(ClientWidth, ClientHeight)); 
 
   inherited;
 end;

この例では、WM_PAINT および WM_SIZE の Windows メッセージに対して、カスタム メッセージ ハンドラを記述しています。ここでは VCL の描画メソッドを直接オーバーライドしているため、WM_PAINT では特別の処理を行う必要があります。WM_SIZE では、VCL メソッドを呼び出す前に、Direct2D キャンバスのサイズを調整する必要があります。最後に、Vcl.Direct2D.TDirect2DCanvas インスタンスを CreateWnd メソッド内で作成しています。これは、キャンバスに有効なウィンドウ ハンドルを関連付ける必要があるためです。

API の互換性

TCanvasTDirect2DCanvas は共通の上位クラス TCustomCanvas から派生しています。このクラスでは、両方のキャンバスの実装でサポートしなければならない高レベルの機能が定義されています。ただし、TDirect2DCanvas が実装しない機能もあります。たとえば TCustomCanvas.Pixels プロパティがそれに該当しますが、それは Direct2D キャンバスではピクセル情報を取得できないためです。どの関数がサポートされ、どの関数がサポートされないかについては、Vcl.Direct2D.TDirect2DCanvas の API の説明を参照してください。

どちらのキャンバスも、PenBrushFont といった、関連するグラフィック オブジェクトの概念を持っています。これらのオブジェクトはどちらのキャンバスの実装でも公開されていますが、その実装方法はまるで異なるため、TCustomCanvas ではグラフィック オブジェクトをまったく公開していません。そのため、GDI と Direct2D をサポートするコンポーネントを設計している場合に、TCustomCanvas オブジェクトを公開することは実際的ではありません。次の例はその限界を示しています。

 procedure TForm1.MyBoxPaint(Sender: TObject);
 begin
   { Assuming the Canvas property of TMyPaintBox is TCustomCanvas, 
     to support both canvas implementations }
 
   { Intermediate API to access graphic objects is different }
   if (MyBox.Canvas is TDirect2DCanvas) then
     (MyBox.Canvas as TDirect2DCanvas).Pen.Color := clRed
   else
     (MyBox.Canvas as TCanvas).Pen.Color := clRed;
 
   { Normal high-level API is available }
   MyBox.Canvas.MoveTo(0, 0);
   MyBox.Canvas.LineTo(MyBox.Width, MyBox.Height);
 end;

この例では、Pen オブジェクトの色を設定しなければなりません。TCustomCanvasPen プロパティを公開していないため、この機能にアクセスするにはオブジェクトのキャストに頼らなければなりません。GDI の Pen と Direct2D の Pen はまったく別のものであることに注意してください。この 2 つは共通の上位クラスを持ちませんが、たとえば GDI のペンを Direct2D のペンに代入することは可能です。

Direct2D 対応コンポーネントの開発

このセクションでは、Direct2D または GDI のキャンバスを使用するカスタム ペイント ボックスの開発方法を説明します。この TAcceleratedPaintBox というコンポーネントは、Vcl.ExtCtrls.TPaintBox から派生したものではありません。TPaintBox 自体が TWinControl ではなく TControl から派生しているためです。これは重要な決定要因となります。TControl から派生したコントロールは、専用の Direct2D キャンバスを作成するのに必要なウィンドウ ハンドルを持ちません。

クラス定義

開発対象のコントロールは、Vcl.Controls.TCustomControlVcl.Controls.TWinControl の派生クラス)から派生していて、次のようなコードになっています。

  TCustomAcceleratedPaintBox = class(TCustomControl)
  private
    FOnPaint: TNotifyEvent;
    FUseD2D: Boolean;
    FD2DCanvas: TDirect2DCanvas;
 
    function CreateD2DCanvas: Boolean;
 
    { Catching paint events }
    procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
    procedure WMSize(var Message: TWMSize); message WM_SIZE;
 
    { Set/Get stuff }
    procedure SetAccelerated(const Value: Boolean);
    function GetGDICanvas: TCanvas;
    function GetOSCanvas: TCustomCanvas;
  protected
    procedure CreateWnd; override;
    procedure Paint; override;
 
  public
    { Life-time management }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy(); override;
 
    { Public properties }
    property Accelerated: Boolean read FUseD2D write SetAccelerated;
    property Canvas: TCustomCanvas read GetOSCanvas;
    property GDICanvas: TCanvas read GetGDICanvas;
    property D2DCanvas: TDirect2DCanvas read FD2DCanvas;
 
    { The Paint event }
    property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
  end;

全体的なレイアウトやパブリック インターフェイスは、Vcl.ExtCtrls.TPaintBox で公開されているものを模倣しています。そのコンポーネントと簡単に取り換えられるコンポーネントを開発しようとしているためです。

初期化機能の実装

CreateD2DCanvas メソッドでは、TDirect2DCanvas インスタンスの作成を試み、それが成功したか失敗したかを示す論理値を返します。CreateD2DCanvas は、後で Direct2D キャンバスを初期化するときに使われます。

 function TCustomAcceleratedPaintBox.CreateD2DCanvas: Boolean;
 begin
   try
     FD2DCanvas := TDirect2DCanvas.Create(Handle);
   except
     { Failed creating the D2D canvas, halt! }
     Exit(false);
   end;
 
   Result := true;
 end;

CreateWnd は上位クラスのもののオーバーライドです。CreateWnd はコンポーネントの生成処理の中で自動的に呼び出されます。そのため、Direct2D 対応のキャンバスの作成はここで実装するのが最適です。

 procedure TCustomAcceleratedPaintBox.CreateWnd;
 begin
   inherited;
 
   { Try to create the custom canvas }
   if (Win32MajorVersion >= 6) and (Win32Platform = VER_PLATFORM_WIN32_NT) then
     FUseD2D := CreateD2DCanvas
   else
     FUseD2D := false;
 end;

FUseD2D という論理値フィールドは、後で GDI キャンバスか Direct2D キャンバスかを選択するときにコンポーネント内で使用します。

プロパティの取得アクセサ メソッド

GetGDICanvas は、従来の GDI キャンバスを返す、継承した Canvas プロパティにアクセスするために内部的に使用するものです。これは GDICanvas プロパティの取得アクセサ メソッドにもなっています。

 function TCustomAcceleratedPaintBox.GetGDICanvas: TCanvas;
 begin
   if FUseD2D then
     Result := nil
   else
     Result := inherited Canvas;
 end;

GetOSCanvas は、このコンポーネントが公開している Canvas プロパティの取得アクセサ メソッドです。Direct2D がサポートされているかを確認し、適切なキャンバス オブジェクトを返します。

 function TCustomAcceleratedPaintBox.GetOSCanvas: TCustomCanvas;
 begin
   if FUseD2D then
     Result := FD2DCanvas
   else
     Result := inherited Canvas;
 end;

SetAccelerated は、Accelerated プロパティの設定アクセサ メソッドです。ユーザーは、Direct2D キャンバスのサポートを実行時に有効にしたり無効にしたりすることができます。

 procedure TCustomAcceleratedPaintBox.SetAccelerated(const Value: Boolean);
 begin
   { Same value? }
   if Value = FUseD2D then
     Exit;
 
   if not Value then
   begin
     FUseD2D := false;
     Repaint;
   end else
   begin
     FUseD2D := FD2DCanvas <> nil;
 
     if FUseD2D then
       Repaint;
   end;
 end;

描画とサイズ変更

Paint メソッドは、基本的に TPaintBox.Paint メソッドの動作を複製しています。描画できるようにキャンバス オブジェクトの準備をし、ユーザーが指定した実際の描画を行うイベント ハンドラを呼び出します。この実装で気をつけなければならないのは、Direct2D に対応しているかどうかを確認してから D2DCanvas プロパティを使用していて、Direct2D がサポートされていない場合には GDICanvas に戻っていることです。

 procedure TCustomAcceleratedPaintBox.Paint;
 begin
   if FUseD2D then
   begin
     D2DCanvas.Font.Assign(Font);
     D2DCanvas.Brush.Color := Color;
  
     if csDesigning in ComponentState then
     begin
       D2DCanvas.Pen.Style := psDash;
       D2DCanvas.Brush.Style := bsSolid;
  
       D2DCanvas.Rectangle(0, 0, Width, Height);
     end;
   end else
   begin
     GDICanvas.Font.Assign(Font);
     GDICanvas.Brush.Color := Color;
  
     if csDesigning in ComponentState then
     begin
       GDICanvas.Pen.Style := psDash;
       GDICanvas.Brush.Style := bsSolid;
  
       GDICanvas.Rectangle(0, 0, Width, Height);
     end;
   end;
  
   if Assigned(FOnPaint) then FOnPaint(Self);
 end;

前のセクションで述べたように、VCL が提供する描画ルーチンをオーバーライドするには、WM_PAINT Windows メッセージのメッセージ ハンドラを実装することが大切です。この例の WMPaint の実装では、Direct2D キャンバスに対応している場合にのみ WM_PAINT メッセージを処理し、対応していなければ継承したメソッドにメッセージを渡しています。

 procedure TCustomAcceleratedPaintBox.WMPaint(var Message: TWMPaint);
 var
   PaintStruct: TPaintStruct;
 begin
   if FUseD2D then
   begin
     BeginPaint(Handle, PaintStruct);
     try
       FD2DCanvas.BeginDraw;
  
       try
         Paint;
       finally
         FD2DCanvas.EndDraw;
       end;
  
     finally
       EndPaint(Handle, PaintStruct);
     end;
   end else
     inherited;
 end;

WMSize メソッドは、WM_SIZE Windows メッセージを処理するために使われます。その目的は Direct2D キャンバスのサイズを更新することです。

 procedure TCustomAcceleratedPaintBox.WMSize(var Message: TWMSize);
 begin
   if FD2DCanvas <> nil then
     ID2D1HwndRenderTarget(FD2DCanvas.RenderTarget).Resize(D2D1SizeU(Width, Height));
  
   inherited;
 end;

関連項目