Apprendre à créer des transitions d'image à image avec Lazarus et BGRABitmap
Par Gilles Vasseur (1)

Le , par gvasseur58

0PARTAGES

7  0 
Apprendre à créer des transitions d'image à image
Avec Lazarus et BGRABitmap

Bonjour à toutes et à tous !

La série de tutoriels inaugurée par celui que vous allez lire 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.

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, mais 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.

Pour illustrer le résultat final de la série, à savoir un composant prêt à l'emploi, voici une vidéo qui le montre en action :



Pour lire le tutoriel, c'est ici : https://gilles-vasseur.developpez.co...sitions/bgra1/

Que pensez-vous de ce tutoriel ?
Quelle utilisation faites-vous du graphisme avec Lazarus ?

Retrouvez les meilleurs cours et tutoriels pour apprendre la programmation avec Lazarus.

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de BeanzMaster
Membre émérite https://www.developpez.com
Le 18/05/2018 à 1:15
Bonjour

Intéressant comme article Gilles, j'ai hâte de lire la suite . Une seule remarque j'ai testé l'exemple 3, sous Windows c'est très fluide, mais sous Linux c'est d'une lenteur déconcertante A vérifier si c'est juste chez moi ou pas. Pour informations je dispose des même version Lazarus et de BGRABitmap sous mes 2 plateformes.

