1. Introduction

Cet article a pour objectif d'expliquer comment créer une texture OpenGL à partir d'un fichier image, et en particulier ici, une image TGA (TARGA). Le code est en C.

Un fichier image est un fichier binaire contenant (le plus souvent) une en-tête (header) de fichier puis les données. Les données peuvent être les données de l'image et la palette de couleur, ou seulement les données si l'image ne nécessite pas de palette. L'en-tête fournis des informations quant à la nature des données : les dimensions de l'image, le type de l'image (avec palette ou non), le type de compression, etc. Un format d'image peut contenir plusieurs types d'images (c'est le cas du format TGA par exemple) comme une image 24 bits non compressées, une image 32 bits, une image 8 bits avec palette, etc. Les informations que l'on trouve sont spécifiques au format de fichier.

Le format TGA peut contenir des images 8, 16, 24 et 32 bits avec ou sans compression. La méthode de compression est l'algorithme RLE, qui permet une compression non destructrice de l'image. La compression JPEG est dite « destructrice », puisque les données compressées ne seront pas identiques à celles d'origine (il y a perte d'information). Les images 8 bits peuvent utiliser une palette de couleur.

Certains formats de fichier ont aussi un « pied » (footer) contenant des informations additionnelles. C'est le cas du format TGA, mais nous ne le verront pas ici ces données ne nous seront pas utiles.

Nous allons voir dans cet article comment lire un fichier d'image TGA ; c'est-à-dire lire son en-tête afin de déterminer comment extraire les données de l'image, puis lire ces données et les stocker en mémoire pour créer à partir de cette image, une texture OpenGL.

Les types utilisés ont les tailles suivantes :

  • char : 1 octet ;
  • short : 2 octets ;
  • int : 4 octets ;
  • ubyte est un octet non signé (unsigned char) d'un octet.

2. Texture OpenGL

Avant de commencer à lire le fichier, il est nécessaire de connaître les données dont nous avons besoin pour créer une texture OpenGL.

Il nous faut bien évidemment un tableau de données pour stocker les pixels de l'image. OpenGL peut prendre plusieurs types de données pour créer une texture (octets, entiers, etc.). Nous nous restreindront ici au type « octet non signé » pour stocker nos données.

Nous avons également besoin de connaître les dimensions de l'image, le nombre de composantes R, V, B, A ou Luminance et Alpha de l'image et le format (RVB, RVBA, Luminance ou Luminance Alpha).

Nous allons regrouper ces données dans une type structure gl_texture_t :

 
Sélectionnez
/* OpenGL texture info */
typedef struct
{
    GLuint  width;           /* largeur */
    GLuint  height;          /* hauteur */

    GLenum  format;          /* RVB, RVBA, Luminance, Luminance Alpha */
    GLint   internalFormat;  /* composantes d'un texel */

    GLubyte *texels;         /* données de l'image */

} gl_texture_t;

Pour la lecture d'une image TGA, nous créeront une fonction qui prendra en paramètre un nom de fichier et un pointeur vers un objet de type gl_texture_t. Une fois l'image chargée, nous pourront utiliser les fonctions OpenGL pour créer une texture à partir de notre objet gl_texture_t.

3. Lecture de l'en-tête TGA

L'en-tête d'un fichier TGA possède divers champs et est invariable d'un fichier à un autre (on retrouve toujours les mêmes champs dans le même ordre et le même nombre). On peut donc regrouper toutes ces informations à l'intérieure d'une structure de données que l'on lira d'un bloc :

 
Sélectionnez
#pragma pack(push, 1)
/* tga header */
typedef struct
{
    ubyte   id_lenght;          /* size of image id */
    ubyte   colormap_type;      /* 1 is has a colormap */
    ubyte   image_type;         /* compression type */

    short   cm_first_entry;     /* colormap origin */
    short   cm_length;          /* colormap length */
    ubyte   cm_size;            /* colormap size */

    short   x_origin;           /* bottom left x coord origin */
    short   y_origin;           /* bottom left y coord origin */

    short   width;              /* picture width (in pixels) */
    short   height;             /* picture height (in pixels) */

    ubyte   pixel_depth;        /* bits per pixel: 8, 16, 24 or 32 */
    ubyte   image_descriptor;   /* 24 bits = 0x00; 32 bits = 0x80 */

} tga_header_t;
#pragma pack(pop)

