Draw Julia set animations by simply defining a 2d line though the complex plane.
A Julia set is simply a recursive function or iterative process that takes the form z=z^2+c
where the initial value of z is a complex number representing a point in 2d space, and c is any complex.
The z argument is updated until the desired number of iterations, or until it’s magnitude exceeds 2, if it exceeds 2 then we know it will eventually trend to infinity and it’s in the set.
A general definition may look something like this:
def julia(z,c,iterations=10):#z and c are both complex numbers (they may be solely imaginary or real but usually a mixture of the two) if abs(z)>2: #if the magnitude of z is greater than 2, the point is outside the julia set return False elif iterations<=0: #If the desired amount of iterations have elapsed and z still doesn't have a magnitude of at least 2 then that point can be considered in the set. return True else: #If iterations still remain and the magnitude of z is under 2, the recursively call the julia set function again, with z replaced by z squared + c and iterations replaced with iterations-1. return julia(z**2+c,c,iterations-1)
This function is then mapped to a (usually 2d Euclidean) space, represented as complex numbers (x+yi), ,and it’s features can be zoomed/scaled to reveal more details indefinitely.
This mapping in its simplest form may look like this:
def complexgraph(func,minX=-1,minY=-1,maxX=1,maxY=1): resolution=(1080,720) xdifference=maxX-minX ydifference=maxY-minY out= for i in range(resolution): row= for j in range(resolution): x=minX+xdifference/resolution*i y=minY+ydifference/resolution*i z=x+y*1j #in python the imaginary number is j (it's an electricians convention where I already means current) row.append(func(z)) out.append(row) return out #With the Julia set called as such complexgraph(lambda z:julia(z,.62-.75j)) #This will make the julia set c=.62-.75j
My current definition for animated Julia sets is as follows
from __future__ import division from PIL import Image,ImageDraw def julia(b,c,d,iterations=30): c=c+d*1j def inner(x,y): z=x+y*1j for i in range(iterations): z=z**b+c absol=abs(z) if absol<2: return (60+(absol%2)*50,64+(absol%2)*40,(64+(absol**1.1)%2)*90) return (absol*260,absol*80,absol*140) return inner def drawfunc(f,resolution=(1080,720),view=(-1.2,-.7,1.2,.7),frame=0): resolution=(int(resolution),int(resolution)) im=Image.new("RGB",resolution,(0,0,0)) for i in range(resolution): for j in range(resolution): x=view+(view-view)*i/resolution y=view+(view-view)*j/resolution col=f(x,y) col=tuple([int(min(255,max(0,i))) for i in col]) im.putpixel((i,j),col) im.save("A%04d.png"%frame) def midpoint(a,b,r): return a+(b-a)*r def drawpath(b1,b2,c1,c2,d1,d2,resolution=(1080,720),iterations=6,steps=10000): for frame in range(2000,3000): r=frame/steps drawfunc(julia(midpoint(b1,b2,r),midpoint(c1,c2,r),midpoint(d1,d2,r)),resolution=resolution,frame=frame) drawpath(2,2,-.8,-1.2,.4,-.2)
While quite short it can produce an endless array of intricate patterns and pairing with a kaleidoscope can force symmetry and produce amazing shapes.
Here are a few renders I’ve made throughout the years: