Effets de transition avec Lazarus et BGRABitmap

7. Le composant TGVTransition complet

Après l'étude de nombreuses transitions, nous avons commencé à implémenter le composant TGVTransition : les interpolations, l'opacité et la vitesse devraient être maîtrisées ; la structure générale du composant a été fixée. Ce tutoriel propose à présent une implémentation complète des fonctionnalités attendues pour des transitions d'image à image avec la bibliothèque BGRABitmap installée dans Lazarus.

2 commentaires Donner une note  l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Avant-propos

Si vous voulez suivre le tutoriel en construisant pas à pas le composant, il faut créer le paquet et un composant selon la méthode expliquée dans ce tutoriel. Les informations nécessaires pour renseigner les caractéristiques du paquet et du composant sont fournies au cours de ce chapitre. Notez cependant que le paquet est à la fois un paquet de conception et d'exécution.

Les paquets d'exécution contiennent toutes les unités dont l'application a besoin pour fonctionner.
Les paquets de conception contiennent les déclarations pour l'enregistrement des composants et des éditeurs de propriétés qui seront intégrés à l'EDI.
Les paquets de conception et exécution comprennent les deux précédents.

Si vous voulez concentrer votre attention sur les problèmes liés au sujet des transitions d'image à image, vous pouvez partir du squelette fourni dans le premier répertoire d'exemple. Notez que le paquet BGRABitmapPack fait partie des paquets requis et qu'il faut par conséquent l'ajouter au paquet en cours de construction.

Afin de ne pas alourdir inutilement le texte qui suit, le code de toutes les méthodes n'est pas fourni. Seules les plus intéressantes sont détaillées. Il vous est donc demandé de vous reporter aux fichiers proposés à chaque étape pour voir la totalité des changements opérés.

Les programmes de test sont présents dans le répertoire exemples accompagnant le tutoriel.

II. Création du paquet et squelette du composant

[Exemple BGRABitmap 45]

Le squelette du composant s'appuie sur le fichier transitionstypes.pas déjà décrit dans le tutoriel précédent et qui contient les déclarations indispensables à son bon fonctionnement. Il fait de même appel au fichier defines.inc qui, comme annoncé, contient les directives de compilation. Ces deux fichiers sont disponibles dans le répertoire des exemples.

La première décision à prendre en matière de nouveau composant est de choisir l'ancêtre le plus adapté. Dans notre cas, il faut partir d'un contrôle graphique puisque l'affichage des images est au cœur des fonctionnalités offertes. La classe TGraphicControl est la candidate retenue : servant de base aux composants tels que TImage ou TPaintBox, elle a les fonctionnalités requises, en particulier un canevas pour le dessin.

Nous pouvions aussi envisager de fonder notre composant sur un composant comme TPanel (ou son ancêtre immédiat TCustomPanel) qui présente l’avantage d'offrir d'origine une surface opaque et la possibilité d'intégrer d'autres composants, mais qui peut paraître trop général pour se spécialiser dans le dessin.

La deuxième décision concerne l'icône associée au composant pour son affichage dans la palette. Ici, nous l'avons intégrée dans un fichier .lrs nommé transitions.lrs et appelé dans la partie initialization de l'unité d'accueil du composant. Cette icône est contenue dans le fichier initial nommé TGVTransition.png.

La dernière décision consiste à choisir les propriétés héritées des ancêtres et que nous souhaitons rendre publiques. Il ne faudrait en effet pas que l'utilisateur ait accès à des propriétés inexploitables, voire nocives !

Les ancêtres possèdent souvent des propriétés cachées par la directive protected que vous pouvez rendre accessibles à la demande en les énumérant, précédées de property, dans une partie public ou published.

Comme un constructeur et un destructeur seront nécessaires, ne serait-ce que pour instancier et détruire les images de travail, nous prévoirons aussi les squelettes des méthodes spéciales Create et Destroy.

Ensuite, nous compléterons notre classe avec quelques fonctions utiles : une fonction statique de classe Copyright pour le copyright, une fonction Count pour renvoyer le nombre de transitions disponibles et une propriété à lecture seule Version pour récupérer la version en cours du composant. Dans la section protected, nous avons aussi ajouté une procédure virtuelle appelée Change afin de centraliser les modifications apportées à l'état du composant. Le corps de cette procédure est pour l'instant vide, mais il sera évidemment renseigné au moment de l'implémentation des notifications.

Enfin, dans la partie interface de l'unité, nous déclarerons une procédure Register pour l'enregistrement de notre composant. Son implémentation sera présente dans la partie implementation.

Voici le code proposé :

 
Sélectionnez
unit transitions;

{$I defines.inc}

interface

uses
  Classes, SysUtils, Controls, ExtCtrls, Graphics, Types,
  BGRABitmapTypes, BGRABitmap,
  transitionstypes; // liste déjà complète, même si inutile à ce stade

type

  { TGVTransition }

  TGVTransition = class(TGraphicControl)
  strict private
    function GetVersion: string;
  protected
    procedure Change; virtual;
  public
    // copyright du composant
    class function Copyright: string; static;
    // création du composant
    constructor Create(AOwner: TComponent); override;
    // destruction du composant
    destructor Destroy; override;
    // nombre de transitions
    function Count: Integer;
  published
    // propriétés héritées
    property Color;
    property ParentColor;
    property Align;
    property Anchors;
    property AutoSize;
    property BorderSpacing;
    property Constraints;
    property DragCursor;
    property DragMode;
    property Enabled;
    property OnChangeBounds;
    property OnClick;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnPaint;
    property OnResize;
    property OnStartDrag;
    property ParentShowHint;
    property PopupMenu;
    property ShowHint;
    property Visible;

    // version du composant
    property Version: string read GetVersion;

  end;

procedure Register;


implementation

uses
  LResources;

procedure Register;
begin
  RegisterComponents('gvsoft', [TGVTransition]);
end;

{ TTransition }

function TGVTransition.GetVersion: string;
// *** version du composant ***
begin
  Result := C_GVVersionSt;
end;

constructor TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
  inherited Create(AOwner);
end;

destructor TGVTransition.Destroy;
// *** destruction du composant ***
begin
  inherited Destroy;
end;

function TGVTransition.Count: Integer;
// *** nombre de transitions ***
begin
  Result := Ord(High(TGVTransitionType)) + 1;
end;

procedure TGVTransition.Change;
// *** notification de changement d'état ***
begin

end;

class function TGVTransition.Copyright: string;
// *** copyright ***
begin
  Result := C_GVVersionSt +  ' - © Gilles Vasseur';
end;

initialization
  {$I transitions.lrs}
end.

Si ce code est compilable, il ne fournit aucune fonctionnalité vraiment intéressante. À nous de le compléter !

Vous avez des doutes sur l'utilisation de private, protected, public, published et leurs dérivés ? Je vous conseille la lecture de mon tutoriel sur la programmation orientée objet, en particulier la partie concernant la notion de portée.

III. Ajout des propriétés essentielles

Nous allons à présent déclarer et implémenter les propriétés essentielles, qui ont pour la plupart déjà été décrites précédemment.

[Exemple BGRABitmap 46]

III-A. Les nouvelles propriétés actives

La partie interface de l'unité du composant indique précisément les déclarations à faire :

 
Sélectionnez
interface

uses
  Classes, SysUtils, Controls, ExtCtrls, Graphics, Types,
  BGRABitmapTypes, BGRABitmap,
  transitionstypes;