Le type tga_header_t doit avoir une taille de 18 octets. Certains compilateurs utilisent par défaut un alignement différent (sur 4 octets par exemple) et qui font qu'une structure de 18 octets occupe 20 octets (multiple de 4). C'est pour cela qu'on utilise les directives préprocesseur #pragma pack pour aligner le type tga_header_t sur un octet.

Étudions les champs de cette structure : id_lenght est la taille d'un champ de données situé juste après l'en-tête du fichier. Ce champ ne nous est pas utile et n'est pas toujours présent. id_lenght nous permet de sauter cette zone mémoire pour atteindre les données de l'image.

colormap_type indique si l'image utilise une palette de couleur ou non. Si colormap_type vaut 1, alors on doit trouver la palette au début du champ de données, juste avant les données des pixels.

image_type indique le type d'image contenu dans le fichier. Les valeurs qu'il peut prendre sont les suivantes :

Valeur Type d'image
0 pas de données images
1 image 8 bits non compressée en mode index couleur (avec palette)
2 image 16, 24 ou 32 bits non compressée en mode BVR
3 image 8 ou 16 bits non compressée en mode dégradé de gris
9 image 8 bits compressée en mode index couleur (avec palette)
10 image 16, 24 ou 32 bits compressée en mode BVR
11 image 8 ou 16 bits compressée en mode dégradé de gris

Les trois variables commençant par cm sont spécifiques à la palette de couleur. cm_first_entry indique l'index de la première couleur dans la palette. Il est possible en effet que la palette soit d'une taille plus grande que nécessaire et les données de la palette peuvent ne pas se trouver au début de ce champs (au milieu par exemple). cm_length est le nombre de couleurs de la palette. Enfin, cm_size est le nombre de bits par couleur de la palette. Il peut être de 15, 16, 24 ou 32.

x_origin et y_origin sont les coordonnées absolues du coin bas gauche de l'image, et elles ne nous serviront pas.

width et height sont les dimensions de l'image (largeur et hauteur). pixel_depth indique le nombre de bits utilisés pour stocker un pixel de l'image. Typiquement 8, 16, 24 ou 32. Le champ image_descriptor n'est pas utile dans notre cas.

À partir de cette structure, nous pouvons déjà obenir un certain nombre d'informations sur l'image et initialiser tous les champs de notre objet gl_texture_t à l'exception de texels. Nous pouvons néanmoins réserver une zone mémoire pour stocker les données pixels de l'image. Voyons la lecture de l'en-tête :

 
Sélectionnez
int ReadTGAFile( char *filename, gl_texture_t *texinfo )
{
    FILE *fp;
    tga_header_t header;
    ubyte *colormap = NULL;

    fp = fopen( filename, "rb" );
    if( !fp ) {
        /* erreur ! */
        return 0;
    }

    /* lecture de l'en-tête */
    fread( &header, sizeof( tga_header_t ), 1, fp );
    GetTextureInfo( &header, texinfo );
    fseek( fp, header.id_lenght, SEEK_CUR );

    /* lecture de la palette... */

    /* allocations mémoire nécessaires... */

    /* lecture des données pixels de l'image... */

    /* désallocations mémoire nécessaires... */

    fclose( fp );
    return 1;
}

La fonction ReadTGAFile() a pour but de lire un fichier image TGA et de stocker ses données dans texinfo. En cas d'échec elle retourne 0 (faux), sinon 1 (vrai).

Pour le moment notre fonction ne fait pas grand chose : ouverture du fichier en mode binaire ("rb"), lecture de l'en-tête dans header, initialisation des variables de texinfo et déplacement au début des données de l'image (rappelez-vous, il peut y avoir des informations additionnelles entre l'en-tête et les données de l'image). La fonction GetTextureInfo() est détaillée dans la section suivante.

4. Type de texture OpenGL

