Accueil > Programmation Cocoa > Introduction aux graphiques sous Cocoa - Partie 2

Introduction aux graphiques sous Cocoa - Partie 2

Par Mike Bean, le 6/11/2001

Traduit par Thierry, le 17/09/2002

L’article d’ajourd’hui est la deuxième partie de notre apprentissage des graphiques 2D sous Cocoa. Dans l’article précédent, j’ai montré comment NSView et NSBezierPath travaillaient ensemble pour nous permettre de dessiner de simples objets, et j’ai parlé de la manière d’utiliser, dans le dessin, quelques types de données fondamentaux du Foundation Framework. Cela nous a permis de faire des élipses et des rectangles en utilisant NSBezierPath.

Maintenant, je vais capitaliser sur ces concepts pour dessiner des contours arbitraires batis à partir de lignes et de courbes, nous permettant ainsi de construire des formes complexes.

La phase d’initialisation d’aujourd’hui sera identique à celle de l’article précédent (à part pour les noms de fichiers, bien sûr). Commencez par créer un nouveau projet appelé Curves (ou ce que vous voulez), et ajoutez y les fichiers pour une sous-classe de NSView appelée CurvesView (ou, encore, ce qui vous convient). Après cela, suivez les étapes du précédent article pour connecter CurvesView à un container NSView dans Interface Builder. Une fois ces étapes essentielles passées, vous serez prêt à progresser dans ce projet.

Avant de plonger dans les mécanismes de NSBezierPath, permettez moi de me faire plaisir avec une petite histoire relative aux courbes de Bézier. Les courbes de Bézier ont été développées par le mathématicien et ingénieur automobile français Pierre Bézier dans les années 70. Elles furent développées à l’origine pour des systèmes de CAD/CAM aidant ainsi à la conception et au prototypage de voitures Renault, puis elles continuèrent leur chemin dans l’image de synthèse.

Nous avons tous été en contact avec les courbes de Bézier puisqu’elles sont les fondements du système de dessin PostScript d’Adobe. Une des raisons pour lesquelles les courbes de Bézier sont largement utilisées dans les images de synthèse tient dans leur nature mathématique : les courbes de Bézier sont décrites par des équations. Les formes dessinées par des courbes de Bézier peuvent être magnifiées sans perte de précision et augmentation de la granularité, comme c’est le cas avec les images bitmap.

Assez d’histoire maintenant. Retournons au travail.

Construction de contours

Les contours de Bézier sont construits en utilisant un petit ensemble de méthodes constructeurs telles que -moveToPoint:, -lineToPoint:, et -curveToPoint:controlPoint1:controlPoint2: (plus de détail sur cette méthode à venir).

Quand vous créez des contours, gardez en mémoire la notion de point courant. Le point courant est le point final du dernier élément ajouté et le point de démarrage du prochain segment de contour. Essentiellement, le point courant est le point le plus récent utilisé dans la construction d’un contour. Cela explique pourquoi -lineToPoint: ne prend qu’un seul argument NSPoint, quand nous savons que deux points définissent une ligne. Par exemple, si votre point courant était (100, 100), alors ce code créerait une ligne verticale :

[aPath lineToPoint:NSMakePoint(100, 300)];

Nous comprenons que le segment de contour commencera au point courant et qu’il s’étendra jusqu’au point indiqué dans l’argument. En utilisant la méthode -moveToPoint:, nous pouvons changer l’emplacement du point courant sans ajouter de contour. Ceci est utile pour définir le point de démarrage d’un contour récemment instancié, ou pour simplement “déplacer votre crayon” vers le milieu d’un contour en cours de construction.

Considérons cet exemple de dessin d’un triangle pour illustrer ces notions. Nous allons d’abord définir trois points qui seront les extrémités de notre triangle; puis une nouvelle instance vide de NSBezierPath; construire le contour de notre triangle en utilisant les trois points que nous avons définis; et finallement, le dessiner à l’écran. Tout ceci sera écrit dans la méthode -drawRect de CurvesView :

- (void)drawRect:(NSRect)rect { // The three vertices of the triangle
NSPoint p1 = NSMakePoint(100, 100);
NSPoint p2 = NSMakePoint(200, 300);
NSPoint p3 = NSMakePoint(300, 100); // Constructing the path
NSBezierPath *triangle = [NSBezierPath bezierPath];
[triangle moveToPoint:p1];
[triangle lineToPoint:p2]; [triangle lineToPoint:p3];
[triangle lineToPoint:p1]; // Draw the path
[[NSColor blueColor] set];
[triangle stroke];   // or
[triangle fill];
}

Comme vous pouvez vous en rendre compte, nous avons d’abord défini les trois sommets du triangle, puis nous avons commencé la construction de notre contour en instanciant NSBezierPath, par l’affectation de la variable triangle à cet objet, puis par déplacement vers le premier point - en mettant  notre crayon sur le papier. A partir de là, nous avons ajouté les trois lignes connectant les sommets, appliqué la couleur et finalement indiqué au triangle de se dessiner lui-même à l’écran.

NSBezierPath comporte une méthode appelée -closePath qui ajoute un élément contour à votre contour, cet élément étant une ligne droite partant du point courant vers le point de démarrage de votre contour - il connecte le début et la fin de votre contour avec une ligne, le fermant ainsi. Comme nous allons le voir dans un instant, il est judicieux de fermer votre contour (si votre contour est supposé être en effet un contour fermé) quand vous en avez fini. Nous aurions pu nous servir de -closePath au-dessus pour terminer notre triangle en remplaçant la dernière instruction

[triangle lineToPoint:p1];

par un message -closePath envoyé à triangle :

[triangle closePath];

Les deux messages envoyés à triangleont pour effet d’ajouter une ligne droite de p3 vers p1. Le second message, utilisant -closePath, a plus pour effet de joindre le point final au point initial du contour que de créer l’apparence d’un contour fermé, comme cela est effectué dans la première méthode. Dans un moment, nous verrons une conséquence plus ennuyante de ne pas utiliser -closePath.

En plus des méthodes qui prennent des points absolus en arguments, NSBezierPath déclare un ensemble de méthodes qui nous permettent d’indiquer des positions relatives de points. Ainsi, au lieu de dire que p2 est situé aux coordonnées (200, 300), nous pouvons dire que p2 est en (100, 200) par rapport à p1. Maintenant donc, p2 s’apparente plus à une direction vectorielle en opposition à un point absolu du plan. En utilisant la méthode relativeLineToPoint:, nous pouvons réécrire notre méthode comme suit :

- (void)drawRect:(NSRect)rect { // The three vertices of the triangle
  NSPoint p1 = NSMakePoint(100, 100);
  NSPoint rp2 = NSMakePoint(100, 200);
  NSPoint rp3 = NSMakePoint(100, -200); // Constructing the path
  NSBezierPath *triangle = [NSBezierPath bezierPath];
  [triangle moveToPoint:p1];
  [triangle relativeLineToPoint:rp2];
  [triangle relativeLineToPoint:rp3];
  [triangle closePath]; // Draw the path
  [[NSColor blueColor] set];
  [triangle stroke];   // or
  [triangle fill];
}

Nous n’utiliserons pas que des lignes pour dessiner des formes, nous pouvons aussi générer des courbes, en utilisant la méthode -curveToPoint:controlPoint1:controlPoint2:. Comme vous pouvez vous en apercevoir, cette méthode est un peu plus compliquée que de dessiner une ligne, laissez moi donc vous toucher un mot ou deux à propos de ces “control points” que nous voyons dans le nom de la méthode.

A chaque point final de la courbe est associé un point de contrôle. Le point de départ de la courbe (le point courant) et le controlPoint1 définissent un vecteur qui est tangent à la courbe au point courant.

De manière similaire, le point final de la courbe (de la liste des arguments) et le controlPoint2 définissent un autre vecteur qui est tangent à la courbe au point final. Ensembles, ces deux vecteurs fournissent asser d’information pour les algorithmes cachés derrière NSBezierPath pour assortir une équation polynomial equation aux contraintes définies. Cette équation, en retour, définit une courbe à l’apparence très naturelle. Vous pouvez voir les différentes parties d’une courbe ci-dessous.

Figure 1.

Nous pouvons incorporer une courbe dans notre triangle pour lui donner un fond en forme de vague de la manière suivante :

- (void)drawRect:(NSRect)rect {     // The three vertices of the triangle
NSPoint p1 = NSMakePoint(100, 100);
NSPoint p2 = NSMakePoint(200, 300);
NSPoint p3 = NSMakePoint(300, 100);     // The control points of the curve
NSPoint c1 = NSMakePoint(200, 200);
NSPoint c2 = NSMakePoint(200, 0);     // Constructing the path
NSBezierPath *triangle = [NSBezierPath bezierPath];
[triangle moveToPoint:p1];
[triangle lineToPoint:p2];
[triangle lineToPoint:p3];
[triangle curveToPoint:p1 controlPoint1:c1 controlPoint2:c2];     // Fill the path
[[NSColor blueColor] set];
[triangle fill];     // Draw the outline
[[NSColor redColor] set];
[triangle stroke];
}

Rappelez-vous que controlPoint1 définit la ligne à partir du point courant et que, controlPoint2 définit la ligne à partir du point donné en argument à curveToPoint:. Notez que dans cet exemple, nous avons d’abord rempli la forme du contour par du bleu et que nous avons ensuite marqué le contour par du rouge, créant ainsi une bordure rouge sur la forme, comme montré sur l’image ci-dessous. Si l’on effectue plusieurs commandes de marquage ou de remplissage telles que celles que nous avons faites ici, les marquages ou remplissages les plus récents sont dessinés au-dessus des précédents appels à ces méthodes.

Figure 2.

Avec ces bases de la création de contour à notre actif, nous allons maintenant jeter un oeil aux propriétés des lignes et des courbes que nous pouvons altérer.

Changer les caractéristiques des contours

Il est possible de changer les caractéristiques des contours de Bézier via plusieurs méthodes. Par exemple, nous pouvons changer l’épaisseur du marquage d’un contour (bordure) en utilisant la méthode -setLineWidth:, où l’argument est un nombre à virgule flotante (float) indiquant la largeur de la ligne en points. Nous pouvons changer l’épaisseur des lignes de notre triangle en ajoutant ce qui suit juste avant d’envoyer le message de marquage au triangle :

[triangle setLineWidth:10];

Cela fera ressembler notre dessin à ceci :

Figure 3.

Si vous réglez la largeur de la ligne à 0 en utilisant cette méthode, alors le contour sera marqué par la plus petite largeur qu’un écran puisse afficher. J’ai choisi ici une largeur épaisse de ligne pour faire ressortir des fonctions sur la manière dont sont joints entre eux les éléments contours. D’abord, notez comment le coin inférieur droit du triangle est un point très acéré qui s’étend beaucoup plus loin que ce que nous avions souhaité à l’origine. L’image ci-dessous montre une ligne fine verte superposée sur la ligne rouge plus épaisse pour illustrer mes propos.

Figure 4.

La raison qui illustre ceci tient dans le fait que par défaut, les lignes d’un contour sont jointes en utilisant le point d’intersection de leur bordure extérieure, là où les deux bordures externes se rejoignent pour ne plsu former qu’un seul point. Ce style de joint par défaut est défini par la constante NSMiterLineJoinStyle, que nous utilisons pour indentifier le style de jointure dans le code. Nous pouvons corriger ce problème de différentes manières.

En premier, nous pouvons changer le style de jointure avec NSRoundLineJoinStyle ou avec NSBevelLineJoinStyle en utilisant la méthode -setLineJoinStyle. Par exemple, si nous voulions faire en sorte que les angles soient arrondis, nous passerions la constante NSRoundLineJoinStyle comme argument à setLineJoinStyle:

[triangle setLineJoinStyle:NSRoundLineJoinStyle];

Autrement, nous pouvons aplatir les coins en utilisant NSBevelJoinStyle. Dans l’image qui suit, je vous montre les trois styles de jointure côte à côte avec une représentation du contour original en vert.

Figure 5.

La jointure de lignes n’est cependant pas la fin de nos ennuis. Vous vous souvenez de notre discussion à propos de la fermeture des contours et des problèmes de jointure de lignes qui leur étés associés ? Bien, voici un parfait exemple de ce qui arrive quand le contour n’est pas officiellement fermé, en dépit du fait que votre forme soit apparemment fermée.

Notez dans le coin inférieur gauche que les lignes ne sont jamais jointes. La raison qui explique ceci est simple : ce coin est à la fois le début et la fin du contour. Nous n’avons jamais dit que ces deux points - malgré qu’ils soient le même - devaient être joints. Pour régler ceci, nous envoyons un message closePath au triangle après la dernière commande de construction qui permet de terminer la forme. Maintenant, tout type de style de jointure sera appliqué correctement, comme illustré ci-dessous :

Figure 6.

Le code final qui produit cette image est comme suit :

- (void)drawRect:(NSRect)rect {
    //The three vertices of the triangle
    NSPoint p1 = NSMakePoint(100, 100);
    NSPoint p2 = NSMakePoint(200, 300);
    NSPoint p3 = NSMakePoint(300, 100);
    NSPoint c1 = NSMakePoint(200, 200);
    NSPoint c2 = NSMakePoint(200, 0);
    // Constructing the path
    NSBezierPath *triangle = [NSBezierPath bezierPath];
    [triangle moveToPoint:p1];
    [triangle lineToPoint:p2];
    [triangle lineToPoint:p3];
    [triangle curveToPoint:p1 controlPoint1:c1 controlPoint2:c2];
    [triangle closePath];
    // Fill the path
    [[NSColor blueColor] set];
    [triangle fill];
    // Draw the outline
    [triangle setLineJoinStyle:NSRoundLineJoinStyle];
    [triangle setLineWidth:10];
    [[NSColor redColor] set];
    [triangle stroke];
    // Draw representation of original path
    [triangle setLineWidth:0];
    [[NSColor greenColor] set];
    [triangle stroke];
}

Pour finir

Bien, vous y voilà ! C’est ainsi que vous pouvez créer toute sorte de forme dont vous auriez besoin. NSBezierPath comporte plein d’autres méthodes qui effectuent des taches utiles, et nous en aborderons beaucoup d’autres dans de futurs articles.

Comme toujours, je vous recommande de vous familiariser avec référence de la classe NSBezierPath si vous ne l’avez pas déjà fait. Pour l’instant, je vous laisse utiliser ces outils des plus utiles avec lesquels vous pouvez faire beaucoup. Dans le prochain article nous commencerons à apprendre comment vous pouvez interagir avec les éléments dessinés à l’écran en se servant de la souris, donc revenez bientôt pour de bons moments.

Textes originaux en anglais sur O’Reilly : http://www.macdevcenter.com/pub/au/159

Thierry Programmation Cocoa , , ,

  1. Pas encore de commentaire
  1. Pas encore de trackbacks
Vous devez être identifié pour poster un commentaire