Pour certains, les palettes sont des entités un peu mystérieuses... Voici donc un tuto sur 2 façon de les manipuler pour obtenir des effets de transitions sympas (plus un troisième expliqué mais non codé car code identique au deuxième

)
Introduction aux palettesAvant de commencer, il faut savoir comment marche une palette sur Nintendo DS. Pour reprendre les bases, toute image qui n'est pas en 16bit utilise forcément une palette. A quoi ça sert ? Et bien ça permet de gagner énormément de place en mémoire

Ainsi, une image en 8bit prend 2 fois moins de place qu'une image 16bit. Sauf qu'au lieu d'avoir accès à toutes les valeurs, toutes les couleurs, chaque pixel sera limité à 0-255 :s Pour palier à ce défaut, on dispose d'un tableau de 256 valeurs, en 16bit... Ainsi, quand un pixel a la couleur indexée 0, ça renvoie en fait à la première entrée dans la palette, etc... Pratique !
Maintenant, on va voir rapidement comme est composée la couleur de la palette. Sur PC, on sait qu'on utilise couramment pour chaque pixel 3 composantes : rouge, vert, et bleu (eh, toi, au fond, qui a dit jaune au lieu de vert.... DEHORS !). Mais alors que sur PC on a 256 valeurs pour chaque composante (que nous nommeront R, V/G, B par souci d'économie de mon clavier

), sur DS on ne peut pas, on se retrouve avec des valeurs plus limitées :0-31 :s Ca explique pourquoi les jeux sur DS ont toujours des dégradés de couleurs moches

Alors que sur PC le nombre de valeurs est suffisant pour quand dans une composante donnée, l'oeil ne voit quasiment pas la différence entre 2 valeurs proches, sur DS on la voit énormément.
Bref, un pixel de 16bit (ou une entrée dans la palette, ça revient au même) est donc composé des valeurs R, G, B sur 5 bit chacune (0-31, car 2
5 = 32....). Ce qui nous fait... 3*5 = 15bit ! Aie ! Mais où est donc le bit manquant ?

En fait, sur DS, il est utilisé en mode buffer 16bit pour afficher/cacher le pixel, c'est le bit dit alpha

Si vous voulez être sûr de pas faire de connerie, mettez toujours le bit 15 à 1, mais bon, dans l'ensemble on s'en tape

16bit... Ca ressemble à quoi un nombre en 16bit en fait ?

Et bien, voilà :
1 00000 00000 00000, ou encore
1 11111 11111 11111. 16 valeurs de 0-1 ! Notez que par souci de lisibilité j'ai découpé en morceaux de 5bit... Le bit tout à gauche est le bit 15, tandis que le bit tout à droite est le 0... Je vous laisse compter pour le reste

Dans le premier nombre que j'ai donné, on voit que le bit 15 est à 1, et que tous les autres bits sont à 0... C'est donc R = 0, G = 0, B = 0... Aucune couleur = tout noir ! Et le suivant, tout est à 1, et on a donc les valeurs maximales : R = 31, G = 31, B = 31, soit BLANC !
Alors, les 5 bits de droite sont rouges, puis les 5 suivants verts, puis les 5 suivants bleus. C'est important à savoir pour la suite, car toute la manipulation des palettes repose sur cet état de fait. En fait, si on veut pouvoir par exemple diminuer la composante rouge d'une couleur, on va devoir décomposer la couleur en ses composantes, modifier la rouge, puis recomposer la couleur. Sinon, on risque de modifier sans faire exprès les autres couleurs

Ainsi, si on multiplie une couleur par 2, par exemple, on va tout multiplier par deux... Mais si on avait rouge = 31, on multiplie par deux, ça fait... 62, ce qui ne rentre pas ! Ca va donc 'déborder' de sa 'case', et ajouter 1 bit à la couleur vert, etc... Donc on peut pas faire ça, on doit extraire les composantes, multiplier le rouge par 2, limiter sa valeur à 31 si ça passe au-dessus, puis on recompose la couleur. Ca parait un poil complexe dit comme ça, mais en fait le code est super court et pas difficile à comprendre du tout.
Changement de baseBon, on va commencer par un code simple pour modifier du blanc et passer à un autre couleur

Ben oui, le blanc c'est toutes les couleurs à fond, donc il suffit de supprimer une composante pour obtenir un changement de couleur ! La démo qui suit possède un intérêt très limité, dans la mesure où elle ne permet que de changer une image dans les blancs/gris/noir en la même image en rouge, vert, bleu, cyan, jaune, ou magenta. Ni plus, ni moins :/
Pour comprendre comment cela marche, il faut revoir la disposition des couleurs :
1 11111 11111 11111... Ca, c'est du blanc, tous les bits sont allumés. Si, dans cette couleur, on voulait supprimer le rouge, il suffirait de mettre les 5 bits de droites à 0 :
1 11111 11111 00000. Là, on a supprimé tout le rouge ! Alors qu'avant on avait R = 31, G = 31, B = 31, on se retrouve avec R = 0, G = 31, B = 31... Quelle couleur est-ce ? CYAN ! Bravo

La question est alors, comment supprimer tout le rouge sans risquer de couper le reste

Et bien la réponse est simple : on va appliquer des filtres, comme dans la vraie vie

On peut appliquer sur une fenêtre un filtre rouge qui bloque la vert et le bleu, mais laisse passer le rouge, et là on va faire exactement pareil...
Pour se faire, on va avoir besoin d'une opération élémentaire sur les bits : '&'... L'opérateur 'ET' permet de faire exactement ce que l'on veut :
- 0 & 0 => 0
- 1 & 0 => 0
- 0 & 1 => 0
- 1 & 1 => 1
Voilà, la seule façon d'avoir un bit allumé, c'est que les 2 bits le soient... Ainsi, si on applique le même principe au rouge, on n'aurait de rouge dans une image que le pixe/la couleur contient du rouge ET si le filtre qu'on applique en contient aussi... Appliquons donc un 'filtre rouge' sur un pixel blanc...
1 11111 11111 111111 00000 00000 11111-------------------1 00000 00000 11111Et oui, il ne reste que le rouge ! Pour avoir un vrai bon filtre rouge, il faut donc que les 5 bits correspondant à rouge soient allumés, ce qui correspond à une valeur de... 31 !
Si on suit le même raisonnement, pour le vert, il faut avoir un filtre avec les 5 bits suivants :
1 00000 11111 00000. En fait, si on veut se simplifier la vie, on peut écrire ce nombre en valeurs décimales.... (31 << 5). 31, car tout le vert d'allumé, c'est VERT = 31, et '<< 5', qui signifie littéralement 'décaler les bits de 5 vers la gauche

On voit bien que si on prend 31 et qu'on décalle vers la gauche, ça fait :
0 00000 00000 11111 =>
0 00000 11111 00000Et de même, pour le bleu, qui est encore 5 bits plus loin, on fera (31 << 10)...
Bon, ça commence à faire beaucoup de théorie et toujours pas de code, ce qui n'est pas vraiment mon habitude

