/*
    Fichier cylgraph.c
    Auteur Bernard Chardonneau

    Logiciel libre, droits d'utilisation précisés en français
    dans le fichier : licence-fr.txt

    Traductions des droits d'utilisation dans les fichiers :
    licence-en.txt , licence-es.txt , licence-pt.txt ,
    licence-eo.txt , licence-eo-utf.txt

    Droits d'utilisation également sur la page web :
    http://cyloop.tuxfamily.org/voir.php?page=droits


    Programme qui génère un graphique à partir des données
    du fichier cyloop.
*/


#define  appli

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "types.h"
#include "limites.h"
#include "messages.h"
#include "image.h"
#include "gentexte.h"
#include "bmp.h"

// longueur maximum d'un nom de fichier
#define  szmax_nomfic   80

// nombre maximum de courbes dans le graphique
#define  max_trace      12

// taille des flèches tracées
#define  taillefleche    5
#define  deborde_trait   5


// prototypes
char *recup_nomfic (char *nom);
void trtargs (int narg, char *varg[]);
void charge_val ();
void charge_val_uint16 (int numtrace);
void charge_val_uint32 (int numtrace);
void charge_val_int32 (int numtrace);
void charge_val_float (int numtrace);
void charge_minmax_int32 (int numtrace);
void charge_minmax_float (int numtrace);
void initpalette ();
void memgris (octet luminosite, octet numcoul);
void memcoul (int32 couleur);
void majtaille_image ();
void init_image ();
void init_graphique ();
void posit_graphique ();
void gen_echelle_x ();
int  trait_vert_quadri (float position);
void rech_minmax ();
void gen_echelle_y ();
int  trait_horiz_quadri (float position);
void trace_graphique ();
void trace_seg (int x, int ydeb, int yfin, octet couleur, int epaisseur);
void ajout_textes ();
void insert_graphique ();
void ajout_fleche_droite (int x, int y);
void ajout_fleche_haut (int x, int y);
void genficimage (char *nomimage);



// variables globales (pour éviter des tas de passages de paramètre)
// d'autres variables sont définies dans image.h

FILE       *descfic;          // descripteur du fichier cyloop
desc_cyloop entetefic;        // entête du fichier cyloop
desc_var    entetevar;        // entête de la variable du fichier cyloop
int    larg_graph  = 0;       // largeur du graphique
int    haut_graph  = 0;       // hauteur du graphique
int    deb_x, deb_y;          // coordonnées du début du graphique dans l'image
octet  *graphique;            // graphique intégré à l'image
char   *titre = NULL;         // titre de l'image
int    optp = 0;              // option échelle verticale en pourcentage
int    nbtrace     = 0;       // nombre de tracés
octet  typetrace [max_trace]; // type des tracés (min, max ou valeur)
octet  epaisseur [max_trace]; // épaisseur des trait ou surface
float  *listeval [max_trace]; // liste des valeurs pour chaque tracé
float  mingraph, maxgraph;    // min et max sur l'axe vertical du graphique



// programme principal

main (int narg, char *varg[])
{
    char *nomfic;    // nom du fichier cyloop
    char *nomimage;  // nom du fichier image


    // mémorisation du nom de la commande
    memcom (*varg);

    // récupération du nom de fichier cyloop et rajout éventuel du suffixe
    if (--narg > 3)
        nomfic = recup_nomfic (*(++varg));
    else
    {
        // "Trop peu d'arguments"
        affiche_err ("ARGS_MANQUANTS");
        putchar ('\n');

        // "Syntaxe : %s nom_fichier_cyloop nom_fichier_image"
        // "                   options_générales descriptions_tracés"
        //
        // "Options générales :"
        // "[-h hauteur_graphique] [-w largeur_graphique] [-H hauteur_image]"
        // "[-W largeur_image] [-T titre_image] -p"
        //
        // "Description des tracés : -[m|M](tépaisseur|a) couleur"
        aff_err_arg ("SYNT_CYLGRAPH1", nomcom ());
        affiche_err ("SYNT_CYLGRAPH2");
        putchar ('\n');
        affiche_err ("SYNT_CYLGRAPH3");
        affiche_err ("SYNT_CYLGRAPH4");
        affiche_err ("SYNT_CYLGRAPH5");
        putchar ('\n');
        err_fatale  ("SYNT_CYLGRAPH6");
    }

    // tentative de lecture du fichier cyloop
    descfic = fopen (nomfic, "r");

    if (! descfic)
        // "Fichier_cyloop %s inexistant ou protégé en lecture"
        err_fatale_arg ("CYLOOP_ABSENT", nomfic);

    // lecture de l'entête du fichier cyloop
    fread (&entetefic, 16, 1, descfic);

    // vérification de la signature
    if (entetefic.signature [0] != 'c' || entetefic.signature [1] != 'y'
                                       || entetefic.signature [2] != 'l')
    {
        // "Le fichier %s n'est pas un fichier_cyloop"
        aff_err_arg ("FICH_NON_CYLOOP", nomfic);
        fclose (descfic);
        return;
    }

    // vérification de la valeur nb_var
    if (entetefic.nb_var)
    {
        // "Ce fichier cyloop utilise une liste de variables"
        // "utiliser la version complète de cyloop"
        affiche_err ("CYLOOP_LISTEVAR");
        affiche_err ("UTIL_VCOMPLETE");
        fclose (descfic);
        return;
    }

    // récupération du nom de fichier image
    nomimage = *(++varg);

    // initialisation des couleurs de base de l'image
    initpalette ();

    // traiter les arguments resta,ts de la ligne de commande
    trtargs (narg, varg);

    // mémoriser les valeurs de la variable du fichier cyloop
    charge_val ();

    // plus besoin d'accéder au fichier cyloop
    fclose (descfic);

    // calculer ou corriger la taille du graphique et de l'image globale
    majtaille_image ();

    // générer l'image de base (fond)
    init_image ();

    // générer le graphique de base (fond et quadrillage)
    init_graphique ();

    // positionner le graphique dans l'image
    posit_graphique ();

    // tracé de l'échelle sur l'axe x
    gen_echelle_x ();

    // rechercher les valeurs min et max du graphique
    rech_minmax ();

    // tracé de l'échelle sur l'axe y
    gen_echelle_y ();

    // rajouter les tracés dans le graphique
    trace_graphique ();

    // rajouter les commentaires dans l'image
    if (titre)
        aff_texte (titre, haut_totale - 10, larg_totale / 2, align_centre);

    ajout_textes ();

    // insérer le graphique dans l'image
    insert_graphique ();

    // générer le fichier image
    genficimage (nomimage);
}



