Charger des images PNG
Date de publication : 05/02/2006 , Date de mise à jour : 06/02/2006
Par
David Henry (Autres articles)
Cet article a pour objectif d'expliquer comment charger une image PNG afin de créer
une texture OpenGL. Nous utiliserons pour cela la bibliothèque libpng.
Les exemples donnés dans ce document sont écrits en C.
1. Introduction
2. Texture OpenGL
3. Structures de la libpng
4. Lecture d'une image PNG depuis un fichier
4.1. Ouverture du fichier et vérification
4.2. Création des structures png_struct et png_info
4.3. Gestion des erreurs de libpng
4.4. Initialisation de la source de lecture du fichier
4.5. Transformation de l'image source
4.6. Récupération des informations sur l'image
4.7. Lecture des données pixels
4.8. Fin de la lecture
5. Lecture d'une image PNG depuis une zone mémoire
5.1. Buffer fichier
5.2. Lecture depuis un buffer fichier
6. Gestion des erreurs
7. Création de la texture
8. Conclusion
1. Introduction
Cet article a pour objectif d'expliquer comment charger une image PNG afin de créer
une texture OpenGL. Nous utiliserons pour cela la bibliothèque libpng.
Les exemples donnés dans ce document sont écrits en C.
« PNG » signifie « Portable network graphics ». C'est un format
de fichier qui a été créé dans l'intention de remplacer le format
GIF (1). Il possède les
caractéristiques suivantes :
- Capable de stocker des images avec palette couleurs (8 bits), en dégradé de gris
(8 et 16 bits) et en vraies couleurs (24 et 32 bits) ;
- Canal alpha (256 niveaux de transparence pour les images 32 bits, un pour les
images 8 bits) ;
- Utilise un algorithme de compression sans perte ;
La bibliothèque libpng est en quelque sorte la bibliothèque officielle pour
manipuler les fichiers PNG. Elle a été écrite en accompagnement aux spécifications
du fichier, disponible sur
http://www.libpng.org/pub/png/. Cette
bibliothèque utilise zlib pour la compression et décompression
des images. Elle est
Open Source.
La libpng propose un ensemble de fonctions pour manipuler facilement les images
PNG et de plusieurs manières possibles. Cet article ne se veut pas être une
documentation exhaustive de l'utilisation de libpng. Pour plus d'informations,
reportez vous à son
manuel en
ligne.
Nous allons voir ici deux méthode pour lire les images PNG. La première, en lisant
les données depuis un fichier, et la seconde en chargeant l'image depuis une zone
mémoire où les données seraient stockées.
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 :
typedef struct
{
GLuint width;
GLuint height;
GLenum format;
GLint internalFormat;
GLubyte *texels;
} 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. Structures de la libpng
Les deux principales structures de la libpng sont png_struct et
png_info. png_struct représente un objet « image png »
et est utilisée en interne par la bibliothèque. On la passe à quasiment toutes les
fonctions de libpng. png_info quant à elle, permet à l'utilisateur de
récupérer des informations sur l'image, comme ses dimensions, sa profondeur, sa palette,
etc.
Toutes les fonctions de la bibliothèque libpng commencent par « png_ ».
Le fichier d'en-tête à inclure pour utiliser la libpng est png.h. Ce fichier
inclut les fichiers pngconf.h et zlib.h. Ce dernier inclut
zconf.h. Si vous comptez redistribuer la bibliothèque avec votre code source,
ne les oubliez pas.
Pour utiliser la libpng, assurez vous donc d'inclure png.h :
Vous devrez également compiler votre programme avec libpng et zlib. En ligne de commande
(ou dans un Makefile), utilisez la commande libpng12-config --cflags pour obtenir
les paramètres de compilation nécessaires à libpng et libpng12-config --libs
pour l'inclusion des bibliothèques à la liaison. Exemple avec un programme constitué de deux
fichiers source, main.c et png.c :
gcc -c main.c -o main.o
gcc `libpng12-config --cflags` -c png.c -o png.o
gcc `libpng12-config --libs` -lz main.o png.o -o monprogramme |
4. Lecture d'une image PNG depuis un fichier
Nous allons écrire une fonction ReadPNGFromFile() prenant en paramètre
un nom de fichier et renvoyant une structure gl_texure_t comme définie
plus haut. Voici le prototype de la fonction :
gl_texture_t *ReadPNGFromFile (const char *filename); |
4.1. Ouverture du fichier et vérification
Tout d'abord, nous devons ouvrir le fichier :
FILE *fp;
png_byte magic[8];
fp = fopen (filename, "rb");
if (!fp)
{
fprintf (stderr, "error: couldn't open \"%s\"!\n", filename);
return NULL;
}
fread (magic, 1, sizeof (magic), fp);
if (!png_check_sig (magic, sizeof (magic)))
{
fprintf (stderr, "error: \"%s\" is not a valid PNG image!\n",
filename);
fclose (fp);
return NULL;
} |
Les huit premiers octets d'un fichier PNG contiennent un numéro magique servant
à authentifier qu'il s'agit d'un fichier PNG. Si ces huits octets ne correspondent
pas à la signature d'un fichier PNG, inutile de continuer. La fonction
png_check_sig() de libpng permet de tester si une chaîne contient
bien la signature d'un fichier PNG.
4.2. Création des structures png_struct et png_info
Maintenant que l'on s'est assuré qu'il s'agit bien d'un fichier PNG, on peut
créer nos deux structures png_struct et png_info. Les
fonctions png_create_read_struct () et
png_create_info_struct () se chargent de ça :
png_structp png_ptr = png_create_read_struct
(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr)
{
fclose (fp);
return NULL;
}
png_infop info_ptr = png_create_info_struct (png_ptr);
if (!info_ptr)
{
fclose (fp);
png_destroy_read_struct (&png_ptr, NULL, NULL);
return NULL;
} |
Tout d'abord, vous devez remarquer que l'on utilise le type png_structp
au lieu de png_struct comme je l'ai dis plus haut. En fait,
png_structp est définie comme étant un pointeur sur un png_struct.
png_ptr est donc un pointeur sur un png_struct. Il en est de
même pour info_ptr qui est un pointeur sur un png_info.
La fonction png_create_read_struct () prend quatre paramètres. Le premier
est la version de la bibliothèque. La macro PNG_LIBPNG_VER_STRING permet
de passer la version de la bibliothèque avec laquelle on compile le programme. Les trois
autres paramètres (optionnels) servent à définir les fonctions à utiliser pour la gestion
des erreurs ; nous les verrons plus tard, en étudiant la seconde méthode de lecture
d'une image PNG. Pour le moment, on peut les laisser à NULL.
La fonction png_create_info_struct () ne nécessite que notre objet
png_struct en paramètre. En cas d'échec, on détruit nos structures avec
png_destroy_read_struct (). Cette fonction prend en premier paramètre
un png_struct (impérativement non nul) et en second un png_info.
Le dernier paramètre est aussi une structure png_info que nous n'utiliseront
pas ici.
Il est également temps de créer un objet de type gl_texture_t pour
stocker les informations et données nécessaires à la création de notre texture :
gl_texture_t *texinfo = (gl_texture_t *)malloc (sizeof (gl_texture_t)); |
4.3. Gestion des erreurs de libpng
libpng utilise le mécanisme de setjmp/longjmp pour la
gestion des erreurs. setjmp() et longjmp() sont des fonctions
système qui proposent un mécanisme similaire aux exceptions en C++. setjmp()
permet de sauvegarder le contexte de la pile à un endroit donné. longjmp()
permet de faire un saut vers un contexte de pile sauvegardé par setjmp().
La fonction setjmp() renvoie 0 lorsqu'elle revient directement et
une valeur non nulle lorsqu'elle revient à travers un appel à longjmp().
La structure png_struct possède une variable de type jmp_buf
permettant de mémoriser un contexte (la destination d'un saut via longjmp())
pour y revenir en cas d'erreur lors de la manipulation des images. On y accède à l'aide
de la fonction png_jmpbuf().
Il faut placer cette destination de saut avant le début du traitement de l'image
et gérer l'interruption de la lecture en cas d'erreur. :
if (setjmp (png_jmpbuf (png_ptr)))
{
fclose (fp);
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
if (texinfo)
{
if (texinfo->texels)
free (texinfo->texels);
free (texinfo);
}
return NULL;
} |
Lorsque libpng rencontre une erreur, il exécute un saut via longjmp()
à cet endroit et se retrouve donc à l'intérieur du bloc if. On peut
ainsi détruire les structures proprement et désallouer la mémoire utilisée pour
la texture.
Remarque : si vous omettez de placer le setjmp(), libpng
appellera alors abord() qui procèdera à la terminaison de votre programme.
Ce n'est probablement pas un comportement souhaité pour votre application.
4.4. Initialisation de la source de lecture du fichier
libpng doit connaître la source de lecture du fichier. Il est possible de gérer
soit même la lecture des données, depuis une source quelconque. libpng propose par
défaut de lire l'image depuis un fichier en utilisant la fonction standard
fread(). Pour cela, il faut lui spécifier le pointeur de fichier
avant de commencer la lecture :
png_init_io (png_ptr, fp);
png_set_sig_bytes (png_ptr, sizeof (magic)); |
La fonction png_init_io() permet de passer notre pointeur de fichier
à la structure png_struct. On spécifie ensuite à libpng le nombre
d'octets déjà lus dans le fichier à l'aide de la fonction
png_set_sig_bytes (). Rappelez vous, on a déjà lu huit octets pour
la signature du fichier. libpng sait ainsi qu'il ne faut pas relire la signature.
Il est à présent possible de commencer à lire le fichier. Commençons par les
informations stockées dans notre png_info :
png_read_info (png_ptr, info_ptr); |
4.5. Transformation de l'image source
Le format PNG peut contenir des images avec palette couleurs, en dégradé de gris et
en couleurs vraies. On utilisera les formats GL_RGB et GL_RGBA
pour les textures en couleurs vraies et GL_LUMINANCE et
GL_LUMINANCE_ALPHA pour les images en dégradé de couleurs. On peut
spécifier à libpng un certain nombre de transformations de l'image source avant de
commencer la lecture. Récupérons d'abord le nombre de bits par pixel et le type d'image
stockée dans le fichier PNG (PNG_COLOR_TYPE_GRAY,
PNG_COLOR_TYPE_RGB, etc.) :
int bit_depth, color_type;
bit_depth = png_get_bit_depth (png_ptr, info_ptr);
color_type = png_get_color_type (png_ptr, info_ptr); |
Il est nécessaire de transformer les images en palette couleurs en images couleurs
vraies :
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb (png_ptr); |
Certaines images en dégradé de gris peuvent être codées sur 1, 2 ou 4 bits. Il
faut les étendre à du 8 bits pour pouvoir ensuite les utiliser avec OpenGL avec
le format GL_LUMINANCE :
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_gray_1_2_4_to_8 (png_ptr);
if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha (png_ptr); |
Certaines images peuvent stocker un canal de couleur sur 16 bits qu'il faut alors
ramener à 8 bits. De même, certaines images en 1, 2 ou 4 bits peuvent coder plusieurs
canaux sur un octet (afin de gagner en mémoire utilisée) qu'il faut étendre à un octet
par canal. libpng permet d'effectuer ces opérations sans perdre d'information sur les
pixels :
if (bit_depth == 16)
png_set_strip_16 (png_ptr);
else if (bit_depth < 8)
png_set_packing (png_ptr); |
Les images avec palette sont ainsi converties en images en couleurs vraies ou en
images avec dégradé de gris sans palette. Chaque canal de couleur (rouge, vert, bleu,
luminance, alpha) sera codé sur 8 bits. Il reste à valider ces transformations avec
la fonction png_read_update_info() :
png_read_update_info (png_ptr, info_ptr); |
Ces transformations seront appliquées lors de la lecture de l'image depuis le
fichier. L'image source (stockée dans le fichier) ne sera pas modifiée. Mais l'image
résultante (que l'on aura en mémoire) ne sera donc pas forcément exactement la même.
4.6. Récupération des informations sur l'image
Après avoir spécifié les transformations voulues, nous pouvons récupérer les
informations sur l'image résultante afin d'initialiser les variables de notre structure
gl_texture_t. La fonction png_get_IHDR() nous permet de
récuperer directement les dimensions de l'image, le nombre de bits par pixels et le
type d'image PNG :
png_get_IHDR (png_ptr, info_ptr,
(png_uint_32*)(&texinfo->width),
(png_uint_32*)(&texinfo->height),
&bit_depth, &color_type,
NULL, NULL, NULL); |
Il ne nous reste plus que le format et le nombre d'octets par pixels (le
« internal format » nécessaire à glTexImage2D()) à
retrouver, que l'on peut facilement déduire à partir du type d'image PNG :
switch (color_type)
{
case PNG_COLOR_TYPE_GRAY:
texinfo->format = GL_LUMINANCE;
texinfo->internalFormat = 1;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
texinfo->format = GL_LUMINANCE_ALPHA;
texinfo->internalFormat = 2;
break;
case PNG_COLOR_TYPE_RGB:
texinfo->format = GL_RGB;
texinfo->internalFormat = 3;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
texinfo->format = GL_RGBA;
texinfo->internalFormat = 4;
break;
default:
break;
} |
4.7. Lecture des données pixels
Nous sommes à présent pret pour lire les données pixels de l'image. Commençons
par allouer de la mémoire pour les stocker dans notre gl_texture_t :
texinfo->texels = (GLubyte *)malloc (sizeof (GLubyte) * texinfo->width
* texinfo->height * texinfo->internalFormat); |
La lecture de l'image se fait à l'aide de la fonction png_read_image().
Cette fonction attend en paramètre notre objet png_struct et un tableau
de pointeurs pointant sur chacune des lignes de pixels de l'image.
On accède simplement au début d'une ligne de pixels de l'image avec l'instruction
suivante :
row_pointers[i] = texinfo->texels[i * texinfo->width * texinfo->internalFormat]; |
Cependant le format PNG n'utilise pas le même système de coordonnées 2D. Le premier
pixel d'une image PNG est situé dans le coin haut-gauche, tandis qu'OpenGL situe le
premier pixel dans le coin bas-gauche. Il faut donc inverser verticalement l'image. Ceci
peut se faire pendant la lecture de l'image en modifiant un peu notre tableau de
pointeurs :
row_pointers[i] = texinfo->texels[(texinfo->height - (i + 1))
* texinfo->width * texinfo->internalFormat]; |
La lecture des pixels donne donc ceci :
png_bytep *row_pointers;
row_pointers = (png_bytep *)malloc (sizeof (png_bytep) * texinfo->height);
for (i = 0; i < texinfo->height; ++i)
{
row_pointers[i] = (png_bytep)(texinfo->texels +
((texinfo->height - (i + 1)) * texinfo->width * texinfo->internalFormat));
}
png_read_image (png_ptr, row_pointers);
free (row_pointers); |
row_pointers est de type png_bytep, qui est un pointeur
sur un octet. row_pointers est donc bien un tableau de pointeurs. Il faut
aussi penser à le libérer dans le bloc if de setjmp().
png_read_image() va ainsi remplir notre tableau
texinfo->texels via le tableau de pointeurs.
4.8. Fin de la lecture
Nous avons lu et stockée notre image dans un format qui nous permettra ensuite
de créer une texture OpenGL. Il faut terminer la lecture de l'image et détruire
proprement les structures allouées pour le décodage de l'image :
png_read_end (png_ptr, NULL);
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
fclose (fp); |
On peut maintenant utiliser la structure gl_texture_t pour créer
une texture OpenGL. Vous pouvez récupérer le code source complet de cette méthode
de lecture depuis un fichier : png.c (9,0 Ko). Nous
allons maintenant une autre méthode de lecture : depuis une zone mémoire où
l'intégralité du fichier PNG est stockée.
5. Lecture d'une image PNG depuis une zone mémoire
Il vous arrivera peut-être d'avoir à traiter des fichiers qui sont déjà stockés
en mémoire et donc sans utiliser les fonctions d'entrée/sortie standards
(fopen(), fread(), fclose(), etc.).
De plus, vous pouvez préferer cette méthode pour lire des images depuis des
fichiers sur le disque. Il est en effet plus rapide de lire la totalité d'un fichier
dans un bloc mémoire que de faire beaucoup d'appels d'entrée/sortie pour lire un
fichier morceau par morceau.
5.1. Buffer fichier
Commençons par définir une structure qui contiendra les données d'un fichier
en mémoire :
typedef struct
{
char name[256];
unsigned char *data;
long length;
long offset;
} file_buffer_t; |
Cette structure contient le nom du fichier (name), ses données
(data), la taille du fichier en octets (length) et
une variable offset indicant la position actuelle dans le fichier
(utile pour la lecture).
Voyons rapidement la lecture d'un fichier dans une structure
file_buffer_t :
file_buffer_t *
readFile (const char *filename)
{
file_buffer_t *file;
FILE *fp;
fp = fopen (filename, "rb");
if (!fp)
{
fprintf (stderr, "error: couldn't open \"%s\"!\n", filename);
return NULL;
}
file = (file_buffer_t *)malloc (sizeof (file_buffer_t));
if (!file)
{
fprintf (stderr, "Error: memory allocation failed "
"for \"%s\"\n", filename);
fclose (fp);
return NULL;
}
strncpy (file->name, filename, sizeof (file->name));
fseek (fp, 0, SEEK_END);
file->length = ftell (fp);
file->offset = 0;
fseek (fp, 0, SEEK_SET);
file->data = (unsigned char *)malloc (file->length);
if (!file->data)
{
fprintf (stderr, "Error: memory allocation failed "
"for \"%s\"\n", filename);
fclose (fp);
free (file);
return NULL;
}
fread (file->data, 1, file->length, fp);
fclose (fp);
return file;
} |
Cette fonction est assez simple. Elle crée une structure file_buffer_t
et ouvre le fichier. Ensuite elle calcule la taille du fichier pour allouer assez
d'espace afin de stocker le fichier tout entier en mémoire, puis lit le fichier. Elle
retourne l'objet file_buffer_t créé et initialisé.
5.2. Lecture depuis un buffer fichier
Nous allons modifier un peu notre fonction de lecture d'une image PNG. Voici le
prototype de notre nouvelle fonction :
gl_texture_t *ReadPNGFromMemory (const file_buffer_t *file); |
Le contenu de cette fonction sera sensiblement le même que celui de
ReadPNGFromFile(), je ne vais donc pas tout recopier.
On peut commencer déjà par supprimer tout ce qui concerne l'ouverture du fichier,
puisque nous traitons avec le contenu du fichier directement. La vérification de la
signature du fichier est légèrement modifiée comme suit :
if (!png_check_sig (file->data, 8))
{
fprintf (stderr, "error: \"%s\" is not a valid PNG image!\n",
file->name);
return NULL;
} |
On compare directement avec les huit premiers octets du buffer. Mais il ne s'agit
pas ici d'une grosse modification, simplement d'une adaptation du code.
La seule modification réside dans la manière de lire les données de l'image source.
Rappelez vous dans ReadPNGFromFile(), on avait passé à libpng notre
pointeur sur fichier afin qu'il utilise les fonctions d'entrée/sortie standards. Nous
pouvons spécifier à libpng la fonction à appeler pour lire un bloc de données avec
png_set_read_fn(). Cette fonction prend en paramètre un pointeur sur les
données source (ici ce sera notre file_buffer_t) et un pointeur sur
une fonction dont le prototype est :
void user_read_data (png_structp png_ptr, png_bytep data, png_size_t length); |
Les données source que vous passez à png_set_read_fn() peuvent être
ce que vous voulez, puisque c'est vous qui les manipulerez dans votre fonction
« user_read_data() ».
Cette fonction user_read_data() attend que length octets
soient copiés depuis la source dans data. Dans notre cas, on peut donc
l'écrire ainsi :
void
png_read_from_mem (png_structp png_ptr, png_bytep data, png_size_t length)
{
file_buffer_t *src = (file_buffer_t *)png_get_io_ptr (png_ptr);
memcpy (data, src->data + src->offset, length);
src->offset += length;
} |
La fonction png_get_io_ptr() nous permet de récupérer le pointeur sur
la source de données que l'on a passé à libpng au moment d'appeler
png_set_read_fn(). On copie ensuite les données demandées dans
data et on met à jour le curseur de position (offset).
Retournons à ReadPNGFromMemory(). Remplacer les instructions
png_init_io(...) et png_set_sig_buytes(...) par le code
suivant :
png_set_read_fn (png_ptr, (png_voidp *)file, png_read_from_mem); |
file est notre objet file_buffer_t et
png_read_from_mem() la fonction définie ci-dessus. Nous ne spécifions plus
à libpng la lecture des huit premiers octets contenant la signature du fichier car
nous avons passé à libpng un pointeur sur le début des données du fichier (et non plus
à l'adresse de base + 8). libpng devra donc les relire lui-même.
Il s'agit de l'unique modification de code pour charger une image PNG depuis une
zone mémoire, le reste n'étant qu'une adaptation du code pour remplacer le pointeur
sur fichier par notre objet file_buffer_t. Le fichier
png_mem.c (11,0 Ko) contient le code complet de la fonction
ReadPNGFromMemory(). Il contient également un exemple de gestion des erreurs
personnalisée, que nous allons voir maintenant.
6. Gestion des erreurs
Comme pour la fonction de lecture des données source, il est possible de spécifier
à libpng les fonctions à appeler lorsqu'une erreur ou un avertissement est rencontré, et
choisir vous même l'action à effectuer dans ces situation. Par exemple, rediriger le
message d'erreur vers un fichier plutôt qu'à l'écran.
Il est possible à la création du png_struct, de préciser les fonctions
à appeler en cas d'erreur ou avertissement. Ces fonctions doivent être de la forme :
void user_error_fn (png_structp png_ptr, png_const_charp error_msg);
void user_warning_fn(png_structp png_ptr, png_const_charp warning_msg); |
png_create_read_struct() prend également en paramètre un pointeur sur
une structure quelconque que le programmeur pourra utiliser à sa guise, pour par
exemple stocker le message d'erreur. Dans notre exemple, on peut s'en servrir pour
stocker le nom du fichier que l'on est en train de charger et ainsi pouvoir dire
pour quel fichier l'erreur s'est produite. Ce pointeur est accessible ensuite à l'aide
de png_get_error_ptr().
Modifions l'appel à png_create_read_struct() :
png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
(png_voidp *)file->name, png_error_fn, png_warning_fn); |
Nous allons maintenant écrire nos fonctions callback,
png_error_fn() et png_warning_fn() :
void
png_error_fn (png_structp png_ptr, png_const_charp error_msg)
{
fprintf (stderr, "png_error: %s (%s)\n", error_msg,
(char *)png_get_error_ptr (png_ptr));
longjmp (png_jmpbuf (png_ptr), 1);
}
void
png_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
{
fprintf (stderr, "png_warning: %s (%s)\n", warning_msg,
(char *)png_get_error_ptr (png_ptr));
} |
Dans cet exemple, on se contente d'afficher le message d'erreur (ou
d'avertissement) sur la sortie d'erreur et le nom de fichier pour lequel
il a été émis.
Vous remarquerez que pour la fonction png_error_fn() on
fait un saut avec longjmp à l'endroit définit précédemment avec
setjmp(). Si l'on ne le fait pas nous même, libpng exécutera notre
fonction d'erreur personnalisée, puis la sienne qui exécutera le
longjmp().
Remarque : pour les développeurs C++, il serait tentant ici de lancer
une exception plutôt que d'utiliser le mécanisme setjmp/longjmp.
C'est malheureusement impossible, car libpng est écrite en C et l'exception lancée ne
sera jamais attrapée.
La gestion des erreurs est illustrée dans le second exemple de code,
png_mem.c (11,0 Ko).
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 :
gl_texture_t *gltex = ReadPNGFromFile (filename);
if (!gltex)
{
fprintf (stderr, "error: couldn't load %s!\n", filename);
exit (-1);
}
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);
free (gltex->texels);
free (gltex); |
D'abord on utilise notre fonction ReadPNGFromFile() 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.
L'utilisation de ReadPNGFromMemory() est quasiment identique. Il y
juste une étape en plus : la lecture du fichier dans un buffer.
8. Conclusion
Nous avons vu dans cet article deux manières de lire une image PNG à l'aide de
la bibliothèque libpng : depuis un fichier, avec les fonctions
d'entrée/sortie standards, et depuis une zone mémoire contenant la totalité du fichier.
Nous avons ensuite vu rapidement la gestion personnalisée des erreurs.
Deux programmes d'exemples sont disponibles :
- png.c (9,0 Ko), lecture d'une image PNG depuis un
fichier ;
- png_mem.c (11,0 Ko), lecture d'une image PNG depuis
une zone mémoire et gestion personnalisée des erreurs ;
À noter que la lecture des images JPEG, à l'aide de la bibliothèque libjpeg,
ressemble beaucoup à la lecture d'images PNG.
Si vous avez des remarques sur cet article, vous pouvez me contacter par mail à
tfc_duke (CHEZ) club-internet (POINT) fr.
| (1) | Graphics interchange format |
 

Cet article est mis à disposition sous un contrat
Creative Commons (licence CC-BY-ND).
|