Mandelbrot Set

Working with complex numbers and iterating the function f_c(z) = z^2 + c, I created this program which lets you explore the depth of this self-repeating fractal. The different colors are caused by different color schemes.

Controls

  • Left mouse click and mouse movements control the zoom frame
  • Middle mouse click zooms in
  • Space takes a screenshot
  • F1 moves a zoom back
  • F2 zooms to default zoom

Mandelbrot set from afar [-6.75, -4.5] to [6.75, 4.5] An eye in the Mandelbrot set [-0.17466172461436666, -1.0720840874732396] to [-0.1746617232046618, -1.0720840865334365] A gap in the Mandelbrot set [-0.16101577503429365, -1.0369693644261544] to [-0.16019958847736634, -1.0364252400548697]Another gap in the Mandelbrot set [0.25, -0.04999999999999982] to [0.40000000000000036, 0.04999999999999982]A spiral in the Mandelbrot set [0.18276898469650216, -0.5821251321730682] to [0.18311316336591232, -0.5818956797267948]


# Python 2.7.7 Code
# Pygame 1.9.1 (for Python 2.7.7)
# Jonathan Frech  4th of December, 2015
#         edited  5th of December, 2015
#         edited 11th of December, 2015

# importing needed modules
import pygame, sys, time, math, os, random

""" COMPLEX NUMBER FUNCTIONS """
# get the absolute value (real number!) of a complex number
def complexAbsolute(Complex):
	real, imaginary = Complex[0], Complex[1]
	return math.sqrt( real**2 + imaginary**2 )

# multiply two complex numbers with each other
def complexMultiply(Complex1, Complex2):
	real1, imaginary1 = Complex1[0], Complex1[1]
	real2, imaginary2 = Complex2[0], Complex2[1]
	return [real1 * real2 - imaginary1 * imaginary2, real1 * imaginary2 + real1 * imaginary2]

# add two complex numbers together
def complexAdd(Complex1, Complex2):
	real1, imaginary1 = Complex1[0], Complex1[1]
	real2, imaginary2 = Complex2[0], Complex2[1]
	return [real1 + real2, imaginary1 + imaginary2]