// récupération du nom du fichier cyloop et rajout éventuel de son suffixe

char *recup_nomfic (char *nom)
{
    char  *nomfic;
    int   lg_nomfic;


    // récupérer la taille du nom de fichier
    lg_nomfic = strlen (nom);

    // si ce nom se termine par le suffixe .cyl
    if ((lg_nomfic > 4) && (strcmp (nom + lg_nomfic - 4, ".cyl") == 0))
    {
        // mémoriser le nom tel quel
        nomfic = malloc (lg_nomfic + 1);
        strcpy (nomfic, nom);
    }
    // sinon
    else
    {
        // rajouter au nom le suffixe .cyl et mémoriser le résultat
        nomfic = malloc (lg_nomfic + 5);
        strcpy (nomfic, nom);
        strcpy (nomfic + lg_nomfic, ".cyl");
    }

    // retourner le nom du fichier
    return nomfic;
}



// prise en compte des arguments restants de la ligne de commande

void trtargs (int narg, char *varg[])
{
    int  optgen;     // prise en compte des options générales des l'image
    char optmM;      // caractère d'une option min ou max de cylgraph
    char option;     // caractère d'une option de cylgraph
    char *posopt;    // adresse du caractère d'une option de cylgraph
    int32 couleur;   // couleur d'un élément du graphique


    // se positionner sur les options de la commande cylgraph
    narg = narg - 2;
    varg ++;

    // prise en contre des options générales à l'image
    optgen = OUI;

    while (**varg == '-' && optgen)
    {
        option = varg [0][1];

        switch (option)
        {
                       // hauteur du graphique
            case 'h' : haut_graph = atoi (varg [1]);
                       break;

                       // hauteur de l'image
            case 'H' : haut_totale = atoi (varg [1]);
                       break;

                       // largeur du graphique
            case 'w' : larg_graph = atoi (varg [1]);
                       break;

                       // largeur de l'image
            case 'W' : larg_totale = atoi (varg [1]);
                       break;

                       // titre de l'image
            case 'T' : titre = varg [1];
                       break;

                       // option pourcentage pour l'échelle verticale
            case 'p' : optp = 1;

                       // pour compenser le saut de 2 arguments successifs
                       narg ++;
                       varg --;
                       break;

                       // fin des options générales
            default  : optgen = NON;
        }

        // passer aux arguments suivants
        if (optgen)
        {
            varg = varg + 2;
            narg = narg - 2;
        }
    }

    // lecture de la partie fixe de l'entête de la variable du fichier cyloop
    fread (&entetevar, 6, 1, descfic);

    // mémorisation des caractéristiques des graphiques
    while (narg > 1 && **varg == '-')
    {
        optmM = varg [0][1];

        // demande de tracé de la valeur min ou max ?
        if (optmM == 'm' || optmM == 'M')
        {
            // s'il est mémorisé pour la variable
            if (entetevar.typedon & mem_min_max)
            {
                // mémoriser la demande
                typetrace [nbtrace] = optmM;

                posopt = &varg [0][2];
            }
            // sinon
            else
            {
                // la demande ne sera pa conservée
                typetrace [nbtrace] = 0;

                // générer le message d'erreur adéquat
                if (entetevar.typedon & compteur)
         // "Variable compteur, les minimums et maximums ne sont pas mémorisés"
                    affiche_err ("MINMAX_COMPTEUR");
                else
         // "Pas de mémorisation des minimums et maximums pour cette variable"
                    affiche_err ("MINMAX_MANQUANT");
            }
        }
        // sinon
        else
        {
            // mémoriser un tracé des valeurs de la variable
            typetrace [nbtrace] = 'v';
            posopt = &varg [0][1];
        }

        // si demande de tracé valide
        if (typetrace [nbtrace])
        {
            // choix entre trait et surface
            option = *posopt;

            switch (option)
            {
                           // tracé d'une surface
                case 'a' : epaisseur [nbtrace++] = 255;
                           break;

                           // tracé d'un trait de 1 à 3 pixels d'épaisseur
                case 't' : if ('1' <= *(posopt + 1) && *(posopt + 1) <= '9')
                               epaisseur [nbtrace++] = *(posopt + 1) - '0';
                           else
                               epaisseur [nbtrace++] = 1;

                           break;

                // "Option -%c inconnue ou mal placée"
                default : aff_err_argnum ("OPTION_INCONNUE", option);
            }

            // récupération de la couleur de ce tracé
            if (option == 'a' || option == 't')
            {
                sscanf (varg [1], "%lX", &couleur);
                memcoul (couleur);
            }
        }

        // passer aux arguments suivants
        varg = varg + 2;
        narg = narg - 2;
    }

    if (narg > 0)
    {
        // "Erreur dans les paramètres à partir de %s"
        aff_err_arg ("ERR_PARAMETRES", *varg);
    }
}



// mémoriser les valeurs de la variable du fichier cyloop

