Effets de transition avec Lazarus et BGRABitmap

5. Rotations, homothéties et filtres

Dans le précédent tutoriel de la série, avec les splines et les bandes, nous avons fini d'étudier les techniques mettant en œuvre des masques afin de produire des transitions plus attrayantes et plus variées que par simple superposition. Il est temps de découvrir d'autres classes et méthodes que nous propose la bibliothèque BGRABitmap pour dessiner à l'aide de rotations, d'homothéties ou de filtres.

3 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'emploi des rotations

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

En géométrie, une rotation est une transformation, tout comme la translation que nous avons déjà exploitée. Plus spécifiquement, nous parlerons de la rotation plane qui fait tourner d'un angle donné les figures autour d'un point, selon une orientation elle aussi fixée. Comme lors d'une translation, une figure n'est ni déformée ni agrandie par rotation. En revanche, la rotation pose des problèmes spécifiques de rendu d'une image après traitement à cause d'inévitables erreurs d'arrondi : heureusement, ces problèmes de crénelage avec certains angles sont traités par la bibliothèque BGRABitmap grâce aux méthodes de dessin incluant le mot anglais antialias (anticrénelage). Ce sont toujours à elles que nous ferons appel, comme nous l'avons d'ailleurs fait jusqu'à présent.

Il existe plusieurs façons d'obtenir des rotations avec BGRABitmap : une méthode spécifique de la classe TBGRABitmap, mais aussi des outils fournis par l'unité BGRATransform consacrée aux transformations. Dans un premier temps, nous n'utiliserons que la méthode PutImageAngle liée à la classe TBGRABitmap, renvoyant l'étude des autres possibilités aux chapitres suivants.

La méthode PutImageAngle, comme de nombreuses méthodes de la classe TBGRABitmap, prend plusieurs formes :

 
Sélectionnez
procedure PutImageAngle(x,y: single; Source: TBGRACustomBitmap; angle: single; AOutputBounds: TRect; imageCenterX: single = 0; imageCenterY: single = 0; AOpacity: Byte=255; ARestoreOffsetAfterRotation: boolean = false; ACorrectBlur: Boolean = false); overload;
     procedure PutImageAngle(x,y: single; Source: TBGRACustomBitmap; angle: single; imageCenterX: single = 0; imageCenterY: single = 0; AOpacity: Byte=255; ARestoreOffsetAfterRotation: boolean = false; ACorrectBlur: Boolean = false); overload;
     procedure PutImageAngle(x,y: single; Source: TBGRACustomBitmap; angle: single; AOutputBounds: TRect; AResampleFilter: TResampleFilter; imageCenterX: single = 0; imageCenterY: single = 0; AOpacity: Byte=255; ARestoreOffsetAfterRotation: boolean = false); overload;
     procedure PutImageAngle(x,y: single; Source: TBGRACustomBitmap; angle: single; AResampleFilter: TResampleFilter; imageCenterX: single = 0; imageCenterY: single = 0; AOpacity: Byte=255; ARestoreOffsetAfterRotation: boolean = false); overload;

Nous voyons que cette méthode est accompagnée de plusieurs paramètres dont certains adoptent une valeur par défaut. C'est sous la forme suivante qu'elle nous intéressera le plus :

 
Sélectionnez
procedure PutImageAngle(x,y: single; Source: TBGRACustomBitmap; angle: single; imageCenterX: single = 0; imageCenterY: single = 0; AOpacity: Byte=255; ARestoreOffsetAfterRotation: boolean = false; ACorrectBlur: Boolean = false); overload;

