Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.
La shoutbox n'est pas chargée par défaut pour des raisons de performances. Cliquez pour charger.

Forum Casio - Projets de programmation


Index du Forum » Projets de programmation » libimg: Transformation et composition d'images pour gint
Lephenixnoir En ligne Administrateur Points: 24645 Défis: 170 Message

libimg: Transformation et composition d'images pour gint

Posté le 10/03/2020 16:08

Ce topic fait partie de la série de topics du fxSDK.

Salut ! Juste à temps pour le CPC #26, voici une bibliothèque pour gint permettant de jouer avec les images sur Graph mono et Graph 90 Ça a été demandé par KikooDX (ici, ici).


Un petit aperçu de ce qu'on peut faire.

Voilà les fonctionnalités principales :

• Gestion complète de la transparence, composition alpha.
• Miroir horizontal et vertical, rotation de 90/180/270 degrés, agrandissement par facteur entier.
• Éclaircissement et assombrissement, fondu au blanc, recoloriage avec une couleur unie.
• Presque toutes les transformations peuvent être utilisées en place.
• Système de positionnement et références à des sous-images très flexible.

La suite de ce topic est un tutoriel d'utilisation directement traduit du README du dépôt, qui est en anglais. Enjoy!

Conversion des images et rendu VRAM

La première chose à faire pour utiliser libimg est de convertir les images au format img_t. Ça se fait en sélectionnant le type libimg-image dans les paramètres du fichier.

IMG.sprite.png = type:libimg-image

L'image fraîchement convertie peut être utilisée depuis le code C en donnant la déclaration externe qui convient, comme pour tout ce qui est converti par fxconv. Elle est prête à l'emploi dès que la définition de img_t et des fonctions de la bibliothèque, qui sont fournies par <libimg.h>, sont incluses.

#include <libimg.h>
extern img_t const sprite;

Faites attention au fait que les images converties sont en lecture seule, comme toujours quand c'est converti à la compilation. Donc on peut pas la modifier ou la transformer en-place ; j'y reviendrai. Pour se rappeller que l'image est en lecture seule, j'ai ajouté le mot-clé const ; c'est entièrement optionnel.

Pour afficher cette superbe image dans la VRAM, utilisez img_render_vram(). Sur les Graph mono type Graph 35+E, c'est la seule façon de dessiner dans la VRAM, car tristement la VRAM n'a pas le même format qu'une img_t. Sur Graph 90+E, des méthodes plus pétées sont disponibles.

img_render_vram(sprite, x, y);

img_render_vram(), comme toutes les autres fonctions de la bibliothèque, tient compte des pixels transparents. Seuls les pixels opaques sont copiés, ce qui permet de combiner facilement des images en les dessinant juste les unes après les autres.

Si vous avez une image en niveaux de gris, utilisez img_render_vram_gray() qui va afficher correctement les pixels gris (avec les mêmes paramètres). img_render_vram() se contentera de faire une approximation des gris par du noir et blanc.


Créer et détruire de nouvelles images

Puisque notre image convertie est en lecture seule, on ne peut pas faire grand-chose d'autre pour l'instant. Si on veut s'amuser avec les transformations et les animations, on doit créer des images dans lesquelles on peut véritablement écrire. Il y a deux moyens de le faire : soit en créant des nouvelles images vides, soit en dupliquant une image existante.

img_create() crée une nouvelle image non initialisée. La valeur (couleur) des pixels est aléatoire dans une nouvelle image, on verra plus tard comment utiliser img_fill() pour leur donner une couleur fixe. Parfois initialiser l'image n'est pas nécessaire et ne ferait que réduire les performances, donc libimg essaie de ne pas vous gêner et ne le fait pas automatiquement.

img_t new_32x48_image = img_create(32, 48);

img_copy() crée une copie d'une image existante. La copie est de la même taille que la source, mais tous les pixels sont dupliqués. On peut toujours écrire dans une copie, même si la source est en lecture seule.

img_t copy_of_sprite = img_copy(sprite);

Bien sûr, la création de nouvelles images peut échouer. Ces deux fonctions utilisent malloc() pour obtenir de la mémoire en interne, et malloc() échoue s'il n'y a pas assez de mémoire disponible. Dans cette situation, img_create() et img_copy() renvoie des images nulles, qui sont comme le pointeur NULL pour les images. Pour savoir si une image est nulle, utilisez img_null().

