Introduction aux graphiques sous Cocoa
Depuis le début de cette série d’articles, j’ai principalement parlé de la manière d’utiliser Cocoa dans le but de construire des applications dotées d’interfaces utilisateurs. J’ai aussi abordé les aspects fondamentaux de la programmation orientée objet (POO) et de la manière de se servir des classes les plus communément utilisées dans les applications. Aujourd’hui, je vais porter votre attention sur la manière de dessiner et de créer des graphiques en 2D sous Cocoa. Je commencerai « petit » aujourd’hui en abordant les aspects basiques du dessin de formes simples dans une fenêtre.
Dans les prochains articles, j’aborderai le dessin de formes plus compliquées, l’implémentation d’animations, tout comme la manipulation de données brutes d’image. Mais avant ça, commençons par le basique.
Rectangles, tailles et points
Les rectangles, les tailles et les points sont représentées par trois types de données Cocoa qui ne sont rien d’autres en fait que des structures C. Elles sont respectivement NSRect, NSSize, et NSPoint.
NSPoint (ou plus simplement, un « point ») est le plus fondamental des trois types de données, nous allons donc en parler en premier. Un point sous Cocoa est une variable qui stocke une paire de coordonnées, à savoir les valeurs x et y d’un emplacement situé sur le plan de notre surface de dessin. Nous parlerons de la surface de dessin de manière plus détaillée par la suite, il suffit de dire que le plan de la surface de dessin n’est qu’un plan Cartésien dont nous avons tous fait connaissance en géométrie scolaire. L’origine (0,0) du plan est dans le coin inférieur gauche de votre surface de dessin, les valeurs positives de l’abscisse x s’étendent vers la droite, celle de l’ordonnée y vers le haut. Avec ce système, une unité de distance est équivallente à un pixel de notre écran, gardez ceci à l’esprit au moment de visualiser la taille des objets que vous allez dessiner à l’écran.
Bien, et à propos des points ? Comment Cocoa les représente ? NSPoint est une structure formelle du langage C qui est constituée comme suit :
typedef struct _NSPoint {
float x;
float y;
} NSPoint;
Si vous n’êtes pas familier des complexités du C, l’instruction typedef définit simplement un nouveau type de donnée — NSPoint qui est équivalent à la structure plus fondamentale de type de donnée _NSPoint. Donc, quand nous parlons de NSPoint, nous parlons de la structure montrée ci-dessus. Si vous n’êtes pas familier des structures C, vous devriez alors retourner dans votre ouvrage de référence préféré sur le C et y reparcourir ce qui traite de ce sujet.
Pour initialiser une variable NSPoint, nous utilisons la fonction fondamentale NSMakePoint, qui prend deux arguments, les coordonnées x et y du point, et retourne une variable NSPoint initialisée avec ces deux arguments. La ligne de code suivante montre comment nous l’utilisons :
NSPoint p = NSMakePoint(10, 45);
Pour maîtriser complètement ces variables (NSPoint, NSRect, et NSSize), vous devez savoir comment accéder aux membres de la structure. En d’autres mots, pour une variable NSPoint donnée, comment est-ce que nous déterminons la valeur de la coordonnée x et celle de la coordonnée y de ce point ? La réponse tient dans l’opérateur « . ». Cet opérateur indique la manière dont nous accédons les membres de la structure par le biais de la syntaxe structVariable.memberName.
Par exemple, si nous voulions récupérer les coordonnées x et y d’une variable de structure NSPoint, nous ferions la chose suivante :
float xCoord = p.x; float yCoord = p.y;
Avec le point déclaré plus haut, xCoord prendrait la valeur “10″, et yCoord “45″. Maintenant que NSPoints a été abordé, passons à NSRect et à NSSize, les deux autres types de données avec lesquels nous allons interagir fréquemment.
NSRect
NSRect (ou tout simplement “rect”) est un autre type de donnée commun défini dans le Foundation framework (Cadre de travail fondamental). Un rect définit une région rectangulaire de la zone de dessin. Ce n’est pas une forme, mais cela pourrait définir les contours d’une forme, d’une fenêtre, d’une aire de dessin, une aire de coloriage ou toutes autres choses de cette nature. Les « rects » sont déterminés par un point d’origine et une taille, qui est elle même définie par une largeur et une hauteur. La définition formelle d’un NSRect, telle que documentée dans le référentiel Foundation, est la suivante :
typedef struct _NSRect {
NSPoint origin;
NSSize size;
} NSRect;
Le membre « origin » est un objet NSPoint qui indique le coin inférieur gauche du rectangle. NSSize est tout simplement un type de structure de donnée qui contient les valeurs relatives à la hauteur et à la largeur du rectangle, ces deux valeurs constituant des membres du rectangle. NSSize répond à la définition formelle suivante :
typedef struct _NSSize {
float width;
float height;
} NSSize;
Pour créer un « rect », nous utilisons la fonction fondamentale NSMakeRect, qui prend quatre arguments : les coordonnées x et y du point d’origine, la largeur et la hauteur. Pour créer une variable NSRect, nous faisons ainsi :
NSRect r = NSMakeRect(10, 10, 100, 150);
Cette instruction crée un rectangle avec un point d’origine situé en (10,10), avec une largeur de 100, et une hauteur de 150. Donc, le coin supérieur droit du rectangle sera situé en (110, 160).
Du fait que les membres du NSRect sont aussi des variables de structure, nous devons utiliser l’opérateur « . » deux fois pour atteindre les parties d’information les plus fondamentales du rectangle. Par exemple, pour accéder à la valeur de la coordonnée x du rectangle que nous avons créé au-dessus, nous devrions écrire ceci :
float xOrigin = r.origin.x;
Si nous voulions connaître la hauteur du rectangle, nous ferions ainsi :
float width = r.size.width;
Maintenant que nous maîtrisons les types fondamentaux de donnée que nous utiliserons dans les graphiques Cocoa, passons à la manière de dessiner des formes.
La toile et le pinceau
Une manière pratique d’appréhender comment Cocoa gère le dessin est d’imaginer comment vous seriez amené à dessiner n’importe quoi. Vous aurez besoin d’une surface pour dessiner dessus et de quelque chose pour dessiner avec en d’autres mots, une toile et un pinceau. Sous Cocoa, la toile est représentée par la classe NSView, et le pinceau par la classe NSBezierPath. Regardons d’abord comment nous préparons notre toile dans Project Builder et Interface Builder, puis nous passerons à la partie la plus attrayante : le dessin réel.
Préparation de la toile
Comme je l’ai dit au-dessus, la toile de Cocoa est prise en charge par la classe NSView. NSView est une classe qui fournit un mécanisme permettant de définir des aires sur un écran pour y dessiner, ainsi que toute la mécanique qui se cache derrière le dessin. NSView est une bête de classe, et partir dans une description détaillée des rouages internes de cette bête serait un exercice de duplication de l’excellente documentation fournie par Apple, de plus cela allongerait inutilement cet article. Donc, je préfère vous conseiller d’aller lire cette documentation pour une meilleure compréhension des fonctionnalités de cette classe, nous parlerons ici de la manière de les utiliser.
Nous ne sommes jamais en interaction avec une instance de NSView c’est une classe abstraite. Par contre, nous créons une sous-classe de NSView, y ajoutons tout notre code de dessin, et interagissons avec cette sous-classe. Project Builder fournit une technique pour créer un sqelette de sous-classe NSView.
La première à faire est de créer un nouveau projet. Appelez-le CocoaDrawing. Après qu’il se soit ouvert, allez dans le menu File et sélectionnez “New File”. Dans le dialogue qui s’ouvre alors sélectionnez “Objective-C NSView Subclass” comme le type de fichier que vous voulez créer. Cliquez sur Next et nommez votre nouveau fichier CocoaDrawing.m. Assurez-vous que CocoaDrawing.h est réglé pour être aussi créé. Cliquez sur Finish, et vous avez votre fichier comprenant une sous-classe de NSView.
L’étape suivante consiste à présenter notre sous-classe NSView à notre application. Cela se fait à l’intérieur d’Interface Builder (IB). Lancez IB en double-cliquant sur le fichier MainMenu.nib de votre projet. Une fois IB ouvert, nous allons passé par une petite astuce pour ajouter notre nouvelle classe, CocoaDrawing, à celle connues de IB. Nous allons glisser le fichier CocoaDrawing.h à partir de la fenêtre de notre projet vers le panneau des instances de IB.

Déposer l’en-tête de la classe CocoaDrawing en la faisant glisser à partir de Project Builder vers Interface Builder.
Cela ajoutera notre nouvelle classe, CocoaDrawing, à la liste du panneau de Classes comme montré ci-dessus. Cette procédure d’ajout de classe à IB peut être effectuée pour toute classe que créerez dans Project Builder. En fait, c’est le cas pour toutes les balises IBOutlet et IBAction que nous voyons dans notre code généré par IB, de cette manière IB peut rendre disponible toutes les outlets et les actions indiquées.

CocoaDrawing ajoutée à la liste des classes dans InterfaceBuilder.
L’étape suivante consiste à déposer un container CustomView sur la fenêtre de notre application. A partir de la Palette des Containers Cocoa, déposer un objet Custom View sur la fenêtre. Le CustomView étant sélectionné, ouvrz le panneau Show Info et vous devriez voir CocoaDrawing sous les Attributs dans la liste des classes. Sélectionnez-la, le nom de la vue se changera alors en CocoaDrawing, et le contenu de cette vue sera maintenant contrôlé par le code de CocoaDrawing. Nous sommes maintenant parés pour découvrir le pinceau de Cocoa. Sauvegardez votre œuvre dans IB, retournez dans Project Builder et passez à la section suivante.

Ajout d’un container de vue à la fenêtre de votre application.

Transformer le CustomView en sous-classe de NSView, CocoaDrawing.
Le pinceau
Notre pinceau Cocoa est pris en charge par la classe NSBezierPath du AppKit (aussi connu sous le nom de trajectoire de Bézier). Comme NSView, NSBezierPath est une classe remplie de toute sortes de comportements. Malheureusement, nous n’allons aujourd’hui n’en effleurer que la surface, mais nous en découvrirons plus plus tard.
Les trajectoires de Bezier sont des objets qui définissent des formes en se bsant sur une série de segments de ligne et de courbe. Par exemple, un rectangle pourrait être représenté par un trajectoire de Bézier comprenant quatre segments de lignes droites. Avec les trajectoires de Bézier, vous pouvez concevoir des formes à la compléxité arbitraire. La procédure pour dessiner une forme dans une vue consiste d’abord à créer un trajectoire, puis de le remplir avec une couleur ou de le dessiner en tant que contour. Nous verrons des exemples de ces deux possibilités bientôt.
Tout le code de dessin que nous allons écrire sera placé dans la méthode drawRect:. Pour ceux d’entre vous qui ont une expérience des graphiques 2D sous Java, drawRect: est analogue à la méthode paint(). L’argument de cette méthode est le rectangle dans lequel le dessin sera placé.
Normalement, nous ne serons pas amenés à appeler drawRect: directement. Par contre, quand un événement quelconque provoquera la nécessité de redessiner le contenu de notre vue, ce process indiquera à la vue qu’elle doit être redessinée. Aujourd’hui nous n’aurons aucune raison d’indiquer manuellement à la vue qu’elle doit se redessiner elle-même, mais cela viendra dans un futur article.
Ce sur quoi nous allons travailler aujourd’hui sera basé sur le dessin de rectangles et d’ellipses, formes qui sont presque les mêmes au niveau du code. Les deux méthodes qui nous concernent sont les constructeurs de commodité +bezierPathWithRect: et +bezierPathWithOvalInRect:. Plongeons dès maintenant dans notre premier exemple de dessin et voyons comment cela fonctionne.
Dans la méthode drawRect:, nous allons ajouter le code suivant :
- (void)drawRect:(NSRect)rect
{
NSRect r = NSMakeRect(10, 10, 50, 60);
NSBezierPath *bp = [NSBezierPath bezierPathWithRect:r];
NSColor *color = [NSColor blueColor];
[color set];
[bp fill];
}
Dans la première ligne, nous définissons le rectangle qui va vite devenir notre trajectoire de Bézier. Ensuite, nous créons un objet trajectoire de Bézier qui indique tout simplement le périmètre du rectangle. Dans la ligne suivante, nous créons une couleur avec laquelle nous remplirons le rectangle, couleur qui deviendra la couleur par défaut des prochains dessins grace à la méthode -set de NSColor. Le message [color set] indique au moteur graphique d’arrière plan que toute opération de dessin sera effectuée avec la couleur bleu (comme indiqué par l’objet color). Enfin, nous envoyons un message à la trajectoire de Bézier pour la remplir avec la couleur définie. Cet extrait de code devrait produire la sortie suivante :

Nous aurions pu aussi remplacer la dernière ligne par [bp stroke], ce qui aurait eu pour effet de dessiner une ligne le long de la trajectoire, comme montré ci-dessous :

Maintenant, si nous avions changé la ligne de code de création de la trajectoire (la deuxième) par celle ci :
NSBezierPath *bp = [NSBezierPath bexierPathWithOvalInRect:r];
La forme dessinée à l’écran aurait été une ellipse avec les mêmes dimensions que le rectangle, comme montré ci-dessous :

Une autre manière de dessiner un rectangle est de se servir de la fonction fondamentale NSRectFill, qui prend un rectangle comme argument. La couleur de remplissage effectuée par cette fonction est la couleur courante de l’environnement graphique. Par exemple, nous pourrions changer le premier exemple de manière à circonvenir l’utilisation de NSBezierPath avec le code suivant :
- (void)drawRect:(NSRect)rect
{
NSRect r = NSMakeRect(10, 10, 50, 60);
NSColor *color = [NSColor blueColor];
[color set];
NSRectFill(r);
}
NSRectFill est aussi une méthode pratique pour colorier le fond d’une vue. Nous pouvons faire ceci en positionnant une couleur en tant que couleur de fond, puis en passant la variable argument de drawRect à NSRectFill. Retournons à notre exemple originel et colorions le fond de noir de la mnière suivante :
- (void)drawRect:(NSRect)rect
{
NSRect r;
NSBezierPath *bp;
[[NSColor blackColor] set];
NSRectFill(rect);
r = NSMakeRect(10, 10, 50, 60);
bp = [NSBezierPath bezierPathWithRect:r];
[[NSColor blueColor] set];
[bp fill];
}
Dans cet exemple, j’ai éliminé la variable color en envoyant un message set directement à l’objet retourné par [NSColor blueColor]. Ce code produit la sortie suivante :

Cette méthode de coloriage du fond de la vue fonctionne parce qu’au moment où la méthode drawRect: est automatiquement invoquée par la vue, les contours de la vue sont passées en tant qu’argument rect. Une autre notre sur le mode de fonctionnement du dessin le code de coloriage du fond doit intervenir avant toute autre commande de dessin. Ceci tient dans le fait que tout objet dessiné sur la vue est dessiné par dessus tout autre objet placé précédemment.
Fin
Bien, voilà l’essentiel de la création de formes par l’utilisation des classes de dessin de Cocoa ! C’est assez simple il n’y en a pas beaucoup plus à dire. Dans le prochain article, nous continuerons notre discussion des graphiques et du dessin en apprenant à constituer des trajectoires plus complexes. En attendant, prenez du plaisir avec ce que nous avons appris aujourd’hui, et expérimentez vos propres idées. Je vous quitte avec une petite application qui dessine une suite de rectangles et d’ellipses générées aléatoirement comme montré ci-dessous. C’est une application simple qui n’utilise pas grand chose de plus que ce que nous avons vu aujourd’hui en terme de dessin. Elle peut être téléchargée ici (68Ko). A bientôt.


Textes originaux en anglais sur O’Reilly : http://www.macdevcenter.com/pub/au/159
Chargement
Commentaires récents