En effet, il nous est demandé de fournir les coordonnées de destination de l'image en rotation, la source de l'image à faire tourner, l'angle (en degrés) de la rotation, les coordonnées du centre de rotation, l'opacité à appliquer au résultat, ainsi que deux éventuelles corrections (replacer l'image dans sa position originelle et corriger les effets de flou). Les paramètres essentiels nous fournissent la possibilité de varier les effets.

[Exemple BGRABitmap 29]

Les masques étant superflus pour l'application d'une rotation complète de l'image sur une autre image, le gestionnaire OnClick de la transition appelée CWCenterRotate pourra être réduit à quelque chose comme :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    fStep := 0;
    repeat
      Inc(fStep);
      LX := 0;
      LY := 0;
      LBGRATemp.Fill(BGRABlack);
      LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
      // traitement ici...
      LX := imgResult.ClientWidth div 2;
      LY := imgResult.ClientHeight div 2;
      LBGRATemp.PutImageAngle(LX, LY, fBGRATo, fStep / 100 * 360, imgResult.ClientWidth / 2, imgResult.ClientHeight / 2, Opacity);
      LBGRATemp.Draw(imgResult.Canvas, 0, 0);
      Application.ProcessMessages;
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Avec LX et LY, nous déplaçons l'image à faire tourner au centre de l'image de résultat. La rotation est fournie grâce à une formule de proportionnalité de fStep : un tour complet vaut 360 degrés et les valeurs positives opèrent par convention une rotation dans le sens des aiguilles d'une montre. Le centre de rotation choisi est le centre de l'image, d'où le calcul de ses coordonnées à partir de ClientWidth et ClientHeight. Enfin, l'opacité est rendue grâce à la fonction habituelle Opacity.

La transition CCWCenterRotate n'exigerait qu'une inversion du sens de rotation, donc l'ajout d'un signe - (moins) devant le paramètre de l'angle de rotation.

Vous aurez remarqué que les coordonnées des points sont des valeurs réelles si bien que le signe de division utilisé pour une meilleure précision est / et non l'instruction div.

Image non disponible

[Exemple BGRABitmap 30]

Les variantes sont faciles à imaginer. Par exemple, pourquoi ne pas faire progresser le centre de rotation du point de coordonnées (0, 0) vers le centre de l'image ? La méthode de travail prendrait alors la forme suivante pour la transition CWSpiral :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    fStep := 0;
    repeat
      Inc(fStep);
      LX:= 0;
      LY:= 0; 
      LBGRATemp.Fill(BGRABlack);
      LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
      // traitement ici...
      LX := imgResult.ClientWidth div 2;
      LY := imgResult.ClientHeight div 2;
      LBGRATemp.PutImageAngle(LX, LY, fBGRATo, fStep / 100 * 360,
        fStep / 100 * imgResult.ClientWidth / 2, // changement
        fStep / 100 * imgResult.ClientHeight / 2, // changement
        Opacity);
      LBGRATemp.Draw(imgResult.Canvas, 0, 0);
      Application.ProcessMessages;
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

L'idée qui sous-tend cette variante est de combiner la rotation de l'image à une translation de son centre de rotation, d'où l'obtention d'une spirale. Pour cela, nous avons transformé le pas en un pourcentage de la translation et de la rotation.

Pour obtenir une rotation tournant dans le sens inverse des aiguilles d'une montre (transition CCWSpiral), nous n'aurions qu'à rendre négatif l'angle de rotation.

[Exemple BGRABitmap 31]

Nous pouvons au contraire faire varier les coordonnées du coin supérieur gauche de l'image et laisser constant le centre de rotation. Il faut simplement prendre garde au fait que si les valeurs sont mal choisies la transition risque de se réaliser en majeure partie hors de la fenêtre d'affichage ! À la fin de la transition, il est nécessaire que les variables LX et LY soient toutes les deux à 0 pour un recouvrement total de l'image de départ.

Voici le code proposé pour une transition baptisée CWRotateRight :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    fStep := 0;
    repeat
      Inc(fStep);
      LX := 0;
      LY := 0;
      LBGRATemp.Fill(BGRABlack);
      LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
      // traitement ici...
      LX := fStep * imgResult.ClientWidth div 100 - imgResult.ClientWidth div 2;
      LY := fStep * imgResult.ClientHeight div 100 - imgResult.ClientHeight div 2;
      LBGRATemp.PutImageAngle(LX, LY, fBGRATo, fStep / 100 * 360,
        imgResult.ClientWidth / 2,
        imgResult.ClientHeight / 2, Opacity);
      LBGRATemp.Draw(imgResult.Canvas, 0, 0);
      Application.ProcessMessages;
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Une copie d'écran de la transition en cours a donné :

Image non disponible

Il est bien entendu possible de créer une transition qui provoquera une rotation dans le sens opposé des aiguilles d'une montre : encore une fois, le signe de l'angle indiqué dans la méthode PutImageAngle aura juste à être inversé.

Nous voyons que les rotations sont plutôt faciles à mettre en œuvre dans la mesure où l'aire de leur exécution reste visible au maximum durant la transition. C'est combinées aux homothéties que nous allons à présent étudier qu'elles révéleront toutes leurs potentialités.

II. L'emploi des homothéties

En géométrie, une homothétie est une transformation correspondant à un agrandissement ou à une réduction. À partir d'elle, nous pouvons élaborer différentes transitions qui feront toutes appel à une seule méthode de TBGRABitmap, à savoir Resample. Oui, il s'agit tout naturellement de la méthode que nous avons déjà utilisée pour redimensionner les images afin qu'elles aient toutes une taille identique !

II-A. Les homothéties simples

La première transition qui peut venir à l'esprit est celle qui consiste à faire apparaître l'image de destination au centre de celle du résultat en augmentant sa taille au fur et à mesure de la progression de l'effet désiré.

Du point de vue de l'algorithme à employer, cette transition paraît simple : à chaque pas, l'image de destination est redimensionnée à la taille voulue puis dessinée au centre de l'image de départ. Il n'est donc pas nécessaire d'employer un masque puisque cette transition fonctionne par recouvrement. En revanche, nous allons avoir besoin d'un objet de type TBGRABitmap supplémentaire.

[Exemple BGRABitmap 32]

En effet, appliquons notre algorithme en tapant le code suivant :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    fStep := 0;
    repeat
      Inc(fStep);
      LX := 0;
      LY := 0;
      LBGRATemp.Fill(BGRABlack);
      LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
      // traitement ici...
      BGRAReplace(fBGRATo, fBGRATo.Resample(fStep * imgResult.ClientWidth  div 100,
      fStep * imgResult.ClientHeight div 100));
      LX := (imgResult.ClientWidth - fBGRATo.Width) div 2;
      LY := (imgResult.ClientHeight - fBgraTo.Height) div 2;
      LBGRATemp.PutImage(LX, LY, fBGRATo, dmDrawWithTransparency, Opacity);
      LBGRATemp.Draw(imgResult.Canvas, 0, 0);
      Application.ProcessMessages;
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Nous avons bien créé l'image de destination en la redimensionnant, défini l'emplacement central de son dessin en calculant les coordonnées de son point supérieur gauche et recouvert l'image de départ. Pourtant, à l'exécution, l'image de destination n'est pas dessinée : un rectangle gris recouvre peu à peu l'image de départ, et c'est tout !

L'explication de ce résultat surprenant est simple : le redimensionnement se fait en perdant ou en créant des informations. Lorsque la taille de l'image diminue, des informations sont définitivement perdues ; lorsque cette même taille augmente, des informations sont ajoutées pour remplir les vides créés. Pour que ce travail se passe au mieux, la bibliothèque BGRABitmap dispose d'algorithmes dédiés, mais il n'en reste pas moins qu'une dégradation de l'image est inévitable, surtout si le rapport d'homothétie est important ou si l'homothétie s'applique plusieurs fois. Pour ce qui est de notre étude, nous sommes dans les deux cas puisque notre image commence par un rectangle virtuel aux dimensions nulles (donc sans informations !) et que l'agrandissement nécessite une centaine de redimensionnements.

Afin de bien visualiser la déperdition inévitable, il nous suffit de modifier une seule ligne à notre code :

 
Sélectionnez
fStep := 40;

Nous commençons ainsi le dessin à partir d'une image avec une taille non nulle, ce qui est déjà plus pertinent.

Voici ce que donne l'exécution de l'application si nous la laissons en l'état :

Image non disponible

La transition a bien fonctionné si nous ne considérons que son déroulement, mais le résultat final est décevant, car l'image obtenue est très floue. Comment pourrait-il en être autrement avec 60 redimensionnements ?

[Exemple BGRABitmap 33]

Une solution est de ne pas travailler avec l'image originale, mais de recopier à chaque tour cette dernière dans une image de travail provisoire. Évidemment, le temps d'exécution sera légèrement dégradé puisque, en plus de la création et de la libération de l'objet provisoire, il faudra compter autant de copies que de pas d'exécution.

Voici le code proposé :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRAResample: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth  div 100,
        fStep * imgResult.ClientHeight div 100) as TBGRABitmap;
        try
          LX := (imgResult.ClientWidth - LBGRAResample.Width) div 2;
          LY := (imgResult.ClientHeight - LBGRAResample.Height) div 2;
          LBGRATemp.PutImage(LX, LY, LBGRAResample, dmDrawWithTransparency, Opacity);
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRAResample.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Il nous a fallu remplacer les occurrences de l'image de destination (fBGRATo) par notre nouvelle image de travail baptisée LBGRAResample. C'est elle que nous redimensionnons à chaque tour, mais comme nous avons pris soin dans le même temps de faire d'elle la copie de l'image originale, nous ne percevons pas de dégradation sensible de son rendu.

