Tous droits réservés

Modification pixels par pixels

Dernière mise à jour :

Après avoir vu le fonctionnement des textures et des surfaces, nous allons voir comment les manipuler pixels par pixels.

Les textures

Nous pouvons également modifier une texture pixels par pixels. En fait, les pixels d’une texture sont stockés dans un tableau. Nous allons récupérer ce tableau, et le modifier.

Le format des pixels

Cette partie sur le format des pixels est assez technique. Elle traite notamment de représentation des nombres en machine. Il n’est pas nécessaire de la comprendre parfaitement pour poursuivre le tutoriel.

Le problème est que pour modifier un pixel et lui donner une autre couleur, il faut savoir comment il est stocké et comment ses couleurs sont représentées. Heureusement, c’est nous qui choisissons cette représentation lorsque nous créons une texture en spécifiant le format de pixel. La plupart du temps, nous choisissons le format SDL_PIXELFORMAT_RGBA8888. Cela veut dire qu’un pixel est représenté avec 8 bits pour chacune de ses composantes. Dans l’ordre :

  • 8 bits pour le rouge ;
  • 8 bits pour le vert ;
  • 8 bits pour le bleu ;
  • 8 bits pour la composante alpha.

Ainsi, 0xFF0000FF représente du rouge, 0x00FF00FF représente du vert et 0x0000FFFF représente du bleu.

Les pixels sont donc codés sur 32 bits dans ce format. Nous allons coder ces pixels avec le type Uint32 (il s’agit d’un type de 32 bits permettant de représenter des entiers) et chacune de ses composantes avec le type Uint8. Dans ce format, l’octet de poids fort représente le rouge et celui de poids faible représente le canal alpha.

Nous pouvons dès lors créer une fonction qui transformera une couleur donnée par ses quatre composantes en pixel de cette couleur dans le format SDL_PIXELFORMAT_RGBA8888.

1
2
3
4
Uint32 couleur(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
{
   return r << 24 | g << 16 | b << 8 | a;
}

Bien sûr, si le format de pixel de la texture n’est pas le même, cette fonction devra être modifiée. Nous n’allons pas utiliser cette solution, mais il est intéressant de la voir pour comprendre comment ça se passe.

Dans le chapitre précédent, nous avions vu la fonction SDL_MapRGB. Il y a également une fonction SDL_MapRGBA à qui l’on fournit les composantes RGBA d’une couleur et qui nous renvoie la valeur du pixel associé. Voici son prototype (qui ne devrait pas vous surprendre).

1
2
3
4
5
Uint32 SDL_MapRGBA(const SDL_PixelFormat* format,
                   Uint8                  r, 
                   Uint8                  g, 
                   Uint8                  b,
                   Uint8                  a)

C’est le même que celui de la fonction SDL_MapRGB avec le paramètre a en plus. C’est cette fonction que nous allons utiliser pour obtenir un pixel d’une couleur donnée.

Oui, cette fonction fait ce que l’on veut, mais comment savoir quel premier paramètre lui donner ? Les surfaces ont un champ format qui correspond à ce paramètre, mais que pouvons-nous faire pour les textures ?

Cette question est en effet importante. Nous connaissons le format de notre texture sous la forme d’un Uint32 (par exemple, la constante SDL_PIXELFORMAT_RGBA8888). Pour passer de cet entier à une variable du type SDL_PixelFormat, nous allons utiliser la fonction SDL_AllocFormat.

1
SDL_PixelFormat* SDL_AllocFormat(Uint32 pixel_format)

Elle prend en paramètre le format d’un pixel sous la forme d’un entier et retourne un pointeur sur SDL_PixelFormat (donc un pointeur sur le format du pixel). En cas d’erreur, elle retourne NULL.

Après avoir fini d’utiliser notre format, il nous faut le libérer avec la fonction SDL_FreeFormat de prototype suivant (elle prend en paramètre le pointeur sur SDL_PixelFormat et ne retourne rien).

1
void SDL_FreeFormat(SDL_PixelFormat* format)

On peut alors faire un code de ce genre.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SDL_Texture *texture;
SDL_PixelFormat *format;

/* Initialisations. */

texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, 3, 3);
format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);

/* On dessine pixels par pixels */

SDL_FreeFormat(pixels);

/* Autres libérations */

Obtenir les composantes d’un pixel

