Effets de transition avec Lazarus et BGRABitmap

6. Les bases du composant TGVTransition

Comme nous avons terminé notre exploration des transitions, nous allons établir les fondations du composant TGVTransition. À l'issue de ce tutoriel, vous aurez mis en place les interpolations, élaboré une meilleure prise en charge de l'opacité et construit le squelette du composant lui-même.

Commentez Donner une note  l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Les interpolations

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

Lors de notre exploration des transitions d'image à image, nous n'avons pas jusqu'ici joué avec un paramètre qui est pourtant potentiellement riche en effets : le décompte des étapes. Dans notre boucle de démonstration, il allait de 1 à 100, toujours selon l'ordre naturel des entiers. Nous allons à présent réfléchir à une modulation de ce déroulement afin d'examiner les conséquences de l'emploi de formules plus sophistiquées que la simple incrémentation. Ces modalités de déroulement du décompte formeront les interpolations.

I-A. L'application de démonstration

[Exemple BGRABitmap 44]

I-A-1. L'interface utilisateur

Pour visualiser correctement l'action des interpolations, nous allons bâtir une petite application qui comprendra essentiellement un TPanel abritant 17 TButton disposés verticalement à intervalle régulier sur son bord gauche. Chaque bouton verra sa propriété Caption porter le nom d'une interpolation tandis que sa propriété Name sera constituée du préfixe « lbl » suivi de ce même nom. Dans un TPanel situé en bas de la fenêtre principale, nous déposerons un composant TTrackBar (renommé tbarMain) qui servira de curseur de réglage de la vitesse d'exécution des interpolations.

Voici l'interface utilisateur attendue :

Image non disponible

Le fichier LFM qui décrit cette interface est le suivant :

 
Sélectionnez
object MainForm: TMainForm
  Left = 256
  Height = 766
  Top = 210
  Width = 857
  ActiveControl = btnLinear
  Caption = 'Test des interpolations - © G. Vasseur 2018'
  ClientHeight = 766
  ClientWidth = 857
  OnCreate = FormCreate
  LCLVersion = '1.8.2.0'
  object pnlMain: TPanel
    Left = 10
    Height = 696
    Top = 10
    Width = 837
    Align = alClient
    BorderSpacing.Around = 10
    ClientHeight = 696
    ClientWidth = 837
    TabOrder = 0
    OnClick = btnLinearClick
    object btnLinear: TButton
      Left = 0
      Height = 25
      Top = 8
      Width = 120
      Caption = 'Linear'
      OnClick = btnLinearClick
      TabOrder = 0
    end
    object btnQuadratic: TButton
      Left = 0
      Height = 25
      Top = 48
      Width = 120
      Caption = 'Quadratic'
      OnClick = btnLinearClick
      TabOrder = 1
    end
    object btnQuintic: TButton
      Left = 0
      Height = 25
      Top = 168
      Width = 120
      Caption = 'Quintic'
      OnClick = btnLinearClick
      TabOrder = 2
    end
    object btnQuartic: TButton
      Left = 0
      Height = 25
      Top = 128
      Width = 120
      Caption = 'Quartic'
      OnClick = btnLinearClick
      TabOrder = 3
    end
    object btnCubic: TButton
      Left = 0
      Height = 25
      Top = 88
      Width = 120
      Caption = 'Cubic'
      OnClick = btnLinearClick
      TabOrder = 4
    end
    object btnSinus: TButton
      Left = 0
      Height = 25
      Top = 208
      Width = 120
      Caption = 'Sinus'
      OnClick = btnLinearClick
      TabOrder = 5
    end
    object btnSlowDownQuadratic: TButton
      Left = 0
      Height = 25
      Top = 368
      Width = 120
      Caption = 'SlowDown  Quadra'
      OnClick = btnLinearClick
      TabOrder = 6
    end
    object btnSlowDownCubic: TButton
      Left = 0
      Height = 25
      Top = 408
      Width = 120
      Caption = 'SlowDown Cubic'
      OnClick = btnLinearClick
      TabOrder = 7
    end
    object btnSpring: TButton
      Left = 0
      Height = 25
      Top = 248
      Width = 120
      Caption = 'Spring'
      OnClick = btnLinearClick
      TabOrder = 8
    end
    object btnExpo: TButton
      Left = 0
      Height = 25
      Top = 288
      Width = 120
      Caption = 'Expo'
      OnClick = btnLinearClick
      TabOrder = 9
    end
    object btnSqrt: TButton
      Left = 0
      Height = 25
      Top = 328
      Width = 120
      Caption = 'Sqrt'
      OnClick = btnLinearClick
      TabOrder = 10
    end
    object btnSlowDownQuartic: TButton
      Left = 0
      Height = 25
      Top = 448
      Width = 120
      Caption = 'SlowDown Quartic'
      OnClick = btnLinearClick
      TabOrder = 11
    end
    object btnSlowDownQuintic: TButton
      Left = 0
      Height = 25
      Top = 488
      Width = 120
      Caption = 'SlowDown Quintic'
      OnClick = btnLinearClick
      TabOrder = 12
    end
    object btnStepsCosinus: TButton
      Left = 0
      Height = 25
      Top = 560
      Width = 120
      Caption = 'Steps Cosinus'
      OnClick = btnLinearClick
      TabOrder = 13
    end
    object btnBounceCos: TButton
      Left = 1
      Height = 25
      Top = 528
      Width = 119
      Caption = 'Bounce Cos'
      OnClick = btnLinearClick
      TabOrder = 14
    end
    object btnCos: TButton
      Left = 1
      Height = 25
      Top = 600
      Width = 119
      Caption = 'Cos'
      OnClick = btnLinearClick
      TabOrder = 15
    end
    object btnHalfCos: TButton
      Left = 1
      Height = 25
      Top = 640
      Width = 119
      Caption = 'HalfCos'
      OnClick = btnLinearClick
      TabOrder = 16
    end
  end
  object pnlBottom: TPanel
    Left = 0
    Height = 50
    Top = 716
    Width = 857
    Align = alBottom
    BorderSpacing.InnerBorder = 10
    ClientHeight = 50
    ClientWidth = 857
    TabOrder = 1
    object tbarWait: TTrackBar
      Left = 11
      Height = 28
      Top = 11
      Width = 301
      Max = 100
      OnChange = tbarWaitChange
      Position = 0
      Align = alLeft
      BorderSpacing.Around = 10
      TabOrder = 0
    end
  end
end

En dehors de menues améliorations dues à l'utilisation de propriétés d'alignement, ce fichier est conforme à la description faite en amont.

I-A-2. Le squelette de l'application

Vous aurez sans doute remarqué qu'il est fait mention en plusieurs endroits du fichier LFM d'un gestionnaire OnClick qu'il faudra générer en double-cliquant (par exemple) sur le premier bouton btnLinear. Optionnellement, afin de faciliter les tests, il faudra aussi raccorder ce gestionnaire à tous les boutons à partir de l'inspecteur d'objets dans la colonne « événements ». Le plus simple est de l'affecter au TPanel lui-même.

Le composant de type TTrackBar est quant à lui relié à un gestionnaire OnChange que nous obtiendrons aussi en double-cliquant sur lui puisqu'il s'agit de son gestionnaire par défaut.

Créons ensuite un gestionnaire de création de la fiche pour les initialisations de l'application. Pour cela, cliquez dans l'inspecteur d'objets sur le nom de la fiche principale (renommée MainForm dans l'exemple fourni). Depuis le volet « événements », double-cliquez sur la case vide après le nom OnCreate : le gestionnaire est alors créé.

Une fois cette série de transformations effectuée (il peut aussi être utile d'ajouter manuellement quelques commentaires), vous devriez obtenir un squelette d'application tel que celui-ci :

 
Sélectionnez
unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  StdCtrls, ComCtrls;

type

  { TMainForm }

  TMainForm = class(TForm)
    btnSlowDownCubic: TButton;
    btnLinear: TButton;
    btnQuadratic: TButton;
    btnQuintic: TButton;
    btnQuartic: TButton;
    btnCubic: TButton;
    btnSinus: TButton;
    btnSlowDownQuadratic: TButton;
    btnSpring: TButton;
    btnExpo: TButton;
    btnSqrt: TButton;
    btnSlowDownQuartic: TButton;
    btnSlowDownQuintic: TButton;
    btnStepsCosinus: TButton;
    btnBounceCos: Tbutton;
    btnCos: TButton;
    btnHalfCos: TButton;
    pnlBottom: TPanel;
    pnlMain: TPanel;
    tbarWait: TTrackBar;
    procedure btnLinearClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure tbarWaitChange(Sender: TObject);
  private

  public

  end;

var
  MainForm: TMainForm;

implementation

{$R *.lfm}

{ TMainForm }

procedure TMainForm.btnLinearClick(Sender: TObject);
// *** lancement des animations ***
begin

end;

procedure TMainForm.FormCreate(Sender: TObject);
// *** création de la fiche ***
begin

end;

procedure TMainForm.tbarWaitChange(Sender: TObject);
// *** changement de la vitesse ***
begin

end;

end.

I-A-3. La gestion de la vitesse d'exécution

Pour le traitement de la vitesse d'exécution, ajoutez un champ privé fSpeed et une propriété Speed. Reliez l'interface et cette propriété grâce aux gestionnaires précédemment créés selon le modèle suivant :

 
Sélectionnez
unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  StdCtrls, ComCtrls;

const
  C_DefaultSpeed = 80;  

type

  { TMainForm }

  TMainForm = class(TForm)
    btnSlowDownCubic: TButton;
    btnLinear: TButton;
    btnQuadratic: TButton;
    btnQuintic: TButton;
    btnQuartic: TButton;
    btnCubic: TButton;
    btnSinus: TButton;
    btnSlowDownQuadratic: TButton;
    btnSpring: TButton;
    btnExpo: TButton;
    btnSqrt: TButton;
    btnSlowDownQuartic: TButton;
    btnSlowDownQuintic: TButton;
    btnStepsCosinus: TButton;
    btnBounceCos: Tbutton;
    btnCos: TButton;
    btnHalfCos: TButton;
    pnlBottom: TPanel;
    pnlMain: TPanel;
    tbarWait: TTrackBar;
    procedure btnLinearClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure tbarWaitChange(Sender: TObject);
  private
    fSpeed: Byte;
    procedure SetSpeed(AValue: Byte);
  public
    property Speed: Byte read fSpeed write SetSpeed default   C_DefaultSpeed;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.lfm}

{ TMainForm }

procedure TMainForm.btnLinearClick(Sender: TObject);
// *** lancement des animations ***
begin

end;

procedure TMainForm.FormCreate(Sender: TObject);
// *** création de la fiche ***
begin
  fSpeed := C_DefaultSpeed;
  tbarWait.Position := fSpeed;
end;

procedure TMainForm.tbarWaitChange(Sender: TObject);
// *** changement de la vitesse ***
begin
  fSpeed := tbarWait.Position;
end;

procedure TMainForm.SetSpeed(AValue: Byte);
// *** vitesse ***
begin
  if fSpeed = AValue then
    Exit;
  fSpeed := AValue;
end;

end.

Nous avons simplement :

  • défini une constante C_DefaultSpeed fixant la vitesse par défaut (80) ;
  • attribué cette valeur au champ fSpeed puisqu'il s'agit de sa valeur par défaut ;
  • positionné la propriété Position du composant de type TTrackBar à cette valeur ;
  • permis via le gestionnaire OnChange du TTrackBar d'adapter la vitesse en fonction du déplacement de son curseur.

Bien que ce soit superflu en l'état, vous aurez noté que nous avons préféré créer une méthode setter pour fixer la valeur de la propriété Speed.

I-A-4. Le traitement des interpolations

Nous parvenons au cœur du sujet qui nous intéresse. Le principe d'une interpolation est simple : à partir d'une donnée entière fournie en entrée, l'interpolation calcule ce que doit devenir cette donnée si elle est ramenée à un pourcentage. Les calculs s'effectuent en fonction d'une formule mathématique elle aussi fournie.

Dans le cas le plus simple, celui que nous avons utilisé jusqu'à présent, le pas était déjà un pourcentage de réalisation de la transition. La formule se réduisait alors à une incrémentation : au pas n succédait le pas n + 1, ce pas étant renvoyé tel quel comme résultat, et ce depuis 1 jusqu'à 100. Dans ce qui suit, nous allons faire en sorte que le repère de fin de boucle soit ramené à 100 et que la fonction linéaire n → n soit remplacée par d'autres fonctions plus complexes.

