Effets de transition avec Lazarus et BGRABitmap 1

1. Installation et application de démonstration

Gilles Vasseur

La série de tutoriels inaugurée par celui que vous lisez en ce moment est destinée au programmeur soucieux d'exploiter au mieux le graphisme avec Lazarus, mais peut-être aussi à celui qui souhaite découvrir ou collecter des algorithmes pour créer des transitions d'image à image, par exemple pour un diaporama. Si les explications s'appuient sur Free Pascal et la bibliothèque BGRABitmap, tous deux des outils gratuits, open source et multiplateformes qui ont fait leurs preuves, elles ne se limitent pas à une illustration de leurs fonctionnalités : les techniques et les raisonnements utilisés devraient être suffisamment commentés pour être exploités par des utilisateurs d'autres langages et d'autres bibliothèques graphiques. Si vous faites partie de cette catégorie de lecteurs, c'est à partir du deuxième tutoriel que vous devriez trouver votre compte, celui en cours expliquant comment installer la bibliothèque et posant les bases d'une application de démonstration. Si vous utilisez déjà Lazarus, sachez que ce qui va être présenté est compilable sans la moindre modification aussi bien sous Windows que sous Linux, en 32 ou 64 bits.

Commentez

- Titre : Effets de transition avec Lazarus et BGRABitmap 1

- Auteur : Gilles Vasseur

- Parution : 17 mai 2018

- Mise à jour : 19 juin 2018

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2018 Gilles Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Installation et application modèle

- II-A. L'interface utilisateur

- II-B. L'installation de BGRABitmap

- II-C. L'application de base

- II-C-1. La construction des objets

- II-C-2. La destruction des objets

- II-C-3. L'action du bouton

- II-C-4. La finition de l'unité

- II-D. Introduction de la vitesse

- II-E. Introduction de l'opacité

- III. Conclusion

I - Introduction

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

La classe TBitmap qui accompagne Lazarus est dotée d'un canevas pour le dessin. Malheureusement, ce dernier est assez rudimentaire et ne répond que très partiellement aux exigences d'une application graphique moderne. La bibliothèque BGRABitmap possède des caractéristiques supplémentaires devenues indispensables de nos jours, comme la prise en charge de la transparence, de la correction de gamma et de l'anticrénelage (antialiasing). Par ailleurs, sa vitesse d'exécution est bien plus satisfaisante que celle obtenue avec le canevas traditionnel. De surcroît, la bibliothèque est régulièrement mise à jour depuis des années par son créateur Johann Elsass et a prouvé sa stabilité et son efficacité à travers des applications comme LazPaint. Cerise sur le gâteau, l'auteur a rendu les instructions de dessin de certaines classes similaires à celles utilisées par le canevas d'HTML5. Il est aussi possible de dessiner un SVG (format de données basé sur XML pour décrire des graphiques vectoriels), donc d'exploiter un masque avec un éditeur vectoriel, la lecture de ce format ayant été récemment améliorée. Mais tout cela nous emmène déjà fort loin !

Plutôt que de reprendre des tutoriels déjà en ligne, il m'a paru préférable d'en rédiger de nouveaux à partir d'un projet unique, mais a priori intéressant pour sa généralité : un composant capable de gérer des transitions entre deux images. La méthode d'exposition suivra celle de la découverte, la résolution des problèmes permettant d'introduire de nouvelles techniques puis de les affiner. Les connaissances en mathématiques pour de nombreuses transitions seront limitées aux opérations élémentaires, mais il faudra parfois se plonger dans quelques notions plus complexes, notamment de trigonométrie. Si nécessaire, des éclaircissements seront apportés au coup par coup. J'espère que ces choix satisferont le plus de lecteurs possible. De toute façon, chacun pourra critiquer ce qui a été fait et proposer de meilleures solutions !

Dans ce premier 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. Dans les tutoriels suivants, je présenterai 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.

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 multiplate-forme comme interface graphique.

Un bon réflexe consiste à sauvegarder tout de suite cet embryon d'application. 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 main.pas.

L'interface utilisateur ne met en jeu que quelques composants des plus communs. En premier lieu, il nous faut deux TImage : une image pour la source et une pour la destination. Il nous faut aussi un composant TPaintBox pour dessiner la transition en cours. Nous leur donnerons des tailles différentes afin de montrer que ces dernières sont traitées correctement par notre application. 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 choisis.

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

Le composant TPaintBox pour le dessin de la transition sera nommé imgResult.

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 à 100. Ces deux 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 nous 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 générée automatiquement 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.2.0'
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 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 = 100
Min = 1
Position = 0
TabOrder = 2
end
object lblSpeed: TLabel
Left = 24
Height = 15
Top = 292
Width = 36
Caption = 'Vitesse'
FocusControl = tbarSpeed
ParentColor = False
end
object imgResult: TPaintBox
Left = 304
Height = 201
Top = 200
Width = 296
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 (ou cliquer sur les points de suspension qui suivent le nom de la propriété) 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 !

Vous pouvez bien entendu choisir les images de votre choix. Les formats de fichiers acceptables sont multiples (jpg, png, bmp…). Les tailles le sont aussi : en fait, elles sont limitées à la mémoire disponible.

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 minutes sur le rebord d'un pot de fleurs. 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 avons décidé d'utiliser la bibliothèque BGRABitmap, nous allons 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, sous Windows, nous le décompacterons par exemple dans le répertoire components de Lazarus. Sous Linux, comme ce répertoire est inaccessible en écriture avec de simples droits d'utilisateur, nous pourrons utiliser quelque chose comme /home/sources/Lazarus/components. Dans les deux cas, le seul sous-répertoire créé par la décompression et vraiment utile est bgrabitmap, que nous placerons directement dans le répertoire components ou à tout autre endroit accessible en lecture et écriture de notre choix.

Quel que soit le système d'exploitation, 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 définissant une des caractéristiques d'un 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.

Certains systèmes d'exploitation comme Linux préfèrent travailler avec une disposition différente des canaux : Red, Green, Blue et Alpha. Qu'à cela ne tienne : la bibliothèque s'occupera automatiquement via la directive BGRABITMAP_RGBAPIXEL de la nécessaire adaptation pour une meilleure efficacité de l'affichage.

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 nous 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);
BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(imgResult.ClientWidth, imgResult.ClientHeight));
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
BGRAReplace(fBGRATo, fBGRATo.Resample(imgResult.ClientWidth, imgResult.ClientHeight));
end;

fBGRAFrom et fBGRATo sont instanciées grâce à un constructeur au nom classique Create. Parmi ses nombreuses variantes, la forme de ce constructeur prend pour paramètre un bitmap (respectivement imgFrom.Picture.Bitmap et imgTo.Picture.Bitmap, c'est-à-dire ceux de l'image source et de l'image de destination).

Comme nous ne savons pas a priori quelle taille font les images à traiter, nous devons les redimensionner pour qu'elles soient compatibles et puissent fusionner dans la troisième, celle du résultat de la transition à une étape donnée. Voilà pourquoi chaque instance (fBGRAFrom et fBGRATo) est redimensionnée grâce à la méthode Resample. Cette dernière ne provoque que peu de perte en qualité grâce à des algorithmes adaptés dont nous parlerons brièvement un peu plus loin. Le redimensionnement se fait en fonction de la largeur et de la hauteur de l'image d'accueil, à savoir imgResult.

La procédure spéciale BGRAReplace fournie par BGRABitmap n'est en fait qu'un raccourci pour éviter la création d'une variable locale intermédiaire. Il serait tentant de créer directement l'objet fBGRAFrom (ou fBGRATo) avec le constructeur Create puis de le redimensionner depuis lui-même avec la méthode Resample. Malheureusement, comme Resample crée un nouvel objet, toute référence à la première instance de l'objet serait perdue à cause de la nouvelle affectation, d'où des fuites inévitables de mémoire. Il faut donc toujours se servir d'une variable intermédiaire pour Resample ou lui préférer la procédure spéciale BGRAReplace qui rend sans doute le code plus lisible.

Voici une autre façon d'écrire le même code, mais sans utiliser BGRAReplace :

procedure TMainForm.FormCreate(Sender: TObject);
// *** construction des objets de travail ***
var
LBGRATemp: TBGRABitmap;
begin
LBGRATemp := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
try
fBGRAFrom := LBGRATemp.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as TBGRABitmap;
LBGRATemp.Assign(imgTo.Picture.Bitmap);
fBGRATo := LBGRATemp.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as TBGRABitmap;
finally
LBGRATemp.Free;
end;
end;

L'essentiel tient à une assignation avec un transtypage nécessaire puisque Resample retourne un objet de type TBGRACustomBitmap alors que l'image attend un TBGRABitmap. La variable locale doit aussi contenir successivement les deux images de travail si bien qu'elle doit changer de contenu avec la méthode appropriée Assign.

La méthode Resample accepte un autre mode (baptisé rmSimpleStretch) que celui par défaut pour redimensionner l'image prise en charge. Comme son nom l'indique, ce mode se contente de répéter les pixels par étirement. Le mode par défaut rmFineResample conviendra bien mieux à notre travail. Ce mode s'appuie sur un des filtres à sa disposition, chacun d'eux établissant 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.

Pour information, voici les filtres disponibles tels que définis et commentés par l'énumération TResampleFilter :

- rfBox : équivalent d'un simple étirement mais avec une meilleure qualité et en utilisant des coordonnées centrées ;

- rfLinear : interpolation linéaire avec une transition très progressive entre les pixels ;

- rfHalfCosine : mélange de rfLinear et de rfCosine, d'où une transition entre pixels de progressivité moyenne (c'est le filtre utilisé par défaut) ;

