3ème partie: afficher une image chunky

Un article de GuruMed.

par StAn.

Nous avons vu la dernière fois comment allouer un pinceau, afin de tracer une ligne (ou autre chose) de la couleur souhaitée.

Tracer des lignes et afficher du texte, c'est bien beau, mais ça reste assez limité. Comment faire pour afficher une image en utilisant un algorithme qui fonctionne aussi bien sous AGA que sous CGX et P96 ?

Tout d'abord, l'image doit faire au maximum 256 couleurs pour pouvoir être affichée en AGA sur le WB. Nous allons donc nous limiter à des images de 256 couleurs maximum.

Les écrans de 256 couleurs ou moins étant forcément basés sur une palette (chaque pixel contient le numéro d'un pinceau dans la palette et non pas directement la couleur RVB), il nous faut un format d'image facilement "remappable" dans la palette de l'écran. Le format Chunky est idéal pour cela: chaque pixel tient dans un octet ayant pour valeur le numéro de pinceau dans la palette de l'image. De plus, la graphics.library de l'OS contient des fonctions servant à tracer des images à ce format.


Sommaire

Convertir une image en chunky

Vous pourriez croire le contraire, mais il n'existe à ma connaissance aucun outil permettant de convertir facilement une image en un fichier contenant des données au format chunky directement utilisables par un programme C. Il existe différents outils faisant plus ou moins ce que l'on désire, mais, malheureusement, soit il le font plutôt moins que plus, soit ils sont buggés.

En définitive, j'ai choisi Personal Paint (PPaint.lha, readme) et ArtPro (artpro1.20b.lzx sur le site de l'auteur, AmigaArenaArtPRO.lzx, ou sur Aminet: ArtPRO1.20.lha, readme).

Personal Paint n'est pas indispensable, mais il est très utile pour réduire le nombre de couleurs d'une image en voyant en temps réel le résultat, et ceci vers un nombre quelconque de couleurs, pas seulement une puissance de 2. Quand à ArtPro, il permet à priori de faire tout ce dont on a besoin, y compris la conversion 24 bits vers un nombre quelconque de couleurs (sans preview), mais il n'est malheureusement pas exempt de bugs.

GfxMaster (GFXMasterV1.10.lha, readme) permet également de sauver des images chunky, mais il est moins puissant. A essayer en cas de problèmes avec ArtPro, mais vous aurez alors probablement besoin d'un outil pour convertir un fichier binaire en source C, ou d'un assembleur pour créer un objet linkable.

Réduction du nombre de couleurs

Chargez votre image dans Personal Paint, puis utilisez le menu "Couleurs->Moins de couleurs..." pour réduire le nombre de couleurs. Il vaut mieux diminuer le nombre de couleurs au maximum acceptable à ce moment, car le WB ne dispose que de 256 pinceaux au total; donc si votre image utilise 256 couleurs différentes, il est très peu probable que toutes ses couleurs soient les bonnes une fois affichée sur le WB.

Si vous utilisez un programme de dessin pour réduire le nombre de couleurs, le résultat sera bien meilleur que si vous laissez ObtainBestPen() faire tout le travail.

Si votre image utilise un nombre de couleurs différent d'une puissance de 2, faites bien attention à ce que toutes les couleurs inutilisées de la palette soient les même qu'une couleur utilisée... Sinon, des pinceaux seront alloués inutilement ! Si nécessaire, recopiez la couleur 0 dans toutes les couleurs non utilisées de la palette. Une autre solution est de noter le nombre de couleurs utilisées et de s'en servir par la suite pour n'allouer que le nombre de couleurs nécessaire (explications ci-dessous).

Conversion en chunky

Lancez ArtPro, et vérifiez dans les settings que le screenmode et les autres options sont correctes; ça peut toujours servir. Chargez votre image en cliquant sur Load. En cas de problème, choisissez un loader approprié en cliquant sur 1.

Si votre image n'a pas été réduite en 256 couleurs ou moins, cliquez sur 3 et choisissez un mode 8 bits ou inférieur. Cliquez ensuite sur Render Ctrl, réglez Mode sur "Palette", puis choisissez le nombre de couleurs. Si vous voulez un nombre de couleurs différent d'une puissance de 2, cochez Custom Palette et entrez le nombre désiré dans Colors Used. Ensuite, cliquez sur Render (cliquez pour faire disparaitre l'image).

Maintenant, cliquez sur 2 pour choisir le format de sauvegarde. Choisissez "CHUNKY". Cliquez sur Config. Sélectionnez:

  • Save Format:
Output: Source
Type: Byte
  • Source Format:
Language: C
Width: Bytes


Cliquez sur Ok, puis Accept and Operate et sauvez ça dans un fichier "image.h".

Editez ce fichier pour changer le nom du tableau généré. Appelez-le par exemple "image" (au lieu de "data").

Il faut aussi sauver la palette. Cliquez à nouveau sur 2, choisissez "PALETTE" et cliquez sur Config. Sélectionnez:

  • Save Format:
Output: Source
Depth: 8 Bit
Type: LoadRGB
  • Source Format:
Language: C
Width: Longs


Cliquez sur Ok, puis Accept and Operate et sauvez ça dans un fichier "palette.h".

Cette opération crée un fichier contenant un tableau d'ULONGs décrivant la palette, au format LoadRGB32. Le premier ULONG contient dans ses 16 bits de poids fort le nombre de couleurs de la palette, et dans ses 16 bits de poids faible le numéro du premier pinceau à utiliser (vous pouvez l'ignorer pour cette fois). Ensuite suivent les couleurs proprement dites, à raison de 3 ULONGS par couleur (Rouge, Vert, Bleu). Enfin, le dernier ULONG vaut toujours zéro. Vous remarquerez au passage qu'ArtPro ne calcule pas les valeur des couleurs comme je l'ai expliqué dans le précédent article, mais se contente de rajouter des 0 dans les 24 bits de poids faible... Ce qui n'est certe pas gênant actuellement, vu que seuls les 8 premiers bits sont pris en compte dans le meilleur des cas.

