Voir le flux RSS

Blog de Gilles Vasseur - Pascal et compagnie

Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques

Noter ce billet
par , 12/04/2018 à 08h39 (162 Affichages)
Jusqu'à présent, nous n'avons utilisé que les rectangles comme comme outils de construction du masque. Non seulement il est possible d'imaginer de nouvelles transitions avec eux, mais d'autres formes géométriques aussi complexes que voulu peuvent créer des effets intéressants.

Utilisation des formes géométriques

Les rectangles


Une première idée serait de faire apparaître un rectangle au centre de l'image d'origine et de le faire croître pour découvrir l'image de destination. C'est l'objet de la transition RectOut. Comme nous avons une certaine expérience de ces transitions mettant en œuvre un masque, nous nous contenterons de fournir directement le code nécessaire.

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
LX := (imgResult.ClientWidth div 2) * fStep div 100;
LY := (imgResult.ClientHeight div 2) * fStep div 100;
LBGRAMask.FillRectAntialias(- LX + imgResult.ClientWidth div 2, - LY +
  imgResult.ClientHeight div 2, LX + imgResult.ClientWidth div 2, LY +
  imgResult.ClientHeight div 2, BGRAWhite);

Les variables locales LX et LY sont utilisées afin d'éviter de recalculer les coordonnées pour chaque portion de la formule.

De même, l'inverse RectIn de cette opération est plutôt facile à obtenir. Afin de varier les solutions apportées aux problèmes posés, au lieu d'intervertir les images selon la technique déjà employée à plusieurs reprises, nous pouvons très bien inverser les couleurs du masque pour obtenir le même résultat : le blanc devient noir et réciproquement.

Le code obtenu sera alors :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
// traitement 2 ici (destination)...
LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth,
  imgResult.ClientHeight, BGRAWhite);
LX := (imgResult.ClientWidth div 2) * fStep div 100;
LY := (imgResult.ClientHeight div 2) * fStep div 100;
  LBGRAMask.FillRectAntialias(LX, LY, imgResult.ClientWidth - LX,
  imgResult.ClientHeight - LY, BGRABlack);

Nom : brectin.png
Affichages : 23
Taille : 287,8 Ko

Les ellipses et les cercles

Pourquoi se limiter à des rectangles*? La bibliothèque BGRABitmap propose d'autres formes prédéfinies dont les ellipses, par ailleurs transformables en cercles en choisissant des coordonnées adaptées. La prudence s'impose cependant, car il faut les choisir de telle façon que l'image de destination puisse de toute façon entièrement recouvrir l'image d'origine.

Avec BGRABitmap, une ellipse pleine est dessinée grâce à un point à partir duquel sont calculés le rayon horizontal et celui vertical. Nous avons deux procédures utiles à notre disposition. Leur dernier paramètre précise si c'est une couleur ou une texture qui est utilisée :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
{** Fills an ellipse }
    procedure FillEllipseAntialias(x, y, rx, ry: single; c: TBGRAPixel); override;
{** Fills an ellipse with a ''texture'' }
    procedure FillEllipseAntialias(x, y,  x, ry: single; texture: IBGRAScanner); override;

La première transition du type ellipsoïdal sera EllipseOut. En voici le code caractéristique :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth,
  imgResult.ClientHeight, BGRABlack);
LBGRAMask.FillEllipseAntialias(imgResult.ClientWidth div 2,
  imgResult.ClientHeight div 2, fStep * imgResult.ClientWidth div 140,
  fStep * imgResult.ClientHeight div 140, BGRAWhite);

Nous sommes revenus à l'application modèle sans intervertir le blanc et le noir, d'où l'initialisation dans la première ligne de traitement du masque avec une surface entièrement noire.

De même, nous pouvons imaginer la transition EllipseIn qui formera une ellipse de plus en plus petite pour la faire disparaître au centre de l'image de résultat.

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth,
  imgResult.ClientHeight, BGRAWhite);