Il pourra parfois être utile d’obtenir les composantes d’un pixel donné (donc passer d’un Uint32 à un SDL_Color. Pour faire cette opération, nous utilisons la fonction SDL_GetRGBA.

1
2
3
4
5
6
void SDL_GetRGBA(Uint32                 pixel,
                 const SDL_PixelFormat* format,
                 Uint8*                 r,
                 Uint8*                 g,
                 Uint8*                 b,
                 Uint8*                 a)

Elle prend en paramètre le pixel, un pointeur sur SDL_PixelFormat (comme SDL_MapRGBA) et des pointeurs sur quatre Uint8 qui auront ensuite comme valeur les composantes de la couleur. Pour récupérer les composantes d’un pixel, on utilise alors ce code (après avoir récupéré le pixel et le format).

1
2
3
SDL_Color c;
SDL_GetRGBA(pixel, format, &c.r, &c.g, &c.b, &c.a);
printf("(%d, %d, %d, %d)", c.r, c.g, c.b, c.a);

Notons l’existence de la fonction SDL_GetRGB qui permet de ne récupérer que les composantes rouge, bleue et verte. Son prototype ne diffère de celui de SDL_GetRGBA que par la disparition du pointeur pour la composante alpha.

Modifier les pixels

Pour obtenir le tableau de pixels d’une texture, nous allons utiliser la fonction SDL_LockTexture. Elle permet de « bloquer » une partie de la texture en lecture seule. Son prototype :

1
2
3
4
int SDL_LockTexture(SDL_Texture*    texture,
                    const SDL_Rect* rect,
                    void**          pixels,
                    int*            pitch)

Elle prend en paramètre :

  • une texture ;
  • un pointeur sur SDL_Rect qui représente la partie de la texture que nous voulons bloquer ;
  • un double pointeur qui pointera sur l’adresse mémoire du tableau de pixels ;
  • un pointeur sur un entier qui contiendra à l’issue de la fonction la longueur d’une ligne, en octet.

La fonction retourne 0 en cas de succès et une valeur négative en cas d’échec. Le paramètre pitch peut paraître abstrait. Prenons donc un exemple : si on veut bloquer toute une texture de longueur 200 et que chaque pixel est stocké sur 32 bits (donc 4 octets), pitch vaudra 800 (200 * 4).

La texture que l’on veut bloquer doit avoir un accès SDL_TEXTUREACCESS_STREAMING.

Voyons comment les pixels sont arrangés dans le tableau en prenant l’exemple d’une image 3x3 (donc w = 3 et h = 3).

Pixel 1

Pixel 2

Pixel 3

Pixel 4

Pixel 5

Pixel 6

Pixel 7

Pixel 8

Pixel 9

0*w + 0

0*w + 1

0*w + 2

1*w + 0

1*w + 1

1*w + 2

2*w + 0

2*w + 1

2*w + 2

Notons que le pitch est alors de 12 (3 pixels par ligne avec chaque pixel faisant 4 octets).

Créons une texture dégradée grâce à cela.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define WIDTH 255
#define HEIGHT 255

void *tmp;
Uint32 *pixels;
SDL_PixelFormat *format;
int pitch;
size_t i, j;

/* Nos initialisations et créations */
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING,
                            WIDTH, HEIGHT); /* On devrait vérifier que la fonction a réussi */
format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);
SDL_LockTexture(texture, NULL, &tmp, &pitch);
pixels = tmp;
for(i = 0; i < HEIGHT; i++)
{
    for(j = 0; j < WIDTH; j++)
        pixels[i * WIDTH + j] = SDL_MapRGBA(format, (Uint8)i, 0, 0, 255);
}

SDL_FreeFormat(format);

Ici, on bloque notre texture et ensuite on modifie chaque case du tableau de pixels. La case i * WIDTH + j correspond au pixel d’abscisse j et d’ordonnée i.

Après avoir réalisé nos modifications, il nous faut débloquer la texture. Cette opération se fait avec la fonction SDL_UnlockTexture de prototype suivant.

1
void SDL_UnlockTexture(SDL_Texture* texture)

Elle prend comme paramètre la texture à débloquer et ne retourne rien. Il faut débloquer la texture avant de l’afficher ou de faire d’autres opérations. Pour afficher notre dégradé, nous allons donc rajouter ceci à notre code précédent.

1
2
3
SDL_UnlockTexture(texture);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);

Notons que nous aurions pu créer une texture de largeur 1. Le redimensionnement de la texture à l’affichage nous aurait quand même permis d’obtenir le dégradé pour toute la fenêtre. Nous aurions alors fait moins de calcul et économisé de la mémoire.

Mettre à jour une texture

