Programs That Make Fractals

demos for beginners, by Hannah Leitheiser

back to projects

Fractals are great visuals in few lines of code. They may be more to mathmaticians, but I wouldn't know. For this page, I'm going to assume you know how fractals basically work. If not, you can probably find someone to explain them in a style that suits you elsewhere on the web.

I thought I might have stumbled on something with the animation, but I think I've seen the technique before. Well, it's an independent discovery, anyway.

Python

Python KIVY

Javascript

C

PHP

Java

LISP

Python3 with PIL

#!/usr/bin/python3

# fractal_pil.py - by Hannah Leitheiser
#    Description: Generates the mandlebrot set in red
#    saved in .png image format.
#    run: python3 fractal_pil.py (in bash)
#   requires: PIL library

# Output a .png file with a rendering of the Mandlebrot fractal.
from PIL import Image

imageSize = (1000,800)

def Mandlebrot(x, y, maxIterations=31):
	"""Returns 0 if the point x + iy is in the Mandlebrot set 
	   or the number of iterations of f(z) = z^2 + c 
	   before exiting bounds."""
	(c, z) = (complex(x, y), 0)
	for i in range(maxIterations):
		z = z**2 + c
		if z.imag ** 2 + z.real ** 2 > 4:
			return i+1
	return 0

image = Image.new('RGB', imageSize)
pixels = image.load()

for x in range(imageSize[0]):
	for y in range(imageSize[1]):
		# This involved some trial and error to center the graph.
		cordx = (x - imageSize[0]/2 - 0.3*imageSize[1])
		             / (imageSize[1]*0.5)
		cordy = (y - imageSize[1]/2) / (imageSize[1]*0.5)
		# We'll just vary the red.
		pixels[x,y] = (Mandlebrot(cordx, cordy)*8,0,0)

image.save('fractal.png')
image.close()

Python3 with kivy

Kivy is a nice environment for creating apps easily. Unfortuneately, for this it's quite slow.
#!/usr/bin/python3

# fractal_kivy.py - by Hannah Leitheiser
#    Description: Generates the mandlebrot set in red
#       In a user window or app.  User can drag the fractal
#       to reposition and double-tap to zoom.
#    run: python3 fractal_kivy.py (in bash)
#   requires: Kivy

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import AsyncImage
from array import array
from kivy.graphics.texture import Texture


def Mandlebrot(x, y, maxIterations=31):
	"""Returns 0 if the point x + iy is in the Mandlebrot set 
	   or the number of iterations of f(z) = z^2 + c 
	   before exiting bounds."""
	(c, z) = (complex(x, y), 0)
	for i in range(maxIterations):
		z = z**2 + c
		if z.imag ** 2 + z.real ** 2 > 4:
			return i+1
	return 0

class FractalWidget(Widget):


	def __init__(self, **kwargs):
		# make sure we aren't overriding any important functionality
		super(FractalWidget, self).__init__(**kwargs)
		self.offsetx = 0
		self.offsety = 0
		self.zoom=0.35
		self.bind(pos=self.update)
		self.bind(size=self.update)
		self.movable = True
		self.update()

	def update(self, *args):
		with self.canvas:
			# We're going to use a texture, which...works, I guess.
			self.texture = Texture.create(size=self.size)
			buf = []
			for y in range(self.height):
				for x in range(self.width):
					cordx = ((x - self.width/2 - self.offsetx) /
						(self.height*self.zoom))
					cordy = ((y - self.height/2 - self.offsety) /
						(self.height*self.zoom))
					buf.append( int(Mandlebrot(cordx, cordy)*8) )
					buf.append(0);
					buf.append(0);
			arr= array('B', buf)
			self.texture.blit_buffer(arr, colorfmt='rgb', bufferfmt='ubyte')
			Rectangle(texture=self.texture, pos=self.pos, size=self.size)
			self.movable = True

	def on_touch_down(self, touch):
		if self.collide_point(*touch.pos):
			if touch.is_double_tap and self.movable:
				self.zoom=self.zoom*2.0
				self.offsetx -= (touch.ox - self.width/2)
				self.offsety -= (touch.oy - self.height/2)
				self.offsetx*=2.0
				self.offsety*=2.0
				self.movable = False
				self.update()

	def on_touch_move(self, touch):
		if self.movable:
			# redraw the texture on the canvas as the user
			# slides it around.
			with self.canvas:
				Rectangle(texture=self.texture,
				      pos=( (touch.x-touch.ox),
				      (touch.y-touch.oy)), size=self.size)

	def on_touch_up(self, touch):
		# redraw the fractal with new offsets
		if self.movable:
			self.movable = False
			self.offsetx += (touch.x-touch.ox)
			self.offsety += (touch.y-touch.oy)
			self.update()



