Direct2D キャンバスの使用
描画用キャンバスの使用 への移動
Microsoft Windows 7 では、Direct2D 技術を使ってレンダリングや描画を行うキャンバス オブジェクトを作成するための、新しい機能が導入されています。独自に大量の描画をアプリケーションで行うような環境では、Direct2D を使用すると有益な場合があります。Direct2D は描画操作をすべて、CPU ではなく GPU に転送します。つまり、アプリケーションではより多くの処理能力を利用できることになります。このトピックでは、Delphi アプリケーションでこの新しい Direct2D キャンバスを利用する方法を説明します。
目次
はじめに
Direct2D キャンバスをサポートしているのは Windows 7 だけです。そのため、Direct2D 対応のアプリケーションは必ず Windows 7 上で開発してください。Direct2D を使用するには、アプリケーションに次のユニットをインクルードする必要があります。
- Vcl.Direct2D。TDirect2DCanvas などの 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 リソースを取得し解放しなければならないからです。関連する Pen、Brush、Font のオブジェクトは、そのオブジェクトを作成した元の TDirect2DCanvas インスタンスと一緒でないと利用できないため、解放する必要があります。この方法の長所は、従来の TCanvas を TDirect2DCanvas と一緒に使えることです。この 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 の互換性
TCanvas と TDirect2DCanvas は共通の上位クラス TCustomCanvas から派生しています。このクラスでは、両方のキャンバスの実装でサポートしなければならない高レベルの機能が定義されています。ただし、TDirect2DCanvas が実装しない機能もあります。たとえば TCustomCanvas.Pixels プロパティがそれに該当しますが、それは Direct2D キャンバスではピクセル情報を取得できないためです。どの関数がサポートされ、どの関数がサポートされないかについては、Vcl.Direct2D.TDirect2DCanvas の API の説明を参照してください。
どちらのキャンバスも、Pen や Brush や Font といった、関連するグラフィック オブジェクトの概念を持っています。これらのオブジェクトはどちらのキャンバスの実装でも公開されていますが、その実装方法はまるで異なるため、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 オブジェクトの色を設定しなければなりません。TCustomCanvas は Pen プロパティを公開していないため、この機能にアクセスするにはオブジェクトのキャストに頼らなければなりません。GDI の Pen と Direct2D の Pen はまったく別のものであることに注意してください。この 2 つは共通の上位クラスを持ちませんが、たとえば GDI のペンを Direct2D のペンに代入することは可能です。
Direct2D 対応コンポーネントの開発
このセクションでは、Direct2D または GDI のキャンバスを使用するカスタム ペイント ボックスの開発方法を説明します。この TAcceleratedPaintBox というコンポーネントは、Vcl.ExtCtrls.TPaintBox から派生したものではありません。TPaintBox 自体が TWinControl ではなく TControl から派生しているためです。これは重要な決定要因となります。TControl から派生したコントロールは、専用の Direct2D キャンバスを作成するのに必要なウィンドウ ハンドルを持ちません。
クラス定義
開発対象のコントロールは、Vcl.Controls.TCustomControl(Vcl.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;