if(img_null(copy_of_sprite)) {
  /* Argh, copy has failed! */
}
else {
  /* Everything is fine, let's go! */
}

Une image nulle n'a pas de pixels, et ne peut pas être lue ou modifiée. Mais vous pouvez quand même donner des images nulles aux fonctions de la libimg ; elles s'en rendront compte et ne feront rien ou renverront d'autres images nulles. Pas de risque de crash avec ça.

Évidemment, puisqu'il y a un malloc(), il doit y avoir un free(). Les images crées par img_create() et img_copy() doivent être libérées une fois que vous n'en avez plus besoin. Appelez img_destroy() pour les libérer.

img_destroy(new_32x48_image);
img_destroy(copy_of_sprite);

Remarquez que les images converties comme sprite, les images nulles et les sous-images (présentées plus tard !) n'ont pas besoin d'être détruites car elles ne sont pas créées avec malloc(). Mais pour vous simplifier la vie, img_destroy() est programmé pour les détecter, donc en cas de doute libérez tout ce dont vous n'avez plus besoin et vous n'aurez pas de problème.


Transformations basiques

Il est temps de s'amuser avec nos superbes images. On va commencer par renverser horizontalement notre sprite pour simuler une animation de marche dans l'autre sens. Pour ça, on a besoin d'une image de la bonne taille qu'on va remplir de blanc. C'est important de remplir parce que le sprite a des pixels transparents, donc si on ne remplit pas la nouvelle image, certains des pixels aléatoires seront visibles autour du sprite transformé.

#include <gint/display.h>

img_t flipped_sprite = img_create(sprite.width, sprite.height);
img_fill(flipped_sprite, C_WHITE);

Cet exemple en profite pour démontrer quelques éléments importants :

• La taille d'une image peut être obtenue en lisant ses attributs width et height.
• La fonction img_fill() remplace tous les pixels d'une image par une couleur uniforme.
• Les couleurs utilisées dans libimg sont les même que celles utilisées dans gint, à l'exception de la transparent sur Graph 90+E, qui est 0x0001. J'y reviendrai très vite.

Maintenant qu'on a une image blanche avec la même taille que sprite, on peut se lancer et y générer le miroir horizontal de sprite :

img_hflip(sprite, flipped_sprite);

Et voilà. Maintenant img_render_vram(flipped_sprite, x, y) affiche la version renversée. Tant que flipped_sprite n'est pas libéré, il peut être affiché autant de fois qu'on veut. (Remarquez que la transformation n'aurait pas marché si flipped_sprite était en lecture seule. En particulier, on ne pourrait pas utiliser sprite comme la cible d'une transformation).

Mais attendez, puisque flipped_sprite était blanc à l'origine, on vient de faire un gros carré blanc tout moche sur l'écran. Ce qu'on voulait en fait c'est un fond transparent. On peut utiliser la couleur spéciale IMG_ALPHA pour l'obtenir.

/* Rend flipped_sprite complètement transparent : */
img_fill(flipped_sprite, IMG_ALPHA);

Sur les Graph mono, les couleurs de la libimg sont exactement les mêmes que les couleurs de gint, donc IMG_ALPHA est la même chose que C_NONE. Sur Graph 90+E, les choses sont un peu plus compliquées. Les couleurs sont au format RGB565, et tous les entiers de 16 bits sont des couleurs RGB565 valides, donc il n'y a pas de valeur disponible pour représenter la transparence. libimg résoud ce dilemme en décretant que 0x0001 représente la transparence. Cela signifie que la couleur 0x0001 n'est pas disponible dans les images de type img_t. Cette couleur a été choisie parce qu'elle est extrêmement sombre, indistinguable du noir, et qu'il est facile de comparer un nombre à 1 donc c'est un poil plus rapide dans le code. Retenez qu'il faut vraiment utiliser IMG_ALPHA pour obtenir de la transparence dans libimg.

Puisque remplir une image avec des pixels transparents est une opération courante, un raccourci appelé img_clear() est fourni pour le faire.

/* Rend aussi flipped_sprite complètement transparent : */
img_clear(flipped_sprite);

