Zoom sur la figure

Relatif à la position de la souris

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Salut,

Je viens de lire le petit tuto sur l'ensemble de Mandelbrot, qui est vraiment sympa. J'ai donc voulu implémenter l'algo proposé en C++, avec l'aide de la SFML ; pas de souci jusqu'à là, j'arrive à dessiner la figure.

J'ai ensuite voulu rajouter le zoom. Mais comme je veux les choses bien faite, j'ai voulu faire un zoom relatif à la position de la souris. C'est à dire que le programme commence à un niveau de zoom et une taille de fenêtre qui permettent de voire la fractale en entier, puis, quand l'utilisateur faire tourner sa molette, on zoome (zoom x2) en centrant la vue sur le curseur (sans modifier la taille de la fenêtre, ofc).

Mais, je ne sais pas comment implémenter ça.

Avant tout, je vous montre le code :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <iostream>
#include <SFML/Graphics.hpp>

struct Param
{
   float x1;
   float x2;
  float y1;
   float y2;

  float zoom;

   int max_iter;
};

sf::Image drawManderbolt(Param p);
Param zoomIn(Param current, int mouseX, int mouseY, int w, int h);

int main()
{
   Param currentParameter;
       currentParameter.x1 = -2.1;
       currentParameter.x2 = 0.6;
       currentParameter.y1 = -1.2;
       currentParameter.y2 = 1.2;
       currentParameter.zoom = 400;
       currentParameter.max_iter = 50;


   sf::Image image = drawManderbolt(currentParameter);

   sf::RenderWindow window(sf::VideoMode(image.getSize().x, image.getSize().y), "Manderbolt");

   // Chargement de l'image dans une texture
   sf::Texture texture;
   texture.loadFromImage(image);

   // Puis de la texture dans un sprite
   sf::Sprite sprite;
   sprite.setTexture(texture);

   while(window.isOpen())
   {
       sf::Event event;
       while(window.pollEvent(event))
       {
           if(event.type == sf::Event::Closed)
           {
               window.close();
           }

           if(event.type == sf::Event::MouseWheelMoved)
           {
               int delta, x, y = 0;
               delta = event.mouseWheel.delta;
               x = sf::Mouse::getPosition(window).x;
               y = sf::Mouse::getPosition(window).y;
               // C'est pour debug, pataper pls, je sais c'est pas bien
               std::cout << delta << " : " << x << " : " << y << std::endl;

               currentParameter = zoomIn(currentParameter, x, y, image.getSize().x, image.getSize().y);
               image = drawManderbolt(currentParameter);
               texture.loadFromImage(image);
               sprite.setTexture(texture);

           }

       }

       window.clear();

       // Affichage de l'image
       window.draw(sprite);

       window.display();
   }



   return EXIT_SUCCESS;
}

sf::Image drawManderbolt(Param p)
{
   int imageX = (p.x2 - p.x1) * p.zoom;
   int imageY = (p.y2 - p.y1) * p.zoom;

   sf::Image image;
   image.create(imageX, imageY, sf::Color::Black);

   for(int x = 0; x < imageX; ++x)
   {
       for(int y = 0; y < imageY; ++y)
       {
           float c_r = x / p.zoom + p.x1;
           float c_i = y / p.zoom + p.y1;
           float z_r = 0;
           float z_i = 0;

           int i = 0;

           do
           {
               float tmp = z_r;
               z_r = z_r * z_r - z_i * z_i + c_r;
               z_i = 2 * z_i * tmp + c_i;
               i++;
           }while(z_r * z_r + z_i * z_i < 4 && i < p.max_iter);

           if(i == p.max_iter)
           {
               image.setPixel(x, y, sf::Color::White);
           }
           else
           {
               image.setPixel(x, y, sf::Color(0, 0, i * 255 / p.max_iter));
           }
       }
   }

   return image;
}



Param zoomIn(Param current, int mouseX, int mouseY, int w, int h)
{
   Param newParameter;

   newParameter.zoom = current.zoom * 2;

   // Coordonnées de la sourie dans le "vrai" repère
   int X = current.x1 + mouseX * (current.x2 - current.x1) / w;
   int Y = current.y1 + mouseY * (current.y2 - current.y1) / h;

   newParameter.x1 = X - (current.x2 - current.x1) / 4;
   newParameter.x2 = X + (current.x2 - current.x1) / 4;
   newParameter.y1 = Y - (current.y2 - current.y1) / 4;
   newParameter.y2 = Y + (current.y2 - current.y1) / 4;

   newParameter.max_iter = current.max_iter * 2;
   // Pour debug aussi, pataper encore ^^
   std::cout << newParameter.x1 << " : " << newParameter.x2 << " : " << newParameter.y1 << " : " << |newParameter.y2 << std::endl;

   return newParameter;
}

