IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

Blog de Gilles Vasseur - Pascal et compagnie

Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque

Noter ce billet
par , 03/04/2018 à 13h35 (831 Affichages)
Grâce aux précédents billets de la série, vous savez installer la bibliothèque BGRABitmap, bâtir un logiciel de test avec la prise en charge de la vitesse d'affichage et de l'opacité, ainsi qu'implémenter quelques transitions simples. Dans ce billet, nous étudierons des techniques plus complexes mettant en œuvre des masques afin de produire des transitions encore plus attrayantes.

La notion de masque

Avec les méthodes employées jusqu'à présent, nous ne pouvons que superposer deux images. Les effets sont en fait produits par un déplacement d'une ou des deux images. Cependant, il est des cas où cette technique est insuffisante : imaginons par exemple une image qui recouvrirait l'autre progressivement, mais en prenant la forme d'une croix qui grandirait jusqu'à couvrir toute la surface.

Le schéma ci-après montre pour trois étapes l'évolution de l'affichage en fonction du temps :

Nom : crossexpand.png
Affichages : 123
Taille : 2,6 Ko

Pour rappel, ces schémas affichent en rouge l'image d'origine et en bleu l'image de destination.

Nous voyons que la procédure qui consisterait à découper l'image de destination en portions à afficher sur l'image d'origine selon la progression de la transition serait complexe à écrire et chronophage. Heureusement, pour nous tirer d'embarras, il existe les masques !

Un masque est une image en tons de gris qui filtre l'affichage de l'image à superposer : plus un pixel est sombre, moins le pixel correspondant de l'image à superposer sera visible. Avec un pixel noir du masque (couleur BGRABlack prédéfinie dans BGRABitmap), nous ferons donc disparaître le pixel de l'image de destination ; avec un pixel blanc (couleur BGRAWhite), nous le garderons visible.

Le fonctionnement de cette technique pour la transition désirée pourrait être représenté ainsi :

Nom : masque.png
Affichages : 132
Taille : 4,5 Ko

Nous dessinerons dans un premier temps l'image d'origine. Dans un deuxième temps, nous dessinerons notre masque qui sera ensuite appliqué à l'image de destination. Le résultat obtenu sera superposé à l'image d'origine pour obtenir l'image finale.

La traduction en code demande de revoir notre application de test pour le gestionnaire OnClick. En particulier, il nous faudra une nouvelle variable locale (baptisée LBGRAMask) pour abriter le dessin du masque.

Voici le code proposé pour l'effet baptisé CrossExpand :

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
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  // création de l'image d'origine
  LBGRAFrom := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    // création de l'image de destination
    LBGRATo := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      // création du masque
      LBGRAMask := TBGRABitmap.Create(imgResult.ClientWidth, ClientHeight, BGRABlack);
      try
        fStep := 0;
        // la boucle des dessins commence...
        repeat
          // étape en cours
          Inc(fStep);
          // traitement 1 ici (source)
          LX := 0;
          LY := 0;
          LBGRAFrom.FillRect(ClientRect, BGRABlack);
          // image d'origine
          LBGRAFrom.PutImage(LX, LY, fBGRAFrom, dmDrawWithTransparency, Opacity(False));
          // traitement 2 ici (destination)...
          // le dessin de la croix commence au centre de l'image
          LX := (imgResult.ClientWidth div 2) * fStep div 100;
          LY := (imgResult.ClientHeight div 2) * fStep div 100;
          // construction du masque
          // entièrement transparent au début
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
          LBGRAMask.FillRectAntialias(-LX + imgResult.ClientWidth div 2, 0,
            LX + imgResult.CLientWidth div 2, imgResult.ClientHeight,
            BGRAWhite);
          LBGRAMask.FillRectAntialias(0, -LY + imgResult.ClientHeight div 2,
            imgResult.CLientWidth, LY + imgResult.ClientHeight div 2,
            BGRAWhite);
          // image de destination...
          LBGRATo.PutImage(0, 0, fBGRATo, dmSet);
          // ... à laquelle on applique le masque
          LBGRATo.ApplyMask(LBGRAMask);
          // destination sur origine
          LBGRAFrom.PutImage(0, 0, LBGRATo, dmDrawWithTransparency, Opacity);
          // le résultat est affiché
          LBGRAFrom.Draw(imgResult.Canvas, 0, 0, False);
          imgResult.Repaint;
          sleep(100 - fSpeed);
        until fStep = 100;
      finally
        LBGRAMask.Free; // libération du masque
      end;
    finally
      LBGRATo.Free; // libération de l'image de destination
    end;
  finally
    LBGRAFrom.Free; // libération de l'image d'origine
    btnGo.Enabled := True;
  end;
