I. Introduction▲
Les programmes de test sont présents dans le répertoire exemples accompagnant le tutoriel.
Dans ce tutoriel, je proposerai une application rudimentaire qui servira de modèle pour le test des transitions. Cette application sera l'occasion de mettre en œuvre les techniques de base de la bibliothèque. Ensuite, dans des tutoriels futurs, je présenterai dans le détail une série de transitions : il s'agira de bien comprendre ce qui est attendu et comment le rendu répondra à cette attente. Plus tard encore, je fournirai une application plus conséquente pour tester un composant prenant en charge les techniques étudiées. Enfin, un composant entièrement opérationnel sera élaboré de telle façon qu'il puisse être intégré à la palette de Lazarus.
Les connaissances en mathématiques pour la plupart des transitions seront limitées aux opérations élémentaires. Si nécessaires, des éclaircissements seront apportés au coup par coup.
II. Installation et application modèle▲
Afin de pouvoir procéder à des tests, une application plutôt simple nous servira de modèle. À partir d'une image source, il s'agira de passer à une image de destination grâce à une transition déterminée. La vitesse de transition ainsi que l'opacité des images en jeu seront paramétrables.
II-A. L'interface utilisateur▲
[Exemple BGRABitmap 01]
Commençons par créer une application à partir du menu « Fichier », option « Nouveau... ». Dans la fenêtre qui s'ouvre, choisissons « Fichier », « Application ».
En choisissant « Fichier », « Application », nous avons créé la forme la plus fréquente des applications gérées par Lazarus, à savoir une application graphique utilisant la bibliothèque LCL multiplateforme comme interface graphique.
Un bon réflexe consiste à sauvegarder tout de suite cet embryon de programme. Créons un répertoire de travail nommé bgratransitions dans lequel figurera un sous-répertoire nommé 01. Sauvegardons-y notre projet avec Ctrl-Maj-S ou l'icône correspondante sous le nom projet01.lpi. De même, à l'invite, enregistrons la fiche principale sous le nom de main.pas.
L'interface utilisateur ne met en jeu que quelques composants des plus communs. En premier lieu, il nous faut trois TImage : une image pour la source, une pour la destination et une pour dessiner la transition en cours. Nous leur donnerons des tailles différentes afin de rendre l'application plus souple quant au format des images à afficher. Un TButton nous servira à démarrer la transition. Un TCheckBox indiquera si la transparence doit être prise en compte. Enfin, un couple TLabel et TTrackBar nous permettra de régler la vitesse de la transition.
Voici à quoi pourra ressembler cette interface :
Pour une meilleure exploitation de l'application, nous devons modifier certaines propriétés des composants utilisés.
En ce qui concerne la fiche principale, nous pouvons :
- la rebaptiser en donnant à sa propriété Name la valeur MainForm ;
- la centrer dans l'écran avec sa propriété Position mise à poScreenCenter ;
- afficher un message adapté en en-tête avec la propriété Caption mise à 'Test des transitions - G. Vasseur 2018'.
L'image source (en haut à gauche) aura sa propriété Name fixée à imgFrom alors que l'image de destination (en haut à droite) sera renommée imgTo. Toutes deux auront leur propriété Stretch à True afin de toujours dessiner entièrement l'image, quelles que soient ses dimensions d'origine.
L'image pour le dessin de la transition sera nommée imgResult. Comme le calcul de la transition tiendra compte des dimensions de la surface de dessin, il est inutile de modifier la propriété Stretch de ce composant.
Le bouton pour l'exécution de la transition sera renommé btnGo tandis que sa propriété Caption deviendra « Démarrez ! ». Nous pouvons d'ores et déjà double-cliquer sur ce bouton pour créer le squelette du gestionnaire OnClick qui lui est attaché. Nous renseignerons ultérieurement ce gestionnaire chargé de lancer la transition.
Le composant de type TCheckBox sera nommé cbOpacity et sa propriété Caption deviendra « Transparence ». Nous nous occuperons plus tard de l'action liée à la modification de sa coche.
Le composant de type TTrackBar sera renommé tbarSpeed. Sa propriété Min sera fixée à 1. Sa propriété Max sera portée à 10. Quant à sa propriété Position, nous lui donnerons la valeur 3. Ses différentes valeurs seront expliquées en temps utile. Le gestionnaire de changement de valeur sera lui aussi étudié à cette occasion.
Enfin, le TLabel s'appellera lblSpeed et sa propriété Caption deviendra « Vitesse ». Comme cette étiquette se rapporte au composant de type TTrackBar, il est conseillé de le lier à lui grâce à la propriété FocusControl. Le choix du composant cible se fait grâce à la liste déroulante qui apparaît lorsque nos cliquons sur la valeur de la propriété.
FocusControl n'aura pas d'utilité dans notre application, mais il vaut mieux prendre l'habitude de renseigner cette propriété indispensable dans certains cas où l'utilisation du clavier est obligatoire.
Pour ceux qui sont habitués à la lecture des fichiers de description des fiches (suffixe .lfm), voici la fiche que j'ai utilisée :
object MainForm: TMainForm
Left = 392
Height = 450
Top = 210
Width = 677
Caption = 'Test des transitions - G. Vasseur 2018'
ClientHeight = 450
ClientWidth = 677
OnCreate = FormCreate
OnDestroy = FormDestroy
Position = poScreenCenter
LCLVersion = '1.8.0.6'
object imgFrom: TImage
Left = 24
Height = 154
Top = 32
Width = 232
Stretch = True
end
object imgTo: TImage
Left = 304
Height = 90
Top = 32
Width = 104
Stretch = True
end
object imgResult: TImage
Left = 304
Height = 224
Top = 208
Width = 298
end
object btnGo: TButton
Left = 24
Height = 25
Top = 407
Width = 75
Caption = 'Démarrer !'
OnClick = btnGoClick
TabOrder = 0
end
object cbOpacity: TCheckBox
Left = 24
Height = 19
Top = 365
Width = 91
Caption = 'Transparence'
TabOrder = 1
end
object tbarSpeed: TTrackBar
Left = 24
Height = 25
Top = 320
Width = 100
Max = 10
Min = 1
Position = 3
TabOrder = 2
end
object lblSpeed: TLabel
Left = 24
Height = 15
Top = 292
Width = 36
Caption = 'Vitesse'
FocusControl = tbarSpeed
ParentColor = False
end
end
À présent, l'inspecteur d'objets devrait avoir cette apparence :