Les fonctions SDL_LockTexture et SDL_UnlockTexture sont parfaites dans le cas où l’on veut modifier quelques pixels de la texture ou si on a besoin de connaître les pixels pour les modifier (par exemple pour un flou gaussien), mais si on veut complètement changer tout ou partie d’une texture et modifier chacun des pixels, on peut faire appel à la fonction SDL_UpdateTexture de prototype

1
2
3
4
int SDL_UpdateTexture(SDL_Texture*    texture,
                      const SDL_Rect* rect,
                      const void*     pixels,
                      int             pitch)

Elle prend en paramètre la texture, un pointeur sur SDL_Rect qui correspond à la partie de la texture que l’on veut mettre à jour (comme d’habitude on passe NULL pour tout mettre à jour), un tableau de pixels (les pixels doivent bien sûr être dans le bon format) et le « pitch » de notre tableau de pixels. Ce paramètre est assez simple à calculer : on veut mettre à jour une texture de longueur w et chaque pixel est codé sur s pixels, on a alors pitch = w * s.

Si la fonction SDL_UnlockTexture ne peut fonctionner qu’avec une structure d’accès SDL_TEXTUREACCESS_STREAMING, SDL_UpdateTexture, elle, fonctionne avec toutes les textures.

Faisons une fonction qui renvoie une texture avec le même dégradé que dans notre exemple précédent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
SDL_Texture *degrade(SDL_Renderer *renderer)
{
    SDL_Texture *texture = NULL;
    SDL_PixelFormat *format;

    texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC,
                                1, 255); /* Il ne faudra pas oublier les vérifications */
    Uint32 pixels[255] = {0};
    size_t i;
    format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);
    for(i = 0; i < 255; i++)
        pixels[i] = SDL_MapRGBA(format, i, 0, 0, 255);
    SDL_UpdateTexture(texture, NULL, pixels, sizeof(Uint32) * 1);
    SDL_FreeFormat(format);
    return texture;
}

Ici, on s’est débarrassé de la seconde boucle car on a créé une surface de largeur 1. On a d’ailleurs pitch = sizeof(Uint32) * 1.

Les surfaces

Le format des pixels, encore et toujours

Les pixels d’une surfaces sont stockés de la même manière que ceux d’une texture et pour les modifier, il nous faut donc connaître le format dans lequel ils sont stockés. Nous choisissons ce format lorsque nous créons une surface en spécifiant une valeur pour les quatre derniers paramètres et en spécifiant le nombre de bits par pixel. Généralement, le nombre de bits par pixel est de 32.

Jusqu’à maintenant, nous ne nous sommes pas occupés des valeurs des masques (nous passions 0 en paramètre), et en fait, nous allons continuer à ne pas nous en occuper. Pour cela, nous allons utiliser la fonction SDL_CreateRGBSurfaceWithFormat.

1
2
3
4
5
SDL_Surface* SDL_CreateRGBSurfaceWithFormat(Uint32 flags,
                                            int    width,
                                            int    height,
                                            int    depth,
                                            Uint32 format)

Comme SDL_CreateRGBSurface, elle prend en paramètre le drapeau (qui doit, rappelons-le, être 0), les dimensions de la surface, le nombre de bits par pixels (généralement 32), et le format des pixels (sous la forme d’un Uint32). On pourra alors utiliser le format SDL_PIXELFORMAT_RGBA8888 pour créer notre surface de cette manière.

1
surface = SDL_CreateRGBSurfaceWithFormat(0, 200, 200, 32, SDL_PIXELFORMAT_RGBA8888);

En fait, une fois qu’on a le format sous la forme d’un SDL_PixelFormat (obtenu par exemple à l’aide de la fonction SDL_AllocFormat), on peut obtenir les quatre derniers paramètres à passer à SDL_CreateRGBSurface pour avoir une surface du même format (ils sont dans les champs Rmask, Gmask et Bmask et Amask du format). La ligne de code précédent est alors équivalente à celle-ci.

1
2
surface = SDL_CreateRGBSurface(0, 200, 200, 32, format->Rmask,
                               format->Gmask, format->Bmask, format->Amask);

Nous allons préférer la première version, notamment parce qu’elle ne demande pas d’avoir déjà un pointeur sur le format voulu.

Modifier les pixels

Contrairement aux textures, on peut accéder directement au tableau de pixels d’une surface. Il s’agit du champ pixels de SDL_Surface. Néanmoins, avant de lire et de modifier ce tableau, il nous faut bloquer la surface avec la fonction SDL_LockSurface de prototype :

