Tous droits réservés

La transparence

À ce niveau du tutoriel, nous savons jouer avec la fenêtre, afficher des images et des textures diverses, etc. Il nous manque cependant quelque chose avant de pouvoir dire qu’on a à peu près fait le tour de tout ce qui concerne l’affichage. Il nous faut savoir jouer avec la transparence.

La transparence alpha

Un problème embêtant

Nous avons vu que nous pouvions décomposer une couleur en quatre composantes, sa composante rouge, sa composante verte, sa composante bleue et sa composante alpha. La composante alpha correspond au niveau de transparence de l’image. Pour le moment, nous ne l’avons jamais vraiment essayé. Remplissons le renderer avec une couleur transparente.\gdef{\resultat}{\mathrm{resultat}} \gdef{\source}{\mathrm{source}} \gdef{\destination}{\mathrm{destination}} \gdef{\alphac}{\mathrm{alpha}}

int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;

    /* On fait toutes nos initialisations ici */
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 100);
    SDL_RenderFillRect(renderer, NULL);
    SDL_RenderPresent(renderer);
    SDL_Delay(1000);
    
    /* On libère toutes nos ressources ici et on fait notre return*/ 
} 

Notre fenêtre est rouge et ce rouge n’est pas du tout transparent. Même en mettant la composante alpha à zéro, notre fenêtre est toujours rouge.

Pourtant, la composante alpha correspond bien au niveau de transparence. Pourquoi notre code ne fonctionne-t-il pas ? En fait, cela est dû au fait que la transparence n’est tout simplement pas activée. Nous pourrions penser qu’il n’y a pas besoin d’activation et qu’il suffit de donner la composante alpha voulue, mais ce n’est pas aussi simple. Le fait est qu’il y a plusieurs types de transparence et qu’il faut indiquer à la SDL lequel nous voulons utiliser.

Les modes de fusion

Voyons donc quels sont les différents types de transparence. Mais avant ça, il est nécessaire de nous poser une question.

Comment fait-on pour rendre un pixel transparent ?

Car, même si nous disons qu’il y a plusieurs types de transparence, il faut encore savoir ce que signifie rendre un pixel transparent.

En fait, la transparence d’un pixel n’est pas réelle et n’existe qu’à l’affichage. Un pixel n’est jamais transparent. L’ordinateur fait juste en sorte de nous le faire apparaître transparent. Pour cela, il fusionne la couleur du pixel à afficher avec la couleur du pixel qui est en dessous. La composante alpha d’un pixel indique juste à quel point il faut prendre en compte la couleur du pixel pour avoir la couleur à afficher. Ainsi, une composante alpha nulle signifie qu’il ne faut pas prendre en compte la couleur du pixel et donc la couleur affichée sera celle du pixel du dessous.

Dans la suite, nous nommerons « pixel source » ou « source » le pixel que l’on veut afficher, « pixel destination » ou « destination » le pixel qui est déjà à l’écran et « pixel résultant » ou « résultat », le pixel résultant de la fusion de la source et de la destination.

Cependant, on peut fusionner les pixels source et destination de plusieurs manières. Ces différentes méthodes s’appellent des modes de fusion ou blend mode en anglais. On pourrait imaginer plusieurs modes de fusion plus ou moins fantaisistes. Le mode le plus simple étant celui ou

resultat=source. \resultat = \source.

Ce mode correspond en fait à une absence totale de transparence (c’est ce que nous avons obtenu précédemment avec notre exemple de fenêtre rouge). Mais ce n’est pas un mode de fusion, puisqu’il n’y a justement pas de fusion.

En gros, les modes de fusion consistent à appliquer une fonction ff telle que resultat=f(source,destination)\resultat = f(\source, \destination).

Les modes de fusion de la SDL

Maintenant que nous voyons un peu mieux en quoi consiste la transparence, nous pouvons nous renseigner sur les différents modes de fusion de la SDL. Ces différents modes peuvent être trouvés dans l’énumération SDL_BlendMode. En regardant les différentes valeurs de cette énumération, on voit que la SDL dispose de quatre modes de fusion (trois en fait puisque l’une des valeurs possibles pour l’énumération correspond à l’absence de transparence).

Mode alpha

Le premier mode possible est celui qui correspond le plus à l’idée que l’on se fait d’une fusion puisqu’il s’agit en fait d’une moyenne pondérée de la source et de la destination, le poids étant déterminé grâce à la valeur de la composante alpha. Ce mode est appelé alpha blending. Il est associé à la valeur SDL_BENDMODE_BLEND et au calcul

resultat=alpha×source255+(255alpha)×destination255. \resultat = \frac{\alphac \times \source}{255} + \frac{(255 - \alphac) \times \destination}{255}.

Si la composante alpha est nulle, il n’y a pas de transparence, si elle vaut 255, seule la couleur de la destination est prise en compte, si elle vaut 128, les deux couleurs sont parfaitement mélangées.

Mode d’addition

Le second mode est appelé (à raison) additive blending puisqu’il consiste à ajouter la source à la destination. Il permet ainsi de rendre les couleurs plus vives. Il est associé à la valeur SDL_BLENDMODE_ADD et au calcul

resultat=alpha×source255+destination. \resultat = \frac{\alphac \times \source}{255} + \destination.

Nous remarquons que plus la valeur de la composante alpha est élevée, plus la valeur ajoutée sera grande c’est-à-dire qu’encore une fois, plus la valeur de la composante alpha est grande, plus la couleur du pixel source sera prise en compte. Notons également que si la valeur du pixel résultant est supérieure à 255, la SDL la ramène à 255. En fait, on a plutôt

resultat=min(255,alpha×source255+destination). \resultat = \min\left(255, \frac{\alphac \times \source}{255} + \destination \right).

Mode de modulation

Le dernier mode, appelé color modulate, n’utilise même pas la valeur de la composante alpha du pixel source. Il est associé à la valeur SDL_BLENDMODE_MOD et au calcul

resultat=source×destination255. \resultat = \frac{\source \times \destination}{255}.

Il multiplie la source par le résultat, ce qui explique qu’on l’appelle parfois « mode de multiplication ».


Chaque transformation est appliquée à chacune des composantes rouge, bleue et verte du pixel. La composante alpha, elle, subit ces transformations.

  • Dans le cas du mode alpha, on a resultat(alpha)=source(alpha)+destination(alpha)×255source(alpha)255\resultat(\alphac) = \source(\alphac) + \destination(\alphac) \times \frac{255 - \source(\alphac)}{255}.
  • Dans les cas du mode d’addition et du mode de modulation, on a resultat(alpha)=destination(alpha)\resultat(\alphac) = \destination(\alphac).

On utilise généralement le mode alpha qui correspond à l’idée qu’on se fait de la transparence.

Gérer la transparence alpha

Il ne nous reste plus qu’à voir comment utiliser ces différents modes.

Avoir un renderer transparent

Pour appliquer un mode à un renderer, il nous faut connaître une seule fonction. Il s’agit de la fonction SDL_SetRenderDrawBlendMode dont le prototype est le suivant.

int SDL_SetRenderDrawBlendMode(SDL_Renderer* renderer,
                               SDL_BlendMode blendMode)

Elle permet d’indiquer à la SDL le mode de fusion que l’on veut donner à notre renderer. Pour cela, on lui envoie le renderer et le mode de fusion voulu. Elle retourne 0 en cas de succès et une valeur négative en cas d’erreur.

Reprenons le code du début de ce chapitre et modifions-le pour rendre notre fond transparent.

int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;

    /* On fait toutes nos initialisations ici */
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 255, 128, 0, 128);
    SDL_RenderFillRect(renderer, NULL);
    SDL_RenderPresent(renderer);
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    SDL_Delay(1000);

    /* On libère toutes nos ressources ici et on fait notre return*/
}

Ça ne fonctionne toujours pas !

C’est normal. Nous avons changé le mode de fusion de notre renderer après avoir dessiné sur l’écran. Il nous faut donc utiliser SDL_SetRenderDrawBlendMode avant d’utiliser SDL_RenderFillRect.

int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;

    /* On fait toutes nos initialisations ici */
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 255, 128, 0, 128);
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    SDL_RenderFillRect(renderer, NULL);
    SDL_RenderPresent(renderer);
    SDL_Delay(1000);

    /* On libère toutes nos ressources ici et on fait notre return*/ 
} 

Cette fois, c’est bon. Le fond de notre fenêtre est devenu plus sombre (parce que le fond de notre fenêtre est noir). On a donc bien fusionné la couleur de la source et la couleur de la destination.