Appuyez sur F11 pour faire apparaître l'inspecteur d'objets s'il est invisible.
Pour parfaire notre interface, il suffit de charger les images fournies avec les fichiers du tutoriel. Nous devons double-cliquer sur la propriété Picture des deux composants TImage et charger l'image voulue. Cette dernière sera tronquée dans la fenêtre de chargement, mais, après validation du choix par un clic sur le bouton « OK », elle devrait occuper tout l'espace d'affichage du composant... si sa propriété Stretch est bien à True !
Voici ce à quoi devrait ressembler notre interface définitive :
Les deux photos de travail sont des photos personnelles. La première est celle d'une mésange bleue et d'un chardonneret qui ont cohabité quelques secondes sur le rebord d'un pot de fleur. La seconde est celle d'un rouge-gorge dans un arbre. Les deux ont été prises au téléobjectif (200 mm) avec un Nikon D5500 depuis la fenêtre de ma cuisine. Il est ainsi prouvé qu'on peut s'intéresser aux oiseaux, aimer cuisiner, tout en trouvant aussi du plaisir à programmer !
II-B. L'installation de BGRABitmap▲
Une fois notre interface utilisateur prête, nous pouvons nous intéresser au code chargé d'afficher une transition entre nos deux photos d'oiseaux. Comme nous allons utiliser la bibliothèque BGRABitmap, nous devons tout d'abord nous la procurer et l'installer.
Pour se procurer la bibliothèque, c'est ici que ça se passe. Après avoir téléchargé le fichier zip, nous pouvons, par exemple, le décompacter dans le dossier components de Lazarus. La seule partie utile pour nous est le sous-répertoire bgrabitmap que nous placerons directement dans le répertoire components ou à tout autre endroit accessible en lecture et écriture.
L'installation du paquet est très simple. Depuis le gestionnaire de fichiers du système d'exploitation, ouvrons le paquet bgrabitmappack.lpk. L'EDI devrait se charger pour afficher la fenêtre suivante :
Si le chargement d'un paquet (.lpk), d'un projet (.lpi ou .lpr) ou d'un fichier de code source (.pas ou .pp) ne s'effectue pas automatiquement, vous pouvez toujours opérer depuis l'EDI avec l'option « Ouvrir » du menu principal « Fichier ».
Comme le paquet ne contient aucun composant, il n'installera rien sur la palette. Nous avons seulement à demander sa compilation en cliquant sur l'icône marquée « Compiler ». En quelques secondes, le tour est joué !
Désormais, si nous désirons utiliser cette bibliothèque, nous procéderons comme indiqué ci-après.
Nous commençons par demander à voir l'inspecteur de projet depuis le menu Projet :
La fenêtre qui s'affiche indique les fichiers et paquets utilisés par le projet en cours. Pour utiliser le paquet BGRABitmap, nous cliquons avec le bouton droit de la souris sur « Paquets requis » :
Aussitôt s'affiche un menu surgissant rudimentaire avec pour seule option la possibilité d'ajouter un paquet :
Dans la liste proposée, nous choisissons BGRABitmapPack avant de valider ce choix avec le bouton marqué « Ok » :
Le paquet est dorénavant intégré au projet. Il sera par conséquent possible d'y faire référence, Lazarus retrouvant les unités nécessaires en cas de besoin. L'intégration du paquet est explicitement marquée dans l'arborescence du projet :