type

  TGVTransitionStateEvent = procedure(Sender: TObject; State: TGVTransitionState;
    Trans: TGVTransitionType; Step: Integer) of object;

  { TGVTransition }

  TGVTransition = class(TGraphicControl)
  strict private
    fSmooth: TGVSmooth;
    fSpecialEffect: TGVSpecialEffect;
    fAutoStrips: boolean;
    fHorizontalStrips, fVerticalStrips: Integer;
    fInterpolation: TGVInterpolation;
    fOpacityState: TGVOpacityState;
    fSpeed: Integer;
    fTransition: TGVTransitionType;
    fStep: Integer;
    fInc: Integer;
    fLastLoop: Integer;
    function GetVersion: string;
    procedure SetAutoStrips(AValue: boolean);
    procedure SetHorizontalStrips(AValue: Integer);
    procedure SetInterpolation(AValue: TGVInterpolation);
    procedure SetOpacityState(AValue: TGVOpacityState);
    procedure SetSmooth(AValue: TGVSmooth);
    procedure SetSpecialEffect(AValue: TGVSpecialEffect);
    procedure SetSpeed(AValue: Integer);
    procedure SetTransition(AValue: TGVTransitionType);
    procedure SetVerticalStrips(AValue: Integer);
    function DestinationOpacity: Integer; inline;
    function SourceOpacity: Integer; inline;
  protected
    function ComputeInterpolation(AStart, AEnd: Single): Single; virtual;
    function ComputeInterpolationInt(AStart, AEnd: Single): Integer;
    procedure Change; virtual;

    // liste des transitions

  public
    // copyright du composant
    class function Copyright: string; static;
    // créatiton du composant
    constructor Create(AOwner: TComponent); override;
    // destruction du composant
    destructor Destroy; override;
    // nombre de transitions
    function Count: Integer;
    // opacité en cours (de 0 à 255)
    function GetOpacity(Up: Boolean = True): Integer;
    // calcul du nombre de bandes pour ASize
    function NumberOfStrips(ASize: Integer): Integer;
    // étape en cours
    property Step: Integer read fStep;

  published
    // propriétés héritées
    // […] voir le listing précédent pour la liste de ces propriétés

    // version du composant
    property Version: string read GetVersion;
    // interpolation d'image à image
    property Interpolation: TGVInterpolation read fInterpolation
      write SetInterpolation default intLinear;
    // vitesse de transition
    property Speed: Integer read fSpeed write SetSpeed default C_DefaultSpeed;
    // type d'opacité utilisée
    property Opacity: TGVOpacityState
      read fOpacityState write SetOpacityState default opaBoth;
    // type de transition
    property Transition: TGVTransitionType
      read fTransition write SetTransition default traNone;
    // mode automatique de calcul du nombre de bandes
    property AutoStrips: Boolean read fAutoStrips write SetAutoStrips;
    // nombre de bandes horizontales (modifiable si AutoStrips est à False)
    property HorizontalStrips: Integer read fHorizontalStrips
      write SetHorizontalStrips;
    // nombre de bandes verticales (modifiable si AutoStrips est à False)
    property VerticalStrips: Integer read fVerticalStrips write SetVerticalStrips;
    // effets spéciaux
    property SpecialEffect: TGVSpecialEffect read fSpecialEffect write SetSpecialEffect default speNone;
    // lissage des transitions
    property Smooth: TGVSmooth read fSmooth write SetSmooth default smoMedium;
  end;

Nous reconnaissons ce qui va nous permettre de traiter l'opacité, le nombre de bandes de certaines transitions, les interpolations, la vitesse, les effets spéciaux, le lissage des transitions et, bien sûr, le type de transition en cours.

D'une manière générale, il s'agit surtout de setters et de getters, les premiers ayant la plupart du temps une structure proche de celle-ci :

 
Sélectionnez
procedure TGVTransition.SetInterpolation(AValue: TGVInterpolation);
// *** détermination de l'interpolation ***
begin
  if fInterpolation = AValue then
    exit;
  fInterpolation := AValue;
  Change;
end;

Après avoir évité un changement sans intérêt, nous mettons à jour le champ visé et nous notifions le changement de l'état du composant par le biais de la méthode Change (toujours vide pour le moment).

III-B. Le traitement des bandes

Les variantes concernent quelques propriétés plus complexes. Ainsi, le traitement des bandes nécessite plusieurs méthodes listées ci-après :

 
Sélectionnez
procedure TGVTransition.SetAutoStrips(AValue: boolean);
// *** calcul automatique du nombre de bandes ***
begin
  if fAutoStrips = AValue then
    exit;
  fAutoStrips := AValue;
  if fAutoStrips then
  begin
    fHorizontalStrips := NumberOfStrips(ClientHeight);
    fVerticalStrips := NumberOfStrips(ClientWidth);
  end;
end;

procedure TGVTransition.SetHorizontalStrips(AValue: Integer);
// *** nombre de bandes horizontales ***
begin
  if (fHorizontalStrips = AValue) or AutoStrips then
    exit;
  fHorizontalStrips := Max(AValue, C_MinHorizontalStrips);
  Change;
end;

procedure TGVTransition.SetVerticalStrips(AValue: Integer);
// *** nombre de bandes verticales ***
begin
  if (fVerticalStrips = AValue) or AutoStrips then
    exit;
  fVerticalStrips := Max(AValue, C_MinVerticalStrips);
  Change;
end;

function TGVTransition.NumberOfStrips(ASize: Integer): Integer;
// *** calcul automatique du nombre de bandes ***
begin
  if ASize < C_MinComputeSize then
    Result := 1
  else
  if ASize < (C_MaxComputeSize div 5) then
    Result := 5
  else
  if ASize < (C_MaxComputeSize div 2) then
    Result := 10
  else
  if ASize < C_MaxComputeSize then
    Result := ASize div C_DefaultStripWidth
  else
    Result := 30;
end;

Que ce soit de manière automatique ou par prise en compte de la demande de l'utilisateur, des calculs sont en effet nécessaires pour adapter le nombre de bandes à la taille des images. La méthode essentielle est la fonction NumberOfStrips qui fait les calculs pour un résultat automatisé le plus pertinent possible.

III-C. Les interpolations

Sans surprise, les interpolations reprennent quant à elles les schémas construits dans le tutoriel précédent :

 
Sélectionnez
procedure TGVTransition.SetInterpolation(AValue: TGVInterpolation);
// *** détermination de l'interpolation ***
begin
  if fInterpolation = AValue then
    exit;
  fInterpolation := AValue;
  Change;
end;

function TGVTransition.ComputeInterpolation(AStart, AEnd: Single): Single;
// *** calcul d'une interpolation ***
var
  LDiff: Single;
  LSign, LStep: Integer;

  function Exponant(AExp: byte): Single; inline;
  begin
    Result := LDiff * Power(LStep, AExp) / Power(100, AExp);
  end;

  function DownExponant(AExp: byte): Single; inline;
  begin
    Result := LDiff - LDiff * Power(100 - LStep, AExp) /
      Power(100, AExp);
  end;

begin
  LDiff := Abs(AEnd - AStart);
  if AStart > AEnd then
    LSign := -1
  else
    LSign := 1;
  LStep := fStep * 100 div fLastLoop;
  case Interpolation of
    intLinear: Result := Exponant(1);
    intQuadratic: Result := Exponant(2);
    intCubic: Result := Exponant(3);
    intQuartic: Result := Exponant(4);
    intQuintic: Result := Exponant(5);
    intSinus: Result := Exponant(1) * sin(pi * LStep / 200);
    intSlowDownQuadratic: Result := DownExponant(2);
    intSlowDownCubic: Result := DownExponant(3);
    intSlowDownQuartic: Result := DownExponant(4);
    intSlowDownQuintic: Result := DownExponant(5);
    intSpring: Result := Exponant(1) * Power(cos(pi * LStep / 100), 2);
    intExpo: Result := Exponant(1) * (Power(2, LStep / 100) - 1);
    intSqrt: Result := Exponant(1) * (Sqrt(LStep) / 10);
    intBounceCos: Result := Exponant(1) + (cos(LStep * pi / 100) + 1) * Exponant(1);
    intSlowDownCos: Result := Exponant(1) + (cos(LStep * pi / 100) + 1) * LStep;
    intStepsCos: Result := Exponant(1) + Power((cos(LStep * pi / 100) + 1), 2) * LStep;
    intCosine: Result := Exponant(1) * (1-cos(LStep/100*Pi))*0.5;
    intHalfCosine: Result := Exponant(1) * ((1-cos(LStep/100*Pi))*0.25 + LStep/100*0.5);
  end;
  Result := AStart + LSign * Result;
end;

function TGVTransition.ComputeInterpolationInt(AStart, AEnd: Single): Integer;
// *** calcul d'une interpolation (entiers) ***
begin
  Result := Round(ComputeInterpolation(AStart, AEnd));
end;

La méthode ComputeInterpolation est la plus conséquente puisqu'elle est en charge du calcul des valeurs nécessaires.

III-D. Le lissage des transitions

Le lissage des transitions mérite que nous nous y arrêtions un peu. Voici la méthode setter qui lui est associée :

 
Sélectionnez
procedure TGVTransition.SetSmooth(AValue: TGVSmooth);
// *** lissage des transitions ***
begin
  if fSmooth = AValue then
    exit;
  fSmooth := AValue;
  case fSmooth of
    smoHigh: begin
      fInc := C_DefaultInc div 2;
      fLastLoop := C_DefaultLastLoop;
    end;
    smoMedium: begin
      fInc := C_DefaultInc;
      fLastLoop := C_DefaultLastLoop;
    end;
    smoNone, smoLow: begin
      fInc := C_DefaultInc;
      fLastLoop := C_DefaultLastLoop div 2;
    end;
    smoFast: begin
      fInc := C_DefaultInc * 2;
      fLastLoop := C_DefaultLastLoop div 2;
    end;
  end;
  Change;
end;

Comme nous l'avions décidé, il s'agit d'adapter le nombre de pas de la boucle de dessin d'une transition et la valeur de ce pas pour établir un équilibre entre la vitesse d'exécution et la fluidité du rendu de la transition.

III-E. L'opacité

L'opacité dépendant elle aussi du nombre de pas de la boucle d'affichage, mais aussi de ce à quoi elle est appliquée (source, destination ou les deux), elle est calculée selon le code suivant :

 
Sélectionnez
procedure TGVTransition.SetOpacityState(AValue: TGVOpacityState);
// *** détermination de l'opacité ***
begin
  if fOpacityState = AValue then
    exit;
  fOpacityState := AValue;
  Change;
end;

function TGVTransition.DestinationOpacity: Integer;
// *** opacité pour l'image de destination ***
begin
  Result := ifthen(fOpacityState in [opaDestination, opaBoth], GetOpacity, C_MaxOpacity);
end;

function TGVTransition.SourceOpacity: Integer;
// *** opacité de l'image d'origine ***
begin
  Result := ifthen(fOpacityState in [opaSource, opaBoth], GetOpacity(False),
    C_MaxOpacity);
end;

function TGVTransition.GetOpacity(Up: Boolean): Integer;
// *** calcul de l'opacité en fonction du pourcentage effectué ***
begin
  Result := ifthen(Up, fStep * C_MaxOpacity div fLastLoop, C_MaxOpacity -
    fStep * C_MaxOpacity div fLastLoop);
end;

Encore une fois, une méthode concentre l'essentiel du mécanisme : GetOpacity. Son fonctionnement est fondé sur un simple calcul de pourcentage.

III-F. Le constructeur de la classe

Enfin, cette étape de construction du composant doit adapter le constructeur Create afin de mettre à jour les valeurs par défaut :

 
Sélectionnez
constructor TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
  inherited Create(AOwner);
  fInterpolation := intLinear;
  fSpeed := C_DefaultSpeed;
  fTransition := traNone;
  fOpacityState := opaBoth;
  fSpecialEffect := speNone;
  fHorizontalStrips := NumberOfStrips(ClientHeight);
  fVerticalStrips := NumberOfStrips(ClientWidth);
  fSmooth := smoMedium;
  fInc := C_DefaultInc;
  fLastLoop := C_DefaultLastLoop;
end;

Il est en effet indispensable que ces valeurs soient en accord avec celles intégrées au fichier .lfm. Ce constructeur sera complété au fur et à mesure de nos besoins.

IV. Ajout des images et du timer

[Exemple BGRABitmap 47]

IV-A. Les images

Le composant ne serait rien s'il ne pouvait manipuler des images ! Nous allons donc, au cours de cette étape, implémenter les objets nécessaires à notre travail.

En premier lieu, il nous faut déclarer des champs pour les images elles-mêmes :

  • fPictureFrom et fPictureTo de type TPicture pour dialoguer avec la LCL ;
  • fBGRAFrom et fBGRATo de type TBGRABitmap pour conserver des originaux de tailles identiques des images de travail ;
  • fLBGRAFrom et fLBGRATo de type TBGRABitmap pour travailler localement suivant la transition en cours, permettant ainsi de perdre momentanément un peu de qualité au cours d'un redimensionnement par exemple ;
  • fLBGRAMask de type TBGRABitmap pour abriter un masque.

Ces sept champs figurent dans la partie strict private de la classe :

 
Sélectionnez
fLBGRAFrom, fLBGRATo, fLBGRAMask: TBGRABitmap;
fPictureFrom, fPictureTo: TPicture;
fBGRAFrom, fBGRATo: TBGRABitmap;

Ce nombre peut sembler élevé, mais la complexité de certaines transitions rend nécessaire cette profusion d'outils graphiques.

L'accès à ces champs n'est jamais direct. Les images d'origine et de destination sont en fait des propriétés définies ainsi dans la partie published de la classe TGVTransition :

 
Sélectionnez
// image d'origine
property PictureFrom: TPicture read GetPictureFrom write SetPictureFrom;
// image de destination
property PictureTo: TPicture read GetPictureTo write SetPictureTo;

Les setters et getters sont alors les suivants :

 
Sélectionnez
function TGVTransition.GetPictureFrom: TPicture;
// *** restitution de l'image d'origine ***
begin
  Result := fPictureFrom;
end;

function TGVTransition.GetPictureTo: TPicture;
// *** restitution de l'image de destination ***
begin
  Result := fPictureTo;
end;

procedure TGVTransition.SetPictureFrom(AValue: TPicture);
// *** acquisition de l'image d'origine ***
begin
  fPictureFrom.Assign(AValue);
  FreeAndNil(fBGRAFrom);
  fBGRAFrom := TBGRABitmap.Create(AValue.Bitmap);
  BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(ClientWidth, ClientHeight));
  Change;
end;

procedure TGVTransition.SetPictureTo(AValue: TPicture);
// *** acquisition de l'image de destination ***
begin
  fPictureTo.Assign(AValue);
  FreeAndNil(fBGRATo);
  fBGRATo := TBGRABitmap.Create(AValue.Bitmap);
  BGRAReplace(fBGRATo, fBGRATo.Resample(ClientWidth, ClientHeight));
  Change;
end;

Si les objets de type TPicture sont évidents à créer, ceux qui abritent les originaux normalisés en taille sont un peu plus complexes : ils sont libérés si nécessaire avant d'être recréés et redimensionnés.

Ces objets doivent être, selon les principes du Pascal Objet, créés et libérés proprement. Il faut par conséquent compléter ainsi le constructeur et le destructeur de notre classe :

 
Sélectionnez
constructor TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
  inherited Create(AOwner);
  fInterpolation := intLinear;
  fSpeed := C_DefaultSpeed;
  fTransition := traNone;
  fOpacityState := opaBoth;
  fSpecialEffect := speNone;
  fPictureFrom := TPicture.Create;
  fPictureTo := TPicture.Create;
  fBGRAFrom := TBGRABitmap.Create(ClientWidth, ClientHeight, BGRABlack);
  fBGRATo := fBGRAFrom.Duplicate as TBGRABitmap;
  fLBGRAFrom := fBGRAFrom.Duplicate as TBGRABitmap;
  fLBGRATo := fBGRAFrom.Duplicate as TBGRABitmap;
  fLBGRAMask := fBGRAFrom.Duplicate as TBGRABitmap;
  fHorizontalStrips := NumberOfStrips(ClientHeight);
  fVerticalStrips := NumberOfStrips(ClientWidth);
  fSmooth := smoMedium;
  fInc := C_DefaultInc;
  fLastLoop := C_DefaultLastLoop;
end;

destructor TGVTransition.Destroy;
// *** destruction du composant ***
begin
  fBGRAFrom.Free;
  fBGRATo.Free;
  fLBGRAFrom.Free;
  fLBGRATo.Free;
  fLBGRAMask.Free;
  fPictureTo.Free;
  fPictureFrom.Free;
  inherited Destroy;
end;

IV-B. Quelques améliorations de la base du composant

Nous allons profiter de cet apport pour améliorer notre classe en lui adjoignant quelques fonctionnalités supplémentaires.

Tout d'abord, nous allons définir une taille par défaut du composant grâce à la surcharge de la fonction de classe GetControlClassDefaultSize déclarée dans la partie protected :

 
Sélectionnez
class function GetControlClassDefaultSize: TSize; override;

Son implémentation est plutôt simple :

 
Sélectionnez
class function TGVTransition.GetControlClassDefaultSize: TSize;
// *** taille par défaut ***
begin
  Result := inherited GetControlClassDefaultSize;
  Result.cx := C_DefaultWidth;
  Result.cy := C_DefaultHeight;
end;