Notons que cette fonction active la transparence alpha pour les fonctions de dessin (lignes, rectangles, ect.). La fonction SDL_RenderClear n’est pas une fonction de dessin, mais de nettoyage. Le fond ne sera donc pas transparent s’il est fait avec SDL_RenderClear.

Avoir une texture transparente

Maintenant, voyons comment avoir une texture transparente. En effet, mettre la texture en tant que surface de rendu (avec SDL_SetRenderTarget) puis utiliser SDL_SetRenderDrawBlendMode ne fonctionne pas. Il nous faut utiliser la fonction SDL_SetTextureBlendMode pour appliquer un mode à notre texture. Voici son prototype.

int SDL_SetTextureBlendMode(SDL_Texture*  texture,
                            SDL_BlendMode blendMode)

Elle prend en paramètre la texture pour laquelle on veut activer la transparence et le mode voulu. Elle retourne 0 en cas de succès et une valeur négative en cas d’erreur. Notons de plus que si le mode demandé n’est pas supporté par la texture, la fonction renverra -1 et activera le mode de fusion le plus proche de celui demandé (ce cas arrive rarement).

On peut alors afficher une texture transparente.

int main(int argc, char *argv[])
{
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Texture *texture = NULL;
    SDL_Rect dst = {0, 0, 100, 100};

    /* On fait toutes nos initialisations ici */
    SDL_RenderClear(renderer);
    SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
    SDL_SetRenderTarget(renderer, texture);
    SDL_SetRenderDrawColor(renderer, 0, 0, 255, 100);
    SDL_RenderFillRect(renderer, NULL);
    SDL_SetRenderTarget(renderer, NULL);
    SDL_RenderCopy(renderer, texture, NULL, &dst);
    SDL_RenderPresent(renderer);
    SDL_Delay(1000);

    /* On libère toutes nos ressources ici et on fait notre return */ 
} 

Des collisions entre les textures et le renderer

Nous savons maintenant appliquer de la transparence au renderer et aux textures. Mais que se passe-t-il lorsque l’on affiche une texture transparente sur un renderer transparent ? Comment les textures interagissent-elles avec le renderer ?

Le mode de transparence de la zone de rendu doit être SDL_BLENDMODE_NONE lorsqu’on travaille sur des textures, sans quoi nous aurons des problèmes d’affichage. Après avoir utilisé SDL_SetRenderTarget pour choisir la texture en tant que zone de rendu, nous changeons donc, si besoin est, le mode de transparence de la zone de rendu en SDL_BENDMODE_NONE.

On peut observer ces différents cas de figures.

Renderer opaque, texture opaque

C’est le cas le plus simple et le plus classique. La texture est opaque et le renderer est opaque, donc on ne verra pas le renderer à travers la texture. On dessine un rectangle bleu sur un fond rouge, le rectangle bleu cache une partie du fond rouge.

SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); /* texture opaque */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);

SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

/* On dessine sur la texture. */
SDL_SetRenderTarget(renderer, texture);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, NULL);

SDL_RenderCopy(renderer, texture, NULL, &dst);
SDL_RenderPresent(renderer);

Renderer transparent, texture opaque

Le résultat de cette expérience n’est pas trop surprenant. Le renderer est transparent, la texture est opaque, donc on voit la texture (le fait que le fond soit transparent ne change rien à cela). On dessine un rectangle bleu sur un fond transparent, donc on voit un rectangle bleu.

SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); /* texture opaque */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, texture);
/* La zone de rendu doit être en BLENDMODE_NONE quand on dessine sur une texture. */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); 
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, NULL);

SDL_RenderCopy(renderer, texture, NULL, &dst);
SDL_RenderPresent(renderer);

Renderer opaque, texture transparente

C’est encore une fois un cas simple et plutôt classique. La texture est transparente et le renderer est opaque, donc on verra le renderer à travers la texture (plus ou moins suivant la valeur de la composante alpha de la texture). On dessine un rectangle bleu, mais transparent, sur un fond rouge, donc on voit le fond rouge à travers le rectangle.

SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); /* texture transparente */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);

SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, texture);
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, NULL);

SDL_RenderCopy(renderer, texture, NULL, &dst);
SDL_RenderPresent(renderer);

Renderer transparent, texture transparente

