Génération de bargraphes en langage C
Génération d'une série de bargraphes
Le fichier
gen-serie-bargraphes.c utilise la définition de
quelques constantes :
// constantes
#include "bargraphe.h"
qui définissent des dimensions des bargraphes en pixels ainsi que le
nombre de couleurs qu'ils peuvent contenir.
// dimensions des bargraphes en pixels
#define larg_bgraph 500
#define haut_bgraph 50
// nombre de couleurs de la palette
#define coul_bgraph 4
Il fait aussi appel aux deux bibliothèques de fonctions décrites ci-dessus
et à la fonction qui permettra de générer l'un des bargraphes.
// bibliothèques de fonctions
#include "trtchamps.h"
#include "entete-bmp.h"
#include "genbargraphe.h"
Pour fonctionner, le programme
gen-serie-bargraphes qui est
lancé depuis PHP a besoin de 2 paramètres :
- le nom du fichier CSV local à lire,
- le préfixe des noms d'images bmp à générer.
Il commence donc par vérifier s'il dispose bien de 2 paramètres et dans
ce cas, tente d'ouvrir le fichier CSV en lecture.
// programme principal : récupère les données d'un fichier csv et crée les images bmp
void main (int narg, char *varg [])
{
FILE *desccsv; // descripteur du fichier csv local
char champ [6]; // champ récupéré dans le fichier csv
char imgbmp [30]; // nom d'une image bmp
int i; // compteur
float taux [coul_bgraph - 1]; // taux de vaccination pour chaque dose
// si appel avec le bon nombre de paramètres
if (--narg == 2)
{
// ouvrir le fichier csv en lecture
desccsv = fopen (varg [1], "r");
// si l'ouverture s'est bien passée
if (desccsv)
{
// traitement pour générer les bargraphes
(...)
// terminé avec le fichier csv
fclose (desccsv);
}
// sinon
else
// message d'erreur
fprintf (stderr, "Fichier %s inaccessible ou inexistant\n", varg [1]);
}
// sinon
else
// autre message d'erreur
fprintf (stderr, "Syntaxe : %s fichier_csv préfixe_images_bmp\n", *varg);
}
Si tout s'est bien passé jusque là :
- on initialise l'entête qu'on mettra au début des fichiers bmp.
Comme tous les bargraphes auront la même entête, il suffit de
le faire une fois au début,
- on saute alors la première ligne du fichier CSV local qui
contient juste une date,
- on lit et récupère le premier champ de la ligne suivante,
- tant que le champ lu n'est pas vide, on traite les données de
la ligne courante, puis on essaie de lire le premier champ de
la ligne suivante.
// initialiser l'entête pour les fichier bmp
gen_entete_bmp (larg_bgraph, haut_bgraph, coul_bgraph);
// sauter la première ligne du fichier csv
finligne (desccsv);
// récupérer le début du premier champ de la ligne suivante
recupchamp (desccsv, champ);
// tant que non fin de fichier csv
while (*champ)
{
// traitement de la ligne lue
(...)
// récupérer le début du premier champ de la ligne suivante
recupchamp (desccsv, champ);
}
Pour les fichiers CSV locaux relatifs à l'âge des vaccinés, le premier
champ récupéré est utilisable tel quel. En revanche, pour les bargraphes
par région ou par département d'outre-mer, le nom de l'endroit concerné
est exprimé en clair dans le fichier CSV. On préfère toutefois condenser
ce nom en ne gardant que 3 caractères minuscules.
Le premier caractère du nom de région ou de DOM étant en majuscule,
on le passe en minuscule par une opération sur les bits du
caractère :
// si champ alphabétique
if (*champ > '9')
{
// passer le premier caractère en minuscules
champ [0] = champ [0] | 0x20;
On aurait pu aussi utiliser la fonction
tolower () pour ce
traitement.
Ensuite, il y a 2 cas particuliers à traiter :
Si le nom d'un DOM commence par
st (Saint-...), à la place des
caractères 2 et 3, soit
t- , on met les caractères 4 et 5 en
passant le 4ème caractère en minuscules. Ainsi, par exemple, le nom
St-Martin va donner le code
sma .
// si le nom du champ commence par "st"
if (champ [0] == 's' && champ [1] == 't')
{
// prendre 2 autres caractères significatifs plus loin
champ [1] = champ [3] | 0x20;
champ [2] = champ [4];
}
Autre cas particulier, le deuxième caractère du nom de région est un
é . Comme ce site web utilise l'encodage de caractères UTF-8
qui est devenu un standard mondial, les caractères accentués des langues
européennes sont codés sur 2 octets. On remplacera ce caractère par un
e et il faudra récupérer le caractère qui le suit encore un
octet plus loin.
// sinon si le 2ème caractère est accentué
else if (champ [1] & 0x80)
{
// le mettre en minuscules
champ [1] = 'e';
// et récupérer le caractère suivant
champ [2] = champ [3];
}
Enfin, on va tronquer ce champ à 3 caractères en mettant le caractère
de fin de chaine (du langage C, mais pas que lui) juste après.
// champ alphabétique limité à 3 caractères
champ [3] = '\0';
À présent, entre le champ 1 du fichier CSV que l'on vient de traiter et
le préfixe des noms de bargraphes passé en paramètre au lancement du
programme (
varg [2]), on a toutes les informations nécessaires
pour définir le nom du bargraphe qui représentera les taux de vaccination
pour le ligne courante du fichier CSV.
// fabriquer le chemin d'accès au fichier image
sprintf (imgbmp, "images/%s-%s.bmp", varg [2], champ);
Dans la ligne courante du fichier CSV, les champs qui suivent concernent
l'effectif de population qui répond au critère du champ 1 et pour chacune
des doses de vaccin, le nombre de doses injectées. Au total, autant de
champs dont on n'a pas besoin que de couleurs dans le bargraphe.
On choisit d'ignorer ces champs.
// sauter les champs relatifs à la population totale
// et au nombre d'injections de chaque dose
// soit autant de champs que de couleurs dans le bargraphe
for (i = 0; i < coul_bgraph; i++)
sautechamp (desccsv);
Les derniers champs de la ligne du fichier CSV concernent les taux de
vaccination pour chacune des doses (soit un champ de moins que de
couleurs dans le bargraphe, la dernière couleur étant pour les non
vaccinés).
On va récupérer et mémoriser ces taux. Toutefois, c'est pour la dernière
dose qu'il y aura eu le moins d'injections, suivie de l'avant dernière
dose, et ainsi de suite jusqu'à la première dose qui est celle qu'aura
reçue le plus grand nombre de personnes.
Pour pouvoir construire le bargraphe, on va mémoriser les taux de
vaccination par valeurs croissantes, c'est à dire dans l'ordre
décroissant des numéros de dose.
Comme en langage C (et dans beaucoup de langage de programmation), le
numéro de la première case d'un tableau (qui correspondra à la dernière
dose) est 0, le numéro pour la première dose sera le nombre de doses
- 1.
// une dose de vaccin de moins que de couleurs dans le bargraphe
i = coul_bgraph - 1;
// on va récupérer les taux de vaccination et les mémoriser
// de la dernière dos à la première (ordre croissant des taux)
// tant qu'on n'a pas récupéré tous les taux de vaccination
while (i-- > 0)
{
// récupérer un taux de vaccination
recupchamp (desccsv, champ);
// le mémoriser
taux [i] = atof (champ);
}
À présent, on peut demander la création du bargraphe.
// générer l'image du barregraphe
cree_bargraphe (imgbmp, taux);
Maintenant, on lit le premier champ de la ligne suivante du fichier CSV
local
// récupérer le début du premier champ de la ligne suivante
recupchamp (desccsv, champ);
et un reprend le même traitement jusqu'à ce que toutes les lignes du
fichier CSV local aient été exploitées.
Génération d'une image bmp représentant un bargraphe
La fonction qui permet de générer un bargraphe utilise un grand nombre
de variables locales :
// position du dernier pixel pour chaque couleur dans la ligne
int pix_dose [coul_bgraph];
char *ligne; // une ligne de l'image (2 pixels par octet)
int lg_ligne; // le nombre d'octets qu'elle nécessite
int manque; // pour compléter la ligne à un multiple de 4 octets
int pos_ligne; // position dans la ligne en cous de remplissage
int fin_coul; // pour compter par groupe de 2 pixels
float coefpix; // nombre de pixels pour 1 %
int couleur; // numéro de la couleur qu'on rajoute
char d_pix; // octet avec les valeurs de 2 couleurs consécutives
int num_ligne; // compteur de lignes
FILE *descfic; // descripteur du fichier bmp
En plus, pour les images bmp comportant jusqu'à 256 couleurs, il faut
définir quelles couleurs sont utilisée parmi 256
3 =
2
24 couleurs possibles. Ainsi, pour chaque pixel de l'image,
il suffira d'indiquer un numéro de couleur.
Dans le fichier BMP, chaque élément de la palette de couleurs est composé
de 4 octets :
- l'intensité de bleu : de 0 à 255 soit de 00 à FF en
hexadécimal,
- l'intensité de vert exprimé de la même façon,
- l'intensité de rouge,
- un 4e octet à 0.
Une valeur sur 4 octets peut être mémorisée dans un entier 32 bits.
Toutefois, avec les microprocesseurs de type Intel, qui équipent les PC,
les valeurs numérique sont stockées en mémoire de l'octet de poids le
plus faible, à l'octet de poids le plus fort.
Ainsi, pour exprimer une couleur qui sera mémorisée dans un entier de 4
octets, on peut écrire un nombre hexadécimal de 6 caractères dans lequel
les composantes colorées apparaîtront dans l'ordre rouge, vert, bleu.
Cet ordre est aussi celui du codage hexadécimal des couleurs dans les
fichiers html.
Pour les bargraphes, on utilisera la palette de couleur suivante :
// palette des couleurs (violet, bleu, bleu vert, vert, jaune d'or, orange, rouge)
static long palette [] = {0x6000FF, 0x2020FF, 0x0080FF, 0x00E000,
0xFFE000, 0xFF8000, 0xFF0000};
Comme toutes les lignes du bargraphe seront identiques, on va en préparer
une et la dupliquer sur toute la hauteur du bargraphe.
On commence par déterminer combien d'octets seront nécessaires pour
mémoriser une ligne, sachant que pour une image comportant de 3 à 16
couleurs, on peut mémoriser les numéros de couleur dans la palette à
raison de 2 pixels consécutifs par octet. Par contre, une ligne de
l'image devra occuper un nombre d'octets multiple de 4.
Le nombre d'octets de la ligne étant calculé, on peut allouer en mémoire
un tableau d'octets pour mémoriser son contenu.
// calculer le nombre d'octets de la ligne mémorisée
lg_ligne = larg_bgraph / 2;
manque = lg_ligne % 4;
if (manque)
lg_ligne = lg_ligne + 4 - manque;
// allouer la mémoire pour une ligne de l'image
ligne = malloc (lg_ligne);
Ensuite, pour chaque couleur du bargraphe, on va calculer la position du
pixel le plus à droite.
On a besoin de calculer le coefficient de conversion entre les taux de
vaccination et le nombre de pixels dans le bargraphe. Or, un taux de
100 % correspond à la largeur du bargraphe
La dernière couleur (correspondant aux non vaccinés) permettra
d'atteindre ces 100 % dans le bargraphe.
Pour les autres couleurs du bargraphe, on part des taux de vaccination
que la fonction
cree_bargraphe (...) a récupérés.
// calcul du nombre de pixels pour 1 % dans le bargraphe
coefpix = larg_bgraph / 100.0;
// dernière couleur correspondant à "non vacciné"
couleur = coul_bgraph - 1;
// le dernier pixel pour cette couleur correspondra à 100 %
pix_dose [couleur] = larg_bgraph;
// tant qu'on n'a pas traité toutes les couleurs du bargraphe
while (couleur)
{
// remonter d'une couleur (ce qui revient à traiter la dose suivante)
couleur --;
// dernier pixel de la couleur pour la dose concernée
pix_dose [couleur] = (coefpix * taux [couleur]) + 0.5;
}
À présent, on va remplir le tableau d'octets qui correspond au contenu
d'une ligne du bargraphe. La difficulté est que chaque octet mémorisé
va correspondre à 2 pixels successifs.
On démarre dans la partie gauche de la ligne avec la première couleur
qui correspond à la dernière dose de vaccin et on va faire le même
traitement pour toutes les couleurs du bargraphe.
// initialisations
couleur = 0; // couleur de la dernière dose
pos_ligne = 0; // début de la ligne
// tant qu'on n'a pas traité toute les couleurs du bargraphe
while (couleur < coul_bgraph)
{
(...)
}
Dans un bargraphe, le plus probable est que 2 pixels consécutifs soient
de la même couleur. On va donc remplir la ligne en traitant les pixels
deux par deux.
On commence par préparer un octet
d_pix qui va contenir les
codes de couleur pour 2 pixels de couleur identique. En numérotation
hexadécimale, le contenu de
d_pix pour les différentes couleurs
possibles sera 00h puis 11h, 22h, 33h, ... (si c'était utile, on pourrait
aller jusqu'à FFh).
On va remplir la ligne avec l'octet préparé tant que 2 pixels consécutifs
sont de cette couleur.
// code de couleur pour 2 pixels consécutifs de la couleur courante
d_pix = couleur * 0x11;
// position du dernier couple de pixels de cette couleur
fin_coul = pix_dose [couleur] / 2;
// tant qu'on n'a pas traité tous les couples de pixels de cette couleur
while (pos_ligne < fin_coul)
// colorer 2 pixels dans la ligne
ligne [pos_ligne ++] = d_pix;
Lorsqu'on a fini, deux cas peuvent se présenter :
- Les 2 pixels qui suivent ont 2 couleurs différentes.
- Les 2 pixels qui suivent sont de la même couleur.
On distingue ces 2 cas en cherchant si le rang du dernier pixel de la
couleur qu'on est en train de traiter est impair ou pair.
Dans le premier cas, le plus souvent, le pixel qui suit est de la
couleur suivante dans la liste des couleurs de la palette. On pourrait
traiter simplement ce cas en rajoutant 1 à la variable
d_pix.
Mais il arrive qu'une couleur du bargraphe doive être sautée. Par
exemple, à partir de février 2022, le nombre de personnes de 70 à 74 ans
ayant reçu au moins une dose de vaccin est tellement proche du nombre de
personnes qui en ont reçu exactement deux doses, qu'il n'est pas possible
de les différencier dans un bargraphe de 500 pixels de large sans
tricher.
On va donc chercher dans une boucle la prochaine couleur pour laquelle
il y a des pixels plus à droite dans le bargraphe que ceux de la dernière
couleur traitée.
Cette couleur trouvée, rajouter les 2 pixels de couleur différente dans
la ligne.
L'autre cas (les 2 pixels qui suivent sont de la même couleur), est
traité beaucoup plus simplement : il suffit de passer à la couleur
suivante et c'est au prochain tour de boucle que cette couleur sera
traitée.
// si le dernier pixel de cette couleur a une valeur impaire
if (pix_dose [couleur] & 1)
{
// sauter les couleurs correspondant au même nombre de pixels
do
couleur ++;
while (pix_dose [couleur - 1] == pix_dose [couleur]);
// colorer ce pixels et le suivant dans les bonnes couleurs
ligne [pos_ligne ++] = (d_pix & 0xF0) | couleur;
}
// sinon
else
// passer à la couleur correspondant à la dose précédente
couleur ++;
Quand on a rajouté tous les couples de pixels dans la ligne, il y reste
une opération (pas indispensable) qui consiste à initialiser la fin de
la ligne, puisque celle-ci doit occuper un nombre d'octets multiple de
4.
// initialiser la fin de ligne
while (pos_ligne < lg_ligne)
ligne [pos_ligne ++] = 0;
On dispose à présent de toutes les information nécessaires pour remplir
le fichier qui contiendra le bargraphe.
On commence par ouvrir ce fichier en écriture et début de fichier (mode
w). Si ce fichier n'existait pas encore, il est créé vierge. Dans
le cas contraire, son ancien contenu est effacé.
On recopie alors dans ce fichier :
- l'entête de l'image BMP,
- la palette des couleurs,
- le contenu de l'image.
Pour le contenu de l'image, comme toutes les lignes sont identiques, il
suffit de recopier le contenu de la variable
ligne autant de
fois que nécessaire.
// c'est parti pour générer le fichier image
// ouvrir le fichier en écriture
descfic = fopen (fichier, "w");
// si l'ouverture s'est bien passée
if (descfic)
{
// copier l'entête bmp
fwrite (entete, sizeof (entete), 1, descfic);
// copier la palette
fwrite (palette, sizeof (palette), 1, descfic);
// générer les lignes de l'image
for (num_ligne = 0; num_ligne < haut_bgraph; num_ligne++)
fwrite (ligne, lg_ligne, 1, descfic);
// le fichier bmp est prêt
fclose (descfic);
}
// sinon
else
// message d'erreur
fprintf (stderr, "Impossible de créer le fichier %s\n", fichier);
Le fichier bmp est prêt. Le programme
gen-serie-bargraphes
pourra éventuellement demande d'en créer d'autres en traitant les lignes
qui suivent dans le fichier CSV local.