Création d'un programme de génération de mosaïque de texte en dotNET - Delphi.NET
Date de publication : 24/08/2005 ,
Date de mise a jour : 24/08/2005
Par
ZeWaren
Voici la création pas à pas d'un programme de création de mosaïque de texte en Delphi pour dotNET (Windows Forms).
I. Mosaïque de texte ?
I.A. Description
I.B. Principe de génération
II. Préparation des fonctions
II.A. Fonction de couleur moyenne
II.B. Fonction de conversion integer vers hexadécimal
III. Conception du logiciel
III.A. Préparation de l'interface
III.B. Procedures Diverses
III.C. Conception de l'algorythme
III.C.1. Structure du HTML Final
III.C.2. Variables de la procedure
III.C.3. Préparation
III.C.4. Génération de la mosaïque
III.C.3. Finalisation
IV. Exemples et Améliorations
IV.A. Exemples
IV.B. Améliorations possibles
IV.C. Conclusion
V. Divers
V.A. Téléchargements
V.B. Crédits
I. Mosaïque de texte ?
I.A. Description
Une mosaïque de texte (text mosaic en anglais) est en fait simplement une image composée de texte.
C'est un effet décoratif et amusant. Les mosaïques de texte sont parfois aussi apppellées ASCII Mosaic dans
certaines contrées du web. On en distinque cependant trois type principaux:
- Celles dont le texte se repète simplement (exemple ci dessous).
- Celles dont les caractères sont tous issus d'un random.
- Celles dont le texte est un texte complet.
 Exemple de mosaïque de texte