Il est aussi possible de faire appel à la méthode StretchPutImage qui présente l'avantage de grouper en son sein le redimensionnement et le dessin. Il n'est donc plus besoin de l'image intermédiaire. Voici le code à utiliser :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LX := (imgResult.ClientWidth - fStep * imgResult.ClientWidth div 100) div 2;
        LY := (imgResult.ClientHeight - fStep * imgResult.ClientHeight div 100) div 2;
        LBGRATemp.StretchPutImage(Rect(LX, LY, LX + fStep * imgResult.ClientWidth div 100,
          LY + fStep * imgResult.ClientHeight div 100), fBGRATo, dmDrawWithTransparency,
          Opacity);
        LBGRATemp.Draw(imgResult.Canvas, 0, 0);
        Application.ProcessMessages;
        sleep(100 - fSpeed);
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Le code de calcul est un peu plus complexe à cause du calcul du rectangle de dessin, mais la solution est plus élégante avec l'économie d'une nouvelle instanciation.

Dans le composant final, c'est cette solution qui sera utilisée. Avec le système des interpolations, en plus d'être plus rapide, elle deviendra même bien plus simple à mettre en œuvre. Par la suite, nous ne conserverons la méthode avec Resample qu'à des fins pédagogiques pour bien identifier les étapes du travail ou pour simplifier les formules de calcul.

Voici un instantané pris lors du déroulement de cette transition nommée GrowCenter :

Image non disponible

[Exemple BGRABitmap 34]

En considérant cette dernière transition, nous pouvons en construire d'autres dont nous ne présenterons que la portion de code caractéristique avec l'emploi de l'objet intermédiaire LBGRAResample. Sans lui, les calculs seront à peine plus complexes puisqu'il faudra calculer la largeur et la hauteur de l'image agrandie en fonction du pas en cours.

La transition qui agrandira l'image de destination en la faisant apparaître dans le coin supérieur gauche de l'image du résultat pour la faire rejoindre son coin inférieur droit sera nommée GrowBottomRight et aura pour code caractéristique :

 
Sélectionnez
LX := 0;
LY := 0;

Oui, c'est suffisant ! De même, voici celui de sa sœur GrowBottomLeft :

 
Sélectionnez
LX := imgResult.ClientWidth - LBGRAResample.Width;
LY := 0;

Dans le même esprit, voici celui de GrowTopRight qui part du coin inférieur gauche pour rejoindre le coin supérieur droit :

 
Sélectionnez
LX := 0;
LY := imgResult.ClientHeight - LBGRAResample.Height;

Le pendant de la transition précédente sera nommé GrowTopLeft et partira du coin inférieur droit pour rejoindre le coin supérieur gauche :

 
Sélectionnez
LX := imgResult.ClientWidth - LBGRAResample.Width;
LY := imgResult.ClientHeight - LBGRAResample.Height;

Sans surprise, ces différentes transitions n'impliquent que la modification du point d'origine de leur dessin.

Voici un écran capté avec la transition GrowTopLeft :

Image non disponible

[Exemple BGRABitmap 35]

Plutôt que de faire varier les deux dimensions de l'image agrandie, il est envisageable de se contenter d'une seule. Nous obtiendrons ainsi un recouvrement de l'image de départ avec l'image de destination en montrant cette dernière comme de moins en moins compressée.

Le code proposé est le suivant :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRAResample: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth  div 100,
        imgResult.ClientHeight) as TBGRABitmap;
        try
          //GrowOverRight
          LX := 0;
          LY := 0;
          LBGRATemp.PutImage(LX, LY, LBGRAResample, dmDrawWithTransparency, Opacity);
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRAResample.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Par rapport aux transitions qui viennent d'être étudiées, comme annoncé, la seule ligne vraiment importante est celle qui concerne le redimensionnement :

 
Sélectionnez
LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth  div 100, imgResult.ClientHeight) as TBGRABitmap;

C'est en effet à ce niveau que la largeur de l'image est modifiée alors que sa hauteur reste constante. La transition dessine l'image de destination sur celle d'origine, de la gauche vers la droite. Elle portera le nom de GrowOverRight :

Image non disponible

Des opérations du même genre peuvent être effectuées à partir des différents bords de l'image à agrandir. Nous pouvons ainsi agrandir l'image vers la gauche en partant du bord droit grâce à la transition GrowOverLeft en ne modifiant qu'une ligne de code :

 
Sélectionnez
LX := imgResult.ClientWidth - LBGRAResample.Width;

