六、Java程序代码
本节给出Java程序代码(JavaScript版本见下节)。目前这个程序运行后只能绘出一个全红的800*600的png格式图像,并存在c:盘的temp文件夹下,名为”image.png”。如果你的c:盘下还没有temp文件夹,就请先创建一下。
七、JavaScript程序代码
要使用HTML内嵌JavaScript程序的朋友则请将下列代码拷贝粘贴到纯文本编辑器中(比如Windows下的Notepad),并存为html文件,可将其命名为“mandelbrot.html”,然后用网络浏览器打开。你应该能看到显示有一个红色的长方形的页面。
八、语言相关部分
首先我们解决掉和所使用的计算机语言密切相关,所以在不同语言之间写法很不同的部分。这些部分和Mandelbrot集合的绘制算法其实没什么关系,属于相应语言中的特定知识,我在此只稍微提一下,不作详细解释。
首先是特定的声明,包括Java程序中“// (1)可调参数”前面的那些行,以及HTML文件中不包含在
<script>和</script>之间段落内(即JavaScript程序部分)的那些行,大多属于“如果懂这门语言就必定懂为什么这么写”的内容,不多做解释。除了HTML中的这句
这是可以看作是创建了一块画布挂在网页上,画布的标识号“id”为“image”。我们将要画出来的图像,就是要显示在这块画布上。
然后是两个版本也都有的“(4)程序入口”部分,程序开始运行时就从这里进入,注意到JavaScript版本的入口在JavaScript程序部分的最上方,而Java版本则是从main()函数开始。和具体绘图相关的则是第(3)、(9)、(10)部分。
“(3)图象缓存”部分都是定义了一个图像缓存。两种语言的绘图方式都必须先在缓存中进行,绘完以后才一次性地用“(10)保存图像”中的saveImage()函数表现出来。Java版本的saveImage()函数中的语句
是将图像保存在“c:\temp”文件夹下的image.png文件中,如果把上面语句中的两个“png”都改成“jpg”,那么存成的文件就是jpeg格式的。大家可以按自己喜好决定。而JavaScript版本的saveImage()是将图像缓存内容表现在刚才说到的标识号为“image”的画布上,从而让我们在浏览器上看到画出的图像。
“(9)在图像像素(col, row)处画上颜色rgb”则就是在第四节中提到的点彩画法,呼叫drawColor(col, row, rgb)函数就能在图像缓存的第col列第row行那个像素上点上颜色代码为rgb的颜色。关于以整数表示的RGB颜色代码,我们在谈论调色盘时会作较具体的介绍。特别值得提一句的是,如果仔细看程序代码的话,在两个版本中,当我们宣称是在第row行画点时,我们其实都是在图像缓存的第IMAGEHEIGHT – row – 1行画点。
这是因为在数学习惯上,Y轴的方向朝上,越往上纵坐标越大;而在计算机绘图中,Y轴的方向通常则朝下。所以如果我们真的是在图像缓存的第row行画点的话,画出来的Mandelbrot集合图像会是上下颠倒的。
上述两个版本的特定声明部分,程序入口的第(4)部分以及和具体绘图相关的第(3)、(9)、(10)部分一直到本文结束都不会再变动,我也不再加解释。
九、绘图算法部分
剩下的(1)、(2)、(5)、(6)、(7)、(8)部分则是和Mandelbrot集合的绘制具体相关部分,如果对比两个版本,可以发现它们很相似。只是由于两种语言的对数据类型的处理方式不同,在Java语言中必须显式地说明每个变量,每个函数的参数以及返回值的数据类型(int,double等),而在JavaScript语言中则只须作var和function声明。相信有一定的计算机编程经验,但没有学过这两种语言的读者也容易看懂。
在“ (1)可调参数”部分定义了7个参数,以Java版本为例:
前两个参数先略过,接下去4个参数定义了一个区域坐标,而最后的IMAGEWIDTH则定义了要绘制的图像的宽度即横向像素数。读者可以在任何时候尝试改变这些参数,尤其是试用本文许多插图下的区域坐标定义,来验证是否能绘出和插图一致的区域的图像。
“ (2)计算所得参数”中则计算了一些通过在(1)中的参数所计算得到的参数,比如所绘图像高度,每个像素所代表的在复平面上的正方形的边长等等。COFFSET和ROFFSET被用在下面要介绍的“(6)计算颜色”函数中,它们的计算公式也许对不熟悉这样写法的读者来说有点奇怪:
可其实这无非是在说,如果IMAGEWIDTH是偶数(IMAGEWIDTH % 2等于0的话),那么COFFSET的值就是(IMAGEWIDTH / 2) – 0.5;如果IMAGEWIDTH是奇数,那么COFFSET的值就是
IMAGEWIDTH / 2,但因为这是整数除法,要去掉余数,所以结果其实和(IMAGEWIDTH-1) / 2
相同。
“(5)主程序”部分就是第四节中介绍的点彩绘图原理。两个针对行和列的循环遍历图片上的每个像素,循环体则是两句无比简单的呼叫:
第一句用我们将介绍的(6)中的calcColor()函数来计算每个像素的颜色,再用前面介绍的(9)
drawColor()方法在图像缓存的对应像素上画出计算出来的颜色。两个循环执行后,所有像素都绘制完毕,最后呼叫(10)saveImage()来表现图像。
上面提到的(1)、(2)、(5)部分,除了会变动可调参数的数值外,一直到本文结束也不再改变。
本文中我们将要重大修改的则是剩下的(6)、(7)、(8)部分,当然也是关键。不过目前看起来它们也简单得很。仍以Java版本为例。
首先计算了两个值cx和cy,它们其实就是第col列第row行那个像素所代表的复平面上的正方形的中心点的坐标,计算中用到了前面计算的参数COFFSET、ROFFSET和PIXELSIZE。“有WIDTH长度的线段,其中心点坐标为OX。线段被均匀分成IMAGEWIDTH段,试问第col段的中心点cx是多少?”这是个非常简单的数学问题,我不作详细推导了,只需注意“第n段”的n是从0开始算的,最左边的是第0段。
计算出像素中心点在复平面上的坐标后,我们将用这个坐标代表的复数进行Mandelbrot集合定义中的迭代运算,即呼叫(7)中的iter()函数,然后调用(8)中的调色盘函数来取得对应的颜色。也就是说,这个像素就被它的中心点代表了。
不过目前(7)和(8)中没什么具体的东西,无论cx和cy是什么,最终(6)总是返回十六进制数FF0000,这是红色的颜色代码,所以绘出的图是一片通红。
枯燥的准备工作终于做完,下面我们将逐步填充和修改(6)、(7)、(8)部分,以达到我们绘制Mandelbrot集合图像的目的。注意到上面两个版本的程序的长度都大约是80行,为达到最终程序只有150行的目标,我们只能再填充70行程序。
(待续)
原文链接:http://www.jianshu.com/p/a7e59209f2f0