一,先堆一下概念:
Mandelbrot集合是在复平面上组成分形的点的集合,它正是以数学家Mandelbrot命名。
Mandelbrot集合可以用复二次多项式。
从数学上来讲,Mandelbrot集合是一个复数的集合。一个给定的复数c或者属于Mandelbrot集合M,或者不属于。比如,取c = 1,那么这个序列就是(0, 1, 2, 5, 26, …),显然它的值会趋于无穷大;而如果取c = i,那么序列就是(0, i, -1+i, -i, -1+i, -i,…),它的值会一直停留在有限半径的圆盘内。
事实上,一个点属于Mandelbrot集合当且仅当它对应的序列(由上面的二项式定义)中的任何元素的模都不大于2。这里的2就是上面提到的“有限半径”。
参考:http://www.cnblogs.com/anderslly/archive/2008/10/10/mandelbrot-set-by-fsharp.html
二,曼德布罗特集合有什么实际意义?
这里就要提出分形的概念了。
数学上认为分形有以下几个特点:
1. 具有无限精细的结构;
2. 比例自相似性;
3. 一般它的分数维大于它的拓扑维数;
4. 可以由非常简单的方法定义,并由递归、迭代产生。
分形(Fractal)一词,是曼德勃罗创造出来的,其原意是不规则、支离破碎的意思,所以分形几何学是一门以非规则几何形态为研究对象的几何学。按照分形几何学的观点,一切复杂对象虽然看似杂乱无章,但他们具有相似性,简单地说,就是把复杂对象的某个局部进行放大,其形态和复杂程度与整体相似。
分形往往由递归、迭代产生,但是我们在纸上做出的图只能作有限次的递归、迭代。所以,下面的代码,绘图的核心就是一个递归。
有兴趣可以去搜索一下:麦田圈密码;谢尔宾斯基地毯;科契雪花曲线。
三,代码实现(Java版)
package Mandelbrot;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Timer;
import java.util.TimerTask;
public class Mandelbrot extends JApplet {
Display canvas; // Display:每个swt程序在最开始都必须创建一个Display对象。它负责swt和操作系统之间的通信。
JButton stopButton, startButton; // 启动按钮被按下时计算将开始,直到它完成计算或用户按下“停止”按钮。
JTextField jtfTime; //定义一个文本框
public void init() {
// 初始化程序通过创建 canvas and button并将它们添加到applet的内容窗格。
this.setSize(900, 600); //把窗口设置的稍微大一点好看
setBackground(Color.gray);//背景设置为灰色
canvas = new Display();//创建一个Display对象
getContentPane().add(canvas, BorderLayout.CENTER); //在窗口面板canvas上面添加控件,添加在布局的中间CENTER
JPanel bottom = new JPanel(); //创建button面板
JPanel bottom_up = new JPanel(); //创建bottom_up面板
bottom.setBackground(Color.gray);//设置button背景色为灰色
startButton = new JButton("Start");//创建一个名字为startButton的按钮控件
startButton.addActionListener(canvas); //注册事件监听器,作用是将startButton的事件处理交给canvas对象去处理
bottom.add(startButton); //Jpanel类的实例化对象bottom上 添加 JButton类的实例化对象startButton;在窗口面板上添加控件
stopButton = new JButton("Stop");
stopButton.addActionListener(canvas);
bottom.add(stopButton);
stopButton.setEnabled(false); //stopButton初始化时候设置为不可操作
JLabel label_left = new JLabel("computation time is:");
bottom_up.add(label_left);
jtfTime = new JTextField(" ");
jtfTime.setEnabled(false);
jtfTime.setSize(15, 15);
bottom_up.add(jtfTime);
JLabel label_right = new JLabel("seconds");
bottom_up.add(label_right);
getContentPane().add(bottom, BorderLayout.NORTH);//在窗口面板bottom上面添加控件,添加在布局的上边NORTH。
getContentPane().add(bottom_up, BorderLayout.SOUTH);//在窗口面板bottom_up上面添加控件,添加在布局的下边SOUTH。
} // end init();
/*调用此方法时由系统applet将暂时或永久停止。画布停止计算线程,如果它正在运行。*/
public void stop() {
canvas.stopRunning();
}
/*下面的嵌套类代表了applet的绘图层并做所有的工作*/
class Display extends JPanel implements ActionListener, Runnable {
Image OSI; // 抽象类Image是所有表示图形图像类的父类。图像保存了曼德尔勃特集合的照片。这是复制到绘图表面,如果它存在。它是由计算的线程创建的。
Graphics OSG; // Graphics类提供基本绘图方法。在Image OSI 上draw的图形上下文。
Thread runner; // 计算线程的声明
boolean running; // running的布尔类型声明
double xmin = -2.5; // The ranges of x and y coordinates that
double xmax = 1; // are represented by this drawing surface
double ymin = -1.25;
double ymax = 1.25;
//由系统调用图显示面。如果离屏图像存在,则把离屏图像复制到屏幕上。如果离屏图像不存在,它只把表面画成黑色的。
public void paintComponent(Graphics g) {
if (OSI == null) {
g.setColor(Color.black); //【设置前景色】的方法是属于【Graphics】的,即设置Graphics的绘图色。语法为:g.setColor(Color对象);
g.fillRect(0,0,getWidth(),getHeight()); //fillRect(int x,int y,int width,int height):是【用预定的颜色填充一个矩形】,得到一个着色的矩形块。
}
else {
g.drawImage(OSI,0,0,null);
}
}
//当用户单击“starting”或“ stopping”按钮时将调用此函数。它通过starting or stopping响应动画。
public void actionPerformed(ActionEvent evt) {
String command = evt.getActionCommand(); //为了避免冲突,给同一个JFrame里每个按钮设置不同的ActionCommand,监听时用这个做条件区分事件,以做不同的响应。
if (command.equals("Start"))
{
startRunning();
time_clock();
}
else if (command.equals("Stop"))
stopRunning();
}
void startRunning() {
if (running)
return;
runner = new Thread(this); // 创建一个线程,该线程将在this显示类中执行run()方法。
running = true;
runner.start();
}
/*做一个定时器,计算绘图的时间*/
void time_clock()
{
Timer timer = new Timer();
timer.schedule(new MyTask(), 0, 1000);
}
class MyTask extends TimerTask
{
int i = 0;
@Override
public void run()
{
i++;
String s = String.valueOf(i);
jtfTime.setText(s);
if( running == false)
{
jtfTime.setText("0 ");
i = 0;
}
}
}
void stopRunning()
{ // 停止计算线程的方法。是通过设置变量running的值来完成的。【线程定期检查这个值】,当running为false时将终止运行。
running = false;
}
//算出迭代计数的值count,以便后面用。
int countIterations(double x, double y)
{
//曼德尔勃特集合在while循环结束前,根据迭代的数量,通过每一个点(x,y)上着色来表示。
//曼德尔勃特的集合点,实际上是,或非常接近它,计数将达到最大值80.这些点将变成紫色。所有其他颜色代表点绝对不是集合点。
int count = 0;
double zx = x;
double zy = y;
while (count < 80 && Math.abs(x) < 100 && Math.abs(zy) < 100) {
double new_zx = zx*zx - zy*zy + x;
zy = 2*zx*zy + y;
zx = new_zx;
count++;
}
return count;
}
int i,j; // 一个正方形的中心像素需要绘画。 这些变量是设置在Display类的run()方法中,在run()方法中用于painter对象的。这同样适用于接下来的两个变量。
int size; // 要画的正方形的大小
int colorIndex; //1和80之间的数字,用于决定正方形的颜色。
Runnable painter = new Runnable() {
// Runnable对象的工作是:画一个square在离屏canvas,然后复制square到屏幕上。
//当run方法被调用时候它将这样运行。square上的数据是由前面的四个变量决定的。
public void run() {
int left = i - size/2;
int top = j - size/2;
OSG.setColor( Color.getHSBColor(colorIndex/100.0F,1F,1F) );
OSG.fillRect(left,top,size,size);
paintImmediately(left,top,size,size);
}
};
public void run()
{ //这是计算线程的run方法。它在一连串增加分辨率的方式下,绘出曼德尔勃特集合。在每一次,它填充applet、给方块着色来代表了曼德尔勃特集合。squares的大小在每次通过减少一半。
startButton.setEnabled(false); // Disable "Start" button
stopButton.setEnabled(true); // and enable "Stop" button
// while thread is running.
int width = getWidth(); // Current size of this canvas.
int height = getHeight();
OSI = createImage(getWidth(),getHeight()); //getWidth、getHeight():得到当前层的宽度与高度。
// Create the off-screen image where the picture will
// be stored, and fill it with black to start.
OSG = OSI.getGraphics(); //可以理解为在OSI上绘画
OSG.setColor(Color.black);
OSG.fillRect(0,0,width,height);
for (size = 64; size >= 1 && running; size = size/2) {
//外循环执行一遍,填充图像与给定大小的方块。这是给定的像素大小。注意如果running为false,则所有循环立即结束。
double dx,dy; // 实际坐标轴的方块尺寸
dx = (xmax - xmin)/width * size;
dy = (ymax - ymin)/height * size;
double x = xmin + dx/2; // x-坐标 of center of square.
for (i = size/2; i < width+size/2 && running; i += size) {
// First nested for loop draws one column of squares.第一个嵌套循环了一列的方块。
double y = ymax - dy/2; // y-坐标 of center of square
for (j = size/2; j < height+size/2 && running; j += size) {
//内层for循环了一个方块,通过计算迭代确定应该是什么颜色,然后调用"painter"对象画出实际的square。
colorIndex = countIterations(x,y); //调用countIterations函数
try {
SwingUtilities.invokeAndWait(painter); //将对象排到事件派发线程的队列中
}
catch (Exception e) {
}
y -= dy;
}
x += dx;
Thread.yield(); // Give other threads a chance to run.
}
}
running = false; // 线程即将结束,因为计算已完成或因为running已设置为false。 在前一种情况下,我们必须设置 running= false来表明线程不再运行。
startButton.setEnabled(true); // Reset states of buttons.
stopButton.setEnabled(false);
} // end run()
} // end nested class Display
} // end class Mandelbrot