Effets de transition avec Lazarus et BGRABitmap

6. Les bases du composant TGVTransition

Gilles Vasseur

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

- Titre : Effets de transition avec Lazarus et BGRABitmap

- Auteur : Gilles Vasseur

- Parution : 21 juin 2018

- Mise à jour : 29 juin 2018

- Public visé :

- Licence : 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.

Sommaire

- I. Les interpolations

- I-A. L'application de démonstration

- I-A-1. L'interface utilisateur

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

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

- I-A-4. Le traitement des interpolations

- I-B. Les interpolations dans le composant

- II. Opacité, vitesse et fluidité

- II-A. L'opacité

- II-B. La vitesse et la fluidité

- III. La structure du projet de composant

- III-A. Le fichier defines.inc

- III-B. Le fichier transitionstypes.pas

- III-C. Le fichier transitions.pas

- III-D. Le fichier transitions.inc

- IV. Conclusion

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 :

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

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 :

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 :

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 :

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 :

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 :

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 :

implementation

uses
math;

{$R *.lfm}

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

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 :

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 :

Animation en format "webm" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://gilles-vasseur.developpez.com/tutoriels/transitions/bgra6/fichiers/testinterpolations.webm

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 :

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 :

// é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 :

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 :

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 :

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 :

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 :

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 :

// 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 :

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 :

// 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 :

{$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 :

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).

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 :

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 :

// 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 :

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.