Effets de transition avec Lazarus et BGRABitmap

4. Travailler avec des masques (suite)

Dans le précédent tutoriel de la série, nous avons étudié des techniques mettant en œuvre des masques afin de produire des transitions encore plus attrayantes. À présent, nous allons encore diversifier les outils à notre disposition en examinant les splines et les transitions par bandes.

13 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. L'utilisation des splines

Les programmes de test sont présents dans le répertoire exemples accompagnant le tutoriel.

I-A. Limites des outils actuels

Avec les méthodes employées pour dessiner, nous avions presque uniquement affaire à des polygones, donc à des angles vifs. Tout au plus avons-nous croisé des ellipses facilement transformables en cercles. Par ailleurs, nous aurions aussi pu exploiter la méthode RoundRectAntialias dont les différentes déclinaisons autorisent le dessin de rectangles aux sommets arrondis.

En voici les définitions au sein de la classe TBGRABitmap :

 
Sélectionnez
{** Fills a rounded rectangle with antialiasing. The corners have an
        elliptical radius of ''rx'' and ''ry''. ''options'' specifies how to
        draw the corners. See [[BGRABitmap Geometry types|geometry types]] }
    procedure FillRoundRectAntialias(x,y,x2,y2,rx,ry: single; c: TBGRAPixel; options: TRoundRectangleOptions = []; pixelCenteredCoordinates: boolean = true); override;
    {** Fills a rounded rectangle with a texture }
    procedure FillRoundRectAntialias(x,y,x2,y2,rx,ry: single; texture: IBGRAScanner; options: TRoundRectangleOptions = []; pixelCenteredCoordinates: boolean = true); override;

Comme l'expliquent les commentaires joints aux déclarations, il s'agit avant tout de définir un rectangle aux coins arrondis grâce à deux rayons ellipsoïdaux et des options qui indiquent la forme de l'arrondi pour chacun des sommets. Nous n'analyserons pas plus ces méthodes qui présentent un intérêt restreint pour le sujet des transitions. Nous voici donc limités à des formes géométriques de base avec lesquelles nous pouvons faire beaucoup, mais souvent avec des calculs ardus dès qu'il faut obtenir des courbes douces.

I-B. Les splines

Les splines vont lever ces limitations en proposant des courbes adoucies très appréciables par exemple lorsque nous chercherons à imiter un liquide. Selon Wikipédia, « en mathématiques appliquées et en analyse numérique, une spline est une fonction définie par morceaux par des polynômes ». L'article précise qu'elles sont très utilisées dans les problèmes d'interpolation et dans ceux liés au lissage de données expérimentales ou de statistiques.

[Exemple BGRABitmap 18]

Le plus simple pour comprendre leur intérêt est de produire une transition avec les outils actuellement en notre possession et d'appliquer ensuite ce nouvel outil. Nous allons proposer une animation qui fera couler l'image de destination depuis le sommet de l'image d'origine. Les coulures seront aléatoires afin d'améliorer le mimétisme de la transition.

Voici le schéma avec calque qui lui est associé :

I-B-1. Première étape sans spline

Dans un premier temps, nous nous contenterons d'utiliser les polygones pour une approximation de cet objectif. Nous allons par conséquent créer un tableau de points qui contiendra ceux du polygone d'origine : nous voulons que les points supérieur gauche et supérieur droit ne bougent pas afin de toujours garder le bord supérieur de l'image comme frontière supérieure, mais que les autres points aient leur ordonnée tirée au hasard puis graduellement augmentée jusqu'à toucher le bord inférieur de l'image à la centième étape.

Du point de vue du code, nous aurons donc quelque chose comme :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
const
  C_Points = 20;
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX, LI: Integer;
  LPts, LPtsTemp: 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;
        // préparation des tableaux
        SetLength(LPts, C_Points);
        SetLength(LPtsTemp, C_Points);
        // remplissage du tableau temporaire de travail
        // et de spoints extrêmes qui ne changeront pas 
        LPts[0] := PointF(0, 0);
        LPtsTemp[1] := PointF(0, 0);
        LPts[Length(LPts) - 1] := PointF(imgResult.ClientWidth, 0);
        LPtsTemp[Length(LPts) - 2] := PointF(imgResult.ClientWidth, 0);
        for LI := 2 to Length(LPts) - 3 do
        begin
          LPtsTemp[LI].x := imgResult.ClientWidth / Length(LPts) * LI;
          // une astuce qui permet d'accentuer les coulures 
          if random < .0.6 then
            // l'ordonnée est tirée au hasard
            LPtsTemp[LI].y := random(imgResult.ClientHeight div 3)
          else
            // ou vaut 0
            LPtsTemp[Li].y := 0;
        end;
        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)...
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
          // les ordonnées augmentent selon la position d'origine
          // l'étape en cours
          for LI := 1 to Length(LPts) - 2 do
          begin
            LPts[LI].x := LPtsTemp[LI].x;
            LPts[LI].y := LPtsTemp[LI].y + (imgResult.ClientHeight - LPtsTemp[LI].y) * fStep / 100;
          end;
          // le polygone est dessiné et rempli
          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;

Le code est suffisamment commenté pour en comprendre les principales étapes. La difficulté majeure tient aux extrémités qui doivent à la fois rester immobiles et se déplacer vers le bas : une solution est de les représenter avec deux points aux destins différents !

Une amélioration à apporter à ce code consisterait à faire disparaître la constante numérique C_Points utilisée pour le nombre de points de la courbe. Elle serait alors remplacée, soit par un calcul en fonction de la largeur de l'image, soit par un paramètre fourni par l'utilisateur. Vous avez cependant sans doute remarqué que cette constante n'a été utilisée que pour l'initialisation des tableaux, les formules de calculs lui préférant la fonction Length appliquée à la structure.

Pour le moment, le résultat n'est pas tout à fait celui escompté :

Image non disponible

Les angles des coulures sont bien trop saillants par endroits ! La question qui se pose est alors : comment pourrions-nous lisser cette courbe pour en faire disparaître les angles vifs ?

I-B-2. Seconde étape avec spline

[Exemple BGRABitmap 19]

Nous connaissons la réponse à cette question puisque les splines répondent par définition à ce type de problème.

Pour les mettre en œuvre, nous allons déclarer un nouveau tableau ouvert de TPointF que nous appellerons LSpline et grâce auquel nous calculerons la nouvelle courbe. De plus, nous remplacerons la ligne de dessin avec FillPolyAntialias par les deux lignes suivantes :

 
Sélectionnez
LSpline := LBGRAMask.ComputeOpenedSpline(LPts, ssOutside);
LBGRAMask.DrawPolyLineAntialias(LSpline, BGRAWhite, 1, BGRAWhite);

La première ligne calcule grâce à la méthode ComputeOpenedSpline les nouveaux points et les affecte à notre nouvelle variable tandis que la seconde dessine la courbe obtenue précédemment grâce à la méthode DrawPolylineAntialias.

Cette fois-ci, voici ce que nous obtenons :

Image non disponible

C'est exactement ce que nous cherchions à faire !

