自绘Mandelbrot集合(三)

作者: e282256515ce | 来源:发表于2017-03-10 06:27 被阅读206次

    六、Java程序代码

    本节给出Java程序代码(JavaScript版本见下节)。目前这个程序运行后只能绘出一个全红的800*600的png格式图像,并存在c:盘的temp文件夹下,名为"image.png"。如果你的c:盘下还没有temp文件夹,就请先创建一下。

    package mandelbrot;
    
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    import javax.imageio.ImageIO;
    
    public class Mandelbrot {
        // (1)可调参数
        double ESCAPERADIUS = 4.0;
        int MAXITERNUMBER = 1000;
    
        // -0.743030 + 0.126433i @ 0.016110 /0.75
        double OX = -0.743030;
        double OY = 0.126433;
        double WIDTH = 0.016110;
        double RATIO = 0.75;
    
        int IMAGEWIDTH = 800;
    
        // (2)计算所得参数
        int IMAGEHEIGHT = (int) (IMAGEWIDTH * RATIO);
        double PIXELSIZE = WIDTH / IMAGEWIDTH;
        double COFFSET = IMAGEWIDTH % 2 == 0 ? (IMAGEWIDTH / 2) - 0.5 : (IMAGEWIDTH / 2);
        double ROFFSET = IMAGEHEIGHT % 2 == 0 ? (IMAGEHEIGHT / 2) - 0.5 : (IMAGEHEIGHT / 2);
    
        // (3)图象缓存
        BufferedImage image = new BufferedImage(IMAGEWIDTH, IMAGEHEIGHT, BufferedImage.TYPE_INT_RGB);
    
        // (4)程序入口
        public static void main(String[] args) {
            new Mandelbrot().draw();
        }
    
        // (5)主程序
        void draw() {
            for (int row = 0; row < IMAGEHEIGHT; row++) {
                for (int col = 0; col < IMAGEWIDTH; col++) {
                    int color = calcColor(col, row);
                    drawColor(col, row, color);
                }
            }
    
            saveImage();
        }
    
        // (6)计算颜色
        int calcColor(int col, int row) {
            double cx = (col - COFFSET) * PIXELSIZE + OX;
            double cy = (row - ROFFSET) * PIXELSIZE + OY;
            double d = iter(cx, cy);
            return getColor(d);
        }
    
        // (7)迭代计算
        double iter(double cx, double cy) {
            return 0;
        }
    
        // (8)调色盘
        int getColor(double d) {
            return 0xFF0000;
        }
    
        // (9)在图像像素(col, row)处画上颜色rgb
        void drawColor(int col, int row, int rgb) {
            image.setRGB(col, IMAGEHEIGHT - row - 1, rgb);
        }
    
        // (10)保存图像
        void saveImage() {
            try {
                ImageIO.write(image, "png", new File("c:\\temp\\image.png"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    七、JavaScript程序代码

    要使用HTML内嵌JavaScript程序的朋友则请将下列代码拷贝粘贴到纯文本编辑器中(比如Windows下的Notepad),并存为html文件,可将其命名为“mandelbrot.html”,然后用网络浏览器打开。你应该能看到显示有一个红色的长方形的页面。

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Mandelbrot</title>
    <script>
    // (4)程序入口
    window.onload = function() {
        // (1)可调参数
        var ESCAPERADIUS = 4.0;
        var MAXITERNUMBER = 1000;
    
        // -0.743030 + 0.126433i @ 0.016110 /0.75
        var OX = -0.743030;
        var OY = 0.126433;
        var WIDTH = 0.016110;
        var RATIO = 0.75;
    
        var IMAGEWIDTH = 800;
        
        // (2)计算所得参数
        var IMAGEHEIGHT = IMAGEWIDTH * RATIO;
        var PIXELSIZE = WIDTH / IMAGEWIDTH;
        var COFFSET = IMAGEWIDTH % 2 == 0 ? (IMAGEWIDTH / 2) - 0.5 : (IMAGEWIDTH / 2);
        var ROFFSET = IMAGEHEIGHT % 2 == 0 ? (IMAGEHEIGHT / 2) - 0.5 : (IMAGEHEIGHT / 2);
        
        // (3)图象缓存
        var canvas = document.getElementById("image"); 
        var context = canvas.getContext("2d");
        canvas.width = IMAGEWIDTH;
        canvas.height = IMAGEHEIGHT;
        var imagedata = context.createImageData(IMAGEWIDTH, IMAGEHEIGHT);
        
        // (5)主程序
        for (row = 0; row < IMAGEHEIGHT; row++) {
            for (col = 0; col < IMAGEWIDTH; col++) {
                var color = calcColor(col, row);
                drawColor(col, row, color);
            }
        }
        
        saveImage();
    
        // (6)计算颜色
        function calcColor(col, row) {
            var cx = (col - COFFSET) * PIXELSIZE + OX;
            var cy = (row - ROFFSET) * PIXELSIZE + OY;
            var d = iter(cx, cy);
            return getColor(d);
        }
    
        // (7)迭代计算
        function iter(cx, cy) {
            return 0;
        }
        
        // (8)调色盘
        function getColor(d) {
            return 0xFF0000;
        }
        
        // (9)在图像像素(col, row)处画上颜色rgb
        function drawColor(col, row, rgb) {
            var pindex = ((IMAGEHEIGHT - row - 1) * IMAGEWIDTH + col) * 4;
            imagedata.data[pindex] = (rgb>>16) & 0xFF;
            imagedata.data[pindex+1] = (rgb>>8) & 0xFF;
            imagedata.data[pindex+2] = rgb & 0xFF;
            imagedata.data[pindex+3] = 255;
        }
        
           // (10)保存图像
        function saveImage() {
            context.putImageData(imagedata, 0, 0);
        }
    };
    
    </script>
    </head>
    <body>
    <canvas id="image" width="0" height="0"></canvas>
    </body>
    </html> 
    

    八、语言相关部分

    首先我们解决掉和所使用的计算机语言密切相关,所以在不同语言之间写法很不同的部分。这些部分和Mandelbrot集合的绘制算法其实没什么关系,属于相应语言中的特定知识,我在此只稍微提一下,不作详细解释。

    首先是特定的声明,包括Java程序中“// (1)可调参数”前面的那些行,以及HTML文件中不包含在<script></script>之间段落内(即JavaScript程序部分)的那些行,大多属于“如果懂这门语言就必定懂为什么这么写”的内容,不多做解释。除了HTML中的这句

    <canvas id="image" width="0" height="0"></canvas>
    

    这是可以看作是创建了一块画布挂在网页上,画布的标识号“id”为“image”。我们将要画出来的图像,就是要显示在这块画布上。

    然后是两个版本也都有的“(4)程序入口”部分,程序开始运行时就从这里进入,注意到JavaScript版本的入口在JavaScript程序部分的最上方,而Java版本则是从main()函数开始。

    和具体绘图相关的则是第(3)、(9)、(10)部分。

    “(3)图象缓存”部分都是定义了一个图像缓存。两种语言的绘图方式都必须先在缓存中进行,绘完以后才一次性地用“(10)保存图像”中的saveImage()函数表现出来。Java版本的saveImage()函数中的语句

        ImageIO.write(image, "png", new File("c:\\temp\\image.png"));
    

    是将图像保存在“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行画点。

        image.setRGB(col, IMAGEHEIGHT - row - 1, rgb);
    

    这是因为在数学习惯上,Y轴的方向朝上,越往上纵坐标越大;而在计算机绘图中,Y轴的方向通常则朝下。所以如果我们真的是在图像缓存的第row行画点的话,画出来的Mandelbrot集合图像会是上下颠倒的。

    上述两个版本的特定声明部分,程序入口的第(4)部分以及和具体绘图相关的第(3)、(9)、(10)部分一直到本文结束都不会再变动,我也不再加解释。

    九、绘图算法部分

    剩下的(1)、(2)、(5)、(6)、(7)、(8)部分则是和Mandelbrot集合的绘制具体相关部分,如果对比两个版本,可以发现它们很相似。只是由于两种语言的对数据类型的处理方式不同,在Java语言中必须显式地说明每个变量,每个函数的参数以及返回值的数据类型(intdouble等),而在JavaScript语言中则只须作varfunction声明。相信有一定的计算机编程经验,但没有学过这两种语言的读者也容易看懂。

    在“ (1)可调参数”部分定义了7个参数,以Java版本为例:

        // (1)可调参数
        var ESCAPERADIUS = 4.0;
        var MAXITERNUMBER = 1000;
    
        // -0.743030 + 0.126433i @ 0.016110 /0.75
        double OX = -0.743030;
        double OY = 0.126433;
        double WIDTH = 0.016110;
        double RATIO = 0.75;
    
        var IMAGEWIDTH = 800;
    

    前两个参数先略过,接下去4个参数定义了一个区域坐标,而最后的IMAGEWIDTH则定义了要绘制的图像的宽度即横向像素数。读者可以在任何时候尝试改变这些参数,尤其是试用本文许多插图下的区域坐标定义,来验证是否能绘出和插图一致的区域的图像。

    “ (2)计算所得参数”中则计算了一些通过在(1)中的参数所计算得到的参数,比如所绘图像高度,每个像素所代表的在复平面上的正方形的边长等等。COFFSET和ROFFSET被用在下面要介绍的“(6)计算颜色”函数中,它们的计算公式也许对不熟悉这样写法的读者来说有点奇怪:

    double COFFSET = IMAGEWIDTH % 2 == 0 ? (IMAGEWIDTH / 2) - 0.5 : (IMAGEWIDTH / 2);
    

    可其实这无非是在说,如果IMAGEWIDTH是偶数(IMAGEWIDTH % 2等于0的话),那么COFFSET的值就是(IMAGEWIDTH / 2) - 0.5;如果IMAGEWIDTH是奇数,那么COFFSET的值就是IMAGEWIDTH / 2,但因为这是整数除法,要去掉余数,所以结果其实和(IMAGEWIDTH-1) / 2相同。

    “(5)主程序”部分就是第四节中介绍的点彩绘图原理。两个针对行和列的循环遍历图片上的每个像素,循环体则是两句无比简单的呼叫:

        int color = calcColor(col, row);
        drawColor(col, row, color);
    

    第一句用我们将介绍的(6)中的calcColor()函数来计算每个像素的颜色,再用前面介绍的(9)drawColor()方法在图像缓存的对应像素上画出计算出来的颜色。两个循环执行后,所有像素都绘制完毕,最后呼叫(10)saveImage()来表现图像。

    上面提到的(1)、(2)、(5)部分,除了会变动可调参数的数值外,一直到本文结束也不再改变。

    本文中我们将要重大修改的则是剩下的(6)、(7)、(8)部分,当然也是关键。不过目前看起来它们也简单得很。仍以Java版本为例。

        // (6)计算颜色
        int calcColor(int col, int row) {
            double cx = (col - COFFSET) * PIXELSIZE + OX;
            double cy = (row - ROFFSET) * PIXELSIZE + OY;
            double d = iter(cx, cy);
            return getColor(d);
        }
    

    首先计算了两个值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行程序。

    (待续)

    相关文章

      网友评论

        本文标题:自绘Mandelbrot集合(三)

        本文链接:https://www.haomeiwen.com/subject/rgavgttx.html