À partir de l'en-tête TGA, nous pouvons en tirer toutes les informations nécessaire quant au type de texture OpenGL que l'on va utiliser. C'est le travail de la fonction GetTextureInfo() :

 
Sélectionnez
void GetTextureInfo( tga_header_t *header, gl_texture_t *texinfo )
{
    texinfo->width = header->width;
    texinfo->height = header->height;

    switch( header->image_type )
    {
        case 3:     /* 8 bits dégradé de gris */
        case 11:    /* 8 bits dégradé de gris (RLE) */
        {
            if( (int)header->pixel_depth == 8 ) {
                texinfo->format = GL_LUMINANCE;
                texinfo->internalFormat = 1;
            }
            else { /* 16 bits */
                texinfo->format = GL_LUMINANCE_ALPHA;
                texinfo->internalFormat = 2;
            }

            break;
        }

        case 1:     /* 8 bits index couleur */
        case 2:     /* 16-24-32 bits BVR */
        case 9:     /* 8 bits index couleur (RLE) */
        case 10:    /* 16-24-32 bits BVR (RLE) */
        {
            /* les images 8 et 16 bits seront converties en 24 bits */
            if( (int)header->pixel_depth <= 24 ) {
                texinfo->format = GL_RGB;
                texinfo->internalFormat = 3;
            }
            else { /* 32 bits */
                texinfo->format = GL_RGBA;
                texinfo->internalFormat = 4;
            }

            break;
        }
    }
}

Les dimensions de l'image (width et height) sont récupérées directement à partir de l'en-tête. En revanche il faut déterminer le nombre de composantes et le format de l'image à partir de son type TGA et du nombre de bits par pixel.

Les images en 8 et 16 bits seront converties à la lecture en 24 bits sauf pour les images en dégradé de gris, où l'on utilise le format GL_LUMINANCE. Les autres sont au format GL_RGB ou GL_RGBA. Une image 16 bits en dégradé de gris est composé de 8 bits pour l'intensité de gris et d'un canal alpha de 8 bits.

5. La palette de couleurs

Les images 8 bits (sauf dégradé de gris) nécessitent une palette de couleur. Chaque octet représentant un pixel dans les données de l'image, est en fait un index pour aller chercher sa couleur dans la palette. La palette est un tableau de couleurs.

Dans cet article on supposera que la palette a toujours une profondeur de 24 bits par couleur. C'est-à-dire, chaque couleur est composé de 8 bits pour le bleu, 8 bits pour le vert et 8 bits pour le rouge. La palette est en BVR (et non RVB !).

Dans notre fonction ReadTGAFile(), nous nous sommes arrêté au début des données de l'image. Nous devons maintenant tester s'il y a une palette de couleur et, le cas échéant, la stocker. Nous la stockeront dans le tableau colormap déclaré en début de fonction :

 
Sélectionnez
/* lecture de la palette (si présente) */
if( header.colormap_type )
{
    /* palette BVR */
    colormap = (ubyte *)malloc( sizeof( ubyte ) * header.cm_length * (header.cm_size >> 3));
    fread( colormap, sizeof( ubyte ), header.cm_length * (header.cm_size >> 3), fp );
}

L'expression (header.cm_size >> 3) nous donne la taille en octet d'une couleur à partir d'une taille en bits (division par 23 ou 8). On peut ainsi connaître la taille en octets de la palette et la lire.

Attention : ici nous allouons dynamiquement de la mémoire pour stocker la palette ; il ne faudra pas oublier de la libérer à la fin de la fonction, lorsque nous auront lus les données !

6. Lecture des données

Il nous faut maintenant lire les données pixels de l'image. Elle se trouvent juste après la palette couleur si celle-ci existe, sinon après le petit champ d'informations additionnelles suivant l'en-tête.

Il nous faut allouer un espace mémoire pour stocker les données pixels :

 
Sélectionnez
/* allocation de mémoire */
texinfo->texels = (GLubyte *)malloc( sizeof( GLubyte ) * texinfo->width * texinfo->height * texinfo->internalFormat );
if( !texinfo->texels ) {
    return 0;
}

À l'aide des variables image_type et pixel_depth de l'en-tête, nous allons devoir déterminer quelle méthode utiliser pour lire ces données. Le plus pratique je pense est de faire un choix conditionnel (switch) suivant image_type puis pour chaque cas, voir avec pixel_depth si c'est nécessaire.

Il y a quatre grand type d'images : les images en 16-24-32 bits, appelées également images en couleurs vraies, les images 8 bits avec palette, les images en dégradé de gris 8 ou 16 bits, et les images compressées (où l'on retrouve les trois premiers types à chaque fois). On va commencer par le plus simple et finir par les images compressées.