//zoomOut en préparation...
//Param zoomOut(Param current, int mouseX, int mouseY, int w, int h)

(Ah, les joies des balises sur 150 lignes…)

Donc, le problème, c'est le changement de repère. Je récupère les coordonnées de la position de ma souris au pixel près dans le repère dont l'origine se situe en haut à gauche de l'écran, avec 1 pixel = 1 unité. Or le repère de la figure est totalement différent, pas la même échelle et il n'est même pas centré sur le centre de la fenêtre. J'essaie donc de transposer les coords de la souris dans le repère de la fractale, puis d'effectuer laborieusement le zoom dessus. Or je n'y arrive pas. Dans le code ci-dessus on voit ma dernière tentative, infructueuse.

Un petit peu d'aide, please ? :)

Merci beaucoup

PS: Premier thread, woohoo !

PPS: Pour prouver que ça marche, quand même (si on omet le zoom), une petite image cadeau!

Édité par Tycoon

+0 -0
Auteur du sujet

J'étais en prévisualisation, et il me semble qu'il n'y avait pas la barre de raccourci ! Enfin, peu importe.

En fait mon problème c'est que je ne sais pas du tout comment m'y prendre pour pouvoir zoomer sur la figure à l'endroit où est le curseur. En gros, j'ai une liste de paramètre pour afficher la fractale, et je veux qu'en appelant "zoomIn", ces paramètres soient modifiés, de façon à ce que le centre de la nouvelle figure corresponde à l'endroit où se trouvait le curseur avant.

+0 -0

J'ai du mal à voir d'où viens ton problème. Le code qui transforme les anciens paramètres en nouveaux me semble correcte.

Que se passe-t-il exactement lorsque tu essayes de zoomer? Qu'est-ce que tu obtiens dans la console pour le debug?

Autrement, je ne connais pas assez le C++ pour pouvoir dire, mais est-ce que le fait que la même variable (struct) soit utilisé en paramètre et en retour à la ligne 61 ne risque pas de faire des choses étranges?

currentParameter = zoomIn(currentParameter, x, y, image.getSize().x, image.getSize().y);

+0 -0
Auteur du sujet

Pfff, je suis stupide. Ma formule est bonne, en effet, seulement j'utilisais des int au lieu de float. Du coup pour X et Y je me retrouvais avec des (-1, 0), (1, 0), etc, à cause de la troncature des coordonnées… Maintenant, ça marche bien ! :)

Autrement, je ne connais pas assez le C++ pour pouvoir dire, mais est-ce que le fait que la même variable (struct) soit utilisé en paramètre et en retour à la ligne 61 ne risque pas de faire des choses étranges?

currentParameter = zoomIn(currentParameter, x, y, image.getSize().x, image.getSize().y);

Berdes

1
 i = i + 1

est tout à fait légal, mon cas est à peu près pareil, aucune raison que ça ne marche pas !

Il ne me reste plus qu'à correctement implémenter le zoomOut (là ça marche pas…), et c'est bon !

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <iostream>
#include <SFML/Graphics.hpp>
#include <ctime>
#include <string>
#include <sstream>

struct Param
{
    float x1;
    float x2;
    float y1;
    float y2;

    float zoom;

    int max_iter;
};

sf::Image drawManderbolt(Param p);
Param zoomIn(Param current, int mouseX, int mouseY, int w, int h);
Param zoomOut(Param current, int mouseX, int mouseY, int w, int h);

int main()
{


    Param currentParameter;
        currentParameter.x1 = -2.1;
        currentParameter.x2 = 0.6;
        currentParameter.y1 = -1.2;
        currentParameter.y2 = 1.2;
        currentParameter.zoom = 400;
        currentParameter.max_iter = 50;


    clock_t chrono;
    sf::Image image = drawManderbolt(currentParameter);
    chrono = clock();
    std::cout << "Initial calculation time (x" << currentParameter.zoom << ", " << currentParameter.max_iter << " iter) :" << (double) chrono/CLOCKS_PER_SEC << " s" << std::endl;


    sf::RenderWindow window(sf::VideoMode(image.getSize().x, image.getSize().y), "Mandelbrot");

    // Chargement de l'image dans une texture
    sf::Texture texture;
    texture.loadFromImage(image);

    // Puis de la texture dans un sprite
    sf::Sprite sprite;
    sprite.setTexture(texture);

    while(window.isOpen())
    {
        sf::Event event;
        while(window.pollEvent(event))
        {
            if(event.type == sf::Event::Closed)
            {
                window.close();
            }

            if(event.type == sf::Event::MouseWheelMoved)
            {
                int delta, x, y = 0;
                delta = event.mouseWheel.delta;
                x = sf::Mouse::getPosition(window).x;
                y = sf::Mouse::getPosition(window).y;

                if(delta == 1)
                {
                    currentParameter = zoomIn(currentParameter, x, y, image.getSize().x, image.getSize().y);
                }
                else if(delta == -1)
                {
                    currentParameter = zoomOut(currentParameter, x, y, image.getSize().x, image.getSize().y);
                }
                else
                {

                }

                image = drawManderbolt(currentParameter);
                texture.loadFromImage(image);
                sprite.setTexture(texture);

            }

        }

        window.clear();

        // Affichage de l'image
        window.draw(sprite);

        window.display();
    }



    return EXIT_SUCCESS;
}

sf::Image drawManderbolt(Param p)
{
    int imageX = (p.x2 - p.x1) * p.zoom;
    int imageY = (p.y2 - p.y1) * p.zoom;

    sf::Image image;
    image.create(imageX, imageY, sf::Color::Black);

    float c_r = 0, c_i = 0,
          z_r = 0, z_i = 0;
    int i = 0;

    for(int x = 0; x < imageX; ++x)
    {
        for(int y = 0; y < imageY; ++y)
        {
            c_r = x / p.zoom + p.x1;
            c_i = y / p.zoom + p.y1;
            z_r = 0;
            z_i = 0;

            i = 0;

            do
            {
                float tmp = z_r;
                z_r = z_r * z_r - z_i * z_i + c_r;
                z_i = 2 * z_i * tmp + c_i;
                i++;
            }while(z_r * z_r + z_i * z_i < 4 && i < p.max_iter);

            if(i == p.max_iter)
            {
                image.setPixel(x, y, sf::Color::White);
            }

            else
            {
                image.setPixel(x, y, sf::Color(0, 0, i * 255 / p.max_iter));
            }

        }
    }

    return image;
}



Param zoomIn(Param current, int mouseX, int mouseY, int w, int h)
{
    Param newParameter;

    newParameter.zoom = current.zoom * 2;

    // Coordonnées de la sourie dans le "vrai" repère
    float X = current.x1 + mouseX * (current.x2 - current.x1) / w;
    float Y = current.y1 + mouseY * (current.y2 - current.y1) / h;

    newParameter.x1 = X - (current.x2 - current.x1) / 4;
    newParameter.x2 = X + (current.x2 - current.x1) / 4;
    newParameter.y1 = Y - (current.y2 - current.y1) / 4;
    newParameter.y2 = Y + (current.y2 - current.y1) / 4;

    newParameter.max_iter = current.max_iter * 2;

    std::cout << "Zoom level: " << newParameter.zoom << std::endl;

    return newParameter;
}

Param zoomOut(Param current, int mouseX, int mouseY, int w, int h)
{
    Param newParameter;

    newParameter.zoom = current.zoom / 2;

    // Coordonnées de la sourie dans le "vrai" repère
    float X = current.x1 + mouseX * (current.x2 - current.x1) / w;
    float Y = current.y1 + mouseY * (current.y2 - current.y1) / h;

    newParameter.x1 = X - (current.x2 - current.x1) * 4;
    newParameter.x2 = X + (current.x2 - current.x1) * 4;
    newParameter.y1 = Y - (current.y2 - current.y1) * 4;
    newParameter.y2 = Y + (current.y2 - current.y1) * 4;

    newParameter.max_iter = current.max_iter / 2;

    std::cout << newParameter.x1 << " : " << newParameter.x2 << " : " << newParameter.y1 << " : " << newParameter.y2 << std::endl;

    return newParameter;
}
+0 -0

Cette réponse a aidé l'auteur du sujet

C'est surtout le fait que ce soit un struct qui me faisait douter.

Pour le zoomOut, il y a un problème quand tu définis newParameter.* : tu multiplies par 4 alors que (current.x2 - current.x1) suffit.

Autrement, il n'y a pas besoin de multiplier le nombre d'itération par 2 quand tu zoom d'un facteur 2. Je ne sais pas vraiment à quel point il faut augmenter pour avoir quelque chose de correcte, mais il se peut qu'un facteur 1.5 (ou moins) soit suffisant. Il faudra que tu fasses quelques tests pour savoir.

+0 -0
Auteur du sujet

Effectivement, merci ! Je n'avais pas trop réfléchie, et j'avais juste fait les opérations inverse de zoomIn, mais en prenant le temps d'y repenser, en effet le x4 est de trop.

Pour le max_iter, du fait que ce soit un int, ça m'embête un peu de multiplier par 1.5 : à cause des arrondies (enfin des troncatures je crois), si je démarre à 50, ça peut faire : 50 -in-> 75 -in-> 112 -in-> 168 -out-> 112 -out-> 74 -out-> 49 : au bout d'un moment on perd en précision. De plus, même si le gain de temps est très appréciable, on ressent assez le manque de précision au bout du 3 ou 4ème zoom, comparé à l'image de départ… Je vais voir comment arranger ça… Sinon je laisserai à 2, et tant pis pour les performances !

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte