Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zéro > Les tutoriels > Non-Officiels > Programmation > C > [SDL] Modifier une image pixel par pixel > Lecture du tutoriel

[SDL] Modifier une image pixel par pixel

Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Avatar
Auteur : Piwaï
Note : Pas de note

Plus d'informations Plus d'informations
Bonjour !! Soyez les bienvenus dans ce mini-tuto qui va vous apprendre à modifier une image pixel par pixel en SDL.

Qu'est-ce que la SDL ?


:o ... Hélas, ceci est un mini-tuto et je vais me concentrer sur l'essentiel. :) Donc si vous ne savez pas ce que c'est que le C, la SDL, pas la peine de s'embêter à lire ce tutoriel, il ne vous sera pas d'une grande aide. Je vous conseille plutôt d'aller voir le très bon tutoriel de M@teo21 ici.

Les pré requis :

Et bien il faut connaître à fond le tutoriel de M@teo21 sur le C/C++ :)
Vous devez disposer d'un IDE C++, avec la SDL installée.

Et puis... c'est tout :D

Et que saura t'on faire à la fin ?

Modifier une image pixel par pixel. Plus précisément, je vais dans un premier temps m'attacher à l'exemple de la réalisation d'une balance des couleurs, puis nous verrons d'autres fonctions particulièrement intéressantes à réaliser.

Sommaire du chapitre :
Icône du chapitre

Un peu de théorie

Posons le problème

Pour ce tutoriel, je vais partir du principe que vous n'êtes plus tout à fait des zéros ;) .

Voici notre problème principal :

Lorsque j'utilise la SDL, comment faire pour modifier mes images en direct, les assombrir, enlever la moitié des pixels, dessiner un éléphant rose ?

Bon pour l'éléphant rose, je cherche encore :p .

Comment faire ?

Le principe est extrêmement simple :

Vous savez naturellement ce qu'est une surface en SDL :D . (Sinon, allez hop au galop on va relire le cours ici)

Une surface est un ensemble de pixels. Elle est composée de n pixels, avec n = son nombre de pixels en largeur multiplié par son nombre de pixels en hauteur.

Qu'est-ce qu'un pixel ? C'est un point sur l'écran :p . Dans une surface, il contient 4 informations : une valeur en rouge, une valeur en vert, une valeur en bleu, et une valeur en alpha.
Je suppose que RVB, vous voyez ce que c'est ;) .

Mais Alpha, c'est quoi ce machin la ?


Et bien cela représente le degré de transparence (entre 0 et 255) du pixel. Avec 255, le pixel est opaque, avec 0 il est transparent.

M@teo21 en a déjà causé ici.

Le souci c'est qu'il parle d'une transparence alpha s'appliquant de manière globale, à une image entière.
Il est possible de mettre une valeur alpha différente pour chaque pixel, c'est d?ailleurs le principe des masques. Je ne vais pas m'appesantir sur le sujet pour le moment, sachez seulement que ça existe, voici d?ailleurs une image expliquant assez clairement comment fonctionne un masque :

Image utilisateur

Ca va jusque là ;) ? J'espère... :-°

Qu'allons nous donc faire ?

Très simple :

1)Je prends ma surface.
2)Je parcours ma surface suivant l'axe des X et l'axe des Y, et je modifie chaque pixel comme je l'entends.
3)Je libère ma surface.

Cependant, il y a quand même quelques complications :euh: .

En effet, pour modifier un pixel, il faut récupérer l'adresse de ce pixel dans la surface, puis récupérer la valeur représentant la couleur du pixel, la décomposer suivant ses quatre composantes, modifier ces composantes, les réunir en une seule valeur, et renvoyer cette information à l?adresse du pixel.

Pfiou... :euh: ça m'a pas l'air si simple que ça finalement :euh:

Ne vous inquiétez pas, on va réussir à simplifier grandement le problème. En effet, vous n'êtes pas les premiers à vouloir modifier une image pixel par pixel. Dans la documentation de la SDL, les exemples de codes pour modifier un pixel sont fournis. Et une fois qu'on les a, tout se simplifie !!

Donc merci la doc SDL et merci Internet, ça prémache le boulot :D :D

Bon, et bien on a tout vu d'un point de vue théorique. Si c'est pas clair... :o Je pense que ça le deviendra avec l'application !! C'est partiii !! :p

Les bases techniques pour que ça fonctionne