Pour que cette définition soit prise en compte, il faut l'invoquer dans le constructeur :

 
Sélectionnez
constructor TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
  inherited Create(AOwner);
  with GetControlClassDefaultSize do
    SetInitialBounds(0, 0, CX, CY);

Les composants visuels ont souvent besoin de redéfinir leur taille par défaut. Comme GetControlClassDefaultSize est une fonction de classe, elle peut être invoquée sans instancier la classe qui l'intègre (voir Les méthodes avec Free Pascal pour plus de précisions).

Nous pouvons aussi prévoir une méthode appelée DoReset pour remettre à zéro les différentes images à partir des composants TPicture enregistrés :

 
Sélectionnez
procedure TGVTransition.DoReset;
// *** remise à zéro ***
begin
  if Assigned(fPictureFrom) and Assigned(fPictureTo) then
  begin
    if (fPictureFrom.Height > 0) and (fPictureFrom.Width > 0) and
      (fPictureTo.Height > 0) and (fPictureTo.Width > 0) then
    begin
      FreeAndNil(fBGRAFrom);
      fBGRAFrom := TBGRABitmap.Create(fPictureFrom.Bitmap);
      BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(ClientWidth, ClientHeight));
      fLBGRAFrom.Assign(fBGRAFrom);
      FreeAndNil(fBGRATo);
      fBGRATo := TBGRABitmap.Create(fPictureTo.Bitmap);
      BGRAReplace(fBGRATo, fBGRATo.Resample(ClientWidth, ClientHeight));
      fLBGRATo.Assign(fBGRATo);
      FreeAndNil(fLBGRAMask);
      fLBGRAMask := TBGRABitmap.Create(ClientWidth, ClientHeight, BGRABlack);
      fStep := 0;
      Change;
    end;
  end;
end;

Cette méthode est déclarée virtuelle dans la partie protégée de la classe en vue de son adaptation à des descendants prévus pour d'autres situations.

Il est important de vérifier l'existence et la validité en termes de taille les images proposées : la fonction Assigned détecte l'assignation et les tests sur les dimensions vérifient que l'image n'ait pas de dimensions nulles, ce qui provoquerait des erreurs à l'exécution.

Toujours dans la perspective d'améliorer notre base de composant, nous allons lui adjoindre une nouvelle propriété baptisée Quality : elle choisira le filtre de redimensionnement le plus adapté à l'utilisateur. Nous en reproduisons ci-après le setter :

 
Sélectionnez
procedure TGVTransition.SetQuality(AValue: TGVQuality);
// *** qualité des transformations (homothéties) ***
begin
  if fQuality = AValue then
    exit;
  fQuality := AValue;
  case fQuality of
    quaLow: fLBGRAFrom.ResampleFilter := rfBox;
    quaNormal: fLBGRAFrom.ResampleFilter := rfHalfCosine;
    quaBest: fLBGRAFrom.ResampleFilter := rfBestQuality;
  end;
end;

Dans l'état actuel du composant final, cette propriété n'est guère utile, mais son importance pourra être développée en approfondissant la gestion des filtres.

IV-C. Le TTimer

Pour clore ce chapitre, nous allons introduire un élément essentiel du mécanisme du composant. Contrairement aux exercices qui ont émaillé les tutoriels précédents, nous allons en effet oublier la boucle de réalisation d'une transition pour adopter une solution plus adaptée à la programmation événementielle. Cette solution passe par l'utilisation d'un objet de type TTimer à déclarer avec les autres champs privés :

 
Sélectionnez
fTimer: TTimer;

Pour l'accompagner, dans la partie protégée, nous déclarerons une procédure apte à gérer l'événement déclenché par ce composant lorsqu'il a atteint la durée qui lui a été fixée :

 
Sélectionnez
procedure OnTimerEvent(Sender: TObject);

Cet événement sera utilisé pour effectuer les tâches relatives à une étape de l'affichage de la transition :

 
Sélectionnez
procedure TGVTransition.OnTimerEvent(Sender: TObject);
// *** événement lié au timer ***
begin
  // DoLoop; (en attente!)
end;

Encore une fois, le constructeur Create doit être complété :

 
Sélectionnez
constructor TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
  inherited Create(AOwner);
  // […] 
  fTimer := TTimer.Create(Self);
  fTimer.Enabled := False;
  fTimer.Interval := C_DefaultTimer - (fSpeed div 2);
  fTimer.OnTimer := @OnTimerEvent;

En revanche, comme l'objet est créé avec le paramètre Self, c'est le composant TGVTransition qui prendra en charge sa destruction : nous n'avons donc pas à nous en préoccuper.

Contrairement à Delphi, Free Pascal impose, dans son mode par défaut du Pascal Objet, l'utilisation du signe @ devant (entre autres) une méthode à affecter à un gestionnaire d'événement.

Un dernier détail et nous en aurons fini avec cette étape : la vitesse d'exécution dépend de la valeur donnée à la propriété Interval du TTimer. Il nous faut par conséquent modifier ainsi le setter de la propriété de gestion de la vitesse Speed :

 
Sélectionnez
procedure TGVTransition.SetSpeed(AValue: Integer);
// *** détermination de la vitesse ***
begin
  if fSpeed = AValue then
    exit;
  fSpeed := AValue;
  fTimer.Interval := C_DefaultTimer - (fSpeed div 2);
  Change;
end;

V. La méthode Paint et les procédures de calculs

[Exemple BGRABitmap 48]

Nos propriétés sont installées, le compteur pour les étapes de la réalisation d'une transition est en place, les images sont prêtes à être traitées : nous allons par conséquent nous occuper des grandes structures du traitement recherché.

V-A. La méthode Paint

Comme elle va être appelée à chaque étape, la méthode Paint est primordiale, mais elle est paradoxalement peu fournie, surtout pour la partie réservée à l'exécution :

 
Sélectionnez
procedure TGVTransition.Paint;
// *** dessin ***
var
  LX, LY: Integer;
begin
  if csDesigning in ComponentState then
  begin
    with Canvas do
    begin
      Font.Color := clBlue;
      Font.Size := 16;
      LX := (Self.Width - TextWidth(Self.Name)) div 2;
      if LX < 0 then
        LX := 0;
      LY := (Self.Height - TextHeight(Self.Name)) div 2;
      if LY < 0 then
        LY := 0;
      TextOut(LX, LY, Self.Name);
    end;
    exit;
  end;
  if Assigned(OnPaint) then
  begin
    Canvas.Font := Font;
    Canvas.Brush.Color := Color;
    inherited Paint;
  end;
  if Assigned(fLBGRAFrom) then
    fLBGRAFrom.Draw(Canvas, 0, 0);
end;

Seules les deux dernières lignes se rapportent à l'affichage proprement dit : si fLBGRAFrom est affectée, c'est elle qui contient l'image qui doit être reproduite sur le canevas du composant, donc visible.

Lorsque nous serons en mode conception (marqué par la présence de csDesigning dans ComponentState), nous nous contenterons d'écrire le nom du composant en son centre. En dehors de ces deux tâches, la méthode vérifie si un gestionnaire d'événement lui est rattaché et lance son exécution si besoin est.

V-B. Les méthodes de travail sur les images

Avant de dessiner, il faut avoir établi quelle image doit être affichée. Suivant les transitions, nous avons vu que le traitement différait. Il pouvait exiger des étapes que nous allons formaliser ainsi :

  • prétraitement indépendant des images (par exemple, pour les splines) ;
  • traitement de l'image d'origine (par exemple, pour les effets spéciaux ou son déplacement) ;
  • traitement avec masque de l'image de destination (pour de très nombreuses transitions) ;
  • traitement sans masque de l'image à afficher (par exemple pour les rotations et les redimensionnements).

Ces différents traitements donnent naissance à autant de méthodes définies comme suit :

 
Sélectionnez
    procedure InitTransition; virtual;
    procedure ComputeSource; virtual;
    procedure ComputeDestination; virtual;
    procedure ComputeFinalDraw; virtual;

Leur caractère virtuel dans la partie protégée de la classe assure qu'elles pourront être surchargées avec souplesse par d'éventuelles classes descendantes. Pour le moment, leur contenu restera vide : il contiendra des appels aux méthodes des transitions elles-mêmes, ainsi qu'à des méthodes associées aux images de travail. Le fichier inclus des transitions trouvera naturellement sa place après elles.

Leurs squelettes sont alors ceux-ci :

 
Sélectionnez
procedure TGVTransition.InitTransition;
// *** pré-traitement ***
begin

end;

procedure TGVTransition.ComputeSource;
// *** traitement de l'image source ***
begin

end;

procedure TGVTransition.ComputeDestination;
// *** traitement de l'image destination ***
begin

end;

procedure TGVTransition.ComputeFinalDraw;
// *** traitement final de l'image ***
begin

end;

// {$I transitions.inc }

VI. Les événements et les états

[Exemple BGRABitmap 49]

Afin d'assurer le bon fonctionnement de notre composant, nous allons le doter de gestionnaires d'événements et d'un automate permettant de tenir compte des différents états qu'il peut prendre.

VI-A. Les gestionnaires d'événements

Du point de vue des événements, nous en définirons trois :

 
Sélectionnez
TGVTransition = class(TGraphicControl)
  strict private
    // […]
    fTransitionChange, fLooping: TNotifyEvent;
    fStateChanged: TGVTransitionStateEvent;
    // […]
  protected
    // […]
    procedure Change; virtual;
    procedure StateChanged; virtual;
    procedure Looping; virtual;
    // […]
  
  published
    // […]
    // le composant a changé - gestionnaire de l'événement
    property OnChange: TNotifyEvent read fTransitionChange
      write fTransitionChange;
    // l'état de la transition a changé - gestionnaire de l'événement
    property OnStateChange: TGVTransitionStateEvent read fStateChanged
      write fStateChanged;
    // étape suivante - gestionnaire de l'événement
    property OnLoop: TNotifyEvent read fLooping write fLooping;
  end;

Il s'agit de réagir au changement du composant (par exemple à la modification de la vitesse choisie), à celui de son état (par exemple de l'état de démarrage à celui d'en cours d'exécution) ou encore au passage à l'étape suivante d'une transition. Chaque gestionnaire est associé à une méthode spécifique chargée de l'exécuter s'il est affecté : Change pour OnChange, StateChanged pour OnStateChange et Looping pour OnLoop.

À partir de ce mécanisme, moyennant des appels appropriés à ces méthodes spécifiques, les changements suivis seront notifiés à l'application appelante qui fera ce que bon lui semble des informations accessibles. Par exemple, comme nous avions déjà intégré un appel à Change dans tous les setters concernés, tout changement des propriétés suivies déclenchera un appel, s'il existe, au gestionnaire OnChange.

Il faut cependant prendre garde au temps consommé par ces méthodes traitées en dehors du composant : elles peuvent ralentir de manière significative ses performances ! Cet avertissement concerne tout particulièrement le gestionnaire OnLoop appelé à chaque étape de la transition.

Le traitement des états fait appel à une procédure de notification spéciale qui sera définie comme suit :

 
Sélectionnez
TGVTransitionStateEvent = procedure(Sender: TObject; State: TGVTransitionState;
    Trans: TGVTransitionType; Step: Integer) of object;

L'utilisateur aura ainsi accès immédiatement à l'état, mais aussi à la transition et à l'étape en cours.

L'implémentation des gestionnaires est on ne peut plus classique :

 
Sélectionnez
procedure TGVTransition.Change;
// *** notification de changement d'état ***
begin
  if Assigned(fTransitionChange) then
    fTransitionChange(Self);
end;

procedure TGVTransition.StateChanged;
// *** notification de changement d'état ***
begin
  if Assigned(fStateChanged) then
    fStateChanged(Self, fState, fTransition, fStep);
end;

procedure TGVTransition.Looping;
// *** notification de l'étape suivante ***
begin
  if Assigned(fLooping) then
    fLooping(Self);
end;

Si le gestionnaire est bien affecté, nous l'exécutons, sinon nous quittons la procédure sans agir. Le paramètre Self est là pour indiquer que c'est une instance de notre composant qui a effectué la notification.

VI-B. Le traitement des états

La propriété State est à première vue une propriété parmi les autres. Pourtant, c'est sur elle que repose le bon déroulement des opérations au sein du composant ! Considérant le champ privé fState, voici sa définition dans la partie publique :

 
Sélectionnez
// état en cours de la transition
property State: TGVTransitionState read fState write SetState default trsWaiting;

Pourquoi avoir choisi la partie publique et non la partie publiée comme pour la plupart des autres propriétés ? C'est tout simplement que cette propriété en lecture et écriture n'a aucun sens à la conception puisqu'elle ne marque que des états en cours d'exécution.

Son setter s'occupe en particulier de déclencher ou d'arrêter le TTimer, d'où l'exécution ou l'arrêt de la transition :

 
Sélectionnez
procedure TGVTransition.SetState(AValue: TGVTransitionState);
// *** changement d'état ***
begin
  if fState = AValue then
    exit;
  fState := AValue;
  if Assigned(fTimer) then
    fTimer.Enabled := (fState = trsRunning);
  StateChanged;
end;

Nous pouvons enfin définir la méthode DoLoop en suspens dans le gestionnaire d'événement OnTimer :

 
Sélectionnez
procedure TGVTransition.DoLoop;
// *** boucle de la transition ***
begin
  Inc(fStep, fInc);
  ComputeSource;
  ComputeDestination;
  ComputeFinalDraw;
  Looping;
  Repaint;
  if fStep >= fLastLoop then
  begin
    State := trsEnding;
    State := trsWaiting;
  end;
end;

Cette méthode suit l'ordre séquentiel d'une étape, depuis l'incrémentation de son numéro jusqu'à la sortie pour l'étape suivante ou le retour à un état d'attente si la transition est achevée. Il s'agit en effet d'effectuer les opérations dans cet ordre :

  • calculer l'image d'origine ;
  • calculer l'image de destination avec masque ;
  • calculer l'image définitive même si aucun masque n'est nécessaire ;
  • notifier le changement d'étape ;
  • demander un dessin immédiat de l'image en cours ;
  • ajuster l'état du composant au cas où le nombre d'étapes requis serait atteint.

Nous pouvons aussi enlever le commentaire dans ce gestionnaire :

 
Sélectionnez
procedure TGVTransition.OnTimerEvent(Sender: TObject);
// *** événement lié au timer ***
begin
  DoLoop;
end;

Enfin, grâce à ces états, nous pouvons restreindre l'usage de certaines propriétés. Par exemple, il est contre-indiqué de changer de transition en pleine exécution de l'une d'entre elles. Nous modifierons le code du setter en conséquence :

 
Sélectionnez
procedure TGVTransition.SetTransition(AValue: TGVTransitionType);
// *** détermination de la transition active ***
begin
  if (fTransition = AValue) or (fState = trsRunning) then
    exit;
  fTransition := AValue;
  Change;
end;

Nous aurions pu déclencher une exception au lieu d'ignorer ce changement de transition alors qu'une autre est en cours d'exécution, mais il nous a semblé préférable de laisser cette responsabilité au développeur. Après tout, c'est à lui de faire en sorte que l’utilisateur ne puisse pas sélectionner ce paramètre via l'interface homme/machine (IHM) fournie.

Reste bien entendu à ne pas oublier d'indiquer dans le constructeur la valeur par défaut de l'état :

 
Sélectionnez
constructor TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
  inherited Create(AOwner);
  // […]
  fState := trsWaiting;

VI-C. Les états en action

[Exemple BGRABitmap 50]

En tenant compte de l'état en cours, nous sommes à même d'obtenir une bonne maîtrise du composant. Pour vous en convaincre, nous allons définir dans la partie publique de notre classe une série de méthodes liées à des actions :

 
Sélectionnez
   // démarrage de la transition
    procedure Start;
    // arrêt de la transition
    procedure Stop;
    // remise à zéro du composant
    procedure Reset;
    // suspension de la transition en cours (si elle existe)
    procedure Suspend;
    // reprise de la transition suspendue (si elle existe)
    procedure Resume;
    // indicateur de transition en cours
    function IsRunning: Boolean;

Leur implémentation est très liée aux états possibles du composant :

 
Sélectionnez
procedure TGVTransition.Start;
// *** déclenchement de la transition ***
begin
  case fState of
    trsRunning: exit;
    trsSuspended: State := trsRestarting;
  else
    fStep := 0;
    InitTransition;
    State := trsStarting;
  end;
  State := trsRunning;
end;

