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 appelées ASCII Mosaic dans certaines contrées du web. On en distingue cependant trois types principaux :
- celles dont le texte se répète simplement (exemple ci-dessous) ;
- celles dont les caractères sont tous issus d'un random ;
- celles dont le texte est un texte complet.
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 tutoriel.
I-B. Principe de génération▲
Le principe est extrêmement simple :
- Décomposer l'image en carrés de petite taille ;
- 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 composantes (rouge, vert et bleu), il nous suffit d'additionner les composantes de tous les pixels entre elles, et de diviser chaque nombre obtenu par le nombre total de pixels. La couleur moyenne d'une image est en fait composée des moyennes des composantes RGB de tous ces pixels.
Rappels
- En .NET, on utilise la fonction Bitmap.GetPixel(x,y) pour récupérer chaque pixel sous la forme d'un type Color. Le type Color contient les variables R, G et B contenant les valeurs 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 valeurs des composantes RGB.
Voici le code de la fonction en question :
Color AverageColor
(
Bitmap bBitmap)
{
int
tRed =
0
,
tBlue =
0
,
tGreen =
0
;
for
(
int
i=
0
;
i<
bBitmap.
Width ;
i++
)
{
for
(
int
j=
0
;
j<
bBitmap.
Height ;
j++
)
{
tRed +=
bBitmap.
GetPixel
(
i,
j).
R;
tBlue +=
bBitmap.
GetPixel
(
i,
j).
B;
tGreen +=
bBitmap.
GetPixel
(
i,
j).
G;
}
}
int
TotalPixels =
bBitmap.
Width *
bBitmap.
Height;
return
Color.
FromArgb
(
tRed /
TotalPixels,
tGreen /
TotalPixels,
tBlue /
TotalPixels);
}
II-B. Fonction de conversion integer vers hexadécimal▲
N'oublions pas qu'en HTML, les couleurs se codent en héxadécimal. 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 algorithmes 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 grands désespoirs.
Il suffit de créer un tableau contenant toutes les valeurs hexadé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 hexadécimale sera la 26e du tableau, soit HexValues[25];.
Voici le code de la fonction :
string
[]
HexValues =
{
"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"
};
string
RGBColorToHEXColor
(
Color RGB)
{
string
SFinal =
"#"
;
SFinal =
SFinal +
HexValues[
RGB.
R];
SFinal =
SFinal +
HexValues[
RGB.
G];
SFinal =
SFinal +
HexValues[
RGB.
B];
return
SFinal;
}
III. Conception du logiciel▲
III-A. Préparation de l'interface▲
Travaillant en windows forms, l'interface finale ressemblera à celle-ci :
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. Procédures diverses▲
Voici le code pour ouvrir une image dans le PictureBox de la form.
ofdFileDialog1.
Filter =
"Graphics|*.bmp|All|*.*"
;
if
(
ofdFileDialog1.
ShowDialog
(
) ==
System.
Windows.
Forms.
DialogResult.
OK)
PictureBox1.
Image =
Image.
FromFile
(
ofdFileDialog1.
FileName);
De la même manière, on utilise le même OpenFileDialog 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.
ofdFileDialog1.
Filter =
"Text Files|*.txt|All|*.*"
;
if
(
ofdFileDialog1.
ShowDialog
(
) ==
System.
Windows.
Forms.
DialogResult.
OK )
rtbTextFile.
LoadFile
(
ofdFileDialog1.
FileName);
Le code associé au bouton Voir permet d'ouvrir la mosaïque générée dans le navigateur par défaut. En .NET, 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 booléen UseShellExecute permet d'ouvrir un fichier exactement comme si on double cliquait dessus dans l'explorateur.
System.
Diagnostics.
ProcessStartInfo psi =
new
System.
Diagnostics.
ProcessStartInfo
(
);
psi.
UseShellExecute =
true
;
psi.
Verb =
"open"
;
psi.
FileName =
MosaicFileName;
System.
Diagnostics.
Process.
Start
(
psi);
III-C. Conception de l'algorithme▲
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>
<!-- ect... -->
</b>
</font>
</body>
</html>
La propriété line-height permet de réduire la distance entre deux lignes de texte. Le texte est mis 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. À chaque fin de ligne, on rajoute une balise br pour revenir à la ligne.
III-C-2. 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 (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. On prépare également les diverses variables qui serviront plus tard.
if
(
SaveFileDialog1.
ShowDialog
(
) ==
System.
Windows.
Forms.
DialogResult.
Cancel )
return
;
bGenerate.
Enabled =
false
;
ProgressBar1.
Maximum =
PictureBox1.
Image.
Height+
15
;
ProgressBar1.
Value =
0
;
bool
UseRandomChars =
cbMosaicType.
SelectedIndex ==
1
;
System.
IO.
StreamWriter HTMLSL =
new
System.
IO.
StreamWriter
(
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>"
);
Graphics MainGraphics =
Graphics.
FromImage
(
PictureBox1.
Image);
int
IndexInFraz =
0
;
int
AHeight =
0
;
int
AWidth =
0
;
Char CurrentChar;
int
BufferInt;
Rectangle Rect =
new
Rectangle
(
);
System.
Random rRandom =
new
System.
Random
(
);
Dans le cas où on n'est pas en mode « Caractères aléatoires », on remplit le tableau de Char avec le contenu du TextBox (texte simple) ou avec celui du RichTextBox (fichier texte), selon le choix de l'utilisateur.
Char[]
FRZ =
new
Char[
1
];
bool
UseRandomChars =
cbMosaicType.
SelectedIndex ==
1
;
if
(
cbMosaicType.
SelectedIndex ==
0
)
{
tbSimpleText.
Text =
tbSimpleText.
Text.
ToString
(
);
FRZ =
new
Char[
tbSimpleText.
Text.
Length];
tbSimpleText.
Text.
CopyTo
(
0
,
FRZ,
0
,
FRZ.
Length);
}
else
if
(
cbMosaicType.
SelectedIndex ==
2
)
{
string
BufferString =
""
;
for
(
int
i=
0
;
i<
rtbTextFile.
Lines.
Length ;
i++
)
BufferString =
BufferString +
rtbTextFile.
Lines[
i];
FRZ =
new
Char[
BufferString.
Length];
BufferString.
CopyTo
(
0
,
FRZ,
0
,
FRZ.
Length);
}
III-C-3. Génération de la mosaïque▲
L'histoire se passe dans une double boucle while, se terminant une fois que les bords de l'image ont été dépassés.
while
(
AHeight <
PictureBox1.
Image.
Width)
{
AWidth =
0
;
while
(
AWidth <
PictureBox1.
Image.
Width)
{
//reste de la procedure
AWidth +=
Rect.
Width;
Application.
DoEvents
(
);
}
HTMLSL.
WriteLine
(
"<br/>"
);
AHeight +=
Rect.
Height-
4
;
ProgressBar1.
Value +=
Rect.
Height -
4
;
}
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 du caractère en cours (et on n'oublie pas de la remettre à zéro quand il arrive à la fin du tableau).
if
(!
UseRandomChars)
{
IndexInFraz++;
if
(
IndexInFraz ==
FRZ.
Length)
IndexInFraz =
0
;
CurrentChar =
FRZ[
IndexInFraz];
}
else
{
BufferInt =
rRandom.
Next
(
64
,
91
);
CurrentChar =
Convert.
ToChar
(
BufferInt);
}
Voilà le cœur de la fonction. Premièrement, on mesure la taille du caractère en cours sur l'image (en fonction de la fonte à utiliser). On remplit ensuite le Rectangle avec les coordonné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.
SizeF BufferSize =
MainGraphics.
MeasureString
(
CurrentChar.
ToString
(
),
FontDialog1.
Font);
Rect =
new
Rectangle
(
AWidth,
AHeight,
Convert.
ToInt32
(
BufferSize.
Width),
Convert.
ToInt32
(
BufferSize.
Height));
Bitmap BufferBitmap =
new
Bitmap
(
Rect.
Width,
Rect.
Height);
Graphics BufferGraphic2 =
Graphics.
FromImage
(
BufferBitmap);
BufferGraphic2.
DrawImage
(
PictureBox1.
Image,
0
,
0
,
Rect,
GraphicsUnit.
Pixel);
Color BufferColor =
AverageColor
(
BufferBitmap);
Finalement on écrit le caractère dans le fichier HTML.
HTMLSL.
Write
(
"<font color=
\"
"
+
RGBColorToHEXColor
(
BufferColor)+
"
\"
>"
+
CurrentChar+
"</font>"
);
III-C-4. Finalisation▲
Pour finir, on ferme les balises 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 :
IV-B. Améliorations possibles▲
Voici une liste d'améliorations possibles 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 qu'à être amélioré. Il permet aussi de se familiariser avec le C# tout en s'amusant un peu.
V. Divers▲
V-A. Téléchargements▲
Les sources du logiciel (zip) : TextMosaic_SRC.zip
L'exe de l'application : TextMosaic.exe
Les Fichiers HTML des exemples :
V-B. Crédits▲
Article écrit par ZeWaren.
Pour tout contact :
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.