end;

Bien que ce code suive l'algorithme annoncé et qu'il soit largement commenté, nous noterons que le programmeur doit prendre garde de toujours libérer les ressources allouées pour les images et faire très attention à la gestion de la transparence afin qu'elle soit correctement prise en charge si elle est activée.

Le traitement de l'image d'origine est superflu dans l'état actuel, mais il permettra si besoin de s'adapter à des situations plus complexes.

Comme nous travaillons dans une boucle, le contenu du masque doit être réinitialisé à chaque étape. Cette remarque explique la présence, en tout début de travail sur le masque, d'un remplissage avec un rectangle entièrement noir, donc produisant une image invisible. Il s'agit d'une convention puisque nous aurions aussi pu partir d'une image blanche à noircir pour les parties à masquer, même si cela nous aurait conduit à dessiner quatre rectangles au lieu de deux.

À l'exécution, nous obtiendrons un écran comme ci-après :

Nom : bcrossexpand.png
Affichages : 161
Taille : 289,2 Ko

La réciproque de la transition CrossExpand pourra être baptisée CrossShrink. Nous aurons seulement par exemple à inverser les images et à modifier légèrement les calculs précédents pour l'obtenir.

Le code deviendra alors :

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
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRAFrom := TBGRABitmap.Create(imgResult.ClientWidth,
    imgResult.ClientHeight, BGRABlack);
  try
    LBGRATo := TBGRABitmap.Create(imgResult.ClientWidth,
      imgResult.ClientHeight, BGRABlack);
    try
      LBGRAMask := TBGRABitmap.Create(imgResult.ClientWidth,
  ClientHeight, BGRABlack);
      try
        fStep := 0;
        repeat
          Inc(fStep);
          // traitement 1 ici (source)
          LX := 0;
          LY := 0;
          LBGRAFrom.FillRect(ClientRect, BGRABlack);
          LBGRAFrom.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency, Opacity(False));
          // traitement 2 ici (destination)...
          LX := (imgResult.ClientWidth div 2) * (100 - fStep) div 100;
          LY := (imgResult.ClientHeight div 2) * (100 - fStep) div 100;
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
          LBGRAMask.FillRectAntialias(-LX + imgResult.ClientWidth div 2, 0,
            LX + imgResult.CLientWidth div 2, imgResult.ClientHeight,
            BGRAWhite);
          LBGRAMask.FillRectAntialias(0, -LY + imgResult.ClientHeight div 2,
            imgResult.CLientWidth, LY + imgResult.CLientHeight div 2,
            BGRAWhite);
          LBGRATo.PutImage(0, 0, fBGRAFrom, dmSet);
          LBGRATo.ApplyMask(LBGRAMask);
          LBGRAFrom.PutImage(0, 0, LBGRATo, dmDrawWithTransparency, Opacity);
          LBGRAFrom.Draw(imgResult.Canvas, 0, 0, False);
          imgResult.Repaint;
          sleep(100 - fSpeed);
        until fStep = 100;
      finally
        LBGRAMask.Free;
      end;
    finally
      LBGRATo.Free;
    end;
  finally
    LBGRAFrom.Free;
    btnGo.Enabled := True;
  end;
end;

L'effet produira des écrans comme suit :

Nom : bcrossshrink.png
Affichages : 145
Taille : 284,3 Ko