La routine complète de dessin est alors :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
const
  C_Points = 20;
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX, LI: Integer;
  LPts, LPtsTemp, LSpline: 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, C_Points);
        SetLength(LPtsTemp, C_Points);
        LPts[0] := PointF(0, 0);
        LPtsTemp[1] := PointF(0, 0);
        LPts[Length(LPts) - 1] := PointF(imgResult.ClientWidth, 0);
        LPtsTemp[Length(LPts) - 2] := PointF(imgResult.ClientWidth, 0);
        for LI := 2 to Length(LPts) - 3 do
        begin
          LPtsTemp[LI].x := imgResult.ClientWidth / Length(LPts) * LI;
          if random < 0.6 then
            LPtsTemp[LI].y := random(imgResult.ClientHeight div 3)
          else
            LPtsTemp[Li].y := 0;
        end;
        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)...
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
          for LI := 1 to Length(LPts) - 2 do
          begin
            LPts[LI].x := LPtsTemp[LI].x;
            LPts[LI].y := LPtsTemp[LI].y + (imgResult.ClientHeight - LPtsTemp[LI].y) * fStep / 100;
          end;
          LSpline := LBGRAMask.ComputeOpenedSpline(LPts, ssOutside);
          LBGRAMask.DrawPolyLineAntialias(LSpline, BGRAWhite, 1, 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;

Comme cette transition est assez originale et esthétique, voici un petit film qui la montre en action :


Cliquez pour lire la vidéo


I-B-3. Examen des nouvelles méthodes

Les nouvelles méthodes introduites méritent que nous nous y arrêtions un instant. La première est ComputeOpenedSpline qui, comme son nom l'indique, calcule une spline pour une courbe ouverte.

Sa déclaration se présente ainsi :

 
Sélectionnez
function ComputeOpenedSpline(const APoints: array of TPointF; AStyle: TSplineStyle): ArrayOfTPointF; override;

Nous voyons qu'elle prend pour paramètres un tableau de points (de type TPointF avec des coordonnées flottantes) et un style de type TSplineStyle. Ce dernier permet de construire la courbe selon l'effet recherché en fonction de l'enveloppe du polygone de départ, c'est-à-dire le polygone obtenu en suivant le contour extérieur du polygone de référence :

Style

Effet

ssInside

La courbe est dessinée à l'intérieur de l'enveloppe du polygone sans inclure les points de début et de fin

ssInsideWithEnds

Idem mais en incluant les points de début et de fin

ssCrossing

La courbe traverse l'enveloppe du polygone sans inclure les points de début et de fin

ssCrossingWithEnds

Idem mais en incluant les points de début et de fin

ssOutside

La courbe est dessinée à l'extérieur de l'enveloppe du polygone en incluant les points de début et de fin

ssRoundOutside

La courbe s'étend à l'extérieur de l'enveloppe du polygone en incluant les points de début et de fin

ssVertexToSide

La courbe est située à l'extérieur de l'enveloppe du polygone et est calculée à partir des tangentes aux points choisis en incluant les points de début et de fin

Certains des styles ne permettent pas d'obtenir un recouvrement complet de l'image d'origine. Par exemple, avec nos formules et les coordonnées choisies, les quatre premiers styles du tableau ne conviennent pas.

Un peu plus loin, dans le prochain exemple, nous verrons en action les différences entre ces différentes options.

La valeur retournée par la fonction est un tableau qui contient les points de la courbe arrondie : c'est ce tableau qui sera finalement dessiné.

ComputeOpenedSpline a pour pendant la méthode ComputeClosedSpline qui ferme la spline. Dans l'exemple choisi, la différence n'était pas perceptible puisque nous nous appuyions sur le bord supérieur de l'image pour former une frontière à la surface de la courbe.

La seconde méthode nouvelle est DrawPolyLineAntialias. Comme son nom l'indique, elle dessine avec anticrénelage une ligne continue composée d'un ou de plusieurs segments de ligne. Comme aussi bon nombre des méthodes présentées, elle a de multiples variétés grâce à la surcharge des déclarations :

 
Sélectionnez
{** Draws a polyline using current pen style/cap/join }
    procedure DrawPolyLineAntialias(const points: array of TPointF; c: TBGRAPixel; w: single); override;
    {** Draws a polyline using current pen style/cap/join.
        ''texture'' specifies the source color to use when filling the line }
    procedure DrawPolyLineAntialias(const points: array of TPointF; texture: IBGRAScanner; w: single); override;
    {** Draws a polyline using current pen style/cap/join.
        ''Closed'' specifies if the end of the line is closed. If it is not closed,
        a space is left so that the next line can fit }
    procedure DrawPolyLineAntialias(const points: array of TPointF; c: TBGRAPixel; w: single; ClosedCap: boolean); override;
    procedure DrawPolyLineAntialias(const points: array of TPointF; texture: IBGRAScanner; w: single; ClosedCap: boolean); override;
    {** Draws a polyline using current pen style/cap/join.
        ''fillcolor'' specifies a color to fill the polygon formed by the points }
    procedure DrawPolyLineAntialias(const points: array of TPointF; c: TBGRAPixel; w: single; fillcolor: TBGRAPixel); override;

Nous voyons que nous pouvons jouer sur les points, les couleurs, les textures, les jointures, la couleur de remplissage, l'épaisseur du trait (paramètre w) ou l'apparence de la fin de la ligne pour un éventuel raccord. Pour le masque, nous avons choisi la dernière mouture de la méthode puisque nous désirions peindre en blanc la surface délimitée par la courbe calculée.

Ces procédures mériteraient à elles seules un développement particulier qui nous conduirait bien loin de notre préoccupation première !

I-B-4. Application étendue des splines

Les transitions décrites jusqu'à présent reposaient la plupart du temps sur l'utilisation d'un ou de plusieurs polygones (triangles et rectangles pour l'essentiel). En appliquant l'outil de la spline à ces transitions, nous pouvons espérer obtenir des animations proches de l'original mais aux formes adoucies.

