Les bureaux des développeurs

23 02 2009 In: Liens

On parle souvent des machines et des logiciels que les développeurs utilisent pour faire leur travail, mais il ne faut pas oublier que l'environnement fait aussi beaucoup, que ce soit le bureau, la chaise ou de manière plus générale les locaux. Je vous invite donc à découvrir les bureaux de certaines entreprises du secteur :

(via MacGeneration)

Succès des livres de développement Mac

23 02 2009 In: Livres

O'Reilly vient de publier l'état du marché des livres informatiques pour l'année 2008, et parmis les livres concernant le développement, la plus grosse augmentation concerne le développement sur Mac qui est en augmentation de plus de 85%. La sortie du SDK iPhone et les nouvelles éditions de livres comme Programmation Cocoa sous Mac OS X ou la version originale en anglais (Cocoa Programming for Mac OS X) n'est certainement pas étrangère à cette augmentation.

Langage C et Xcode

19 02 2009 In: C

Puisque la question fut posée maintes fois: "Comment programme-t-on en langage C sous XCode".

Créer un projet (pour réunir tous les fichiers)

  • Aller dans le menu File > New Project > Command Line Utility > Standard Tool. Puis cliquer sur Choose.
  • Yapuka éditer le fichier main.c.

Lancer le programme

Pour lancer le programme, direction le menu Build > Build & Run.

Comme il est souvent intéressant de voir du texte s'afficher, il est de bon ton d'ouvrir la Console, par le menu Run > Console.

Y'a qu'à demander.

Des ressources Cocoa en français

19 02 2009 In: Cocoa, iPhone / iPod Touch, Liens

Les ressources en français concernant Cocoa et le développement sur Mac / iPhone sont relativement rare, alors quand plusieurs sont mise en ligne la même semaine, je ne peux m'empecher de vous les signaler :

Générateur d'images fractales (4)

16 02 2009 In: Pas à pas

Cet épisode sera consacré au redimensionnement de la vue qui affiche l'image fractale.

Redimensionnement de la vue

Ajoutons naïvement la possibilité de redimensionner la vue, sans trop penser aux conséquences.

CFRMandelbrotRender

Ajoutons deux variables d'instance pour savoir à quelles dimensions faire le rendu:

@interface CFRMandelbrotRender : NSObject {

    // Dimensions de la bitmap
    NSUInteger largeurBitmap;   
    NSUInteger hauteurBitmap;       
}

- (void) setLargeurBitmap:(NSUInteger)largeurPixels;
- (void) setHauteurBitmap:(NSUInteger)hauteurPixels;

- (NSBitmapImageRep*) bitmapImageRep;

@end

N'oublions pas de les initialiser:

- (id) init
{
    if(self = [super init])
    {
        largeurBitmap = 400;
        hauteurBitmap = 300;
    }

    return self;
}

Il faut également ajouter les setters de ces deux variables d'instance:

- (void) setLargeurBitmap:(NSUInteger)largeurPixels
{
    largeurBitmap = largeurPixels;  
}

- (void) setHauteurBitmap:(NSUInteger)hauteurPixels
{
    hauteurBitmap = hauteurPixels;  
}

À vous maintenant de modifier le code de -bitmapImageRep où sont utilisées ces dimensions.

CFRFractalView

C'est la vue qui va dire au CFRMandelbrotRender à quelle taille il doit générer la bitmap:

- (void)drawRect:(NSRect)rect
{

            …

    // Afficher la bitmap
    [render setLargeurBitmap:[self bounds].size.width];
    [render setHauteurBitmap:[self bounds].size.height];
    NSBitmapImageRep* bitmapRep = [render bitmapImageRep];
    [bitmapRep drawAtPoint:NSMakePoint(0,0)];

}

[self bounds] renvoie un NSRect définissant les limites de la vue. Nous fixons les dimensions de la bitmap pour qu'elles collent à celles de la vue.

Redimensionnement de la vue en même temps que la fenêtre.