Le code complet appelle maintenant img_create(), img_clear() puis img_hflip() pour transformer. C'est aussi une séquence courante, donc un raccourci appelé img_hflip_create() a été conçu pour aller plus vite.

img_t flipped_sprite = img_hflip_create(sprite);

Toutes les transformations fonctionnent sur ce modèle. Cela comprend:

img_hflip() et img_vflip() qui renverse horizontalement et verticalement.
img_rotate() qui tourne de 0, 90, 180 ou 270 degrés.
img_upscale() qui multiple la taille d'un facteur entier.
img_dye() qui remplace tous les pixels opaques par une couleur unie.
img_ligthen(), img_whiten() et img_darken() qui jouent sur la luminosité.

On peut aussi commencer à mentionner des conventions utiles de la bibliothèque :

• Toutes les transformations prennent la source en la destination en premiers arguments, et ensuite le reste des paramètres comme l'angle de rotation.
• Toutes les transformations ont une variante _create() qui ne prend pas de destination en argument, et en crée une avec juste la bonne taille, puis la retourne après la transformation.


Sous-surfaces et positionnement

Jusqu'ici on n'a transformé des images que vers des destinations de la même taille, ou si vous avez essayé la rotation ou l'agrandissement, de tailles similaires. Imaginons qu'on veut créer une spritesheet avec à la fois le sprite original et la version renversée. On a un problème parce que img_hflip() ne nous permet pas de dire où placer le résultat transformé dans l'image de destination. En fait, img_hflip() place toujours le résultat dans le coin haut gauche.

C'est parce que la libimg possède un système de positionnement plus puissant que de rajouter des paramètres. Ce système est construit autour de l'idée d'extraire des références à des sous-images.

Prenons directement un exemple. On va créer une spritesheet de 32x16 avec un sprite de 16x16 sur la gauche et un sur la droite.

img_t spritesheet = img_create(32, 16);

Le sprite de droite est à la position (16,0) dans spritesheet. Si on veut l'utiliser souvent ou appliquer beaucoup de transformations, on va devoir répéter ces coordonnées de nombreuses fois, ce qui est casse-pieds et source d'erreur. À la place, on peut utiliser la fonction img_sub() pour obtenir une référence vers la moitié droite de spritesheet.

img_t right_sprite = img_sub(spritesheet, 16, 0, 16, 16);

img_sub() prend cinq paramètres : l'image source, et la position et taille de la sous-image qui nous intéresse, sous la forme x, y, w et h. Le sprite de droite commence à x=16 et y=0, et est de largeur w=16 et hauteur h=16.

img_sub() ne crée pas une nouvelle image. Elle donne simplement une nouvelle vue sur les mêmes pixels. Modifier right_sprite affecterait totalement la moitié droite de spritesheet. D'ailleurs, on n'a qu'à faire ça.

img_fill(right_sprite, C_BLACK);

Et juste comme ça, on vient de remplir uniquement la moitié de spritesheet. Appeler img_fill(spritesheet, C_BLACK) en aurait rempli la totalité. Voyez comment le système de positionnement rend img_fill() aussi polyvalent qu'une fonction de remplissage de rectangles.

Avec tout ça sous la main, on peut maintenant créer notre spritesheet :

/* Copie le sprite normal dans la moitié gauche */
img_render(sprite, spritesheet);
/* Renverse le sprite sur la moitié droite */
img_hflip(sprite, right_sprite);

Bien sûr, ce système marche avec toutes les transformations. Notez cependant que si l'image ou sous-image donnée comme destination doit être au moins aussi grande que le résultat de la transformation. Si la destination est plus petite, la transformation ne fera rien.

La sous-image ne contient pas nouveaux pixels, donc elle n'a pas besoin d'être détruite. Vous pouvez quand même appeler img_destroy() dessus et il ne se passera rien. Mais si on n'a pas besoin de la détruire, on n'a pas besoin de la stocker dans une variable. Et donc on peut renverser le sprite de cette façon :

img_hflip(sprite, img_sub(spritesheet, 16, 0, 16, 16));

Ça se lit « renverse sprite horizontalement, et écris le résultat dans spritesheet, à la position (16,0) dans un rectangle de taille 16x16 ».

