Utilisation du canevas Direct2D

De RAD Studio
Aller à : navigation, rechercher

Remonter à Utilisation des canevas pour dessiner


Microsoft Windows 7 a introduit une nouvelle fonctionnalité permettant la création d'objets de canevas qui utilisent la technologie Direct2D pour le rendu et le dessin. L'utilisation de Direct2D peut être bénéfique dans certaines circonstances, où une application effectue beaucoup de dessin personnalisé. Direct2D transmet toutes les opérations de dessin au GPU au lieu du CPU, ce qui signifie davantage de puissance disponible pour votre application. Cette rubrique indique comment profiter du nouveau canevas Direct2D dans votre application Delphi.


Premières étapes

Le canevas Direct2D est seulement supporté sur Windows 7. Assurez-vous que vous développez votre application orientée Direct2D sur cette version de Windows. Pour utiliser le canevas Direct2D, vous devez inclure les unités suivantes dans votre application :

  • Vcl.Direct2D, qui expose les classes wrapper VCL telles que TDirect2DCanvas.
  • D2D1, qui contient des traductions d'en-têtes pour l'API Microsoft Direct2D.

Il est important de savoir que, même si la VCL fournit les classes nécessaires, vous devrez toujours utiliser les interfaces Direct2D afin de bénéficier de la plupart des fonctionnalités avancées qui rendent le canevas Direct2D utile.

Les contrôles VCL normaux ne sont pas orientés Direct2D par défaut. Ils utilisent l'ancien objet Vcl.Graphics.TCanvas compatible GDI. Pour utiliser la nouvelle technologie, vous devez ajouter explicitement le support de Vcl.Direct2D.TDirect2DCanvas à vos contrôles.

Vérification du support du canevas Direct2D

Par souci de commodité, le TDirect2DCanvas supporte la méthode de classe Supported qui peut être utilisée pour déterminer si le système d'exploitation sur lequel l'application s'exécute supporte le canevas Direct2D. Une exception peut quand même être déclenchée sous certaines circonstances lors de la création du canevas Direct2D. Le meilleur moment pour vérifier la disponibilité de Direct2D est quand le canevas Direct2D est sur le point d'être créé :

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

Utilisation du canevas Direct2D

Il existe deux méthodes pratiques dans lesquelles vous pouvez profiter de Direct2D :

  • Partage de la surface de dessin entre le canevas GDI original et le nouveau canevas Direct2D.
  • Utilisation du canevas Direct2D exclusivement.

Les deux méthodes ont leurs propres avantages et désavantages. Vous pouvez ainsi décider de la meilleure solution à utiliser à l'instant.


Utilisation du canevas Direct2D avec le canevas GDI

La façon la plus facile de commencer l'utilisation de Direct2D est d'implémenter un dessin personnalisé sur votre fiche ou contrôle. Cela est accompli en écrivant un gestionnaire d'événements OnPaint ou en redéfinissant la méthode 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;

L'exemple précédent utilise un gestionnaire d'événements FormPaint assigné à l'événement OnPaint de la fiche. OnPaint est déclenché à chaque fois que la fiche doit dessiner sa surface. Sachez que, pour chaque redessin, une nouvelle instance de TDirect2DCanvas est créée. C'est nécessaire car le contexte de périphérique réel (HDC) associé à la fiche peut changer à chaque fois, invalidant toutes les instances de TDirect2DCanvas préalablement créées.

Alors que cette méthode est la plus simple, elle peut être en pratique inacceptable, car les ressources Direct2D doivent être acquises et libérées à chaque redessin. Les objets Pen, Brush et Font doivent être libérés, car ils ne peuvent être utilisés que par l'instance de TDirect2DCanvas originale qui les a créés. Les avantages de cette méthode sont que vous pouvez utiliser le TCanvas original avec TDirect2DCanvas. Même si les deux objets de canevas dessinent sur la même surface, vous pouvez les utiliser en même temps sans trop de problème.

Utilisation du canevas Direct2D exclusivement

La seconde méthode est utile quand vous voulez créer une instance unique de TDirect2DCanvas, dont le cycle de vie est lié au composant propriétaire. Dans ce cas, vous devez intercepter les messages Windows WM_PAINT et WM_SIZE pour effectuer vos propres routines de dessin :

 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;

Dans l'exemple précédent, nous avons écrit des gestionnaires de messages personnalisés pour les messages Windows WM_PAINT et WM_SIZE. WM_PAINT doit être traité de façon personnalisée, car nous redéfinissons directement les méthodes de dessin VCL. WM_SIZE est nécessaire pour ajuster la taille du canevas Direct2D avant l'appel des méthodes VCL. Enfin, nous créons l'instance de Vcl.Direct2D.TDirect2DCanvas dans la méthode CreateWnd, car nous devons disposer d'un handle de fenêtre valide pour y associer le canevas.

