# code by Griffith for LLumen.Neocities.org from multiprocessing import Pool from PIL import Image import datetime import re # default constants FOREGROUND = (10,95,145)#(170, 200, 255) BACKGROUND = (255,255,255)#(40, 40, 55) # HD resolution by default WIDTH = 1920 HEIGHT = 1080 THREADS = 8 # 1 = single threaded # mandelbrot value calculator class MandelGen: # configuration of the view # resolution (pixels) width=400 height=400 zoom=0.4 # location to center on x=-0.8 y=0.0 # formula settings max_steps=150 escape_radius=2.1 # number of parallel processes # works best at threads = cpu cores threads=8 box=True # the output list data = [] # calculates the value of a single pixel def calcPoint(self, n): # locate the pixel x1 = n % self.width y1 = round(n / self.width) # convert to coordinates near the set a = (x1 / self.width - 0.5) / self.zoom # x and y mapped b = (y1 / self.height - 0.5) / self.zoom # to center a /= self.height / self.width # fix aspect ratio a += self.x # center on the set location b += self.y # marks the area from the inset if (self.box): boxa0 = (-0.5 / 40 / self.height * self.width) - 0.725 boxa1 = (0.5 / 40 / self.height * self.width) - 0.725 boxb0 = -0.5 / 40 - 0.24 boxb1 = 0.5 / 40 - 0.24 if a > boxa0 and a < boxa1 and b > boxb0 and b < boxb1: return 0.0 c = complex(a, b) # covert to complex for computation z = c out = 1.0 # iterate the formula for i in range(self.max_steps): z = z * z + c # mandelbrot formula az = abs(z) # orbit trap around origin for coloring inside the set out = min(az * 2, out) if (az > self.escape_radius): # escape iteration for coloring outside the set out = pow(i / self.max_steps, 0.5) break # print a percentage finished every 10% if (y1 % int(self.height / 10) == 0 and x1 == self.width - 1): print(str(round(y1 / self.height * 100)) + "% finished") return out # manages and created a parallel pool for calculating pixels def start(self): p = Pool(processes=self.threads) self.data = p.map(self.calcPoint, range(self.width * self.height)) p.close() # mixes two colors based on a percentage value def mix(percent, detailColor=(255,255,255), backColor=(0,0,0)): outColor = [0,0,0] for n in range(3): d = detailColor[n] b = backColor[n] outColor[n] = int(d * percent + b * (1 - percent)) return tuple(outColor) gen = MandelGen() # set default values gen.width = WIDTH gen.height = HEIGHT gen.threads = THREADS gen.x = -1.0 foreC = FOREGROUND backC = BACKGROUND name = "mandel_threaded" # query input and validate for values # invalid input is ignored; defaults are used temp = input("What resolution? (1=HD, 2=4k...)\t") if (temp != "" and re.search("^\d*\.?\d+$", temp) != None): gen.width = int(WIDTH * float(temp)) gen.height = int(HEIGHT * float(temp)) temp = input("What color for the detail? (0-255,0-255,0-255)\t") if (temp != ""): l = temp.replace("(", "", 1).replace(")", "", 1).split(",") i = [int(n) for n in l] foreC = tuple(i) temp = input("What color for the background? (0-255,0-255,0-255)\t") if (temp != ""): l = temp.replace("(", "", 1).replace(")", "", 1).split(",") i = [int(n) for n in l] backC = tuple(i) temp = input("What should the file be named? (Caution: no overwrite warning!)\t") if (temp != ""): name = temp # start the timer startTime = datetime.datetime.now() # generate the mandelbrot values gen.start() print("Computing colors") # parallel compatible mix function def newMix(n): return mix(n, foreC, backC) # convert the floats from the generation to colors cp = Pool(processes=8) colors = cp.map(newMix, gen.data) cp.close() print("Generating inset") inset = MandelGen() inset.width = int(gen.width * .38) inset.height = int(gen.height * .38) inset.x = -0.725 inset.y = -0.24 inset.zoom = 40 inset.max_steps = 200 inset.box = False inset.start() print("Inserting inset") offset = int(gen.width * .02) for x in range(inset.width + 2): for y in range(inset.height + 2): if (x == 0 or y ==0 or x > inset.width or y > inset.height): colors[x + offset + (y + offset) * int(gen.width)] = tuple([255,255,255]) else: color = mix(inset.data[(x - 1) + (y - 1) * inset.width], foreC, backC) colors[x + offset + (y + offset) * int(gen.width)] = tuple(color) print("Saving image") export = Image.new("RGB", (gen.width, gen.height)) export.putdata(colors) export.save(name + ".png") # export.save(name + ".jpeg", progressive=True, quality=90) # stop timer deltaTime = datetime.datetime.now() - startTime print("Time required: " + str(deltaTime))