Filez éditer MyDocument.xib sous Interface Builder:

  • sélectionnez la vue
  • dans l'inspecteur, dans l'onglet qui représente une règle, activez les flèches intérieures à la vue, ainsi que les ergots extérieurs (tout doit être activé).

Enregistrez, puis lancez le programme.

La bitmap s'est agrandie mais l'ensemble reste collé dans le coin supérieur gauche! En fait, notre programme calcule la partie droite de l'ensemble, alors que ce que nous voulons est que l'ensemble remplisse tout la vue.

Redimensionnement et intervalles

Nous voilà de retour à la planche à dessin. Posons déjà le comportement attendu lors d'un redimensionnement:

  1. La vue reste complètement remplie
  2. Le point situé au centre de la vue doit y rester
  3. La figure grandit ou rétrécit, on ne cadre pas plus large ou plus étroit

Pour la génération de la bitmap, il faut utiliser les mêmes proportions que la vue: je fais le choix de privilégier la largeur, qui sera complètement affiché. La hauteur sera, elle, déterminée en fonction de la largeur et des proportions de la vue.

Nouvelles variables d'instance

Deux variables d'instance deviennent nécessaires:

@interface CFRMandelbrotRender : NSObject {

    // Dimensions de la bitmap
    NSUInteger largeurBitmap;   
    NSUInteger hauteurBitmap;

    // Intervalle de calcul
    double largeur;     // Largeur de l'intervalle du repère calculé
    Complexe_t centre;  // Centre du repère

}

Il faut penser à initialiser ces variables:

- (id) init
{
    if(self = [super init])
    {
        largeurBitmap = 400;
        hauteurBitmap = 300;

        largeur = 4.0;
        centre.reel = 0.7;
        centre.imag = 0.0;
    }

    return self;
}

Conversion des coordonnées (2)

Nous allons devoir reprendre la formule de conversion des coordonnées entre la bitmap et le repère mathématique. Voici la représentation du problème:

Conversion des coordonnées

Nous voulons déterminer la position de point.

Calcul de la hauteur du repère

On a les relations:

échelle = largeur / largeurBitmap = hauteur / hauteurBitmap
=> hauteur = (largeur / largeurBitmap) * hauteurBitmap

Position du point

Le plus simple est de réfléchir en localisant "point" en fonction du point inférieur gauche du repère, puis de translater (dans ce qui suit, pointLocal est le point intermédiaire).

Pour l'abscisse:

pointBitmap.x / largeurBitmap = pointLocal.x / largeur
=> pointLocal.x = largeur * (pointBitmap.x / largeurBitmap)

Pour l'ordonnée, c'est à peine plus difficile:

(hauteurBitmap - pointBitmap.y) / hauteurBitmap = pointLocal.y / hauteur
=> pointLocal.y = hauteur * (1 - pointBitmap.y/hauteurBitmap)

Maintenant, il faut translater par rapport au coin inférieur gauche:

coinInférieurGauche.x = centre.x - largeur/2
coinInférieurGauche.y = centre.y - hauteur/2

Au final:

point = pointLocal + coinInférieurGauche

point.x = largeur * (pointBitmap.x/largeurBitmap.x - 1/2) + centre.x point.y = hauteur * (1/2 - pointBitmap.y/hauteurBitmap) + centre.y

La méthode de conversion

Une fois les formules converties en code:

- (Complexe_t) complexeAvecCoordBitmapX:(NSUInteger)bitmapX y:(NSUInteger)bitmapY
{
    Complexe_t point;

    double hauteur = (largeur/(double)largeurBitmap) * (double)hauteurBitmap;

    point.reel = largeur * ((double)bitmapX/(double)largeurBitmap - 0.5) + centre.reel;
    point.imag = hauteur * (0.5 - (double)bitmapY/(double)hauteurBitmap) + centre.imag;

    return point;
}

Nous utilisons dorénavant cette méthode pour fournir les coordonnées de c:

c = [self complexeAvecCoordBitmapX:x y:y];

Résultat

Essayons maintenant de lancer notre application. Ça marche !

Vous pouvez changer l'intervalle de calcul. Mettez, dans la méthode -init, largeur à 2: ça zoome.
Essayez de modifier les coordonnées du centre: on peut se déplacer.

La fenêtre de rendu

La suite

Nous sommes presque prêts à permettre la navigation dans l'ensemble de Mandelbrot. Presque. En effet, je ne sais pas chez vous, mais mon iMac G5 se fait un peu vieux et le redimensionnement de la fenêtre est loin d'être fluide. Nous nous attaquerons donc à l'optimisation dans le prochain épisode.

Le projet XCode complet à télécharger.

Renaud Pradenc
Céroce.com

En vrac

16 02 2009 In: Interview, iPhone / iPod Touch, Liens

Voici mes trouvailles de la semaine dans le monde Cocoa et Mac :

Anniversaire de Cocoa.fr

16 02 2009 In: Cocoa.fr

Le 7 février, le blog Cocoa.fr a fêté ses 1 an, et je voudrais donc partager avec vous quelques chiffres sur cette première année :

  • 171 billets et 204 commentaires
  • 2 auteurs : Renaud Pradenc et moi même
  • 22 497 visiteurs
  • ~200 inscrits au flux RSS

J'espère qu'il ne s'agit que du premier anniversaire de Cocoa.fr et qu'il y en aura beaucoup d'autres. Et si j'ai une chose que j'aimerais voir évoluer, c'est le nombre de commentaires, donc n'hésiter à donner votre avis sur les billets, ce que vous aimez, ce que vous aimez moins, etc. Cela nous permet de savoir ce que vous attendez et donc de faire des billets en conséquence.

Et pour finir la séance nostalgie, je vous invite à lire Bienvenue sur Cocoa.fr, le tout premier billet du blog (qui était juste une annonce, et Wil Shipley : Monster Marketing le premier vrai contenu du blog.

Générateur d'images fractales (3)

10 02 2009 In: Pas à pas

Après la génération de notre premier image dans l'article précédent, nous allons remanier notre programme pour le rendre plus propre.

Remaniement

Le remaniement (en anglais: Refactoring) est une opération qu'il est bon de faire continuellement. Il s'agit :

  • de changer l'architecture de l'application
  • d'améliorer la documentation
  • ou simplement de renommer certaines classes, méthodes ou variables.

Le but est de faciliter les modifications à venir, en gardant le code le plus propre possible tout au long de la vie du logiciel. Notez bien que nous n'ajoutons pas de fonctionnalités.

Isoler les fonctions sur les nombres complexes

Les fonctions mathématiques peuvent être réutilisées à divers endroits de notre programme, et pas seulement pour la calcul de l'image fractale, nous allons donc les séparer:

  • Créez un nouveau source C, appelé CFRComplexe.c (menu File > New File > C and C++ > C File).
  • Déplacez la définition de la structure ainsi que le prototype de Carre() dans CFRComplexe.h.
  • Déplacez la fonction Carre() dans CFRComplexe.c.

Pendant que nous y sommes, nous créons la fonction ModuleAuCarre():

double ModuleAuCarre(Complexe_t z)
{
    return z.reel*z.reel + z.imag*z.imag;
}

N'oubliez pas de rajouter son prototype dans le .h.

Isoler le calcul de l'image fractale

Les calculs appartenant à la couche Modèle, nous allons créer un objet CFRMandelbrotRender qui ne fait que générer la bitmap:

@interface CFRMandelbrotRender : NSObject {

}

- (NSBitmapImageRep*) bitmapImageRep;
@end

Déplaçons dans CFRMandelbrot.m tout le code qui fait le calcul de la bitmap:

- (NSBitmapImageRep*) bitmapImageRep
{
    // Créer la bitmap
    NSBitmapImageRep* bitmapRep = 
        [[NSBitmapImageRep alloc]
        initWithBitmapDataPlanes:NULL
        pixelsWide:400
        pixelsHigh:300
        bitsPerSample:8
        samplesPerPixel:1
        hasAlpha:NO
        isPlanar:NO
        colorSpaceName:NSDeviceWhiteColorSpace
        bytesPerRow:0
        bitsPerPixel:8];

    // Calculer l'ensemble de Mandelbrot:
    // Parcourir tous les points de la bitmap
    double x, y;
    for(x = 0; x < 400; x++)
    {
        for(y = 0; y < 300; y++)
        {
            // Convertir les coordonnées
            Complexe_t c;
            c.reel =  x/100.0 - 2;
            c.imag   = -y/100.0 + 1.5;

            // Initialiser z[0]
            Complexe_t z = {0.0, 0.0};

            NSUInteger n;
            for(n=0; n < MAX_ITERATIONS; n++)
            {
                // z[n+1] = z[n+1]^2 + c
                Complexe_t zCarre = Carre(z);
                z.reel = zCarre.reel + c.reel;
                z.imag = zCarre.imag + c.imag;

                // La suite diverge si |z| > 2
                if(ModuleAuCarre(z) > 4.0)
                    break;
            }

            // Donner le niveau de gris au pixel
            NSUInteger nuance = n * 255 / MAX_ITERATIONS;
            [bitmapRep setPixel:&nuance atX:x y:y];
        }
    }

    [bitmapRep autorelease];
    return bitmapRep;
}

Les modifications sont les suivantes:

  • la nouvelle fonction ModuleAuCarre() est appelée au lieu de faire le calcul en ligne.
  • la méthode [bitmapRep autorelease] est appelée pour que la mémoire occupée par la bitmap soit libérée, mais seulement après que la vue ait pu l'afficher.

Remaniement de la vue

Renommer

La vue pourrait maintenant servir à afficher autre chose que l'ensemble de Mandelbrot, par exemple celui de Julia. Ceci nous pousse à la renommer. Nous allons utiliser l'outil de Refactoring de XCode pour cela:

  • Ouvrez CFRMandelbrotView.m
  • Sur la ligne @implementation CFRMandelbrotView, sélectionnez le texte CFRMandelbrotView
  • Choisissez l'article de menu Edit > Refactor…
  • Le menu pop-up étant sur Rename, tapez le nouveau nom: CFRFractalView
  • Cliquez sur Preview, puis Apply.

Voilà un outil bien pratique, d'autant plus qu'il renomme la classe y-compris dans le fichier XIB !

Fixer la classe de rendu

Mettre à part le code qui fait le rendu nous impose maintenant de créer un lien pour récupérer la bitmap. Ajoutons une outlet à CFRFractalView:

#import "CFRMandelbrotRender.h"

@interface CFRFractalView : NSView {
    IBOutlet CFRMandelbrotRender* render;
}

@end

Basculez sous Interface Builder. Instanciez un exemplaire de CFRMandelbrotRender, et reliez l'outlet render de la vue à cet objet.

Méthode drawRect:

Retournons sous XCode, dans CFRFractalView.m. Ajoutons à la méthode drawRect: le nécessaire pour obtenir la bitmap et l'afficher:

- (void)drawRect:(NSRect)rect
{
    if(render)  // L'outlet est fixée
    {
        // Afficher la bitmap
        NSBitmapImageRep* bitmapRep = [render bitmapImageRep];
        [bitmapRep drawAtPoint:NSMakePoint(0,0)];       
    }

    else    // L'outlet "render" n'est pas fixée
    {
        [[NSColor blueColor] set];
        NSRectFill(rect);
    }

}

J'ai ici gérer le cas où l'on aurait oublier de relier l'outlet render. La vue serait alors emplie de bleu.

Résultat

Nous pouvons à présent lancer le programme: le résultat est très exactement le même. Mission accomplie !

Nous disposons dorénavant d'une bonne base pour poursuivre le développement. À bientôt pour la suite.

Le projet XCode complet à télécharger.

Renaud Pradenc
Céroce.com

En vrac : iPhone, OpenCL, etc.

10 02 2009 In: iPhone / iPod Touch, Liens

Une grosse actualité ces derniers temps pour le monde Apple ces derniers temps. Je vous invite donc à découvrir les articles suivants :

Générateur d'images fractales (2)

05 02 2009 In: Pas à pas

Cet article est le deuxième d'une série. Je vous recommande de commencer par le premier article, et même de le laisser ouvert pour pouvoir suivre au mieux celui-ci.

Cette fois-ci, nous entrons dans le vif du sujet, avec une première version du générateur. C'est parti !

Mise en place du projet

  • Créez un projet de type Cocoa Document-Based.
    Notre application devra tôt ou tard enregistrer et charger des documents.

  • Créez un fichier source de type Objective-C NSView subclass, appelé CFRMandelbrotView.
    Cette vue personnalisée effectuera le calcul et l'affichage.

  • Ouvrez MyDocument.nib dans Interface Builder. Dans la fenêtre Window se trouve un texte. Effacez-le.

  • Glissez une Custom View dans la fenêtre. Vous la trouverez dans la palette Library > Cocoa > Views & Cells.

  • Dans l'onglet View Size de l'Inspecteur, réglez les dimensions (W et H) de la vue à 400 x 300 pixels.

  • Dans l'onglet View Identity mettez la classe à CFRMandelbrotView.

Enregistrez, et revenez à XCode.
Pour simplifier, c'est la vue qui effectuera le calcul. Nous brisons le paradigme MVC, mais ce n'est pas bien grave pour ce programme d'essai.

Gestion des nombres complexes

Le langage Objective-C ne gère pas les nombres complexes. Nous allons devoir coder cela nous même. Pour commencer, déclarons dans CFRMandelbrotView.h un type "nombre complexe", basé sur une structure:

typedef struct Complexe_s
{
    double reel;    // Partie réelle
    double imag;    // Partie imaginaire
} Complexe_t;

Ajoutez également le prototype de la fonction qui permet d'élever un nombre complexe au carré:

Complexe_t Carre(Complexe_t z);

Passons maintenant à CFRMandelbrotView.m, pour y écrire la fonction:

Complexe_t Carre(Complexe_t z)
{
    Complexe_t carre;

    carre.reel = z.reel*z.reel - z.imag*z.imag;
    carre.imag = 2.0 * z.reel * z.imag;

    return carre;
}

Génération et affichage

Nous allons maintenant écrire la méthode -drawRect:. Cette méthode est appelée à chaque fois qu'il faut rafraîchir la vue. En pratique, dans ce premier programme, elle ne sera appelée que lors du premier affichage.

Création de la bitmap

Les bitmaps sont représentées par la classe NSBitmapRep. Créons une instance et initialisons-là:

NSBitmapImageRep* bitmapRep = [[NSBitmapImageRep alloc]
    initWithBitmapDataPlanes:NULL
    pixelsWide:400
    pixelsHigh:300
    bitsPerSample:8
    samplesPerPixel:1
    hasAlpha:NO
    isPlanar:NO
    colorSpaceName:NSDeviceWhiteColorSpace
    bytesPerRow:0
    bitsPerPixel:8];

Voilà une méthode avec beaucoup de paramètres !

  • Le premier paramètre est mis à NULL. Nous voulons que la méthode réserve la mémoire pour la bitmap elle-même.
  • pixelsWide et pixelsHigh sont les dimensions de la bitmap, en pixels.
  • samplesPerPixel (échantillons/pixel)
    Un échantillon est une composante de la couleur. Par exemple, une image RVB possède trois échantillons: un Rouge, un Bleu et un Vert. Comme nous générons une image en niveaux de gris, il n'y a qu'un échantillon par pixel.
  • bitsPerSample (bits/échantillon)
    Nous générons une image en 256 niveau de gris, ce qui requiert 8 bits/échantillon.
  • hasAlpha
    Indique si l'image possède une couche alpha (opacité). Dans notre cas, non.
  • isPlanar
    Il est possible de gérer plusieurs couches de couleurs séparément (une couche rouge, une verte, une bleu, une alpha). Pas dans notre cas.
  • colorSpaceName (nom de l'espace de couleur)
    Nous utilisons l'espace de couleur NSDeviceWhiteColorSpace, qui nous permet d'avoir la valeur maximale blanche. Vous pourrez essayer avec NSDeviceBlackColorSpace si vous voulez.
  • bytesPerRow (octets par ligne) Nous mettons à 0 pour que la méthode calcule cette valeur elle-même en fonction des autres paramètres.
  • bitsPerPixel
    C'est toujours 8 bits/pixel.

Calcul de l'ensemble

// Parcourir tous les points de la bitmap
double x, y;
for(x = 0; x < 400; x++)
{
    for(y = 0; y < 300; y++)
    {

Nous parcourons tous les pixels de la bitmap.

        // Convertir les coordonnées
        Complexe_t c;
        c.reel =  x/100.0 - 2;
        c.imag = -y/100.0 + 1.5;

Nous convertissons les coordonnées x et y du repère de la bitmap vers celui du plan mathématique.

        // Initialiser z[0]
        Complexe_t z = {0.0, 0.0};

Le calcul de chaque pixel commence par la mise à zéro de c.

        NSUInteger n;
        for(n=0; n < MAX_ITERATIONS; n++)
        {
            // z[n+1] = z[n+1]^2 + c
            Complexe_t zCarre = Carre(z);
            z.reel = zCarre.reel + c.reel;
            z.imag = zCarre.imag + c.imag;

            // La suite diverge si |z| > 2
            double moduleAuCarre = z.reel*z.reel + z.imag*z.imag;
            if(moduleAuCarre > 4.0)
                break;
        }

Nous recherchons ensuite combien il faut d'itérations pour que la suite diverge. Remarquez une optimisation ici: au lieu de tester si le module
√(a^2 + b^2) > 2
nous testons si
(a^2 + b^2) > 4

une racine carrée étant un calcul très coûteux en temps machine. Le code reste toutefois peu efficace: son amélioration est prévue dans un prochain article. La priorité est ici donnée à sa lisibilité.

        // Donner le niveau de gris au pixel
        NSUInteger nuance = n * 255 / MAX_ITERATIONS;

J'ai remanié la formule donnée dans l'article précédent. La division est faite en dernier, ce qui permet de faire tous les calculs sur des entiers (ce qui évite les casts et accélère un peu).

        [bitmapRep setPixel:&nuance atX:x y:y];
    }
}

Et enfin, nous écrivons la nuance dans la bitmap. Vous remarquerez que le premier paramètre de setPixel: est un tableau d'entiers. Étant donné que nous travaillons avec un seul échantillon par pixel, le tableau n'a qu'un seul indice, et il revient donc au même de passer un pointeur sur nuance.

Affichage

Affichons la bitmap dans la vue:

// Afficher la bitmap
[bitmapRep drawAtPoint:NSMakePoint(0,0)];

Rappelez-vous que l'origine de la vue se trouve dans le coin inférieur gauche.

Et finalement, il ne faut pas oublier de libérer la mémoire:

[bitmapRep release];

Le résultat

Lancez le programme. La fenêtre doit s'ouvrir avec l'ensemble dessiné.

L'ensemble généré

C'est beau !

La suite

Essayez différentes valeurs de MAX_ITERATIONS. Le rendu sera différent. Nous ajouterons bientôt des contrôles pour changer les paramètres du calcul.

Pour l'instant, la nature fractale de l'ensemble ne saute pas aux yeux, mais en observant attentivement, on retrouve tout de même un peu la forme de la "tête" sur les deux "ailes".
Une des améliorations à apporter sera justement la possibilité de se déplacer dans le plan, en particulier, de zoomer.

Le projet XCode complet à télécharger.

Renaud Pradenc
Céroce.com