Developpez.com - Rubrique Lazarus

Le Club des Développeurs et IT Pro

Apprendre à créer des transitions d'image à image avec Lazarus et BGRABitmap

Par Gilles Vasseur (1)

Le 2018-05-17 14:26:22, par gvasseur58, Responsable Lazarus & Pascal
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.
  Discussion forum
27 commentaires
  • circular17
    Membre confirmé
    Bonjour !

    Joie de voir un tutoriel BGRABitmap.

    Effectivement, le composant TImage est une surcouche qui peut ralentir l'affichage. Le scintillement peut être empêché sur la PaintBox avec DoubleBuffer, ou bien en utilisant le composant TBGRAVirtualScreen du paquet BGRAControls. Ce composant expose un évenement OnRedrawBitmap. Il est possible de demander un réaffichage différé avec DiscardBitmap ou bien de demander un réaffichage immédiat avec RedrawBitmap. Cela dit, cela demanderait de refactoriser un peu le code qui pour le moment est une boucle qui contient le dessin.

    Notez que sous MacOS, dans mes souvenirs il n'est pas possible de redessiner directement sur la fenêtre. Donc une PaintBox est nécessaire. D'autre part, il n'est pas possible non plus de dessiner en dehors d'un événement OnPaint. Une boucle qui contient le dessin ne fonctionnerait donc pas pour cette plateforme. Une approche intermédiaire pourrait fonctionner : une boucle qui incrémente le temps et appelle RedrawBitmap/Invalidate et dans l'évenement le dessin en fonction de la position temporelle. Afin que cela marche sous Linux il peut être nécessaire d'appeler Application.ProcessMessages parce que le rafraîchissement des contrôles ne se fait parfois pas quand on fait Repaint mais quand on laisse la main à la boucle de message. Pas sûr que ce soit si simple au final.

    En fin de compte, le plus simple qui marche pour toutes les plateformes est l'utilisation d'un Timer qui appelle DiscardBitmap ou bien Invalidate avec une PaintBox et de dessiner dans l'évenement OnPaint.

    Il se peut cependant que le taux du Timer soit un peu lent. Si on veut utiliser toute la CPU disponible, on peut utiliser Application.AddOnIdleHandler / RemoveOnIdleHandler pour inscrire une procédure qui sera appelée dès que possible. Dans cette procédure on peut appeler Sleep et Invalidate, ce qui déclenchera l’événement OnPaint tout le temps.

    Au sujet de la fonction StretchDraw, si le rectangle de destination a la même taille que l'image, c'est en fait équivalent à Draw. D'ailleurs en fait, la fonction de la LCL Draw appelle la fonction StretchDraw et c'est le Widgetset en dessous qui détermine en fait s'il y a lieu d'étirer ou pas. Alors je ne pense pas que la lenteur vienne de là. J'ai un vague souvenir comme quoi le composant TImage a lui-même un Timer pour se rafraichir. L'intérêt est que l'on peut dessiner sur le Canvas d'une TImage et cela ne réaffiche pas l'image pour chaque ligne tracée.

    Cordialement,

    Johann
  • Roland Chastain
    Rédacteur/Modérateur
    @Jipété

    Pas tout compris à ton message.

    Je te confirme que le paquet bgracontrols.lpk existe. Où l'as-tu cherché ? Il ne fait pas partie de la bibliothèque BGRABitmap, mais de l'ensemble de composants BGRAControls, qui sont deux choses différentes.

    Après il est possible que certains exemples inclus dans BGRABitmap utilisent (sans peut-être suffisamment en avertir l'utilisateur) les composants BGRAControls. Quoiqu'il en soit, le composant BGRAVirtualScreen fait partie du paquet BGRAControls, et non pas de la bibliothèque BGRABitmap.

    Envoyé par circular17
    ou bien en utilisant le composant TBGRAVirtualScreen du paquet BGRAControls.
  • Jipété
    Expert éminent sénior
    Envoyé par BeanzMaster
    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.
  • BeanzMaster
    Expert confirmé
    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 :
    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;
  • gvasseur58
    Responsable Lazarus & Pascal
    Le tutoriel a été mis à jour. J'ai remplacé le composant TImage par un composant TPaintBox : certaines portions de code ont donc été modifiées, mais aussi le fichier LFM et des copies d'écran. Il serait intéressant de voir pourquoi un composant aussi commun que TImage est aussi peu efficace sous Linux . Les autres épisodes de la série sont prêts : il va falloir que je vérifie que les modifications apportées n'ont pas de conséquences fâcheuses sur certaines transitions.

    Il va sans dire que Jérôme a été cité pour son apport à la fin du tutoriel
  • BeanzMaster
    Expert confirmé
    Envoyé par gvasseur58
    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]

    Pour le problème de scintillement essayes de rajouter Doublebuffered := true dans le OnCreate de la form.

    Envoyé par gvasseur58

    Oui, sauf qu'il faut que je le fasse dans 40 applications .
    Tu peux utiliser notepad++ pour remplacer les occurrences dans les fichiers Pas et lfm

    Envoyé par gvasseur58
    Le tutoriel a été mis à jour. J'ai remplacé le composant TImage par un composant TPaintBox : certaines portions de code ont donc été modifiées, mais aussi le fichier LFM et des copies d'écran. Il serait intéressant de voir pourquoi un composant aussi commun que TImage est aussi peu efficace sous Linux . Les autres épisodes de la série sont prêts : il va falloir que je vérifie que les modifications apportées n'ont pas de conséquences fâcheuses sur certaines transitions.
    Le problème du TImage est un problème entre multi-plateforme. Dans Windwos et Linux et en plus suivant le Bureau (GTK, QT...) l'affichage n'est pas gérer de la même façon. Une autre problème avec le TImage c'est qu'il contient un bitmap qui s'affiche avant le canvas. Pour pouvoir utiliser le TImage convenablement. Il faut transférer le BGRABitmap dans le Bitmap du TImage et non pas sur le Canvas. L'erreur d'affichage et la lenteur vient de la je pense. Derrière la fonction Draw de BGRABitmap se cache en réalité la fonction TCanvas.StretchDraw(); celle-ci est déja à la base moins performante sous Linux que sous Windows et c'est une fonction plutôt complexe à cause de la gestion des Widgets. Du coup il y a surement un conflit interne avec le TImage qui utilise également cette fonction pour l'affichage du bitmap sur son canvas.

    Envoyé par gvasseur58

    Il va sans dire que Jérôme a été cité pour son apport à la fin du tutoriel
    Merci
  • circular17
    Membre confirmé
    Toute contribution de documentation est la bienvenue.
  • Roland Chastain
    Rédacteur/Modérateur
    Je vous propose, au cas où cela intéresserait quelqu'un, un article sur la bibliothèque BGRABitmap que j'avais écrit l'autre année (pour un livre qui finalement n'a pas vu le jour). Johann avait eu la gentillesse de le relire, donc l'article ne devrait pas contenir trop d'erreurs.

    rchastain-initiation-bgrabitmap.zip

    @Jipété
    C'est écrit par quelqu'un qui travaille sous Windows.

    Mais le mieux est sans doute de consulter le cours écrit par Johann lui-même.
  • gvasseur58
    Responsable Lazarus & Pascal
    Envoyé par circular17
    Effectivement, le composant TImage est une surcouche qui peut ralentir l'affichage. Le scintillement peut être empêché sur la PaintBox avec DoubleBuffer, ou bien en utilisant le composant TBGRAVirtualScreen du paquet BGRAControls. Ce composant expose un évenement OnRedrawBitmap. Il est possible de demander un réaffichage différé avec DiscardBitmap ou bien de demander un réaffichage immédiat avec RedrawBitmap. Cela dit, cela demanderait de refactoriser un peu le code qui pour le moment est une boucle qui contient le dessin.
    Merci pour ces précisions. Je rassure tout le monde : le composant final est bâti sur TGraphicControl et utilise un TTimer. La boucle n'est qu'une solution temporaire afin de se concentrer sur les transitions elles-mêmes. De mon point de vue, il s'agit d'avoir un aperçu de l'effet obtenu, la transition réelle ayant une implémentation bien plus optimisée et complexe (par exemple, j'introduis des interpolations qui évitent la linéarité des transitions). Dans les exemples, j'ai définitivement abandonné le composant TImage (de toute façon bien trop lourd pour l'usage que j'en faisais) puisqu'il est avantageusement remplaçable par un TPaintBox. Quant aux utilisateurs d'Apple, ils pourront intervenir dans la suite de la série : je n'ai pas d'Apple (et je n'en aurai pas car je fais une allergie à cette marque ) et ne peux rien vérifier.

    Envoyé par circular17

    Il se peut cependant que le taux du Timer soit un peu lent. Si on veut utiliser toute la CPU disponible, on peut utiliser Application.AddOnIdleHandler / RemoveOnIdleHandler pour inscrire une procédure qui sera appelée dès que possible. Dans cette procédure on peut appeler Sleep et Invalidate, ce qui déclenchera l’événement OnPaint tout le temps.
    TTimer n'est pas ce qu'il y a de mieux, mais pour des transitions d'image à image, ce n'est en général pas un problème rédhibitoire, sauf pour les grands formats d'images. Je vais essayer, mais je crains les applications qui phagocytent toute la CPU .

    Encore merci,

    Gilles
  • circular17
    Membre confirmé
    Effectivement Gilles tu as tout à fait raison, BGRABitmap ne nécessite pas d'être installé. Les paquets qui ont besoin d'être installés sont ceux qui ajoutent des contrôles utilisateur. Les installer permet de les voir dans la barre d'outils et d'ouvrir les projets qui les contiennent.

    J'ai ajouté des liens vers les paquets qui utilisent BGRABitmap dans le wiki. Cela apportera plus une vision d'ensemble.

    Il n'y a pas encore de documentation pour l'utilisation d'OpenGL avec BGRABitmap. Je vais peut-être en écrire une. C'est toujours le dilemme : passer du temps à coder des nouvelles fonctionnalités ou passer du temps à écrire de la documentation et faire attendre les demandes de fonctionnalités.

    Jipété, le paquet BGRAControls a sa page ici : http://wiki.freepascal.org/BGRAControls.
    Et son source est ici : https://github.com/bgrabitmap/bgracontrols

    Le composant TBGRAVirtualScreen est simplement un panel sur lequel on peut dessiner, avec les événements dont je parle plus haut.

    J'aime beaucoup ton tuto Roland. Les programmes d'exemples sont bienvenus. Que penses-tu de le publier en ligne ?