class FractalApp(App):

	def build(self):
		return FractalWidget()


if __name__ == '__main__':
	FractalApp().run()

Javascript

I'm actually surprised how fast it renders for a scripting language. Click to zoom, double-click to zoom out (sorry, it's a bit iffy on the double-clicks).
Your browser does not support the HTML5 canvas tag.
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="description" content="Programming Fractals">
  <meta name="keywords" content="JavaScript,Mandlebrot">
  <meta name="author" content="Hannah Leitheiser">
  <title>Javascript Fractal Explorer - Click to Zoom, Doubleclick to Zoom out</title>
</head>

<body>

<canvas id="fractalCanvas" width="600" height="600" 
	style="border:1px solid #000000;">
	Your browser does not support the HTML5 canvas tag.
	</canvas>

<script>

// Some variable for orienting the fractal.
var zoom = 2;
var offsetX = 0;
var offsetY = 0;

var canvas = document.getElementById("fractalCanvas");
var ctx = canvas.getContext("2d");
var width = canvas.clientWidth;
var height = canvas.clientHeight;

/* --------------------- mandlebrot(x, y) ----------------------------

   Returns 0 if the point x + iy is in the Mandlebrot set 
    or the number of iterations of f(z) = z^2 + c 
   before exiting bounds. */
function mandlebrot(x, y) {
	// People have made libraries for complex algebra
	// but the math is pretty simple.
	c_r = x;
	c_i = y;
	z_r = 0.0;
	z_i = 0.0;
	for(var i = 1 ; i < 33 ; i++) {
		z_r2 = (z_r * z_r) - (z_i * z_i) + c_r;
		z_i = (2 * z_r * z_i) + c_i;
		z_r = z_r2;
		if( ( (z_r * z_r) + (z_i * z_i)) > 4) {
			return i;
			}
		}
	return 0;
	}


/* ------------------------ draw() --------------------------------
   Draws the fractal on the canvas. */

function draw() {
	// Four byte array for each pixel, RGBA.
	var pixeldata = new Uint8ClampedArray( width *height * 4);
	for(var x = 0 ; x < width ; x++) {
		for(var y = 0 ; y < height ; y++) { 
			// We'll iterate up to 32 times, then multiply
			// that by 8 to get 0-255, for 8-bit color.
			var iterations = 
			   mandlebrot( zoom*(x-width/2 - offsetX) / height - 0.55, 
			   zoom * (y- height/2 - offsetY) / height);
			pixeldata[(y * width + x)*4] = iterations *8;
			// Make the pixel opaque.
			pixeldata[(y * width + x)*4 + 3] = 255;
			}
		}

	// Transfers the data array to the canvas context.
	var data = new ImageData( pixeldata, width, height );
	ctx.putImageData(data, 0,0);
	}

/* ---------------------- click handlers ------------------------ */


// These are for trying to prevent zooming on two clicks when the 
// user double-clicks.
var dblClickClearer;
// That is, a double-click is not happening.
var notDouble = true;

// Double click zooms out.
function zoom_out_fractal(e) {
	notDouble = false;
	zoom = zoom * 1.5;
	offsetX = offsetX / (1.5);
	offsetY = offsetY / (1.5);
	draw();
	// Used to keep double-clicks from being processed as clicks.
	clearTimeout(dblClickClearer);
	dblClickClearer = 
		setTimeout( function() { notDouble = true; }, 800);
	}

function zoom_fractal(e) {
	if(notDouble)
		setTimeout( function() {
			if(notDouble) {
				// Zoom and recenter on the clicked point.
				zoom = zoom / 1.5;
				offsetX = offsetX + (width/2) - 
				   e.clientX + canvas.getBoundingClientRect().left;
				offsetY = offsetY + (height/2) - 
				   e.clientY + canvas.getBoundingClientRect().top; ;
				offsetX = offsetX * 1.5
				offsetY = offsetY * 1.5
				draw(); 
				}
			}, 200);
	}


canvas.addEventListener( "dblclick",  zoom_out_fractal);
canvas.addEventListener( "click",  zoom_fractal)

draw();
</script>

</body>
</html>

C with libpng

The math is simple enough, but higher level problems such as generating graphics take more work.
/* 

fractal.c
 Hannah Leitheiser

Produces a fractal saved as fractal_c.png.

compile: gcc fractal.c -o frac_c -lpng -lm
dependencies: libpng and zlib
Ubuntu library installation: 
	sudo apt-get install libpng-dev
	sudo apt-get install zlib1g-dev

*/