procedure TGVTransition.Stop;
// *** arrêt d'une transition ***
begin
  if State = trsRunning then
  begin
    State := trsStopped;
    fStep := fLastLoop;
    State := trsWaiting;
  end;
end;

procedure TGVTransition.Reset;
// *** remise à zéro de l'affichage ***
begin
  DoReset;
  Repaint;
end;

procedure TGVTransition.Suspend;
// *** suspension de la transition ***
begin
  if fState = trsRunning then
    State := trsSuspended;
end;

procedure TGVTransition.Resume;
// reprise d'une animation ***
begin
  if fState = trsSuspended then
    Start;
end;

function TGVTransition.IsRunning: Boolean;
// *** transition en cours ? ***
begin
  Result := (fState = trsRunning);
end;

L'état le plus complexe est trsSuspended, car il demande d'être vigilant en cas de démarrage ou de redémarrage d'une transition. Il ne faudrait pas dans ce cas réinitialiser le compteur des boucles.

Un autre usage des états peut être illustré par la méthode Resize chargée de gérer le redimensionnement du composant. Pendant ce redimensionnement, il faut mettre la transition en pause pour recalculer, entre autres, les bandes peut-être inadaptées aux nouvelles dimensions.

Voici le code proposé :

 
Sélectionnez
procedure TGVTransition.Resize;
// *** le contrôle est redimensionné ***
var
  LState: TGVTransitionState;
begin
  LState := fState;
  State := trsPaused;
  try
    inherited Resize;
    if AutoStrips then
    begin
      fHorizontalStrips := NumberOfStrips(ClientHeight);
      fVerticalStrips := NumberOfStrips(ClientWidth);
    end;
    if not (csDesigning in ComponentState) and not (fState = trsRunning) then
      DoReset;
  finally
    State := LState;
  end;
end;

L'aspect intéressant de cette routine est la conservation de l'état dans la variable locale LState pour mettre le composant en mode pause jusqu'à la fin du traitement où l'état initial est récupéré.

VII. Le composant complet

[Exemple BGRABitmap 51]

Pour obtenir un composant opérationnel complet, il ne manque que deux étapes : l'implémentation de toutes les transitions et leur utilisation dans les méthodes laissées vides jusqu'à présent, qui auront en charge leur exploitation.

VII-A. L'implémentation des transitions

Pour ce qui est de l'implémentation des transitions, elle s'appuie sur le travail préparatoire effectué dans les tutoriels précédents. Les transitions sont même souvent d'un aspect plus simple que leurs équivalents antérieurs puisque la gestion des étapes est prise en charge en dehors des routines les implémentant et que l'utilisation des interpolations rend a priori le code plus lisible.

Voici la routine qui permet de déplacer l'image d'origine vers la droite :

 
Sélectionnez
procedure TGVTransition.DoRight;
// *** image vers la droite ***
begin
  fX := ComputeInterpolation(0, ClientWidth);
end;

On ne peut imaginer plus simple !

Voici la transition qui étend verticalement l’image de destination grâce à un masque :

 
Sélectionnez
procedure TGVTransition.DoVerticalExpand;
// *** VerticalExpand ***
begin
 fLBGRAMask.FillRectAntialias(ComputeInterpolation(ClientWidth / 2, 0), 0,
   ComputeInterpolation(ClientWidth / 2, ClientWidth), ClientHeight,
   BGRAWhite, False);
end;

Les rotations sont souvent regroupées par deux : celle qui tourne dans le sens des aiguilles d'une montre avec celle qui tourne dans le sens contraire. Un paramètre booléen permet alors de discriminer les deux cas.

Voici par exemple la rotation qui se déplace vers la droite :

 
Sélectionnez
procedure TGVTransition.DoRotateRight(CCW: boolean);
// *** CCWRotateRight et CWRotateRight ***
var
  L2Pi: Integer;
begin
  if CCW then
    L2Pi := -360
  else
    L2Pi := 360;
  fLBGRAFrom.PutImageAngle(ComputeInterpolation(0, ClientWidth / 2),
    ComputeInterpolation(ClientHeight, ClientHeight / 2),
    fLBGRATo, ComputeInterpolation(0, L2Pi), ClientWidth / 2,
    ClientHeight / 2, DestinationOpacity);
end;

Enfin, certaines méthodes restent plutôt complexes parce qu'elles utilisent plusieurs transformations. Par exemple, voici la transition qui se dirige vers la gauche tout en effectuant une rotation et en agrandissant l'image de destination :

 
Sélectionnez
procedure TGVTransition.DoRotateGrowLeft(CCW: Boolean);
// *** RotateGrowCCWLeft et RotateGrowCWLeft ***
var
  LBGRATemp: TBGRAAffineBitmapTransform;
begin
  LBGRATemp := TBGRAAffineBitmapTransform.Create(fLBGRATo);
  try
    LBGRATemp.RotateDeg(ifthen(CCW, ComputeInterpolation(0, -360),
      ComputeInterpolation(0, 360)));
    LBGRATemp.Scale(fStep / fLastLoop);
    LBGRATemp.Translate(ComputeInterpolation(ClientWidth, 0),
      0);
    fLBGRAFrom.Fill(LBGRATemp, dmDrawWithTransparency);
  finally
    LBGRATemp.Free;
  end;
end;

Comme quelques autres, elle a besoin d'une image temporaire supplémentaire pour arriver à ses fins.

Les transitions les plus originales ont même besoin de deux objets supplémentaires ! Voici par exemple la transition qui agrandit l'image de destination tout en créant un effet de tourbillon :

 
Sélectionnez
procedure TGVTransition.DoMultiTwirl;
// *** MultiTwirl ***
var
  LBGRATemp: TBGRAAffineScannerTransform;
  LTwirl: TBGRATwirlScanner;
begin
  LBGRATemp := TBGRAAffineScannerTransform.Create(fLBGRATo);
  try
    LBGRATemp.Scale(fStep / fLastLoop);
    LTwirl := TBGRATwirlScanner.Create(LBGRATemp,
      Point(ClientWidth div 2, ClientHeight div 2), (fLastLoop - fStep) *
      ifthen(ClientWidth > ClientHeight, ClientWidth, ClientHeight));
    try
      fLBGRAFrom.Fill(LTwirl, dmDrawWithTransparency);
    finally
      LTwirl.Free;
    end;
  finally
    LBGRATemp.Free;
  end;
end;

L'édition du fichier inclus transitions.inc montrera que certaines transitions sont inédites : il faut bien donner un peu de substance supplémentaire à ce tutoriel, non ?

VII-B. Les méthodes de traitement des images

Reste à compléter les méthodes chargées de l'exploitation de toutes ces transitions en attente.

InitTransition est de loin la plus courte. Son rôle se résume à faire les calculs en amont de l'exécution de la transition. Ce sont les splines qui occupent la place :

 
Sélectionnez
procedure TGVTransition.InitTransition;
// *** pré-traitement ***
begin
  case Transition of
    traSplinesLeft, traSplinesRight: DoPrepareSpline;
    traSplinesUp, traSplinesDown: DoPrepareSplineUpOrDown;
  end;
end;

ComputeSource s'occupe de l'image d'origine. D'une part, elle déplace l'image si nécessaire ; d'autre part, elle traite les effets spéciaux qui peuvent être appliqués :

 
Sélectionnez
procedure TGVTransition.ComputeSource;
// *** traitement de l'image source ***
var
  LBGRATemp: TBGRABitmap;
begin
  fX := 0;
  fY := 0;
  fLBGRAFrom.FillRect(ClientRect, Color);
  case Transition of
    traGrowPushUp, traLeaveUp, traPushUp: DoUp;
    traGrowPushDown, traLeaveDown, traPushDown: DoDown;
    traGrowPushLeft, traLeaveLeft, traPushLeft: DoLeft;
    traGrowPushRight, traLeaveRight, traPushRight: DoRight;
    traLeaveBottomRight: DoBottomRight;
    traLeaveBottomLeft: DoBottomLeft;
    traLeaveTopRight: DoTopRight;
    traLeaveTopLeft: DoTopLeft;
  end;
  // effets spéciaux
  case fSpecialEffect of
    speEmboss: LBGRATemp := fBGRAFrom.FilterEmboss(ComputeInterpolation(0, 180),
      ComputeInterpolationInt(64, 256)) as TBGRABitmap;
    speContour: LBGRATemp := fBGRAFrom.FilterContour as TBGRABitmap;
    speNegative: begin
      LBGRATemp := fBGRAFrom.Duplicate as TBGRABitmap;
      LBGRATemp.Negative;
    end;
    speLinearNegative: begin
      LBGRATemp := fBGRAFrom.Duplicate as TBGRABitmap;
      LBGRATemp.LinearNegative;
    end;
    speSwapRedBlue: begin
      LBGRATemp := fBGRAFrom.Duplicate as TBGRABitmap;
      LBGRATemp.SwapRedBlue;
    end;
    speSphere: LBGRATemp := fBGRAFrom.FilterSphere as TBGRABitmap;
    speRotate: LBGRATemp := fBGRAFrom.FilterRotate(PointF(ClientWidth / 2,
      ClientHeight/ 2), ComputeInterpolation(0, 360)) as TBGRABitmap;
    speGrayscale: begin
      LBGRATemp := fBGRAFrom.Duplicate as TBGRABitmap;
      LBGRATemp.InplaceGrayscale;
    end;
    spePixelate: LBGRATemp := fBGRAFrom.FilterPixelate(ComputeInterpolationInt(2, 24),
      True) as TBGRABitmap;
    speTwirl: LBGRATemp := fBGRAFrom.FilterTwirl(
      Point(ClientWidth div 2, ClientHeight div 2),
      ifthen(ClientWidth > ClientHeight, ComputeInterpolation(0, ClientWidth),
      ComputeInterpolation(0, ClientHeight))) as TBGRABitmap;
    speBlurMotion: LBGRATemp := fBGRAFrom.FilterBlurMotion(ComputeInterpolation(0, 15),
      ComputeInterpolation(0, 180), False) as TBGRABitmap;
  else
    LBGRATemp := fBGRAFrom.Duplicate as TBGRABitmap;
  end;
  try
    fLBGRAFrom.PutImage(Round(fX), Round(fY), LBGRATemp, dmDrawWithTransparency,
      SourceOpacity);
  finally
    LBGRATemp.Free;
  end;
end;

ComputeDestination s'occupe de l'image de destination. Sa principale caractéristique est l'application d'un masque produit par les routines appelées en amont :

 
Sélectionnez
procedure TGVTransition.ComputeDestination;
// *** traitement de l'image destination ***
begin
  fX := 0;
  fY := 0;
  fLBGRAMask.FillRectAntialias(0, 0, ClientWidth, ClientHeight, BGRABlack);
  case Transition of
    traUncoverDown: DoUncoverUpOrDown(False);
    traUncoverUp: DoUncoverUpOrDown(True);
    traUncoverLeft: DoUncover(False);
    traUncoverRight: DoUncover(True);
    traOverDown, traPushDown: DoOverUpOrDown(False);
    traOverUp, traPushUp: DoOverUpOrDown(True);
    traOverLeft, traPushLeft: DoOver(False);
    traOverRight, traPushRight: DoOver(True);
    traUncoverBottomRight: DoUncoverBottom(True);
    traUncoverBottomLeft: DoUncoverBottom(False);
    traUncoverTopRight: DoUncoverTop(True);
    traUncoverTopLeft: DoUncoverTop(False);
    traUncoverHorizontalBackAndForth: DoUncoverHorizontalBackAndForth;
    traUncoverVerticalBackAndForth: DoUncoverVerticalBackAndForth;
    traOverSlipBottomRight: DoOverSlipBottom(True);
    traOverSlipBottomLeft: DoOverSlipBottom(False);
    traOverSlipTopRight: DoOverSlipTop(True);
    traOverSlipTopLeft: DoOverSlipTop(False);
    traOverBottomRight: DoOverBottom(True);
    traOverBottomLeft: DoOverBottom(False);
    traOverTopRight: DoOverTop(True);
    traOverTopLeft: DoOverTop(False);
    traBottomRightExpand: DoBottomExpand(True);
    traBottomLeftExpand: DoBottomExpand(False);
    traTopRightExpand: DoTopExpand(True);
    traTopLeftExpand: DoTopExpand(False);
    traLeaveUp: DoLeaveUpOrDown(True);
    traLeaveDown: DoLeaveUpOrDown(False);
    traLeaveLeft: DoLeave(False);
    traLeaveRight: DoLeave(True);
    traBottomRightShrink, traLeaveBottomRight: DoLeaveBottom(True);
    traBottomLeftShrink, traLeaveBottomLeft: DoLeaveBottom(False);
    traTopRightShrink, traLeaveTopRight: DoLeaveTop(True);
    traTopLeftShrink, traLeaveTopLeft: DoLeaveTop(False);
    traHorizontalExpand: DoHorizontalExpand;
    traVerticalExpand: DoVerticalExpand;
    traHorizontalShrink: DoHorizontalShrink;
    traVerticalShrink: DoVerticalShrink;
    traRectExpand: DoRectExpand;
    traRectShrink: DoRectShrink;
    traEllipseExpand: DoEllipseExpand;
    traEllipseShrink: DoEllipseShrink;
    traDiskExpand: DoDiskExpand;
    traDiskShrink: DoDiskShrink;
    traCrossExpand: DoCrossExpand;
    traCrossShrink: DoCrossShrink;
    traShrinkExpandDown: DoShrinkExpandUpOrDown(False);
    traShrinkExpandUp: DoShrinkExpandUpOrDown(True);
    traTrianglesExpand: DoTrianglesExpand;
    traTrianglesShrink: DoTrianglesShrink;
    traMegaStar: DoMegaStar;
    traSplinesDown: DoSplinesDown;
    traSplinesUp: DoSplinesUp;
    traSplinesRight: DoSplinesRight;
    traSplinesLeft: DoSplinesLeft;
    traStripsDown: DoStripsUpOrDown(False);
    traStripsUp: DoStripsUpOrDown(True);
    traStripsRight: DoStrips(True);
    traStripsLeft: DoStrips(False);
    traStripsInterlacedLeftRight: DoStripsHorizontalInterlaced(True);
    traStripsInterlacedRightLeft: DoStripsHorizontalInterlaced(False);
    traStripsInterlacedUpDown: DoStripsInterlaced(True);
    traStripsInterlacedDownUp: DoStripsInterlaced(False);
    traRectUpDown: DoRectUpOrDown(False);
    traRectDownUp: DoRectUpOrDown(True);
    traWipeRight: DoWipe(True);
    traWipeLeft: DoWipe(False);
    traOverRightSin: DoOverSin(True);
    traOverLeftSin: DoOverSin(False);
    traBouncingRight: DoBouncing(True);
    traBouncingLeft: DoBouncing(False);
    traJumpRight: DoJump(True);
    traJumpLeft: DoJump(False);
    traFadeIn: DoFadeIn;
    //traDummy: DoDummy;
  else
    DoNegateMask;
  end;
  fLBGRATo.PutImage(Round(fX), Round(fY), fBGRATo, dmSet);
  fLBGRATo.ApplyMask(fLBGRAMask);
end;

Le masque est calculé pour toutes les transitions. Au cas où il ne serait pas utile, il est inversé par la méthode DoNegateMask dont le code est rudimentaire :

 
Sélectionnez
procedure TGVTransition.DoNegateMask;
// *** masque inversé ***
begin
  fLBGRAMask.Negative;
end;

Nous avons ainsi la certitude que toute l'image sera bien conservée. Ensuite, il suffit de recopier l'image originale fBGRATo dans celle de travail fLBGRATo et d'appliquer le masque fLBGRAMask à cette dernière.