Ici on sait que la taille du rectangle doit être 16x16, parce que c'est la taille du sprite. Donc on peut s'en passer en indiquant seulement une largeur et une hauteur de -1. Ça créera une référence qui commencera à la positon (16,0) et ira jusqu'au coin en bas à droite de spritesheet. img_hflip() ne modifiera quand même que les premiers 16x16 pixels dans le coin haut gauche, donc ce n'est pas grave si la référence est plus grande que nécessaire.

img_hflip(sprite, img_sub(spritesheet, 16, 0, -1, -1));

Si vous avez l'impression que c'est une construction commune, vous avez parfaitement raison ! Et donc un raccourci a été créé pour aller plus vite. Au lieu de spécifier une largeur et une hauteur de -1, on peut appeler img_at() qui les spécifiera à notre place. img_at(img, x, y) est la même chose que img_sub(img, x, y, -1, -1). Donc la transformation devient :

img_hflip(sprite, img_at(spritehseet, 16, 0));

Ce qui se lit « renverse sprite horizontalement et écris le résultat dans spritesheet, à la position (16,0) ». C'est beaucoup plus court que tout à l'heure, et comme la référence à la sous-image n'a pas besoin d'être libérée, on ne risque pas de créer de fuite de mémoire.

Les références aux sous-images sont un outil puissant et elles peuvent être utilisées d'un bon nombre de façons. Mais il y a une règle d'or : détruire l'original détruit aussi les sous-images. Une fois que vous avez appelé img_destroy(spritesheet), toutes les sous-images, y compris right_sprite, deviennent invalides et toute utilisation serait ue erreur. Gardez toujours un oeil sur les originaux !

Comme exemple de la polyvalence des références aux sous-images, remarques qu'on peut utiliser img_sub() sur la source de la transformation pour n'en transformer qu'une partie !

J'ai utilisé img_render() sans expliquer ce qu'elle faisait, donc c'est un bon moment pour y jeter un oeil.

Rendu direc sur la VRAM (Graph 90+E)

Sur Graph mono, la VRAM n'a pas le même format qu'une img_t, donc on ne peut pas utiliser la VRAM comme la destination d'une transformation. On ne peut qu'utiliser img_render_vram() pour copier une image vers la VRAM une fois qu'elle a été préparée.

Mais sur Graph 90+E, la VRAM a bel et bien le même format qu'une img_t. Pour en obtenir une référence, utilisez img_vram() :

img_t vram_as_an_image = img_vram();

Comme ce n'est qu'une référence, il n'y a pas besoin de la détruire avec img_destroy(). Le faire quand même ne produit aucun effet, donc en cas de doute, libérez toujours toutes les images après utilisation.

Cette référence vers la VRAM nous permet de transformer directement vers la VRAM sans passer par des images temporaires. Par exemple, on peut écrire :

img_hflip(sprite, img_at(img_vram(), x, y));

De la même façon, les deux appels suivants reùplissent un rectangle de la VRAM (bien que drect() soit plus rapide) :

#include <gint/display.h>

drect(x, y, w, h, color);
img_fill(img_sub(img_vram(), x, y, w, h), color);

C'est ici que la fonction img_render() commence à briller. Cette fonction copie l'image source vers l'image destination sans la transformer. Contrairement aux transformations, elle supporte le clipping, donc si la destination est plus petite que la source, elle copie uniquement les pixels visibles. (Les transformations paniquent et ne font rien si cette situation se présente.) En fait, sur Graph 90+E la fonction img_render_vram() n'est qu'un raccourci pour :

img_render(src, img_at(img_vram(), x, y));

img_render() est utilisée le plus souvent de ette façon, pour copier (faire le rendu) d'une image vers la VRAM. D'où son nom.


Transformations en-place

Certaines transformations peuvent transformer une image sans utiliser d'image destination auxiliaire. L'image source est écrasée durant la transformation, donc l'original est perdu mais il n'y a pas besoin de mémoire supplémentaire. Ça s'appelle une transformation en-place.

Il n'est possible de transformer en-place que si l'image transformée est de la même taille que l'image source. Dans la version actuelle, cela inclut toutes les transformations, sauf les rotations par 90 et 270 degrés quand l'image n'est pas carrée, et l'agrandissement par une facteur non trivial.

Pour exécuter une transformation en-place, utilisez la source comme destination :