#include <stdio.h>
#include <stdint.h>
#include <complex.h>
#include <malloc.h>
#include <png.h>

/* return 0 for points in the Mandlebrot set, 
	iteration count for those known to escape */
int mandlebrot(double complex c, int maxIterations) {
	double complex z = 0;
	for(unsigned int i = 0 ; i < maxIterations ; i++) {
		z = z * z + c;
		if(creal(z) * creal(z) + cimag(z) * cimag(z) > 4)
			return i+1;
		}
	return 0;
	}

int main(int argc, char *argv[]) {

	// Specify an output image size
	int width = 1000;
	int height = 800;

	/* Image handling is mostly copypaste from 
	   http://www.labbookpages.co.uk/software/imgProc/files/libPNG/makePNG.c,
	   just edited down a bit for less error control. */
	png_infop info_ptr = NULL;
	png_bytep row = NULL;
	FILE* fp = fopen("fractal_c.png", "wb");
	png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
			NULL, NULL, NULL);
	info_ptr = png_create_info_struct(png_ptr);
	png_init_io(png_ptr, fp);

	// Write header (8 bit colour depth)
	png_set_IHDR(png_ptr, info_ptr, width, height,
			8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
			PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

	// Set title
	png_text title_text;
		title_text.compression = PNG_TEXT_COMPRESSION_NONE;
		title_text.key = "Fractal";
		title_text.text = "Fractal";
		png_set_text(png_ptr, info_ptr, &title_text, 1);

	png_write_info(png_ptr, info_ptr);

	// Allocate memory for one row (3 bytes per pixel - RGB)
	row = (png_bytep) malloc(3 * width * sizeof(png_byte));

	// Write image data
	float scale = 2.0 / height;
	for (int y=0 ; y<height ; y++) {
		for (int x=0 ; x<width ; x++) {
			row[x*3]=(uint8_t)mandlebrot( (double)x * scale - 1.85 +
				(((double)y * scale - 1)*I), 32 ) * 8;
			}
		png_write_row(png_ptr, row);
		}

	// End write
	png_write_end(png_ptr, NULL);

	fclose(fp);
	png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
	png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
	free(row);

	return 0;
	}

PHP with gd library

Try it!
Creating a user interface will be left as an exercise for the reader.
<?php
// fractal.php - by Hannah Leitheiser
//    Description: Generates the mandlebrot set in red
//        saved in .png image format.
//    run: php moon.php > fractal.png (in bash)
//      (or put it in a website, I guess)
//    demonstration:
//       http://flower.web.runbox.net/fractal/fractal.php
//   requires: php's gd library for image processing
header('Content-Type: image/png');

/* return 0 for points in the Mandlebrot set, iteration count for those known to escape */
function Mandlebrot($x, $y) {

	/* PHP seems to lack native support for complex numbers, so we'll
	   just write the calculation using regular variables.
	   Stuff most of you will have learned in middle school, so I
	   won't try to explain it. */

	$c_r = $x;
	$c_i = $y;
	$z_r = 0.0;
	$z_i = 0.0;
	for($i = 1 ; $i < 33 ; $i++) {
		$z_r2 = ($z_r * $z_r) - ($z_i * $z_i) + $c_r;
		$z_i = (2 * $z_r * $z_i) + $c_i;
		// Not using the intermediate variable had some
		// interesting effects.
		$z_r = $z_r2;
		if( ( ($z_r * $z_r) + ($z_i * $z_i)) > 4) {
			return $i;
			}
		}
	return 0;
	}


$imageSizeX = 1000;
$imageSizeY = 800;

// Create the image.
$image = imagecreatetruecolor($imageSizeX,$imageSizeY);
$bkcolor = imagecolorallocate($image, 0, 0, 0);

/* PHP seems to have no easy way to simply set RGB of pixels.
   It wants to use pallets, so I have to set an array of 
   colors. */
$colors = array();
for($i = 0 ; $i < 33 ; $i++) {
	array_push($colors, imagecolorallocate($image, $i*8, 0, 0));
	}

// Now just set the color of each pixel.
for($x = 0 ; $x < $imageSizeX ; $x++) {
	for($y = 0 ; $y < $imageSizeY ; $y++) {
		imagesetpixel($image, $x,$y, $colors[  Mandlebrot( (float)$x/400.0 - 1.85, (float)$y/400.0-1) ] );
		}
	}

// And done.  Simple as that.
imagepng($image);
imagedestroy($image);
?>

Java for Android