6.1. Les images en couleurs vraies

Les images en couleurs vraies sont des images en 16, 24 ou 32 bits où les données « complètes » de chaque pixel se suivent. Pour les images TGA, les pixels sont en Bleu-Vert-Rouge (BVR). Pour OpenGL, nous avons besoin de données stockées en Rouge-Vert-Bleu (RVB) (à moins d'utiliser une extension permettant d'utiliser directement des données au format BVR). Il suffit donc de lire le nombre d'octets approprié pour autant de pixels qu'il y'a dans l'image (pour connaître le nombre de pixels, il faut multiplier la hauteur par la largeur) :

 
Sélectionnez
void ReadTGA24bits( FILE *fp, gl_texture_t *texinfo )
{
    unsigned int i;

    for( i = 0; i < texinfo->width * texinfo->height; i++ )
    {
        /* lecture et conversion de BVR en RVB */
        texinfo->texels[ (i * 3) + 2 ] = (GLubyte)fgetc( fp );
        texinfo->texels[ (i * 3) + 1 ] = (GLubyte)fgetc( fp );
        texinfo->texels[ (i * 3) + 0 ] = (GLubyte)fgetc( fp );
    }
}

Pour chaque pixel, on lit bien 3 octets (24 bits). Pour connaître la position des données d'un pixel, il faut multiplier par 3 sa position i dans l'image, car on utilise 3 octet par pixel. Ensuite, on peut accéder aux composantes rouge, vert et bleu en ajoutant 0, 1 ou 2 à la position du début des données du pixel.

Pour une image 32 bits, c'est pareil sauf que l'on utilise 4 octets par pixel. Le dernier octet est la composante alpha (transparance) :

 
Sélectionnez
void ReadTGA32bits( FILE *fp, gl_texture_t *texinfo )
{
    unsigned int i;

    for( i = 0; i < texinfo->width * texinfo->height; i++ )
    {
        /* lecture et conversion de BVRA en RVBA */
        texinfo->texels[ (i * 4) + 2 ] = (GLubyte)fgetc( fp );
        texinfo->texels[ (i * 4) + 1 ] = (GLubyte)fgetc( fp );
        texinfo->texels[ (i * 4) + 0 ] = (GLubyte)fgetc( fp );
        texinfo->texels[ (i * 4) + 3 ] = (GLubyte)fgetc( fp );
    }
}

Les images en 16 bits sont un peu plus délicates, car il faut extraire les 3 composantes d'une zone mémoire de 2 octets. Chaque composante est codée sur 5 bits ; le bit de poids fort (le plus à gauche) de ces deux octets n'est pas utilisé (c'est une image 15 bits en fait).

Pour extraire chaque composante, on applique un masque binaire :

Composante Masque Masque en Binaire
rouge couleur & 7C00 01111100 00000000
vert couleur & 03E0 00000011 11100000
bleu couleur & 001F 00000000 00011111

Une fois la composante isolée, il faut faire un décalage de 10 bits vers la droite pour le rouge et 5 bits pour le vert afin d'avoir une valeur comprise entre 0 et 31. Il faut ramener cette valeur sur un intervalle de 0 à 255. Pour cela, on multiplie chaque composante obtenue par 8 (ou on fait un décalage à gauche de 3 rangs).

Pour chaque pixel, on lit 2 octets. On peut les lire d'un bloc ou les lire octet par octet. Dans de dernier cas, il faut décaller le premier octet de 8 rangs pour reconstituer la valeur sur 16 bits.

 
Sélectionnez
void ReadTGA16bits( FILE *fp, gl_texture_t *texinfo )
{
    unsigned int i;
    short color;

    for( i = 0; i < texinfo->width * texinfo->height; i++ )
    {
        /* lecture du pixel sur 16 bits */
        color = fgetc( fp ) + (fgetc( fp ) << 8);

        /* convertion en 24 bits */
        texinfo->texels[ (i * 3) + 0 ] = (GLubyte)(((color & 0x7C00) >> 10) << 3);
        texinfo->texels[ (i * 3) + 1 ] = (GLubyte)(((color & 0x03E0) >>  5) << 3);
        texinfo->texels[ (i * 3) + 2 ] = (GLubyte)(((color & 0x001F) >>  0) << 3);
    }
}

6.2. Les images 8 bits avec palette couleurs

La lecture d'images 8 bits avec palette n'est pas difficile non plus. Chaque « donnée pixel » est codée sur un octet. Cet octet est l'index de la couleur 24 bits (dans le cadre de cet article) dans la palette.

Pour créer une texture OpenGL à partir d'une image 8 bits, on va d'abord la convertir en 24 bits.

 
Sélectionnez
void ReadTGA8bits( FILE *fp, ubyte *colormap, gl_texture_t *texinfo )
{
    unsigned int i;
    ubyte color;

    for( i = 0; i < texinfo->width * texinfo->height; i++ )
    {
        /* lecture du pixel */
        color = fgetc( fp );

        /* conversion en 24bits RVB */
        texinfo->texels[ (i * 3) + 2 ] = (GLubyte)colormap[ (color * 3) + 0 ];
        texinfo->texels[ (i * 3) + 1 ] = (GLubyte)colormap[ (color * 3) + 1 ];
        texinfo->texels[ (i * 3) + 0 ] = (GLubyte)colormap[ (color * 3) + 2 ];
    }
}

L'accès aux composantes des couleurs de la palette se fait de la même façon que pour une image 24 bits. On en profite ici pour échanger les composantes bleu et rouges dans notre tableau de pixels.

6.3. Les images en dégradé de gris

Les images en dégradé de gris sont simples à traiter. Pour les images 8 bits, chaque octet représente la valeur de l'intensité lumineuse. Pour les images 16 bits, le second octet est la valeur du cannal alpha.

 
Sélectionnez
void ReadTGAgray8bits( FILE *fp, gl_texture_t *texinfo )
{
    unsigned int i;

    for( i = 0; i < texinfo->width * texinfo->height; i++ )
    {
        /* lecture de l'intensité lumineuse du pixel */
        texinfo->texels[i] = (GLubyte)fgetc( fp );
    }
}

Pour ce type d'image, on peut même lire les données en une instruction :

 
Sélectionnez
fread( texinfo->texels, sizeof( GLubyte ), texinfo->width * texinfo->height, fp );

Pour une image 16 bits, seule une ligne change :

 
Sélectionnez
void ReadTGAgray16bits( FILE *fp, gl_texture_t *texinfo )
{
    unsigned int i;

    for( i = 0; i < texinfo->width * texinfo->height; i++ )
    {
        /* lecture de l'intensité lumineuse + cannal alpha du pixel */
        texinfo->texels[ (i * 2) + 0 ] = (GLubyte)fgetc( fp );
        texinfo->texels[ (i * 2) + 1 ] = (GLubyte)fgetc( fp );
    }
}

6.4. Les images avec compression RLE

Les images compressées utilisent un algorithme de compression appelé Run Lenght Encoding (RLE). Il est est simple et non destructeur (les données de l'image ne sont pas altérée  on retrouve au décodage la même chose qu'à l'encodage).

Le principe est le suivant : les champs de pixels consécutifs égaux sont remplacés par deux valeurs, un pour leur nombre, l'autre pour leur valeur. Ainsi on on gagne à partir de trois pixels consécutifs égaux pour une image 8 bits (la valeur et le pixel sont codés sur un octet chacun). Dans le cas où il y a une large zone de pixels complètement différents les un des autres, on l'indique sur un octet ainsi que leur nombre (les deux sur le même octet). La première valeur est toujours stockée sur un octet. La seconde dépend du type d'image (1, 2, 3 ou 4 octets).

Concrètement, ce que l'on fait : on lit un octet. Le bit de poids fort (le plus à gauche) indique s'il s'agit un champs d'octets consécutifs égaux (1) ou pas (0). Les 7 bits restant sont le nombre d'octets égaux ou non égaux du champs que l'on va traiter. Dans le cas d'un champs d'octets égaux, on lit un pixel, qui est la valeur de tous les pixels de ce champs, et on initialise ces dernier avec. Si le champs n'est pas constitué de pixels égaux, on lit chaque pixel à partir du fichier.

Remarque : comme le nombre de pixels par champs est codé sur 7 bits, on ne peut avoir au plus que 128 pixels traités par packet.

Voici comment faire pour une image 24 bits. Les autres types d'image se lisent pareil, avec les méthodes de décodage des octets vues plus haut sans compression. Voyez les sources complètes pour les autres types d'image (voir fin de l'article) :

 
Sélectionnez
void ReadTGA24bitsRLE( FILE *fp, gl_texture_t *texinfo )
{
    int i, size;
    ubyte rgb[3];
    ubyte packet_header;
    GLubyte *ptr = texinfo->texels;

    while( ptr < texinfo->texels + (texinfo->width * texinfo->height) * 3 )
    {
        /* lit le premier octet */
        packet_header = fgetc( fp );
        size = 1 + (packet_header & 0x7f);

        if( packet_header & 0x80 )
        {
            /* run-length packet */
            fread( rgb, sizeof( ubyte ), 3, fp );

            for( i = 0; i < size; i++, ptr += 3 )
            {
                ptr[0] = rgb[2];
                ptr[1] = rgb[1];
                ptr[2] = rgb[0];
            }
        }
        else
        {
            /* non run-length packet */
            for( i = 0; i < size; i++, ptr += 3 )
            {
                ptr[2] = (GLubyte)fgetc( fp );
                ptr[1] = (GLubyte)fgetc( fp );
                ptr[0] = (GLubyte)fgetc( fp );
            }
        }
    }
}

Comme il peut y avoir d'autres données après les données de l'image, on doit veiller à ne pas dépasser la taille de l'image. On utilise un masque binaire pour extraire le premier bit et les sept autres.

7. Création de la texture

Maintenant que l'on a lu et stocké les données de l'image sous un format exploitable par OpenGL, on peut aisément créer notre texture à partir de l'objet gl_texture_t :

 
Sélectionnez
/* charge une image TGA */
if( !ReadTGAFile( filename, &gltex ) ) {
    printf( "error: couldn't load %s!\n", filename );
    exit(1);
}

/* génère la texture */
glGenTextures( 1, &texid );
glBindTexture( GL_TEXTURE_2D, texid );

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

glTexImage2D( GL_TEXTURE_2D, 0, gltex.internalFormat, gltex.width, gltex.height,
                0, gltex.format, GL_UNSIGNED_BYTE, gltex.texels );

/* OpenGL possède sa propre copie de l'image */
free( gltex.texels );

D'abord on utilise notre fonction ReadTGAFile() pour initialiser gltex, un objet de type gl_texture_t. En cas de succès de la lecture de l'image, on crée la texture OpenGL avec les fonctions faites pour. Vérifiez bien que les dimensions de votre image sont multiple de 2 ou alors il vous faudra utiliser gluScaleImage() ou bien utiliser gluBuild2DMipmaps() à la place de glTexImage2D() (dans ce cas il faut aussi modifier le paramètre de filtrage associé à GL_TEXTURE_MAG_FILTER).

Une fois la texture créée, on libère la mémoire allouée pour stocker les données des pixels de l'image car OpenGL possède sa propre copie de ces données, il est donc inutile d'encombrer la mémoire avec ça.

8. Conclusion

Cet article vous a permis de voir comment on crée une texture à partir d'une image stockée dans un fichier, ici un fichier TGA. Pour d'autres formats, ça se passe un peu de la même manière : lecture de l'en-tête, choix de la méthode appropriée pour lire les données suivant le type de l'image, etc.

Il y a deux façons de lire un fichier : lire les éléments un par un, comme ici, ou lire le fichier tout entier dans une mémoire tampon puis extraire les données à partir de cette zone mémoire. Cette dernière est peut-être plus rapide car demande moins d'accès fichiers. Elle est d'ailleurs facilement adaptable ici.

Pour certains formats, des bibliothèques toutes prêtes existent pour manipuler les fichiers images comme libpng pour le format PNG, libjpeg pour le format JPEG ou libtiff pour les images TIFF. Il existe églament des bibliothèques permettant de gérer plusieurs formats de fichiers image avec une seule fonction, comme SDL_image.

Vous pouvez télécharger les sources complètes (tga.c) avec un exemple simple et les réutiliser à votre guise ; elles sont entièrement libres. Je me suis inspiré d'une documentation plus complète trouvée sur le net il y'a longtemps, je ne sais plus où... Vous pouvez tester le programme avec des images de test TGA de différents types. Contact : tfc_duke, (CHEZ) club-internet, (POINT) fr.

Version PDF : télécharger (54.1 Ko)