LBGRAMask.FillEllipseAntialias(imgResult.ClientWidth div 2,
  imgResult.ClientHeight div 2, (100 - fStep) * imgResult.ClientWidth
  div 140, (100 - fStep) * imgResult.ClientHeight div 140, BGRABlack);

La technique consistant à inverser le blanc et le noir du masque a été réutilisée ici.

Nom : bellipseout.png
Affichages : 24
Taille : 287,9 Ko

Les formes composées


Grâce aux procédures fournies avec BGRABitmap, nous sommes en mesure de dessiner toutes sortes de figures, de la plus simple (par exemple, un triangle) à la plus complexe. Pour ce faire, outre les rectangles et les ellipses déjà vus, nous disposons d'un outil très utile : FillPolyAntialias. Cette méthode construit un polynôme dont les points sont donnés en paramètre sous forme d'un tableau ouvert de TPointF et remplit ensuite la surface ainsi délimitée par la couleur fournie en second paramètre.

TPointF est un enregistrement défini dans la RTL de Free Pascal. Il considère les points comme un enregistrement de deux réels (type single) sur lesquels l'utilisateur peut intervenir grâce à un ensemble conséquents de routines et d'opérateurs de classe :

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
{ TPointF }
  TPointF =
{$ifndef FPC_REQUIRES_PROPER_ALIGNMENT}
  packed
{$endif FPC_REQUIRES_PROPER_ALIGNMENT}
  record
       x,y : Single;
       public
          function Add(const apt: TPoint): TPointF;
          function Add(const apt: TPointF): TPointF;
          function Distance(const apt : TPointF) : Single;
          function DotProduct(const apt : TPointF) : Single;
          function IsZero : Boolean;
          function Subtract(const apt : TPointF): TPointF;
          function Subtract(const apt : TPoint): TPointF;
          procedure SetLocation(const apt :TPointF);
          procedure SetLocation(const apt :TPoint);
          procedure SetLocation(ax,ay : Longint);
          procedure Offset(const apt :TPointF);
          procedure Offset(const apt :TPoint);
          procedure Offset(dx,dy : Longint);
 
          function  Scale (afactor:Single)  : TPointF;
          function  Ceiling : TPoint;
          function  Truncate: TPoint;
          function  Floor   : TPoint;
          function  Round   : TPoint;
          function  Length  : Single;
          class operator = (const apt1, apt2 : TPointF) : Boolean;
          class operator <> (const apt1, apt2 : TPointF): Boolean;
          class operator + (const apt1, apt2 : TPointF): TPointF;
          class operator - (const apt1, apt2 : TPointF): TPointF;
          class operator - (const apt1 : TPointF): TPointF;
          class operator * (const apt1, apt2: TPointF): Single; // scalar product
          class operator * (const apt1: TPointF; afactor: single): TPointF;
          class operator * (afactor: single; const apt1: TPointF): TPointF;
       end;

La seule véritable difficulté est de choisir correctement les points et de prévoir avec rigueur leur déplacement au cours du déroulement de la transition*!

Un premier exemple illustrera ce que nous venons de découvrir. Nous pouvons imaginer une transition qui superposerait comme d'habitude l'image de destination à celle d'origine en utilisant quatre triangles isocèles dont la base serait un des côtés du rectangle que forme l'image et dont le sommet opposé à cette base suivrait le milieu du côté où il se situe pour rejoindre le centre de la même image. La transition verrait donc les triangles s'étendre jusqu'à se rejoindre au centre de l'image. Le schéma de fonctionnement serait donc le suivant :

Nom : trianglesexpand1.png
Affichages : 19
Taille : 3,4 Ko

Pour la construction du masque, nous aurons :

Nom : trianglesexpand2.png
Affichages : 19
Taille : 6,9 Ko

