Notes techniques sur les datatypes
Un article de GuruMed.
Sommaire |
Comprendre les datatypes et leur utilisation
Par Corto
L'étude des formats de fichier et des modes de compression m'avaient tenu écarté des datatypes mais le génie de ces plugins du système d'exploitation m'a ramené à la raison, me contraignant à m'y pencher de plus près. Les datatypes apportent une grande souplesse et laissent transparaître une étonnante facilité d'utilisation. C'est bien le cas, mais il en va autrement lorsque l'on arpente la face développeur ... La documentation est à l'unanimité jugée très pauvre et les datatypes ont suivi des évolutions parallèles et pas forcément officielles. Des recherches (principalement dans les archives des newsgroups sur Google) m'ont permis de compiler bon nombre d'observations que je vous présente ici.
Principe
Les datatypes se présentent comme une hiérarchie de classes orientées objet, de type BOOPSI (je compte sur Sara pour nous éclairer la-dessus ;) Ainsi, pour manipuler un son, on accède à une unique interface (la classe mère) quel que soit le format d'origine : pour ce dernier nous avons une classe fille qui est sollicité pour chaque format.
Ben Hutching dans un post de novembre 1999 se livre à cette présentation des datatypes. Ils sont implémentés par une combinaison de deux choses :
- Un (ou plusieurs) fichier descripteur de datatype. Ils décrivent comment reconnaître les types de fichiers et quel code doit lui être appliqué. AddDatatypes tente de lire ces informations pour maintenir une liste en mémoire grâce à la datatypes.library. Ces descripteurs sont normalement installés dans DEVS:Datatypes donc ils seront lus par AddDataTypes au démarrage.
- Une bibliothèque implémentant une classe BOOPSI qui hérite de datatypesclass, souvent indirectement. Par exemple, le datatype gérant le JPEG implémente une sous-classe de picture.datatype, à savoir jpeg.datatype.
Considérations techniques
Le chargement d'un objet (nous travaillerons ici sur les images) doit respecter une certaine marche à suivre. Nous décrirons donc les différentes étapes nécessaires pour afficher une fichier image.
Allouer un nouvel objet avec NewDTObject
L'appel à cette fonction constitue la base même de l'utilisation des datatypes. On a à nouveau recourt aux tags que l'on passe en paramètre. La plupart du temps, l'image à afficher devra subir des transformations pour pouvoir s'adapter aux paramètres de l'écran. Ainsi, soit on passe les informations indiquant comment doit être transformée l'image, soit on s'en passe et le bitmap chargé par défaut devra être ajusté par la suite. Quoiqu'il en soit, NewDTObject donne vis à une entité datatype que l'on peut ensuite façonner.
Les tags permettent d'indiquer des directives au datatype mais aussi de récupérer par le biais de pointeurs des informations (nous verrons jsute après un autre moyen). Parmi les tags, citons les plus importants avec leur signification :
- DTA_GroupID, GID_PICTURE : nous voulons traiter un fichier de nature "image"
- GA_Width : largeur du rastport
- GA_Height : hauteur du rastport
- PDTA_ModeID : modeID de l'écran dans lequel il faudra rendre l'image
- PDTA_Screen : pointeur sur l'écran destiné à recevoir l'image. Dans ce cas-là, les couleurs de l'écran sont utilisées comme couleurs de référence pour le remappage.
- PDTA_FreeSourceBitMap, TRUE
- PDTA_DestMode, MODE_V43 :
- PDTA_UseFriendBitMap, TRUE
- PDTA_NumColors,
- PDTA_NumSparse : nombre de stylos pour l'image (fixé par l'appli)
- PDTA_SparseTable, (UBYTE *) : tableau de pens
- PDTA_DestBitMap : NewDTObject ne fournit pas de BitMap, celui-ci n'est accessible qu'après appel à la méthode DTM_PROCLAYOUT. Voici une des erreurs les plus courantes.
- OBP_Precision, PRECISION_EXACT
- PDTA_Remap : il doit être renseigné (que ce soit à TRUE ou FALSE suivant ce qui est souhaité), sinon ça peut poser des problèmes pour obtenir le bitmap.
L'accesseur GetDTAttrs
Il permet de récupérer les infos de l'image contenues dans la structure associée BitMapHeader, ainsi que les registres de couleurs de l'image d'origine. Si nous souhaitons afficher une image sur un écran propriétaire, nous ne pouvons ouvrir ce dernier dans les bonnes dimensions qu'après avoir obyenu les informations sur l'image. Cette fonction GetDTAttrs nous donne ces informations, comme la palette de couleurs et les dimensions de l'objet.
DoMethod DTM_FRAMEBOX : FrameBox
L'organisation objet des datatypes autorise l'appel de méthodes sur notre instance de datatype. La solution retenue, comme dans MUI, s'appelle DoMethod à laquelle on indique la méthode (hum ...) à exécuter.
DoMethod DTM_PROCLAYOUT
NewDTObject avait préparé notre image en reconnaissant le format, en invocant la bonne classe fille et en stockant les propriétés de l'image. Et bien l'opération PROCLAYOUT révèle tout cela en réajustant l'image aux conditions spécifiées et en récupérant l'image remappée et sa palette. Pour rappel, si le tag PDTA_Screen est passé à NewDTObject c'est la palette de cet écran qui devient référente. D'autre part, c'est la valeur du tag booléen PDTA_Remap qui commande ou non le remappage.
L'appel à cette méthode est inévitable ! Olaf Barthel confirme que le picture.datatype Picasso fonctionne conformément au datatype d'origine. En revanche, il semble que le datatype CGFX permet d'accéder au BitMap sans utiliser DTM_PROCLAYOUT. Sachez qu'avec les datatypes on ne peut présager de rien et surtout pas du type des datatypes et des versions installés chez l'utilisateur !
Après le PROCLAYOUT, on peut donc accéder au DestBitMap qui contient désormais l'image, ou en cas d'échec à PDTA_BitMap mais alors rien n'est garanti quant à l'état de l'image (surtout si NewDTObject avait été invoqué avec FreeSourceBitMap à TRUE).
Obtention du BitMap
Nous avons abordé la fonction GetDTAttrs mais après l'opération de "layout", nous pouvons en plus accéder à la fonction ReadPixelArray qui ouvre de nouveaux horizons. Avant la V43, il n'était pas possible de récupérer les données 24 bits d'un BitMap, la meilleure restituion étant le HAM8 pour l'AGA. Depuis la version V43 introduite par CGFX, l'accès au buffer du BitMap est autorisé ça grâce à la méthode PDTA_READPIXELARRAY (la version de Picasso96 ne la supporte pas, cependant). La méthode PDTM_READPIXELARRAY permet en effet de récupérer le buffer d'une image sous la forme d'un tableau de pixels, ce qui peut s'avérer primordial dans certains cas.
Ces différentes manières d'accéder à l'image soulèvent un problème concernant le terme de BitMap : on note une différence entre BitMap système et BitMap CyberGfx. Il est en effet possible d'avoir un pointeur sur un BitMap sans pouvoir l'utiliser directement car on n'est pas censé savoir comment il est structuré. Encore une fois : prudence !
La sous-classe du picture.datatype (iff, jpeg, ...) délivre l'image comme un BitMap alloué par AllocBitMap, et à moins qu'il ne soit réarrangé, il sera libéré avec un FreeBitMap(). Les BitMap standards doivent être en mémoire CHIP, mais un datatype intelligent ne chargera pas toutes les données en mémoire tant qu'il n'a pas reçu l'appel à sa méthode GM_LAYOUT duquel il peut obtenir un pointeur sur l'écran de destination.
Copie du BitMap
Ca y est, notre image est reconnue, chargée, traitée et moulinée. Encore faut-il l'afficher, n'oublions pas que c'est la raison pour laquelle nous nous sommes donnés tant de mal ! Il n'y a qu'à copier les données lues dans un BitMap écran de façon classique, via le RastPort bien sûr.
DisposeDTObject
Si l'on reprend la terminologie objet, NewDTObject s'apparente au constructeur et crée une instance de datatype. Lorsque le travail est effectué, un appel à son pendant, le destructeur DisposeDTObject, permet de libérer proprement les ressources allouées.
Evolution des datatypes
C'est certainement la démocratisation des cartes graphiques qui a poussé à la réalisation de versions 43 des datatypes. On ne peut que s'en réjouir, même si cela a apporté des divergences. Il serait bon dans l'avenir qu'une future version pousse les différents acteurs à se conformer à des spécifications communes ET écrites de façon détaillée.
Afin d'illustrer à nouveau le mécanisme d'ouverture d'une image sur le Workbench, prenons ici un exemple d'une image GIF. Des tests effectués par Matthias Scheler en 1996, sur un 4000-040 en AGA, nous permettent de revenir brièvement sur le fonctionnement interne des datatypes.
Avec la version 42 (15 s) :
- gif.datatype charge l'image chunky
- il la convertit en format planar
- picture.datatype convertit l'image en chunky pour pouvoir procéder à un remapping
- picture.datatype applique le mapping des couleurs
- picture.datatype convertit l'image en planar pour pouvoir l'afficher
Avec la version 43 (8 s) :
- gif.datatype charge l'image chunky
- picture.datatype applique le mapping des couleurs
- picture.datatype convertit l'image en planar pour pouvoir l'afficher
Notes techniques sur les palettes
Comme pour les bitmaps, on ne sera pas surpris des nombreuses possibilités d'accéder aux palettes de couleurs. Ces informations proviennent d'un message d'Olaf Barthel.
Le picture.datatype initialise toujours PDTA_CRegs, en concordance avec PDTA_NumColors. Chaque sous-classe doit remplir, en toute rigueur, les tags PDTA_GRegs et PDTA_ColorRegisters (toutes ne le font pas ... ça serait trop facile !).
Voici la distinction entre les 3 tables :
- PDTA_CRegs : palette de l'image originale telle qu'elle est lue par son datatype (ilbm, gif, ...). Si on charge une image sans demander au picture.datatype de remapper pour utiliser moins de couleurs ou pour utiliser les couleurs d'un écran spécifique, c'est la table à utiliser avec SetRGB32() avant d'afficher l'image.
- PDTA_GRegs : le datatype qui lit l'image (ex. ilbm) doit copier le contenu de PDTA_CRegs dans cette table. Le picture.datatype doit changer cette table plus tard en remappant ou en adaptant les couleurs à celles d'un écran donné, avec éventuellement moins de couleurs. Le 'G' dans PDTA_GRegs signifie "Got" ou obtenu, en substitution à la palette d'origine.
- PDTA_ColorRegisters : c'est normalement une copie de la table PDTA_GRegs, avec les valeurs des composantes 32 bits décalées de 24 bits pour donner une composante 8 bits.
Appel à témoins :)
Afin de compléter les informations de cet article, j'espère que vous pourrez me faire part de votre expérience, puisque seule la mise en pratique permet de comprendre comment fonctionne tel ou tel point précis de la mise en oeuvre des datatypes. Quelques aspects n'ont pas été évoqués ici, comme les fonctions ObtainDataType et ReleaseDataType, les méthodes de type DRAW, la transparence, le redimensionnement, ...