A Bientôt
Avatar de Jipété
Expert éminent sénior https://www.developpez.com
Le 18/05/2018 à 7:45
Citation Envoyé par BeanzMaster Voir le message
j'ai testé l'exemple 3, sous Windows c'est très fluide, mais sous Linux c'est d'une lenteur déconcertante
Linux en MV ? Si oui, je l'ai remarqué aussi avec d'autres projets : dans le host Linux le projet va très bien, et dans une MV Linux (où j'ai la dernière version de Laz/Fpc) ça rame d'une manière impressionnante.
Avatar de gvasseur58
Responsable Delphi https://www.developpez.com
Le 18/05/2018 à 9:27
Citation Envoyé par BeanzMaster Voir le message

Une seule remarque j'ai testé l'exemple 3, sous Windows c'est très fluide, mais sous Linux c'est d'une lenteur déconcertante
Comme Jipété l'a écrit ci-dessus, il semblerait que ce soit sur les machines virtuelles que se pose le problème. Avec une mémoire réservée au graphisme souvent ridicule, ce n'est pas forcément étonnant. Mais il y a peut-être d'autres raisons, car voici le composant en action sous Linux Ubuntu Studio avec Oracle VM VirtualBox à jour :



On voit que les transitions sont plutôt rapides (à vrai dire, la boucle comporte 72 pas, mais la différence avec 100 n'est pas considérable), mais qu'il y a parfois un petit problème en fin de transition (un freeze bref mais visible).

J'attends confirmation de la source du problème !

Gilles
Avatar de Jipété
Expert éminent sénior https://www.developpez.com
Le 18/05/2018 à 10:20
Citation Envoyé par gvasseur58 Voir le message
Avec une mémoire réservée au graphisme souvent ridicule, ce n'est pas forcément étonnant.
OMG tu as surement raison : dans mes MV, pour ne pas pomper les ressources du host, je n'attribue en général que 64 à 128 Mo.
Promis, la prochaine machine je l'achète avec 16 Go minimum (là je n'ai que 4).

-- Un aparté purement esthétique --

Je sais bien que les machines peuvent tout faire, mais est-il alors nécessaire de tout faire puis tout montrer ?

Je ne parle pas de ta démo, qui est une démo, justement, mais c'est elle qui me met la puce à l'oreille et la larme à l'œil.
On ne voit plus que ça partout à la téloche, maintenant, et ça se répand à toute allure, le moindre petit sujet devient un calvaire visuel et sonore :
entre les transitions d'un plan à l'autre à coups d'effets parasite, flash de couleurs, tournage de pages, déformations d'image, et le rajout de bruits pour accentuer, bien marquer l'effet visuel, passer du temps devant cet appareil devient une torture audio-visuelle.

Juste parce que les nouvelles consoles de montage permettent ces effets ? Mais à part du brouillage visuel et de la pollution sonore, ces ajouts n'apportent rien.

C'est Saint-Ex' qui disait
Citation Envoyé par Saint-Ex'
La perfection, ce n'est pas quand il n'y a plus rien à rajouter, c'est quand il n'y a plus rien à enlever.
Donc virez-nous tous ces effets de transitions et les documentaires gagneront en lisibilité et et fluidité du message passé.

Sur Arte ils s'amusent même à utiliser des polices pourries, pour perdre en lisibilité : on marche sur la tête, là ! Une police est là pour qu'on puisse bien lire le message qu'elle a à transmettre et surtout, qu'elle se fasse oublier.
Les graveurs de caractères depuis 500 ans doivent se retourner dans leur tombe et maudire ces machines modernes et les imbéciles incultes qui les utilisent si mal, s'imaginant que puisqu'on peut tout faire alors il faut tout faire, même si c'est du grand n'importe quoi.

À croire que les mecs ne regardent pas les documents qu'ils produisent...
Avatar de BeanzMaster
Membre émérite https://www.developpez.com
Le 18/05/2018 à 15:34
Citation Envoyé par gvasseur58 Voir le message
Comme Jipété l'a écrit ci-dessus, il semblerait que ce soit sur les machines virtuelles que se pose le problème. Avec une mémoire réservée au graphisme souvent ridicule, ce n'est pas forcément étonnant. Mais il y a peut-être d'autres raisons, car voici le composant en action sous Linux Ubuntu Studio avec Oracle VM VirtualBox à jour :

On voit que les transitions sont plutôt rapides (à vrai dire, la boucle comporte 72 pas, mais la différence avec 100 n'est pas considérable), mais qu'il y a parfois un petit problème en fin de transition (un freeze bref mais visible).

J'attends confirmation de la source du problème !

Gilles
Bonjour malheureusement ce n'est pas une MV.

Mon système Linux :
Manjaro avec KDE et carte graphique NVidia GTX 1070 8Go et tous les drivers sont parfaitement installés

Je n'ai pas testé d'autres projets utilisant BGRABitmap mais le problème vient surement de lui. Avec le projet sur lequel je travaille actuellement j'ai un perte de vitesse sous Linux de l'ordre de 4/5 fps comparé à Windows pour un affichage d'un bitmap dans une fenêtre en 1024x768. C'est à dire pas grand chose. Avec BGRABitmap et ton exemple je peux compter les minutes. Donc serait ce dût à l'utilisation de KDE ? j'en doute

Je vais faire d'autre test avec BGRABitmap je te tiens au courant.

Jérôme
Avatar de BeanzMaster
Membre émérite https://www.developpez.com
Le 18/05/2018 à 16:10
Me revoilà, le coupable c'est le TImage. En le remplaçant par un simple TPanel c'est tout bon la vitesse est identique à Windows

Voici les changements que j'ai effectué :

Code : Sélectionner tout
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
type

  { TMainForm }

  TMainForm = class(TForm)
    btnGo: TButton;
    cbOpacity: TCheckBox;
    imgFrom: TImage;
    imgTo: TImage;
    lblSpeed: TLabel;
    imgResult: TPanel; // Remplacement du TImage
    tbarSpeed: TTrackBar;
    procedure btnGoClick(Sender: TObject);
    procedure cbOpacityChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure imgResultPaint(Sender: TObject);
    procedure tbarSpeedChange(Sender: TObject);
  private
    fBGRAFrom, fBGRATo: TBGRABitmap;
    LBGRATemp: TBGRABitmap; // Ajout du tampon pour l'animation
    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;    

procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var

  LY, LX: Integer;
begin
  btnGo.Enabled := False;

  try
    LX := 0;
    LY := 0;
    fStep := 0;
    repeat
      Inc(fStep);
      LBGRATemp.FillRect(ClientRect, 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);
      imgResult.Repaint;
      Application.ProcessMessages; // Envoi du message pour redessiner le TPanel imgResult
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
   //
    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));
  fSpeed := C_DefaultSpeed;
  tbarSpeed.Position:= Speed;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack); // Creation du tampon