Le code correspondant serait :

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
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX: Integer;
  LPts: array of TPointF;
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;
        SetLength(LPts, 3);
        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)...
          // triangle bord gauche
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
          LPts[0].x := 0;
          LPts[0].y := 0;
          LPts[1].x := imgResult.ClientWidth / 2 * fStep / 100;
          LPts[1].y := imgResult.ClientHeight / 2;
          LPts[2].x := 0;
          LPts[2].y := imgResult.ClientHeight;
          LBGRAMask.FillPolyAntialias(LPts, BGRAWhite);
          // triangle opposé (bord droit)
          LPts[0].x := imgResult.ClientWidth;
          LPts[0].y := 0;
          LPts[1].x := imgResult.ClientWidth - imgResult.ClientWidth / 2 * fStep / 100;
          LPts[1].y := imgResult.ClientHeight / 2;
          LPts[2].x := imgResult.ClientWidth;
          LPts[2].y := imgResult.ClientHeight;
          LBGRAMask.FillPolyAntialias(LPts, BGRAWhite);
          // triangle bord bas
          LPts[0].x := 0;
          LPts[0].y := imgResult.ClientHeight;
          LPts[1].x := imgResult.ClientWidth / 2;
          LPts[1].y := imgResult.ClientHeight - imgResult.ClientHeight / 2 * fStep / 100;
          LPts[2].x := imgResult.ClientWidth;;
          LPts[2].y := imgResult.ClientHeight;
          LBGRAMask.FillPolyAntialias(LPts, BGRAWhite);
          //triangle opposé (bord haut)
          LPts[0].x := 0;
          LPts[0].y := 0;
          LPts[1].x := imgResult.ClientWidth / 2;
          LPts[1].y := imgResult.ClientHeight / 2 * fStep / 100;
          LPts[2].x := imgResult.ClientWidth;
          LPts[2].y := 0;
          LBGRAMask.FillPolyAntialias(LPts, BGRAWhite);
          LBGRATo.PutImage(0, 0, fBGRATo, dmSet);
          LBGRATo.ApplyMask(LBGRAMask);
          LBGRAFrom.PutImage(0, 0, LBGRATo, dmDrawWithTransparency, Opacity);
          LBGRAFrom.Draw(imgResult.Canvas, 0, 0);
          imgResult.Repaint;
          sleep(100 - fSpeed);
        until fStep = 100;
      finally
        LBGRAMask.Free;
      end;
    finally
      LBGRATo.Free;
    end;
  finally
    LBGRAFrom.Free;
    btnGo.Enabled := True;
  end;
end;

Vous aurez remarqué que les coordonnées manipulées par le tableau de points sont exprimées par des nombres flottants, d'où le F qui clôt le nom du type et l'emploi de / pour la division au lieu de div. Notez aussi que nous pourrions simplifier les calculs en supprimant des lignes redondantes (laissées pour une meilleure compréhension des calculs effectués) puisque certaines variables contiennent déjà la valeur à leur affecter.

Vous pouvez raccourcir l'écriture du code en fournissant en une seule instruction l'abscisse et l'ordonnée d'un point. Par exemple :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
LPts[1].x := imgResult.ClientWidth / 2 * fStep / 100;
LPts[1].y := imgResult.ClientHeight / 2;

peut aussi s'écrire :

Code pascal : Sélectionner tout - Visualiser dans une fenêtre à part
Lpts[1]:= PointF(imgResult.ClientWidth / 2 * fStep / 100,  imgResult.ClientHeight / 2);

La transition en action donnera par exemple :

Nom : btrianglesexpand.png
Affichages : 29
Taille : 328,7 Ko

Évidemment, nous pouvons compliquer à loisir notre transition, même à partir de figures simples comme les triangles, afin d'obtenir des transitions vraiment spectaculaires. Nous verrons bientôt une illustration de cette possibilité !

Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Viadeo Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Twitter Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Google Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Facebook Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Digg Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Delicious Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog MySpace Envoyer le billet « Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques » dans le blog Yahoo

Commentaires