[Exemple BGRABitmap 20]

Reprenons par exemple la transition RightBottomExpand qui agrandit un rectangle en direction du point inférieur droit de l'image finale, recouvrant peur à peu l'image d'origine par celle de destination. Nous pouvons stocker dans un tableau les coordonnées du rectangle qui sert au dessin, calculer une spline à partir d'eux, et dessiner le résultat sur notre masque.

Le code donnerait alors ceci :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX: Integer;
  LPts, LSpline: 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;
        repeat
          Inc(fStep);
          LX := 0;
          LY := 0;
          LBGRAFrom.FillRect(ClientRect, BGRABlack);
          LBGRAFrom.PutImage(LX, LY, fBGRAFrom, dmDrawWithTransparency, Opacity(False));
          SetLength(LPts, 4);
          LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
          //LBGRAMask.FillRectAntialias(0, 0, imgResult.ClientWidth * fStep div 100,
          //  imgResult.ClientHeight * fStep div 100, BGRAWhite);
          LPts[0] := PointF(0, 0);
          LPts[1] := PointF(imgResult.ClientWidth * fStep div 100, 0);
          LPts[2] := PointF(imgResult.ClientWidth * fStep div 100, imgResult.ClientHeight * fStep div 100);
          LPts[3] := PointF(0, imgResult.ClientHeight * fStep div 100);
          LSpline := LBGRAMask.ComputeClosedSpline(LPts, ssVertexToSide);
          LBGRAMask.DrawPolyLineAntialias(LSpline, BGRAWhite, 1, 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;

Les deux lignes de calculs mises en commentaires sont les deux lignes originales écrites pour la transition sans spline.

La transition dessine désormais une forme arrondie proche de l'ellipse :

Image non disponible

Afin de visualiser les différences entre les options associées au calcul de la spline, nous ajouterons une ligne provisoire à notre code :

 
Sélectionnez
LBGRATo.PutImage(0, 0, fBGRATo, dmSet);
LBGRATo.ApplyMask(LBGRAMask);
LBGRAFrom.PutImage(0, 0, LBGRATo, dmDrawWithTransparency, Opacity);
LBGRAFrom.DrawPolylineAntialias(LPts,BGRA(255,0,0,255),3);  // nouvelle ligne
LBGRAFrom.Draw(imgResult.Canvas, 0, 0);
imgResult.Repaint;

La forme d'origine de la ligne multiple est reproduite au fur et à mesure de la transition. Elle est d'autant plus visible que sa couleur de dessin est le rouge. Il ne nous reste qu'à modifier le dernier paramètre de la méthode ComputeClosedSpline pour voir les résultats obtenus.

 
Sélectionnez
LSpline := LBGRAMask.ComputeClosedSpline(LPts, ssVertexToSide);

Nous verrons ainsi ce que signifie tel ou tel paramètre. Nous vérifierons aussi que certaines options ne couvrent pas entièrement l'image.

Voici par exemple un instantané pris avec l'option ssCrossing :

Image non disponible

Il est clair que la courbe finale « traverse » bien la courbe d'origine (le rectangle) !

II. Les transitions par bandes

Certaines transitions répètent un motif sur la zone d'affichage. Afin d'illustrer ce cas, nus allons construire une série de transitions qui dessineront des rectangles régulièrement espacés qui feront apparaître l'image de destination. L'espacement sera vertical ou horizontal.

II-A. Les bandes horizontales

[Exemple BGRABitmap 21]

Il s'agit dans un premier temps de produire des bandes horizontales. Le principe est de diviser la hauteur de l'image par le nombre de bandes et de commencer à dessiner l'image de destination depuis les points ainsi trouvés. Chaque bande (rectangle) commencera à un point et ira jusqu'au point suivant (vers le haut ou vers le bas) en suivant le rythme des étapes, donc de 0 à 100.

La transition StripsDown nous servira de modèle.

Son schéma de fonctionnement avec un calque sera celui-ci :

Image non disponible

Pour nos essais, nous déclarerons une constante C_NumberOfStrips qui fournira le nombre de bandes à dessiner. Comme pour les points de la spline, nous envisagerons ultérieurement le choix de ce nombre par l'utilisateur lui-même.

Grâce à cette constante, nous pourrons calculer aisément l'ordonnée de départ de chacune des bandes. Les abscisses sont par ailleurs déjà connues puisque les bandes occuperont en permanence toute la largeur de l'image.

La hauteur de l'image est quelconque si bien que nous serons susceptibles de nous heurter à deux problèmes. Le premier viendrait de ce que la hauteur ne serait pas divisible exactement par le nombre de bandes si bien qu'il nous faudrait tenir compte des problèmes d'arrondi afin de bien recouvrir toute la surface de l'image. Bien que peu probable, le second problème surgirait si la hauteur était plus petite en pixels que le nombre de bandes : ce cas ne sera pas traité pour le moment, mais nous devrons le garder en tête.

Pour rendre les calculs plus clairs, nous définirons aussi une variable locale LStripHeight qui contiendra la hauteur de la bande en fonction du nombre de bandes et de la hauteur de l'image.

Voici le code proposé :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***

const
  C_NumberOfStrips = 10;
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX, LI, LStripHeight: Integer;
begin
  btnGo.Enabled := False;
  // calcul de la hauteur d'une bande + 1 pour les arrondis
  LStripHeight := 1 + imgResult.ClientHeight div C_NumberOfStrips;
  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);
          LX := 0;
          LY := 0;
          LBGRAFrom.FillRect(ClientRect, BGRABlack);
          LBGRAFrom.PutImage(LX, LY, fBGRAFrom, dmDrawWithTransparency, Opacity(False));
          // nous traçons les bandes
          for LI := 0 to C_NumberOfStrips - 1 do
          begin
            // chaque bande est un rectangle
            LBGRAMask.FillRectAntialias(0, LStripHeight * LI,
              imgResult.ClientWidth,
              LStripHeight * fStep div 100 + LStripHeight * LI,
              BGRAWhite);
          end;
          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;