end;

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

procedure TMainForm.imgResultPaint(Sender: TObject);
begin
    LBGRATemp.Draw(imgResult.Canvas, 0, 0); // Transfert du tampon à la surface d'affichage
end;
Avatar de BeanzMaster
Membre émérite https://www.developpez.com
Le 18/05/2018 à 16:38
Le mieux serait de passer par un timer, et plus besoin du OnPaint du TPanel

Code : Sélectionner tout
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
type

  { TMainForm }

  TMainForm = class(TForm)
    btnGo: TButton;
    cbOpacity: TCheckBox;
    imgFrom: TImage;
    imgTo: TImage;
    lblSpeed: TLabel;
    imgResult: TPanel;
    tbarSpeed: TTrackBar;
    Timer1: TTimer;
    procedure btnGoClick(Sender: TObject);
    procedure cbOpacityChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure tbarSpeedChange(Sender: TObject);
    procedure Timer1StartTimer(Sender: TObject);
    procedure Timer1StopTimer(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    fBGRAFrom, fBGRATo: TBGRABitmap;
    LBGRATemp: TBGRABitmap;
    LY, LX: Integer;
    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;
Code : Sélectionner tout
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
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
begin
  Timer1.Interval:= (101-Speed); // 101 car l'interval ne doit pas être à zero
  Timer1.Enabled:= true; 
end;

procedure TMainForm.Timer1StartTimer(Sender: TObject);
begin
  LX := 0;
  LY := 0;
  fStep := 0;
  btnGo.Enabled := False;
end;

procedure TMainForm.Timer1StopTimer(Sender: TObject);
begin
  btnGo.Enabled := True;
end;

procedure TMainForm.Timer1Timer(Sender: TObject);
begin
  Inc(fStep);
  LBGRATemp.FillRect(ClientRect, 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);
  if fstep = 100 then Timer1.Enabled:=false;
end;
EDIT : Petite video de demonstration

Avatar de gvasseur58
Responsable Delphi https://www.developpez.com
Le 18/05/2018 à 16:42
Bravo

J'aurais dû m'en douter, car ce TImage est une source continuelle de problèmes. Heureusement, le composant lui-même est un descendant de TGraphicControl fonctionnant avec un TTimer, d'où la fluidité des transitions, même sous un Linux en VM. Le composant final n'a aucun besoin d'un composant TImage pour s'afficher, ce qui est une bonne nouvelle (dans un premier temps, il a même été descendant de TPaintBox, un composant beaucoup plus léger - d'ailleurs, il faudrait voir s'il ne convient pas lui aussi, le TPanel étant à mon goût trop généraliste et pas spécialement prévu pour afficher des images).

En revanche, je ne vais pas adopter dans les exemples le TTimer (qui est bien sûr la solution choisie pour le composant), car je souhaite que le lecteur se concentre sur les transitions et non sur leur déroulement. En particulier, afin d'éviter des chevauchements et des problèmes de synchronisation (particulièrement sensibles avec Windows cette fois-ci), il faut travailler avec des états, ce que je présenterai bien plus tard... Le code que tu présentes a en effet un défaut : si tu as pensé à éviter une valeur nulle pour le déclenchement de l'événement OnTimer, il reste un problème en suspens pour les vitesses rapides : que se passe-t-il si l'intervalle du timer est très proche, voire supérieur au temps de réalisation de la transition ? Il faut penser aux rotations plutôt chronophages, par exemple. De plus, l'intervalle du TTimer est imprécis et l'expérience montre qu'en dessous de 10ms, l'utilisateur rencontre (souvent) des problèmes de synchronisation.

Je vais cependant, grâce à toi, ajouter une note dans le tutoriel et te citer comme collaborateur.

Ce qui m'ennuie le plus, c'est que je suis devant un cruel dilemme : simplement signaler la modification ou revoir les 4 tutoriels qui sont déjà rédigés avec leurs 40 exemples .

Bonne journée,

Gilles
Avatar de BeanzMaster
Membre émérite https://www.developpez.com
Le 18/05/2018 à 17:03
Citation Envoyé par gvasseur58 Voir le message
Bravo

J'aurais dû m'en douter, car ce TImage est une source continuelle de problèmes. Heureusement, le composant lui-même est un descendant de TGraphicControl fonctionnant avec un TTimer, d'où la fluidité des transitions, même sous un Linux en VM. Le composant final n'a aucun besoin d'un composant TImage pour s'afficher, ce qui est une bonne nouvelle (dans un premier temps, il a même été descendant de TPaintBox, un composant beaucoup plus léger - d'ailleurs, il faudrait voir s'il ne convient pas lui aussi, le TPanel étant à mon goût trop généraliste et pas spécialement prévu pour afficher des images).
Oui c'est vrai le TPanel est généraliste mais on veux juste avoir accès au Canvas. On pourrais tout aussi bien se servir du Canvas de la fenêtre, ça ne changerai pas grand chose. D'ailleur bon nombre d'exemple réaliser par Lainz sont basés sur le Canvas de la fenêtre et le "OnPaint" pour l'affichage d'animation

Citation Envoyé par gvasseur58 Voir le message

En revanche, je ne vais pas adopter dans les exemples le TTimer (qui est bien sûr la solution choisie pour le composant), car je souhaite que le lecteur se concentre sur les transitions et non sur leur déroulement. En particulier, afin d'éviter des chevauchements et des problèmes de synchronisation (particulièrement sensibles avec Windows cette fois-ci), il faut travailler avec des états, ce que je présenterai bien plus tard... Le code que tu présentes a en effet un défaut : si tu as pensé à éviter une valeur nulle pour le déclenchement de l'événement OnTimer, il reste un problème en suspens pour les vitesses rapides : que se passe-t-il si l'intervalle du timer est très proche, voire supérieur au temps de réalisation de la transition ? Il faut penser aux rotations plutôt chronophages, par exemple. De plus, l'intervalle du TTimer est imprécis et l'expérience montre qu'en dessous de 10ms, l'utilisateur rencontre (souvent) des problèmes de synchronisation.
Normal, je comprend. Sinon la solution pour la synchronisation est d'utiliser un "ThreadTimer" j'en ai un, je pourrais mettre la source à disposition

Citation Envoyé par gvasseur58 Voir le message

Je vais cependant, grâce à toi, ajouter une note dans le tutoriel et te citer comme collaborateur.
Merci

Citation Envoyé par gvasseur58 Voir le message

Ce qui m'ennuie le plus, c'est que je suis devant un cruel dilemme : simplement signaler la modification ou revoir les 4 tutoriels qui sont déjà rédigés avec leurs 40 exemples .

Bonne journée,

Gilles
Remplaces simplement le TImage par un TPaintBox cela me semble le plus simple, non ?

Bonne fin de journée également

Allez hop je retourne au boulot (le vrai cette fois)
Avatar de gvasseur58
Responsable Delphi https://www.developpez.com
Le 18/05/2018 à 17:30
Je reviens avec d'autres expérimentations en VM. En fait, il suffit d'utiliser un TPanel (pour lequel j'ai gardé le nom imgResult), de retirer le Repaint (qui efface le canevas du composant) et d'ajouter un ProcessMessages pour récupérer les clics sur les boutons et curseurs. Pas besoin de surcharger le gestionnaire OnPaint.

Procédure :
1. Je remplace le TImage du résultat par un TPanel ou un TPaintBox renommé imgResult.
2. Je supprime imgResult.Repaint ;
3. J'ajoute Application.ProcessMessages.

Il me reste un léger problème de scintillement . J'imagine qu'il reste une question de mémoire vidéo comme l'a suggéré Jipété, mais ça ne règle pas tout...
Quelqu'un a t-il testé les exemples sur d'autres machines pur Linux ?

Gilles

[EDIT]
Citation Envoyé par BeanzMaster Voir le message

Remplace simplement le TImage par un TPaintBox cela me semble le plus simple, non ?
Oui, sauf qu'il faut que je le fasse dans 40 applications .
Responsables bénévoles de la rubrique Lazarus : Alcatîz - Gilles Vasseur -

Partenaire : Hébergement Web