- rfCosine : interpolation selon le cosinus donnant une transition rapide entre pixels ;

- rfBicubic : interpolation bicubique (apportant du flou) ;

- rfMitchell : filtre selon l'algorithme de Mitchell adapté pour un rétrécissement de l'image ;

- rfSpline : filtre à base de spline adapté à l'agrandissement d'une image, si ce n'était un léger flou ;

- rfLanczos2 : algorithme de Lanczos avec un rayon de 2 et un flou corrigé ;

- rfLanczos3 : algorithme de Lanczos avec un rayon de 3 et un fort contraste ;

- rfLanczos4 : algorithme de Lanczos avec un rayon de 4 et un fort contraste ;

- rfBestQuality : qualité supérieure utilisant rfMitchell ou rfSpline selon les cas pour un meilleur rendu.

Le filtre de redimensionnement peut être changé en modifiant directement la variable ResampleFilter de la classe TBGRABitmap.

Par la suite, nous nous contenterons d'adopter le filtre et le mode par défaut : nous obtiendrons des redimensionnements tout à fait satisfaisants pour une rapidité d'exécution acceptable.

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;

Si vous débutez avec les objets de Free Pascal, rappelez-vous qu'un objet créé doit toujours être détruit sous peine de fuites de mémoire gênantes, voire bloquantes par accumulation. Nous avons l'assurance que le gestionnaire FormDestroy sera appelé lorsque la fiche sera détruite. Comme les objets ont été créés dans le gestionnaire FormCreate, il suffit de les détruire ici pour être certain que la mémoire allouée sera libérée.

Remarque complémentaire : avec Free Pascal, 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 TPaintBox.

Avec Linux, l'utilisation d'un composant TImage pour l'affichage du résultat d'une transition peut conduire à un ralentissement important de l'affichage. C'est pourquoi nous avons remplacé le composant TImage par un composant TPaintBox. Si vous voulez malgré tout utiliser un composant TImage, il vous faudra très légèrement modifier le code : dans celui présenté un peu plus loin, vous devrez ajouter l'appel à la méthode Repaint juste avant la ligne qui fait appel à Application.ProcessMessages afin de rafraîchir immédiatement l'affichage.