Le code ressemble beaucoup à ce qui a déjà été étudié. Une copie instantanée d'écran a pu donner :

Image non disponible

La réciproque de cette transition sera appelée StripsUp. Sans surprise, le cœur du code nécessaire sera :

 
Sélectionnez
for LI := 1 to C_NumberOfStrips do
begin
  LBGRAMask.FillRectAntialias(0, LStripHeight * LI - LStripHeight * fStep div 100,
   imgResult.ClientWidth,
   LStripHeight * LI,
   BGRAWhite);
end;

II-B. Les bandes verticales

[Exemple BGRABitmap 22]

La réalisation de bandes verticales est très proche celle de bandes horizontales si bien que nous allons en profiter pour introduire une autre façon de gérer le nombre de bandes que l'emploi d'une constante.

Nous allons donc créer une fonction nommée NumberOfStrips imbriquée dans le gestionnaire OnClick dont nous nous servons sans cesse. Cette fonction retournera le nombre de bandes selon la largeur de l'image à produire : 1 bande pour une image de moins de 10 pixels de largeur ; 5 bandes pour moins de 300 pixels ; la largeur divisée par C_DefaultNumberOfStrips (une constante fixée à 20) pour moins de 1000 pixels ; 30 bandes pour les images plus larges.

Les valeurs choisies ont été fixées empiriquement. Le nombre de bandes par défaut bénéficie d'une constante, car il s'agit d'une valeur susceptible d'être modifiée selon les goûts de chacun !

Après avoir été toutes nommées, ces constantes pourront être regroupées dans une unité à part.

Voici le code proposé :

 
Sélectionnez
function NumberOfStrips: Integer;
  const
    C_DefaultNumberOfStrips = 20;
  begin
    if imgResult.ClientWidth < 10 then
      Result := 1
    else
    if (imgResult.ClientWidth < 300) then
      Result := 5
    else
    if (imgResult.ClientWidth < 1000) then
      Result := imgResult.ClientWidth div C_DefaultNumberOfStrips
    else
      Result := 30;
  end;

Pour ce qui est du code de la transition StripsLeft, voici que nous pouvons écrire :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRAFrom, LBGRATo, LBGRAMask: TBGRABitmap;
  LY, LX, LI, LStripWidth: Integer;

  function NumberOfStrips: Integer;
  const
    C_DefaultNumberOfStrips = 20;
  begin
    if imgResult.ClientWidth < 10 then
      Result := 1
    else
    if (imgResult.ClientWidth < 300) then
      Result := 5
    else
    if (imgResult.ClientWidth < 1000) then
      Result := imgResult.ClientWidth div C_DefaultNumberOfStrips
    else
      Result := 30;
  end;

begin
  btnGo.Enabled := False;
  LStripWidth := 1 + imgResult.ClientWidth div NumberOfStrips;
  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);
          LX := 0;
          LY := 0;
          LBGRAFrom.FillRect(ClientRect, BGRABlack);
          LBGRAFrom.PutImage(LX, LY, fBGRAFrom, dmDrawWithTransparency, Opacity(False));
          for LI := 1 to NumberOfStrips do
          begin
            LBGRAMask.FillRectAntialias(LStripWidth * LI - LStripWidth * fStep div 100, 0,
              LStripWidth * LI,
              imgResult.ClientHeight, BGRAWhite);
          end;
          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;

Ce sont à présent les ordonnées qui sont constantes en recouvrant toute la hauteur de l'image. Partant d'une position fixe déterminée par LI, les rectangles s'agrandissent vers la gauche de l'image en fonction de l'étape à réaliser.

Un instantané de cette transition donnera par exemple :

Image non disponible

En diminuant fortement la largeur de l'image finale jusqu'à ce qu'elle ait une valeur inférieure à 300 pixels, nous obtiendrons par exemple, sans calculs supplémentaires :

Image non disponible

Nous pouvons de la même manière créer la transition StripsRight. Nous n'en proposerons que le code et une vidéo de démonstration :

 
Sélectionnez
for LI := 0 to NumberOfStrips - 1 do
begin
  // chaque bande est un rectangle
  LBGRAMask.FillRectAntialias(LStripWidth * LI, 0,
  LStripWidth * fStep div 100 + LStripWidth * LI,
  imgResult.ClientHeight, BGRAWhite);
end;

Cliquez pour lire la vidéo


II-C. Entrelacer les bandes

[Exemple BGRABitmap 23]

III. Conclusion

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2018 Gilles Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.