""" GAME """
# game class
class GAME():
	# initialize program
	def __init__(self):
		self.WIDTH, self.HEIGHT = 1080, 720
		self.SIZE = [self.WIDTH, self.HEIGHT]
		self.SURF = pygame.Surface(self.SIZE)
		
		# scaling is set to 1
		self.SCALE = 1
		self.SCALEDSIZE = [self.WIDTH * self.SCALE, self.HEIGHT * self.SCALE]
		self.SCREEN = pygame.display.set_mode(self.SCALEDSIZE)

		self.TICKS = 0
		self.RUNNING = True

		self.history = []
		self.tl = [-6.75, -4.5]
		self.br = [6.75, 4.5]
		self.mouse1 = None
		self.mouse2 = None

		# create color scheme
		self.colors = []
		# white scheme
		#"""
		for _ in range(0, 256):
			self.colors.append([_, _, _])#"""

		# red-blue scheme
		"""
		for r in range(0, 16):
			for b in range(0, 16):
				self.colors.append( [255/16*r, 0, 255/16*b] )
		self.colors[-1] = [0, 0, 0]#"""

		# totally random scheme
		"""
		for _ in range(0, 256):
			self.colors.append([random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)])#"""

		# red-green scheme
		"""
		color1 = []
		for _ in range(0, 16):
			color1.append(255/16.*_)
		
		color2 = []
		for _ in range(0, 16):
			color2.append(255/16.*_)

		for c1 in color1:
			for c2 in color2:
				self.colors.append([c1, c2, 0])#"""

		"""for r in range(0, 16):
			for b in range(0, 16):
				self.colors.append( [70/16.*r, 0, 70/16.*b] )"""
		
		# randomly related colors
		"""
		self.colors.append([50, 50, 50])
		for _ in range(0, 255):
			c = self.colors[_]
			n = random.randint(0, 2)
			if random.randint(0, 1) == 0:
				c[n] += 10
				if c[n] > 255:
					c[n] = 255
			else:
				c[n] -= 10
				if c[n]  0:
			self.tl = self.history[-1][0]
			self.br = self.history[-1][1]
			self.history.pop(-1)
			self.redraw()
			self.caption()

	# reset game
	def reset(self):
		self.setcorners([-6.75, -4.5], [6.75, 4.5])
		self.lastpos = None
		self.redraw()
		self.caption()

	# redraw mandelbrot set
	def redraw(self):
		self.n = 0
		self.draw = True

	# update caption
	def caption(self):
		pygame.display.set_caption("Mandelbrot set from " + str(self.tl) + " to " + str(self.br))

	# tick function
	def tick(self):
		# handle events
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				quit()
			
			if event.type == pygame.KEYDOWN:
				# screenshot
				if event.key == pygame.K_SPACE:
					self.screenshot()

				# total reset
				if event.key == pygame.K_F2:
					self.reset()

				# move one step back
				if event.key == pygame.K_F1:
					self.moveback()

			# mouse up
			if event.type == pygame.MOUSEBUTTONUP:
				if event.button == 1:
					self.mouse2 = list( pygame.mouse.get_pos() )
					self.mouse1, self.mouse2 = self.fixmouse(self.mouse1, self.mouse2)

			# left clicking starts a new zoom frame
			# middle clicking zooms into said zoom frame
			if event.type == pygame.MOUSEBUTTONDOWN:
				if event.button == 1:
					self.mouse1 = list( pygame.mouse.get_pos() )
					self.mouse2 = None

				if event.button == 2 and self.mouse1 and self.mouse2 and self.mouse1[0] != self.mouse2[0] and self.mouse1[1] != self.mouse2[1]:
					# get positions
					p1 = self.mouse1[:]
					p2 = self.mouse2[:]

					# calculate
					xdif, ydif = float(self.br[0] - self.tl[0]), float(self.br[1] - self.tl[1])
				
					x = p1[0] / (self.WIDTH  / xdif) + self.tl[0]
					y = p1[1] / (self.HEIGHT / ydif) + self.tl[1]
					p1 = [x, y]

					x = p2[0] / (self.WIDTH  / xdif) + self.tl[0]
					y = p2[1] / (self.HEIGHT / ydif) + self.tl[1]
					p2 = [x, y]

					# update
					self.setcorners(p1, p2)
					self.caption()

					# reset
					self.redraw()
					self.lastpos = None

		# right mouse click moves zoom frame
		if pygame.mouse.get_pressed()[2]:
			if self.mouse1 and self.mouse2:
				xdif = self.mouse2[0] - self.mouse1[0]
				ydif = self.mouse2[1] - self.mouse1[1]
				
				self.mouse1 = list( pygame.mouse.get_pos() )
				self.mouse2[0] = self.mouse1[0] + xdif
				self.mouse2[1] = self.mouse1[1] + ydif

				self.mouse1[0] -= xdif / 2
				self.mouse1[1] -= ydif / 2
				self.mouse2[0] -= xdif / 2
				self.mouse2[1] -= ydif / 2

	# fixes the mouse positions
	def fixmouse(self, tl, br):
		# make sure, p1 is at the top left, p2 at the bottom right
		if tl[0] > br[0]:
			p = tl[0]
			tl[0] = br[0]
			br[0] = p
		if tl[1] > br[1]:
			p = tl[1]
			tl[1] = br[1]
			br[1] = p

		# fix ratio
		ratio = float(self.WIDTH) / float(self.HEIGHT)
		xdif = float(br[0] - tl[0])
		tl[1] = br[1] - (xdif/ratio)

		return tl, br

	# render function
	def render(self):
		# draw
		if self.draw:
			for _ in range(0, 3000):
				X, Y = self.n % self.WIDTH, self.n / self.WIDTH

				xdif, ydif = float(self.br[0] - self.tl[0]), float(self.br[1] - self.tl[1])
				x = X / (self.WIDTH  / xdif) + self.tl[0]
				y = Y / (self.HEIGHT / ydif) + self.tl[1]

				# calculate
				Complex = [x, y]
				Z = [0, 0]
				iterations = 0
			
				while complexAbsolute(Z) < 2 and iterations  self.WIDTH * self.HEIGHT:
				self.draw = False

		# blit
		self.SCREEN.blit(pygame.transform.scale(self.SURF, self.SCALEDSIZE), [0, 0])
		
		# zoom frame
		if self.mouse1:
			# get positions
			p1 = self.mouse1[:]
			if self.mouse2:
				p2 = self.mouse2[:]
			else:
				p2 = list( pygame.mouse.get_pos() )

			p1, p2 = self.fixmouse(p1, p2)
			
			# draw zoom frame
			pygame.draw.line(self.SCREEN, [255, 0, 0], [p1[0], p1[1]], [p1[0], p2[1]])
			pygame.draw.line(self.SCREEN, [255, 0, 0], [p1[0], p1[1]], [p2[0], p1[1]])
			pygame.draw.line(self.SCREEN, [255, 0, 0], [p2[0], p2[1]], [p2[0], p1[1]])
			pygame.draw.line(self.SCREEN, [255, 0, 0], [p2[0], p2[1]], [p1[0], p2[1]])

		# flip
		pygame.display.flip()
	
	# quits
	def quit(self):
		self.RUNNING = False

	# takes a screenshot
	def screenshot(self):
		path = os.getcwd() + "/out/"
		
		try:
			if not os.path.isdir(path):
				os.mkdir(path)
			
			name = "img" + str(len(os.listdir(path))) + " " + str(self.tl) + " to " + str(self.br) + ".png"
			pygame.image.save(self.SURF, path + name)
		except:
			pass

	# run function (uses tick() and render())
	def run(self):
		ticksPerSecond = 60
		lastTime = time.time() * 1000000000
		nsPerTick =  1000000000.0 / float(ticksPerSecond)
	
		ticks = 0
		frames = 0
	
		lastTimer = time.time() * 1000
		delta = 0.0
		
		while self.RUNNING:
			now = time.time() * 1000000000
			delta += float(now - lastTime) / float(nsPerTick)
			lastTime = now
			shouldRender = False
					
			while delta >= 1:
				ticks += 1
				self.TICKS += 1
				self.tick()
				delta -= 1
				shouldRender = True
			
			if shouldRender:
				frames += 1
				self.render()
			
			if time.time() * 1000 - lastTimer >= 1000:
				lastTimer += 1000
				
				# debug
				# print("Frames: " + str(frames) + ", ticks: " + str(ticks))
			
				frames = 0
				ticks = 0

# start game
GAME().run()
Advertisements

3 thoughts on “Mandelbrot Set

  1. Pingback: Mandelbrot Set II – J-Blog

  2. Pingback: Multibrot Set – J-Blog

  3. Pingback: Mandelbrot Set III – J-Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s