Nous pouvons à présent travailler avec notre nouveau projet en toute sérénité !
II-C. L'application de base▲
Grâce aux étapes précédentes, nous avons une interface utilisateur en place et une bibliothèque prête à l'emploi. Reste à concevoir la partie métier de l'application modèle.
Dans un premier temps, afin de nous simplifier la tâche, nous laisserons de côté les notions de vitesse et de transparence pour nous concentrer sur la construction des objets nécessaires. Nous en profiterons pour réfléchir aux éléments indispensables à toute transition dans l'affichage de deux images, c'est-à-dire au contenu du gestionnaire OnClick de l'unique bouton de l'application.
II-C-1. La construction des objets▲
Pour travailler, la bibliothèque BGRABitmap fait appel à au moins deux unités (BGRABitmapTypes et BGRABitmap) que nous allons immédiatement ajouter à la clause uses de l'interface de l'unité principale baptisée ici main.pas :
unit
main;
{$
mode
objfpc
}
{$
H+
}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
StdCtrls, ComCtrls, BGRABitmapTypes, BGRABitmap;
L'unité BGRABitmapTypes, comme son nom l'indique, définit les types utilisés par la bibliothèque tandis que l'unité BGRABitmap s'occupe d'adapter la plupart des demandes du programmeur aux différentes plates-formes gérées. Ainsi, derrière un même appel peut se cacher une façon différente de traiter un problème graphique sans que l'utilisateur n'ait à s'en soucier.
La classe essentielle à utiliser est TBGRABitmap. C'est elle qui prend en charge l'anticrénelage et la transparence à travers une organisation sur 32 bits de chaque pixel. BGRA est d'ailleurs l'acronyme de Blue, Green, Red et Alpha, chacun de ces termes désignant un canal permettant de déterminer une des caractéristiques de chaque pixel.
Un pixel est en effet obtenu à partir de la composition d'un octet pour la quantité de bleu, d'un autre pour le vert, d'un troisième pour le rouge, et d'un dernier pour l'opacité (canal alpha). Plus un octet contient une valeur forte plus le canal correspondant est prononcé. En particulier, une valeur de 0 pour le canal alpha indique une complète transparence alors qu'une valeur de 255 du même canal marque une opacité totale.
Pour accéder à ces pixels et dessiner, il faut tout d'abord créer des objets de type TBGRABitmap. Dans la partie privée de la classe représentant la fiche principale (TMainForm), nous déclarons par conséquent deux objets de travail :
private
fBGRAFrom, fBGRATo: TBGRABitmap;
Chaque objet abritera une image, ce qu'indiquera leur création. Les constructeurs possibles pour de tels objets sont multiples, mais nous ne sous servirons pour notre première application que de celui qui prend en paramètre un TBitmap tel que fourni par défaut par la LCL. Ce type de constructeur récupère le bitmap d'un autre objet afin de pouvoir le manipuler à sa guise avant de le restituer grâce à une méthode nommée Draw. Ici, il s'agit de récupérer le bitmap de chaque composant TImage.
Le meilleur emplacement pour créer de tels objets est bien souvent le gestionnaire FormCreate de la fiche principale. Nous en obtenons le squelette en double-cliquant dans l'inspecteur d'objets sur la partie valeur de la propriété OnCreate de l'objet MainForm. Ce squelette est alors à renseigner ainsi :
procedure
TMainForm.FormCreate(Sender: TObject);
//
***
construction
des
objets
de
travail
***
begin
fBGRAFrom := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
end
;
Comme nous ne savons pas a priori quelle taille fait une image à traiter, après l'avoir créée, nous devons la redimensionner pour que nos deux images soient compatibles et puissent fusionner dans la troisième, celle du résultat de la transition à une étape donnée.
Par conséquent, nous complétons notre gestionnaire avec un appel à la méthode Resample de l'objet créé. Cette méthode sait redimensionner de manière élégante, c'est-à-dire avec peu de perte grâce à un algorithme adapté, tout bitmap qui lui est confié.
Le redimensionnement se fait ici en fonction de la largeur et de la hauteur de l'image d'accueil, à savoir imgResult :
procedure
TMainForm.FormCreate(Sender: TObject);
//
***
construction
des
objets
de
travail
***
begin
fBGRAFrom := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
fBGRAFrom := fBGRAFrom.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as
TBGRABitmap;
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
fBGRATo := fBGRATo.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as
TBGRABitmap;
end
;
Vous remarquerez le transtypage du résultat de la méthode Resample par as accompagné du type TBGRABitmap. En effet, la méthode renvoie un objet de type TBGRACustomBitmap qui doit devenir le type TBGRABitmap attendu par les variables affectées.
La méthode Resample accepte d'autres modes que celui par défaut pour redimensionner l'image prise en charge. Ces modes établissent tous un compromis entre la vitesse d'exécution et la finesse du traitement, le résultat dépendant du poids de chacun de ces deux facteurs. Le mode par défaut rmFineResample conviendra sans problème à notre exemple.
II-C-2. La destruction des objets▲
La destruction des objets se fera naturellement dans le gestionnaire FormDestroy de la fiche principale. Il est lui aussi obtenu en double-cliquant dans l'inspecteur d'objets sur la partie valeur de la propriété OnDestroy de l'objet MainForm. Sa rédaction est des plus simples :
procedure
TMainForm.FormDestroy(Sender: TObject);
//
***
destruction
des
objets
de
travail
***
begin
fBGRAFrom.Free;
fBGRATo.Free;
end
;
Rappelez-vous qu'un objet créé doit toujours être détruit sous peine de fuites de mémoire gênantes par accumulation. Les objets d'interface et la plupart des composants sont détruits automatiquement par des procédés spéciaux intégrés que ne possèdent par les objets ordinaires.
II-C-3. L'action du bouton▲
Nous disposons de deux objets graphiques pour travailler. Le gestionnaire de l'événement OnClick du bouton btnGo va à présent abriter la logique permettant l'affichage des transitions.
Ici aussi nous déclarerons localement une variable LBGRATemp de type TBGRABitmap. C'est elle qui servira à manipuler nos deux premiers objets. Après l'avoir créée, elle sera peinte en noir et remplie avec l'image source. Un traitement à définir sera appliqué, en particulier sur les coordonnées des images de travail (pour le moment, cette partie sera vide). L'image de destination sera alors peinte par transparence sur la première. Enfin, le résultat sera transmis au composant imgResult de type TImage qu'il faudra repeindre pour qu'il rende compte immédiatement des changements effectués.
Le tout est inséré dans une boucle gérée par la variable fStep de type byte à déclarer dans la partie privée de la fiche principale : il faudra boucler 100 fois pour décrire le cycle complet d'une transition.
private
fBGRAFrom, fBGRATo: TBGRABitmap;
fStep: Byte
;
Au niveau programmation, voici ce que donne ce gestionnaire de l'événement OnClick du bouton :
procedure
TMainForm.btnGoClick(Sender: TObject);
//
***
dessin
***
var
LBGRATemp: TBGRABitmap;
LY, LX: Integer
;
begin
//
on
désactive
le
bouton
le
temps
de
la
boucle
btnGo.Enabled := False
;
//
on
crée
le
bitmap
provisoire
de
travail
LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
//
on
protège
la
boucle
pour
être
sûr
de
libérer
les
ressources
try
//
on
fixe
les
coordonnées
par
défaut
LX := 0
;
LY := 0
;
//
on
initialise
le
compteur
de
boucle
fStep := 0
;
//
entrée
dans
la
boucle
repeat
//
premier
pas
Inc(fStep);
//
le
bitmap
de
travail
est
peint
en
noir
LBGRATemp.FillRect(ClientRect, BGRABlack);
//
on
y
peint
l'image
source
LBGRATemp.PutImage(0
, 0
, fBGRAFrom, dmSet);
//
traitement
ici...
à
venir
!!!
//
on
peint
la
seconde
image
par
transparence
LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency);
//
le
résultat
est
peint
sur
le
canevas
de
l'image
de
résultat
LBGRATemp.Draw(imgResult.Canvas, 0
, 0
);
//
on
force
cette
image
à
se
redessiner
imgResult.Repaint;
//
on
ralentit
la
boucle
sleep(10
);
//
sortie
de
la
boucle
au
centième
passage
effectué
until
fStep = 100
;
//
fin
de
la
protection
finally
//
libération
des
ressources
LBGRATemp.Free;
//
le
bouton
est
de
nouveau
opérationnel
btnGo.Enabled := True
;
end
;
end
;
Il est bon de protéger les ressources en les incluant dans un bloc try... finally. Vous vous assurez ainsi de retrouver une situation stable, avec des ressources libérées proprement et des composants dans l'état souhaité malgré les aléas des manipulations de l'utilisateur.
Les commentaires ligne par ligne devraient aider à la compréhension du fonctionnement de cette méthode. L'essentiel tient dans une boucle effectuée cent fois, chaque passage correspondant à une étape de la transition.
L'emploi d'une boucle de type repeat...until au lieu d'une boucle for s'explique par le fait que le pas des itérations est susceptible de changer en fonction de la vitesse désirée. En fait, nous verrons un peu plus loin que BGRABitmap n'est pas vraiment concernée par ce problème.
Nous noterons quand même un constructeur nouveau qui prend comme paramètres deux entiers (l'un pour la largeur, l'autre pour la hauteur de l'image) ainsi qu'une valeur de couleur (ici, une constante prédéfinie) de type TBGRAPixel défini dans l'unité BGRABitmapTypes.
Des méthodes très utiles appartenant à la classe TBGRABitmap sont par ailleurs utilisées dans notre propre méthode. FillRect remplit un rectangle avec une couleur donnée, ici le noir. PutImage dessine à une position donnée une image fournie en paramètre avec un mode déterminé (par transparence, par aplat...). Enfin, Draw dessine sur un canevas fourni en paramètre l'image contenue dans l'objet de type TBGRABitmap en cause.
Le type TBGRAPixel intéressera certains lecteurs curieux. Il s'agit en fait d'un enregistrement étendu qui encapsule une certain nombre de méthodes capables d'intervenir sur les données internes de type byte (les canaux déjà évoqués).
En voici la déclaration :
TBGRAPixel = packed
record
private
function
GetClassIntensity: word
;
function
GetClassLightness: word
;
procedure
SetClassIntensity(AValue: word
);
procedure
SetClassLightness(AValue: word
);
public
{$
IFDEF
BGRABITMAP_RGBAPIXEL
}
red, green, blue, alpha: byte
;
{$
ELSE
}
blue, green, red, alpha: byte
;
{$
ENDIF
}
procedure
FromRGB(ARed,AGreen,ABlue: Byte
; AAlpha: Byte
= 255
);
procedure
FromColor(AColor: TColor; AAlpha: Byte
= 255
);
procedure
FromString(AStr: string
);
procedure
FromFPColor(AColor: TFPColor);
procedure
ToRGB(out
ARed,AGreen,ABlue,AAlpha: Byte
); overload
;
procedure
ToRGB(out
ARed,AGreen,ABlue: Byte
); overload
;
function
ToColor: TColor;
function
ToString: string
;
function
ToGrayscale(AGammaCorrection: boolean
= true
): TBGRAPixel;
function
ToFPColor: TFPColor;
class
Operator := (Source: TBGRAPixel): TColor;
class
Operator := (Source: TColor): TBGRAPixel;
property
Intensity: word
read
GetClassIntensity write
SetClassIntensity;
property
Lightness: word
read
GetClassLightness write
SetClassLightness;
end
;
Dans le listing de notre méthode, nous remarquons l'emploi de Repaint pour redessiner l'image affichant l'étape en cours de la transition. Contrairement à d'autres méthodes comme Invalidate ou Update qui demandent aussi de redessiner l'image (totalement pour la première, partiellement pour la seconde), Repaint force le rafraîchissement du dessin sans passer par une file d'attente. L'avantage essentiel de cette méthode est l'obtention sans délai du dessin ; son inconvénient principal est un risque de scintillement.
Une ultime remarque s'impose enfin : le traitement du graphisme est suffisamment rapide grâce à cette bibliothèque pour qu'il soit (parfois) nécessaire de le ralentir. Voilà pourquoi nous avons introduit une attente dans la boucle. Sans elle, certains effets auraient été invisibles !
Une expérience intéressante consisterait à retirer imgResult avant le Repaint. Dès lors, c'est la fiche entière qui devrait être repeinte, ce qui ralentirait de manière conséquente la vitesse d'affichage.
Pour l'instant, faut d'une transition active, l'exécution semble erronément figée : il faut attendre que le bouton « Démarrer » redevienne cliquable pour fermer l'application.
II-C-4. La finition de l'unité▲
Notre unité est prête, si ce n'est de menues améliorations à prévoir. Pour le moment, nous nous contenterons de modifier par programmation la propriété Caption de la fenêtre principale pour y afficher le nom de la transition en cours. Pour ce faire, nous créons une chaîne de ressource qui facilitera une éventuelle traduction et une référence à cette chaîne dans le gestionnaire de création de la fiche.
Voici notre unité terminée :
unit
main;
{$
mode
objfpc
}
{$
H+
}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
StdCtrls, ComCtrls, BGRABitmapTypes, BGRABitmap;
type
{
TMainForm
}
TMainForm = class
(TForm)
btnGo: TButton;
cbOpacity: TCheckBox;
imgFrom: TImage;
imgTo: TImage;
imgResult: TImage;
lblSpeed: TLabel;
tbarSpeed: TTrackBar;
procedure
btnGoClick(Sender: TObject);
procedure
FormCreate(Sender: TObject);
procedure
FormDestroy(Sender: TObject);
private
fBGRAFrom, fBGRATo: TBGRABitmap;
fStep: Byte
;
public
end
;
resourcestring
rsTestName = '
Test
des
transitions
-
G.
Vasseur
2018
-
XXXXXXXXX
'
;
var
MainForm: TMainForm;
implementation
{$
R
*.lfm
}
{
TMainForm
}
procedure
TMainForm.btnGoClick(Sender: TObject);
//
***
dessin
***
var
LBGRATemp: TBGRABitmap;
LY, LX: Integer
;
begin
btnGo.Enabled := False
;
LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
try
LX := 0
;
LY := 0
;
fStep := 0
;
repeat
Inc(fStep);
LBGRATemp.FillRect(ClientRect, BGRABlack);
LBGRATemp.PutImage(0
, 0
, fBGRAFrom, dmSet);
//
traitement
ici...
LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency);
LBGRATemp.Draw(imgResult.Canvas, 0
, 0
);
imgResult.Repaint;
sleep(10
);
until
fStep = 100
;
finally
LBGRATemp.Free;
btnGo.Enabled := True
;
end
;
end
;
procedure
TMainForm.FormCreate(Sender: TObject);
//
***
construction
des
objets
de
travail
***
begin
Caption := rsTestName;
fBGRAFrom := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
fBGRAFrom := fBGRAFrom.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as
TBGRABitmap;
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
fBGRATo := fBGRATo.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as
TBGRABitmap;
end
;
procedure
TMainForm.FormDestroy(Sender: TObject);
//
***
destruction
des
objets
de
travail
***
begin
fBGRAFrom.Free;
fBGRATo.Free;
end
;
end
.
L'ensemble peut être compilé... mais rien d'extraordinaire ne se passera puisque nous n'avons prévu aucune transition particulière : nous obtenons simplement ce que nous avons demandé, à savoir l'affichage de l'image de destination.
Nous faudra-t-il encore patienter pour obtenir de véritables transitions ? Non, car nous allons procéder à un essai avec une ligne qui remplacera ou suivra le commentaire inclus dans la méthode :
//
traitement
ici...
LY := - imgResult.ClientHeight + imgResult.ClientHeight * fStep div
100
; //
OVERDOWN
Par ce simple calcul, nous demandons que l'ordonnée de l'image de destination soit incrémentée selon le pourcentage de transition réalisée. Sachant que l'ordonnée d'origine est située au-dessus du point 0 (exactement à une distance égale à la hauteur de l'image) et que les ordonnées vont croissant en informatique, cette incrémentation de -imgResult.ClientHeight à 0 va faire apparaître progressivement l'image de destination.
Nous obtenons ainsi notre première transition ! Notre capacité à la paramétrer est encore très faible, mais nous allons bientôt la doter de fonctionnalités relatives à la vitesse d'exécution puis au dessin par transparence.
La première mouture de l'application est incluse dans le répertoire 01 des fichiers d'exemples.
II-D. Introduction de la vitesse▲
[Exemple BGRABitmap 02]
En matière de vitesse, deux cas sont à examiner suivant la rapidité de traitement et d'affichage des outils graphiques utilisés : utilisation de la LCL standard ou mise en œuvre de la bibliothèque BGRABitmap.
Avec la LCL, il y a fort à parier que l'accélération sera nécessaire : on pourra par exemple supprimer l'instruction d'attente (sleep) et diminuer le nombre d'étapes d'affichage en incrémentant le pas fStep d'un nombre supérieur à 1. Toutefois, il faudra prendre garde à la limite fixée à 100. En effet, seuls les diviseurs de 100 conviendront comme pas : 1, 2, 4, 5, 10, 20, 25, 50. D'autres valeurs sont possibles, mais au prix d'une vérification de la limite et de l'achèvement de la transition. Il va sans dire que les pas supérieurs à 20 ne seront pas pertinents pour l'affichage, car plus le pas sera élevé plus l'affichage sera saccadé.
Avec BGRABitmap, le problème est au contraire une trop grande rapidité d'exécution ! Pour s'en convaincre, il suffit de reprendre l'exemple précédent avec notre première transition et de retirer l'instruction sleep. Heureusement, la maîtrise de la vitesse en est grandement simplifiée puisque nous n'aurons qu'à jouer sur le paramètre fourni à sleep pour ralentir ou accélérer l'affichage !
Dans notre fiche principale, nous allons introduire une nouvelle propriété nommée Speed qui renverra vers un champ privé fSpeed :
private
fBGRAFrom, fBGRATo: TBGRABitmap;
fSpeed: Byte
; //
vitesse
procedure
SetSpeed(AValue: Byte
);
public
property
Speed: Byte
read
fSpeed write
SetSpeed default
C_DefaultSpeed;
end
;
La constante C_DefaultSpeed vaudra 80 : elle fixe la valeur par défaut de la vitesse. Elle sera déclarée au début de l'unité, avant la fiche :
const
C_DefaultSpeed = 80
; //
vitesse
par
défaut
type
{
TMainForm
}
TMainForm = class
(TForm)
En général, il est préférable d'éviter les constantes numériques codées en dur dans le code. En effet, le changement d'une valeur grâce à une constante nommée sera immédiatement propagé dans tout le code qui sera par ailleurs bien plus lisible.
La méthode SetSpeed pourra ressembler à ceci :
procedure
TMainForm.SetSpeed(AValue: Byte
);
//
***
détermination
de
la
vitesse
***
begin
if
fSpeed = AValue then
Exit;
fSpeed := AValue;
end
;
Notons que cette méthode est inutile dans l'état puisqu'elle n'effectue pas de traitements spécifiques sur le champ interne fSpeed. Cependant, l'éventuelle introduction de tests comme le déclenchement d'un gestionnaire (OnSpeedChange, par exemple) se fera par ce biais sans effort particulier.
À présent que la vitesse est accessible en tant que propriété, il reste à l'utiliser à la place de la constante fournie à la procédure sleep :
procedure
TMainForm.btnGoClick(Sender: TObject);
//
***
dessin
***
var
LBGRATemp: TBGRABitmap;
LY, LX: Integer
;
begin
btnGo.Enabled := False
;
LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
try
LX := 0
;
LY := 0
;
fStep := 0
;
repeat
Inc(fStep);
LBGRATemp.FillRect(ClientRect, BGRABlack);
LBGRATemp.PutImage(0
, 0
, fBGRAFrom, dmSet);
//
traitement
ici...
LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency);
LBGRATemp.Draw(imgResult.Canvas, 0
, 0
);
imgResult.Repaint;
sleep(100
- fSpeed); //
changement
ici
until
fStep = 100
;
finally
LBGRATemp.Free;
btnGo.Enabled := True
;
end
;
end
;
Plus la vitesse augmente moins le paramètre fourni à sleep doit être élevé, d'où la soustraction.
Il ne faut pas oublier de mentionner la valeur par défaut de cette vitesse dans le gestionnaire de création de la fiche et d'adapter la propriété Position par défaut du composant tbarSpeed :
procedure
TMainForm.FormCreate(Sender: TObject);
//
***
construction
des
objets
de
travail
***
begin
Caption := rsTestName;
fBGRAFrom := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
fBGRAFrom := fBGRAFrom.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as
TBGRABitmap;
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
fBGRATo := fBGRATo.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as
TBGRABitmap;
fSpeed := C_DefaultSpeed; //
vitesse
tbarSpeed.Position:= Speed;
end
;
Dans notre exemple, il nous faut aussi gérer le changement de vitesse via le composant tbarSpeed, ce qui est très facile si nous considérons le gestionnaire OnChange qui lui est associé. Nous double-cliquons dans l'inspecteur d'objets sur l'événement correspondant du composant et nous fournissons le code suivant pour le traitement :
procedure
TMainForm.tbarSpeedChange(Sender: TObject);
//
***
changement
de
vitesse
***
begin
Speed := tbarSpeed.Position;
end
;
Par cette unique ligne de code, nous signifions que la vitesse prendra la même valeur que la position du curseur du composant. Il ne reste alors qu'à réintroduire notre transition de test dans le gestionnaire OnClick de notre unique bouton :
//
traitement
ici...
LY := - imgResult.ClientHeight + imgResult.ClientHeight * fStep div
100
; //
OVERDOWN
Nous pouvons désormais régler la vitesse de transition en jouant avec le curseur du composant tbarSpeed. La mouture améliorée de l'application est incluse dans le répertoire 02 des fichiers d'exemples.
II-E. Introduction de l'opacité▲
[Exemple BGRABitmap 03]
Un élément que nous aimerions déterminer est sans doute l'opacité des images, car cette fonctionnalité nous permettrait de les dessiner avec une transparence plus ou moins forte. La bibliothèque BGRABitmap propose de nombreuses routines dont un des paramètres est justement l'opacité.
Le degré de transparence est fourni dans la composition d'une couleur par un entier de 0 à 255 appelé canal alpha. Comme indiqué plus haut, la valeur 255 rend un pixel totalement opaque tandis que la valeur 0 le rend totalement transparent. Les valeurs intermédiaires permettent de nuancer cette transparence.
Toujours dans un souci d'une meilleure lisibilité de notre code, nous allons définir une constante fixant la valeur maximale de l'opacité :
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
StdCtrls, ComCtrls, BGRABitmapTypes, BGRABitmap;
const
C_DefaultSpeed = 80
;
C_MaxOpacity = 255
; //
opacité
maximale
Plutôt que de calculer à la main l'opacité pour un pourcentage donné de transition réalisée, nous allons créer une fonction appelée Opacity qui la calculera automatiquement selon une fonction linéaire tout en offrant à l'utilisateur la possibilité d'activer ou de désactiver ce traitement particulier.
Il nous faut par conséquent un champ de type booléen pour l'activation de l'opacité, une propriété manipulant ce champ et une fonction renvoyant la valeur de l'opacité suivant l'état du système à un temps déterminé. Ces éléments sont déclarés dans la classe abritant la fiche principale :
{
TMainForm
}
TMainForm = class
(TForm)
btnGo: TButton;
cbOpacity: TCheckBox;
imgFrom: TImage;
imgTo: TImage;
imgResult: TImage;
lblSpeed: TLabel;
tbarSpeed: TTrackBar;
procedure
btnGoClick(Sender: TObject);
procedure
FormCreate(Sender: TObject);
procedure
FormDestroy(Sender: TObject);
procedure
tbarSpeedChange(Sender: TObject);
private
fBGRAFrom, fBGRATo: TBGRABitmap;
fStep: Byte
;
fSpeed: Byte
;
fWithOpacity: Boolean
; //
opacité
procedure
SetWithOpacity(AValue: Boolean
);
procedure
SetSpeed(AValue: Byte
);
public
function
Opacity(Up: Boolean
= True
): Byte
; //
opacité
à
utiliser
property
Speed: Byte
read
fSpeed write
SetSpeed default
C_DefaultSpeed;
property
WithOpacity: Boolean
read
fWithOpacity write
SetWithOpacity;
end
;
Le traitement de la propriété WithOpacity est similaire à celui correspondant à la propriété Speed :
procedure
TMainForm.SetWithOpacity(AValue: Boolean
);
//
***
détermination
de
l'opacité
***
begin
if
fWithOpacity = AValue then
Exit;
fWithOpacity := AValue;
end
;
La propriété WithOpacity telle qu'elle est définie ici appelle la même remarque que sa consœur : en l'état, une simple écriture de la valeur du champ interne aurait été possible, mais l'écriture d'une méthode particulière prépare des extensions comme un gestionnaire d'événement dédié.
D'ores et déjà, nous pouvons réagir au changement de la case à cocher chargée de gérer la prise en compte ou non de l'opacité. En double-cliquant sur cette case à cocher, Lazarus crée un gestionnaire que nous renseignerons ainsi :
procedure
TMainForm.cbOpacityChange(Sender: TObject);
//
***
gestion
de
la
transparence
***
begin
WithOpacity := cbOpacity.Checked;
end
;
L'opacité sera dorénavant active si la case est cochée. Par défaut, nous savons que le champ sera initialisé à False lors de la création de la fiche : il n'est donc pas nécessaire de lui donner explicitement cette valeur dans la méthode OnCreate.
Reste bien sûr la fonction Opacity introduite par nos soins. Son paramètre fixé par défaut à True indiquera si l'opacité doit augmenter avec le pourcentage réalisé de transition ou si elle doit au contraire diminuer. Cette possibilité nous permettra par exemple de faire apparaître une image tout en faisant disparaître l'autre.
Il nous faut donc distinguer plusieurs cas, suivant si la prise en compte de l'opacité est activée ou non, et suivant la valeur du paramètre Up. Voici le code proposé :
function
TMainForm.Opacity(Up: Boolean
): Byte
;
//
***
calcul
de
l'opacité
en
fonction
du
pourcentage
effectué
***
begin
if
fWithOpacity then
begin
if
Up then
Result := fStep * C_MaxOpacity div
100
else
Result := C_MaxOpacity - fStep * C_MaxOpacity div
100
;
end
else
Result := C_MaxOpacity;
end
;
La méthode ne pose pas de problèmes insurmontables. Il s'agit de ramener un pourcentage à une valeur comprise entre 0 et 255, c'est-à-dire dans l'intervalle autorisé des valeurs de l'opacité. Si l'opacité est inactive, l'image sera totalement opaque. Si le paramètre de la fonction est à False, une soustraction permet de faire décroître l'opacité.
Pour que tout cela fonctionne, il faut encore modifier les paramètres des méthodes de BGRABitmap qui gèrent la transparence. Le gestionnaire du clic sur le bouton devient alors :
procedure
TMainForm.btnGoClick(Sender: Tobject);
//
***
dessin
***
var
LBGRATemp: TBGRABitmap;
LY, LX: Integer
;
begin
btnGo.Enabled := False
;
LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
try
LX := 0
;
LY := 0
;
fStep := 0
;
repeat
Inc(fStep);
LBGRATemp.FillRect(ClientRect, BGRABlack);
LBGRATemp.PutImage(0
, 0
, fBGRAFrom, dmSet, Opacity(False
)); //
opacité
!
//
traitement
ici...
LY := - imgResult.ClientHeight + imgResult.ClientHeight * fStep div
100
; //
OVERDOWN
LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency, Opacity); //
opacité
!
LBGRATemp.Draw(imgResult.Canvas, 0
, 0
);
imgResult.Repaint;
sleep(100
- fSpeed);
until
fStep = 100
;
finally
LBGRATemp.Free;
btnGo.Enabled := True
;
end
;
end
;
Nous avons choisi de faire disparaître peu à peu l'image d'origine tandis que l'image qui la recouvre voit sa visibilité s'accroître dans les mêmes proportions. Il suffit de lancer notre application qui servira de modèle avec l'unique transition que nous connaissons et de cocher la case marqué « Transparence » pour obtenir des images comme celle-ci :
Nous voyons que le rouge-gorge commence à se dessiner alors que la mésange et le chardonneret s'évanouissent déjà.
La fonction Opacity définit par défaut la valeur de son paramètre Up p à True. Dès lors, il n'est pas nécessaire d'indiquer de paramètre si c'est la valeur recherchée. À la place de Opacity, nous aurions cependant tout aussi bien pu écrire Opacity(True) ou Opacity().
[Exemple BGRABitmap 04]
L'exemple ci-dessus contient l'application qui reprend tous les éléments étudiés plus haut.
III. Conclusion▲
La première partie de notre travail s'achève ici : nous avons installé la bibliothèque BGRABitmap et réalisé l'application qui nous servira de modèle pour les tests. Grâce à cette dernière, nous aurons deux images pour travailler et de quoi gérer l'opacité et la vitesse d'affichage de la résultante du traitement par nos transitions. Le tutoriel suivant définira et implémentera les transitions elles-mêmes.