Les transitions entre images sous Lazarus avec BGRABitmap (XV) - Utilisation des formes géométriques
par
, 12/04/2018 à 09h39 (823 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);
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.
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 :
Pour la construction du masque, nous aurons :
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 :
É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é !