It occurs to me, it would probably be good to sit back and try to create system for intrepreting user input when navigating the fractal. Two-fingered zooms, sliding, providing low-resolution instant feedback, then processing detail. Basically the same problem as navigating a map. Given it's a common issue, probably someone has standard code. Or at least a standard protocol.

/* FractalActivity.java, by Hannah Leitheiser (somewhat).  
   There are a lot of other files, but they were
   mostly a function of starting with a HelloWorld app.  
   Beyond a few text strings, this is all I changed.
   
   Draws the Mandlebrot set and allows you to zoom in by
   touching the canvas.
 */

/*
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.instantapps.samples.hello.feature;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;

import static android.graphics.Color.rgb;

/**
 * This Activity displays a simple hello world text and a button to open the GoodbyeActivity.
 * (and then I added stuff, so now it displays a fractal).
 */
public class FractalActivity extends AppCompatActivity {

    double zoom = 3;
    double offsetX = 0;
    double offsetY = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));


    }

    public class MyView extends View implements View.OnTouchListener
    {
        Paint paint = null;


        public MyView(Context context)
        {
            super(context);
            setOnTouchListener(this);
            paint = new Paint();
        }

        // Centers on where you touch, then zooms in; there's no going back.
        public boolean onTouch (View v, MotionEvent event) {
            zoom = zoom / 1.5;
            offsetX = offsetX - (event.getX() - ((getWidth()/2) + getLeft()));
            offsetY = offsetY - (event.getY() - ((getHeight()/2) + getTop()));
            offsetX = offsetX * 1.5;
            offsetY = offsetY * 1.5;
            v.invalidate();
            return false;
        }

        int mandlebrot(double x, double y) {
            double c_r = x;
            double c_i = y;
            double z_r = 0.0;
            double z_i = 0.0;
            for(int i = 1 ; i < 33 ; i++) {
                double z_r2 = (z_r * z_r) - (z_i * z_i) + c_r;
                z_i = (2 * z_r * z_i) + c_i;
                z_r = z_r2;
                if( ( (z_r * z_r) + (z_i * z_i)) > 4) {
                    return i;
                }
            }
            return 0;
        }


        @Override
        protected void onDraw(Canvas canvas)
        {
            super.onDraw(canvas);
            //canvas2 = canvas;
            int width = getWidth();
            int height = getHeight();

            /* Using a bitmap seems slightly faster than iterativly 
               setting pixels on the canvas.  Should be a shorter path to
               memory, I'd hope.  Sadly, on my Galaxy 5, this still runs
               pretty slow.  Too slow, I'd say, to be usable without coming
               up with some more clever way to render the fractal. */

            Bitmap bit;
            bit = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            int[] intArray = new int[width*height];
            for(int x = 0 ; x < width ; x++)
                for(int y = 0 ; y < height ; y++) {
                    int iterations =
                            mandlebrot( zoom*(x-width/2 - offsetX) / height - 0.55,
                                    zoom * (y- height/2 - offsetY) / height);
                    intArray[(x + y*width)] = rgb(iterations*8, 0, 0);
                }
            bit.setPixels( intArray, 0, width,0,0,width,height);
            canvas.drawBitmap(bit,0, 0, null);
        }
    }

}


EMACS LISP

Everybody loves LISP. Parenthesis. More Parenthesis. Almost looks graphical if you zoom until the terminal allows you to go no further.

; mandlebrot.el
;  emacs LISP
;  by Hannah Leithieiser
;  prints ASCII text of the Mandlebrot
;  	set

(defun mandlebrot(x y)
	"Pots the Mandelbrot set.  
	0 if inside, otherwise 1-7 if outside,
	higher numbers for more iterations before escape."

	; creating an internal recursive function
	(defun mand(x y i zr zi)
	(let ((zr2 zr)(zi2 zi))
	(if (> i 30) 0
		; if out of bounds,
		(if (> (+ (* zr2 zr2)
			  (* zi2 zi2)) 4)
			  ; return 1-7
			  (floor (* (+ i 1) 0.25))
			; otherwise call anothre recursion
			(mand
				x
				y
				(+ i 1)
				(+ (- (* zr2 zr2) (* zi2 zi2)) x)
				(+ (* 2 zr2 zi2) y)
				)
			)
		)))
	; call the function initially
	(mand x y 0 0 0)
	)

; 8 characters in order of darkness, by my guess
(setq chars '(" " "." ":" "-" "*" "%" "#" "ΒΆ") )

; 400 lines is pretty extreme, true
(dotimes (y 200)
	(dotimes (x 400)
		(princ (nth (mandlebrot
			(* (- x 300) 0.0060)
			(* (- y 100) 0.012))
			chars)))
	(princ "\n"))