2ème partie: allouer des couleurs
Un article de GuruMed.
Allouer une palette de couleurs
par StAn.
Nous avons vu dans le premier article de cette série comment ouvrir une fenêtre. Vous avez peut-être essayé d'utiliser quelques fonctions de la graphics.library pour écrire dedans... et remarqué que SetAPen() ne permet que de choisir un "pinceau" (pen) parmis la palette actuelle du WB. C'est très limitatif, puisqu'on ne maitrise absolument pas cette palette ! Au mieux, on peut tabler sur du gris dans le pinceau 0, du noir en 1 et du blanc en 2...
Heureusement, l'OS 3.0 a apporté des fonctions supplémentaire pour modifier proprement la palette de tout écran public tel que le WB.
Allouer un pinceau de couleur
La fonction ObtainBestPen() de la graphics.library permet d'obtenir un pinceau, c'est-à-dire un emplacement dans la palette, de la couleur que l'on désire.
D'après les AutoDocs, ObtainBestPen() s'utilise de cette façon:
color = ObtainBestPen(cm,r,g,b,tags....);
Avec:
- color : numéro de pinceau (entre 0 et 255) ou -1 si échec.
- cm : pointeur sur une struct ColorMap
- r : quantité de rouge (fraction 32 bit justifiée à gauche)
- g : quantité de vert (fraction 32 bit justifiée à gauche)
- b : quantité de bleu (fraction 32 bit justifiée à gauche)
- tags : tags spécifiant les réglages de la sélection du pinceau.
Le principe d'ObtainBestPen() est le suivant: si un pinceau de la palette actuelle contient déjà la couleur demandée (couleur plus ou moins approximative selon les réglages passés dans les tags), alors ce pinceau sera fourni en valeur de retour. Sinon, s'il reste de la place dans la palette, un nouveau pinceau de la couleur demandée sera créé. S'il ne reste pas de place, le pinceau de la couleur la plus approchante sera choisi.
Procédons par ordre. Il nous faut d'abord une structure ColorMap. Par chance, toute structure ViewPort contient justement un pointeur vers une ColorMap. Par miracle, tout écran intuition contient une structure ViewPort ! Et comme on a vraiment beaucoup de veine, chaque fenêtre est également associée à un écran intuition :-). Nous avons donc tout ce qu'il faut sous la main.
Dans intuition/intuition.h, struct Window, on peut lire:
struct Screen *WScreen; /* this Window's Screen */
Dans intuition/screens.h, struct Screen:
struct ViewPort ViewPort; /* describing the Screen's display */
Et enfin dans graphics/view.h, struct ViewPort:
struct ColorMap *ColorMap; /* table of colors for this viewport */
Ceci nous permet d'écrire les lignes suivantes:
struct ColorMap *cm; cm = win->WScreen->ViewPort.ColorMap;
Ensuite, les valeurs de rouge, vert et bleu. Elles sont encodées chacune sur 32 bits, ce qui nous laisse une certaine marge puisqu'actuellement aucune carte graphique (grand public) ne gère plus de 8 bits par composante à ma connaissance.
Si vous voulez obtenir une couleur quelconque R,G,B (avec R, G et B compris chacun entre 0 et 255 inclus), voici comment calculer les valeurs de r, g et b à passer à ObtainBestPen():
ULONG r,g,b; r = ( R<<24 ) | ( R<<16 ) | ( R<<8 ) | R; g = ( G<<24 ) | ( G<<16 ) | ( G<<8 ) | G; b = ( B<<24 ) | ( B<<16 ) | ( B<<8 ) | B;
En dernier viennent les tags:
OBP_Precision détermine la précision souhaitée de la correspondance entre la couleur demandée et la couleur obtenue. Les valeurs possibles sont PRECISION_GUI, PRECISION_ICON, PRECISION_IMAGE et PRECISION_EXACT. PRECISION_GUI sert pour les dessins dont la couleur exacte n'a que peu d'importance, tandis que PRECISION_EXACT permet d'obtenir la couleur la plus proche que possible de la couleur demandée. La valeur par défaut est PRECISION_IMAGE.
OBP_FailIfBad, s'il est positionné sur TRUE, demande à ce qu'ObtainBestPen() échoue s'il est impossible d'allouer une couleur dans la précision souhaitée, au lieu de retourner la couleur la plus proche. Vous n'en aurez probablement pas besoin.
Ces tags sont donc optionnels, les valeurs par défaut convenant à la plupart des usages.
Tout est maintenant prêt pour tracer une zolie ligne rouge dans notre fenêtre. Reprenons le source du premier article en y intégrant le code nécessaire:
#include <intuition/intuition.h>
#include <proto/exec.h>
#include <proto/graphics.h> // pour ObtainBestPen(), ReleasePen(),
// SetAPen(), Move() et Draw()
#include <proto/intuition.h>
#include <stdio.h>
struct GfxBase *GfxBase;
struct IntuitionBase *IntuitionBase;
int main(void)
{
struct Window *win=NULL;
LONG redPen;
struct ColorMap *cm;
ULONG r,g,b;
IntuitionBase = (struct IntuitionBase *) OpenLibrary( "intuition.library", 36 );
if( !IntuitionBase ) {
puts( "Impossible d'ouvrir intuition.library v36." );
return 20;
}
GfxBase = (struct GfxBase *) OpenLibrary( "graphics.library", 39 );
if( !GfxBase ) {
puts( "Impossible d'ouvrir graphics.library v39." );
CloseLibrary( (struct Library *) IntuitionBase );
return 20;
}
win = OpenWindowTags( NULL,
WA_Title, (ULONG)"Pouet",
WA_InnerWidth, 320,
WA_InnerHeight, 240,
WA_DragBar, TRUE,
WA_CloseGadget, TRUE,
WA_DepthGadget, TRUE,
WA_IDCMP, IDCMP_CLOSEWINDOW,
TAG_END );
if( !win ) {
puts( "Impossible d'ouvrir la fenêtre." );
CloseLibrary( (struct Library *) IntuitionBase );
CloseLibrary( (struct Library *) GfxBase );
return 10;
}
cm = win->WScreen->ViewPort.ColorMap;
r = 255<<24 | 255<<16 | 255<<8 | 255;
g = 0<<24 | 0<<16 | 0<<8 | 0;
b = 0<<24 | 0<<16 | 0<<8 | 0;
redPen = ObtainBestPen( cm, r, g, b, TAG_END );
if( redPen == -1 ) {
puts( "Impossible d'allouer un pinceau !" );
CloseWindow( win );
CloseLibrary( (struct Library *) IntuitionBase );
CloseLibrary( (struct Library *) GfxBase );
return 10;
}
SetAPen( win->RPort, redPen );
Move( win->RPort, 0, 239 );
Draw( win->RPort, 319, 0 );
WaitPort( win->UserPort );
ReleasePen( cm, redPen );
CloseWindow( win );
CloseLibrary( (struct Library *) IntuitionBase );
CloseLibrary( (struct Library *) GfxBase );
return 0;
}
Je ne vais pas rentrer dans les détails en ce qui concerne l'ouverture et la fermeture de la graphics.library, ainsi que la libération des ressources en cas d'échec, puisque le principe est exactement le même que dans le source précédent.
Ceci nous amène directement à la ligne:
redPen = ObtainBestPen( cm, r, g, b, TAG_END );
Rien de spécial puisque tout a déjà été expliqué plus haut. La seule chose à noter est qu'aucun tag n'a été spécifié: seul TAG_END a donc été écrit à l'emplacement de la liste des tags.
Ensuite, on teste si l'allocation a réussi. Normalement, il devrait être impossible qu'elle échoue sur l'écran du WB, mais on ne sait jamais.
SetAPen( win->RPort, redPen );
Allouer un pinceau, c'est bien, mais il faut encore le sélectionner pour tracer avec ! C'est à ça que sert SetAPen(). On lui donne le RastPort de la fenêtre en premier argument, et le pinceau obtenu avec ObtainBestPen() en second. Par la suite, toutes les opérations de tracé (basiques) utiliseront cette couleur.
Move( win->RPort, 0, 239 );
Move() déplace le "curseur" de tracé vers la position spécifiée en 2ème et 3ème arguments. Les opérations de tracé suivantes commenceront donc en (0,239) (sachant que (0,0) est l'angle supérieur gauche de la fenêtre).
Draw( win->RPort, 319, 0 );
Draw() trace une ligne allant de la position du curseur jusqu'à celle passée en arguments. Ici, on trace donc une ligne rouge allant de (0,239) à (319,0).
ReleasePen( cm, redPen );
Une fois qu'on a fini d'utiliser le pinceau, il faut le libérer. C'est ce à quoi sert ReleasePen().
Voilà. Si vous essayez ce programme, vous vous apercevrez que le segment est tracé en partie sur la bordure de la fenêtre, et non pas bien comme il faut dans la zone à l'intérieur de la fenêtre. La raison est tout simplement que les coordonnées que l'on donne à Move(), Draw() etc. sont relatives au bord supérieur gauche de la fenêtre, bordures incluses !
Il existe deux solutions pour remédier à cela:
- Rajouter la taille des bordures de la fenêtre à toutes les coordonnées
La structure Window renferme quatre champs contenant la taille des bordures:
BYTE BorderLeft, BorderTop, BorderRight, BorderBottom;
Il suffit donc de rajouter BorderLeft à toutes les coordonnées horizontales et BorderTop à toutes les coordonnées verticales:
Move( win->RPort, win->BorderLeft+0, win->BorderTop+239 );
Draw( win->RPort, win->BorderLeft+319, win->BorderTop+0 );
- Utiliser le tag WA_GimmeZeroZero à l'ouverture de la fenêtre.
Utiliser cette option signifie que l'on souhaite que les coordonnées soient relatives à l'intérieur de la fenêtre. De plus, les opérations de tracé seront clippées (c'est-à-dire limitées, coupées) sur les bordures de la fenêtre; il est donc impossible d'écrire sur la bordure d'une fenêtre GimmeZeroZero.
C'est en quelque sorte la solution de facilité, mais il semblerait que les performances soient moindres lorsqu'on utilise des fenêtres de ce type. En tout cas, c'est ce qu'on m'a dit :-) (NDHenes: oui, utiliser GimmeZeroZero, c'est mal, les députés prevoient une loi l'interdisant). De plus, cela consomme plus de mémoire (comme expliqué dans le fichier d'includes intuition/intuition.h juste avant les champs BorderLeft, BorderTop, etc.).
Vous connaissez maintenant l'essentiel pour dessiner en couleur dans une fenêtre du WB. La prochaine fois, nous verrons le tracé de graphiques au format chunky.