void charge_val ()
{
    int taillelisteval; // nombre d'octet d'un tableau listeval
    int numtrace;       // numéro de tracé


    // calcul de la taille d'une zone mémoire mémorisant les valeurs d'un tracé
    taillelisteval = entetefic.nb_don_cycle * sizeof (float);

    // pour chaque tracé à réaliser dans le graphique
    for (numtrace = 0; numtrace < nbtrace; numtrace++)
    {
        // allouer une zone mémoire pour extraire les données du fichier cyloop
        listeval [numtrace] = malloc (taillelisteval);

        // terminé si échec
        if (! listeval [numtrace])
            // "Manque de place mémoire pour générer un graphique"
            err_fatale ("MANQUE_MEMOIRE1");

        // et l'initialiser avec une valeur flottante indéterminée
        memset (listeval [numtrace], 0xFF, taillelisteval);

        // positionnement sur l'entête de la variable
        fseek (descfic, 16, SEEK_SET);

        // lecture de la partie fixe de l'entête de la variable
        fread (&entetevar, 6, 1, descfic);

        // et de coef_moy s'il est global à la variable
        if (! (entetevar.typedon & cmoy_sep))
            fread (&entetevar.coef_moy, 2, 1, descfic);

        // extraire les valeurs de la variable du fichier cyloop

        // si compteur
        if (entetevar.typedon & compteur)
        {
            // 2 formats de données sont possibles
            if (entetevar.typedon & donnees_32bits)
                charge_val_uint32 (numtrace);
            else
                charge_val_uint16 (numtrace);
        }
        // sinon, mémorisation de valeurs
        else
        {
            // si tracé de la valeur moyenne de la variable durant le cycle
            if (typetrace [numtrace] == 'v')
            {
                // 2 formats de données sont possibles dans le fichier cyloop
                if (entetevar.typedon & val_float)
                    charge_val_float (numtrace);
                else
                    charge_val_int32 (numtrace);
            }
            // sinon tracé de la valeur min ou max de la variable
            else
            {
                // 2 formats de données sont possibles dans le fichier cyloop
                if (entetevar.typedon & val_float)
                    charge_minmax_float (numtrace);
                else
                    charge_minmax_int32 (numtrace);
            }
        }
    }
}



// extraire les valeurs d'une variable de type compteur 16 bits