Du premier essai, nous pouvons tirer un squelette de méthode réutilisable. Notre programme de test devient alors, en ne modifiant que la partie étudiée ci-dessus :

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
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRAFrom := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    LBGRATo := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      LBGRAMask := TBGRABitmap.Create(imgResult.ClientWidth, ClientHeight, BGRABlack);
      try
        fStep := 0;
        repeat
          Inc(fStep);
          // traitement 1 ici (source)
          LX := 0;
          LY := 0;
          LBGRAFrom.FillRect(ClientRect, BGRABlack);
          LBGRAFrom.PutImage(LX, LY, fBGRAFrom, dmDrawWithTransparency, Opacity(False));
 
          // traitement 2 ici (destination)...
 
          // construction du masque
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
 
          LBGRATo.PutImage(0, 0, fBGRATo, dmSet);
          LBGRATo.ApplyMask(LBGRAMask);
          LBGRAFrom.PutImage(0, 0, LBGRATo, dmDrawWithTransparency, Opacity);
          LBGRAFrom.Draw(imgResult.Canvas, 0, 0, False);
          imgResult.Repaint;
          sleep(100 - fSpeed);
        until fStep = 100;
      finally
        LBGRAMask.Free;
      end;
    finally
      LBGRATo.Free;
    end;
  finally
    LBGRAFrom.Free;
    btnGo.Enabled := True;
  end;
end;

À partir de cette trame, nous pouvons construire de nombreuses autres transitions dès lors qu'elles produisent des images résultant de portions de l'image de destination.

Nous savons aussi que certains cas impliquent l'inversion des images avec des calculs un peu modifiés. Une autre solution de ce problème est possible désormais avec la technique des masques : nous pouvons par exemple faire disparaître progressivement l'image d'origine et ne dessiner avec un masque que la portion valide (celle à voir) de l'image de destination. L'inversion ne portera alors au pire que sur le noir et le blanc du masque.

Par exemple, voici une réécriture avec les masques de la transition LeaveTopLeft :

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
// traitement 1 ici (source)
LX := -imgResult.ClientWidth * fStep div 100;
LY := -imgResult.ClientHeight * fStep div 100;
LBGRAFrom.FillRect(ClientRect, BGRABlack);
LBGRAFrom.PutImage(LX, LY, fBGRAFrom, dmDrawWithTransparency,
  Opacity(False));
// traitement 2 ici (destination)...
LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth,
  imgResult.ClientHeight, BGRAWhite);
LBGRAMask.FillRectAntialias(LX, LY, LX + imgResult.ClientWidth,
LY + imgResult.ClientHeight, BGRABlack);
LBGRATo.PutImage(0, 0, fBGRATo, dmSet);
LBGRATo.ApplyMask(LBGRAMask);
LBGRAFrom.PutImage(0, 0, LBGRATo, dmDrawWithTransparency, Opacity);
LBGRAFrom.Draw(imgResult.Canvas, 0, 0, False);
imgResult.Repaint;

Toutes les transitions simples étudiées jusqu'ici pourraient être réécrites avec des masques. Nous y perdrions en simplicité du code et très légèrement en vitesse d'affichage, mais nous n'aurions besoin que d'une méthode pour générer toutes les transitions. Nous utiliserons cette possibilité lorsque nous réaliserons une application reprenant l'ensemble des transitions implémentées.

Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Viadeo Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Twitter Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Google Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Facebook Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Digg Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Delicious Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog MySpace Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XI) - La notion de masque » dans le blog Yahoo

Mis à jour 03/04/2018 à 19h00 par gvasseur58

Catégories
Programmation , Free Pascal , Lazarus , Graphisme

Commentaires

  1. Avatar de devEric69
    • |
    • permalink
    [SNIP] nous pouvons dessiner avec un masque que la portion valide (celle à voir) de l'image de destination.[SNIP]
    Merci pour ces tutos très didactiques .

    Ce dernier scénario imaginé peut être très utile avec une série chronologique de graphiques sur lesquels on a fixé la couleur du fond à blanc de partout (c'est un ex.), ce qui permet d'assigner le canal alpha à cette même couleur pour toute la série chronologique. On peut alors afficher une évolution de l'historique des graphes - façon diaporama, mais avec transition "bêtement additive" - en empilant les images-calques pour voir l'évolution de leur historique, grâce au concept des calques .

    BGRABitmap semble être une biblio. graphique très utile.
  2. Avatar de gvasseur58
    • |
    • permalink
    Merci pour les encouragements !
    Effectivement, l'idée de superposer des graphiques paraît bonne. BGRABitmap est une excellente bibliothèque dont je ne montre que certains aspects tant elle est riche. En fait, les techniques exposées sont disponibles dans toutes les bibliothèques graphiques dignes de ce nom : simplement, par défaut, Free Pascal et Lazarus ne proposent pas d'outils équivalents aussi performants.