Voir le flux RSS

Blog de Gilles Vasseur - Pascal et compagnie

Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base

Note : 2 votes pour une moyenne de 4,50.
par , 07/03/2018 à 16h49 (182 Affichages)
Grâce aux étapes précédentes, nous avons une interface utilisateur en place et une bibliothèque prête à l'emploi. Reste donc à concevoir la partie métier de notre application modèle. C'est ce que je propose ci-après, avec à la clé la toute première transition opérationnelle .

L'application modèle

Dans un premier temps, afin de 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.

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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
 
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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
  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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
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 de fondre 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 efficace, c'est-à-dire avec très peu de perte, 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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
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[/SIZE][/FONT] 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 [I]rmFineResample[/I conviendra sans problème à notre exemple.

La destruction des objets

La destruction des objets se fera naturellement dans le gestionnaire FormDestroy[/SIZE][/FONT] 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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
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.

L'action du bouton

Nous disposons de deux objets graphiques pour travailler. Le gestionnaire OnClick du bouton btnGo va 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 [COLOR=#4f81bd TImage[/COLOR] qu'il faudra repeindre pour qu'il rende compte immédiatement des changements effectués.

Au niveau programmation, voici ce que donne cette méthode :
Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 
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.

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 peut intéresser 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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 dessin sans passer par une file d'attente. L'avantage essentiel est l'obtention sans délai du dessin ; l'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 !

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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
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 :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
// 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 !

Nom : 00a10.png
Affichages : 40
Taille : 288,1 Ko

Notre capacité à la paramétrer est encore très faible, mais nous allons prochainement nous doter de fonctionnalités relatives à la vitesse d'exécution puis au dessin par transparence.

Vous trouverez ici le code source de l'exemple : 00a.zip.

Comme la taille des fichiers téléchargeables est limitée, il faudra choisir une image pour les deux composants de TImage de travail, par exemple les deux photos proposées dans le premier épidode de la série. Attention : la transition d'essai est à inclure comme indiqué dans le billet du blog !

Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Viadeo Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Twitter Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Google Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Facebook Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Digg Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Delicious Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog MySpace Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (IV) - L'application de base » dans le blog Yahoo

Mis à jour 08/03/2018 à 14h34 par gvasseur58

Catégories
Programmation , Free Pascal , Lazarus , Graphisme

Commentaires