Dans la plupart des cas, le format de prédilection pour exporter des mosaïques de texte est le HTML. C'est ce
format que générera le logiciel final de ce tutorial.
I.B. Principe de génération
Le principe est extrêmement simple:
- Décomposer l'image en carrés de petites tailles.
- Calculer la couleur moyenne de chaque carré.
- Ajouter au document final un caractère avec la couleur calculée.
II. Préparation des fonctions
II.A. Fonction de couleur moyenne
Afin d'obtenir la couleur moyenne de chaque carré, nous devons d'abord créer une fonction
calculant la couleur moyenne d'un bitmap. Chaque pixel étant composé de trois composante (rouge, vert et bleu),
il nous suffit d'additionner les composantes de tous les pixels entres elles, et de diviser chaque nombre obtenus
par le nombre total de pixel. La couleur moyenne d'une image est en fait composée des moyennes des composantes RGB
de tous ces pixels. Rappels:
- En dotNET, on utilise la fonction Bitmap.GetPixel(x,y) pour récuperer chaque pixel sous la forme d'un type Color.
Le type Color contient les variables R, G et B contenant les valeures des composantes
rouge, verte et bleue du pixel.
- Pour créer directement un Color, on utilise la fonction Color.FromARGB à laquelle on
passe en paramètres les valeures des composantes RGB.
Voici le code de la fonction en question :
function AverageColor(Bitmap : Bitmap): Color;
var
i, j : word;
tRed, tBlue, tGreen : int64;
TotalPixels : int64;
begin
tRed := 0; tBlue := 0; tGreen := 0;
for i := 0 to Bitmap.Width - 1 do
for j := 0 to Bitmap.Height - 1 do
begin
tRed := tRed + Bitmap.GetPixel(i,j).R;
tBlue := tBlue + Bitmap.GetPixel(i,j).B;
tGreen := tGreen + Bitmap.GetPixel(i,j).G;
end;
TotalPixels := Bitmap.Width * Bitmap.Height;
Result := Color.FromArgb(tRed div TotalPixels, tGreen div TotalPixels, tBlue div TotalPixels);
end;
II.B. Fonction de conversion integer vers hexadécimal
N'oublions pas qu'en HTML, les couleurs se code en héxadécimale. Et comme on ne connait pas la fonction
pour convertir un integer en un string le représentant en hexa, il va nous falloir nous écrire une petite fonction.
Il existe certainement des algorythmes plus compliqués pour cette opération, mais le principe suivant est bon
à connaitre car il peut s'avérer très utile dans les moments de grand désespoirs.
Il suffit simplement de créer un tableau contenant toutes les valeures héxadécimales de 0 à 255 (avec une
bonne boucle for c'est vite fait), et d'assigner à chaque integer la valeur du tableau correspondant à son
contenu. Exemple: pour un integer contenant la valeur 25, sa valeur héxadécimale sera la 26ème du tableau, soit
HexValues[25];
Voici le code de la fonction:
HexValues : array [0..255] of string =
('00','01','02','03','04','05','06','07','08','09','0A','0B','0C','0D','0E','0F',
'10','11','12','13','14','15','16','17','18','19','1A','1B','1C','1D','1E','1F',
'20','21','22','23','24','25','26','27','28','29','2A','2B','2C','2D','2E','2F',
'30','31','32','33','34','35','36','37','38','39','3A','3B','3C','3D','3E','3F',
'40','41','42','43','44','45','46','47','48','49','4A','4B','4C','4D','4E','4F',
'50','51','52','53','54','55','56','57','58','59','5A','5B','5C','5D','5E','5F',
'60','61','62','63','64','65','66','67','68','69','6A','6B','6C','6D','6E','6F',
'70','71','72','73','74','75','76','77','78','79','7A','7B','7C','7D','7E','7F',
'80','81','82','83','84','85','86','87','88','89','8A','8B','8C','8D','8E','8F',
'90','91','92','93','94','95','96','97','98','99','9A','9B','9C','9D','9E','9F',
'A0','A1','A2','A3','A4','A5','A6','A7','A8','A9','AA','AB','AC','AD','AE','AF',
'B0','B1','B2','B3','B4','B5','B6','B7','B8','B9','BA','BB','BC','BD','BE','BF',
'C0','C1','C2','C3','C4','C5','C6','C7','C8','C9','CA','CB','CC','CD','CE','CF',
'D0','D1','D2','D3','D4','D5','D6','D7','D8','D9','DA','DB','DC','DD','DE','DF',
'E0','E1','E2','E3','E4','E5','E6','E7','E8','E9','EA','EB','EC','ED','EE','EF',
'F0','F1','F2','F3','F4','F5','F6','F7','F8','F9','FA','FB','FC','FD','FE','FF');
function RGBColorToHEXColor(RGB : Color) : string;
var
sFinal : string;
begin
sFinal := '#';
sFinal := sFinal + HexValues[RGB.R];
sFinal := sFinal + HexValues[RGB.G];
sFinal := sFinal + HexValues[RGB.B];
Result := sFinal;
end;
III. Conception du logiciel
III.A. Préparation de l'interface
Travaillant en windows forms, l'interface finale ressemblera à celle ci:
 Interface Finale de l'Application
Soit:
- 4 Button (System.Windows.Forms.Button)
- 3 Label (System.Windows.Forms.Label)
- 1 ComboBox (System.Windows.Forms.ComboBox)
- 1 TextBox (System.Windows.Forms.TextBox)
- 1 RichTextBox (System.Windows.Forms.RichTextBox)
- 1 PictureBox (System.Windows.Forms.PictureBox)
- 1 ProgressBar (System.Windows.Forms.ProgressBar)
- 2 GroupBox (System.Windows.Forms.GroupBox)(facultatif)
C'est une interface très simple; rien ne vous empêche d'aller plus loin et de l'améliorer.
III.B. Procedures Diverses
Voici le code pour ouvrir une image dans le PictureBox de la form. On sentira une nette différence par
rapport à un code Delphi pour Win32, dans la forme du code (if OpenDialog1.Execute then Image1.Picture.LoadFromFile(FileName);).
OpenFileDialog1.Filter := 'Graphics|*.bmp;*.jpg;*.gif;*.png|All|*.*';
if OpenFileDialog1.ShowDialog = System.Windows.Forms.DialogResult.OK then
PictureBox1.Image := Image.FromFile(OpenFileDialog1.FileName);
De la même manière, on utilise le mêmeOpenFileDialog pour charger un fichier texte dans
le RichTextBox. Assigner les Filters au début de chaque procédure permet de n'utiliser qu'un seul
OpenFileDialog pour les deux opérations.
OpenFileDialog1.Filter := 'Text Files|*.txt|All|*.*';
if OpenFileDialog1.ShowDialog = System.Windows.Forms.DialogResult.OK then
rtbTextFile.LoadFile(OpenFileDialog1.Filename);
Le bouton Voir permet d'ouvrir directement la mosaïque générée dans le navigateur par default.
En dotNET, la fonction ShellExecute disparait, pour laisser place à un nouveau type: ProcessStartInfo,
que l'on passe en paramètre de la fonction System.Diagnostics.Process.Start une fois rempli. Le boolean
UseShellExecute permet d'ouvrir un fichier exactement comme si on double cliquait dessus dans l'explorateur.
var
psi : ProcessStartInfo;
begin
psi := ProcessStartInfo.Create;
psi.UseShellExecute := true;
psi.Verb := 'open';
psi.FileName := MosaicFileName;
MessageBox.Show(MosaicFileName);
System.Diagnostics.Process.Start(psi);
end;
III.C. Conception de l'algorythme
III.C.1. Structure du HTML Final
Le fichier HTML final ressemble à ceci :
<html>
<head>
<title>Titre</title>
</head>
<body style="line-height: 0.7" bgcolor="black">
<basefont face="Nom de Police" size="1">
<b>
<font color="#A6C5DF">A</font><font color="#AAC7E1">B</font><font color="#00C756">C</font><br>
</b>
</font>
</body>
</html>
La proprieté line-height permet de reduire la distance entre
deux lignes de texte. Le texte est mise en gras pour une meilleure visibilité de l'image.
On ajoute ensuite un à un les caractères en choisissant leur couleur avec une balise font. A
chaque fin de ligne on rajoute une balise br pour revenir à la ligne.
III.C.2. Variables de la procedure
Voici la liste des variables utilisées dans la procedure, ainsi que leurs descriptions.
var
FRZ : array of Char;
i : byte;
HTMLSL: StreamWriter;
AWidth, AHeight : word;
IndexInFraz : byte;
BufferBitmap : Bitmap;
BufferGraphic2 : Graphics;
BufferColor : Color;
Rect : Rectangle;
MainGraphic : Graphics;
BufferSize : SizeF;
BufferString : string;
BufferInt : word;
UseRandomChars : boolean;
CurrentChar : Char;
III.C.3. Préparation
On affiche d'abord la fenêtre de demande d'enregistrement afin que l'utilisateur choisisse son fichier cible,
puis on désactive le bouton de génération (pour ne pas pouvoir cliquer deux fois
dessus en même temps). On prépare aussi la ProgressBar et la variable indiquant si on
se trouve dans le mode "caractères aléatoires" (soit la deuxième position de la ComboBox).
On prépare aussi le fichier HTML en lui ajoutant ses entêtes, et on assigne à la variable MainGraphic
le contenu du PictureBox.
if SaveFileDialog1.ShowDialog = System.Windows.Forms.DialogResult.Cancel then
Exit;
bGenerate.Enabled := false;
ProgressBar1.Maximum := PictureBox1.Image.Height+15;
ProgressBar1.Value := 0;
UseRandomChars := cbMosaicType.SelectedIndex = 1;
HTMLSL := StreamWriter.Create(SaveFileDialog1.FileName);
HTMLSL.WriteLine('<html><head><title>dotNET Text Mosaic</title></head><body style="line-height: 0.7" bgcolor="black">'
+'<basefont face="'+FontDialog1.Font.Name+'" size="1"><b>');
MainGraphic := Graphics.FromImage(PictureBox1.Image);
Dans le cas où on n'est pas en mode "Caractères Aléatoires", on rempli le tableau de Char avec
le contenu du TextBox (Texte Simple) ou avec celui du RichTextBox (Fichier Texte), selon le choix de l'utilisateur.
if cbMosaicType.SelectedIndex = 0 then
begin
tbSimpleText.Text := tbSimpleText.Text.ToString;
setlength(FRZ, length(tbSimpleText.Text));
tbSimpleText.Text.CopyTo(0, FRZ, 0, length(FRZ));
end
else if cbMosaicType.SelectedIndex = 2 then
begin
BufferString := '';
for i := 0 to length(rtbTextFile.Lines) - 1 do
BufferString := BufferString + rtbTextFile.lines[i];
setlength(FRZ, Length(BufferString));
BufferString.CopyTo(0, FRZ, 0, length(FRZ));
end;
III.C.4. Génération de la mosaïque
L'histoire se passe dans un double repeat/until, se terminant une fois que les bords de l'images ont été
dépassés.
IndexInFraz := 0;
AHeight := 0;
repeat
AWidth := 0;
repeat
AWidth := AWidth + Rect.Width;
Application.DoEvents;
until AWidth > PictureBox1.Image.Width;
HTMLSL.WriteLine('<br>');
AHeight := AHeight + Rect.Height-4;
ProgressBar1.Value := ProgressBar1.Value + Rect.Height-4;
until AHeight > PictureBox1.Image.Height;
Rappel, en dotNET, la fonction Application.DoEvents équivaut à Application.ProcessMessages.
La variable rect contient la taille du caractère en cours (en pixel).
Premièrement, on choisit le caractère à afficher. Si c'est un caractère aléatoire, on utilise la fonction
Random pour générer un nombre entre 65 et 91 (ce qui correspond aux lettres majuscules en ASCII), que l'on
convertit en un Char par la fonction Convert.ToChar(). Sinon, on prend le caractère dans le tableau
FRZ[] et on incrémente la variable IndexInFraz qui correspond à l'index en cours (et on n'oublie
pas de la remettre à zero quand il arrive à la fin du tableau).
if not UseRandomChars then
begin
inc(indexinfraz);
if indexinfraz = length(FRZ) then
indexinfraz := 0;
CurrentChar := FRZ[IndexInFraz];
end
else
begin
repeat
BufferInt := Random(91);
until (BufferInt > 64);
CurrentChar := Convert.ToChar(BufferInt);
end;
Voila le coeur de la fonction. Premièrement, on mesure la taille du caractère en
cours sur l'image (en fonction de la fonte à utiliser). On rempli ensuite le
Rectangle avec les coordonées de la partie de l'image dont on veut la couleur moyenne.
On créer alors un Bitmap, associé à un Graphics, sur lequel on peint la
partie de l'image (à l'aide de la fonction Graphics.DrawImage()), avant de calculer sa couleur moyenne.
BufferSize := MainGraphic.MeasureString(CurrentChar, FontDialog1.Font);
Rect.X := AWidth; Rect.Y := AHeight;
Rect.Width := Round(BufferSize.Width);
Rect.Height := Round(BufferSize.Height);
BufferBitmap := Bitmap.Create(Rect.Width, Rect.height, PixelFormat.Format24bppRgb);
BufferGraphic := Graphics.FromImage(BufferBitmap);
BufferGraphic.DrawImage(PictureBox1.Image, 0,0, Rect, GraphicsUnit.Pixel);
BufferColor := averagecolor(BufferBitmap);
Finalement on écrit le caractère dans le fichier HTML.
HTMLSL.Write('<font color="'+RGBColorToHEXColor(BufferColor)+'">'+CurrentChar+'</font>');
III.C.3. Finalisation
Pour finir, on ferme les balise HTML du fichier, avant de le fermer. On réactive les boutons
et on associe le nom de fichier de la mosaïque dans la variable du même nom (pour le bouton Voir).
HTMLSL.WriteLine('</b></font></body></html>');
HTMLSL.Close;
bGenerate.Enabled := true;
bSeeMosaic.Enabled := true;
MosaicFileName := SaveFileDialog1.FileName;
IV. Exemples et Améliorations
IV.A. Exemples
Voici quelques exemples de mosaïques générées avec ce logiciel:
 Developpez.com
 Earth is great
IV.B. Améliorations possibles
Voici une liste d'améliorations possible pour ce logiciel.
- Permettre à l'utilisateur de choisir la couleur de fond.
- Inclure la gestion des fichiers JPEG et autres
- Inclure les autres caractères dans la génération aléatoire (pas seulement les lettres majuscules)
Dans tous les cas, un grand nombre d'améliorations est possible. N'hésitez pas à utiliser ce code
dans vos applications.
IV.C. Conclusion
C'est un programme tout simple qui ne demande à être améliorer. Il permet aussi de se familiariser
avec le language delphi pour dotNET tout en s'amusant un peu.
V. Divers
V.A. Téléchargements
V.B. Crédits
Article écrit par ZeWaren.
Pour tout contact : main@fzwte.net
N'hésitez pas à visiter mon site: fzwte.net
J'espère que cet article vous a plu, et qu'il vous a servi à quelque chose. N'hésitez pas à me contacter si
vous trouvez une erreur ou si vous avez quoi que ce soit qui pourrait améliorer cet article.
Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
|