void charge_val_uint16 (int numtrace)
{
    uint16 total;      // la donnée
    uint16 coef_moy; // coefficient diviseur pour le calcul de moyenne
    int    instant;      // compteur de boucle


    // si compteur de passage séparé pour chaque instant du cycle
    if (entetevar.typedon & cmoy_sep)
    {
        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // lecture du compteur et des données d'un instant du cycle
            fread (&coef_moy, 2, 1, descfic);

            if (! fread (&total, 2, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            if (coef_moy)
                listeval [numtrace][instant] = (float) total / coef_moy;
        }
    }
    // sinon, une valeur coef_moy globale pour tout le cycle
    else
    {
        // prendre en compte la valeur de ce compteur
        coef_moy = entetevar.coef_moy;

        // terminé si compteur pas encore initialisé (fichier cyloop vierge)
        if (! coef_moy)
            return;

        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // si on a atteint le dernier instant du cycle avec mise à jour
            if (instant == entetevar.der_enreg)
            {
                // corriger coef_moy pour la fin du cycle
                if (entetevar.ponderation)
                    coef_moy = coef_moy - 256;
                else
                    coef_moy --;

                // si coef_moy est arrivé à 0, la suite
                // du cycle n'a jamais été parcourue
                if (! coef_moy)
                    return;
            }

            // lecture de la donnée d'un instant du cycle
            if (! fread (&total, 2, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            listeval [numtrace][instant] = (float) total / coef_moy;
        }
    }
}



// extraire les valeurs d'une variable de type compteur 32 bits

void charge_val_uint32 (int numtrace)
{
    uint32 total;      // la donnée
    uint16 coef_moy; // coefficient diviseur pour le calcul de moyenne
    int    instant;      // compteur de boucle


    // si compteur de passage séparé pour chaque instant du cycle
    if (entetevar.typedon & cmoy_sep)
    {
        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // lecture du compteur et des données d'un instant du cycle
            fread (&coef_moy, 2, 1, descfic);

            if (! fread (&total, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            if (coef_moy)
                listeval [numtrace][instant] = (float) total / coef_moy;
        }
    }
    // sinon, une valeur coef_moy globale pour tout le cycle
    else
    {
        // prendre en compte la valeur de ce compteur
        coef_moy = entetevar.coef_moy;

        // terminé si compteur pas encore initialisé (fichier cyloop vierge)
        if (! coef_moy)
            return;

        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // si on a atteint le dernier instant du cycle avec mise à jour
            if (instant == entetevar.der_enreg)
            {
                // corriger coef_moy pour la fin du cycle
                if (entetevar.ponderation)
                    coef_moy = coef_moy - 256;
                else
                    coef_moy --;

                // si coef_moy est arrivé à 0, la suite
                // du cycle n'a jamais été parcourue
                if (! coef_moy)
                    return;
            }

            // lecture de la donnée d'un instant du cycle
            if (! fread (&total, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            listeval [numtrace][instant] = (float) total / coef_moy;
        }
    }
}



// extraire les valeurs d'une variable de type entier 32 bits

void charge_val_int32 (int numtrace)
{
    int32  total;      // la donnée
    uint16 coef_moy; // coefficient diviseur pour le calcul de moyenne
    int    instant;      // compteur de boucle


    // si compteur de passage séparé pour chaque instant du cycle
    if (entetevar.typedon & cmoy_sep)
    {
        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // lecture du compteur et des données d'un instant du cycle
            fread (&coef_moy, 2, 1, descfic);

            if (! fread (&total, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            if (coef_moy)
                listeval [numtrace][instant] = (float) total / coef_moy;

            // si valeurs min et max mémorisées dans le fichier cyloop
            if (entetevar.typedon & mem_min_max)
            {
                // les sauter
                if (fseek (descfic, 8, SEEK_CUR) != 0)
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                    err_fatale ("CYLOOP_TRONQUE");
            }
        }
    }
    // sinon, une valeur coef_moy globale pour tout le cycle
    else
    {
        // prendre en compte la valeur de ce compteur
        coef_moy = entetevar.coef_moy;

        // terminé si compteur pas encore initialisé (fichier cyloop vierge)
        if (! coef_moy)
            return;

        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // si on a atteint le dernier instant du cycle avec mise à jour
            if (instant == entetevar.der_enreg)
            {
                // corriger coef_moy pour la fin du cycle
                if (entetevar.ponderation)
                    coef_moy = coef_moy - 256;
                else
                    coef_moy --;

                // si coef_moy est arrivé à 0, la suite
                // du cycle n'a jamais été parcourue
                if (! coef_moy)
                    return;
            }

            // lecture de la donnée d'un instant du cycle
            if (! fread (&total, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            listeval [numtrace][instant] = (float) total / coef_moy;

            // si valeurs min et max mémorisées dans le fichier cyloop
            if (entetevar.typedon & mem_min_max)
            {
                // les sauter
                if (fseek (descfic, 8, SEEK_CUR) != 0)
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                    err_fatale ("CYLOOP_TRONQUE");
            }
        }
    }
}



// extraire les valeurs d'une variable de type flottant

void charge_val_float (int numtrace)
{
    float  total;      // la donnée
    uint16 coef_moy; // coefficient diviseur pour le calcul de moyenne
    int    multip;     // pour tenir compte du rapport de 256 éventuel
    int    instant;      // compteur de boucle


    // coefficient multiplicateur entre coef_moy et les données flottantes
    if (entetevar.ponderation)
        multip = 256;
    else
        multip = 1;

    // si compteur de passage séparé pour chaque instant du cycle
    if (entetevar.typedon & cmoy_sep)
    {
        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // lecture du compteur et des données d'un instant du cycle
            fread (&coef_moy, 2, 1, descfic);

            if (! fread (&total, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            if (coef_moy)
                listeval [numtrace][instant] = (total / coef_moy) * multip;

            // si valeurs min et max mémorisées dans le fichier cyloop
            if (entetevar.typedon & mem_min_max)
            {
                // les sauter
                if (fseek (descfic, 8, SEEK_CUR) != 0)
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                    err_fatale ("CYLOOP_TRONQUE");
            }
        }
    }
    // sinon, une valeur coef_moy globale pour tout le cycle
    else
    {
        // prendre en compte la valeur de ce compteur
        coef_moy = entetevar.coef_moy;

        // terminé si compteur pas encore initialisé (fichier cyloop vierge)
        if (! coef_moy)
            return;

        // calcul de la donnée à utiliser pour chaque instant du cycle
        for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
        {
            // si on a atteint le dernier instant du cycle avec mise à jour
            if (instant == entetevar.der_enreg)
            {
                // corriger coef_moy pour la fin du cycle
                if (entetevar.ponderation)
                    coef_moy = coef_moy - 256;
                else
                    coef_moy --;

                // si coef_moy est arrivé à 0, la suite
                // du cycle n'a jamais été parcourue
                if (! coef_moy)
                    return;
            }

            // lecture de la donnée d'un instant du cycle
            if (! fread (&total, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                err_fatale ("CYLOOP_TRONQUE");

            // calcul de la donnée pour le graphique
            listeval [numtrace][instant] = (total / coef_moy) * multip;

            // si valeurs min et max mémorisées dans le fichier cyloop
            if (entetevar.typedon & mem_min_max)
            {
                // les sauter
                if (fseek (descfic, 8, SEEK_CUR) != 0)
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
                    err_fatale ("CYLOOP_TRONQUE");
            }
        }
    }
}



// extraire les valeurs min ou max d'une variable de type entier 32 bits

void charge_minmax_int32 (int numtrace)
{
    int32 minmax;    // la valeur min ou max récupérée
    int32 interdite; // valeur interdite pour la variable minmax
    int   instant;     // compteur de boucle
    long  saut;      // saut entre 2 valeur min ou max d'instants successifs
    long  posit;     // pour se positionner dans le fichier cyloop


    // octets à sauter entre 2 valeurs min ou max successives
    if (entetevar.typedon & cmoy_sep)
        saut = 10;
    else
        saut = 8;

    // position de la valeur min ou max du premier instant du cycle
    if (typetrace [numtrace] == 'm')
    {
        posit = saut - 4;
        interdite = max_int32;
    }
    else
    {
        posit = saut;
        interdite = min_int32;
    }

    // calcul de la donnée à utiliser pour chaque instant du cycle
    for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
    {
        if (fseek (descfic, posit, SEEK_CUR) != 0)
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
            err_fatale ("CYLOOP_TRONQUE");

        if (! fread (&minmax, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
            err_fatale ("CYLOOP_TRONQUE");

        // mémorisation de la valeur récupérée si significative
        if (minmax != interdite)
            listeval [numtrace][instant] = (float) minmax;

        // pour passer au min ou max de l'instant suivant du cycle
        posit = saut;
    }
}



// extraire les valeurs min ou max d'une variable de type flottant

void charge_minmax_float (int numtrace)
{
    float minmax;    // la valeur min ou max récupérée
    float interdite; // valeur interdite pour la variable minmax
    int   instant;     // compteur de boucle
    long  saut;      // saut entre 2 valeur min ou max d'instants successifs
    long  posit;     // pour se positionner dans le fichier cyloop


    // octets à sauter entre 2 valeurs min ou max successives
    if (entetevar.typedon & cmoy_sep)
        saut = 10;
    else
        saut = 8;

    // position de la valeur min ou max du premier instant du cycle
    if (typetrace [numtrace] == 'm')
    {
        posit = saut - 4;
        interdite = max_float;
    }
    else
    {
        posit = saut;
        interdite = min_float;
    }

    // calcul de la donnée à utiliser pour chaque instant du cycle
    for (instant = 0; instant < entetefic.nb_don_cycle; instant++)
    {
        if (fseek (descfic, posit, SEEK_CUR) != 0)
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
            err_fatale ("CYLOOP_TRONQUE");

        if (! fread (&minmax, 4, 1, descfic))
         // "Erreur de lecture du fichier cyloop : fichier probablement tronqué"
            err_fatale ("CYLOOP_TRONQUE");

        // mémorisation de la valeur récupérée si significative
        if (minmax != interdite)
            listeval [numtrace][instant] = minmax;

        // pour passer au min ou max de l'instant suivant du cycle
        posit = saut;
    }
}



// initialisation des couleurs de base de l'image

void initpalette ()
{
    memgris (0xFF, coulbase_graph);  // couleur de base du graphique
    memgris (0xB0, coulbase_image);  // couleur de base de l'image
    memgris (0xA0, coulbase_quadri); // traits quadrillage
    memgris (0x00, coulbase_texte);  // couleur du texte

    nb_coul = 4;  // le numéro de couleur suivant dans la palette
}



// mémorisation d'un niveau de gris dans la palette

void memgris (octet luminosite, octet numcoul)
{
    int i;  // compteur


    for (i = 0; i < 3; i++)
        palette [numcoul][i] = luminosite;

    palette [numcoul][3] = 0;
}



// mémorisation d'une couleur dans la palette

void memcoul (int32 couleur)
{
    if (nb_coul < 16)
    {
        memcpy (palette [nb_coul], &couleur, 3);
        palette [nb_coul++][3] = 0;
    }
    else
    {
        // "trop de couleurs demandées pour le graphique"
        err_fatale ("TROP_COULEURS");
    }
}



// calculer ou corriger la taille du graphique et de l'image globale

void majtaille_image ()
{
    // si largeur totale initialisée, l'arrondir au multiple de 8 supérieur
    larg_totale = (larg_totale + 7) / 8 * 8;

    // calcul de la largeur du graphique si elle n'est pas initialisée
    if (larg_graph == 0)
    {
        if (larg_totale == 0 || larg_totale >= 400)
            // largeur par défaut
            larg_graph = 400;
        else
            // largeur limitée à celle de l'image
            larg_graph = larg_totale;
    }

    // calcul de la hauteur du graphique si elle n'est pas initialisée
    if (haut_graph == 0)
    {
        if (haut_totale == 0 || haut_totale >= 100)
            // hauteur par défaut
            haut_graph = 100;
        else
            // hauteur limitée à celle de l'image
            haut_graph = haut_totale;
    }

    // si la largeur totale de l'image n'est pas initialisée
    if (larg_totale == 0)
        // la fixer à 100 pixels de plus que la largeur du graphique
        // arrondie au multiple de 8 supérieur
        larg_totale = (larg_graph + 107) / 8 * 8;

    // si la hauteur totale de l'image n'est pas initialisée
    if (haut_totale == 0)
        // la fixer (calcul à ajuster en fonction des textes à insérer)
        haut_totale = haut_graph + 50;
}



// générer l'image de base (couleur de fond)

void init_image ()
{
    int taille_image;  // utile suite à un problème de fonctionnment de sizeof


    // calcul du nombre d'octets de l'image
    taille_image = larg_totale * haut_totale;

    // allouer une zone mémoire pour l'image
    image = malloc (taille_image);

    if (! image)
        // "Manque de place mémoire pour générer l'image"
        err_fatale ("MANQUE_MEMOIRE2");

    // remplir l'image avec la couleur de fond
    memset (image, coulbase_image, taille_image);
}



// générer le graphique de base (couleur de fond)

void init_graphique ()
{
    int taille_graph;  // utile suite à un problème de fonctionnment de sizeof


    // calcul du nombre d'octets du graphique
    taille_graph = larg_graph * haut_graph;

    // allouer une zone mémoire pour le graphique
    graphique = malloc (taille_graph);

    if (! graphique)
        // "Manque de place mémoire pour générer l'image"
        err_fatale ("MANQUE_MEMOIRE2");

    // remplir le graphique avec la couleur de fond
    memset (graphique, coulbase_graph, taille_graph);
}



// positionner le graphique dans l'image

void posit_graphique ()
{
    int   marge_larg;  // espace libre en largeur autour du graphique
    int   marge_haut;  // espace libre en hauteur autour du graphique


    // détermination de la position du graphique
    marge_larg = larg_totale - larg_graph;
    marge_haut = haut_totale - haut_graph;

    if (marge_larg > 80)
        deb_x = marge_larg - 30;
    else
        deb_x = marge_larg / 2;

    if (marge_haut > 40)
        deb_y = marge_haut - 30;
    else
        deb_y = marge_haut / 2;
}



// tracé de l'échelle sur l'axe x

void gen_echelle_x ()
{
    // intervalles normalisés entre 2 traits verticaux de l'échelle
    // on utilise des tableaux statiques pour éviter une
    // allocation mémoire et initialisation inutiles

    // durées des intervalles
    static uint32 durees []  =
           { 1, 2, 5, 10, 30, 1, 2, 5, 10, 30, 1, 2, 6, 12, 1, 2,
             5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000 };

    // échelle de temps en secondes pour ces intervalles
    static uint32 echelle [] =
           { 1, 1, 1, 1, 1, 60, 60, 60, 60, 60, 3600, 3600, 3600, 3600,
             secjour, secjour, secjour, secjour, secjour, secjour, secjour,
             secjour, secjour, secjour, secjour, secjour, secjour };

    // durée en jour des 11 premiers mois de l'année (maximum pour février)
    static int jourmois [] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30 };

    uint32 duree_comp; // durée minimale d'un intervalle
    uint32 intervalle; // durée réelle d'un intervalle
    float  som_int;    // durée de plusieurs intervalles
    int    numjour;    // numéro de jour dans le mois ou l'année
    int    mois;       // numéro de mois dans l'année
    int    posit_x;    // position du trait vertical généré
    int    decal_x;    // deb_x + décalage pour l'affichage des numéros de mois
    int    i;          // compteur


    // si cycle irrégulier
    if (entetefic.caract_gen & cycle_ireg)
    {
        numjour = 0;

        // cycle annuel, 1 trait par mois
        if (entetefic.caract_gen & cycle_annuel)
        {
            // initialisation
            mois = 1;
            decal_x = deb_x + (larg_graph / 24);

            aff_entier (mois, deb_y - 5, decal_x, align_centre);

            // un trait vertical par mois avec le numéro du mois
            while (mois < 12)
            {
                numjour = numjour + jourmois [mois - 1];
                mois ++;

                posit_x = trait_vert_quadri (numjour / 366.0);

                aff_entier (mois, deb_y - 5, decal_x + posit_x, align_centre);
            }
        }
        // cycle mensuel
        else
        {
            // initialisation
            numjour = 1;
            aff_entier (numjour, deb_y - 5, deb_x, align_gauche);

            // un trait vertical et un numéro de jour tous les 3 jours
            while (numjour < 31)
            {
                numjour = numjour + 3;
                posit_x = trait_vert_quadri ((numjour - 1) / 31.0);

                aff_entier (numjour, deb_y - 5, deb_x + posit_x, align_gauche);
            }
        }
    }
    // sinon, cycle régulier
    else
    {
        // durée minimale d'un intervalle
        duree_comp = entetefic.duree_cycle / 12;

        // recherche de la durée normalisée immédiatement supérieure
        i = 0;

        while (durees [i] * echelle [i] < duree_comp)
            i++;

        // initialisation tracé
        intervalle = durees [i] * echelle [i];

        som_int = intervalle;

        // tracer les traits verticaux du quadrillage et l'échelle
        do
        {
            posit_x = trait_vert_quadri
                               ((float) som_int / entetefic.duree_cycle);

            aff_entier (som_int / echelle [i],
                            deb_y - 5, deb_x + posit_x, align_centre);

            som_int += intervalle;
        }
        while (som_int < entetefic.duree_cycle);
    }
}



/*
   tracé d'un trait vertical du quadrillage
   on pourrait aussi appeler trace_seg, mais cette fonction
   effectue un traitement inutilement compliqué
*/


int trait_vert_quadri (float position)
{
    int posit_x;    // position du trait vertical
    octet *adrpix;  // adresse d'un pixel du graphique
    int i;          // compteur


    // calcul de la position du trait
    posit_x = larg_graph * position;

    // initialisation adresse pixel
    adrpix = graphique + posit_x;

    // tracé du trait
    for (i = 0; i < haut_graph; i++)
    {
        // mettre à jour un pixel
        *adrpix = coulbase_quadri;

        // descendre d'une ligne
        adrpix += larg_graph;
    }

    return posit_x;
}



// recherche des valeurs min et max pour le tracé du graphique

void rech_minmax ()
{
    int i, j;    // compteurs


    // calcul des valeurs min et max de l'échelle
    mingraph = max_float;
    maxgraph = min_float;

    // pour tous les tracés
    for (i = 0; i < nbtrace; i++)
    {
        // prise en compte des valeurs
        for (j = 0; j < entetefic.nb_don_cycle; j++)
        {
            if (listeval [i][j] < mingraph)
                mingraph = listeval [i][j];

            if (listeval [i][j] > maxgraph)
                maxgraph = listeval [i][j];
        }
    }
}



// tracé de l'échelle sur l'axe y

void gen_echelle_y ()
{
    float max_abs;     // plus grande valeur absolue entre mingraph et maxgraph
    float intervaprox; // valeur aproximative de l'intervalle
    float intervalle;  // intervalle sélectionné
    float som_int;     // somme de plusieurs intervalles
    char  formatfloat [10]; // format d'un nombre en virgule flottante
    int   puis10;      // puissance de 10 de l'intervalle
    float facteur;     // facteur multiplicatif dérivé de puis1000
    int   posit_y;     // position du trait horizontal généré


    // terminé si fichier cyloop sans données enregistrées
    if (mingraph > maxgraph)
        return;

    // si mingraph et maxgraph tous deux positifs
    if (mingraph >= 0)
    {
        // pour la lisibilité du graphique, il vaut
        // mieux passer quelquefois la valeur min à 0
        if (mingraph < maxgraph / 2)
            mingraph = 0;

        max_abs = maxgraph;
    }
    // sinon si mingraph et maxgraph tous deux négatifs
    else if (maxgraph <= 0)
    {
        if (mingraph / 2 < maxgraph)
            maxgraph = 0;

        max_abs = -mingraph;
    }
    // sinon mingraph négatif et maxgraph positif
    else
    {
        // recherche de la plus grande valeur absolue des extrémités du graphe
        if (-mingraph > maxgraph)
            max_abs = -mingraph;
        else
            max_abs = maxgraph;
    }

    // valeur aproximative d'un intervalle
    intervaprox = maxgraph / 3;

    // cas particulier, toutes les valeurs négatives
    if (intervaprox < 0)
        intervaprox = - intervaprox;

    // recherche d'un intervalle normalisé immédiatement supérieur
    intervalle = 5;
    puis10     = 0;

    // grands intervalles, recherche de la puissance de 10 supérieure
    // on fait un 2ème test pour éviter le débordement numérique
    while (intervalle < intervaprox && intervalle < (max_float / 10))
    {
        intervalle = intervalle * 10;
        puis10 ++;
    }

    // petits intervalles, recherche de la puissance de 10 inférieure
    while (intervalle > intervaprox)
    {
        intervalle = intervalle / 10;
        puis10 --;
    }

    // afiner la valeur de l'intervalle du simple au double
    intervaprox = intervaprox / 2;

    if (intervalle < intervaprox)
    {
        intervalle = intervalle * 2;
        puis10 ++;

        if (intervalle < intervaprox)
            intervalle = intervalle * 2;
    }

    // rechercher la valeur du trait le plus bas à tracer
    som_int = 0;

    while (som_int > mingraph)
        som_int = som_int - intervalle;

    while (som_int <= mingraph)
        som_int = som_int + intervalle;

    // déterminer le meilleur format d'affichage de l'échelle
    facteur = 1;

    // si demande d'affichage d'un pourcentage
    // et intervalle ni trop grand ni trop petit
    if (optp && (-5 <= puis10) && (intervalle <= 5))
    {
        // coef multiplicateur
        facteur = 100;

        if (puis10 > -3)
            // affichage d'un pourcentage entier
            strcpy (formatfloat, "1.0f %%");
        else
            // affichage d'un pourcentage à virgule
            sprintf (formatfloat, "0.%df %%", -puis10 - 2);
    }
    // sinon, pas de pourcentage
    else
    {
        // si intervalle entier
        if (intervalle >= 1)
        {
            if (max_abs <= 10000)
                // affichage entier
                *formatfloat = '\0';
            else if (max_abs <= 1E6)
            {
                // échelle en milliers
                if (intervalle < 1000)
                    strcpy (formatfloat, "1.1fk");
                else
                    strcpy (formatfloat, "1.0fk");

                facteur = 0.001;
            }
            else if (max_abs <= 1E9)
            {
                // échelle en millions
                if (intervalle < 1000000)
                    strcpy (formatfloat, "1.1fM");
                else
                    strcpy (formatfloat, "1.0fM");

                facteur = 0.000001;
            }
            else
                // puissance de 10 fortement positive
                strcpy (formatfloat, "1.1E");
        }
        // sinon intervalle avec valeur décimale
        else
        {
            switch (puis10)
            {

                case -1 :
                case -2 : // affichage d'un nombre à virgule
                case -3 : sprintf (formatfloat, "0.%df", -puis10);
                          break;

                          // échelle en millièmes
                case -4 : strcpy (formatfloat, "1.1fm");
                          facteur = 1000;
                          break;

                case -5 : // échelle en millionièmes
                case -6 : strcpy (formatfloat, "1.0fµ");
                          facteur = 1000000;
                          break;

                case -7 : strcpy (formatfloat, "1.1fµ");
                          facteur = 1000000;
                          break;

                          // puissance de 10 fortement négative
                default : strcpy (formatfloat, "1.1E");
            }
        }
    }

    // tracer les traits horizontaux du quadrillage
    while (som_int <= maxgraph)
    {
        posit_y = trait_horiz_quadri (som_int);

        // et rajouter l'échelle
        if (*formatfloat)
            aff_float (som_int * facteur, formatfloat,
                       posit_y + deb_y + 7, deb_x - 5, align_droite);
        else
            aff_entier ((long) som_int,
                       posit_y + deb_y + 7, deb_x - 5, align_droite);

        som_int = som_int + intervalle;
    }
}



// tracé d'un trait horizontal du quadrillage

int trait_horiz_quadri (float position)
{
    int posit_y;    // position du trait horizontal


    // calcul de la position du trait
    posit_y = haut_graph * (position - mingraph) / (maxgraph - mingraph);

    // tracé du trait (sauf si en bordure de graphique)
    if (posit_y < haut_graph)
        memset (graphique + (posit_y * larg_graph),
                                    coulbase_quadri, larg_graph);

    return posit_y;
}



// rajouter les tracés dans le graphique

void trace_graphique ()
{
    float coef_vert;
    int   numtrace;
    int   indice;
    int   x, y, yprec;
    float *liste;


    // pour calcul hauteur d'un point sur le graphique
    coef_vert = haut_graph / (maxgraph - mingraph);

    // tracé du graphique
    for (numtrace = 0; numtrace < nbtrace; numtrace++)
    {
        liste = listeval [numtrace];

        // initialisation nécessaire pour 1er point
        if (isnan (*liste))
            yprec = 0;
        else
            yprec = (*liste - mingraph) * coef_vert;

        // tracé du graphique
        for (x = 0; x < larg_graph; x++)
        {
            // calcul de la position d'un point du graphique
            indice = (float) x * entetefic.nb_don_cycle / larg_graph;

            // si ce point est défini
            if (! isnan (liste [indice]))
            {
                y = (liste [indice] - mingraph) * coef_vert;

                // si tracé sous le forme de trait
                if (epaisseur [numtrace] < 255)
                {
                    // faire un trait plus ou moins épais
                    trace_seg (x, yprec, y, numtrace + 4, epaisseur [numtrace]);

                    // mémoriser y pour aller au point suivant
                    yprec = y;
                }
                // sinon
                else
                    // tracé d'un histogramme
                    trace_seg (x, 0, y, numtrace + 4, 1);
            }
        }
    }
}



// tracé d'un trait vertical dans le graphique

void trace_seg (int x, int ydeb, int yfin, octet couleur, int epaisseur)
{
    octet *adrpix;  // adresse d'un pixel du graphique
    int ymin, ymax; // valeurs min et max de y
    int i;          // compteur


    // calculer le plus petit et le plus grand entre ydeb et yfin
    if (ydeb < yfin)
    {
        ymin = ydeb;
        ymax = yfin;
    }
    else
    {
        ymin = yfin;
        ymax = ydeb;
    }

    // prendre en compte l'épaisseur sur les verticales
    ymax = ymax + (epaisseur / 2);
    ymin = ymin - ((epaisseur - 1) / 2);

    // corriger les valeurs de y qui sortiraient du graphique
    if (ymin < 0)
        ymin = 0;
    else if (ymin >= haut_graph)
        ymin = haut_graph - 1;

    if (ymax >= haut_graph)
        ymax = haut_graph - 1;

    // initialisation adresse pixel
    adrpix = graphique + (ymin * larg_graph) + x;

    // tracé du trait
    for (i = ymin; i <= ymax; i++)
    {
        // mettre à jour un pixel
        *adrpix = couleur;

        // voire plus si traits épais
        if (epaisseur - 1)
        {
            if (x)
                *(adrpix - 1) = couleur;

            if (epaisseur > 2 && x < larg_graph - 1)
                *(adrpix + 1) = couleur;
        }

        // monter d'une ligne
        adrpix += larg_graph;
    }
}



// rajouter les commentaires dans l'image

void ajout_textes ()
{
}



// insérer le graphique dans l'image

void insert_graphique ()
{
    octet *adrpix_graph;      // adresse d'un pixel du graphique
    octet *adrpix_image;      // adresse d'un pixel de l'image
    octet *sauv_adrpix_image; // sauvegarde de adrpix_image
    int   avant;              // compteur de pixels;
    int   i;                  // compteur;


    // initialisation
    sauv_adrpix_image = image + (deb_y * larg_totale) + deb_x;
    adrpix_image = sauv_adrpix_image;
    adrpix_graph = graphique;

    // insertion du graphique dans l'image
    for (i = 0; i < haut_graph; i++)
    {
        // copier une ligne
        memcpy (adrpix_image, adrpix_graph, larg_graph);

        // passer à la ligne du dessus
        adrpix_graph += larg_graph;
        adrpix_image += larg_totale;
    }

    // on va tracer un trait horizontal en dessous de l'image

    // pour faire commencer le trait horizontal un peu à gauche de l'image
    if (deb_x > deborde_trait)
        avant = deborde_trait;
    else
        avant = deb_x;

    // réinitialiser adrpix_image
    adrpix_image = sauv_adrpix_image;

    // tracé du trait horizontal suivi si possible d'une flèche coté droit
    if (deb_y)
    {
        memset (adrpix_image - larg_totale - avant, coulbase_texte,
                                                    larg_graph + avant);
        ajout_fleche_droite (deb_x + larg_graph, deb_y - 1);
    }
    else
        memset (adrpix_image - avant, coulbase_texte, larg_graph + avant);

    // on va tracer un trait vertical à gauche de l'image

    // pour faire commencer le trait vertical un peu en dessous de l'image
    if (deb_y > deborde_trait)
        avant = deborde_trait;
    else
        avant = deb_y;

    adrpix_image = adrpix_image - (avant * larg_totale);

    // positionner le trait vertical au mieux
    if (deb_x)
    {
        // ajout d'une flèche sur le coin bas droit de l'image
        ajout_fleche_haut (deb_x - 1, deb_y + haut_graph);

        // le trait vertical sera à gauche du graphique
        adrpix_image --;
    }

    // tracé du trait vertical à gauche du graphique
    for (i = -avant; i < haut_graph; i++)
    {
        *adrpix_image = coulbase_texte;
        adrpix_image += larg_totale;
    }
}



// ajout d'une flèche orientée vers la droite dans l'image

void ajout_fleche_droite (int x, int y)
{
    octet *adrpix;  // adresse d'un pixel de l'image
    int   i;        // compteur


    // la flèche ne sera tracée que s'il y a la place
    if (y + 1 < taillefleche)
        return;

    if (larg_totale - x < taillefleche)
        return;

    // adresse du point d'encrage de la flèche
    adrpix = image + (y * larg_totale) + x;

    // trait horizontal central
    memset (adrpix, coulbase_texte, taillefleche);

    // traits horizontaux de plus en plus courts de part et d'autre
    for (i = 1; i < taillefleche; i++)
    {
        memset (adrpix - (i * larg_totale), coulbase_texte, taillefleche - i);
        memset (adrpix + (i * larg_totale), coulbase_texte, taillefleche - i);
    }
}



// ajout d'une flèche orientée vers le haut dans l'image

void ajout_fleche_haut (int x, int y)
{
    octet *adrpix;  // adresse d'un pixel de l'image
    int   i;        // compteur


    // la flèche ne sera tracée que s'il y a la place
    if (x + 1 < taillefleche)
        return;

    if (haut_totale - y < taillefleche)
        return;

    // adresse du coin bas gauche de la flèche
    adrpix = image + (y * larg_totale) + x - taillefleche + 1;

    // tracé de la flèche
    for (i = (taillefleche * 2) - 1; i > 0; i = i - 2)
    {
        // tracé d'un trait horizontal
        memset (adrpix, coulbase_texte, i);

        // remonter d'une ligne et avancer d'un pixel
        adrpix = adrpix + larg_totale + 1;
    }
}



// génération du fichier image

void genficimage (char *nomimage)
{
    FILE      *descbmp;    // descripteur du fichier image BMP
    char      *imgbmp;     // nom du fichier image BMP
    char      *cmdconvert; // commande de conversion du fichier BMP
    entetebmp bmp_entete;  // entete du fichier bmp
    octet     *adrpix;     // pointeur sur un pixel de l'image
    octet     doublepix;   // caractère du fichier BMP contenant 2 pixels
    int       i, j;        // compteurs;


    // fabrication du nom de fichier image bmp de travail si nécessaire
    i = strlen (nomimage) - 1;

    while (i > 0 && nomimage [i] != '.')
        i--;

    // si l'image à générer est une image BMP
    if (strcasecmp (nomimage + i, ".bmp") == 0)
        // on utilisera juste son nom
        imgbmp = nomimage;
    // sinon
    else
    {
        // fabriquer un nom d'image BMP de même préfixe
        imgbmp = malloc (i + 5);

        memcpy (imgbmp, nomimage, i);
        strcpy (imgbmp + i, ".bmp");
    }

    // tentative de création du fichier image BMP
    descbmp = fopen (imgbmp, "w");

    if (! descbmp)
        // "Impossible de créer un fichier image de travail %s"
        err_fatale_arg ("IMPOS_CRE_FIMAGE", imgbmp);

    // remplissage de l'entête du fichier BMP

    // partie invariante pour le type de fichier généré
    bmp_entete.bm [0]      =  'B';
    bmp_entete.bm [1]      =  'M';
    bmp_entete.zero        =   0;
    bmp_entete.taillebi    =  40;
    bmp_entete.un          =   1;
    bmp_entete.bits_coul   =   4;
    bmp_entete.compress    =   0;
    bmp_entete.pixelx_m    = 5000;
    bmp_entete.pixely_m    = 5000;
    bmp_entete.coulimport  =   0;

    // partie qui dépend des caractéristiques de l'image
    bmp_entete.couleurs    = nb_coul;
    bmp_entete.pixels_l    = larg_totale;
    bmp_entete.pixels_h    = haut_totale;
    bmp_entete.deplacement = 54 + (4 * nb_coul);
    bmp_entete.szimage     = (bmp_entete.pixels_l / 2) * bmp_entete.pixels_h;
    bmp_entete.taillefic   = bmp_entete.szimage + bmp_entete.deplacement;

    // copie de l'entête dans le fichier
    fwrite (&bmp_entete.bm, sizeof (bmp_entete) - 2, 1, descbmp);

    // rajout de la palette dans le fichier
    fwrite (palette, 4, nb_coul, descbmp);

    // initialisation pointeur
    adrpix = image;

    // rajout de l'image proprement dite
    for (i = 0; i < haut_totale; i++)
    {
        for (j = 0; j < larg_totale; j = j + 2)
        {
            // on regroupe 2 pixels consécutifs sur un octet
            doublepix = (*adrpix << 4) | *(adrpix + 1);

            // qu'on recopie dans le fichier
            fputc (doublepix, descbmp);

            // avant de passer aux pixels suivants
            adrpix = adrpix + 2;
        }
    }

    // image bmp prête
    fclose (descbmp);

    // si le fichier image à créer n'est pas un fichier BMP
    if (imgbmp != nomimage)
    {
        // détruire la version précédente du fichier image si elle existe
        unlink (nomimage);

        // générer la commande de conversion du fichier image et l'exécuter
        cmdconvert = malloc (strlen (nomimage) + strlen (imgbmp) +10);
        sprintf (cmdconvert, "convert %s %s", imgbmp, nomimage);
        system (cmdconvert);

        // si le fichier image n'a pu être créé
        if (access (nomimage, 0) < 0)
        {
            // messages d'erreur
            // "Commande convert de ImageMagick indisponible"
            // "Image générée au format BMP dans le fichier %s"
            affiche_err ("CONVERT_ABSENTE");
            aff_err_arg ("IMAGE_RESTE_BMP", imgbmp);
        }
        // sinon
        else
            // on peut détruire le fichier BMP de travail
            unlink (imgbmp);
    }
}