img_t sprite_copy = img_copy(sprite);
img_hflip(sprite_copy, sprite_copy);

Il est également possible d'utiliser une sous-image d'une image comme source et une autre sous-image comme cible. L'appel ci-dessous renverse horizontalement la moitié gauche de spritesheet dans la moitié droite :

img_hflip(img_sub(spritesheet, 0, 0, 16, 16), img_at(spritesheet, 16, 0));

On peut aussi faire ça directement sur la VRAM sur Graph 90+E puisque la VRAM n'est qu'une img_t déguisée.

Remarquez cependant qu'on ne peut pas transformer si la source et la cible se recouvrent partiellement (ie. elles ne sont pas disjointes même elles en sont pas non plus la même zone). Aucune transformation ne le supporte et le résultats sera toujours faux !


Accès aux pixels et transformations personnalisées

Toute image peut être lue manuellement pour connaître la couleur des pixels, et également écrite si elle n'est pas en lecture seule. img_t est une stucture avec une description de l'image et les attributs utiles suivants :

img.width et img.height sont la largeur et la hauteur de l'image, respectivement.
img.stride est le nombre de pixels entre deux lignes. Souvent c'est plus grand que img.width !
img.pixels est le tableau des pixels.

Le format est ligne par ligne (row-major order) avec espacement (stride). Voici à quoi une image de taille 8x3 avec un espacement de 12 ressemble en mémoire. Les nombres sur le diagramme correspondent aux indices dans img.pixels.

<------ img.width ------>
+-------------------------+-------------+
|  0  1  2  3  4  5  6  7 |  .  .  .  . |  ^
| 12 13 14 15 16 18 18 19 |  .  .  .  . |  img.height
| 24 25 26 27 28 29  etc  |  .  .  .  . |  v
+-------------------------+-------------+
<------------ img.stride ------------->

Par exemple, le troisième pixel de la deuxième ligne est img.pixels[13]. Remarquez qu'il faut ajouter img.stride pour descendre d'une ligne, et non pas img.width. Par ailleurs, les indices représentés par des points, comme pixels[8], ne font pas partie de l'image et ne doivent en aucun cas être lus.

La raison derrière ce format avec espacement est pour supporter les références aux sous-images. Quand on crée une spritesheet de 32x16, l'espacement est de 32 et donc il n'y a pas de vide, comme on pourrait s'y atendre. Mais quand on extrait une moitié de 16x16, même si on ne regarde plus que la moitié des lignes, il y a quand même 32 pixels entre chaque ! C'est pour ça que l'espacement d'une image est souvent plus grand que sa largeur.

Cela signifie aussi que les images ne sont pas continues dans la mémoire, donc il n'est pas possible de remplacer tous les pixels en un seul appel à memcpy().

Voici comment une fonction fait pour itérer sur tous les pixels d'une image. Le code suivant remplace tous les pixels transparents par du blanc :

img_pixel_t *px = img.pixels;

for(int y = 0; y < img.height; y++)
{
    for(int x = 0; x < img.width; x++)
    {
        /* Et on fait ce qu'on veut avec px[x] */
        if(px[x] == IMG_ALPHA) px[x] = C_WHITE;
    }

    px += img.stride;
}

Les pixels sont de type img_pixel_t. La façon de les manipuler dépend de la plateforme visée :

• Sur Graph mono, img_pixel_t est uint8_t et ses valeurs sont les couleurs de <gint/display.h>. La couleur transparente IMG_ALPHA est égale à C_NONE.
• Sur Graph 90+E, img_pixel_t est uint16_t et ses valeurs sont les couleurs RGB565. Les couleurs opaques de <gint/display.h> peuvent être utilisées, à l'exception de IMG_ALPHA = 0x0001 qui représente la transparence. Remarquez que IMG_ALPHA n'est pas égal à C_NONE.


Fife86 Hors ligne Membre Points: 839 Défis: 0 Message

Citer : Posté le 20/06/2024 16:51 | #


