Écrit par David Henry, le 5 février 2006
Mis à jour le 18 août 2007
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. Il possède les caractéristiques suivantes :
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.
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
:
/* OpenGL texture info */ struct gl_texture_t { GLuint width; /* largeur */ GLuint height; /* hauteur */ GLenum format; /* RVB, RVBA, Luminance, Luminance Alpha */ GLint internalFormat; /* composantes d'un texel */ GLuint id; GLubyte *texels; /* données de l'image */ };
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
.
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 :
#include <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
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 :
struct gl_texture_t *ReadPNGFromFile (const char *filename);
Tout d'abord, nous devons ouvrir le fichier :
FILE *fp; png_byte magic[8]; /* Open image file */ fp = fopen (filename, "rb"); if (!fp) { fprintf (stderr, "error: couldn't open \"%s\"!\n", filename); return NULL; } /* Read magic number */ fread (magic, 1, sizeof (magic), fp); /* Check for valid magic number */ 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.
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 :
/* Create a png read struct */ png_structp png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { fclose (fp); return NULL; } /* Create a png info struct */ 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 :
/* Create our OpenGL texture object */ struct gl_texture_t *texinfo = (struct gl_texture_t *) malloc (sizeof (struct gl_texture_t));
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. :
/* Initialize the setjmp for returning properly after a libpng error occured */ 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.
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 :
/* Setup libpng for using standard C fread() function with our FILE pointer */ png_init_io (png_ptr, fp); /* Tell libpng that we have already read the magic number */ 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
:
/* Read png info */ png_read_info (png_ptr, info_ptr);
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; /* Get some usefull information from header */ 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 :
/* Convert index color images to RGB images */ 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
:
/* Convert 1-2-4 bits grayscale images to 8 bits grayscale. */ 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()
:
/* Update info structure to apply transformations */ 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.
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 :
/* Retrieve updated information */ png_uint_32 w, h; png_get_IHDR (png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL); texinfo->width = w; texinfo->height = h;
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: /* Badness */ break; }
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
:
/* We can now allocate memory for storing pixel data */ 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; /* Setup a pointer array. Each one points at the begening of a row. */ 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)); } /* Read pixel data using row pointers */ png_read_image (png_ptr, row_pointers); /* We don't need row pointers anymore */ 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.
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 :
/* Finish decompression and release memory */ 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.
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.
Commençons par définir une structure qui contiendra les données d'un fichier en mémoire :
/* File buffer struct */ struct file_buffer_t { char name[256]; unsigned char *data; long length; long offset; };
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
:
struct file_buffer_t * readFile (const char *filename) { struct file_buffer_t *file; FILE *fp; /* Open file */ fp = fopen (filename, "rb"); if (!fp) { fprintf (stderr, "error: couldn't open \"%s\"!\n", filename); return NULL; } /* Create a file buffer */ file = (struct file_buffer_t *)malloc (sizeof (struct file_buffer_t)); if (!file) { fprintf (stderr, "Error: memory allocation failed " "for \"%s\"\n", filename); fclose (fp); return NULL; } /* Copy file name */ strncpy (file->name, filename, sizeof (file->name)); /* Get file length */ fseek (fp, 0, SEEK_END); file->length = ftell (fp); file->offset = 0; fseek (fp, 0, SEEK_SET); /* Allocate memory for file data */ 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; } /* Read whole file data */ 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é.
Nous allons modifier un peu notre fonction de lecture d'une image PNG. Voici le prototype de notre nouvelle fonction :
struct gl_texture_t *ReadPNGFromMemory (const struct 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 :
/* Check for valid magic number */ 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 = (struct file_buffer_t *)png_get_io_ptr (png_ptr); /* Copy data from image buffer */ memcpy (data, src->data + src->offset, length); /* Advance in the file */ 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 :
/* Set "png_read" callback function and give source of data */ 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.
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()
:
/* Create a png 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).
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
:
/* Load PNG image */ struct gl_texture_t *gltex = ReadPNGFromFile (filename); if (!gltex) { fprintf (stderr, "error: couldn't load %s!\n", filename); exit (EXIT_FAILURE); } /* Generate texture */ glGenTextures (1, &texid); glBindTexture (GL_TEXTURE_2D, texid); /* Setup texture filters */ 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 has its own copy of texture data */ 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 des puissances 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.
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 :
À 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) gmail (POINT) com.
Cet article est mis à disposition sous un contrat Creative Commons (licence CC-BY-ND).