Compatibilité de l'API

TCanvas et TDirect2DCanvas dérivent tous deux d'un ancêtre commun, TCustomCanvas, qui définit la fonctionnalité de haut niveau que les deux implémentations de canevas doivent supporter. Bien que dans certains cas, TDirect2DCanvas n'implémente pas certaines fonctions, par exemple la propriété TCustomCanvas.Pixels; il n'y a aucun moyen d'obtenir des informations de pixel pour les canevas Direct2D. Vous devez consulter la documentation de l'API relative à Vcl.Direct2D.TDirect2DCanvas pour voir les fonctions qui sont supportées et celles qui ne le sont pas.

Les deux canevas ont la notion des objets graphiques associés tels que Pen, Brush et Font. Tandis que les deux implémentations de canevas exposent ces objets, ils sont implémentés dans des façons complètement distinctes, et ainsi TCustomCanvas n'expose pas du tout les objets graphiques. Il est ainsi pas pratique d'exposer un objet TCustomCanvas si vous concevez un composant qui doit supporter GDI et Direct2D. L'exemple suivant démontre la limitation :

 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;

Dans l'exemple précédent, nous avons défini la couleur de l'objet Pen. Puisque TCustomCanvas n'expose pas une propriété Pen, nous avons eu recours au transtypage d'objet pour accéder à cette fonctionnalité. Sachez que Pen GDI et Pen Direct2D sont complètement différents. Ils ne partagent pas un ancêtre commun ; mais vous pouvez par exemple assigner un crayon GDI à un crayon Direct2D.

Développement d'un composant orienté Direct2D

Cette section montre comment développer une boîte à peindre personnalisée qui utilise le canevas Direct2D ou le canevas GDI. Le composant, appelé TAcceleratedPaintBox, ne dérive pas de Vcl.ExtCtrls.TPaintBox, car TPaintBox dérive lui-même de TControl et pas de TWinControl. C'est un facteur de décision important ; les contrôles dérivés de TControl n'ont pas de handle de fenêtre, qui est nécessaire pour créer un canevas Direct2D dédié.

La définition de classe

Le contrôle développé est dérivé de Vcl.Controls.TCustomControl (qui à son tour dérive de Vcl.Controls.TWinControl) et ressemble à ceci :

  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;

La disposition générale et l'interface publique tentent d'imiter celles exposées par Vcl.ExtCtrls.TPaintBox.

Implémentation de la fonctionnalité d'initialisation

La méthode CreateD2DCanvas tente de créer une instance de TDirect2DCanvas et renvoie une valeur booléenne indiquant si cela s'est bien passé ou a échoué. CreateD2DCanvas est utilisé par la suite pour initialiser le canevas 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 est redéfinie depuis la classe ancêtre. CreateWnd is invoquée automatiquement dans le processus de création du composant. C'est l'emplacement parfait pour implémenter la création du canevas orienté 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;

Le champ booléen FUseD2D est utilisé par la suite dans le composant pour sélectionner le canevas GDI ou le canevas Direct2D.

Méthodes Getter des propriétés

GetGDICanvas est utilisée en interne pour accéder à la propriété Canvas héritée qui renvoie le canevas GDI original. Elle sert aussi de méthode Getter de la propriété GDICanvas.

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

GetOSCanvas est la méthode Getter de la propriété Canvas exposée par ce composant. Elle vérifie si le support Direct2D est disponible et renvoie l'objet de canevas approprié.

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

SetAccelerated est la méthode Setter de la propriété Accelerated. L'utilisateur peut activer ou désactiver le support du canevas Direct2D à l'exécution.

 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;

Dessin et redimensionnement

La méthode Paint reproduit en fait le comportement de la méthode TPaintBox.Paint. Elle prépare l'objet de canevas pour le dessin et invoque le gestionnaire d'événements fourni par l'utilisateur qui fera le dessin réel. Une chose à savoir dans cette implémentation est qu'elle vérifie si Direct2D est activé et utilise la propriété D2DCanvas, revenant à GDICanvas si Direct2D n'est pas supporté.

 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;

Comme décrit dans les sections précédentes, il est important d'implémenter un gestionnaire de messages pour le message Windows WM_PAINT, afin de redéfinir les routines de dessin fournies par la VCL. Notre implémentation de WMPaint traite le message WM_PAINT seulement si le canevas Direct2D est activé ; sinon, le message est passé à la méthode héritée.

 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;

La méthode WMSize est utilisée pour traiter le message Windows WM_SIZE. Son objectif est de mettre à jour la taille du canevas Direct2D.

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

Voir aussi