1
int SDL_LockSurface(SDL_Surface* surface)

Elle prend en paramètre la surface à bloquer et retourne 0 en cas de succès et une valeur négative en cas d’erreur.

Après avoir fini nos modifications il faut, tout comme pour les textures, débloquer la surface. Nous le devinons facilement, cela se fait avec la fonction SDL_UnlockSurface de prototype :

1
void SDL_UnlockSurface(SDL_Surface* surface)

On peut alors faire un dégradé sur une surface à l’aide du code suivant.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Uint32 *pixels;
size_t i, j;
surface = SDL_CreateRGBSurfaceWithFormat(0, 255, 255, 32, SDL_PIXELFORMAT_RGBA8888); 
SDL_LockSurface(surface);
pixels = surface->pixels;
for(i = 0; i < 255; i++)
{
    for(j = 0; j < 255; j++)
        pixels[i * 255 + j] = SDL_MapRGBA(surface->format, 0, 0, (Uint8)i, 255);
}

Faisons maintenant une fonction setPixel qui prend en paramètre une surface débloquée, les composantes d’une couleur et les coordonnées d’un pixel et donne à ce pixel la couleur associée. On va considérer que les coordonnées passées en paramètre existent.

1
2
3
4
5
6
void setPixel(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b, Uint8 a, size_t x, size_t y)
{
    Uint32 *pixels = surface->pixels; /* Nos pixels sont sur 32 bits */
    Uint32 couleur = SDL_MapRGBA(surface->format, r, g, b, a);
    pixels[y * surface->w + x] = couleur;
}

Une nouvelle surface ?

En fait, une fois que l’on a un tableau de pixels, on peut s’en servir pour créer une nouvelle surface. On peut par exemple créer une surface de la bonne taille, puis copier le tableau de pixels dans le champ pixel de cette surface. Mais, il y a mieux. La SDL offre en effet une fonction qui se charge de créer la surface et de faire cette copie à notre place. Il s’agit de la fonction SDL_CreateRGBSurfaceWithFormatFrom. Son prototype est le suivant.

1
2
3
4
5
6
SDL_Surface* SDL_CreateRGBSurfaceWithFormatFrom(void*  pixels,
                                                int    width,
                                                int    height,
                                                int    depth,
                                                int    pitch,
                                                Uint32 format)

Ses paramètres ne nous sont pas étrangers. On a :

  • pixels correspond au tableau de pixels que l’on veut assigner à notre surface ;
  • width et height sont la largeur et la hauteur de la surface ;
  • depth est le nombre de bits par pixels ;
  • pitch est le nombre d’octets pris par une ligne de la surface (il est stocké dans le champ pitch de la surface) ;
  • format est le format des pixels (sour la forme d’un Uint32).

Pour avoir la couleur associée à un pixel, nous allons donc utiliser SDL_AllocFormat pour avoir le format sous la forme d’un SDL_PixelFormat. Ceci nous permettra ensuite d’utiliser SDL_MapRGBA.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SDL_PixelFormat *format = NULL;
Uint32 pixels[255 * 255];
size_t i, j;
SDL_Surface *surface = NULL;
format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA8888);
for(i = 0; i < 255; i++)
{
    for(j = 0; j < 255; j++)    
        pixels[i * 255 + j] = SDL_MapRGBA(format, (i * j) % 255, 0, i % 255, 255);
}
surface = SDL_CreateRGBSurfaceWithFormatFrom(pixels, 255, 255, 32, 255 * sizeof(Uint32), 
                                             SDL_PIXELFORMAT_RGBA8888);
SDL_FreeFormat(format);

Puisque le tableau à allouer peut être de taille vraiment conséquente (par exemple pour une très grande image), la plupart du temps, nous allons allouer le tableau de pixels dynamiquement. Et bien sûr, nous allons éviter d’avoir des nombres en durs dans notre code, et plutôt utiliser des constantes.

Ici, on crée d’abord un tableau de Uint32. Ensuite, ce tableau est initialisé dans une boucle for avant d’être utilisé dans la fonction SDL_CreateRGBSurfaceWithFormatFrom. On a un pitch de 255 * sizeof(Uint32) car la surface que l’on crée à une largeur de 255 pixels et que chaque pixel est codé sur un Uint32 (puisque son format est SDL_PIXELFORMAT_RGBA8888).

Il y a également une fonction SDL_CreateRGBSurfaceFrom qui est à SDL_CreateRGBSurface ce que SDL_CreateRGBSurfaceWithFormatFrom est à SDL_CreateRGBSurfaceWithFormat. Nous ne l’utilisons pas ici, car nous préférons pouvoir donner le format à la fonction.

Lier surfaces et textures

De la texture à la surface

Nous avons vu dans la partie précédent comment passer de la surface à la texture avec la fonction SDL_CreateTextureFromSurface. Mais comment faire l’opération inverse ? Il n’existe malheureusement pas de fonction pour créer une surface à partir d’une texture. Si nous voulons le faire, il nous faudra donc gérer tout ça à la main. Pour cela, nous allons :

  • bloquer la texture ;
  • créer un tableau de pixels de même dimension ;
  • copier les pixels de la texture dans ce tableau ;
  • créer une surface avec ces pixels.

Il faudra notamment faire attention au format des pixels. En effet, si le format de la texture n’est pas celui que nous voulons pour notre surface, nous ne devrons pas juste copier les pixels de la texture dans le tableau, mais nous devrons aussi changer leur format. Pour cela, nous pourrions utiliser SDL_GetRGBA pour récupérer les composantes d’un pixel de la texture, puis utiliser SDL_MapRGBA pour obtenir un pixel de cette couleur dans le format voulu.

Mais si nous créons une surface de même format que la texture, cela signifie que le tableau de pixels de la texture peut être utilisé directement pour créer la surface, le tableau de pixels d’une surface et celui d’une texture sont équivalents. On a alors un code plus général en récupérant le format de la texture (grâce à SDL_QueryTexture), afin de pouvoir utiliser la fonction avec n’importe quelle texture. On écrit alors ce code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SDL_Surface *createSurfaceFromTexture(SDL_Texture *texture)
{
    Uint32 format_pixels;
    SDL_Surface *surface = NULL;
    void *pixels = NULL;
    int pitch, w, h;

    if (SDL_QueryTexture(texture, &format_pixels, NULL, &w, &h) != 0)
    {
        fprintf(stderr, "SDL_QueryTexture: %s.\n", SDL_GetError());
        goto query_texture_fail;
    }

    if (SDL_LockTexture(texture, NULL, &pixels, &pitch) != 0)
    {
        fprintf(stderr, "SDL_LockTexture: %s.\n", SDL_GetError());
        goto lock_texture_fail;
    }

    surface = SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, 32, w * sizeof(Uint32)
                                                 format_pixels);
    if(NULL == surface)
        fprintf(stderr, "Erreur SDL_CreateSurfaceFrom : %s.\n", SDL_GetError());

    SDL_UnlockTexture(texture);
lock_texture_fail:
query_texture_fail:
    return surface;
}

Du renderer à la surface ?

Nous venons de voir comment passer d’une texture à une surface. Mais comment faire pour passer de notre cible de rendu à une surface ? En gros, comment accéder à ses pixels ? Cette opération est possible grâce à la fonction SDL_RenderReadPixels.

1
2
3
4
5
int SDL_RenderReadPixels(SDL_Renderer*   renderer,
                         const SDL_Rect* rect,
                         Uint32          format,
                         void*           pixels,
                         int             pitch)

Elle prend en paramètre le renderer dont on veut les pixels, un SDL_Rect qui correspond à la partie du renderer dont on veut récupérer les pixels (NULL si on veut la totalité), le format dans lequel on veut que ces pixels nous soient remis (pour ne pas changer, nous utiliseront SDL_PIXELFORMAT_RGBA8888), un pointeur qui pointera sur le tableau de pixels après l’exécution de la fonction et le pitch de notre tableau (on le détermine comme d’habitude avec la taille d’un pixel et la largeur du tableau).

La fonction renvoie 0 en cas de succès et une valeur négative en cas d’erreur.

Ainsi, si notre fenêtre a une largeur WIDTH et une hauteur HEIGHT, nous pouvons récupérer ses pixels avec le code suivant (on suppose que le renderer est rattaché à la fenêtre).

1
2
3
int pitch = sizeof(Uint32) * WIDTH;
Uint32 *pixels = malloc(pitch * HEIGTH);
SDL_RenderReadPixels(renderer, NULL, SDL_PIXELFORMAT_RGBA8888, pixels, pitch);

La fonction SDL_RenderPixels écrit dans la zone mémoire qu’on lui donne avec l’argument pixels. Il faut donc que le pointeur qu’on lui passe en paramètre pointe sur une zone mémoire valide et l’espace réservé doit être de taille suffisante (ici, on l’alloue avec malloc).