Vous noterez que ces considérations ne concerneront pas le composant final.

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.Fill(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 permet la gestion des événements
Application.ProcessMessages;
// 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 ce gestionnaire. Le cœur de la méthode est 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.

Nous noterons cependant 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.

La méthode Fill remplit une zone définie avec un pixel fourni en paramètre (ou une texture pour une de ses définitions). Elle est déclarée dans l'ancêtre immédiat de TBGRABitmap, à savoir TBGRACustomBitmap.

La directive de surcharge overload indique qu'il existe plusieurs versions de cette procédure. Nous n'en comptons pas moins de cinq différentes (dont certaines sont encre abstraites à ce niveau) afin de s'adapter à de nombreuses situations. Celle qui nous intéresse prend comme paramètre une couleur de type TBGRAPixel :

procedure Fill(c: TColor); virtual; overload;
procedure Fill(c: TBGRAPixel); virtual; overload;
procedure Fill(texture: IBGRAScanner; mode: TDrawMode); virtual; abstract; overload;
procedure Fill(texture: IBGRAScanner); virtual; abstract; overload;
procedure Fill(c: TBGRAPixel; start, Count: integer); virtual; abstract; overload;

PutImage dessine à une position donnée une image fournie en paramètre avec un mode déterminé (par transparence, par aplat…). Sa définition est unique :

procedure PutImage(x, y: integer; Source: TBGRACustomBitmap; mode: TDrawMode; AOpacity: byte = 255); override;

Nous expliquerons la signification des paramètres mode (de type TDrawMode) et AOpacity (fixé par défaut à 255) lorsque nous introduirons le concept d'opacité. Contentons-nous de dire que cette opacité correspond au canal alpha déjà cité.

Nous avons pu omettre de mentionner ce dernier paramètre lors de l'appel de la procédure puisque la valeur par défaut de 255 nous convenait.

Enfin, Draw dessine, à une position donnée et sur un canevas fourni en paramètre, l'image contenue dans l'objet de type TBGRABitmap en cause. Elle a deux définitions complémentaires :

procedure Draw(ACanvas: TCanvas; x, y: integer; Opaque: boolean=True); override;
procedure Draw(ACanvas: TCanvas; Rect: TRect; Opaque: boolean=True); override;

Nous pouvons soit fournir les coordonnées du point où commencera le dessin sur le canevas, soit le rectangle visé. L'opacité tient à un booléen Opaque fixé par défaut à True. Cette méthode sera l'outil privilégié pour transférer le produit de notre travail sur le canevas du composant visé (TImage, TPaintBox…).

Le type TBGRAPixel pourra intéresser certains lecteurs curieux. Il s'agit en fait d'un enregistrement étendu qui encapsule les 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;

Il y est en particulier fait attention à l'ordre des canaux suivant le système d'exploitation.

Une ultime remarque s'impose : le traitement du graphisme est suffisamment rapide grâce à cette bibliothèque pour qu'il soit (souvent) nécessaire de le ralentir. Voilà pourquoi nous avons introduit une attente dans la boucle. Sans elle, certains effets auraient été invisibles !

Pour l'instant, faute 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: TPaintBox;
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.Fill(BGRABlack);
LBGRATemp.PutImage(0, 0, fBGRAFrom, dmSet);
// traitement ici...
LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency);
LBGRATemp.Draw(imgResult.Canvas, 0, 0);
Application.processMesaages;
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);
BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(imgResult.ClientWidth, imgResult.ClientHeight));
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
BGRAReplace(fBGRATo, fBGRATo.Resample(imgResult.ClientWidth, imgResult.ClientHeight));
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 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 progressivement faire apparaître 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 : l'utilisation de la LCL standard ou celle 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 à 10 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 (OnChange, 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.Fill(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.

Nous ne devons 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 ***
var
LBGRATemp: TBGRABitmap;
begin
Caption := rsTestName;
LBGRATemp := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
try
fBGRAFrom := LBGRATemp.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as TBGRABitmap;
LBGRATemp.Assign(imgTo.Picture.Bitmap);
fBGRATo := LBGRATemp.Resample(imgResult.ClientWidth, imgResult.ClientHeight) as TBGRABitmap;
finally
LBGRATemp.Free;
end;
fSpeed := C_DefaultSpeed;
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.

Pour le moment, ce réglage n'est pas possible durant la transition qui fige l'application, bloquant tout événement à cause de la boucle de calcul. Comme indiqué, nous réglerons ce problème plus tard.

La nouvelle mouture 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 entièrement transparent. Les valeurs intermédiaires permettent de nuancer cette transparence.

Toujours dans un souci de 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.

