Developpez.com

Le Club des Développeurs et IT Pro

Les interpolations et fonctions d'easing avec Lazarus II - Une approche empirique

Un billet de Gilles VASSEUR

Le 15/01/2019, par gvasseur58, Responsable Lazarus & Pascal
Pour les besoins du composant TGVTransition qui traite les transitions d'image à image, nous avons déjà présenté des éléments afin de créer des interpolations. Nous reprenons ci-après les formules empiriques qui ont accompagné ce projet dans sa forme initiale.

L'objectif d'une première fonction appelée Exponant était de renvoyer une valeur entre AStart et AEnd modifiée par la puissance AExp utilisée. Quelle que soit la valeur de AExp, nous obtenions une valeur comprise entre 0 et (AEnd - AStart).

Par exemple, si la puissance valait 1, nous étions en présence d'une progression linéaire : en effet, la différence entre AStart et AEnd était alors multipliée par le pas à la puissance 1 (elle était donc inchangée) puis divisée par 100 à la même puissance (donc par 100 inchangé encore une fois). Vous aurez reconnu la formule qui permet de calculer le simple pourcentage d'une valeur ! Pour des puissances supérieures à 1, nous nous retrouvions dans les cas étudiés dans le billet précédent.

Pseudo-code :

Code :
1
2
3
4
5
6
7
8
9
AStart entier (valeur initiale de l'interpolation) 
AEnd entier (valeur finale de l'interpolation) 
AStep entier (pas/étape en cours) 
 
fonction Exponant 
paramètre en entrée : AExp entier 
sortie : Result entier 
 
Result = Valeur arrondie de (AStart - AEnd) multipliée par (AStep à la puissance AExp) divisée par (100 à la puissance AExp)
En Pascal, nous avions :

Code delphi :
1
2
3
4
function Exponant(AExp: Byte): Integer; 
  begin 
    Result := Round((AEnd - AStart) * Power(AStep, AExp) / Power(100, AExp)); 
  end;

Une autre fonction nommée DownExponant permettait de donner l'illusion d'un ralentissement. Pour cela, il suffisait d'inverser les calculs de l'accélération positive par deux soustractions. En partant de (AEnd - AStart), nous arrivions progressivement à 0 :

Code delphi :
1
2
3
4
function DownExponant(AExp: Byte): Integer; 
  begin 
    Result := AEnd - AStart - Round((AEnd - AStart) * Power(100 - AStep, AExp) / Power(100, AExp)); 
  end;

Ces deux fonctions formaient la base des fonctions d'interpolation utilisées dont l'écriture se trouvait grandement simplifiée.

Les fonctions d'interpolation étaient elles-mêmes définies dans une énumération :

Code delphi :
1
2
3
4
5
type 
  
  TInterpolation = (intLinear, intQuadratic, intCubic, intQuartic, intQuintic, 
    intSinus, intSpring, intExpo, intSqrt, intSlowDownQuadratic, intSlowDownCubic, 
    intSlowDownQuartic, intSlowDownQuintic, intBounceCos, intStepsCos);

Enfin, elles étaient traitées dans une méthode dont la structure comportait essentiellement un case of (choix...parmi) :

Code delphi :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function ComputeInterpolation(AStart, AEnd: Integer; AStep: Integer; 
  AInter: TInterpolation; ABack: Boolean): Integer; 
// *** calcul des interpolations *** 
begin 
  case AInter of 
    intLinear: Result := Exponant(1); 
    intQuadratic: Result := Exponant(2); 
    intCubic: Result := Exponant(3); 
    intQuartic: Result := Exponant(4); 
    intQuintic: Result := Exponant(5); 
    // [...] 
    intSlowDownQuadratic: Result := DownExponant(2); 
    intSlowDownCubic: Result := DownExponant(3); 
    intSlowDownQuartic: Result := DownExponant(4); 
    intSlowDownQuintic: Result := DownExponant(5); 
    // [...] 
  end; 
  // [...] 
end;

Simplement, pour permettre un retour de AEnd vers AStart, un paramètre de type booléen ABack était utilisé à la toute fin de la même méthode :

Code delphi :
1
2
3
4
5
6
7
 
    intSlowDownQuintic: Result := DownExponant(5); 
    // [...] 
  end; 
  if ABack then 
    Result := AEnd - Result; 
end;

D'autres fonctions ont ensuite été ajoutées pour obtenir des effets variés.

Comme nous pouvions nous y attendre, la racine carrée ou les puissances de 2 produisaient des effets proches des puissances déjà vues. Elles les modulaient seulement avec leur propre progression.

Leur emploi dans le case of s'appuyait aussi sur la fonction Exponant :

Code delphi :
1
2
 intExpo: Result := Round(Exponant(1) * (Power(2, AStep / 100) - 1)); 
    intSqrt: Result := Round(Exponant(1) * (Sqrt(AStep) / 10));

Nous avions aussi adjoint des formules à base de fonctions trigonométriques. Leurs cycles permettaient en particulier d'envisager des effets de rebonds :

Code delphi :
1
2
3
4
5
6
7
8
9
10
11
12
case AInter of 
    // [...] 
    intSinus: Result := Round(Exponant(1) * sin(pi * AStep / 200)); 
    intSpring: Result := Round(Exponant(1) * (Power(cos(pi * AStep / 100), 2))); 
    // [...] 
    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; 
  // [...] 
end;

Pour le moment, contentons-nous d'avoir un aperçu de ce que produisaient ces interpolations appliquées à de simples boutons :

https://youtu.be/Ii0Vx33BZ3c

Ce billet n'a fait que rappeler les outils créés de manière empirique pour l'écriture d'un composant particulier. Cependant, il montre qu'avec peu de moyens, nous pouvons créer des animations déjà sympathiques et variées.

Par la suite, il s'agira de formaliser cette approche en la rendant compatible avec celle qu'offrent CSS ou JavaScript par exemple (souvent via des bibliothèques complémentaires). A très bientôt !
  Billet blog