Enfin, ComputeFinalDraw prend en charge le traitement final de l'image, en particulier si aucun masque n'a été appliqué :

 
Sélectionnez
procedure TGVTransition.ComputeFinalDraw;
// *** traitement final de l'image ***
begin
  case Transition of
    traCCWRotateRight: DoRotateRight(True);
    traCWRotateRight: DoRotateRight(False);
    traCCWCenterRotate: DoCenterRotate(True);
    traCWCenterRotate: DoCenterRotate(False);
    traCCWRotateLeft: DoRotateLeft(True);
    traCWRotateLeft: DoRotateLeft(False);
    traCWSpiral: DoSpiral(False);
    traCCWSpiral: DoSpiral(True);
    traGrowCenter: DoGrowCenter;
    traGrowBottomRight: DoGrowBottom(True);
    traGrowBottomLeft: DoGrowBottom(False);
    traGrowTopRight: DoGrowTop(True);
    traGrowTopLeft: DoGrowTop(False);
    traGrowCWRotate: DoGrowRotate(False);
    traGrowCCWRotate: DoGrowRotate(True);
    traGrowCenterCCWRotate: DoGrowCenterRotate(True);
    traGrowCenterCWRotate: DoGrowCenterRotate(False);
    traGrowPushRight, traGrowOverRight: DoGrowOver(True);
    traGrowPushLeft, traGrowOverLeft: DoGrowOver(False);
    traGrowPushUp, traGrowOverUp: DoGrowOverUpOrDown(True);
    traGrowPushDown, traGrowOverDown: DoGrowOverUpOrDown(False);
    traRotateCCWGrowLeft: DoRotateGrowLeft(True);
    traRotateCWGrowLeft: DoRotateGrowLeft(False);
    traRotateCCWGrowTopLeft: DoRotateGrowTopLeft(True);
    traRotateCWGrowTopLeft: DoRotateGrowTopLeft(False);
    traRotateCCWTopLeft: DoRotateTopLeft(True);
    traRotateCWTopLeft: DoRotateTopLeft(False);
    traRotateCCWBottomRight: DoRotateBottomRight(True);
    traRotateCWBottomRight: DoRotateBottomRight(False);
    traMappingDown: DoMappingDown;
    traMappingUp: DoMappingUp;
    traMultiTwirl: DoMultiTwirl;
    traTwirl: DoTwirl;
    traSphere: DoSphere;
    traCylinderRight: DoCylinderRight;
    traCylinderLeft: DoCylinderLeft;
    traMultiExpand: DoMultiExpand;
    traMultiExpandTwirl: DoMultiExpandTwirl;
    traDrawFromBounds: DODrawFromBounds;
    traOffsetTopLeft: DoOffset(False);
    traOffsetBottomRight: DoOffset(True);
    traPixelate: DoPixelate;
    traBlurMotion: DoBlurMotion;
    {$IFDEF Debug_Transitions}
    traDummy: DoDummy;
    {$ENDIF}
    else
      fLBGRAFrom.PutImage(0, 0, fLBGRATo, dmDrawWithTransparency, DestinationOpacity);
  end;

Les transitions sans masque opèrent directement sur l'image de travail fBGRAFrom alors que les autres transitions utilisent la méthode PutImage finale pour actualiser cette même image. L'essentiel est que fBGRAFrom contienne l'image à afficher : c'est, faut-il le rappeler, elle qui est invoquée dans Paint.

Finalement, toutes ces méthodes se résument à une longue suite de cas possibles auxquels sont appliquées quelques méthodes comme PutImage ou ApplyMask. Si nous ne tenons pas compte des méthodes implémentant les transitions dans le fichier inclus, les effets spéciaux sont les seuls à faire appel à des outils plus sophistiqués.

VII-C. Finition de la classe

La déclaration de la classe est affectée par ces modifications. Nous avons en effet besoin de quelques champs privés pour définir les coordonnées sur lesquelles s'appuieront les transitions pour dessiner :

 
Sélectionnez
fY, fX: Single;
fPts, fPtsTemp: array of TPointF;

Pour des résultats plus précis, les transitions du composant utilisent des nombres réels de type Single et non des entiers.

Bien sûr, Il faut aussi déclarer la liste des méthodes des transitions :

 
Sélectionnez
// liste des transitions
    procedure DoNegateMask;
    procedure DoPrepareSpline;
    procedure DoPrepareSplineUpOrDown;
    procedure DoUp;
    procedure DoDown;
    procedure DoRight;
    procedure DoLeft;
    procedure DoBottomRight;
    procedure DoBottomLeft;
    procedure DoTopRight;
    procedure DoTopLeft;
    procedure DoUncoverUpOrDown(Up: boolean);
    procedure DoUncover(ToRight: boolean);
    procedure DoOverUpOrDown(Up: boolean);
    procedure DoOver(ToRight: boolean);
    procedure DoUncoverBottom(ToRight: boolean);
    procedure DoUncoverTop(ToRight: boolean);
    procedure DoUncoverHorizontalBackAndForth;
    procedure DoUncoverVerticalBackAndForth;
    procedure DoOverSlipTop(ToRight: boolean);
    procedure DoOverSlipBottom(ToRight: boolean);
    procedure DoOverBottom(ToRight: boolean);
    procedure DoOverTop(ToRight: boolean);
    procedure DoBottomExpand(ToRight: boolean);
    procedure DoTopExpand(ToRight: boolean);
    procedure DoLeaveUpOrDown(Up: boolean);
    procedure DoLeave(ToRight: boolean);
    procedure DoLeaveBottom(ToRight: boolean);
    procedure DoLeaveTop(ToRight: boolean);
    procedure DoVerticalExpand;
    procedure DoHorizontalExpand;
    procedure DoHorizontalShrink;
    procedure DoVerticalShrink;
    procedure DoRectExpand;
    procedure DoRectShrink;
    procedure DoEllipseExpand;
    procedure DoEllipseShrink;
    procedure DoDiskExpand;
    procedure DoDiskShrink;
    procedure DoCrossExpand;
    procedure DoCrossShrink;
    procedure DoShrinkExpandUpOrDown(Up: boolean);
    procedure DoTrianglesExpand;
    procedure DoTrianglesShrink;
    procedure DoMegaStar;
    procedure DoSplinesDown;
    procedure DoSplinesUp;
    procedure DoSplinesRight;
    procedure DoSplinesLeft;
    procedure DoStrips(ToRight: boolean);
    procedure DoStripsUpOrDown(Up: boolean);
    procedure DoStripsHorizontalInterlaced(LeftFirst: boolean);
    procedure DoStripsInterlaced(UpFirst: boolean);
    procedure DoRectUpOrDown(Up: Boolean);
    procedure DoWipe(ToRight: boolean);
    procedure DoOverSin(ToRight: boolean);
    function Parabola(AOffset, AMax, APerCent: Integer): Single;
    procedure DoBouncing(ToRight: boolean);
    procedure DoJump(ToRight: boolean);
    procedure DoBlank;
    procedure DoFadeIn;
    procedure DoRotateRight(CCW: boolean);
    procedure DoCenterRotate(CCW: boolean);
    procedure DoRotateLeft(CCW: boolean);
    procedure DoSpiral(CCW: Boolean);
    procedure DoGrowCenter;
    procedure DoGrowBottom(ToRight: Boolean);
    procedure DoGrowTop(ToRight: Boolean);
    procedure DoGrowRotate(CCW: Boolean);
    procedure DoGrowCenterRotate(CCW: Boolean);
    procedure DoGrowOver(ToRight: Boolean);
    procedure DoGrowOverUpOrDown(Up: Boolean);
    procedure DoRotateGrowLeft(CCW:Boolean);
    procedure DoRotateGrowTopLeft(CCW:Boolean);
    procedure DoRotateTopLeft(CCW:Boolean);
    procedure DoRotateBottomRight(CCW:Boolean);
    procedure DoMappingDown;
    procedure DoMappingUp;
    procedure DoMultiTwirl;
    procedure DoTwirl;
    procedure DoSphere;
    procedure DoCylinderRight;
    procedure DoCylinderLeft;
    procedure DoMultiExpand;
    procedure DoMultiExpandTwirl;
    procedure DoDrawFromBounds;
    procedure DoOffset(ToRight: Boolean);
    procedure DoPixelate;
    procedure DoBlurMotion;
    {$IFDEF Debug_Transitions}
    procedure DoDummy;
    {$ENDIF}

Les transitions sont plus nombreuses que ces méthodes : c'est que certaines d'entre elles prennent en charge plusieurs transitions aux fonctionnements similaires.

Notre composant est enfin opérationnel !

VIII. Conclusion

Avec ce tutoriel, vous êtes parvenu au terme de la construction du composant TGVTransition. Il vous suffit de l'installer et d'imaginer les applications qui pourront en faire usage. Encore mieux, vous pouvez l'améliorer, soit en le rendant plus rapide, soit en créant de nouvelles transitions spectaculaires. À vous de jouer !

Pour ceux qui chercheraient à compléter leurs connaissances, l'ultime tutoriel de la série proposera l'installation du paquet dans l'EDI Lazarus ainsi que des exemples d’utilisation de ce nouveau composant.

Mes remerciements vont à Alcatîz et à BeanzMaster pour leur relecture technique et à f-leb pour la correction orthographique.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2018 Gilles Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.