Vous pourriez assouplir ce traitement de l'opacité en l'appliquant soit à l'image source, soit à l'image de destination, soit aux deux, soit enfin à aucune d'entre elles. C'est ce que nous ferons dans un futur tutoriel.

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 pour cela distinguer plusieurs cas, suivant que 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 difficiles. 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 nous 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.Fill(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;

La méthode PutImage (comme bien d'autres) a besoin d'un paramètre déterminant le mode de dessin à appliquer. Ce mode est de type TDrawMode qui détermine ce qu'il se passe lorsqu'un pixel est dessiné sur un autre. Il peut prendre cinq valeurs :

- dmSet : le pixel d'origine est remplacé par le nouveau pixel ;

- dmSetExceptTransparent : le pixel d'origine est remplacé si le nouveau pixel a le canal alpha fixé à 255 ;

- dmLinearBlend ou dmFastBlend : le pixel d'origine et le nouveau sont mélangés en fonction des canaux alpha de chacun d'eux, mais sans correction de gamma (rapide, mais le rendu est moins bon qu'avec dmDrawWithTransparency) ;

- dmDrawWithTransparency : le pixel d'origine et le nouveau sont mélangés en fonction des canaux alpha de chacun d'eux, avec correction de gamma ;

- dmXor : les valeurs de tous les canaux sont calculées avec la fonction ou exclusif (surtout utile dans certains calculs de différence binaire).

Nous utiliserons dmSet pour couvrir une surface et dmDrawWithTransparency pour la recouvrir en tenant compte de la transparence.

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ée « 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'estompent déjà.

La fonction Opacity définit par défaut la valeur de son paramètre Up à 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().

Voici le listing complet de notre application :

unit main;

{$mode objfpc}{$H+}

interface

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

const
C_DefaultSpeed = 80;
C_MaxOpacity = 255;

type

{ TMainForm }

TMainForm = class(TForm)
btnGo: TButton;
cbOpacity: TCheckBox;
imgFrom: TImage;
imgTo: TImage;
imgResult: TPaintBox;
lblSpeed: TLabel;
tbarSpeed: TTrackBar;
procedure btnGoClick(Sender: TObject);
procedure cbOpacityChange(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure tbarSpeedChange(Sender: TObject);
private
fBGRAFrom, fBGRATo: TBGRABitmap;
fSpeed: Byte;
fStep: Byte;
fWithOpacity: Boolean;
procedure SetSpeed(AValue: Byte);
procedure SetWithOpacity(AValue: Boolean);
public
function Opacity(Up: Boolean = True): Byte;
property Speed: Byte read fSpeed write SetSpeed default C_DefaultSpeed;
property WithOpacity: Boolean read fWithOpacity write SetWithOpacity;
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.Fill(BGRABlack);
LBGRATemp.PutImage(0, 0, fBGRAFrom, dmSet, Opacity(False));
// traitement ici...
LY := - imgResult.ClientHeight + imgResult.ClientHeight * fStep div 100; // OVERDOWN
LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency, Opacity);
LBGRATemp.Draw(imgResult.Canvas, 0, 0);
Application.ProcessMessages;
sleep(100 - fSpeed);
until fStep = 100;
finally
LBGRATemp.Free;
btnGo.Enabled := True;
end;
end;

procedure TMainForm.cbOpacityChange(Sender: TObject);
// *** gestion de la transparence ***
begin
WithOpacity := cbOpacity.Checked;
end;

procedure TMainForm.FormCreate(Sender: TObject);
// *** construction des objets de travail ***
begin
Caption := rsTestName;
fBGRAFrom := TBGRABitmap.Create(imgFrom.Picture.Bitmap);
BGRAReplace(fBGRAFrom, fBGRAFrom.Resample(imgResult.ClientWidth, imgResult.ClientHeight));
fBGRATo := TBGRABitmap.Create(imgTo.Picture.Bitmap);
BGRAReplace(fBGRATo, fBGRATo.Resample(imgResult.ClientWidth, imgResult.ClientHeight));
fSpeed := C_DefaultSpeed;
tbarSpeed.Position:= Speed;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
// *** destruction des objets de travail ***
begin
fBGRAFrom.Free;
fBGRATo.Free;
end;

procedure TMainForm.tbarSpeedChange(Sender: TObject);
// *** changement de vitesse ***
begin
Speed := tbarSpeed.Position;
end;

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

procedure TMainForm.SetWithOpacity(AValue: Boolean);
// *** détermination de l'opacité ***
begin
if fWithOpacity = AValue then
Exit;
fWithOpacity := AValue;
end;

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;

end.

La dernière mouture de l'application est incluse dans le répertoire 03 des fichiers d'exemples.

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.

Mes remerciements vont à Alcatîz, tourlourou et BeanzMaster pour leur relecture technique et à f-leb pour la correction orthographique.