Si vous voulez une palette avec un nombre de couleurs différent d'une puissance de 2, éditez le fichier "palette.h". Remplacez les 16 bits de poids fort de la première valeur du tableau par le nombre de couleurs effectif. Par exemple, si votre palette ne fait que 200 couleurs (0xc8) au lieu de 256 (0x100), remplacez 0x01000000 par 0x00c80000. Ensuite, supprimez toutes les couleurs en trop à la fin du tableau.

Changez le nom du tableau de "data" en "palette"; et puis rajoutez un retour à la ligne à la fin des deux fichiers, car ça peut poser des problèmes...


Afficher l'image...

Trois choses à faire:

  • allouer les pinceaux nécessaire
  • remapper l'image chunky avec les numéros des pinceaux obtenus
  • tracer l'image chunky


Allouer la palette

Nous allons simplement balayer le tableau "palette" et allouer chaque pinceau comme expliqué dans le précédent article. Les numéros des pinceaux obtenus seront stockés dans un tableau afin de pouvoir remapper l'image chunky, puis libérer les pinceaux à la fin du programme.

Le premier UWORD du tableau est le nombre de couleurs de la palette. Le second peut ici être ignoré et devrait valoir 0.


	
LONG tmpPen;
WORD pen;
UBYTE pens[256];

for( pen=0; pen<(palette[0]>>16); pen++ ) {
   tmpPen = ObtainBestPen( cm,
               palette[pen*3+1],
               palette[pen*3+2],
               palette[pen*3+3],
               OBP_Precision, PRECISION_ICON, TAG_END );
   if( tmpPen==-1 ) { puts( "Impossible d'allouer les pinceaux." ); break; }
   else pens[i]=tmpPen;
}

Pour varier un peu, j'ai rajouté cette fois ci un tag (OBP_Precision) permettant de choisir la précision de la couleur obtenue. PRECISION_ICON signifie que l'on n'y accorde pas beaucoup d'importance... Mais ça ne veut pas dire que les couleurs seront forcément mauvaises :-) .

Remapper l'image

Maintenant, afficher l'image telle-quelle ne marcherait pas: elle n'aurait pas les bonnes couleurs. En effet, si dans notre palette la couleur 0 est du noir, ce n'est le cas de la palette du WB qui contient généralement du gris en position 0. Le pinceau qu'on a obtenu en faisant un ObtainBestPen() sur cette couleur ne vaut donc certainement pas 0. Il faut donc changer tous les 0 de l'image chunky en la valeur obtenue par ObtainBestPen() sur la couleur 0 de notre palette... et faire de même pour toutes les autres couleurs.


	
ULONG pixel;

for( pixel=0; pixel<IMAGE_WIDTH*IMAGE_HEIGHT; pixel++ ) {
   image[pixel] = pens[image[pixel]];
}

Vous le voyez, c'est une opération extrèmement simple. Je suppose ici que les constantes IMAGE_WIDTH et IMAGE_HEIGHT ont été définies et valent respectivement la largeur et la hauteur de l'image à afficher.

Tracer l'image

Enfin, il ne reste plus qu'à tracer l'image chunky obtenue. Il existe deux fonctions de la graphics.library permettant de faire cela: WritePixelArray8() et WriteChunkyPixels(). La première est disponible sous OS 3.0 mais nécessite l'allocation d'un RastPort temporaire et ne fonctionne qu'avec une image multiple de 16 pixels en largeur, tandis que la seconde n'existe que depuis l'OS 3.1 et est plus simple d'utilisation. Une autre chose à noter en ce qui concerne WritePixelArray8() est que cette fonction efface l'image chunky ! (Note: si j'en crois le readme de NewWPA8, ce bug affecte aussi WriteChunkyPixels(); mais ceci ne concerne pas les utilisateurs de cartes graphiques.)