Sans tester, ça donnerait un truc comme ça (en supposant que l'image soit accessible en écriture dans ses métadonnées fxconv)


Comment on le spécifie dans les métadonnées ? ^^'
It's Show Time !!!
Mes Jeux :
- Street Fighter : Pour les accrocs du free-fight.
- Kirby's DreamLand : Gobe , Gobe , Gobe !!!
- L'invasion Seanchans : Détruit la flotte ennemis a bord du "Danseur des vagues".


< Le recoin du C-Engine >
Lephenixnoir En ligne Administrateur Points: 24645 Défis: 170 Message

Citer : Posté le 20/06/2024 16:58 | #


J'était en train de toucher au code de bopti_render pour bien comprendre comment il fonctionnait, mais je n'arrive pas à ce que mes modifications soit prises en compte lorsque j'execute fxsdk build-fx. Pourtant, je fais bien giteapc build --skip-configure gint

Remplace build par install. Il faut installer aussi.

Comment on le spécifie dans les métadonnées ? ^^'

section: .data
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Fife86 Hors ligne Membre Points: 839 Défis: 0 Message

Citer : Posté le 20/06/2024 17:10 | #


Merci beaucoup

Je viens de réussir à hflip l'image !


       int j1 = l + i*layers + y * lw * layers;
       int j2 = l + (lw-i-1)*layers + y * lw * layers;


J'ai rajouté le y dans la formule de calcul de j1 et j2 aussi
It's Show Time !!!
Mes Jeux :
- Street Fighter : Pour les accrocs du free-fight.
- Kirby's DreamLand : Gobe , Gobe , Gobe !!!
- L'invasion Seanchans : Détruit la flotte ennemis a bord du "Danseur des vagues".


< Le recoin du C-Engine >
Lephenixnoir En ligne Administrateur Points: 24645 Défis: 170 Message

Citer : Posté le 20/06/2024 17:20 | #


Oups, bien vu l'erreur !

Note que dans le shift je me suis trompé. Si l'image a une taille qui n'est pas multiple de 32 il y a initialement des zéros à droite. Après le flip les zéros sont à gauche, donc j'ai tout décalé vers la gauche. Mais j'ai oublié de transférer les bits perdus de chaque entier de 32 bits que je décale vers l'entier de 32 bits précédent.

Tu peux ignorer cette étape si ça te dérange pas que le padding soit à gauche après le flip.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Fife86 Hors ligne Membre Points: 839 Défis: 0 Message

Citer : Posté le 20/06/2024 17:25 | #


Oui, oui, j'ai vu ^^"
C'est pas un gros soucis à prendre en compte.

J'ai trouvé un moyen pour monitorer les fps de mon programme avec libprof.

Une idée de comment mesurer l'espace mémoire utilisé durant l'execution du programme ?
It's Show Time !!!
Mes Jeux :
- Street Fighter : Pour les accrocs du free-fight.
- Kirby's DreamLand : Gobe , Gobe , Gobe !!!
- L'invasion Seanchans : Détruit la flotte ennemis a bord du "Danseur des vagues".


< Le recoin du C-Engine >
Lephenixnoir En ligne Administrateur Points: 24645 Défis: 170 Message

Citer : Posté le 20/06/2024 17:27 | #


Pour la partie dynamique tu peux connaître l'espace utilisé/libre dans l'arène malloc() gérée par gint comme ceci :

#include <gint/kmalloc.h>
kmalloc_gint_stats_t *stats = kmalloc_get_gint_stats(kmalloc_get_arena("_uram"));
stats->free_memory;
stats->used_memory;

Plus de détails dans kmalloc.h.

Si cette arène est pleine gint passe sur le malloc() système pour lequel tu ne peux malheureusement pas avoir de statistiques. Donc ce n'est une valeur précise que tant que l'arène est pas pleine.
Mon graphe (11 Avril): ((Rogue Life || HH2) ; PythonExtra ; serial gint ; Boson X ; passe gint 3 ; ...) || (shoutbox v5 ; v5)
Fife86 Hors ligne Membre Points: 839 Défis: 0 Message

Citer : Posté le 20/06/2024 17:33 | #


Super merci
It's Show Time !!!
Mes Jeux :
- Street Fighter : Pour les accrocs du free-fight.
- Kirby's DreamLand : Gobe , Gobe , Gobe !!!
- L'invasion Seanchans : Détruit la flotte ennemis a bord du "Danseur des vagues".


< Le recoin du C-Engine >

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v4.3 © créé par Neuronix et Muelsaco 2004 - 2024 | Il y a 56 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd