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

16022009 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

Articles similaires