Nous utiliserons ici WriteChunkyPixels().

	
WriteChunkyPixels( win->RPort,        // RastPort sur lequel tracer
   win->BorderLeft, win->BorderTop,   // Position de l'angle supérieur gauche dans le RPort
   win->BorderLeft+IMAGE_WIDTH-1, win->BorderTop+IMAGE_HEIGHT-1,  // Angle inférieur droit
   image,                             // Données chunky à tracer
   IMAGE_WIDTH );                     // Nombre d'octets par ligne de l'image

Voyez l'autodoc de WriteChunkyPixels() pour les détails.


Voici le programme final, reprenant en partie la base mise en place dans les articles précédents. N'oubliez pas d'adapter IMAGE_WIDTH et IMAGE_HEIGHT à la taille de votre image.

	
#include <intuition/intuition.h>
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <stdio.h>

#define IMAGE_WIDTH 320
#define IMAGE_HEIGHT 240

#include "image.h"
#include "palette.h"

int main(void)
{
   struct Window *win=NULL;
   struct ColorMap *cm;
   LONG tmpPen,pixel;
   WORD pen;
   UBYTE pens[256];

   // Ouverture de la fenêtre
   win = OpenWindowTags( NULL,
            WA_Title,       (ULONG)"Pouet",
            WA_InnerWidth,  IMAGE_WIDTH,
            WA_InnerHeight, IMAGE_HEIGHT,
            WA_DragBar,     TRUE,
            WA_CloseGadget, TRUE,
            WA_DepthGadget, TRUE,
            WA_IDCMP,       IDCMP_CLOSEWINDOW,
            TAG_END );

   if( !win ) {
      puts( "Impossible d'ouvrir la fenêtre." );
      return 10;
   }

   // Allocation des couleurs
   cm = win->WScreen->ViewPort.ColorMap;
   for( pen=0; pen<(palette[0]>>16); pen++ ) {
      tmpPen = ObtainBestPen( cm,
                  palette[pen*3+1],
                  palette[pen*3+2],
                  palette[pen*3+3],
                  OBP_Precision, PRECISION_ICON, TAG_END );
      if( tmpPen==-1 ) break;
      else pens[pen]=tmpPen;
   }

   if( tmpPen==-1 ) {
      puts( "Impossible d'allouer un pinceau !" );
      CloseWindow( win );
      return 10;
      // Note: en l'absence du tag OBP_FailIfBad, s'il est impossible
      // d'allouer un pinceau, alors il est impossible d'allouer TOUS les
      // pinceaux. Il est donc inutile de les libérer avant de quitter...
   }

   // Remappage des pixels
   for( pixel=0; pixel<IMAGE_WIDTH*IMAGE_HEIGHT; pixel++ ) {
      image[pixel] = pens[image[pixel]];
   }

   // Tracé de l'image
   WriteChunkyPixels( win->RPort,        // RastPort sur lequel tracer
      win->BorderLeft, win->BorderTop,   // Position de l'angle supérieur gauche dans le RPort
      win->BorderLeft+IMAGE_WIDTH-1, win->BorderTop+IMAGE_HEIGHT-1,  // Angle inférieur droit
      image,                             // Données chunky à tracer
      IMAGE_WIDTH );                     // Nombre d'octets par ligne de l'image

   WaitPort( win->UserPort );

   CloseWindow( win );

   // Libération des pinceaux
   for( pen=0; pen<(palette[0]>>16); pen++ ) {
      ReleasePen( cm, pens[pen] );
   }

   return 0;
}

Vous pouvez voir que, dans un souci de simplification, il n'y a plus d'ouverture de bibliothèques. En effet, la plupart des compilateurs disposent d'un mécanisme d'ouverture automatique des bibliothèques système telles que intuition.library et graphics.library (avec VBCC, il suffit de rajouter -lauto sur la ligne de commande). L'inconvénient de cette méthode est qu'on a moins de contrôle sur les versions des bibliothèques et qu'on ne peut pas afficher de message d'erreur adéquat en cas d'échec. Mais afin de ne pas surcharger les programmes d'exemple, j'ai décidé de laisser faire le compilateur; vous pouvez toujours remettre les ouvertures et fermetures de bibliothèques si nécessaire.

Une précision importante au sujet de la libération des pinceaux que j'ai oublié de mentionner dans le précédent article: si vous libérez un pinceau alors que votre image est toujours affichée, ses couleurs risquent de changer si un autre programme alloue lui aussi des pinceaux à ce moment là. Donc il ne faut libérer les pinceaux qu'une fois que l'image n'est plus visible ! A noter cependant que ce problème ne se pose pas sur les écrans hi/true color, puisqu'ils ne sont pas réellement palettisés...

Enfin, si votre application utilise beaucoup WriteChunkyPixels(), recommendez à vos utilisateurs sous AGA l'installation d'un patch tel que NewWPA8 (NewWPA8.lha, readme) ou BlazeWCP (Blazewcp.lha, readme) qui accélère énormément les fonctions de tracé chunky. De plus, il devrait corriger le bug de WritePixelArray8() faisant que l'image est effacée lors du tracé.