Mais maintenant qu'on a compris ça, je vais pouvoir montrer le code. Pour changer de couleur, comme on voit dans la démo, il suffit donc de copier la palette d'origine ailleurs, tout en appliquant le filtre, puis de la charger comme n'importe quelle palette... (évitez de modifier directement les données incluses dans une rom, sinon quand on fait reset dans le jeu on a perdu les données d'origine

)
On doit donc commencer par déclarer une nouvelle palette : u16 newpal[256]; // Nouvelle palette Il faut de préférence la déclarer hors
de la fonction (ajout de Djé, c'était ça ??)de toute façon, sinon on a des problèmes d'alignement qu'on ne veut pas avoir

Ensuite, on a la fonction pour copier et appliquer un filtre à la volée :
(c):
void CreatePal(u16 *pal, u16 color){
u16 i;
for(i = 0; i < 256; i++){
newpal[i] = pal[i]&color;
}
}
Tout simple ! On copie les 256 couleurs, en appliquant le filtre avant. *pal est un pointeur sur la palette, mais ne vous en faites pas, ce n'est rien

Et en pratique ?
(c):
CreatePal((u16*)test_Pal, RED);
PA_LoadBgPal(0, 3, (void*)newpal);
Alors, voilà, on voit que c'est tout con pour utiliser le pointeur, on lui donne la variable qu'il faut (la palette d'origine). Le (u16*) a été ajouté car la palette est déclarée en constante et que ça plaisait pas au compilo

Ensuite, il suffit de charger la palette avec vos fonctions habituelles...
Notez le petit 'RED'... C'est le nom que j'ai donné à mon filtre rouge

J'ai en fait déclaré 5 filtres au début :
(c):
#define RED 31
#define GREEN (31<<5)
#define BLUE (31<<10)
Rien de mystique, on a déjà vu à quoi correspondent ces chiffres barbares...
Bon, ceux qui suivent attentivement se rappelleront que j'avais parlé de faire 6 couleurs : CYAN, JAUNE, et MAGENTA en plus, qu'on ne retrouve pas ici

En fait, on va utiliser l'autre opération sur les bits, le 'OU'/'OR', pour combiner les filtres

(c):
CreatePal((u16*)test_Pal, RED|GREEN);
Si on mélange du rouge et du vert, on obtient quoi ? JAUNE !
Bon, je vous explique quand même le principe du OR

- 0 | 0 => 0
- 1 | 0 => 1
- 0 | 1 => 1
- 1 | 1 => 1
Voilà, il suffit qu'un des 2 bits soit allumé pour que le résultat soit 1

Si on applique ça avec nos filtres rouges et verts....
00000 00000 11111 (rouge)
00000 11111 00000 (vert)
----------------- => OPERATION OR !
00000 11111 11111 => nouveau filtre combiné.
Et si on applique ce filtre à une couleur blanche ?
00000 11111 1111111111 11111 11111----------------- => OPERATION ET/AND !
00000 11111 11111 => on a supprimé la composante bleue

On obtient donc du jaune

CQFD

On peut donc combiner les filtres comme on veut pour avoir les 6 couleurs 'de base'
Changement de couleur !Passons maintenant à un exemple plus complexe, mais probablement plus utile : faire changer une image de couleur, complètement
Sur cette vidéo, on voit passer du rouge au vert, du vert au bleu, etc... C'est donc différent de ce qu'on avait dans la partie précédente, qui consistait juste à changer le blanc/gris en couleur en supprimant les autres composantes. Là, on doit changer une couleur en une autre.
Bon, dans un souci de rapidité du code (qui est déjà assez lent), je ne fais pas de la vraie manipulation poussée avec des calculs hyper complexes

Le principe que j'ai choisi d'adopter, c'est de prendre une image avec une forte dominante rouge, et de me dire que de toute façon les composantes bleues et vertes sont en gros au même niveau, et forcément plus faible que le rouge

Bon, cette fois-ci on ne créé pas la palette de couleur à la volée, il faut en fait créer les 4 grosses palettes (rouge, bleu, jaune, et vert, dans cet exemple) au début...
De façon totalement arbitraire, j'ai défini comme ça :
(c):
#define RED 0
#define BLUE 1
#define YELLOW 2
#define GREEN 3
Puis, on a donc une fonction qui créé les palettes :
(c):
void InitPalettes(void){
s32 i;
u16 r, b;
for (i = 0; i < 256; i++) {
r = bug_Pal[i]&31;
b = (bug_Pal[i]>>10)&31;
shippal[RED][i] = PA_RGB(r, b, b);
shippal[BLUE][i] = PA_RGB(b, b, r);
shippal[YELLOW][i] = PA_RGB(r, r, b);
shippal[GREEN][i] = PA_RGB(b, r, b);
}
}
On scanne les 256 couleurs de la palette d'origine (bug_Pal), une par une... Et on extrait le rouge et bleu. Pourquoi le rouge et le bleu ? Le rouge, parce que c'est la couleur dominante de notre palette/image d'origine, le bleu parce que ça ne l'est pas

J'ai dit que par simplicité j'allais assimiler le bleu et le vert aux mêmes valeurs, donc voilà, j'ai pris le bleu de façon arbitraire, j'aurai tout aussi bien pu prendre le vert

Pour les extraire :
(c):
r = bug_Pal[i]&31;
b = (bug_Pal[i]>>10)&31;
Un code simple. Pour le rouge, on prend la couleur, on ne garde que les 5 premiers bits (valeur 31, donc). Pour le bleu, on décale vers la droite de 10 bits (ce qui ramène nos bits 10 à 14 du bleu aux bits 0 à 4, comme le rouge en fait), et on applique un filtre pour garder une valeur de 0 à 31 uniquement. On a donc R compris entre 0 et 31 pour le rouge, et B pareil, mais pour le bleu

Que du bon !
Ensuite, il faut recomposer dans les nouvelles palettes la 'bonne couleur'... Si on part du principe que pour une image uniformément rouge, la dominante est la couleur rouge, pour avoir une image bleue on gardera le bleu, etc...
(c):
shippal[RED][i] = PA_RGB(r, b, b);
shippal[BLUE][i] = PA_RGB(b, b, r);
shippal[YELLOW][i] = PA_RGB(r, r, b);
shippal[GREEN][i] = PA_RGB(b, r, b);
Ainsi, pour faire la palette rouge on garde le Rouge en rouge, et applique la composante bleu (secondaire, donc) au vert et au bleu (PA_RGB(r, g, b))
Pour faire le bleu, on applique à la composante bleue la valeur qu'on avait pour le rouge (qui est la valeur dominante), et pour les autres composantes on fout le bleu (valeur faible)
Pour jaune, on applique la valeur forte (r) au rouge et au vert, et enfin pour le vert on n'applique la valeur forte qu'au vert...
A partir de là, on a donc réussi à créer des belles palettes des couleurs que l'on veut... Sauf que dans mon exemple, on a des belles transitions, alors que là... non

Il faut donc un code pour passer en douceur d'une palette à une autre.... Quand on passe de rouge à vert, on veut que le rouge diminue progressivement, pendant que le vert augmente, etc...
Le code est là un peu plus complexe :
(c):
// Extraction des composantes d'une couleur, qu'on place dans R, G, B
#define GET_RGB(truecolor, r, g, b) r = truecolor&31; g = (truecolor>>5)&31; b = (truecolor>>10)&31;
extern inline u8 UPDATE_COLOR(u8 level, u8 col1, u8 col2) {
return (col1*(32-level) + (col2*level))/32; // Adapter la couleur
}
void ChangeColor(u8 oldcolor, u8 color, u8 level){
s32 i, r, g, b, r1, g1, b1, r2, g2, b2;
for (i = 0; i < 256; i++) { // Pour chaque couleur de la palette
GET_RGB(shippal[oldcolor][i], r1, g1, b1); // Extraire les valeurs R, G, B de la couleur d'origine
GET_RGB(shippal[color][i], r2, g2, b2); // Extraire les valeurs R, G, B de la couleur de destination
// Mettre à jour la couleur intermédiaire
r = UPDATE_COLOR(level, r1, r2);
g = UPDATE_COLOR(level, g1, g2);
b = UPDATE_COLOR(level, b1, b2);
// Nouvelle couleur !
palette[i] = PA_RGB(r, g, b);
}
PA_LoadBgPal(0, 3, palette); // Charger la nouvelle couleur
}
On va faire comme avant, couleur par couleur, en plusieurs étapes...
Tout d'abord, pour changer progressivement, il suffit d'utiliser ChangeColor(u8 oldcolor, u8 color, u8 level). Cette fonction prend le numéro de la palette d'origine, puis celui de la palette finale, et un 'niveau'. Ce niveau varie de 0 à 32, 0 étant 100% palette d'origine, et 32 100% palette d'arrivée... On peut donc ainsi faire une simple boucle for pour aller de 0 à 32 et faire varier progressivement

Je ne détaillerai pas cette partie du code, j'ai choisi de ne pas faire une boucle for mais un truc plus compliqué qui permet de ne pas bloquer la boucle principale pendant la transition

Ensuite, on va voir le principe sous-jacent à cette fonction ChangeColor... C'est en fait relativement simple : on prend la couleur de départ, on la décompose en ses valeurs R, G, et B... On prend la couleur d'arrivée, on fait pareil. Ensuite, on va devoir obtenir les valeurs R, G, et B intermédiaires entre ses 2 valeurs selon le niveau de transition... Pour les composantes d'origine (R1, G1, B1), on va multiplier par '32 - niveau'... Pour les composantes d'arrivée (R2, G2, B2), on va multiplier par 'niveau'. Ainsi, si on ajoute le tout, on obtient un total de 32-niveau+niveau => 32 fois les valeurs normales. Il suffit de diviser par 32 pour obtenir la bonne composante intermédiaire... Et ça marche :
Prenons avec une valeur de 0 pour le niveau :
R1 => R1*(32-0) = R1*32
R2 => R2*0 = 0
(R1+R2)/32 => R1*32/32 = R1 ! Donc pour un niveau de 0, on retrouve la valeur de base
Ensuite, pour une valeur de 32... (finale)
R1 => R1*(32-32) = R1*0 = 0
R2 => R2*32
(R1+R2)/32 => (0+R2*32)/32 = R2 ! C'est bien la valeur d'arriver.
Et pour une valeur intermédiaire, au hasard... 16

R1 => R1*(32-16) = R1*16
R2 => R2*16
(R1+R2)/32 => (R1*16+R2*16)/32 = (R1+R2)/2 ! C'est la moyenne des 2 composantes

Voilà, ça c'était pour illustrer le principe

Une fois ceci fait, il ne reste plus qu'à charger la palette et tout est bon

Cette démo se passe donc en 2 étapes :
1. On génère une palette par couleur d'arrivée possible
2. On génère des palettes intermédiaires à la volée, entre la palette de départ et la palette d'arrivée, et ce sont elles que l'on charge
Ajuster le gammaOn va maintenant voir une nouvelle démo pour changer la luminosité/gamma d'une palette

Pour obtenir cet effet, il y a en fait 2 façon de faire. Une que je n'aime pas trop : pour aller vers le blanc, il suffit d'ajouter 1 à chaque composante X fois... Je n'aime pas trop car si on a une ou deux couleurs dominantes, elles arrivent à 31 largement avant la couleur non dominante, qui va mettre plus de temps à 'monter'... Du coup, j'ai réutilisé le code de la démo précédente en le simplifiant : on va faire comme la transition vraiment progressive/proportionnelle vers une couleur, sauf que là on va faire vers 31, 31, 31 : Blanc ! (ou 0, 0, 0 pour noir

)
Cette fois-ci, pas besoin de générer de palette avant, tout sera fait à la volée

On va prendre une variable gamma, qu'on met à 0. Si gamma passe à 32, se sera 100% blanc, si on passe à -32, 100% noir, etc...

Je vous poste le code rapidement, c'est vraiment la même chose

(c):
void GeneratePalette(s8 gamma){
s32 i, r, g, b;
u8 color = 31; // Composante allumée/Blanc par défaut
if(gamma < 0) {
gamma = -gamma; // Positiver la valeur
color = 0;
}
for (i = 0; i < 256; i++) { // Pour chaque couleur de la palette
GET_RGB(bug_Pal[i], r, g, b); // Extraire les valeurs R, G, B de la couleur d'origine
// Mettre à jour la couleur intermédiaire
r = UPDATE_COLOR(gamma, r, color);
g = UPDATE_COLOR(gamma, g, color);
b = UPDATE_COLOR(gamma, b, color);
// Nouvelle couleur !
newpal[i] = PA_RGB(r, g, b);
}
PA_LoadBgPal(0, 3, newpal); // Charger la nouvelle couleur
}
Voilà, au lieu de prendre la palette d'arrivée et d'en extraire les composantes, on fixe les composantes à 'color' (31 pour blanc, 0 pour noir, sachant qu'on met 0 si le gamma est négatif

).
Je pense que c'est assez simple pour être compris avec les explications de la démo précédente, alors je m'arrête là pour cette semaine et vous souhaite une bonne lecture d'Harry Potter
