📜 ⬆️ ⬇️

Mastering F #: building a colorful Mandelbrot set with navigation and integration with C #

opening speech


This article is intended for those who are at least a little familiar with the languages ​​C # and F #. In any case, I tried to make the code as readable as possible and give a description of each piece. You can familiarize yourself with the F # language in the following articles:

On Habré, many common words have already been written about the F # language - its history, origin, and features. I do not want to repeat myself, so I propose to go straight to the point. So, the action plan is as follows:
  1. Construction of the Mandelbrot set;
  2. Results visualization;
  3. Integration with C # and not only with it.
So let's go.


Construction of the Mandelbrot set


An example of building a Mandelbrot set in the F # language has already been considered on Habré , but we will take a slightly different approach and consider some points in more detail.

How is the Mandelbrot set defined?

Consider a sequence of numbers generated by the following equation:
C n + 1 = C n 2 + c 0
If such a sequence does not go beyond the two complex numbers C 1 (1, 1i) and C 2 (-1, -1i), then the complex number c 0 belongs to the Mandelbrot set. Therefore, the Mandelbrot set is the set of all such numbers c 0 , for which the sequence considered above remains within C 1 and C 2 .
')
Mandelbrot set and F #

To work with complex numbers in F # there is a library Microsoft.FSharp.Math, which, in turn, is available in the FsharpPowerPack package (you can also find the installation guide and other useful information about this package by clicking the link). Add this library to our project and specify it:

open Microsoft.FSharp.Math 

Now we can manipulate complex numbers in the program. Define the minimum and maximum boundaries:

 let cMax = complex 1.0 1.0 let cMin = complex -1.0 -1.0 

Let's go directly to the function. We implement the function of checking membership in the set as follows:

 let rec isInMandelbrotSet (z, c, iter, count) = if (cMin < z) && (z < cMax) && (count < iter) then isInMandelbrotSet ( ((z * z) + c), c, iter, (count + 1) ) else count 

The recursive isInMandelbrotSet function works until the number to be tested goes beyond cMax and cMin, and the recursion depth does not exceed the number of iterations. Upon completion, the function returns the number of perfect recursion steps (this will come in handy later when we “color” our set).

Results visualization


We have already built a lot, it remains only to display it. But here the fun begins.

Since complex numbers consist of two parts, we can create their mapping on a two-dimensional plane. The Mandelbrot set exists between C 1 (1, 1i) and C 2 (-1, -1i), therefore the coordinate system we need will have a center at the point (0, 0), the abscissa and ordinate axes will be limited to -1.0 and 1.0.
image

Therefore, we need to transfer the coordinates of the points from the complex plane to the one used in the construction of the image.

We do this as follows:

 let scalingFactor s = s * 1.0 / 200.0 let mapPlane (x, y, s, mx, my) = let fx = ((float x) * scalingFactor s) + mx let fy = ((float y) * scalingFactor s) + my complex fx fy 

This function will return the corresponding complex number for each point of the “familiar” plane. We will use the mx and my values ​​in the future to implement navigation in our image.

Now we can easily draw our entire set on the familiar plane. To do this, we need to “walk” through all the coordinates of the plane and check that each point (more precisely, the corresponding complex number) belongs to the Mandelbrot set. If the point belongs to the set, we will make it black, otherwise - we will color it in another color. To do this, we will make a special function “coloring”:

 let colorize c = let r = (4 * c) % 255 let g = (6 * c) % 255 let b = (8 * c) % 255 Color.FromArgb(r,g,b) 

The function will take the number of iterations we received from the isInMandelbrotSet function and determine the RGB color value. Numerical coefficients can be set any, but in order to achieve a smooth transition of colors, it is desirable to set them with a small difference. Also, here we need the System.Drawing library:

 open System.Drawing 

With the help of this library, we will create a new bitmap-image and will add points of a certain color to it. So, our drawing function will look like this:

 let createImage (s, mx, my, iter) = let image = new Bitmap(400, 400) for x = 0 to image.Width - 1 do for y = 0 to image.Height - 1 do let count = isInMandelbrotSet( Complex.Zero, (mapPlane (x, y, s, mx, my)), iter, 0) if count = iter then image.SetPixel(x,y, Color.Black) else image.SetPixel(x,y, colorize( count ) ) let temp = new Form() in temp.Paint.Add(fun e -> e.Graphics.DrawImage(image, 0, 0)) temp 

Here we use the System.Windows.Forms component:

 open System.Windows.Forms 

It remains only to run our program and enjoy the result. You can do this as follows:

 do Application.Run(createImage (1.5, -1.5, -1.5, 20)) 

We specify the initial parameters: scale, offset in X and Y, as well as the number of iterations. The greater the number of iterations, the more detailed our image will be (and, accordingly, the longer it will take to create). Something like this should get the result of the work:
image