Pour repérer facilement ces interpolations, nous allons leur dédier un type énumération comme ceci :

 
Sélectionnez
type

  TInterpolation = (intLinear, intQuadratic, intCubic, intQuartic, intQuintic,
    intSinus, intSpring, intExpo, intSqrt, intSlowDownQuadratic, intSlowDownCubic,
    intSlowDownQuartic, intSlowDownQuintic, intBounceCos, intStepsCos);

  { TMainForm }

  TMainForm = class(TForm)

Ce type, comme le montre le code reproduit, est déclaré dans la partie interface de l'unité, juste avant la déclaration de la fiche principale. Chaque élément de cette énumération correspondra à une interpolation particulière.

De la même manière, dans la partie public de la fiche principale, nous allons déclarer une fonction qui calculera l'interpolation en renvoyant son résultat sous forme d'un entier :

 
Sélectionnez
function ComputeInterpolation(AStart, AEnd: Integer; AStep: Integer; AInter: TInterpolation;
      ABack: Boolean = False): Integer;

Cette fonction prend en entrée deux entiers AStart et AEnd qui correspondent respectivement au début et à la fin de la plage à interpoler. Le paramètre AStep correspond au pas en cours au sein de l'intervalle défini. AInter correspond au type d'interpolation, c'est-à-dire à un élément de l'énumération TInterpolation. Enfin, le paramètre ABack, qui vaut False par défaut, indique s'il s'agit d'une croissance ou d'une décroissance de l'interpolation.

Dans le composant final, cette fonction ne prendra que les deux premiers paramètres, les autres étant des états du composant fixés par des propriétés spécifiques. De plus, elle travaillera avec des nombres flottants pour une précision plus grande.

En effectuant un Ctrl-Maj-C au niveau de cette déclaration de fonction, demandez à présent la création de son squelette :

 
Sélectionnez
function TMainForm.ComputeInterpolation(AStart, AEnd: Integer; AStep: Integer;
  AInter: TInterpolation; ABack: Boolean): Integer;
// *** calcul des interpolations ***
begin

end;

Enfin, ajoutez une clause uses dans la partie implementation de l'unité et ajoutez-lui l'unité math qui contient des routines mathématiques nécessaires :

 
Sélectionnez
implementation

uses
  math;

{$R *.lfm}

Nous sommes prêts à renseigner la fonction de calcul ! En voici le code qui sera expliqué ci-après :

 
Sélectionnez
function TMainForm.ComputeInterpolation(AStart, AEnd: Integer; AStep: Integer;
  AInter: TInterpolation; ABack: Boolean): Integer;
// *** calcul des interpolations ***

  function Exponant(AExp: Byte): Integer;
  begin
    Result := Round((AEnd - AStart) * Power(AStep, AExp) / Power(100, AExp));
  end;

  function DownExponant(AExp: Byte): Integer;
  begin
    Result := AEnd - AStart - Round((AEnd - AStart) *
      Power(100 - AStep, AExp) / Power(100, AExp));
  end;

begin
  case AInter of
    intLinear: Result := Exponant(1);
    intQuadratic: Result := Exponant(2);
    intCubic: Result := Exponant(3);
    intQuartic: Result := Exponant(4);
    intQuintic: Result := Exponant(5);
    intSinus: Result := Round(Exponant(1) * sin(pi * AStep / 200));
    intSpring: Result := Round(Exponant(1) * (Power(cos(pi * AStep / 100), 2)));
    intExpo: Result := Round(Exponant(1) * (Power(2, AStep / 100) - 1));
    intSqrt: Result := Round(Exponant(1) * (Sqrt(AStep) / 10));
    intSlowDownQuadratic: Result := DownExponant(2);
    intSlowDownCubic: Result := DownExponant(3);
    intSlowDownQuartic: Result := DownExponant(4);
    intSlowDownQuintic: Result := DownExponant(5);
    intBounceCos: Result := Exponant(1) + Round((cos(AStep * pi / 100) + 1) * Exponant(1));
    intStepsCos: Result := Exponant(1) + Round(Power((cos(AStep * pi / 100) + 1), 2) * 100);
    intCos: Result := Round(Exponant(1) * (1 - cos(AStep / 100 * pi)) / 2);
    intHalfCos: Result := Round(Exponant(1) * ((1 - cos(AStep /100 * Pi)) / 4 + AStep / 200));
  end;
  if ABack then
    Result := AEnd - Result;
end;

L'objectif de la fonction Exponant est de renvoyer une valeur entre AStart et AEnd qui soit modifiée par la puissance utilisée.

Par exemple, si la puissance vaut 1, nous obtenons la progression linéaire habituelle : en effet, la différence entre AStart et AEnd sera multipliée par le pas (ou l'étape, si vous préférez) à la puissance 1 (elle sera donc inchangée) puis divisée par 100 à la même puissance (donc par 100 inchangé encore une fois). Vous aurez reconnu la formule permettant de calculer le pourcentage d'une valeur.

Si la puissance vaut 2, un phénomène intéressant va se produire. En effet, la différence entre les deux valeurs entrées sera cette fois-ci multipliée par le carré du pas et divisé par 100 au carré. Au début de la boucle, les valeurs retournées seront bien plus petites que celles du premier cas avec une puissance de 1. Au bout du compte, les valeurs pour la fin de la boucle seront cependant identiques : cela signifie que le résultat de la fonction paraîtra ralenti pour de faibles valeurs du pas et qu'il « rattrapera » celui de la puissance 1 pour finir au même niveau à la fin de la boucle. La progression n'est par conséquent plus linéaire et crée un effet d'inertie d'autant plus marqué que l'exposant est fort.

La fonction DownExponant fonctionne selon un principe inverse : elle renvoie des valeurs importantes au début pour ralentir vers la fin de la boucle, d'où des effets de ralentissement. Plus l'exposant qui lui est affecté est fort, plus l'effet de ralentissement sera prononcé.

Les fonctions qui emploient des sinus ou des cosinus sont les plus spectaculaires, car leur cycle permet d'imaginer des effets de rebonds ou de saccades. Nous aurons ainsi intSpring qui paraît bondir comme un ressort après avoir pris de l'élan alors que intBounceCos rebondira au moment de l'arrivée. En revanche, les interpolations intCos et intHalfCos sont fondées sur le cosinus appliqué à la fonction linéaire initiale et ont un comportement qui se rapproche de celles qui ralentissent ou accélèrent le mouvement.

Enfin, non linéaires elles aussi, les fonctions comprenant une racine carrée ou une puissance de 2 modulent selon leur propre profil le résultat obtenu.

Le mieux est de pouvoir visualiser ces résultats. Pour cela, entrez le code suivant dans le gestionnaire du bouton :

 
Sélectionnez
procedure TMainForm.btnLinearClick(Sender: TObject);
// *** lancement des animations ***
var
  Li, LWidth: Integer;
begin
  LWidth := pnlMain.Width - btnLinear.Width;
  for Li := 1 to 100 do
  begin
    btnLinear.Left := ComputeInterpolation(0, LWidth, Li, intLinear);
    btnQuadratic.Left := ComputeInterpolation(0, LWidth, Li, intQuadratic);
    btnCubic.Left := ComputeInterpolation(0, LWidth, Li, intCubic);
    btnQuartic.Left := ComputeInterpolation(0, LWidth, Li, intQuartic);
    btnQuintic.Left := ComputeInterpolation(0, LWidth, Li, intQuintic);
    btnSinus.Left := ComputeInterpolation(0, LWidth, Li, intSinus);
    btnSpring.Left := ComputeInterpolation(0, LWidth, Li, intSpring);
    btnExpo.Left := ComputeInterpolation(0, LWidth, Li, intExpo);
    btnSqrt.Left := ComputeInterpolation(0, LWidth, Li, intSqrt);
    btnSlowDownQuadratic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownQuadratic);
    btnSlowDownCubic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownCubic);
    btnSlowDownQuartic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownQuartic);
    btnSlowDownQuintic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownQuintic);
    btnStepsCosinus.Left := ComputeInterpolation(0, LWidth, Li, intStepsCos);
    btnBounceCos.Left := ComputeInterpolation(0, LWidth, Li, intBounceCos);
   btnCos.Left := ComputeInterpolation(0, LWidth, Li, intCos);
   btnHalfCos.Left := ComputeInterpolation(0, LWidth, Li, intHalfCos);
    sleep(100 - Speed);
    pnlMain.Repaint;
  end;
  for Li := 1 to 100 do
  begin
    btnLinear.Left := ComputeInterpolation(0, LWidth, Li, intLinear, True);
    btnQuadratic.Left := ComputeInterpolation(0, LWidth, Li, intQuadratic, True);
    btnCubic.Left := ComputeInterpolation(0, LWidth, Li, intCubic, True);
    btnQuartic.Left := ComputeInterpolation(0, LWidth, Li, intQuartic, True);
    btnQuintic.Left := ComputeInterpolation(0, LWidth, Li, intQuintic, True);
    btnSinus.Left := ComputeInterpolation(0, LWidth, Li, intSinus, True);
    btnSpring.Left := ComputeInterpolation(0, LWidth, Li, intSpring, True);
    btnExpo.Left := ComputeInterpolation(0, LWidth, Li, intExpo, True);
    btnSqrt.Left := ComputeInterpolation(0, LWidth, Li, intSqrt, True);
    btnSlowDownQuadratic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownQuadratic, True);
    btnSlowDownCubic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownCubic, True);
    btnSlowDownQuartic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownQuartic, True);
    btnSlowDownQuintic.Left := ComputeInterpolation(0, LWidth, Li, intSlowDownQuintic, True);
    btnStepsCosinus.Left:= ComputeInterpolation(0, LWidth, Li, intStepsCos, True);
    btnBounceCos.Left := ComputeInterpolation(0, LWidth, Li, intBounceCos, True);
    btnCos.Left := ComputeInterpolation(0, LWidth, Li, intCos, True);
    btnHalfCos.Left := ComputeInterpolation(0, LWidth, Li, intHalfCos, True);
    sleep(100 - Speed);
    pnlMain.Repaint;
  end;
end;

Tous les boutons doivent se déplacer en un aller-retour, chacun étant gouverné par une des interpolations. La propriété Left de chacun d'eux est donc soumise à notre fonction qui prend pour point de départ le côté gauche du TPanel et pour point d'arrivée son côté droit duquel nous aurons ôté la largeur du bouton pour avoir l'impression de le voir buter contre lui.

Voici une vidéo qui montre l'application en mouvement :

Ces interpolations seront très utiles pour notre composant : non seulement elles multiplieront les possibilités d'animation, mais elles simplifieront aussi le paramétrage des transitions en fixant les limites de leur action dans un intervalle qui sera automatiquement traduit en pourcentage.

Le sujet des interpolations est assez vaste pour mériter qu'un tutoriel à part entière lui soit consacré : il sera présenté à l'issue de cette série comme une forme de bonus !

I-B. Les interpolations dans le composant

Les interpolations de notre composant différeront peu de ce qui vient d'être vu. Comme annoncé, la fonction principale ComputeInterpolation ne prendra en entrée que deux paramètres de type Single. Elle s'appuiera en effet pour les autres données nécessaires sur des champs définis dans la classe du composant : fStep pour le pas actuel et fLastLoop pour le dernier pas attendu.

Comme une version de cette fonction avec un entier sera parfois utile pour certaines méthodes de BGRABitmap qui n'acceptent que ce type de données, nous la définirons dans la foulée en l'appelant ComputeInterpolationInt.

Voici le listing correspondant à ces deux méthodes du composant à venir :

 
Sélectionnez
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;

Nous aurons ainsi à notre disposition un outil puissant à utiliser à chaque fois qu'une variable dépendra de l'étape en cours de la transition.

II. Opacité, vitesse et fluidité

Jusqu'à présent, dans les exemples qui montraient les transitions à l’œuvre, nous n'avons pas fait varier certains paramètres qu'il est temps de prendre en compte. L'opacité se réduisait à une valeur binaire tandis que la vitesse se contentait d'agir sur le paramètre de la procédure Sleep. Nous allons dans la suite de ce tutoriel imaginer et implémenter des processus plus souples et plus variés.

II-A. L'opacité

L'opacité a été réduite à son activation et à sa désactivation suivant une valeur booléenne. Pourquoi ne pas prendre en compte les deux images de travail et jouer sur les différentes possibilités qui s'offrent alors ? Nous sommes ainsi amenés à distinguer quatre cas repris dans l'énumération suivante :

 
Sélectionnez
 // état de l'opacité
  TGVOpacityState = (opaNone, opaSource, opaDestination, opaBoth);

Nous aurons une absence de traitement de l'opacité, une opacité seulement appliquée à l'image d'origine, une opacité seulement appliquée à l'image de destination, ainsi qu'une opacité pour un traitement des deux images.

Pour adapter le composant à cette nouvelle énumération, il suffira de définir une propriété Opacity comme ceci :

 
Sélectionnez
property Opacity: TGVOpacityState
      read fOpacityState write SetOpacityState default opaBoth;

Le setter de cette propriété Opacity ne fera pas grand-chose de plus que changer si nécessaire la valeur du champ interne et signaler ce changement :

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

En revanche, une première fonction GetOpacity renverra suivant la valeur de son unique paramètre booléen Up la valeur de l'opacité : à True, l'opacité augmentera ; à False, elle diminuera. Il est tenu compte, avec les champs fStep et fLastLoop déjà rencontrés, du pourcentage effectué de la transition :

 
Sélectionnez
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;

Cette fonction fondamentale est alors appelée suivant le type d'image traitée. Nous aurons ainsi pour l'image d'origine la fonction SourceOpacity :

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

Si l'image d'origine est concernée par le traitement de l'opacité, la fonction fondamentale est appelée avec le paramètre indiquant que nous souhaitons sa disparition progressive, sinon le maximum d'opacité est appliqué.

De la même manière, mais en vérifiant que c'est l'image de destination qui est concernée, nous définirons la fonction DestinationOpacity :

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

Finalement, avec peu de moyens, nous aurons traité le problème de l'opacité des images pour tous les cas qui nous intéressent.

II-B. La vitesse et la fluidité

À l'usage, même si la bibliothèque BGRABitmap traite avec une bonne efficacité les images, nous avons vu que certains filtres par exemple sont plutôt lents. Il nous faudra aussi tenir compte de la taille des images ou de la puissance de traitement de l'ordinateur sur lequel fonctionnera notre composant. Ne maîtrisant pas a priori ces facteurs, nous sommes amenés à prévoir différents compromis entre la vitesse d'exécution et la fluidité des transitions.

Nous allons tout d'abord définir une énumération des différentes qualités programmées :

 
Sélectionnez
// rapport entre qualité d'affichage et vitesse
  TGVSmooth = (smoNone, smoHigh, smoMedium, smoLow, smoFast);

Le principe sur lequel repose notre amélioration de la maîtrise de l'affichage peut s'énoncer comme suit : si nous voulons accélérer une transition, nous pourrons augmenter le pas de chaque étape (fStep) ou diminuer le nombre d'étapes (fLastLoop) ; si nous voulons améliorer la qualité d'une transition à travers une meilleure fluidité, nous devrons au contraire diminuer ce pas ou augmenter le nombre d'étapes.

La traduction en code de ce principe donnera ceci pour notre composant :

 
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;

Nous aurons reconnu dans cette méthode le setter d'une propriété Smooth définie comme ceci :

 
Sélectionnez
// lissage des transitions
    property Smooth: TGVSmooth read fSmooth write SetSmooth default smoMedium;

Les constantes C_DefaultInc et C_DefaultLastLoop seront regroupées dans une unité distincte étudiée plus loin. Par défaut, la première vaut 96 et la seconde 2. Les modifications que vous pourrez leur apporter devront faire attention aux formules utilisées afin d'éviter des recouvrements partiels.

Il pourrait être tentant de ne travailler qu'avec une seule variable qui serait fLastLoop, bien sûr. Cependant, ce serait oublier que nous utilisons aussi des interpolations qui n'agissent qu'à chaque pas : plus la valeur du pas augmente, moins il est fait appel à l'interpolation active. Les résultats au niveau de l'affichage seraient par conséquent différents si nous n'avions joué que sur le nombre total de pas.

III. La structure du projet de composant

Comme tout projet digne de ce nom, le nôtre est structuré en fichiers dédiés à une tâche particulière. Nous définirons ainsi un fichier pour les directives de compilation (defines.inc), une unité pour la déclaration des constantes et types utilisés (transitionstypes.pas), une unité contenant la classe du composant (transitions.pas), ainsi qu'un fichier d'inclusion pour regrouper le code nécessaire à l'implémentation de toutes les transitions (transitions.inc). Ce sont ces fichiers que nous allons décrire ci-après.

III-A. Le fichier defines.inc

En ne comportant que trois lignes, le fichier transitions.inc est le plus bref qui soit ! Il est cependant très utile puisqu'il contient les directives de compilation applicables à toutes les unités.

Voici à quoi est réduit son contenu :

 
Sélectionnez
{$mode objfpc}{$H+}

// pour ajouter de nouvelles transitions
{.$DEFINE Debug_Transitions}

Avec la première ligne, nous indiquons au compilateur que nous travaillons avec le Pascal Objet et des chaînes longues.

La dernière directive est inactive grâce à l'insertion d'un point avant le signe $. En retirant ce point, nous activerons la directive Debug_Transitions qui, comme son nom l'indique, sera utile au débogage. C'est avec cette directive que de nouvelles transitions seront plus facilement soumises à des tests. Nous la retrouverons par conséquent à certains endroits stratégiques du code.

Ce fichier doit être inclus au début de toutes les unités pour pouvoir jouer son rôle. Il est par ailleurs susceptible d'être complété au cas où une fonctionnalité particulière deviendrait nécessaire à l'ensemble du projet.

III-B. Le fichier transitionstypes.pas

Le fichier transitionstypes.pas contient les déclarations indispensables au bon fonctionnement du composant. Il comprend aussi quelques fonctions utiles pour éviter, par exemple, les listes fastidieuses de noms de transitions à recopier.