Pour une transition GrowOverDown, qui partira du haut de l'image pour aller vers le bas, nous modifierons le code de la façon suivante :

 
Sélectionnez
LBGRAResample := fBGRATo.Resample(imgResult.ClientWidth,
  fStep * imgResult.ClientHeight div 100) as TBGRABitmap;
try
  LX := 0;
  LY := 0;

D'une manière similaire, la transition opposée (GrowOverUp) sera implémentée comme suit :

 
Sélectionnez
LBGRAResample := fBGRATo.Resample(imgResult.ClientWidth,
  fStep * imgResult.ClientHeight div 100) as TBGRABitmap;
try
  LX := 0;
  LY := imgResult.ClientHeight - LBGRAResample.Height;

Comme pour la série de transitions vue précédemment, il est aussi possible d'utiliser la méthode StretchPutImage afin d'éviter la création d'un objet intermédiaire. Voici par exemple le code nécessaire à la transition GrowTopLeft :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // GrowTopLeft
        LX := imgResult.ClientWidth - fStep * imgResult.ClientWidth div 100;
        LY := imgResult.ClientHeight - fStep * imgResult.ClientHeight div 100;
        LBGRATemp.StretchPutImage(Rect(LX, LY, imgResult.ClientWidth,
          imgResult.ClientHeight), fBGRATo, dmDrawWithTransparency,
          Opacity);
        LBGRATemp.Draw(imgResult.Canvas, 0, 0);
        Application.ProcessMessages;
        sleep(100 - fSpeed);
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Voici un résultat intermédiaire de la transition GrowOverUp :

Image non disponible

II-B. Les homothéties combinées à des rotations

La combinaison de rotations et d'homothéties, avec d’éventuelles translations, est elle aussi susceptible de produire des effets remarquables. Nous allons en suggérer quelques-uns ci-après.

En premier lieu, nous pouvons faire tourner l'image de destination tout en l'agrandissant au centre de l'image d'origine. Il s'agit toujours d'une transition avec recouvrement et qui ne demande par conséquent pas de masque.

[Exemple BGRABitmap 36]

Le code suivant est une proposition d'implémentation de GrowCenterCWRotate :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRAResample: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth div 100,
        fStep * imgResult.ClientHeight div 100) as TBGRABitmap;
        try
          LX := imgResult.ClientWidth div 2;
          LY := imgResult.ClientHeight div 2;
          LBGRATemp.PutImageAngle(LX, LY, LBGRAResample,
            fStep * 360 / 100, LBGRAResample.Width div 2, LBGRAResample.Height div 2, Opacity);
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRAResample.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

L'image de destination est tout d'abord redimensionnée avant de se voir appliquer une rotation centrale dans le sens des aiguilles d'une montre. La transition GrowCenterCCWRotate qui tournera dans le sens contraire des aiguilles d'une montre sera obtenue par simple changement de signe de l'angle de rotation :

 
Sélectionnez
LBGRATemp.PutImageAngle(LX, LY, LBGRAResample, -fStep * 360 / 100, LBGRAResample.Width div 2, LBGRAResample.Height div 2, Opacity);

Voici une vidéo qui montre la transition GrowCenterCWRotate en action :

Exemple BGRABitmap 37]

Dans le prolongement de cette approche, nous pouvons affecter différemment les paramètres internes de cette méthode de base. Par exemple, nous pouvons faire varier le point d'application du recouvrement de l'image grâce à une translation et choisir le coin supérieur gauche de l'image pour centre de rotation. La transition obtenue pourra s'appeler GrowCWRotate. En voici le code :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRAResample: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth div 100,
        fStep * imgResult.ClientHeight div 100) as TBGRABitmap;
        try
          LX := (imgResult.ClientWidth - fStep * imgResult.ClientWidth div 100) div 2;
          LY := imgResult.ClientHeight - fStep * imgResult.ClientHeight div 100;
          LBGRATemp.PutImageAngle(LX, LY, LBGRAResample,
            fStep * 360 / 100, 0, 0, Opacity);
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRAResample.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Sa consœur GrowCCWRotate qui fera tourner l'image dans le sens opposé des aiguilles d'une montre n'aura, comme toujours, qu'une différence de signe à apporter à l'angle de rotation. Voici par conséquent l'unique ligne de code qui la distinguera de la précédente :

 
Sélectionnez
LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth div 100, - fStep * imgResult.ClientHeight div 100) as TBGRABitmap;

La vidéo qui suit rend compte de l'intérêt de cette nouvelle transition :

Exemple BGRABitmap 38]

Sans chercher à être exhaustif en matière de variantes, une autre idée serait de n'appliquer l'agrandissement que sur la largeur de l'image et de fixer le centre de rotation au centre de l'image du résultat. Voici le code de la transition que nous baptiserons GrowWidthCWRotate  :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRAResample: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRAResample := fBGRATo.Resample(fStep * imgResult.ClientWidth div 100,
          imgResult.ClientHeight) as TBGRABitmap;
        try
          LX := imgResult.ClientWidth div 2;
          LY := imgResult.ClientHeight div 2;
          LBGRATemp.PutImageAngle(LX, LY, LBGRAResample,
            fStep * 360 / 100, imgResult.ClientWidth div 2,
            imgResult.ClientHeight div 2, Opacity);
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRAResample.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Pas mal, non ? Nous choisirons parmi ces variantes celles que nous voudrons : il ne s'agit pas simplement de multiplier les transitions, mais bien de chercher celles qui seront les plus susceptibles d'intéresser l'utilisateur final du composant à venir !

III. Une unité dédiée à la transformation des images

Comme nous l'indiquions en introduction de ce tutoriel, translations, homothéties et rotations sont aussi utilisables via une unité spécialisée dans les applications affines : BGRATransform.

Cette unité va d'autant plus nous être utile qu'elle simplifie souvent l'écriture du code des transformations vues jusqu'à maintenant. Imaginons par exemple que nous souhaitions produire une transition nommée RotateCWGrowTopLeft qui combinerait trois éléments dans cet ordre :

  • une rotation dans le sens des aiguilles d'une montre de l'image de destination selon un angle donné ;
  • une mise à l'échelle par homothétie de cette même image ;
  • une translation toujours de cette même image lui faisant traverser l'image du résultat, de la droite vers la gauche, et du bas vers le haut.