So, the full listing of our program:

 #light open Microsoft.FSharp.Math open System open System.Drawing open System.Windows.Forms let cMax = complex 1.0 1.0 let cMin = complex -1.0 -1.0 let rec isInMandelbrotSet (z, c, iter, count) = if (cMin < z) && (z < cMax) && (count < iter) then isInMandelbrotSet ( ((z * z) + c), c, iter, (count + 1) ) else count let scalingFactor s = s * 1.0 / 200.0 let offsetX = -1.0 let offsetY = -1.0 let mapPlane (x, y, s, mx, my) = let fx = ((float x) * scalingFactor s) + mx let fy = ((float y) * scalingFactor s) + my complex fx fy let colorize c = let r = (4 * c) % 255 let g = (6 * c) % 255 let b = (8 * c) % 255 Color.FromArgb(r,g,b) let createImage (s, mx, my, iter) = let image = new Bitmap(400, 400) for x = 0 to image.Width - 1 do for y = 0 to image.Height - 1 do let count = isInMandelbrotSet( Complex.Zero, (mapPlane (x, y, s, mx, my)), iter, 0) if count = iter then image.SetPixel(x,y, Color.Black) else image.SetPixel(x,y, colorize( count ) ) let temp = new Form() in temp.Paint.Add(fun e -> e.Graphics.DrawImage(image, 0, 0)) temp do Application.Run(createImage (1.5, -1.5, -1.5, 20)) 


Subtotals

As you can see, it took us a little more than 40 lines to create a “color” Mandelbrot set and not so much time. But we cannot yet fully enjoy the beauty of fractal images (we can be more precise, but it is quite inconvenient to change the scale of the image before each compilation). To overcome this problem, it is necessary to add interface elements - navigation and zoom in / out keys, preferably changing the details.

Of course, you can create all interface elements inside F # just as we created the form itself using the System.Windows.Forms library. But on the other hand, it would be much more interesting (and, perhaps, more logical) to do it in the framework of a full-fledged Windows Forms application! Well, then we will do it now.

Integration with C # and not only with it


After building an F # application, we get a library at the output that contains all the functionality we need. For convenient access to the necessary functions of this library, we will declare a namespace in the F # code. This is done as follows: at the very beginning of the code we add

 module Fractal 

In addition, we no longer need the form, as well as the code that runs it. Therefore, now the function that we will call from the outside will look like this:

 let createImage (s, mx, my, iter) = let image = new Bitmap(400, 400) for x = 0 to image.Width - 1 do for y = 0 to image.Height - 1 do let count = isInMandelbrotSet( Complex.Zero, (mapPlane (x, y, s, mx, my)), iter, 0) if count = iter then image.SetPixel(x,y, Color.Black) else image.SetPixel(x,y, colorize( count ) ) image 

The function called createImage returns the generated bitmap-image containing the Mandelbrot set.

We use F # -library in Windows Forms

In fact, everything is very simple: you only need to add a ready-made library to a new project (for example, Windows Forms or any other). Having created a project in MS Visual Studio, you can do this by using the Add Reference command in the Solution Explorer window, selecting the necessary library. As we previously designated the namespace in the library ( module Fractal ), then in the new project all the functions of this library are available to us. Now you can call the function that generates the finished image as follows:

 Bitmap image = Fractal.createImage(1.5, -1.5, -1.5, 20); 

The resulting bitmap-image can already be used in any way - for example, add as a background to the element PictureBox.

Adding navigation elements

As you can see, we can “request” the generation of an image with different parameters: scale, number of iterations, offset relative to X and Y. So, it’s easy to add navigation. As a container for our image, we take a PictureBox, navigation is possible with the help of 6 buttons: zoom in / out, move up / down, left / right.

For convenience, we will create a separate FractalClass class that will store the state of the current fractal: scale, offsets from the center, level of approximation. The main class method will query the image of the set with the current parameters.

  private Bitmap Draw() { int iters = iterations + 2 * steps; return Fractal.createImage(currSc, currMvX, currMvY, iters); } 

The rest of the class methods will change the state of the fractal and call the Draw () method. So, for example, the approximation method might look like:

  public Bitmap ZoomIn() { scale = 0.9 * scale; zoomSteps++; return Draw(); } 

It remains only to add a call to the corresponding methods of the FractalClass class to the navigation key handling. The result will be approximately as follows:
image

The ability to transfer to the function the required number of iterations allows you to detail the fractal as it approaches. Therefore, with each step, the image becomes more and more interesting:
image

Not only Windows Forms

Just as we used the functions of the connected library in a Windows Forms application, we can use the ready-made library in any other .NET application: be it Silverlight, a web application, or anything else.

Materials used:

Thank you all for your attention, I hope it was interesting!

PS: request for errors / inaccuracies in the text to notify personal messages.

Source: https://habr.com/ru/post/116775/


All Articles