I. Avant-propos▲
Si vous voulez suivre le tutoriel en construisant pas à pas le composant, il faut créer le paquet et un composant selon la méthode expliquée dans ce tutoriel. Les informations nécessaires pour renseigner les caractéristiques du paquet et du composant sont fournies au cours de ce chapitre. Notez cependant que le paquet est à la fois un paquet de conception et d'exécution.
Les paquets d'exécution contiennent toutes les unités dont l'application a besoin pour fonctionner.
Les paquets de conception contiennent les déclarations pour l'enregistrement des composants et des éditeurs de propriétés qui seront intégrés à l'EDI.
Les paquets de conception et exécution comprennent les deux précédents.
Si vous voulez concentrer votre attention sur les problèmes liés au sujet des transitions d'image à image, vous pouvez partir du squelette fourni dans le premier répertoire d'exemple. Notez que le paquet BGRABitmapPack fait partie des paquets requis et qu'il faut par conséquent l'ajouter au paquet en cours de construction.
Afin de ne pas alourdir inutilement le texte qui suit, le code de toutes les méthodes n'est pas fourni. Seules les plus intéressantes sont détaillées. Il vous est donc demandé de vous reporter aux fichiers proposés à chaque étape pour voir la totalité des changements opérés.
Les programmes de test sont présents dans le répertoire exemples accompagnant le tutoriel.
II. Création du paquet et squelette du composant▲
[Exemple BGRABitmap 45]
Le squelette du composant s'appuie sur le fichier transitionstypes.pas déjà décrit dans le tutoriel précédent et qui contient les déclarations indispensables à son bon fonctionnement. Il fait de même appel au fichier defines.inc qui, comme annoncé, contient les directives de compilation. Ces deux fichiers sont disponibles dans le répertoire des exemples.
La première décision à prendre en matière de nouveau composant est de choisir l'ancêtre le plus adapté. Dans notre cas, il faut partir d'un contrôle graphique puisque l'affichage des images est au cœur des fonctionnalités offertes. La classe TGraphicControl est la candidate retenue : servant de base aux composants tels que TImage ou TPaintBox, elle a les fonctionnalités requises, en particulier un canevas pour le dessin.
Nous pouvions aussi envisager de fonder notre composant sur un composant comme TPanel (ou son ancêtre immédiat TCustomPanel) qui présente l’avantage d'offrir d'origine une surface opaque et la possibilité d'intégrer d'autres composants, mais qui peut paraître trop général pour se spécialiser dans le dessin.
La deuxième décision concerne l'icône associée au composant pour son affichage dans la palette. Ici, nous l'avons intégrée dans un fichier .lrs nommé transitions.lrs et appelé dans la partie initialization de l'unité d'accueil du composant. Cette icône est contenue dans le fichier initial nommé TGVTransition.png.
La dernière décision consiste à choisir les propriétés héritées des ancêtres et que nous souhaitons rendre publiques. Il ne faudrait en effet pas que l'utilisateur ait accès à des propriétés inexploitables, voire nocives !
Les ancêtres possèdent souvent des propriétés cachées par la directive protected que vous pouvez rendre accessibles à la demande en les énumérant, précédées de property, dans une partie public ou published.
Comme un constructeur et un destructeur seront nécessaires, ne serait-ce que pour instancier et détruire les images de travail, nous prévoirons aussi les squelettes des méthodes spéciales Create et Destroy.
Ensuite, nous compléterons notre classe avec quelques fonctions utiles : une fonction statique de classe Copyright pour le copyright, une fonction Count pour renvoyer le nombre de transitions disponibles et une propriété à lecture seule Version pour récupérer la version en cours du composant. Dans la section protected, nous avons aussi ajouté une procédure virtuelle appelée Change afin de centraliser les modifications apportées à l'état du composant. Le corps de cette procédure est pour l'instant vide, mais il sera évidemment renseigné au moment de l'implémentation des notifications.
Enfin, dans la partie interface de l'unité, nous déclarerons une procédure Register pour l'enregistrement de notre composant. Son implémentation sera présente dans la partie implementation.
Voici le code proposé :
unit
transitions;
{$I defines.inc}
interface
uses
Classes, SysUtils, Controls, ExtCtrls, Graphics, Types,
BGRABitmapTypes, BGRABitmap,
transitionstypes; // liste déjà complète, même si inutile à ce stade
type
{ TGVTransition }
TGVTransition = class
(TGraphicControl)
strict
private
function
GetVersion: string
;
protected
procedure
Change; virtual
;
public
// copyright du composant
class
function
Copyright: string
; static
;
// création du composant
constructor
Create(AOwner: TComponent); override
;
// destruction du composant
destructor
Destroy; override
;
// nombre de transitions
function
Count: Integer
;
published
// propriétés héritées
property
Color;
property
ParentColor;
property
Align;
property
Anchors;
property
AutoSize;
property
BorderSpacing;
property
Constraints;
property
DragCursor;
property
DragMode;
property
Enabled;
property
OnChangeBounds;
property
OnClick;
property
OnDblClick;
property
OnDragDrop;
property
OnDragOver;
property
OnEndDrag;
property
OnMouseDown;
property
OnMouseEnter;
property
OnMouseLeave;
property
OnMouseMove;
property
OnMouseUp;
property
OnMouseWheel;
property
OnMouseWheelDown;
property
OnMouseWheelUp;
property
OnPaint;
property
OnResize;
property
OnStartDrag;
property
ParentShowHint;
property
PopupMenu;
property
ShowHint;
property
Visible;
// version du composant
property
Version: string
read
GetVersion;
end
;
procedure
Register
;
implementation
uses
LResources;
procedure
Register
;
begin
RegisterComponents('gvsoft'
, [TGVTransition]);
end
;
{ TTransition }
function
TGVTransition.GetVersion: string
;
// *** version du composant ***
begin
Result := C_GVVersionSt;
end
;
constructor
TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
inherited
Create(AOwner);
end
;
destructor
TGVTransition.Destroy;
// *** destruction du composant ***
begin
inherited
Destroy;
end
;
function
TGVTransition.Count: Integer
;
// *** nombre de transitions ***
begin
Result := Ord(High(TGVTransitionType)) + 1
;
end
;
procedure
TGVTransition.Change;
// *** notification de changement d'état ***
begin
end
;
class
function
TGVTransition.Copyright: string
;
// *** copyright ***
begin
Result := C_GVVersionSt + ' - © Gilles Vasseur'
;
end
;
initialization
{$I transitions.lrs}
end
.
Si ce code est compilable, il ne fournit aucune fonctionnalité vraiment intéressante. À nous de le compléter !
Vous avez des doutes sur l'utilisation de private, protected, public, published et leurs dérivés ? Je vous conseille la lecture de mon tutoriel sur la programmation orientée objet, en particulier la partie concernant la notion de portée.
III. Ajout des propriétés essentielles▲
Nous allons à présent déclarer et implémenter les propriétés essentielles, qui ont pour la plupart déjà été décrites précédemment.
[Exemple BGRABitmap 46]
III-A. Les nouvelles propriétés actives▲
La partie interface de l'unité du composant indique précisément les déclarations à faire :
interface
uses
Classes, SysUtils, Controls, ExtCtrls, Graphics, Types,
BGRABitmapTypes, BGRABitmap,
transitionstypes;
type
TGVTransitionStateEvent = procedure
(Sender: TObject; State: TGVTransitionState;
Trans: TGVTransitionType; Step: Integer
) of
object
;
{ TGVTransition }
TGVTransition = class
(TGraphicControl)
strict
private
fSmooth: TGVSmooth;
fSpecialEffect: TGVSpecialEffect;
fAutoStrips: boolean
;
fHorizontalStrips, fVerticalStrips: Integer
;
fInterpolation: TGVInterpolation;
fOpacityState: TGVOpacityState;
fSpeed: Integer
;
fTransition: TGVTransitionType;
fStep: Integer
;
fInc: Integer
;
fLastLoop: Integer
;
function
GetVersion: string
;
procedure
SetAutoStrips(AValue: boolean
);
procedure
SetHorizontalStrips(AValue: Integer
);
procedure
SetInterpolation(AValue: TGVInterpolation);
procedure
SetOpacityState(AValue: TGVOpacityState);
procedure
SetSmooth(AValue: TGVSmooth);
procedure
SetSpecialEffect(AValue: TGVSpecialEffect);
procedure
SetSpeed(AValue: Integer
);
procedure
SetTransition(AValue: TGVTransitionType);
procedure
SetVerticalStrips(AValue: Integer
);
function
DestinationOpacity: Integer
; inline
;
function
SourceOpacity: Integer
; inline
;
protected
function
ComputeInterpolation(AStart, AEnd: Single
): Single
; virtual
;
function
ComputeInterpolationInt(AStart, AEnd: Single
): Integer
;
procedure
Change; virtual
;
// liste des transitions
public
// copyright du composant
class
function
Copyright: string
; static
;
// créatiton du composant
constructor
Create(AOwner: TComponent); override
;
// destruction du composant
destructor
Destroy; override
;
// nombre de transitions
function
Count: Integer
;
// opacité en cours (de 0 à 255)
function
GetOpacity(Up: Boolean
= True
): Integer
;
// calcul du nombre de bandes pour ASize
function
NumberOfStrips(ASize: Integer
): Integer
;
// étape en cours
property
Step: Integer
read
fStep;
published
// propriétés héritées
// […] voir le listing précédent pour la liste de ces propriétés
// version du composant
property
Version: string
read
GetVersion;
// interpolation d'image à image
property
Interpolation: TGVInterpolation read
fInterpolation
write
SetInterpolation default
intLinear;
// vitesse de transition
property
Speed: Integer
read
fSpeed write
SetSpeed default
C_DefaultSpeed;
// type d'opacité utilisée
property
Opacity: TGVOpacityState
read
fOpacityState write
SetOpacityState default
opaBoth;
// type de transition
property
Transition: TGVTransitionType
read
fTransition write
SetTransition default
traNone;
// mode automatique de calcul du nombre de bandes
property
AutoStrips: Boolean
read
fAutoStrips write
SetAutoStrips;
// nombre de bandes horizontales (modifiable si AutoStrips est à False)
property
HorizontalStrips: Integer
read
fHorizontalStrips
write
SetHorizontalStrips;
// nombre de bandes verticales (modifiable si AutoStrips est à False)
property
VerticalStrips: Integer
read
fVerticalStrips write
SetVerticalStrips;
// effets spéciaux
property
SpecialEffect: TGVSpecialEffect read
fSpecialEffect write
SetSpecialEffect default
speNone;
// lissage des transitions
property
Smooth: TGVSmooth read
fSmooth write
SetSmooth default
smoMedium;
end
;
Nous reconnaissons ce qui va nous permettre de traiter l'opacité, le nombre de bandes de certaines transitions, les interpolations, la vitesse, les effets spéciaux, le lissage des transitions et, bien sûr, le type de transition en cours.
D'une manière générale, il s'agit surtout de setters et de getters, les premiers ayant la plupart du temps une structure proche de celle-ci :
procedure
TGVTransition.SetInterpolation(AValue: TGVInterpolation);
// *** détermination de l'interpolation ***
begin
if
fInterpolation = AValue then
exit;
fInterpolation := AValue;
Change;
end
;
Après avoir évité un changement sans intérêt, nous mettons à jour le champ visé et nous notifions le changement de l'état du composant par le biais de la méthode Change (toujours vide pour le moment).
III-B. Le traitement des bandes▲
Les variantes concernent quelques propriétés plus complexes. Ainsi, le traitement des bandes nécessite plusieurs méthodes listées ci-après :
procedure
TGVTransition.SetAutoStrips(AValue: boolean
);
// *** calcul automatique du nombre de bandes ***
begin
if
fAutoStrips = AValue then
exit;
fAutoStrips := AValue;
if
fAutoStrips then
begin
fHorizontalStrips := NumberOfStrips(ClientHeight);
fVerticalStrips := NumberOfStrips(ClientWidth);
end
;
end
;
procedure
TGVTransition.SetHorizontalStrips(AValue: Integer
);
// *** nombre de bandes horizontales ***
begin
if
(fHorizontalStrips = AValue) or
AutoStrips then
exit;
fHorizontalStrips := Max(AValue, C_MinHorizontalStrips);
Change;
end
;
procedure
TGVTransition.SetVerticalStrips(AValue: Integer
);
// *** nombre de bandes verticales ***
begin
if
(fVerticalStrips = AValue) or
AutoStrips then
exit;
fVerticalStrips := Max(AValue, C_MinVerticalStrips);
Change;
end
;
function
TGVTransition.NumberOfStrips(ASize: Integer
): Integer
;
// *** calcul automatique du nombre de bandes ***
begin
if
ASize < C_MinComputeSize then
Result := 1
else
if
ASize < (C_MaxComputeSize div
5
) then
Result := 5
else
if
ASize < (C_MaxComputeSize div
2
) then
Result := 10
else
if
ASize < C_MaxComputeSize then
Result := ASize div
C_DefaultStripWidth
else
Result := 30
;
end
;
Que ce soit de manière automatique ou par prise en compte de la demande de l'utilisateur, des calculs sont en effet nécessaires pour adapter le nombre de bandes à la taille des images. La méthode essentielle est la fonction NumberOfStrips qui fait les calculs pour un résultat automatisé le plus pertinent possible.
III-C. Les interpolations▲
Sans surprise, les interpolations reprennent quant à elles les schémas construits dans le tutoriel précédent :
procedure
TGVTransition.SetInterpolation(AValue: TGVInterpolation);
// *** détermination de l'interpolation ***
begin
if
fInterpolation = AValue then
exit;
fInterpolation := AValue;
Change;
end
;
function
TGVTransition.ComputeInterpolation(AStart, AEnd: Single
): Single
;
// *** calcul d'une interpolation ***
var
LDiff: Single
;
LSign, LStep: Integer
;
function
Exponant(AExp: byte
): Single
; inline
;
begin
Result := LDiff * Power(LStep, AExp) / Power(100
, AExp);
end
;
function
DownExponant(AExp: byte
): Single
; inline
;
begin
Result := LDiff - LDiff * Power(100
- LStep, AExp) /
Power(100
, AExp);
end
;
begin
LDiff := Abs(AEnd - AStart);
if
AStart > AEnd then
LSign := -1
else
LSign := 1
;
LStep := fStep * 100
div
fLastLoop;
case
Interpolation of
intLinear: Result := Exponant(1
);
intQuadratic: Result := Exponant(2
);
intCubic: Result := Exponant(3
);
intQuartic: Result := Exponant(4
);
intQuintic: Result := Exponant(5
);
intSinus: Result := Exponant(1
) * sin(pi * LStep / 200
);
intSlowDownQuadratic: Result := DownExponant(2
);
intSlowDownCubic: Result := DownExponant(3
);
intSlowDownQuartic: Result := DownExponant(4
);
intSlowDownQuintic: Result := DownExponant(5
);
intSpring: Result := Exponant(1
) * Power(cos(pi * LStep / 100
), 2
);
intExpo: Result := Exponant(1
) * (Power(2
, LStep / 100
) - 1
);
intSqrt: Result := Exponant(1
) * (Sqrt(LStep) / 10
);
intBounceCos: Result := Exponant(1
) + (cos(LStep * pi / 100
) + 1
) * Exponant(1
);
intSlowDownCos: Result := Exponant(1
) + (cos(LStep * pi / 100
) + 1
) * LStep;
intStepsCos: Result := Exponant(1
) + Power((cos(LStep * pi / 100
) + 1
), 2
) * LStep;
intCosine: Result := Exponant(1
) * (1
-cos(LStep/100
*Pi))*0
.5
;
intHalfCosine: Result := Exponant(1
) * ((1
-cos(LStep/100
*Pi))*0
.25
+ LStep/100
*0
.5
);
end
;
Result := AStart + LSign * Result;
end
;
function
TGVTransition.ComputeInterpolationInt(AStart, AEnd: Single
): Integer
;
// *** calcul d'une interpolation (entiers) ***
begin
Result := Round(ComputeInterpolation(AStart, AEnd));
end
;
La méthode ComputeInterpolation est la plus conséquente puisqu'elle est en charge du calcul des valeurs nécessaires.
III-D. Le lissage des transitions▲
Le lissage des transitions mérite que nous nous y arrêtions un peu. Voici la méthode setter qui lui est associée :
procedure
TGVTransition.SetSmooth(AValue: TGVSmooth);
// *** lissage des transitions ***
begin
if
fSmooth = AValue then
exit;
fSmooth := AValue;
case
fSmooth of
smoHigh: begin
fInc := C_DefaultInc div
2
;
fLastLoop := C_DefaultLastLoop;
end
;
smoMedium: begin
fInc := C_DefaultInc;
fLastLoop := C_DefaultLastLoop;
end
;
smoNone, smoLow: begin
fInc := C_DefaultInc;
fLastLoop := C_DefaultLastLoop div
2
;
end
;
smoFast: begin
fInc := C_DefaultInc * 2
;
fLastLoop := C_DefaultLastLoop div
2
;
end
;
end
;
Change;
end
;
Comme nous l'avions décidé, il s'agit d'adapter le nombre de pas de la boucle de dessin d'une transition et la valeur de ce pas pour établir un équilibre entre la vitesse d'exécution et la fluidité du rendu de la transition.
III-E. L'opacité▲
L'opacité dépendant elle aussi du nombre de pas de la boucle d'affichage, mais aussi de ce à quoi elle est appliquée (source, destination ou les deux), elle est calculée selon le code suivant :
procedure
TGVTransition.SetOpacityState(AValue: TGVOpacityState);
// *** détermination de l'opacité ***
begin
if
fOpacityState = AValue then
exit;
fOpacityState := AValue;
Change;
end
;
function
TGVTransition.DestinationOpacity: Integer
;
// *** opacité pour l'image de destination ***
begin
Result := ifthen(fOpacityState in
[opaDestination, opaBoth], GetOpacity, C_MaxOpacity);
end
;
function
TGVTransition.SourceOpacity: Integer
;
// *** opacité de l'image d'origine ***
begin
Result := ifthen(fOpacityState in
[opaSource, opaBoth], GetOpacity(False
),
C_MaxOpacity);
end
;
function
TGVTransition.GetOpacity(Up: Boolean
): Integer
;
// *** calcul de l'opacité en fonction du pourcentage effectué ***
begin
Result := ifthen(Up, fStep * C_MaxOpacity div
fLastLoop, C_MaxOpacity -
fStep * C_MaxOpacity div
fLastLoop);
end
;
Encore une fois, une méthode concentre l'essentiel du mécanisme : GetOpacity. Son fonctionnement est fondé sur un simple calcul de pourcentage.
III-F. Le constructeur de la classe▲
Enfin, cette étape de construction du composant doit adapter le constructeur Create afin de mettre à jour les valeurs par défaut :
constructor
TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
inherited
Create(AOwner);
fInterpolation := intLinear;
fSpeed := C_DefaultSpeed;
fTransition := traNone;
fOpacityState := opaBoth;
fSpecialEffect := speNone;
fHorizontalStrips := NumberOfStrips(ClientHeight);
fVerticalStrips := NumberOfStrips(ClientWidth);
fSmooth := smoMedium;
fInc := C_DefaultInc;
fLastLoop := C_DefaultLastLoop;
end
;
Il est en effet indispensable que ces valeurs soient en accord avec celles intégrées au fichier .lfm. Ce constructeur sera complété au fur et à mesure de nos besoins.
IV. Ajout des images et du timer▲
[Exemple BGRABitmap 47]
IV-A. Les images▲
Le composant ne serait rien s'il ne pouvait manipuler des images ! Nous allons donc, au cours de cette étape, implémenter les objets nécessaires à notre travail.
En premier lieu, il nous faut déclarer des champs pour les images elles-mêmes :
- fPictureFrom et fPictureTo de type TPicture pour dialoguer avec la LCLÂ ;
- fBGRAFrom et fBGRATo de type TBGRABitmap pour conserver des originaux de tailles identiques des images de travail ;
- fLBGRAFrom et fLBGRATo de type TBGRABitmap pour travailler localement suivant la transition en cours, permettant ainsi de perdre momentanément un peu de qualité au cours d'un redimensionnement par exemple ;
- fLBGRAMask de type TBGRABitmap pour abriter un masque.
Ces sept champs figurent dans la partie strict private de la classe :
fLBGRAFrom, fLBGRATo, fLBGRAMask: TBGRABitmap;
fPictureFrom, fPictureTo: TPicture;
fBGRAFrom, fBGRATo: TBGRABitmap;
Ce nombre peut sembler élevé, mais la complexité de certaines transitions rend nécessaire cette profusion d'outils graphiques.
L'accès à ces champs n'est jamais direct. Les images d'origine et de destination sont en fait des propriétés définies ainsi dans la partie published de la classe TGVTransition :
// image d'origine
property
PictureFrom: TPicture read
GetPictureFrom write
SetPictureFrom;
// image de destination
property
PictureTo: TPicture read
GetPictureTo write
SetPictureTo;
Les setters et getters sont alors les suivants :
function
TGVTransition.GetPictureFrom: TPicture;
// *** restitution de l'image d'origine ***
begin
Result := fPictureFrom;
end
;
function
TGVTransition.GetPictureTo: TPicture;
// *** restitution de l'image de destination ***
begin
Result := fPictureTo;
end
;
procedure
TGVTransition.SetPictureFrom(AValue: TPicture);
// *** acquisition de l'image d'origine ***
begin
fPictureFrom.Assign(AValue);
FreeAndNil(fBGRAFrom);
fBGRAFrom := TBGRABitmap.Create(AValue.Bitmap);
BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(ClientWidth, ClientHeight));
Change;
end
;
procedure
TGVTransition.SetPictureTo(AValue: TPicture);
// *** acquisition de l'image de destination ***
begin
fPictureTo.Assign(AValue);
FreeAndNil(fBGRATo);
fBGRATo := TBGRABitmap.Create(AValue.Bitmap);
BGRAReplace(fBGRATo, fBGRATo.Resample(ClientWidth, ClientHeight));
Change;
end
;
Si les objets de type TPicture sont évidents à créer, ceux qui abritent les originaux normalisés en taille sont un peu plus complexes : ils sont libérés si nécessaire avant d'être recréés et redimensionnés.
Ces objets doivent être, selon les principes du Pascal Objet, créés et libérés proprement. Il faut par conséquent compléter ainsi le constructeur et le destructeur de notre classe :
constructor
TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
inherited
Create(AOwner);
fInterpolation := intLinear;
fSpeed := C_DefaultSpeed;
fTransition := traNone;
fOpacityState := opaBoth;
fSpecialEffect := speNone;
fPictureFrom := TPicture.Create;
fPictureTo := TPicture.Create;
fBGRAFrom := TBGRABitmap.Create(ClientWidth, ClientHeight, BGRABlack);
fBGRATo := fBGRAFrom.Duplicate as
TBGRABitmap;
fLBGRAFrom := fBGRAFrom.Duplicate as
TBGRABitmap;
fLBGRATo := fBGRAFrom.Duplicate as
TBGRABitmap;
fLBGRAMask := fBGRAFrom.Duplicate as
TBGRABitmap;
fHorizontalStrips := NumberOfStrips(ClientHeight);
fVerticalStrips := NumberOfStrips(ClientWidth);
fSmooth := smoMedium;
fInc := C_DefaultInc;
fLastLoop := C_DefaultLastLoop;
end
;
destructor
TGVTransition.Destroy;
// *** destruction du composant ***
begin
fBGRAFrom.Free;
fBGRATo.Free;
fLBGRAFrom.Free;
fLBGRATo.Free;
fLBGRAMask.Free;
fPictureTo.Free;
fPictureFrom.Free;
inherited
Destroy;
end
;
IV-B. Quelques améliorations de la base du composant▲
Nous allons profiter de cet apport pour améliorer notre classe en lui adjoignant quelques fonctionnalités supplémentaires.
Tout d'abord, nous allons définir une taille par défaut du composant grâce à la surcharge de la fonction de classe GetControlClassDefaultSize déclarée dans la partie protected :
class
function
GetControlClassDefaultSize: TSize; override
;
Son implémentation est plutôt simple :
class
function
TGVTransition.GetControlClassDefaultSize: TSize;
// *** taille par défaut ***
begin
Result := inherited
GetControlClassDefaultSize;
Result.cx := C_DefaultWidth;
Result.cy := C_DefaultHeight;
end
;
Pour que cette définition soit prise en compte, il faut l'invoquer dans le constructeur :
constructor
TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
inherited
Create(AOwner);
with
GetControlClassDefaultSize do
SetInitialBounds(0
, 0
, CX, CY);
Les composants visuels ont souvent besoin de redéfinir leur taille par défaut. Comme GetControlClassDefaultSize est une fonction de classe, elle peut être invoquée sans instancier la classe qui l'intègre (voir Les méthodes avec Free Pascal pour plus de précisions).
Nous pouvons aussi prévoir une méthode appelée DoReset pour remettre à zéro les différentes images à partir des composants TPicture enregistrés :
procedure
TGVTransition.DoReset;
// *** remise à zéro ***
begin
if
Assigned(fPictureFrom) and
Assigned(fPictureTo) then
begin
if
(fPictureFrom.Height > 0
) and
(fPictureFrom.Width > 0
) and
(fPictureTo.Height > 0
) and
(fPictureTo.Width > 0
) then
begin
FreeAndNil(fBGRAFrom);
fBGRAFrom := TBGRABitmap.Create(fPictureFrom.Bitmap);
BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(ClientWidth, ClientHeight));
fLBGRAFrom.Assign(fBGRAFrom);
FreeAndNil(fBGRATo);
fBGRATo := TBGRABitmap.Create(fPictureTo.Bitmap);
BGRAReplace(fBGRATo, fBGRATo.Resample(ClientWidth, ClientHeight));
fLBGRATo.Assign(fBGRATo);
FreeAndNil(fLBGRAMask);
fLBGRAMask := TBGRABitmap.Create(ClientWidth, ClientHeight, BGRABlack);
fStep := 0
;
Change;
end
;
end
;
end
;
Cette méthode est déclarée virtuelle dans la partie protégée de la classe en vue de son adaptation à des descendants prévus pour d'autres situations.
Il est important de vérifier l'existence et la validité en termes de taille les images proposées : la fonction Assigned détecte l'assignation et les tests sur les dimensions vérifient que l'image n'ait pas de dimensions nulles, ce qui provoquerait des erreurs à l'exécution.
Toujours dans la perspective d'améliorer notre base de composant, nous allons lui adjoindre une nouvelle propriété baptisée Quality : elle choisira le filtre de redimensionnement le plus adapté à l'utilisateur. Nous en reproduisons ci-après le setter :
procedure
TGVTransition.SetQuality(AValue: TGVQuality);
// *** qualité des transformations (homothéties) ***
begin
if
fQuality = AValue then
exit;
fQuality := AValue;
case
fQuality of
quaLow: fLBGRAFrom.ResampleFilter := rfBox;
quaNormal: fLBGRAFrom.ResampleFilter := rfHalfCosine;
quaBest: fLBGRAFrom.ResampleFilter := rfBestQuality;
end
;
end
;
Dans l'état actuel du composant final, cette propriété n'est guère utile, mais son importance pourra être développée en approfondissant la gestion des filtres.
IV-C. Le TTimer▲
Pour clore ce chapitre, nous allons introduire un élément essentiel du mécanisme du composant. Contrairement aux exercices qui ont émaillé les tutoriels précédents, nous allons en effet oublier la boucle de réalisation d'une transition pour adopter une solution plus adaptée à la programmation événementielle. Cette solution passe par l'utilisation d'un objet de type TTimer à déclarer avec les autres champs privés :
fTimer: TTimer;
Pour l'accompagner, dans la partie protégée, nous déclarerons une procédure apte à gérer l'événement déclenché par ce composant lorsqu'il a atteint la durée qui lui a été fixée :
procedure
OnTimerEvent(Sender: TObject);
Cet événement sera utilisé pour effectuer les tâches relatives à une étape de l'affichage de la transition :
procedure
TGVTransition.OnTimerEvent(Sender: TObject);
// *** événement lié au timer ***
begin
// DoLoop; (en attente!)
end
;
Encore une fois, le constructeur Create doit être complété :
constructor
TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
inherited
Create(AOwner);
// […]
fTimer := TTimer.Create(Self
);
fTimer.Enabled := False
;
fTimer.Interval := C_DefaultTimer - (fSpeed div
2
);
fTimer.OnTimer := @OnTimerEvent;
En revanche, comme l'objet est créé avec le paramètre Self, c'est le composant TGVTransition qui prendra en charge sa destruction : nous n'avons donc pas à nous en préoccuper.
Contrairement à Delphi, Free Pascal impose, dans son mode par défaut du Pascal Objet, l'utilisation du signe @ devant (entre autres) une méthode à affecter à un gestionnaire d'événement.
Un dernier détail et nous en aurons fini avec cette étape : la vitesse d'exécution dépend de la valeur donnée à la propriété Interval du TTimer. Il nous faut par conséquent modifier ainsi le setter de la propriété de gestion de la vitesse Speed :
procedure
TGVTransition.SetSpeed(AValue: Integer
);
// *** détermination de la vitesse ***
begin
if
fSpeed = AValue then
exit;
fSpeed := AValue;
fTimer.Interval := C_DefaultTimer - (fSpeed div
2
);
Change;
end
;
V. La méthode Paint et les procédures de calculs▲
[Exemple BGRABitmap 48]
Nos propriétés sont installées, le compteur pour les étapes de la réalisation d'une transition est en place, les images sont prêtes à être traitées : nous allons par conséquent nous occuper des grandes structures du traitement recherché.
V-A. La méthode Paint▲
Comme elle va être appelée à chaque étape, la méthode Paint est primordiale, mais elle est paradoxalement peu fournie, surtout pour la partie réservée à l'exécution :
procedure
TGVTransition.Paint;
// *** dessin ***
var
LX, LY: Integer
;
begin
if
csDesigning in
ComponentState then
begin
with
Canvas do
begin
Font.Color := clBlue;
Font.Size := 16
;
LX := (Self
.Width - TextWidth(Self
.Name)) div
2
;
if
LX < 0
then
LX := 0
;
LY := (Self
.Height - TextHeight(Self
.Name)) div
2
;
if
LY < 0
then
LY := 0
;
TextOut(LX, LY, Self
.Name);
end
;
exit;
end
;
if
Assigned(OnPaint) then
begin
Canvas.Font := Font;
Canvas.Brush.Color := Color;
inherited
Paint;
end
;
if
Assigned(fLBGRAFrom) then
fLBGRAFrom.Draw(Canvas, 0
, 0
);
end
;
Seules les deux dernières lignes se rapportent à l'affichage proprement dit : si fLBGRAFrom est affectée, c'est elle qui contient l'image qui doit être reproduite sur le canevas du composant, donc visible.
Lorsque nous serons en mode conception (marqué par la présence de csDesigning dans ComponentState), nous nous contenterons d'écrire le nom du composant en son centre. En dehors de ces deux tâches, la méthode vérifie si un gestionnaire d'événement lui est rattaché et lance son exécution si besoin est.
V-B. Les méthodes de travail sur les images▲
Avant de dessiner, il faut avoir établi quelle image doit être affichée. Suivant les transitions, nous avons vu que le traitement différait. Il pouvait exiger des étapes que nous allons formaliser ainsi :
- prétraitement indépendant des images (par exemple, pour les splines) ;
- traitement de l'image d'origine (par exemple, pour les effets spéciaux ou son déplacement) ;
- traitement avec masque de l'image de destination (pour de très nombreuses transitions) ;
- traitement sans masque de l'image à afficher (par exemple pour les rotations et les redimensionnements).
Ces différents traitements donnent naissance à autant de méthodes définies comme suit :
procedure
InitTransition; virtual
;
procedure
ComputeSource; virtual
;
procedure
ComputeDestination; virtual
;
procedure
ComputeFinalDraw; virtual
;
Leur caractère virtuel dans la partie protégée de la classe assure qu'elles pourront être surchargées avec souplesse par d'éventuelles classes descendantes. Pour le moment, leur contenu restera vide : il contiendra des appels aux méthodes des transitions elles-mêmes, ainsi qu'à des méthodes associées aux images de travail. Le fichier inclus des transitions trouvera naturellement sa place après elles.
Leurs squelettes sont alors ceux-ci :
procedure
TGVTransition.InitTransition;
// *** pré-traitement ***
begin
end
;
procedure
TGVTransition.ComputeSource;
// *** traitement de l'image source ***
begin
end
;
procedure
TGVTransition.ComputeDestination;
// *** traitement de l'image destination ***
begin
end
;
procedure
TGVTransition.ComputeFinalDraw;
// *** traitement final de l'image ***
begin
end
;
// {$I transitions.inc }
VI. Les événements et les états▲
[Exemple BGRABitmap 49]
Afin d'assurer le bon fonctionnement de notre composant, nous allons le doter de gestionnaires d'événements et d'un automate permettant de tenir compte des différents états qu'il peut prendre.
VI-A. Les gestionnaires d'événements▲
Du point de vue des événements, nous en définirons trois :
TGVTransition = class
(TGraphicControl)
strict
private
// […]
fTransitionChange, fLooping: TNotifyEvent;
fStateChanged: TGVTransitionStateEvent;
// […]
protected
// […]
procedure
Change; virtual
;
procedure
StateChanged; virtual
;
procedure
Looping; virtual
;
// […]
published
// […]
// le composant a changé - gestionnaire de l'événement
property
OnChange: TNotifyEvent read
fTransitionChange
write
fTransitionChange;
// l'état de la transition a changé - gestionnaire de l'événement
property
OnStateChange: TGVTransitionStateEvent read
fStateChanged
write
fStateChanged;
// étape suivante - gestionnaire de l'événement
property
OnLoop: TNotifyEvent read
fLooping write
fLooping;
end
;
Il s'agit de réagir au changement du composant (par exemple à la modification de la vitesse choisie), à celui de son état (par exemple de l'état de démarrage à celui d'en cours d'exécution) ou encore au passage à l'étape suivante d'une transition. Chaque gestionnaire est associé à une méthode spécifique chargée de l'exécuter s'il est affecté : Change pour OnChange, StateChanged pour OnStateChange et Looping pour OnLoop.
À partir de ce mécanisme, moyennant des appels appropriés à ces méthodes spécifiques, les changements suivis seront notifiés à l'application appelante qui fera ce que bon lui semble des informations accessibles. Par exemple, comme nous avions déjà intégré un appel à Change dans tous les setters concernés, tout changement des propriétés suivies déclenchera un appel, s'il existe, au gestionnaire OnChange.
Il faut cependant prendre garde au temps consommé par ces méthodes traitées en dehors du composant : elles peuvent ralentir de manière significative ses performances ! Cet avertissement concerne tout particulièrement le gestionnaire OnLoop appelé à chaque étape de la transition.
Le traitement des états fait appel à une procédure de notification spéciale qui sera définie comme suit :
TGVTransitionStateEvent = procedure
(Sender: TObject; State: TGVTransitionState;
Trans: TGVTransitionType; Step: Integer
) of
object
;
L'utilisateur aura ainsi accès immédiatement à l'état, mais aussi à la transition et à l'étape en cours.
L'implémentation des gestionnaires est on ne peut plus classique :
procedure
TGVTransition.Change;
// *** notification de changement d'état ***
begin
if
Assigned(fTransitionChange) then
fTransitionChange(Self
);
end
;
procedure
TGVTransition.StateChanged;
// *** notification de changement d'état ***
begin
if
Assigned(fStateChanged) then
fStateChanged(Self
, fState, fTransition, fStep);
end
;
procedure
TGVTransition.Looping;
// *** notification de l'étape suivante ***
begin
if
Assigned(fLooping) then
fLooping(Self
);
end
;
Si le gestionnaire est bien affecté, nous l'exécutons, sinon nous quittons la procédure sans agir. Le paramètre Self est là pour indiquer que c'est une instance de notre composant qui a effectué la notification.
VI-B. Le traitement des états▲
La propriété State est à première vue une propriété parmi les autres. Pourtant, c'est sur elle que repose le bon déroulement des opérations au sein du composant ! Considérant le champ privé fState, voici sa définition dans la partie publique :
// état en cours de la transition
property
State: TGVTransitionState read
fState write
SetState default
trsWaiting;
Pourquoi avoir choisi la partie publique et non la partie publiée comme pour la plupart des autres propriétés ? C'est tout simplement que cette propriété en lecture et écriture n'a aucun sens à la conception puisqu'elle ne marque que des états en cours d'exécution.
Son setter s'occupe en particulier de déclencher ou d'arrêter le TTimer, d'où l'exécution ou l'arrêt de la transition :
procedure
TGVTransition.SetState(AValue: TGVTransitionState);
// *** changement d'état ***
begin
if
fState = AValue then
exit;
fState := AValue;
if
Assigned(fTimer) then
fTimer.Enabled := (fState = trsRunning);
StateChanged;
end
;
Nous pouvons enfin définir la méthode DoLoop en suspens dans le gestionnaire d'événement OnTimer :
procedure
TGVTransition.DoLoop;
// *** boucle de la transition ***
begin
Inc(fStep, fInc);
ComputeSource;
ComputeDestination;
ComputeFinalDraw;
Looping;
Repaint;
if
fStep >= fLastLoop then
begin
State := trsEnding;
State := trsWaiting;
end
;
end
;
Cette méthode suit l'ordre séquentiel d'une étape, depuis l'incrémentation de son numéro jusqu'à la sortie pour l'étape suivante ou le retour à un état d'attente si la transition est achevée. Il s'agit en effet d'effectuer les opérations dans cet ordre :
- calculer l'image d'origine ;
- calculer l'image de destination avec masque ;
- calculer l'image définitive même si aucun masque n'est nécessaire ;
- notifier le changement d'étape ;
- demander un dessin immédiat de l'image en cours ;
- ajuster l'état du composant au cas où le nombre d'étapes requis serait atteint.
Nous pouvons aussi enlever le commentaire dans ce gestionnaire :
procedure
TGVTransition.OnTimerEvent(Sender: TObject);
// *** événement lié au timer ***
begin
DoLoop;
end
;
Enfin, grâce à ces états, nous pouvons restreindre l'usage de certaines propriétés. Par exemple, il est contre-indiqué de changer de transition en pleine exécution de l'une d'entre elles. Nous modifierons le code du setter en conséquence :
procedure
TGVTransition.SetTransition(AValue: TGVTransitionType);
// *** détermination de la transition active ***
begin
if
(fTransition = AValue) or
(fState = trsRunning) then
exit;
fTransition := AValue;
Change;
end
;
Nous aurions pu déclencher une exception au lieu d'ignorer ce changement de transition alors qu'une autre est en cours d'exécution, mais il nous a semblé préférable de laisser cette responsabilité au développeur. Après tout, c'est à lui de faire en sorte que l’utilisateur ne puisse pas sélectionner ce paramètre via l'interface homme/machine (IHM) fournie.
Reste bien entendu à ne pas oublier d'indiquer dans le constructeur la valeur par défaut de l'état :
constructor
TGVTransition.Create(AOwner: TComponent);
// *** construction du composant ***
begin
inherited
Create(AOwner);
// […]
fState := trsWaiting;
VI-C. Les états en action▲
[Exemple BGRABitmap 50]
En tenant compte de l'état en cours, nous sommes à même d'obtenir une bonne maîtrise du composant. Pour vous en convaincre, nous allons définir dans la partie publique de notre classe une série de méthodes liées à des actions :
// démarrage de la transition
procedure
Start;
// arrêt de la transition
procedure
Stop;
// remise à zéro du composant
procedure
Reset;
// suspension de la transition en cours (si elle existe)
procedure
Suspend;
// reprise de la transition suspendue (si elle existe)
procedure
Resume;
// indicateur de transition en cours
function
IsRunning: Boolean
;
Leur implémentation est très liée aux états possibles du composant :
procedure
TGVTransition.Start;
// *** déclenchement de la transition ***
begin
case
fState of
trsRunning: exit;
trsSuspended: State := trsRestarting;
else
fStep := 0
;
InitTransition;
State := trsStarting;
end
;
State := trsRunning;
end
;
procedure
TGVTransition.Stop;
// *** arrêt d'une transition ***
begin
if
State = trsRunning then
begin
State := trsStopped;
fStep := fLastLoop;
State := trsWaiting;
end
;
end
;
procedure
TGVTransition.Reset;
// *** remise à zéro de l'affichage ***
begin
DoReset;
Repaint;
end
;
procedure
TGVTransition.Suspend;
// *** suspension de la transition ***
begin
if
fState = trsRunning then
State := trsSuspended;
end
;
procedure
TGVTransition.Resume;
// reprise d'une animation ***
begin
if
fState = trsSuspended then
Start;
end
;
function
TGVTransition.IsRunning: Boolean
;
// *** transition en cours ? ***
begin
Result := (fState = trsRunning);
end
;
L'état le plus complexe est trsSuspended, car il demande d'être vigilant en cas de démarrage ou de redémarrage d'une transition. Il ne faudrait pas dans ce cas réinitialiser le compteur des boucles.
Un autre usage des états peut être illustré par la méthode Resize chargée de gérer le redimensionnement du composant. Pendant ce redimensionnement, il faut mettre la transition en pause pour recalculer, entre autres, les bandes peut-être inadaptées aux nouvelles dimensions.
Voici le code proposé :
procedure
TGVTransition.Resize;
// *** le contrôle est redimensionné ***
var
LState: TGVTransitionState;
begin
LState := fState;
State := trsPaused;
try
inherited
Resize;
if
AutoStrips then
begin
fHorizontalStrips := NumberOfStrips(ClientHeight);
fVerticalStrips := NumberOfStrips(ClientWidth);
end
;
if
not
(csDesigning in
ComponentState) and
not
(fState = trsRunning) then
DoReset;
finally
State := LState;
end
;
end
;
L'aspect intéressant de cette routine est la conservation de l'état dans la variable locale LState pour mettre le composant en mode pause jusqu'à la fin du traitement où l'état initial est récupéré.
VII. Le composant complet▲
[Exemple BGRABitmap 51]
Pour obtenir un composant opérationnel complet, il ne manque que deux étapes : l'implémentation de toutes les transitions et leur utilisation dans les méthodes laissées vides jusqu'à présent, qui auront en charge leur exploitation.
VII-A. L'implémentation des transitions▲
Pour ce qui est de l'implémentation des transitions, elle s'appuie sur le travail préparatoire effectué dans les tutoriels précédents. Les transitions sont même souvent d'un aspect plus simple que leurs équivalents antérieurs puisque la gestion des étapes est prise en charge en dehors des routines les implémentant et que l'utilisation des interpolations rend a priori le code plus lisible.
Voici la routine qui permet de déplacer l'image d'origine vers la droite :
procedure
TGVTransition.DoRight;
// *** image vers la droite ***
begin
fX := ComputeInterpolation(0
, ClientWidth);
end
;
On ne peut imaginer plus simple !
Voici la transition qui étend verticalement l’image de destination grâce à un masque :
procedure
TGVTransition.DoVerticalExpand;
// *** VerticalExpand ***
begin
fLBGRAMask.FillRectAntialias(ComputeInterpolation(ClientWidth / 2
, 0
), 0
,
ComputeInterpolation(ClientWidth / 2
, ClientWidth), ClientHeight,
BGRAWhite, False
);
end
;
Les rotations sont souvent regroupées par deux : celle qui tourne dans le sens des aiguilles d'une montre avec celle qui tourne dans le sens contraire. Un paramètre booléen permet alors de discriminer les deux cas.
Voici par exemple la rotation qui se déplace vers la droite :
procedure
TGVTransition.DoRotateRight(CCW: boolean
);
// *** CCWRotateRight et CWRotateRight ***
var
L2Pi: Integer
;
begin
if
CCW then
L2Pi := -360
else
L2Pi := 360
;
fLBGRAFrom.PutImageAngle(ComputeInterpolation(0
, ClientWidth / 2
),
ComputeInterpolation(ClientHeight, ClientHeight / 2
),
fLBGRATo, ComputeInterpolation(0
, L2Pi), ClientWidth / 2
,
ClientHeight / 2
, DestinationOpacity);
end
;
Enfin, certaines méthodes restent plutôt complexes parce qu'elles utilisent plusieurs transformations. Par exemple, voici la transition qui se dirige vers la gauche tout en effectuant une rotation et en agrandissant l'image de destination :
procedure
TGVTransition.DoRotateGrowLeft(CCW: Boolean
);
// *** RotateGrowCCWLeft et RotateGrowCWLeft ***
var
LBGRATemp: TBGRAAffineBitmapTransform;
begin
LBGRATemp := TBGRAAffineBitmapTransform.Create(fLBGRATo);
try
LBGRATemp.RotateDeg(ifthen(CCW, ComputeInterpolation(0
, -360
),
ComputeInterpolation(0
, 360
)));
LBGRATemp.Scale(fStep / fLastLoop);
LBGRATemp.Translate(ComputeInterpolation(ClientWidth, 0
),
0
);
fLBGRAFrom.Fill(LBGRATemp, dmDrawWithTransparency);
finally
LBGRATemp.Free;
end
;
end
;
Comme quelques autres, elle a besoin d'une image temporaire supplémentaire pour arriver à ses fins.
Les transitions les plus originales ont même besoin de deux objets supplémentaires ! Voici par exemple la transition qui agrandit l'image de destination tout en créant un effet de tourbillon :
procedure
TGVTransition.DoMultiTwirl;
// *** MultiTwirl ***
var
LBGRATemp: TBGRAAffineScannerTransform;
LTwirl: TBGRATwirlScanner;
begin
LBGRATemp := TBGRAAffineScannerTransform.Create(fLBGRATo);
try
LBGRATemp.Scale(fStep / fLastLoop);
LTwirl := TBGRATwirlScanner.Create(LBGRATemp,
Point(ClientWidth div
2
, ClientHeight div
2
), (fLastLoop - fStep) *
ifthen(ClientWidth > ClientHeight, ClientWidth, ClientHeight));
try
fLBGRAFrom.Fill(LTwirl, dmDrawWithTransparency);
finally
LTwirl.Free;
end
;
finally
LBGRATemp.Free;
end
;
end
;
L'édition du fichier inclus transitions.inc montrera que certaines transitions sont inédites : il faut bien donner un peu de substance supplémentaire à ce tutoriel, non ?
VII-B. Les méthodes de traitement des images▲
Reste à compléter les méthodes chargées de l'exploitation de toutes ces transitions en attente.
InitTransition est de loin la plus courte. Son rôle se résume à faire les calculs en amont de l'exécution de la transition. Ce sont les splines qui occupent la place :
procedure
TGVTransition.InitTransition;
// *** pré-traitement ***
begin
case
Transition of
traSplinesLeft, traSplinesRight: DoPrepareSpline;
traSplinesUp, traSplinesDown: DoPrepareSplineUpOrDown;
end
;
end
;
ComputeSource s'occupe de l'image d'origine. D'une part, elle déplace l'image si nécessaire ; d'autre part, elle traite les effets spéciaux qui peuvent être appliqués :
procedure
TGVTransition.ComputeSource;
// *** traitement de l'image source ***
var
LBGRATemp: TBGRABitmap;
begin
fX := 0
;
fY := 0
;
fLBGRAFrom.FillRect(ClientRect, Color);
case
Transition of
traGrowPushUp, traLeaveUp, traPushUp: DoUp;
traGrowPushDown, traLeaveDown, traPushDown: DoDown;
traGrowPushLeft, traLeaveLeft, traPushLeft: DoLeft;
traGrowPushRight, traLeaveRight, traPushRight: DoRight;
traLeaveBottomRight: DoBottomRight;
traLeaveBottomLeft: DoBottomLeft;
traLeaveTopRight: DoTopRight;
traLeaveTopLeft: DoTopLeft;
end
;
// effets spéciaux
case
fSpecialEffect of
speEmboss: LBGRATemp := fBGRAFrom.FilterEmboss(ComputeInterpolation(0
, 180
),
ComputeInterpolationInt(64
, 256
)) as
TBGRABitmap;
speContour: LBGRATemp := fBGRAFrom.FilterContour as
TBGRABitmap;
speNegative: begin
LBGRATemp := fBGRAFrom.Duplicate as
TBGRABitmap;
LBGRATemp.Negative;
end
;
speLinearNegative: begin
LBGRATemp := fBGRAFrom.Duplicate as
TBGRABitmap;
LBGRATemp.LinearNegative;
end
;
speSwapRedBlue: begin
LBGRATemp := fBGRAFrom.Duplicate as
TBGRABitmap;
LBGRATemp.SwapRedBlue;
end
;
speSphere: LBGRATemp := fBGRAFrom.FilterSphere as
TBGRABitmap;
speRotate: LBGRATemp := fBGRAFrom.FilterRotate(PointF(ClientWidth / 2
,
ClientHeight/ 2
), ComputeInterpolation(0
, 360
)) as
TBGRABitmap;
speGrayscale: begin
LBGRATemp := fBGRAFrom.Duplicate as
TBGRABitmap;
LBGRATemp.InplaceGrayscale;
end
;
spePixelate: LBGRATemp := fBGRAFrom.FilterPixelate(ComputeInterpolationInt(2
, 24
),
True
) as
TBGRABitmap;
speTwirl: LBGRATemp := fBGRAFrom.FilterTwirl(
Point(ClientWidth div
2
, ClientHeight div
2
),
ifthen(ClientWidth > ClientHeight, ComputeInterpolation(0
, ClientWidth),
ComputeInterpolation(0
, ClientHeight))) as
TBGRABitmap;
speBlurMotion: LBGRATemp := fBGRAFrom.FilterBlurMotion(ComputeInterpolation(0
, 15
),
ComputeInterpolation(0
, 180
), False
) as
TBGRABitmap;
else
LBGRATemp := fBGRAFrom.Duplicate as
TBGRABitmap;
end
;
try
fLBGRAFrom.PutImage(Round(fX), Round(fY), LBGRATemp, dmDrawWithTransparency,
SourceOpacity);
finally
LBGRATemp.Free;
end
;
end
;
ComputeDestination s'occupe de l'image de destination. Sa principale caractéristique est l'application d'un masque produit par les routines appelées en amont :
procedure
TGVTransition.ComputeDestination;
// *** traitement de l'image destination ***
begin
fX := 0
;
fY := 0
;
fLBGRAMask.FillRectAntialias(0
, 0
, ClientWidth, ClientHeight, BGRABlack);
case
Transition of
traUncoverDown: DoUncoverUpOrDown(False
);
traUncoverUp: DoUncoverUpOrDown(True
);
traUncoverLeft: DoUncover(False
);
traUncoverRight: DoUncover(True
);
traOverDown, traPushDown: DoOverUpOrDown(False
);
traOverUp, traPushUp: DoOverUpOrDown(True
);
traOverLeft, traPushLeft: DoOver(False
);
traOverRight, traPushRight: DoOver(True
);
traUncoverBottomRight: DoUncoverBottom(True
);
traUncoverBottomLeft: DoUncoverBottom(False
);
traUncoverTopRight: DoUncoverTop(True
);
traUncoverTopLeft: DoUncoverTop(False
);
traUncoverHorizontalBackAndForth: DoUncoverHorizontalBackAndForth;
traUncoverVerticalBackAndForth: DoUncoverVerticalBackAndForth;
traOverSlipBottomRight: DoOverSlipBottom(True
);
traOverSlipBottomLeft: DoOverSlipBottom(False
);
traOverSlipTopRight: DoOverSlipTop(True
);
traOverSlipTopLeft: DoOverSlipTop(False
);
traOverBottomRight: DoOverBottom(True
);
traOverBottomLeft: DoOverBottom(False
);
traOverTopRight: DoOverTop(True
);
traOverTopLeft: DoOverTop(False
);
traBottomRightExpand: DoBottomExpand(True
);
traBottomLeftExpand: DoBottomExpand(False
);
traTopRightExpand: DoTopExpand(True
);
traTopLeftExpand: DoTopExpand(False
);
traLeaveUp: DoLeaveUpOrDown(True
);
traLeaveDown: DoLeaveUpOrDown(False
);
traLeaveLeft: DoLeave(False
);
traLeaveRight: DoLeave(True
);
traBottomRightShrink, traLeaveBottomRight: DoLeaveBottom(True
);
traBottomLeftShrink, traLeaveBottomLeft: DoLeaveBottom(False
);
traTopRightShrink, traLeaveTopRight: DoLeaveTop(True
);
traTopLeftShrink, traLeaveTopLeft: DoLeaveTop(False
);
traHorizontalExpand: DoHorizontalExpand;
traVerticalExpand: DoVerticalExpand;
traHorizontalShrink: DoHorizontalShrink;
traVerticalShrink: DoVerticalShrink;
traRectExpand: DoRectExpand;
traRectShrink: DoRectShrink;
traEllipseExpand: DoEllipseExpand;
traEllipseShrink: DoEllipseShrink;
traDiskExpand: DoDiskExpand;
traDiskShrink: DoDiskShrink;
traCrossExpand: DoCrossExpand;
traCrossShrink: DoCrossShrink;
traShrinkExpandDown: DoShrinkExpandUpOrDown(False
);
traShrinkExpandUp: DoShrinkExpandUpOrDown(True
);
traTrianglesExpand: DoTrianglesExpand;
traTrianglesShrink: DoTrianglesShrink;
traMegaStar: DoMegaStar;
traSplinesDown: DoSplinesDown;
traSplinesUp: DoSplinesUp;
traSplinesRight: DoSplinesRight;
traSplinesLeft: DoSplinesLeft;
traStripsDown: DoStripsUpOrDown(False
);
traStripsUp: DoStripsUpOrDown(True
);
traStripsRight: DoStrips(True
);
traStripsLeft: DoStrips(False
);
traStripsInterlacedLeftRight: DoStripsHorizontalInterlaced(True
);
traStripsInterlacedRightLeft: DoStripsHorizontalInterlaced(False
);
traStripsInterlacedUpDown: DoStripsInterlaced(True
);
traStripsInterlacedDownUp: DoStripsInterlaced(False
);
traRectUpDown: DoRectUpOrDown(False
);
traRectDownUp: DoRectUpOrDown(True
);
traWipeRight: DoWipe(True
);
traWipeLeft: DoWipe(False
);
traOverRightSin: DoOverSin(True
);
traOverLeftSin: DoOverSin(False
);
traBouncingRight: DoBouncing(True
);
traBouncingLeft: DoBouncing(False
);
traJumpRight: DoJump(True
);
traJumpLeft: DoJump(False
);
traFadeIn: DoFadeIn;
//traDummy: DoDummy;
else
DoNegateMask;
end
;
fLBGRATo.PutImage(Round(fX), Round(fY), fBGRATo, dmSet);
fLBGRATo.ApplyMask(fLBGRAMask);
end
;
Le masque est calculé pour toutes les transitions. Au cas où il ne serait pas utile, il est inversé par la méthode DoNegateMask dont le code est rudimentaire :
procedure
TGVTransition.DoNegateMask;
// *** masque inversé ***
begin
fLBGRAMask.Negative;
end
;
Nous avons ainsi la certitude que toute l'image sera bien conservée. Ensuite, il suffit de recopier l'image originale fBGRATo dans celle de travail fLBGRATo et d'appliquer le masque fLBGRAMask à cette dernière.
Enfin, ComputeFinalDraw prend en charge le traitement final de l'image, en particulier si aucun masque n'a été appliqué :
procedure
TGVTransition.ComputeFinalDraw;
// *** traitement final de l'image ***
begin
case
Transition of
traCCWRotateRight: DoRotateRight(True
);
traCWRotateRight: DoRotateRight(False
);
traCCWCenterRotate: DoCenterRotate(True
);
traCWCenterRotate: DoCenterRotate(False
);
traCCWRotateLeft: DoRotateLeft(True
);
traCWRotateLeft: DoRotateLeft(False
);
traCWSpiral: DoSpiral(False
);
traCCWSpiral: DoSpiral(True
);
traGrowCenter: DoGrowCenter;
traGrowBottomRight: DoGrowBottom(True
);
traGrowBottomLeft: DoGrowBottom(False
);
traGrowTopRight: DoGrowTop(True
);
traGrowTopLeft: DoGrowTop(False
);
traGrowCWRotate: DoGrowRotate(False
);
traGrowCCWRotate: DoGrowRotate(True
);
traGrowCenterCCWRotate: DoGrowCenterRotate(True
);
traGrowCenterCWRotate: DoGrowCenterRotate(False
);
traGrowPushRight, traGrowOverRight: DoGrowOver(True
);
traGrowPushLeft, traGrowOverLeft: DoGrowOver(False
);
traGrowPushUp, traGrowOverUp: DoGrowOverUpOrDown(True
);
traGrowPushDown, traGrowOverDown: DoGrowOverUpOrDown(False
);
traRotateCCWGrowLeft: DoRotateGrowLeft(True
);
traRotateCWGrowLeft: DoRotateGrowLeft(False
);
traRotateCCWGrowTopLeft: DoRotateGrowTopLeft(True
);
traRotateCWGrowTopLeft: DoRotateGrowTopLeft(False
);
traRotateCCWTopLeft: DoRotateTopLeft(True
);
traRotateCWTopLeft: DoRotateTopLeft(False
);
traRotateCCWBottomRight: DoRotateBottomRight(True
);
traRotateCWBottomRight: DoRotateBottomRight(False
);
traMappingDown: DoMappingDown;
traMappingUp: DoMappingUp;
traMultiTwirl: DoMultiTwirl;
traTwirl: DoTwirl;
traSphere: DoSphere;
traCylinderRight: DoCylinderRight;
traCylinderLeft: DoCylinderLeft;
traMultiExpand: DoMultiExpand;
traMultiExpandTwirl: DoMultiExpandTwirl;
traDrawFromBounds: DODrawFromBounds;
traOffsetTopLeft: DoOffset(False
);
traOffsetBottomRight: DoOffset(True
);
traPixelate: DoPixelate;
traBlurMotion: DoBlurMotion;
{$IFDEF Debug_Transitions}
traDummy: DoDummy;
{$ENDIF}
else
fLBGRAFrom.PutImage(0
, 0
, fLBGRATo, dmDrawWithTransparency, DestinationOpacity);
end
;
Les transitions sans masque opèrent directement sur l'image de travail fBGRAFrom alors que les autres transitions utilisent la méthode PutImage finale pour actualiser cette même image. L'essentiel est que fBGRAFrom contienne l'image à afficher : c'est, faut-il le rappeler, elle qui est invoquée dans Paint.
Finalement, toutes ces méthodes se résument à une longue suite de cas possibles auxquels sont appliquées quelques méthodes comme PutImage ou ApplyMask. Si nous ne tenons pas compte des méthodes implémentant les transitions dans le fichier inclus, les effets spéciaux sont les seuls à faire appel à des outils plus sophistiqués.
VII-C. Finition de la classe▲
La déclaration de la classe est affectée par ces modifications. Nous avons en effet besoin de quelques champs privés pour définir les coordonnées sur lesquelles s'appuieront les transitions pour dessiner :
fY, fX: Single
;
fPts, fPtsTemp: array
of
TPointF;
Pour des résultats plus précis, les transitions du composant utilisent des nombres réels de type Single et non des entiers.
Bien sûr, Il faut aussi déclarer la liste des méthodes des transitions :
// liste des transitions
procedure
DoNegateMask;
procedure
DoPrepareSpline;
procedure
DoPrepareSplineUpOrDown;
procedure
DoUp;
procedure
DoDown;
procedure
DoRight;
procedure
DoLeft;
procedure
DoBottomRight;
procedure
DoBottomLeft;
procedure
DoTopRight;
procedure
DoTopLeft;
procedure
DoUncoverUpOrDown(Up: boolean
);
procedure
DoUncover(ToRight: boolean
);
procedure
DoOverUpOrDown(Up: boolean
);
procedure
DoOver(ToRight: boolean
);
procedure
DoUncoverBottom(ToRight: boolean
);
procedure
DoUncoverTop(ToRight: boolean
);
procedure
DoUncoverHorizontalBackAndForth;
procedure
DoUncoverVerticalBackAndForth;
procedure
DoOverSlipTop(ToRight: boolean
);
procedure
DoOverSlipBottom(ToRight: boolean
);
procedure
DoOverBottom(ToRight: boolean
);
procedure
DoOverTop(ToRight: boolean
);
procedure
DoBottomExpand(ToRight: boolean
);
procedure
DoTopExpand(ToRight: boolean
);
procedure
DoLeaveUpOrDown(Up: boolean
);
procedure
DoLeave(ToRight: boolean
);
procedure
DoLeaveBottom(ToRight: boolean
);
procedure
DoLeaveTop(ToRight: boolean
);
procedure
DoVerticalExpand;
procedure
DoHorizontalExpand;
procedure
DoHorizontalShrink;
procedure
DoVerticalShrink;
procedure
DoRectExpand;
procedure
DoRectShrink;
procedure
DoEllipseExpand;
procedure
DoEllipseShrink;
procedure
DoDiskExpand;
procedure
DoDiskShrink;
procedure
DoCrossExpand;
procedure
DoCrossShrink;
procedure
DoShrinkExpandUpOrDown(Up: boolean
);
procedure
DoTrianglesExpand;
procedure
DoTrianglesShrink;
procedure
DoMegaStar;
procedure
DoSplinesDown;
procedure
DoSplinesUp;
procedure
DoSplinesRight;
procedure
DoSplinesLeft;
procedure
DoStrips(ToRight: boolean
);
procedure
DoStripsUpOrDown(Up: boolean
);
procedure
DoStripsHorizontalInterlaced(LeftFirst: boolean
);
procedure
DoStripsInterlaced(UpFirst: boolean
);
procedure
DoRectUpOrDown(Up: Boolean
);
procedure
DoWipe(ToRight: boolean
);
procedure
DoOverSin(ToRight: boolean
);
function
Parabola(AOffset, AMax, APerCent: Integer
): Single
;
procedure
DoBouncing(ToRight: boolean
);
procedure
DoJump(ToRight: boolean
);
procedure
DoBlank;
procedure
DoFadeIn;
procedure
DoRotateRight(CCW: boolean
);
procedure
DoCenterRotate(CCW: boolean
);
procedure
DoRotateLeft(CCW: boolean
);
procedure
DoSpiral(CCW: Boolean
);
procedure
DoGrowCenter;
procedure
DoGrowBottom(ToRight: Boolean
);
procedure
DoGrowTop(ToRight: Boolean
);
procedure
DoGrowRotate(CCW: Boolean
);
procedure
DoGrowCenterRotate(CCW: Boolean
);
procedure
DoGrowOver(ToRight: Boolean
);
procedure
DoGrowOverUpOrDown(Up: Boolean
);
procedure
DoRotateGrowLeft(CCW:Boolean
);
procedure
DoRotateGrowTopLeft(CCW:Boolean
);
procedure
DoRotateTopLeft(CCW:Boolean
);
procedure
DoRotateBottomRight(CCW:Boolean
);
procedure
DoMappingDown;
procedure
DoMappingUp;
procedure
DoMultiTwirl;
procedure
DoTwirl;
procedure
DoSphere;
procedure
DoCylinderRight;
procedure
DoCylinderLeft;
procedure
DoMultiExpand;
procedure
DoMultiExpandTwirl;
procedure
DoDrawFromBounds;
procedure
DoOffset(ToRight: Boolean
);
procedure
DoPixelate;
procedure
DoBlurMotion;
{$IFDEF Debug_Transitions}
procedure
DoDummy;
{$ENDIF}
Les transitions sont plus nombreuses que ces méthodes : c'est que certaines d'entre elles prennent en charge plusieurs transitions aux fonctionnements similaires.
Notre composant est enfin opérationnel !
VIII. Conclusion▲
Avec ce tutoriel, vous êtes parvenu au terme de la construction du composant TGVTransition. Il vous suffit de l'installer et d'imaginer les applications qui pourront en faire usage. Encore mieux, vous pouvez l'améliorer, soit en le rendant plus rapide, soit en créant de nouvelles transitions spectaculaires. À vous de jouer !
Pour ceux qui chercheraient à compléter leurs connaissances, l'ultime tutoriel de la série proposera l'installation du paquet dans l'EDI Lazarus ainsi que des exemples d’utilisation de ce nouveau composant.
Mes remerciements vont à Alcatîz et à BeanzMaster pour leur relecture technique et à f-leb pour la correction orthographique.