Dans cette seconde partie, nous allons étudier toutes les petites briques à assembler pour créer un superbe lego.

Attention, cette fois on rentre dans le code, et tout de suite c'est un peu moins marrant.


Lesquelles ? Haha !! Grande question ;) .
En fait, il nous faut une fonction pour connaître la couleur d'un pixel, et une fonction pour modifier sa couleur.

Et c'est la partie la plus difficile de ce tutoriel. En effet, ce sont des notions que vous n'avez pas forcement vus avec M@teo21, et qui ne sont pas évidentes à comprendre.

Je vous propose donc d'utiliser ces fonctions comme des boites noires :euh: . On se fiche de savoir comment elles fonctionnent, du moment qu'elles marchent autant s'en servir. Ce n'est pas très déontologique, mais ça va nous permettre d'avancer plus vite.

J'ai décidé de quand même commenter le code, si vous maitrisez les pointeurs et les zones de mémoires, le fonctionnement devrait vous paraitre plus clair.

D'abord, récupérer la couleur d'un pixel :

Code : C
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* ********************************************************************* */
/*obtenirPixel : permet de récupérer la couleur d'un pixel
Paramètres d'entrée/sortie :
SDL_Surface *surface : la surface sur laquelle on va récupérer la couleur d'un pixel
int x : la coordonnée en x du pixel à récupérer
int y : la coordonnée en y du pixel à récupérer

Uint32 resultat : la fonction renvoie le pixel aux coordonnées (x,y) dans la surface
*/
Uint32 obtenirPixel(SDL_Surface *surface, int x, int y)
{
    /*nbOctetsParPixel représente le nombre d'octets utilisés pour stocker un pixel.
    En multipliant ce nombre d'octets par 8 (un octet = 8 bits), on obtient la profondeur de couleur
    de l'image : 8, 16, 24 ou 32 bits.*/
    int nbOctetsParPixel = surface->format->BytesPerPixel;
    /* Ici p est l'adresse du pixel que l'on veut connaitre */
    /*surface->pixels contient l'adresse du premier pixel de l'image*/
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * nbOctetsParPixel;

    /*Gestion différente suivant le nombre d'octets par pixel de l'image*/
    switch(nbOctetsParPixel)
    {
        case 1:
            return *p;

        case 2:
            return *(Uint16 *)p;

        case 3:
            /*Suivant l'architecture de la machine*/
            if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
                return p[0] << 16 | p[1] << 8 | p[2];
            else
                return p[0] | p[1] << 8 | p[2] << 16;

        case 4:
            return *(Uint32 *)p;

        /*Ne devrait pas arriver, mais évite les erreurs*/
        default:
            return 0; 
    }
}


Que doit t'on retenir ?

- Cette fonction reçoit une surface en paramètre, ainsi que la position en X et en Y du pixel sur l'image (rappel : le point de coordonnées 0,0 est en haut à gauche de l'image).
- Elle retourne un entier qui décrit la couleur du pixel (rouge, vert, bleu et alpha).

Donc, sur le même principe, voici comment modifier un pixel :

Code : C
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/* ********************************************************************* */
/*definirPixel : permet de modifier la couleur d'un pixel
Paramètres d'entrée/sortie :
SDL_Surface *surface : la surface sur laquelle on va modifier la couleur d'un pixel
int x : la coordonnée en x du pixel à modifier
int y : la coordonnée en y du pixel à modifier
Uint32 pixel : le pixel à insérer
*/
void definirPixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
{
    /*nbOctetsParPixel représente le nombre d'octets utilisés pour stocker un pixel.
    En multipliant ce nombre d'octets par 8 (un octet = 8 bits), on obtient la profondeur de couleur
    de l'image : 8, 16, 24 ou 32 bits.*/
    int nbOctetsParPixel = surface->format->BytesPerPixel;
    /*Ici p est l'adresse du pixel que l'on veut modifier*/
    /*surface->pixels contient l'adresse du premier pixel de l'image*/
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * nbOctetsParPixel;

    /*Gestion différente suivant le nombre d'octets par pixel de l'image*/
    switch(nbOctetsParPixel)
    {
        case 1:
            *p = pixel;
            break;

        case 2:
            *(Uint16 *)p = pixel;
            break;

        case 3:
            /*Suivant l'architecture de la machine*/
            if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            {
                p[0] = (pixel >> 16) & 0xff;
                p[1] = (pixel >> 8) & 0xff;
                p[2] = pixel & 0xff;
            }
            else
            {
                p[0] = pixel & 0xff;
                p[1] = (pixel >> 8) & 0xff;
                p[2] = (pixel >> 16) & 0xff;
            }
            break;

        case 4:
            *(Uint32 *)p = pixel;
            break;
    }
}