Nous pouvons alors faire une capture d’écran grâce à ça en créant une surface avec ces pixels et en la sauvegardant avec la fonction SDL_SaveBMP.

Notons de plus que SDL_RenderReadPixels permet de lire les pixels de la cible de rendu. Cela signifie que si la cible de rendu est la cible par défaut (la fenêtre), nous obtiendrons les pixels de la fenêtre. Mais si la cible de rendu est une texture, ce sont ses pixels que nous obtiendrons. Nous avons alors un moyen d’obtenir les pixels d’une texture dont l’accès est SDL_TEXTUREACCES_TARGET.

Quelles opérations privilégier ?

Bon, après avoir vu tout cela, il nous faut nous rendre compte de quelque chose : la plupart de ces opérations sont coûteuses et lentes ; enfin, relativement lente. Il ne faut donc pas en abuser. La documentation nous prévient d’ailleurs de cela. Par exemple, sur la page de SDL_RenderReadPixel, nous pouvons lire cet avertissement.

WARNING : This is a very slow operation, and should not be used frequently.

Documentation de la SDL

Elle ne doivent pas être utilisées fréquemment.

Encore une fois, c’est en lisant la documentation que nous pouvons accéder à ces informations. Par exemple, regardons la partie remarque de la documentation de SDL_UpdateTexture.

This is a fairly slow function, intended for use with static textures that do not change often.

If the texture is intended to be updated often, it is preferred to create the texture as streaming and use the locking functions referenced below. While this function will work with streaming textures, for optimization reasons you may not get the pixels back if you lock the texture afterward.

Documentation de la SDL

On apprend ici que SDL_UpdateTexture est une fonction lente, prévue pour être utilisée sur des textures statiques (donc d’accès SDL_TEXTUREACCESS_STATIC). La documentation conseille de préférer l’utilisation de SDL_LockTexture si le but est de modifier une texture d’accès SDL_TEXTUREACCESS_STREAMING.

En plus de tout cela, il faut savoir que les textures ne sont pas faites pour une modification pixels par pixels. Elles ont l’avantage d’êtres affichables rapidement, de pouvoir être redimensionnée à la volée et copiée rapidement, mais elles ne sont pas prévues pour de la modification pixels par pixels. Les surfaces sont plus adaptées à cela. Ainsi, si nous voulons modifier une image sans l’afficher (nous le feront dans le chapitre suivant), nous préférerons utiliser une surface. Et surtout, nous éviterons au maximum d’avoir à modifier les pixels d’une texture.

Pour changer facilement les pixels et travailler avec nos surfaces et nos textures, il nous faudra veiller à ce qu’elles aient toutes le même format.

Pensons notamment aux surfaces obtenues avec SDL_LoadBMP. Nous ne savons pas quelle est leur format de pixel. Mais pas d’inquiétude, la SDL nous fournit la fonction SDL_ConvertSurfaceFormat qui convertit une surface dans un format choisi.

1
2
3
SDL_Surface* SDL_ConvertSurfaceFormat(SDL_Surface* src,
                                      Uint32       pixel_format,
                                      Uint32       flags)

Elle prend en paramètre la surface à convertir, le format dans lequel doivent être convertis les pixels et des drapeaux (nous devons passer 0 pour cet argument).

Grâce à cette fonction, on peut faire en sorte que nos surfaces chargées avec SDL_LoadBMP aient bien le format voulu. On peut même faire une fonction qui nous renvoie la surface dans le bon format.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
SDL_Surface *loadBMP(char path[], Uint32 format)
{
    SDL_Surface *tmp, *s;
    tmp = SDL_LoadBMP(path);
    if(NULL == tmp)
    {
        fprintf(stderr, "Erreur SDL_LoadBMP : %s", SDL_GetError());
        return NULL;
    }
    s = SDL_ConvertSurfaceFormat(tmp, format, 0);
    SDL_FreeSurface(tmp);
    if (NULL == s)
    {
        fprintf(stderr, "Erreur SDL_ConvertSurfaceFormat : %s", SDL_GetError());
        return NULL;
    }
    return s;
}

Bien sûr, cela n’est utile que dans le cas où nous allons effectivement lire ou modifier les pixels de nos surfaces. Si nous n’accédons jamais aux pixels, la manière dont ils sont stockés nous importe peu.


Ce chapitre est maintenant fini. Dans le chapitre suivant, nous allons nous attaquer à la transparence, que nous n’avons jamais utilisée jusqu’à maintenant.