├── .gitignore ├── LICENSE ├── README.md ├── mandelbrot ├── calculate.py ├── colors.py ├── generate.py ├── generate_scheme.py ├── indicator │ └── indicator.ino └── main.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | results.txt 3 | __pycache__/ 4 | user_schemes.py 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ethan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # py-mandelbrot 2 | A simple mandelbrot zoom program made using python and pygame. Includes a variety of color schemes as well as a tool for creating your own. 3 | -------------------------------------------------------------------------------- /mandelbrot/calculate.py: -------------------------------------------------------------------------------- 1 | from numba import jit 2 | from serial import Serial 3 | from time import sleep, time 4 | import generate 5 | import os 6 | 7 | # Default values for calculations can be found in the 8 | # start_gen function in the main.py project file. 9 | 10 | serial_port = None 11 | # Serial Codes 12 | # 1 = Job Started 13 | # 2 = Calculation Set Completed 14 | # 3 = Job Completed 15 | 16 | @jit 17 | def calculate(x, y): # Self explanatory 18 | complex_value = complex(x, y) 19 | z = 0 20 | for i in range(128): # TODO: Change from hardcoded value 21 | z = z**2 + complex_value 22 | if z.real >= 2 or z.real <= -2: 23 | return i + 1 24 | return -1 25 | 26 | def find_points(width, height, min_x, max_x, min_y, max_y): 27 | """ 28 | Calls the calculate() function on each point. 29 | 30 | Paramters: 31 | width, height: dimensions of the image/complex plane 32 | min_x, min_y: the minimum x and yi values for the plane 33 | max_x, max_y: the maximum x and yi values for the plane 34 | """ 35 | 36 | # TODO: Fix arduino job status indicator 37 | results_txt = open('results.txt', 'w+') 38 | 39 | write_serial(b'1') 40 | totalJobs = width * height 41 | write_serial(bytes(str(totalJobs), encoding="utf-8")) 42 | 43 | x_step = (max_x - min_x) / width 44 | y_step = (max_y - min_y) / height 45 | 46 | current_point = [min_x, min_y] 47 | x_point = 1 48 | y_point = 1 49 | while y_point <= height: # TODO: Fix precision issues (Perturbation or store in array) 50 | row = [] 51 | while x_point <= width: 52 | point_result = calculate(*current_point) 53 | row.append(f"{point_result},{current_point[0]},{current_point[1]}") 54 | current_point[0] += x_step 55 | x_point += 1 56 | # TODO: Rewrite arduino progress indicator 57 | for point in row: 58 | results_txt.write(f"{point}\n") 59 | current_point[1] += y_step 60 | current_point[0] = min_x 61 | y_point += 1 62 | x_point = 1 63 | 64 | results_txt.close() 65 | write_serial(b'9') 66 | write_serial(b'x') 67 | generate.generate_image(int(width), int(height)) 68 | 69 | def write_serial(char_byte): 70 | """ 71 | Sends a byte to the specified serial port to be read 72 | by an arduino. 73 | 74 | Parameters: 75 | char_byte: the character to be sent, must be a bytes like object 76 | """ 77 | global serial_port 78 | if not serial_port: 79 | try: 80 | serial_port = Serial('/dev/ttyACM0', 9600) 81 | print("Found arduino") 82 | sleep(3) # Sleeps to give the arduino time to reset 83 | except: 84 | serial_port = 'x' 85 | print("Invalid port") 86 | if serial_port != 'x': 87 | serial_port.write(char_byte) 88 | -------------------------------------------------------------------------------- /mandelbrot/colors.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | # Uses RGB values 3 | # TODO: Add more color codes for more iterations 4 | # TODO: Add different color schemes that can be selected from settings GUI 5 | def random_colors(): 6 | result = {} 7 | result[-1] = (randint(0, 255), randint(0, 255), randint(0, 255)) 8 | for i in range(128): 9 | result[i+1] = (randint(0, 255), randint(0, 255), randint(0, 255)) 10 | return result 11 | 12 | purple_rainbow = { 13 | -1: (0, 0, 0), 14 | 1: (0, 0, 100), 15 | 2: (10, 0, 100), 16 | 3: (20, 0, 100), 17 | 4: (30, 0, 100), 18 | 5: (40, 0, 100), 19 | 6: (50, 0, 100), 20 | 7: (70, 0, 110), 21 | 8: (90, 0, 110), 22 | 9: (100, 10, 120), 23 | 10: (100, 30, 120), 24 | 11: (120, 40, 120), 25 | 12: (120, 60, 130), 26 | 13: (120, 70, 140), 27 | 14: (120, 90, 150), 28 | 15: (130, 100, 150), 29 | 16: (140, 110, 160), 30 | 17: (150, 120, 160), 31 | 18: (160, 130, 160), 32 | 19: (180, 140, 160), 33 | 20: (210, 140, 160), 34 | 21: (210, 100, 160), 35 | 22: (210, 60, 170), 36 | 23: (220, 30, 190), 37 | 24: (240, 10, 200), 38 | 25: (255, 0, 170), 39 | 26: (240, 0, 130), 40 | 27: (240, 20, 100), 41 | 28: (240, 40, 60), 42 | 29: (240, 70, 30), 43 | 30: (220, 100, 10), 44 | 31: (0, 0, 100), 45 | 32: (10, 0, 100), 46 | 33: (20, 0, 100), 47 | 34: (30, 0, 100), 48 | 35: (40, 0, 100), 49 | 36: (50, 0, 100), 50 | 37: (70, 0, 110), 51 | 38: (90, 0, 110), 52 | 39: (100, 10, 120), 53 | 40: (100, 30, 120), 54 | 41: (120, 40, 120), 55 | 42: (120, 60, 130), 56 | 43: (120, 70, 140), 57 | 44: (120, 90, 150), 58 | 45: (130, 100, 150), 59 | 46: (140, 110, 160), 60 | 47: (150, 120, 160), 61 | 48: (160, 130, 160), 62 | 49: (180, 140, 160), 63 | 50: (210, 140, 160), 64 | 51: (210, 100, 160), 65 | 52: (210, 60, 170), 66 | 53: (220, 30, 190), 67 | 54: (240, 10, 200), 68 | 55: (255, 0, 170), 69 | 56: (240, 0, 130), 70 | 57: (240, 20, 100), 71 | 58: (240, 40, 60), 72 | 59: (240, 70, 30), 73 | 60: (220, 100, 10), 74 | 61: (200, 110, 10), 75 | 62: (190, 120, 10), 76 | 63: (190, 130, 20), 77 | 64: (180, 140, 30), 78 | 65: (170, 150, 40), 79 | 66: (160, 160, 50), 80 | 67: (150, 170, 70), 81 | 68: (140, 180, 90), 82 | 69: (130, 190, 100), 83 | 70: (120, 200, 110), 84 | 71: (110, 210, 120), 85 | 72: (100, 220, 130), 86 | 73: (90, 230, 140), 87 | 74: (80, 240, 150), 88 | 75: (70, 250, 160), 89 | 76: (60, 230, 170), 90 | 77: (50, 200, 180), 91 | 78: (40, 180, 190), 92 | 79: (30, 160, 200), 93 | 80: (20, 140, 210), 94 | 81: (10, 120, 220), 95 | 82: (0, 100, 230), 96 | 83: (20, 80, 240), 97 | 84: (40, 60, 250), 98 | 85: (60, 40, 230), 99 | 86: (80, 20, 10), 100 | 87: (100, 0, 30), 101 | 88: (220, 100, 10), 102 | 89: (220, 100, 10), 103 | 90: (220, 100, 10), 104 | 91: (220, 100, 10), 105 | 92: (220, 100, 10), 106 | 93: (220, 100, 10), 107 | 94: (220, 100, 10), 108 | 95: (220, 100, 10), 109 | 96: (220, 100, 10), 110 | 97: (220, 100, 10), 111 | 98: (220, 100, 10), 112 | 99: (220, 100, 10), 113 | 100: (220, 100, 10), 114 | 101: (220, 100, 10), 115 | 102: (220, 100, 10), 116 | 103: (220, 100, 10), 117 | 104: (220, 100, 10), 118 | 105: (220, 100, 10), 119 | 106: (220, 100, 10), 120 | 107: (220, 100, 10), 121 | 108: (220, 100, 10), 122 | 109: (220, 100, 10), 123 | 110: (220, 100, 10), 124 | 111: (220, 100, 10), 125 | 112: (220, 100, 10), 126 | 113: (220, 100, 10), 127 | 114: (220, 100, 10), 128 | 115: (220, 100, 10), 129 | 116: (220, 100, 10), 130 | 117: (220, 100, 10), 131 | 118: (220, 100, 10), 132 | 119: (220, 100, 10), 133 | 120: (220, 100, 10), 134 | 121: (220, 100, 10), 135 | 122: (220, 100, 10), 136 | 123: (220, 100, 10), 137 | 124: (220, 100, 10), 138 | 125: (220, 100, 10), 139 | 126: (220, 100, 10), 140 | 127: (220, 100, 10), 141 | 128: (220, 100, 10) 142 | } 143 | 144 | fractal_flames = { -1: (230, 23, 23), 145 | 1: (230, 26, 23), 146 | 2: (231, 29, 23), 147 | 3: (231, 32, 23), 148 | 4: (232, 34, 23), 149 | 5: (232, 37, 23), 150 | 6: (232, 40, 23), 151 | 7: (233, 43, 23), 152 | 8: (233, 46, 23), 153 | 9: (234, 49, 23), 154 | 10: (234, 52, 23), 155 | 11: (234, 55, 23), 156 | 12: (235, 57, 23), 157 | 13: (235, 60, 23), 158 | 14: (236, 63, 23), 159 | 15: (236, 66, 23), 160 | 16: (236, 69, 23), 161 | 17: (237, 72, 23), 162 | 18: (237, 75, 23), 163 | 19: (238, 78, 23), 164 | 20: (238, 80, 23), 165 | 21: (238, 83, 23), 166 | 22: (239, 86, 23), 167 | 23: (239, 89, 23), 168 | 24: (240, 92, 23), 169 | 25: (240, 95, 23), 170 | 26: (240, 98, 23), 171 | 27: (241, 101, 23), 172 | 28: (241, 103, 23), 173 | 29: (242, 106, 23), 174 | 30: (242, 109, 23), 175 | 31: (243, 112, 23), 176 | 32: (243, 115, 23), 177 | 33: (243, 118, 23), 178 | 34: (244, 121, 23), 179 | 35: (244, 123, 23), 180 | 36: (245, 126, 23), 181 | 37: (245, 129, 23), 182 | 38: (245, 132, 23), 183 | 39: (246, 135, 23), 184 | 40: (246, 138, 23), 185 | 41: (247, 141, 23), 186 | 42: (247, 144, 23), 187 | 43: (247, 146, 23), 188 | 44: (248, 149, 23), 189 | 45: (248, 152, 23), 190 | 46: (249, 155, 23), 191 | 47: (249, 158, 23), 192 | 48: (249, 161, 23), 193 | 49: (250, 164, 23), 194 | 50: (250, 167, 23), 195 | 51: (251, 169, 23), 196 | 52: (251, 172, 23), 197 | 53: (251, 175, 23), 198 | 54: (252, 178, 23), 199 | 55: (252, 181, 23), 200 | 56: (253, 184, 23), 201 | 57: (253, 187, 23), 202 | 58: (253, 190, 23), 203 | 59: (254, 192, 23), 204 | 60: (254, 195, 23), 205 | 61: (255, 198, 23), 206 | 62: (255, 201, 23), 207 | 63: (255, 201, 23), 208 | 64: (255, 199, 23), 209 | 65: (254, 197, 22), 210 | 66: (254, 196, 22), 211 | 67: (254, 194, 22), 212 | 68: (253, 192, 21), 213 | 69: (253, 190, 21), 214 | 70: (253, 188, 20), 215 | 71: (252, 187, 20), 216 | 72: (252, 185, 20), 217 | 73: (252, 183, 19), 218 | 74: (251, 181, 19), 219 | 75: (251, 180, 19), 220 | 76: (251, 178, 18), 221 | 77: (250, 176, 18), 222 | 78: (250, 174, 17), 223 | 79: (250, 172, 17), 224 | 80: (250, 171, 17), 225 | 81: (249, 169, 16), 226 | 82: (249, 167, 16), 227 | 83: (249, 165, 16), 228 | 84: (248, 163, 15), 229 | 85: (248, 162, 15), 230 | 86: (248, 160, 14), 231 | 87: (247, 158, 14), 232 | 88: (247, 156, 14), 233 | 89: (247, 154, 13), 234 | 90: (246, 153, 13), 235 | 91: (246, 151, 13), 236 | 92: (246, 149, 12), 237 | 93: (245, 147, 12), 238 | 94: (245, 146, 11), 239 | 95: (245, 144, 11), 240 | 96: (244, 142, 11), 241 | 97: (244, 140, 10), 242 | 98: (244, 138, 10), 243 | 99: (243, 137, 10), 244 | 100: (243, 135, 9), 245 | 101: (243, 133, 9), 246 | 102: (242, 131, 9), 247 | 103: (242, 129, 8), 248 | 104: (242, 128, 8), 249 | 105: (241, 126, 7), 250 | 106: (241, 124, 7), 251 | 107: (241, 122, 7), 252 | 108: (240, 120, 6), 253 | 109: (240, 119, 6), 254 | 110: (240, 117, 6), 255 | 111: (240, 115, 5), 256 | 112: (239, 113, 5), 257 | 113: (239, 111, 4), 258 | 114: (239, 110, 4), 259 | 115: (238, 108, 4), 260 | 116: (238, 106, 3), 261 | 117: (238, 104, 3), 262 | 118: (237, 103, 3), 263 | 119: (237, 101, 2), 264 | 120: (237, 99, 2), 265 | 121: (236, 97, 1), 266 | 122: (236, 95, 1), 267 | 123: (236, 94, 1), 268 | 124: (235, 92, 0), 269 | 125: (235, 90, 0), 270 | } -------------------------------------------------------------------------------- /mandelbrot/generate.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import colors 3 | import user_schemes 4 | import sys 5 | 6 | points = [] 7 | color_code = "Random" 8 | color_scheme = None # The actual dict or dict reference with RGB color codes 9 | 10 | def generate_image(width, height): 11 | """ 12 | Generates an image using the points stored in results.txt and 13 | color codes it based on the iteration value of each point. 14 | 15 | Parameters: 16 | width, height: The requested width and height of the image 17 | """ 18 | 19 | global color_code 20 | global color_scheme 21 | if color_code == "True Random": 22 | color_scheme = colors.random_colors() 23 | elif not color_scheme: 24 | if color_code == "Random": 25 | color_scheme = colors.random_colors() 26 | elif color_code == "Purple Pop": 27 | color_scheme = colors.purple_rainbow 28 | elif color_code == "Fractal Flames": 29 | color_scheme = colors.fractal_flames 30 | else: 31 | for var in dir(user_schemes): #TODO: Fix 32 | if color_code == var: 33 | dict_access = f'global color_scheme; color_scheme = user_schemes.{var}' 34 | exec(dict_access) 35 | break 36 | else: 37 | print("Invalid color code entry, closing program.") 38 | sys.exit() 39 | 40 | 41 | with open('results.txt', 'r') as f: 42 | img = Image.new('RGB', (width, height), color='white') 43 | pic = img.load() 44 | points = [] 45 | for line in f: 46 | iterations = line.split(',') 47 | points.append(iterations[0]) 48 | x = 0 49 | y = 0 50 | for point in points: 51 | if x < width: 52 | try: 53 | pic[x,y] = color_scheme[int(point)] 54 | # Assigns each point to a color value stored in the colors.py 55 | x += 1 56 | except: 57 | x += 1 58 | else: 59 | y += 1 60 | x = 0 61 | try: 62 | pic[x,y] = color_scheme[int(point)] 63 | x += 1 64 | except: 65 | x += 1 66 | 67 | img.resize((int(width/2), int(height/2)), resample=Image.ANTIALIAS) 68 | 69 | img.save('mandelbrot.png') -------------------------------------------------------------------------------- /mandelbrot/generate_scheme.py: -------------------------------------------------------------------------------- 1 | from sys import argv 2 | from math import ceil 3 | from random import randint 4 | 5 | def generate_scheme(): 6 | """ 7 | Generates a color scheme in the colors.py file with 128 different 8 | RGB color codes. Any number of color codes under 129 can be provided. 9 | These codes will then be interpolated between. 10 | 11 | This functionality can be used by executing the python script followed 12 | by specific arguments: 13 | python3 generate_scheme.py ... 14 | 15 | Example usage: 16 | python3 generate_scheme.py test_scheme 250,250,250 120,120,120 40,40,40 17 | 18 | Addtional commands: 19 | -c: Clears the user_schemes file, must be the first argument 20 | -r: generates a set number of random RGB codes to interpolate bewteen. Must be 21 | followed by the total number of codes to interpolated between (less than 129). 22 | Example usage (Generating a scheme named scheme_name with five codes): 23 | python3 generate_scheme.py -r scheme_name 5 24 | """ 25 | 26 | name = argv[1] 27 | start_codes = [] 28 | full_codes = [] 29 | 30 | if argv[1] == "-c": 31 | with open('user_schemes.py', 'w+') as f: 32 | f.write('') 33 | print("File cleared.") 34 | return 35 | elif argv[1] == "-r": 36 | try: 37 | name = argv[2] 38 | total = int(argv[3]) 39 | for i in range(total): 40 | code = (randint(0,255), randint(0,255), randint(0,255)) 41 | if i == 0: 42 | full_codes.append(code) 43 | else: 44 | start_codes.append(code) 45 | except: 46 | print("Invalid argument input for random flag.") 47 | elif len(argv) < 4: 48 | print("Invalid usage, missing required arguments.") 49 | return 50 | else: 51 | full_codes, start_codes = parse_rgb() 52 | 53 | between = int(ceil(128 - len(start_codes)) / (len(start_codes) - 1)) 54 | 55 | for i, code in enumerate(start_codes[:-1]): 56 | new_code = list(code) 57 | full_codes.append(code) 58 | end_code = start_codes[i+1] 59 | if i == len(start_codes[:-1]) - 1: 60 | between = 128 - len(full_codes) + 1 61 | r_step = (end_code[0] - code[0]) / between 62 | g_step = (end_code[1] - code[1]) / between 63 | b_step = (end_code[2] - code[2]) / between 64 | for i in range(between): 65 | new_code[0] += r_step 66 | new_code[1] += g_step 67 | new_code[2] += b_step 68 | result = [round(val) for val in new_code] 69 | full_codes.append(tuple(result)) 70 | 71 | with open('user_schemes.py', 'a+') as f: 72 | f.write(name + " = {") 73 | for i, code in enumerate(full_codes): 74 | if i == 0: 75 | f.write(f" -1: {code},\n") 76 | else: 77 | f.write(f"\t{i}: {code},\n") 78 | f.write("}\n\n") 79 | 80 | print("Scheme created successfully.") 81 | 82 | 83 | def parse_rgb(): 84 | full_codes = [] 85 | start_codes = [] 86 | for i, code in enumerate(argv[2:]): 87 | code = code.split(',') 88 | new_code = [] 89 | if len(code) == 3: 90 | for val in code: 91 | try: 92 | val = int(val) 93 | new_code.append(val) 94 | except: 95 | print("Invalid color code argument.") 96 | return 97 | if i != 0: 98 | start_codes.append(tuple(new_code)) 99 | else: 100 | full_codes.append(tuple(new_code)) 101 | return full_codes, start_codes 102 | 103 | if __name__ == "__main__": 104 | generate_scheme() -------------------------------------------------------------------------------- /mandelbrot/indicator/indicator.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | bool jobActive = false; 4 | int jobLedPin = 2; 5 | int calcLedPin = 4; 6 | char inactive[] = "No Job Running"; 7 | char jobProgress[] = "0% Complete"; 8 | String progressBar = "[ ]"; 9 | char input; 10 | bool lightOn = false; 11 | int blinkTime = 250; 12 | int timeOn = 0; 13 | int startOn = 0; 14 | long totalJobs = -1; 15 | long completedJobs = 0; 16 | float jobPercentage; 17 | String progressText; 18 | LiquidCrystal lcd(7, 8, 9, 10, 11, 12); 19 | 20 | 21 | void setup() { 22 | pinMode(jobLedPin, OUTPUT); 23 | pinMode(calcLedPin, OUTPUT); 24 | Serial.begin(9600); 25 | lcd.begin(16, 2); 26 | lcd.clear(); 27 | lcd.print(inactive); 28 | } 29 | 30 | void loop() { 31 | if (Serial.available()) { 32 | input = Serial.read(); 33 | } 34 | 35 | if (jobActive) { 36 | if (lightOn == true) { 37 | timeOn = millis() - startOn; 38 | if (timeOn > blinkTime) { 39 | digitalWrite(calcLedPin, LOW); 40 | lightOn = false; 41 | completedJobs += 1; 42 | jobPercentage = float(completedJobs) / float(totalJobs) * 100; 43 | progressText = String(jobPercentage) + "%"; 44 | lcd.clear(); 45 | lcd.print(progressText); 46 | input = Serial.read(); 47 | } 48 | } else if (input == '2') { 49 | digitalWrite(calcLedPin, HIGH); 50 | lightOn = true; 51 | startOn = millis(); 52 | } 53 | 54 | if (input == '9'){ 55 | jobActive = false; 56 | digitalWrite(jobLedPin, LOW); 57 | digitalWrite(calcLedPin, LOW); 58 | lcd.clear(); 59 | lcd.print(inactive); 60 | } 61 | } else { 62 | if (input == '1'){ 63 | while (totalJobs == -1){ 64 | totalJobs = Serial.parseInt(); 65 | } 66 | digitalWrite(jobLedPin, HIGH); 67 | jobActive = true; 68 | lcd.clear(); //Get rid of this 69 | lcd.print(jobProgress); 70 | lcd.setCursor(0, 1); 71 | lcd.print(progressBar); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mandelbrot/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import tkinter as tk 3 | import pygame as pg 4 | import sys 5 | import calculate 6 | import generate 7 | import user_schemes 8 | 9 | def get_settings(): 10 | """ 11 | Generates a GUI to collect basic generation settings. 12 | Sets default values for any entries that are not filled. 13 | """ 14 | 15 | menu = tk.Tk() 16 | tk_rgb = "#%02x%02x%02x" % (128, 192, 200) 17 | menu.title('Mandelbrot Settings') 18 | menu.configure(background=tk_rgb) 19 | 20 | def start_gen(gen_settings, color_option, status_label): 21 | arguments = [500, 500, -2.0, 1.0, -1.5, 1.5] 22 | # Argument Order: width, height, minx, maxx, miny, maxy 23 | if color_option.get() != "": 24 | generate.color_code = color_option.get() 25 | for i, setting in enumerate(gen_settings): 26 | value = setting.get() 27 | try: 28 | value = float(setting.get()) 29 | arguments[i] = value 30 | except ValueError: 31 | print("Invalid input, supplying default value of ", arguments[i]) 32 | status_label.configure(text='Calculating...') 33 | menu.update_idletasks() 34 | calculate.find_points(*arguments) 35 | menu.destroy() 36 | pg_window(int(arguments[0]), int(arguments[1])) 37 | 38 | width_label = tk.Label(menu, text='Window Width', background=tk_rgb) 39 | width_field = tk.Entry(menu) 40 | height_label = tk.Label(menu, text='Window Height', background=tk_rgb) 41 | height_field = tk.Entry(menu) 42 | color_value = tk.StringVar(menu) 43 | color_codes = {"Really Random", "Random", "Purple Pop", "Fractal Flames"} 44 | for var in dir(user_schemes): 45 | if not var.startswith('__'): 46 | color_codes.add(var) 47 | color_label = tk.Label(menu, text='Color Scheme', background=tk_rgb) 48 | color_options = tk.OptionMenu(menu, color_value, *color_codes) 49 | minx_label = tk.Label(menu, text='Min X', background=tk_rgb) 50 | minx_field = tk.Entry(menu) 51 | maxx_label = tk.Label(menu, text='Max X', background=tk_rgb) 52 | maxx_field = tk.Entry(menu) 53 | miny_label = tk.Label(menu, text='Min Y', background=tk_rgb) 54 | miny_field = tk.Entry(menu) 55 | maxy_label = tk.Label(menu, text='Max Y', background=tk_rgb) 56 | maxy_field = tk.Entry(menu) 57 | 58 | entries = [width_field, height_field, minx_field, maxx_field, 59 | miny_field, maxy_field] 60 | 61 | generate_status = tk.Label(menu, text='Waiting...', background=tk_rgb) 62 | generate_button = tk.Button(menu, text='Generate Set', 63 | command=lambda: start_gen(entries, color_value, generate_status)) 64 | 65 | # TODO: Find less stupid way to do this 66 | width_label.grid(row=1, column=0) 67 | width_field.grid(row=1, column=1, columnspan=2) 68 | height_label.grid(row=2, column=0) 69 | height_field.grid(row=2, column=1, columnspan=2) 70 | color_label.grid(row=3, column=0) 71 | color_options.grid(row=3, column=1, columnspan=2) 72 | minx_label.grid(row=4, column=0) 73 | minx_field.grid(row=4, column=1, columnspan=2) 74 | maxx_label.grid(row=5, column=0) 75 | maxx_field.grid(row=5, column=1, columnspan=2) 76 | miny_label.grid(row=6, column=0) 77 | miny_field.grid(row=6, column=1, columnspan=2) 78 | maxy_label.grid(row=7, column=0) 79 | maxy_field.grid(row=7, column=1, columnspan=2) 80 | generate_status.grid(row=8, column=0) 81 | generate_button.grid(row=8, column=1) 82 | 83 | menu.mainloop() 84 | 85 | def zoom(mouse_x, mouse_y, width, height, zoom_x, zoom_y): 86 | start_x = mouse_x - zoom_x 87 | end_x = mouse_x + zoom_x 88 | start_y = mouse_y - zoom_y 89 | end_y = mouse_y + zoom_y 90 | x = 0 91 | y = 0 92 | 93 | min_x = 0 94 | max_x = 0 95 | min_y = 0 96 | max_y = 0 97 | 98 | with open('results.txt', 'r') as f: 99 | for line in f: 100 | if x % width == 0: 101 | x = 0 102 | y += 1 103 | 104 | # Checks if line in file matches a vertex of the zoom 105 | # rectangle and sets 106 | if x == start_x and y == start_y: 107 | point = line.split(',') 108 | min_x = float(point[1]) 109 | min_y = float(point[2]) 110 | elif x == end_x and y == end_y: 111 | point = line.split(',') 112 | max_x = float(point[1]) 113 | max_y = float(point[2]) 114 | 115 | x += 1 116 | 117 | calculate.find_points(width, height, min_x, max_x, min_y, max_y) 118 | 119 | def pg_window(width, height): 120 | pg.init() 121 | 122 | fill_color = 255, 255, 255 # White 123 | window = pg.display.set_mode((width, height)) 124 | 125 | set_img = pg.image.load('mandelbrot.png') 126 | zoom_x = int(width * .15) 127 | zoom_y = int(height * .15) 128 | 129 | while True: 130 | window.fill(fill_color) 131 | 132 | mouse_pos = pg.mouse.get_pos() 133 | zoom_rect = pg.Rect((0,0), (zoom_x * 2 + 1, zoom_y * 2 + 1)) 134 | zoom_rect.center = mouse_pos 135 | # zoom variables are the distance from mouse position to edge of rect 136 | # Multiplying each variable by 2 and adding 1 gives the rect its size 137 | if mouse_pos[0] - zoom_x < 0: 138 | zoom_rect.center = (zoom_x + 1, zoom_rect.center[1]) 139 | elif mouse_pos[0] + zoom_x > width: 140 | zoom_rect.center = (width - zoom_x - 2, zoom_rect.center[1]) 141 | # These statements prevent the rect from moving off screen even when the mouse does 142 | if mouse_pos[1] - zoom_y < 0: 143 | zoom_rect.center = (zoom_rect.center[0], zoom_y + 1) 144 | elif mouse_pos[1] + zoom_y > height: 145 | zoom_rect.center = (zoom_rect.center[0], height - zoom_y - 2) 146 | 147 | for event in pg.event.get(): 148 | if event.type == pg.QUIT: 149 | sys.exit() 150 | if event.type == pg.MOUSEBUTTONDOWN: 151 | zoom(*zoom_rect.center, width, height, zoom_x, zoom_y) 152 | set_img = pg.image.load('mandelbrot.png') 153 | 154 | window.fill(fill_color) 155 | window.blit(set_img, (0, 0)) 156 | pg.draw.rect(window, (30, 30, 30), zoom_rect, 5) # 5 is the pixel width of the rect 157 | pg.display.flip() 158 | 159 | 160 | if __name__ == "__main__": 161 | get_settings() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | future==0.17.1 2 | llvmlite==0.29.0 3 | numba==0.44.1 4 | numpy==1.16.4 5 | Pillow==6.2.0 6 | pkg-resources==0.0.0 7 | pygame==1.9.6 8 | pyglet==1.3.2 9 | pyserial==3.4 10 | --------------------------------------------------------------------------------