# MandelJulia.py # Stephen Timothy Cooney # Thanksgiving, 2009 (gobble gobble) # # Provides some simple functionality for generating # mandelbrot and julia set images. # # Built with Python 3.1.1 # # Classes: # CooneyImage: Provides a rgb 8bit per channel image interface. # Can save PPM P6 files. (Photoshop can open these) # Mandelbrot: Generates a mandelbrot image. # Julia: Generates a julia set, change julia.k (or pass it into prepare()) # to create unique images. # # See bottom for implementation. import io import math #defines a simple RGB image with 1 byte per channel (24 bits per pixel) class CooneyImage: #by default, a 0x0 image with an empty buffer def __init__(self): self.width = 0 self.height = 0 self.buffer = bytearray(0) #create an widthxheight buffer that can store rgb(8bits per channel) def CreateBuffer(self, width, height): self.width = int(width) self.height = int(height) self.buffer = bytearray(self.width * self.height * 3) #set a pixel at coord[0](x), coord[1](y) to color[0](r), color[1](g), color[1](b) def SetPixel(self, coord, color): self.buffer[(coord[0] + coord[1]*self.width)*3+0] = color[0] self.buffer[(coord[0] + coord[1]*self.width)*3+1] = color[1] self.buffer[(coord[0] + coord[1]*self.width)*3+2] = color[2] #save the image as a PPM P6 file (very simple) def SavePPM(self, filename): #generate strings for the header magicNumber = "P6\n" imageWidth = str(self.width) + "\n" imageHeight = str(self.height) + "\n" maxColor = "255\n" #open the file. #todo: note the lack of error checking FILE = io.open(filename, "wb") #write the header FILE.write(bytes(magicNumber, encoding="ascii")) FILE.write(bytes(imageWidth, encoding="ascii")) FILE.write(bytes(imageHeight, encoding="ascii")) FILE.write(bytes(maxColor, encoding="ascii")) #write the image data FILE.write(self.buffer) #just in case... writing a new line FILE.write(bytes("\n", encoding="ascii")) #close the file FILE.close() #Generates a mandelbrot image class Mandelbrot: # Doesn't actually run, however it sets # up enough so that if it is run, it will # work. def __init__(self): self.iterations = 30 center = (-0.75, 0) size = (3, 3) resolution = 128 self.Prepare(center, size, resolution) #Set up the class so it can make a Mandelbrot def Prepare(self, center, size, resolution): #size on coordinate system self.size = size self.aspectRatio = self.size[0]/self.size[1] #center on coordinate system self.center = center #describe the min/max (on coordinate system) self.min = (self.center[0]-self.size[0]/2.0, self.center[1]-self.size[1]/2.0) self.max = (self.center[0]+self.size[0]/2.0, self.center[1]+self.size[1]/2.0) #prepare the image self.resolution = resolution #pixels wide self.pixelSize = (int(self.resolution), int(self.resolution/self.aspectRatio)) self.image = CooneyImage() self.image.CreateBuffer(self.resolution, self.resolution/self.aspectRatio) #Generate the actual Mandelbrot. Way cool! def Go(self): #complex constant c c = [0,0] #loop through the imagninary(i) range for i in range(self.pixelSize[1]): #determine the imaginary component of c #C_Imaginary = the min of the coordinate system plus #the max of the coordinate system times the ratio #across the buffer that we are at. c[1] = self.min[1] + self.size[1] * (i/(self.pixelSize[1]-1)) #loop through the real(x) range for x in range(self.pixelSize[0]): #determine the real component of c #C_Real = the min of the coordinate system plus #the max of the coordinate system times the ratio #across the buffer that we are at. c[0] = self.min[0] + self.size[0] * (x/(self.pixelSize[0]-1)) #z... z = [0, 0] #loop a large number of times and determine if the #iterative function approaces infinity (greater than 2) #or not. isInside = True #assume inside until outside iterations = 0 distance = 0 for n in range(self.iterations): #iterative function on z #Zn+1 = Zn^2 + C zn = [0,0] zn[0] = z[0]*z[0] - z[1]*z[1] + c[0] zn[1] = 2*z[0]*z[1] + c[1] z[0] = zn[0] z[1] = zn[1] distance = z[0]*z[0] + z[1]*z[1] #if the distance from the func to the origin #is greater than 2 (or 4 in squared space) #then we can assume it's approaching infinity if distance > 4: isInside = False nIterations = n break #set the color of the current pixel depending on whether #or not the current pixel's iteration approaches infinity or not if isInside: #mark the current pixel (x, i) as inside distanceRatio = distance/4 distanceColorScalar = pow(1.0-distanceRatio, 32) self.image.SetPixel((x, i), (0,0,int(distanceColorScalar * 255))) else: #mark the current pixel (x, i) outside #draw a red halo outwards reflecting how long it took #to show that it's heading towards infinity self.image.SetPixel((x, i), (int(nIterations/self.iterations * 255),0,0)) class Julia: # Doesn't actually run, however it sets # up enough so that if it is run, it will # work. def __init__(self): self.iterations = 30 k = (0.353, 0.288) center = (0, 0) size = (3, 3) resolution = 128 self.Prepare(center, size, resolution, k) #Set up the class so it can make a Julia Set def Prepare(self, center, size, resolution, k): #set the complex constant for the julia self.k = k #size on coordinate system self.size = size self.aspectRatio = self.size[0]/self.size[1] #center on coordinate system self.center = center #describe the min/max (on coordinate system) self.min = (self.center[0]-self.size[0]/2.0, self.center[1]-self.size[1]/2.0) self.max = (self.center[0]+self.size[0]/2.0, self.center[1]+self.size[1]/2.0) #prepare the image self.resolution = resolution #pixels wide self.pixelSize = (int(self.resolution), int(self.resolution/self.aspectRatio)) self.image = CooneyImage() self.image.CreateBuffer(self.resolution, self.resolution/self.aspectRatio) #Generate the actual Julia. Way cool! def Go(self): #complex constant c c = [0,0] #loop through the imagninary(i) range for i in range(self.pixelSize[1]): #determine the imaginary component of c #C_Imaginary = the min of the coordinate system plus #the max of the coordinate system times the ratio #across the buffer that we are at. c[1] = self.min[1] + self.size[1] * (i/(self.pixelSize[1]-1)) #loop through the real(x) range for x in range(self.pixelSize[0]): #determine the real component of c #C_Real = the min of the coordinate system plus #the max of the coordinate system times the ratio #across the buffer that we are at. c[0] = self.min[0] + self.size[0] * (x/(self.pixelSize[0]-1)) #z... z = [0, 0] #loop a large number of times and determine if the #iterative function approaces infinity (greater than 2) #or not. isInside = True #assume inside until outside iterations = 0 distance = 0 for n in range(self.iterations): #iterative function on z #Zn+1 = Zn^2 + C zn = [0,0] if n == 0: zn[0] = c[0] zn[1] = c[1] else: zn[0] = z[0]*z[0] - z[1]*z[1] + self.k[0] zn[1] = 2*z[0]*z[1] + self.k[1] z[0] = zn[0] z[1] = zn[1] distance = z[0]*z[0] + z[1]*z[1] #if the distance from the func to the origin #is greater than 2 (or 4 in squared space) #then we can assume it's approaching infinity if distance > 4: isInside = False nIterations = n #store for coloration break #set the color of the current pixel depending on whether #or not the current pixel's iteration approaches infinity or not if isInside: #mark the current pixel (x, i) inside distanceRatio = distance/4 distanceColorScalar = pow(1.0-distanceRatio, 32) self.image.SetPixel((x, i), (0,0,int(distanceColorScalar * 255))) else: #mark the current pixel (x, i) outside #draw a red halo outwards reflecting how long it took #to show that it's heading towards infinity self.image.SetPixel((x, i), (int(nIterations/self.iterations * 255),0,0)) mandel = Mandelbrot() #for the most part, using the defaults mandel.Prepare(mandel.center,mandel.size,256) mandel.Go() mandel.image.SavePPM("Mandel.ppm") julia = Julia() #for the most part, using the defaults julia.Prepare(julia.center,julia.size,256,julia.k) julia.Go() julia.image.SavePPM("Julia.ppm")