[Exemple BGRABitmap 39]

Avec les outils dont nous disposons, nous devrions nous en tirer, mais la nouvelle unité proposée simplifiera grandement le code à fournir :

 
Sélectionnez
implementation

uses
  BGRATransform;

{$R *.lfm}

{ TMainForm }

procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LBGRATransform: TBGRAAffineBitmapTransform;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(0, 0, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRATransform := TBGRAAffineBitmapTransform.Create(fBGRATo);
        try
          //LX := 0;
          //LY := 0;
          LBGRATransform.RotateDeg(fStep * 360 / 100);
          LBGRATransform.Scale(fStep / 100);
          LBGRATransform.Translate(imgResult.ClientWidth - fStep *
            imgResult.ClientWidth / 100, imgResult.ClientHeight - fStep   * imgResult.ClientHeight / 100);
          LBGRATemp.Fill(LBGRATransform, dmDrawWithTransparency);
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRATransform.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Nous avons tout d'abord introduit une clause uses afin de pouvoir utiliser l'unité désirée. À la place de l'objet de travail de type TBGRABitmap habituel, nous avons déclaré et créé un objet de la classe TBGRAAffineBitmapTransform. C'est à ce dernier que nous avons appliqué les méthodes qui nous intéressent et que nous allons décrire à présent.

La méthode RotateDeg produit une rotation en degrés. Elle prend un paramètre indiquant des degrés dont les valeurs positives indiquent une rotation dans le sens des aiguilles d'une montre.

Il existe aussi un équivalent avec un paramètre en radians dont les valeurs positives, conformément aux conventions habituelles, indiquent une rotation dans le sens inverse des aiguilles d'une montre.

La méthode Scale met à l'échelle l'image selon le paramètre réel fourni. En fait, elle existe sous deux formes surchargées :

 
Sélectionnez
procedure Scale(sx,sy: single); overload;
procedure Scale(factor: single); overload;

La première forme permet de distinguer les rapports d'homothéties suivant les deux axes possibles alors que la première forme applique uniformément le même rapport.

La méthode Translate déplace l'image affectée par les deux données fournies (respectivement, la largeur et la hauteur).

Une texture est un objet implémentant l'interface IBGRAScanner (par exemple, un objet de type TBGRABitmap ou TBGRAAffineBitmapTransform), qui peut être vu comme une image sans limites dont la surface est couverte d'un motif qui se répète.

La méthode Fill connaît plusieurs définitions surchargées :

 
Sélectionnez
procedure Fill(texture: IBGRAScanner; mode: TDrawMode); override;
procedure Fill(texture: IBGRAScanner); override;
procedure Fill(c: TBGRAPixel; start, Count: integer); override;

Pour rappel, c'est sous une forme simplifiée et issue de l'ancêtre TBGRACustomBitmap que nous l'avons jusqu'à présent utilisée afin d'effacer le contenu d'une surface. C'est sous la première forme du tableau qu'elle nous sera la plus utile dorénavant : elle a besoin d'une texture, donc d'une image transformée, ainsi que d'une indication du type de dessin souhaité (ici, par transparence).

Le code proposé définit RotateGrowCWTopLeft que voici en action :

L'inversion du signe devant fStep lors de la rotation suffit pour définir la transition RotateGrowCCWTopLeft :

 
Sélectionnez
LBGRATransform.RotateDeg(-fStep * 360 / 100);

Comme nous l'avons déjà fait précédemment, nous pourrions jouer avec d'autres paramètres : la largeur ou la hauteur peuvent rester fixes, le redimensionnement peut varier selon une formule plus complexe qu'une simple linéarité, la translation peut suivre une autre direction…

Il serait fastidieux de décrire à chaque fois toutes les possibilités : n'hésitez pas à garder les transitions qui vous paraissent les plus esthétiques, les plus surprenantes ou les plus amusantes ! Quant aux autres, oubliez-les tout simplement !

[Exemple BGRABitmap 40]

L'intérêt de cette unité ne s'arrête pas là. En effet, elle dispose d'autres outils très utiles pour des effets encore plus originaux. Par exemple, TBGRATwirlScanner propose une distorsion de l'image qui, associée à la transformation TBGRAAffineScannerTransform, peut surprendre.

Le code proposé sera le suivant :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp: TBGRABitmap;
  LBGRATransform: TBGRAAffineScannerTransform;
  LTwirl: TBGRATwirlScanner;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
    try
      fStep := 0;
      repeat
        Inc(fStep);
        LX := 0;
        LY := 0;
        LBGRATemp.Fill(BGRABlack);
        LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
        // traitement ici...
        LBGRATransform := TBGRAAffineScannerTransform.Create(fBGRATo);
        try
          //LX := 0;
          //LY := 0;
          LBGRATransform.Scale(fStep / 100);
          LTwirl := TBGRATwirlScanner.Create(LBGRATransform,
            Point(imgResult.ClientWidth div 2, imgResult.ClientHeight div 2), (100 - fStep) *
            ifthen(imgResult.ClientWidth > imgResult.ClientHeight, imgResult.ClientWidth, imgResult.ClientHeight));
          try
            LBGRATemp.Fill(LTwirl, dmDrawWithTransparency);
          finally
            LTwirl.Free;
          end;
          LBGRATemp.Draw(imgResult.Canvas, 0, 0);
          Application.ProcessMessages;
          sleep(100 - fSpeed);
        finally
          LBGRATransform.Free;
        end;
      until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

Nous voyons que la première image de type TBGRAAffineScannerTransform est soumise à une rotation puis qu'elle est soumise à une distorsion grâce à l'objet de type TBGRATwirlScanner. Le résultat est alors appliqué à l'image finale. Comme nous travaillons avec un objet implémentant l'interface IBGRAScanner, l'image sera répétée sur la surface pour la recouvrir totalement, en particulier lorsque le rétrécissement de l'image de travail sera fort.

La fonction ifthen qui est employée pour retourner la plus grande des deux valeurs ClientWidth ou ClientHeight est présente dans l'unité math qui doit par conséquent être ajoutée à la clause uses dans la partie implementation.

Une courte vidéo rend compte de la transition MultiTwirl en action :

Ce genre de transition couvrant d'emblée la totalité de l'espace, l'opacité perd une grande partie de son sens.

IV. Les filtres et les méthodes spéciales

Nous allons dans ce chapitre voir qu'il est possible d'adjoindre des filtres aux opérations étudiées jusqu'à présent ou de les affecter grâce à certaines méthodes non encore vues de la classe TBGRABitmap. Certains de ces nouveaux outils nous intéresseront peu, car ils opèrent en bloc, sans étapes exploitables par notre boucle : ils pourront cependant s'appliquer à l'image d'origine pour des effets spéciaux. D'autres sont adaptables à notre objectif : nous les détaillerons tout particulièrement.

IV-A. Une application de test

[Exemple BGRABitmap 41]

Plutôt que d'expliquer l'action de la série de méthodes en jeu dans ce chapitre, il est sans doute préférable de les voir en action. C'est l'objet de l'application simpliste qui suit : elle ne sera même pas interactive, se contentant d'afficher une série d'images affectées chacune par une méthode différente.

L'interface aligne par conséquent dix-neuf composants TImage. Le premier contiendra l'image de travail, les autres les images obtenues après l'opération. Des composants TLabel afficheront en toutes lettres les effets appliqués.

Voici ce que donne cette interface rudimentaire :

Image non disponible

Le fichier LFM correspondant, débarrassé des données liées à l'image de travail, est celui-ci :

 
Sélectionnez
object MainForm: TMainForm
  Left = 269
  Height = 709
  Top = 192
  Width = 859
  Caption = 'Essai de quelques méthodes de la classe TBGRABimap'
  ClientHeight = 709
  ClientWidth = 859
  OnCreate = FormCreate
  OnDestroy = FormDestroy
  LCLVersion = '1.8.2.0'
  object imgMain: TImage
    Left = 16
    Height = 146
    Top = 16
    Width = 170
    Stretch = True
  end
  object imgNone: TImage
    Left = 16
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblNone: TLabel
    Left = 16
    Height = 15
    Top = 307
    Width = 64
    BorderSpacing.Around = 10
    Caption = 'Pas d''action'
    ParentColor = False
  end
  object imgVerticalFlip: TImage
    Left = 120
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object imgHorizontalFlip: TImage
    Left = 224
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblVerticalFlip: TLabel
    Left = 120
    Height = 15
    Top = 307
    Width = 57
    BorderSpacing.Around = 10
    Caption = 'VerticalFlip'
    ParentColor = False
  end
  object lblHorizontalFlip: TLabel
    Left = 224
    Height = 15
    Top = 307
    Width = 74
    BorderSpacing.Around = 10
    Caption = 'HorizontalFlip'
    ParentColor = False
  end
  object imgNegative: TImage
    Left = 328
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblNegative: TLabel
    Left = 328
    Height = 15
    Top = 307
    Width = 47
    BorderSpacing.Around = 10
    Caption = 'Negative'
    ParentColor = False
  end
  object imgLinearNegative: TImage
    Left = 432
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblLinearNegative: TLabel
    Left = 432
    Height = 15
    Top = 307
    Width = 79
    BorderSpacing.Around = 10
    Caption = 'LinearNegative'
    ParentColor = False
  end
  object imgInplaceGrayscale: TImage
    Left = 536
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblInplaceGrayscale: TLabel
    Left = 536
    Height = 15
    Top = 307
    Width = 88
    BorderSpacing.Around = 10
    Caption = 'InplaceGrayscale'
    ParentColor = False
  end
  object imgSwapRedBlue: TImage
    Left = 640
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblSwapRedBlue: TLabel
    Left = 640
    Height = 15
    Top = 307
    Width = 71
    BorderSpacing.Around = 10
    Caption = 'SwapRedBlue'
    ParentColor = False
  end
  object imgInplaceNormalize: TImage
    Left = 744
    Height = 90
    Top = 200
    Width = 90
    Stretch = True
  end
  object lblInplaceNormalize: TLabel
    Left = 744
    Height = 15
    Top = 307
    Width = 92
    BorderSpacing.Around = 10
    Caption = 'InplaceNormalize'
    ParentColor = False
  end
  object imgPixelate: TImage
    Left = 16
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterPixelate: TLabel
    Left = 16
    Height = 15
    Top = 480
    Width = 66
    Caption = 'FilterPixelate'
    ParentColor = False
  end
  object imgFilterBlurMotion: TImage
    Left = 120
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterBlurMotion: TLabel
    Left = 120
    Height = 15
    Top = 480
    Width = 86
    Caption = 'FilterBlurMotion'
    ParentColor = False
  end
  object imgFilterSmartZoom3: TImage
    Left = 224
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterSmartZoom3: TLabel
    Left = 224
    Height = 15
    Top = 480
    Width = 95
    Caption = 'FilterSmartZoom3'
    ParentColor = False
  end
  object imgFilterMedian: TImage
    Left = 328
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterSmooth: TLabel
    Left = 328
    Height = 15
    Top = 480
    Width = 66
    Caption = 'FilterMedian'
    ParentColor = False
  end
  object imgFilterContour: TImage
    Left = 432
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterContour: TLabel
    Left = 432
    Height = 15
    Top = 480
    Width = 70
    Caption = 'FilterContour'
    ParentColor = False
  end
  object imgFilterEmboss: TImage
    Left = 536
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterEmboss: TLabel
    Left = 536
    Height = 15
    Top = 480
    Width = 67
    Caption = 'FilterEmboss'
    ParentColor = False
  end
  object imgFilterSphere: TImage
    Left = 640
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterSphere: TLabel
    Left = 640
    Height = 15
    Top = 480
    Width = 62
    Caption = 'FilterSphere'
    ParentColor = False
  end
  object imgFilterRotate: TImage
    Left = 744
    Height = 90
    Top = 368
    Width = 90
  end
  object lblFilterRotate: TLabel
    Left = 744
    Height = 15
    Top = 480
    Width = 60
    Caption = 'FilterRotate'
    ParentColor = False
  end
  object imgFilterPlane: TImage
    Left = 16
    Height = 90
    Top = 512
    Width = 90
  end
  object lblFilterPlane: TLabel
    Left = 16
    Height = 15
    Top = 623
    Width = 55
    Caption = 'FilterPlane'
    ParentColor = False
  end
  object imgFilterTwirl: TImage
    Left = 120
    Height = 90
    Top = 512
    Width = 90
  end
  object lblFilterTwirl: TLabel
    Left = 120
    Height = 15
    Top = 623
    Width = 64
    Caption = 'lblFilterTwirl'
    ParentColor = False
  end
end

Le code lié à cette application est lui aussi d'une extrême simplicité. Dans le gestionnaire de création de la fiche principale OnCreate, nous créons l'image de travail et nous lui appliquons successivement chaque filtre pour afficher le résultat dans le composant TImage prévu. La seule difficulté consiste à distinguer les méthodes ordinaires qui sont appelées sans précautions particulières et les filtres qui sont des méthodes qui créent un objet comme le faisait déjà Resample : dans ce dernier cas, il faut prévoir la libération de la ressource de l'image puisqu'elle va être recréée par le filtre.

Voici le code proposé :

 
Sélectionnez
procedure TMainForm.FormCreate(Sender: TObject);
var
  LBmp: TBGRABitmap;
begin
  BGRABmp := TBGRABitmap.Create(imgMain.Picture.Bitmap);
  BGRAReplace(BGRABmp, BGRABmp.Resample(imgNone.ClientWidth, imgNone.ClientHeight));
  BGRABmp.Draw(imgNone.Canvas, 0, 0, True);
  BGRABmp.VerticalFlip;
  BGRABmp.Draw(imgVerticalFlip.Canvas, 0, 0, True);
  BGRABmp.VerticalFlip;
  BGRABmp.HorizontalFlip;
  BGRABmp.Draw(imgHorizontalFlip.Canvas, 0, 0, True);
  BGRABmp.HorizontalFlip;
  BGRABmp.Negative;
  BGRABmp.Draw(imgNegative.Canvas, 0, 0, True);
  BGRABmp.Negative;
  BGRABmp.LinearNegative;
  BGRABmp.Draw(imgLinearNegative.Canvas, 0, 0, True);
  BGRABmp.LinearNegative;
  BGRABmp.SwapRedBlue;
  BGRABmp.Draw(imgSwapRedBlue.Canvas, 0, 0, True);
  BGRABmp.SwapRedBlue;
  BGRABmp.InplaceNormalize;
  BGRABmp.Draw(imgInplaceNormalize.Canvas, 0, 0, True);
  BGRABmp.InplaceNormalize;
  LBmp := BGRABmp.FilterPixelate(8, True) as TBGRABitmap;
  LBmp.Draw(imgPixelate.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterBlurMotion(30, 0, True) as TBGRABitmap;
  LBmp.Draw(imgFilterBlurMotion.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterMedian(moHighSmooth) as TBGRABitmap;
  LBmp.Draw(imgFilterMedian.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterSmartZoom3(moMediumSmooth) as TBGRABitmap;
  LBmp.Draw(imgFilterSmartZoom3.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterContour as TBGRABitmap;
  LBmp.Draw(imgFilterContour.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterEmboss(90, 128) as TBGRABitmap;
  LBmp.Draw(imgFilterEmboss.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterSphere as TBGRABitmap;
  LBmp.Draw(imgFilterSphere.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterRotate(PointF(imgFilterRotate.ClientWidth / 2,
    imgFilterRotate.ClientHeight / 2), 45) as TBGRABitmap;
  LBmp.Draw(imgFilterRotate.Canvas, 0, 0);
  LBmp.Free;
  LBmp := BGRABmp.FilterPlane as TBGRABitmap;
  LBmp.Draw(imgFilterPlane.Canvas, 0, 0);
  Lbmp.Free;
  LBmp := BGRABmp.FilterTwirl(Point(imgFilterTwirl.ClientWidth div 2,
    imgFilterTwirl.ClientHeight div 2), imgFilterTwirl.ClientWidth) as     TBGRABitmap;
  LBmp.Draw(imgFilterTwirl.Canvas, 0, 0);
  LBmp.Free;
  BGRABmp.InplaceGrayscale;
  BGRABmp.Draw(imgInplaceGrayscale.Canvas, 0, 0, True);
end;

Les filtres ont été regroupés vers la fin du gestionnaire. La seule exception est la méthode GrayScale qui est appliquée tout à la fin : elle est en effet irréversible !

L'exécution de cette application produit une fenêtre comme celle qui suit :

Image non disponible

Le choix est large et nous allons en profiter pour y puiser de nouveaux outils !

IV-B. Les effets spéciaux

Comme nous l'avons dit plus haut, parmi les méthodes présentées, certaines ne se prêtent pas facilement à des transitions parce qu'elles s'appliquent en une seule fois. D'autres comme FilterRotate existent déjà sous une forme plus adaptée à notre propos. Une idée à retenir serait de réserver les plus pertinentes d'entre elles à une modification du fond de la transition, c'est-à-dire de l'image d'origine. Nous pouvons par exemple imaginer que l'image d'origine soit immédiatement transformée par la méthode FilterContour afin de dessiner uniquement le contour des objets décelables sur elle. C'est sur cette image transformée que serait dessinée celle de destination.

Nous verrons dans le composant final que cette idée sera développée en proposant une option d'affichage baptisée « effets spéciaux ».

Sans guère nous étendre pour le moment sur cette option, nous noterons qu'elle exploitera les méthodes selon le tableau suivant :

Nom de l'effet

Méthode

Effet obtenu

None

Aucune

Pas d'effet actif

Emboss

FilterEmboss

Gaufrage de l'image (paramétrable, mais avec des différences peu visibles)

Contour

FilterContour

Contour des objets décelables sur l'image

Negative

Negative

Effet de négatif photo

NegativeLinear

NegativeLinear

Effet de négatif photo sans correction de gamma

SwapRedBlue

SwapRedBlue

Inversion du rouge et du bleu

Sphere

FilterSphere

Intégration dans une sphère

Rotate

FilterRotate

Rotation (paramétrable)

GrayScale

InplaceGrayScale

Niveaux de gris

Pixelate

FilterPixelate

Pixellisation de l'image (paramétrable)

Twirl

FilterTwirl

Distorsion par enroulement
(paramétrable)

BlurMotion

FilterBlurMotion

Flou orienté
(paramétrable)

Cette option opérera donc par un prétraitement de l'image d'origine suivi du traitement de l'image de destination elle-même.

IV-C. Les transitions avec filtre

Certaines des méthodes entrevues sont cependant directement exploitables du point de vue qui nous intéresse et peuvent engendrer de nouvelles transitions originales. Nous en verrons plus spécialement deux : FilterPixelate et FilterBlurMotion.

[Exemple BGRABitmap 42]

Nous connaissons déjà la première puisque nous avons suggéré de l'intégrer aux options de prétraitement. Elle peut en fait donner naissance à une transition inédite : il s'agira de faire apparaître en bloc l'image de destination, mais avec une décroissance progressive de la taille des pixels affichés jusqu'au minimum possible.

Voici le code proposé (à noter qu'il n'utilise pas les masques) :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRAPixelate: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    fStep := 0;
    repeat
      LX := 0;
      LY := 0;
      Inc(fStep);
      LBGRATemp.Fill(BGRABlack);
      LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
      // traitement 2 ici (destination)...
      LBGRAPixelate := fBGRATo.Duplicate as TBGRABitmap;
      try
        BGRAReplace(LBGRAPixelate, LBGRAPixelate.FilterPixelate(100 - fStep, True) as TBGRABitmap);
        LBGRATemp.PutImage(LX, LY, LBGRAPixelate, dmDrawWithTransparency, Opacity);
      finally
        LBGRAPixelate.Free;
      end;
      LBGRATemp.Draw(imgResult.Canvas, 0, 0);
      Application.ProcessMessages;
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

La méthode FilterPixelate est déclarée ainsi au cœur de la classe TBGRABitmap :

 
Sélectionnez
function FilterPixelate(pixelSize: integer; useResample: boolean; filter: TResampleFilter = rfLinear): TBGRACustomBitmap; override;

Son paramètre essentiel nommé pixelsize permet de déterminer la taille d'affichage du pixel représenté sous la forme d'un carré dont la longueur du côté est donnée par le nombre de pixels à utiliser. Le paramètre useResample indique si un algorithme de redimensionnement doit être mis en œuvre tandis que le paramètre filter fournit le nom de cet algorithme (par défaut, il vaut rfLinear).

Voici une courte vidéo de cette transition (appelée tout simplement Pixelate) en action :

Dans le même esprit, nous allons construire une transition qui partira d'un flou marqué à une image parfaitement claire.

[Exemple BGRABitmap 43]

Le modèle de code de la transition que nous nommerons BlurMotion reprendra celui de la transition précédente Pixelate :

 
Sélectionnez
procedure TMainForm.btnGoClick(Sender: TObject);
// *** dessin ***
var
  LBGRATemp, LBGRABlurMotion: TBGRABitmap;
  LY, LX: Integer;
begin
  btnGo.Enabled := False;
  LBGRATemp := TBGRABitmap.Create(imgResult.ClientWidth, imgResult.ClientHeight, BGRABlack);
  try
    fStep := 0;
    repeat
      LX := 0;
      LY := 0;
      Inc(fStep);
      LBGRATemp.Fill(BGRABlack);
      LBGRATemp.PutImage(LX, LY, fBGRAFrom, dmSet, Opacity(False));
      // traitement 2 ici (destination)...
      LBGRABlurMotion := fBGRATo.Duplicate as TBGRABitmap;
      try
        BGRAReplace(LBGRABlurMotion, LBGRABlurMotion.FilterBlurMotion(100 - fStep,
          100 - fStep, False));
        LBGRATemp.PutImage(LX, LY, LBGRABlurMotion, dmDrawWithTransparency, Opacity);
      finally
        LBGRABlurMotion.Free;
      end;
      LBGRATemp.Draw(imgResult.Canvas, 0, 0);
      Application.ProcessMessages;
      sleep(100 - fSpeed);
    until fStep = 100;
  finally
    LBGRATemp.Free;
    btnGo.Enabled := True;
  end;
end;

La fonction FilterBlurMotion renvoie elle aussi un objet de type TBGRABitmap, d'où la nécessité de passer par un système assez lourd de destruction d'objet à chaque tour si nous souhaitons gagner en netteté.

La vidéo suivante rend compte du résultat de notre travail :

La lourdeur de réalisation de cette transition s'accompagne d'une lenteur d'exécution qui, à moins de trouver une optimisation du code, fera y réfléchir à deux fois avant de l'intégrer sans adaptation au composant final ! Il ne s'agit en rien d'une critique de l'implémentation de l'effet, mais d'une remarque inhérente à l'effet lui-même.

V. Conclusion

Avec ce tutoriel se termine la découverte des principales transitions qui seront utilisées par le composant final. Dès le prochain épisode, nous poserons les fondations du composant TGVTransition en commençant par les effets d'interpolation, une gestion mieux maîtrisée de l'opacité et de la fluidité de l'affichage, ainsi qu'une implémentation de son squelette.

Mes remerciements vont à Alcatîz, BeanzMaster et circular17 pour leur relecture technique et à f-leb pour la correction orthographique.

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.