De même :
Cette fonction reçoit en paramètre une surface, les coordonnées x et y du pixel, et la couleur que l'on veut y mettre.

L'intérêt principal de ces fonctions ?

Elles sont faites pour fonctionner avec n'importe quel format d'image, vous n'aurez jamais de couleurs faussées avec ces fonctions.

Mais... Qu'est-ce qu'un Uint32 ???


C'est un entier d'un type un peu particulier (non signé et enregistré sur 32 bits), créé par la bibliothèque SDL (il existe des Uint8, des Uint16, etc.).

Un entier non signé ne peut contenir que des valeurs positives. 32 bits indiques le nombre de valeurs qu'il peut prendre.
Un Uint32 peut prendre des valeurs entre 0 et (2^32) -1.
Un Unit8 peut prendre des valeurs entre 0 et (2^8) -1, c'est à dire entre 0 et 255.

Vous devez donc récupérer la valeur d'un pixel avec ce genre de code :

Code : C
1
2
3
4
5
6
Uint32 pixel;
/*Pour récupérer le code couleur d'un pixel*/
pixel=obtenirPixel(surface,x,y);

/*Et pour changer la valeur d'un pixel*/
definirPixel(surface,x,y,pixel);


Maintenant bien sur, ce qui nous intéresse, c'est modifier la valeur de la variable pixel.

Donc dans un premier temps, il faut récupérer les composantes rouge, vert, bleu et alpha du pixel, puis les modifier, et enfin les réinjecter.

En SDL, chaque composante couleur est stockée sur 8 bits. Ce sont donc des Uint8.


On va utiliser les fonctions SDL_GetRGBA et SDL_MapRGBA.

Il faut lui passer les 4 variables de composante par référence.
Voici un code relativement clair, du moins je l'espère :
Code : C
1
2
3
4
5
6
7
8
9
/*Composantes pouvant prendre des valeurs entre 0 et 255*/
Uint8 r,g,b,a;
/* On extrait de pixel la valeur de chaque composante*/
SDL_GetRGBA(pixel, surface->format, &r, &v, &b, &a);

/* On modifiera ici les variables r, v, b et a.*/

/*Et une fois qu'on les a modifiées, on réinjecte dans pixel.*/
pixel=SDL_MapRGBA(surface->format, r, v, b, a);


Petite précision : 'surface' est le nom de votre surface :p , et format permet à la fonction de connaître la façon dont sont enregistrés vos pixels (nous... on s'en fout :p ). Il faut juste retenir que le premier paramètre est sous la forme 'nomDeLaSurface->format'.

Heu.. Serait-il possible de résumer ?


Bien sur, bien sur :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Uint32 pixel;
Uint8 r,g,b,a;
int x,y;
x=3;
y=5;

SDL_LockSurface(surface); /*On bloque la surface*/

pixel=obtenirPixel(surface,x,y);
SDL_GetRGBA(pixel, surface->format, &r, &v, &b, &a);

/*Ici, on mettra du code pour modifier les composantes du pixel.*/

/*Et une fois qu'on les a modifiés :*/
pixel=SDL_MapRGBA(surface->format, r, v, b, a);

/*Et pour changer la valeur d'un pixel :*/
definirPixel(surface,x,y,pixel);

SDL_UnlockSurface(surface); /*On libère la surface, elle peut être utilisée pour une autre tâche*/


C'est à peu près clair :) ? Bon bah on va pouvoir continuer !! C'est bientôt fini, ne vous inquiétez pas !! :D

Donc désormais, nous avons récupéré r, v, b, et a qui sont les composantes de notre pixel.

Ces composantes sont des Uint8. Ils sont stockés sur 8 bits. Donc... ?
Et bien ils peuvent prendre chacun 256 valeurs différentes. C'est à dire une valeur entre 0 et 255.

Comment ça je l'ai déjà dit ? :-° C'est pour que ça rentre mieux :D :D .

Le 0 indique l'absence de la composante, (et la transparence totale pour la composante alpha), et le 255 indique le maximum de cette couleur (et opaque pour la composante alpha).

Quelques exemples :

r,g,b,a :

0,0,0,255 : Aucune couleur --> noir. Et opaque.
255,255,255,255 : Toutes les couleurs --> de la lumière blanche !! Et opaque aussi bien sur.
255,0,0,128 : Seule la composante rouge est présente pour les couleurs, c'est donc un rouge. Cependant, ce pixel est à moitié transparent (128=255/2) donc il laisse filtrer une partie de la couleur du pixel sur lequel il sera collé.
255,255,255,0 : Un pixel de couleur blanche... Et totalement transparent !! Donc... Il ne sera pas visible !! :)

C'est compris ? ;)

A savoir : les opérations sur des Uint8 ne doivent jamais sortir de l'intervalle [0,255]


Si par exemple vous prenez un pixel qui a une composante rouge de 150, et que vous lui additionnez 150, vous n'obtiendrez ni 300 (puisque le maximum est 255), ni 255, mais plutôt... N'importe quoi :D .

La solution ? Convertir les Uint8 en int lors des calculs.

Concrètement, voici comment faire :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int tampon; /*Variable entière qui sert de tampon*/
    int ajout;
    Uint8 composante,resultat;

    composante=155;
    ajout=120;
    tampon=(int)composante + ajout;

    /*On fait en sorte que tampon reste dans l'intervalle [0,255] */
    if (tampon>255) {tampon=255;} else if (tampon<0) {tampon=0;}

    /*Avec l'opérateur cast (Uint8) on tranforme tampon en un entier de type Uint8*/
    resultat=(Uint8)tampon;


Bon et bien vous avez tous les outils en main il me semble.

Comment modifier l'intégralité de l'image ?

Comme dans les cours de M@teo21, avec une boucle for.


Par exemple :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int x,y;

    SDL_LockSurface(surface); /*On bloque la surface*/
    for (y=0;y<surface->h;y++)
    {
        for (x=0;x<surface->w;x++)
        {
/*Ici on modifie chaque pixel*/
        }
    }
    SDL_UnlockSurface(surface); /*On libère la surface, elle peut être utilisée*/


Dernier point, mais non des moindres : une fois une image chargée en mémoire, il peut être intéressant d'en réaliser une copie, dans une autre image, afin de modifier cette deuxième image et garder l'original intact.

Voici donc le principe de fonctionnement :
1) Charger l'image originale
2) Créer une surface vide dans la nouvelle image, possédant les caractéristiques de l'image originale
3) Copier l'image originale dans la nouvelle image.

C'est donc relativement simple, voici le code :
Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/*On déclare les pointeurs sur des surfaces que l'on va utiliser*/
SDL_Surface *image = NULL;
SDL_Surface *copie = NULL;

/*On charge l'image à copier*/
image = SDL_LoadBMP("image.bmp");

/*On créé une surface vide de la taille de l'image à copier*/
copie = SDL_CreateRGBSurface(SDL_HWSURFACE, image->w, image->h, 32,  MASQUE_ROUGE, MASQUE_VERT, MASQUE_BLEU, MASQUE_ALPHA);

/*On copie l'image originale*/
SDL_BlitSurface(image, NULL, copie, NULL);

Il est probable que la fonction SDL_CreateRGBSurface vous paraisse bien étrange. Voici ce qu'en dit M@teo21 dans son cours :

Citation : M@teo21

Cette fonction prend... beaucoup de paramètres :p (8 !) D'ailleurs, peu d'entre eux nous intéressent pour l'instant, donc je vais éviter de vous détailler ceux qui ne nous serviront pas de suite.
Comme en C nous sommes obligés d'indiquer tous les paramètres (les paramètres facultatifs n'existent qu'en C++), nous enverrons la valeur 0 quand le paramètre ne nous intéresse pas.

Regardons de plus près les 4 premiers paramètres, les plus intéressants :

* Une liste de flags (des options). Vous avez le choix entre :
o SDL_HWSURFACE : la surface sera chargée en mémoire vidéo. Il y a moins d'espace dans cette mémoire que dans la mémoire système (quoique, avec les cartes 3D qu'on sort de nos jours...), mais cette mémoire est plus optimisée et accélérée.
o SDL_SWSURFACE : la surface sera chargée en mémoire système où il y a beaucoup de place, mais cela obligera votre processeur à faire plus de calculs. Si vous aviez chargé la surface en mémoire vidéo, c'est la carte 3D qui aurait fait la plupart des calculs.

* La largeur de la surface (en pixels)
* La hauteur de la surface (en pixels)
* Le nombre de couleurs (en bits / pixel)



Voici donc comment on alloue notre nouvelle surface en mémoire :

Code : C
rectangle = SDL_CreateRGBSurface(SDL_HWSURFACE, 220, 180, 32, 0, 0, 0, 0);


Les 4 derniers paramètres sont mis à 0 comme je vous l'ai dit car ils ne nous intéressent pas.


Mais, dans ton exemple de code, tu n'as pas mis les 4 derniers paramètres à 0 !!


Effectivement. :) N'oubliez pas que lorsque l'on manipule des images, on se rapproche beaucoup de tout ce qui est bas niveau.

Dans la plupart des cas, laisser les 4 derniers paramètres à 0 convient parfaitement.

Sauf dans un : lorsque l'on veut modifier la transparence alpha d'une image, pixel par pixel !!

Si l'on veut que nos modifications de transparence soient prises en compte lors d'un blit (affichage d'une image dans une autre), il va nous falloir modifier les 4 derniers paramètres, pour indiquer la façon dont les données sont enregistrées.

Bien sur, j'ai utilisée des constantes, que je définis ainsi au début de mon programme :

Code : C
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/*Suivant l'architecture de la machine, deux manières d'enregistrer des données en mémoire*/
#if SDL_BYTEORDER == SDL_BIG_ENDIAN

    #define MASQUE_ROUGE 0xff000000
    #define MASQUE_VERT  0x00ff0000
    #define MASQUE_BLEU  0x0000ff00
    #define MASQUE_ALPHA 0x000000ff

#else

    #define MASQUE_ROUGE 0x000000ff
    #define MASQUE_VERT  0x0000ff00
    #define MASQUE_BLEU  0x00ff0000
    #define MASQUE_ALPHA 0xff000000

#endif


Tout ce que vous devez retenir, c'est qu'il vous faudra mettre ces 4 constantes en derniers paramètres pour que la modification pixel par pixel de transparence alpha fonctionne.

A quoi ça peut bien servir ?


Et bien, par exemple, cela peut permettre de réaliser des masques, c'est à dire rendre une image plus ou moins transparente en divers endroits.

Et voila !! Il ne vous reste plus qu'à mélanger tout ça pour obtenir de superbes effets.

Voila, c'est fini !!

J'espère que ce mini-tuto vous aura plus, et que vous deviendrez des pro de la manipulation d'image :D :D .

Bien sur, nous n'avons réalisé qu'une balance des couleurs :( . Et alors o_O ?

Bah... Qu'est-ce que je pourrai réaliser d'autre ??


Alors là, il existe moult et pléthores possibilités de fonctions à réaliser.

Quelques exemples :

Luminosité
Contraste
Rotation
Symétrie
Niveau de gris
Ajout d'un masque alpha en niveau de gris à une image en couleur.
etc.

Quelques pistes :

Pour la luminosité, c'est comme la balance des couleurs, sauf qu'on ajoute ou enlève la même valeur pour chaque composante (rouge, vert, bleu). On fait ainsi tendre l'image vers le blanc (255,255,255) ou le noir (0,0,0).

Pour le contraste, il faut calculer la valeur moyenne sur toute l'image pour chaque composante R,V,B, puis ensuite pour chaque pixel le rapprocher (homogénéité) ou l'éloigner (contraste) de cette valeur pour chaque composante, proportionnellement à un coefficient de contraste.

Pour les niveaux de gris, pour chaque pixel on fait la moyenne de ces 3 composantes couleurs, et on leur attribut cette moyenne.

Voila, désormais c'est définitivement terminé ^^ .

Ah j'oubliai : si vous réalisez des effets sympas sur des images, suite à ce tutorial (ou non :euh: ), n'hésitez pas à me le signaler dans les commentaires, ça m'intéresse :D .
Retour en haut Retour en haut


Créé : le 29/03/2006 à 10:22:52
Modifié : Aujourd'hui à 18:30:16
Avancement : 100%
Nb de visites : 19644
Licence : Copie non autorisée

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | Fil RSS | XHTML 1.0 | CSS 2.0
Édité par Simple IT SARL : Nous contacter | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Hébergement web - Correction de tutoriels - Créer un site
Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 420 Zéros connectés | Requêtes SQL 9 requêtes | Temps de génération de la page : Total (SQL) 0.1323s (0.1215s)