Pensez à séparer dans une unité dédiée les constantes, les déclarations de type, les chaînes de ressources et les fonctions outils du reste de votre projet. Dès que ce dernier prendra une certaine ampleur, sa maintenance en sera grandement facilitée.

En premier lieu, l'unité définit des constantes afin d'éviter de se perdre dans le code au cas où une valeur serait finalement à modifier. Par exemple, le composant fonctionne très bien avec 96 étapes dans la boucle au lieu de 100 comme dans les démonstrations précédentes. Nous avons vu que ce nombre un peu réduit, repris par la constante C_DefaultLastLoop, permet de jouer avec le pas défini par la constante C_DefaultInc pour concilier un bon niveau de fluidité et une vitesse acceptable.

Les autres constantes devraient être suffisamment documentées pour ne pas poser de problème majeur de compréhension :

 
Sélectionnez
const
  C_GVVersionSt = '1.0.2.64'; // version
  C_MaxSpeed = 100; // vitesse maximale
  C_DefaultSpeed = C_MaxSpeed div 2; // vitesse par défaut
  C_DefaultTimer = C_DefaultSpeed + 1; // intervalle du timer interne
  C_MaxOpacity = 255;  // opacité maximale
  C_MinVerticalStrips = 1; // minimum de bandes verticales
  C_MinHorizontalStrips = 1; // minimum de bandes horizontales
  C_MaxComputeSize = 1000; // maximum de taille pour le calcul des bandes
  C_MinComputeSize = 10; // minimum de taille pour le calcul des bandes
  C_MinPointsSpline = 4; // minimum de points pour une spline
  C_MinResample = 4; // minimum de la taille d'une image
  C_DefaultWidth = 300; // largeur par défaut du composant
  C_DefaultHeight = C_DefaultWidth * 3 div 4; // hauteur par défaut du composant
  C_DefaultStripWidth = C_DefaultWidth div 10; // largeur d'une bande par défaut
  C_DefaultStripHeight = C_DefaultHeight div 10; // hauteur d'une bande par défaut
  C_DefaultInc = 2;  // incrément accélérateur par défaut
  C_DefaultLastLoop = 96; // dernière boucle par défaut

Nous y voyons en particulier des valeurs concernant la vitesse, la taille des bandes pour les transitions concernées et la taille par défaut du composant lui-même.

Ces constantes sont suivies d'une série d'énumérations dont les fonctions sont aussi plutôt faciles à comprendre.

Il s'agit d'énumérer :

  • les interpolations dont nous avons parlé plus haut (TGVInterpolation) ;
  • les transitions implémentées regroupées dans un fichier à venir (TGVTransitionType) ;
  • les états possibles du composant, une donnée qui sera indispensable au bon déroulement des transitions (TGVTransitionState) ;
  • les états relatifs à l'opacité avec ses modalités elles aussi étudiées plus haut (TGVOpacityState) ;
  • les effets spéciaux en rapport avec l'image d'origine (TGVSpecialEffect) ;
  • les différentes qualités des dessins obtenus, pour les rotations et les homothéties (TGVQuality) ;
  • les rapports de compromis entre qualité d'affichage et vitesse vus précédemment (TGVSmooth).
 
Sélectionnez
type
  // interpolations
  TGVInterpolation = (intLinear, intQuadratic, intCubic, intQuartic, intQuintic,
    intSinus, intSlowDownQuadratic, intSlowDownCubic, intSlowDownQuartic,
    intSlowDownQuintic, intSpring, intExpo, intSqrt, intBounceCos, intStepsCos,
    intSlowDownCos, intCosine, intHalfCosine);

  // transitions
  TGVTransitionType = (traNone,
    traUncoverDown, traUncoverUp, traUncoverLeft, traUncoverRight,
    traOverDown, traOverUp, traOverLeft, traOverRight,
    traUncoverBottomRight, traUncoverBottomLeft, traUncoverTopRight, traUncoverTopLeft,
    traUncoverHorizontalBackAndForth, traUncoverVerticalBackAndForth,
    traOverSlipBottomRight, traOverSlipBottomLeft, traOverSlipTopRight,
    traOverSlipTopLeft,
    traOverBottomRight, traOverBottomLeft, traOverTopRight, traOverTopLeft,
    traBottomRightExpand, traBottomLeftExpand, traTopRightExpand, traTopLeftExpand,
    traBottomRightShrink, traBottomLeftShrink, traTopRightShrink, traTopLeftShrink,
    traLeaveUp, traLeaveDown, traLeaveLeft, traLeaveRight,
    traLeaveBottomRight, traLeaveBottomLeft, traLeaveTopRight, traLeaveTopLeft,
    traPushRight, traPushLeft, traPushDown, traPushUp,
    traHorizontalExpand, traVerticalExpand, traHorizontalShrink, traVerticalShrink,
    traRectExpand, traRectShrink,
    traEllipseExpand, traEllipseShrink,
    traDiskExpand, traDiskShrink,
    traCrossExpand, traCrossShrink,
    traShrinkExpandDown, traShrinkExpandUp,
    traTrianglesExpand, traTrianglesShrink,
    traMegaStar,
    traSplinesDown, traSplinesUp, traSplinesRight, traSplinesLeft,
    traStripsDown, traStripsUp, traStripsRight, traStripsLeft,
    traStripsInterlacedLeftRight, traStripsInterlacedRightLeft,
    traStripsInterlacedUpDown, traStripsInterlacedDownUp,
    traRectUpDown, traRectDownUp,
    traWipeRight, traWipeLeft,
    traOverRightSin, traOverLeftSin,
    traBouncingRight, traBouncingLeft, traJumpRight, traJumpLeft,
    traCWRotateRight, traCCWRotateRight, traCCWCenterRotate, traCWCenterRotate,
    traCWRotateLeft, traCCWRotateLeft, traCCWSpiral, traCWSpiral,
    traGrowCenter,
    traGrowBottomRight, traGrowBottomLeft, traGrowTopRight, traGrowTopLeft,
    traGrowCWRotate, traGrowCCWRotate, traGrowCenterCCWRotate, traGrowCenterCWRotate,
    traGrowOverRight, traGrowOverLeft, traGrowOverUp, traGrowOverDown,
    traGrowPushRight, traGrowPushLeft, traGrowPushUp, traGrowPushDown,
    traFadeIn,
    traRotateCCWGrowLeft, traRotateCWGrowLeft,
    traRotateCCWGrowTopLeft, traRotateCWGrowTopLeft,
    traRotateCCWTopLeft, traRotateCWTopLeft,
    traRotateCCWBottomRight, traRotateCWBottomRight,
    traMappingDown, traMappingUp,
    traMultiTwirl, traTwirl,
    traSphere, traCylinderRight, traCylinderLeft,
    traMultiExpand, traMultiExpandTwirl,
    traDrawFromBounds,
    traOffsetTopLeft, traOffsetBottomRight,
    traPixelate, traBlurMotion
    {$IFDEF Debug_Transitions}
    , traDummy
    {$ENDIF}
    );

  // état d'une transition
  TGVTransitionState = (trsWaiting, trsStarting, trsEnding, trsRunning, trsSuspended,
    trsRestarting, trsStopped, trsPaused);

  // état de l'opacité
  TGVOpacityState = (opaNone, opaSource, opaDestination, opaBoth);

  // effets spéciaux (image d'origine)
  TGVSpecialEffect = (speNone, speEmboss, speContour, speNegative, speLinearNegative,
    speSwapRedBlue, speSphere, speRotate, speGrayscale, spePixelate, speTwirl,
    speBlurMotion);

  // qualité des transformations
  TGVQuality = (quaNone, quaLow, quaNormal, quaBest);

  // rapport entre qualité d'affichage et vitesse
  TGVSmooth = (smoNone, smoHigh, smoMedium, smoLow, smoFast);

Ces énumérations sont suivies d'une série de constantes typées qui établissent une bijection entre les valeurs recensées par les énumérations et leur libellé en toutes lettres :

 
Sélectionnez
const
  C_TransitionStateToStr: array[TGVTransitionState] of string =
    ('Waiting', 'Starting', 'Ending', 'Running', 'Suspended', ' Restarting',
    'Stopped', 'Paused');

  C_OpacityStateToStr: array[TGVOpacityState] of string =
    ('None', 'Source', 'Destination', 'Both');

  C_SpecialEffectToStr: array[TGVSpecialEffect] of string =
    ('None', 'Emboss', 'Contour', 'Negative', 'LinearNegative', 'SwapRedBlue', 'Sphere',
    'Rotate', 'Grayscale', 'Pixelate', 'Twirl', 'BlurMotion');

  C_InterpolationToStr: array[TGVInterpolation] of string =
    ('Linear', 'Quadratic', 'Cubic', 'Quartic', 'Quintic',
    'Sinus', 'SlowDownQuadratic', 'SlowDownCubic', 'SlowDownQuartic',
    'SlowDownQuintic', 'Spring', 'Exp', 'Sqrt', 'BounceCos', 'StepsCos',
    'SlowDownCos', 'Cosine', 'HalfCosine');

  C_SmoothToStr: array[TGVSmooth] of string =
    ('None', 'High', 'Medium', 'Low', 'Fast');

  C_QualityToStr: array[TGVQuality] of string =
    ('None', 'Low', 'Normal', 'Best');

  C_TransitionToStr: array[TGVTransitionType] of string =
    ('None',
    'UncoverDown', 'UncoverUp', 'UncoverLeft', 'UncoverRight',
    'OverDown', 'OverUp', 'OverLeft', 'OverRight',
    'UncoverBottomRight', 'UncoverBottomLeft', 'UncoverTopRight', 'UncoverTopLeft',
    'UncoverHorizontalBackAndForth', 'UncoverVerticalBackAndForth',
    'OverSlipBottomRight', 'OverSlipBottomLeft', 'OverSlipTopRight',
    'OverSlipTopLeft',
    'OverBottomRight', 'OverBottomLeft', 'OverTopRight', 'OverTopLeft',
    'BottomRightExpand', 'BottomLeftExpand', 'TopRightExpand', 'TopLeftExpand',
    'BottomRightShrink', 'BottomLeftShrink', 'TopRightShrink', 'TopLeftShrink',
    'LeaveUp', 'LeaveDown', 'LeaveLeft', 'LeaveRight',
    'LeaveBottomRight', 'LeaveBottomLeft', 'LeaveTopRight', 'LeaveTopLeft',
    'PushRight', 'PushLeft', 'PushDown', 'PushUp',
    'HorizontalExpand', 'VerticalExpand', 'HorizontalShrink', 'VerticalShrink',
    'RectExpand', 'RectShrink',
    'EllipseExpand', 'EllipseShrink',
    'DiskExpand', 'DiskShrink',
    'CrossExpand', 'CrossShrink',
    'ShrinkExpandDown', 'ShrinkExpandUp',
    'TrianglesExpand', 'TrianglesShrink',
    'MegaStar',
    'SplinesDown', 'SplinesUp', 'SplinesRight', 'SplinesLeft',
    'StripsDown', 'StripsUp', 'StripsRight', 'StripsLeft',
    'StripsInterlacedLeftRight', 'StripsInterlacedRightLeft',
    'StripsInterlacedUpDown', 'StripsInterlacedDownUp',
    'RectUpDown', 'RectDownUp',
    'WipeRight', 'WipeLeft',
    'OverRightSin', 'OverLeftSin',
    'BouncingRight', 'BouncingLeft', 'JumpRight', 'JumpLeft',
    'CWRotateRight', 'CCWRotateRight', 'CCWCenterRotate', 'CWCenterRotate',
    'CWRotateLeft', 'CCWRotateLeft', 'CCWSpiral', 'CWSpiral',
    'GrowCenter',
    'GrowBottomRight', 'GrowBottomLeft', 'GrowTopRight', 'GrowTopLeft',
    'GrowCWRotate', 'GrowCCWRotate', 'GrowCenterCCWRotate', 'GrowCenterCWRotate',
    'GrowOverRight', 'GrowOverLeft', 'GrowOverUp', 'GrowOverDown',
    'GrowPushRight', 'GrowPushLeft', 'GrowPushUp', 'GrowPushDown',
    'FadeIn',
    'RotateCCWGrowLeft', 'RotateCWGrowLeft',
    'RotateCCWGrowTopLeft', 'RotateCWGrowTopLeft',
    'RotateCCWTopLeft', 'RotateCWTopLeft',
    'RotateCCWBottomRight', 'RotateCWBottomRight',
    'MappingDown', 'MappingUp',
    'MultiTwirl', 'Twirl',
    'Sphere', 'CylinderRight', 'CylinderLeft',
    'MultiExpand', 'MultiExpandTwirl', 'DrawFromBounds',
    'OffsetTopLeft', 'OffsetBottomRight',
    'Pixelate', 'BlurMotion'
    {$IFDEF Debug_Transitions}
    , 'Dummy'
    {$ENDIF}
    );

Enfin, des fonctions sont proposées en guise d'outils. Il s'agit de transformer des chaînes de caractères en un résultat correspondant à telle ou telle énumération :

 
Sélectionnez
// chaîne en transition
 function StrToTransitionType(const St: string): TGVTransitionType;
 // chaîne en interpolation
 function StrToInterpolation(const St: string): TGVInterpolation;
 // chaîne en effet spécial
 function StrToSpecialEffect(const St: string): TGVSpecialEffect;
 // chaîne en lissage de l'affichage
 function StrToSmooth(const St: string): TGVSmooth;
 // chaîne en qualité d'affichage
 function StrToQuality(const St: string): TGVQuality;
 // chaîne en type d'opacité
 function StrToOpacity(const St: string): TGVOpacityState;

L'implémentation de ces fonctions est fondée sur un algorithme identique :

  • une valeur neutre de retour est fixée par défaut ;
  • la chaîne d'entrée est réduite à des minuscules afin d'éviter les problèmes de casse ;
  • l'énumération choisie est parcourue jusqu'à ce que la fonction CompareText de l'unité SysUtils (à reprendre dans la clause uses de la section implementation) renvoie la valeur 0 pour signaler l'identité des chaînes (dans ce cas, l'indice devient la valeur de retour) ou jusqu'à ce que la dernière valeur de l'énumération soit dépassée.

Voici le code proposé pour ces fonctions :

 
Sélectionnez
implementation

uses
  sysutils;

function StrToTransitionType(const St: string): TGVTransitionType;
// *** chaîne en transition ***
var
  LTra: TGVTransitionType;
begin
  Result := traNone;
  for LTra := low(TGVTransitionType) to high(TGVTransitionType) do
    if CompareText(St, C_TransitionToStr[LTra]) = 0 then
    begin
      Result := LTra;
      exit;
    end;
end;

function StrToInterpolation(const St: string): TGVInterpolation;
// *** chaîne en interpolation ***
var
  LInt: TGVInterpolation;
begin
  Result := intLinear;
  for LInt := low(TGVInterpolation) to high(TGVInterpolation) do
    if CompareText(St, C_InterpolationToStr[LInt]) = 0 then
    begin
      Result := LInt;
      exit;
    end;
end;

function StrToSpecialEffect(const St: string): TGVSpecialEffect;
// *** chaîne en effet spécial ***
var
  LSpeEffect: TGVSpecialEffect;
begin
  Result := speNone;
  for LSpeEffect := low(TGVSpecialEffect) to high(TGVSpecialEffect) do
    if CompareText(St, C_SpecialEffectToStr[LSpeEffect]) = 0 then
    begin
      Result := LSpeEffect;
      exit;
    end;
end;

function StrToSmooth(const St: string): TGVSmooth;
// *** chaîne en lissage de l'affichage ***
var
  LSmooth: TGVSmooth;
begin
  Result := smoNone;
  for LSmooth := low(TGVSmooth) to high(TGVSmooth) do
    if CompareText(St, C_SmoothToStr[LSmooth]) = 0 then
    begin
      Result := LSmooth;
      exit;
    end;
end;

function StrToQuality(const St: string): TGVQuality;
// *** chaîne en qualité d'affichage ***
var
  LQuality: TGVQuality;
begin
  Result := quaNone;
  for LQuality := low(TGVQuality) to high(TGVQuality) do
    if CompareText(St, C_QualityToStr[LQuality]) = 0 then
    begin
      Result := LQuality;
      exit;
    end;
end;

function StrToOpacity(const St: string): TGVOpacityState;
// *** chaîne en type d'opacité ***
var
  LOpa: TGVOpacityState;
begin
  Result := opaNone;
  for LOpa := low(TGVOpacityState) to high(TGVOpacityState) do
    if CompareText(St, C_OpacityStateToStr[LOpa]) = 0 then
    begin
      Result := LOpa;
      exit;
    end;
end;

end.

Ces fonctions peuvent aussi être réalisées en utilisant les RTTI, mais Lazarus, dans des versions anciennes, n'est pas connu pour être très fiable dans ce domaine.

Une unité de déclarations est certes toujours un peu rébarbative, mais il est absolument nécessaire de la créer afin de pouvoir compléter ou corriger le composant sans se perdre dans le code. L'aspect rébarbatif est d'ailleurs heureusement compensé par la simplicité extrême des outils mis en œuvre !

III-C. Le fichier transitions.pas

Le fichier transition.pas est en fait une unité dont l'objectif essentiel sera de déclarer et de définir la classe TGVTransition. C'est aussi elle qui comprendra la procédure Register d'enregistrement du composant dans la palette de Lazarus.

Ce fichier sera l'objet de toute notre attention dans le prochain tutoriel.

III-D. Le fichier transitions.inc

Le fichier transitions.inc est un fichier inclus dans transitions.pas. Il correspondra à l'implémentation des transitions et ne comportera par conséquent aucune section de déclaration. L'intérêt d'un tel fichier séparé est surtout d'ordre pratique : en allégeant le fichier principal, nous faciliterons la relecture du code.

Ce fichier sera lui aussi l'objet de toute notre attention dans le prochain tutoriel. Pour le moment, disons qu'il contient de nombreuses méthodes très proches de celles examinées dans la série en cours et qui constituent le travail préopératoire à leur implémentation finale.

IV. Conclusion

Avec ce tutoriel, nous avons posé les fondations du composant TGVTransition en commençant par des effets d'interpolation, en poursuivant par une gestion mieux maîtrisée de l'opacité et du rapport entre vitesse d'exécution et fluidité, et en terminant par un aperçu de sa structure. Dans le tutoriel suivant, nous implémenterons les transitions et finaliserons les fonctionnalités de notre 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 ni 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.