Ce cas est le seul un peu difficile à appréhender. Le calcul se fait normalement suivant le mode de transparence choisi. Si la transparence du renderer et celle de la texture sont totales (la composante alpha est nulle), aucune couleur n’apparaît puisque toutes les couleurs sont transparentes, ce qui peut être un peu déconcertant. On dessine un rectangle transparent sur un fond transparent, donc on obtient quelque chose de transparent.

SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); /* texture transparente */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

SDL_SetRenderDrawColor(renderer, 255, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, texture);
/* La zone de rendu doit être en BLENDMODE_NONE quand on dessine sur une texture. */
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); 
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
SDL_RenderFillRect(renderer, NULL);

SDL_SetRenderTarget(renderer, NULL);

SDL_RenderCopy(renderer, texture, NULL, &dst);
SDL_RenderPresent(renderer);

Cependant, nous n’aurons que rarement à nous soucier de cas comme ceux-ci. En général, on veut un fond opaque sur lequel on dessine des choses éventuellement transparentes.

Avec les surfaces

La transparence alpha

Il nous est également possible d’appliquer de la transparence alpha sur une surface. Pour cela, nous devons utiliser l’équivalent de la fonction SDL_SetTextureBlendMode pour les surfaces. Elle s’appelle, de façon non originale, SDL_SetSurfaceBlendMode.

int SDL_SetSurfaceBlendMode(SDL_Surface*  surface,
                            SDL_BlendMode blendMode)

Son prototype ne nous surprend guère puisqu’il s’agit de celui de SDL_SetTextureBlendMode, la seule différence étant qu’il faut, bien entendu, passer une surface et non une texture en premier argument. Elle s’utilise donc de la même manière et renvoie 0 en cas de succès et une valeur négative en cas d’échec.

Notons également l’existence de la fonction SDL_GetSurfaceBlendMode qui, comme son nom l’indique, permet d’obtenir le mode de fusion d’une surface.

int SDL_GetSurfaceBlendMode(SDL_Surface*   surface,
                            SDL_BlendMode* blendMode)

Elle prend en paramètre une surface et un pointeur sur l’énumération SDL_BlendMode. À la fin de la fonction, la valeur pointée par blendmode vaudra le mode de fusion de la surface passée en paramètre. La fonction renvoie 0 si elle réussit et une valeur négative sinon.

Nous aurons rarement (voire jamais) à l’utiliser, mais elle a le mérite d’exister.

Nous ne l’avons pas vu, mais elle existe également pour les textures sous le nom de SDL_GetTextureBlendMode.

int SDL_GetTextureBlendMode(SDL_Texture*   texture,
                            SDL_BlendMode* blendMode)

Là encore, nous n’aurons pas souvent à l’utiliser.

Rendre une couleur transparente

Il est également possible de ne rendre qu’une couleur transparente. Imaginons par exemple que l’on veuille afficher une image sur notre fenêtre. Notre image a malheureusement une couleur de fond, et ce fond ne permet pas une bonne intégration de notre image sur la fenêtre. Par exemple, on veut afficher l’image qui suit sur une fenêtre blanche et on veut faire disparaître son fond noir.

Exemple
Exemple

Il est possible de supprimer la couleur noire de l’image grâce à la fonction SDL_SetColorKey.

int SDL_SetColorKey(SDL_Surface* surface,
                    int          flag,
                    Uint32       key)

Elle prend en paramètre une surface, un drapeau (SDL_TRUE pour activer la transparence, SDL_FALSE pour la désactiver) et un Uint32 qui correspond à la couleur à rendre transparente. Tous les pixels de cette couleur seront alors transparents.

Cela signifie que si, dans notre exemple, d’autres pixels que le fond avaient la couleur noire, ils seraient également transparent. Il faut faire attention à cela.

Pour rendre une couleur transparente, voici finalement le code que nous pouvons utiliser.

SDL_Surface *surface = SDL_LoadBMP("image.bmp");
SDL_SetColorKey(surface, SDL_TRUE, SDL_MapRGB(surface->format, 0, 0, 0));

En gros, rien de bien compliqué à utiliser.


Et c’est un nouveau chapitre qui se termine ici. Nous pouvons (enfin) manipuler la transparence. Il nous faudra néanmoins faire attention à ceci.

La transparence est gérée par notre programme, mais pas par les images au format BMP. Ainsi, si nous enregistrons une surface (avec SDL_SaveBMP), la transparence ne sera pas conservée.