fractal 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fractal/__init__.py +8 -0
- fractal/base.py +412 -0
- fractal/cifs.py +100 -0
- fractal/colors.py +21 -0
- fractal/ifs.py +98 -0
- fractal/julia.py +106 -0
- fractal/lsystem.py +117 -0
- fractal/mandelbrot.py +105 -0
- fractal-0.5.0.dist-info/METADATA +291 -0
- fractal-0.5.0.dist-info/RECORD +13 -0
- fractal-0.5.0.dist-info/WHEEL +5 -0
- fractal-0.5.0.dist-info/licenses/LICENSE +21 -0
- fractal-0.5.0.dist-info/top_level.txt +1 -0
fractal/__init__.py
ADDED
fractal/base.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
基类
|
|
3
|
+
功能说明:
|
|
4
|
+
- 保存图片:点击"保存"按钮 或 Ctrl+P
|
|
5
|
+
- 放大:左键点击显示选择框,拖动调整位置,再次点击确认放大
|
|
6
|
+
- 缩小:右键点击缩小
|
|
7
|
+
- 取消选择:右键点击或按ESC键
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import tkinter as tk
|
|
11
|
+
from tkinter import messagebox, filedialog
|
|
12
|
+
import threading
|
|
13
|
+
|
|
14
|
+
# 尝试导入Pillow和numpy用于高性能绘图
|
|
15
|
+
try:
|
|
16
|
+
from PIL import Image, ImageTk, ImageDraw
|
|
17
|
+
HAS_PIL = True
|
|
18
|
+
except ImportError:
|
|
19
|
+
HAS_PIL = False
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import numpy as np
|
|
23
|
+
HAS_NUMPY = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
HAS_NUMPY = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Base(object):
|
|
29
|
+
|
|
30
|
+
def __init__(self, size, callRun, title=""):
|
|
31
|
+
"""
|
|
32
|
+
size: 画布尺寸
|
|
33
|
+
callRun: 子类的绘画函数回调
|
|
34
|
+
title: 画布标题
|
|
35
|
+
"""
|
|
36
|
+
self._run = callRun
|
|
37
|
+
self.title = title if title else "Fractal"
|
|
38
|
+
self.width = size[0]
|
|
39
|
+
self.height = size[1]
|
|
40
|
+
|
|
41
|
+
# 绘图模式标记:'pixel'为像素绘图,'line'为线条绘图
|
|
42
|
+
self._draw_mode = 'pixel'
|
|
43
|
+
|
|
44
|
+
# 创建像素数组(用于存储计算结果)
|
|
45
|
+
if HAS_NUMPY:
|
|
46
|
+
self._pixel_array = np.zeros((self.height, self.width, 3), dtype=np.uint8)
|
|
47
|
+
self._pixel_array.fill(255) # 白色背景
|
|
48
|
+
else:
|
|
49
|
+
self._pixel_array = None
|
|
50
|
+
|
|
51
|
+
# 创建主窗口
|
|
52
|
+
self.root = tk.Tk()
|
|
53
|
+
self.root.title(self.title)
|
|
54
|
+
self.root.resizable(False, False)
|
|
55
|
+
|
|
56
|
+
# 创建主框架
|
|
57
|
+
self.main_frame = tk.Frame(self.root)
|
|
58
|
+
self.main_frame.pack(padx=5, pady=5)
|
|
59
|
+
|
|
60
|
+
# 创建画布
|
|
61
|
+
self.canvas = tk.Canvas(self.main_frame, width=self.width, height=self.height, bg='white')
|
|
62
|
+
self.canvas.pack()
|
|
63
|
+
|
|
64
|
+
# 创建按钮框架
|
|
65
|
+
self.button_frame = tk.Frame(self.root)
|
|
66
|
+
self.button_frame.pack(fill=tk.X, padx=5, pady=5)
|
|
67
|
+
|
|
68
|
+
# 保存按钮
|
|
69
|
+
self.save_btn = tk.Button(self.button_frame, text="保存图片", command=self._on_save_click,
|
|
70
|
+
width=12, height=1, bg="#4CAF50", fg="white", font=("Arial", 10))
|
|
71
|
+
self.save_btn.pack(side=tk.LEFT, padx=5)
|
|
72
|
+
|
|
73
|
+
# 另存为按钮
|
|
74
|
+
self.save_as_btn = tk.Button(self.button_frame, text="另存为...", command=self._on_save_as_click,
|
|
75
|
+
width=12, height=1, bg="#2196F3", fg="white", font=("Arial", 10))
|
|
76
|
+
self.save_as_btn.pack(side=tk.LEFT, padx=5)
|
|
77
|
+
|
|
78
|
+
# 状态标签
|
|
79
|
+
self.status_label = tk.Label(self.button_frame, text="就绪", font=("Arial", 9), fg="gray")
|
|
80
|
+
self.status_label.pack(side=tk.RIGHT, padx=10)
|
|
81
|
+
|
|
82
|
+
# 创建图像对象
|
|
83
|
+
if HAS_PIL:
|
|
84
|
+
self._pil_image = Image.new('RGB', (self.width, self.height), 'white')
|
|
85
|
+
self._draw = ImageDraw.Draw(self._pil_image) # PIL绘图对象
|
|
86
|
+
self.image = ImageTk.PhotoImage(self._pil_image, master=self.root)
|
|
87
|
+
else:
|
|
88
|
+
self.image = tk.PhotoImage(width=self.width, height=self.height, master=self.root)
|
|
89
|
+
self.image.put("white", to=(0, 0, self.width, self.height))
|
|
90
|
+
|
|
91
|
+
self.canvas_img = self.canvas.create_image(0, 0, anchor="nw", image=self.image)
|
|
92
|
+
|
|
93
|
+
self.psave = False
|
|
94
|
+
self.scalaRate = 4 # 放大(缩小)倍数
|
|
95
|
+
self.ctrl = False # Ctrl键按下
|
|
96
|
+
self.cnt = 1 # 连续命名时标号
|
|
97
|
+
self._running = True
|
|
98
|
+
self._drawing = False
|
|
99
|
+
self._draw_thread = None
|
|
100
|
+
|
|
101
|
+
# 选择框相关
|
|
102
|
+
self._selection_rect = None # 选择框ID
|
|
103
|
+
self._selecting = False # 是否在选择模式
|
|
104
|
+
self._selection_center = None # 选择框中心坐标
|
|
105
|
+
self._drag_start = None # 拖动起始坐标
|
|
106
|
+
|
|
107
|
+
# 绑定事件
|
|
108
|
+
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
|
|
109
|
+
self.root.bind("<Control-p>", self._on_save)
|
|
110
|
+
self.root.bind("<Control-s>", self._on_save_click)
|
|
111
|
+
self.root.bind("<Escape>", self._cancel_selection) # ESC取消选择
|
|
112
|
+
self.canvas.bind("<Button-1>", self._on_left_click) # 左键
|
|
113
|
+
self.canvas.bind("<Button-3>", self._on_right_click) # 右键
|
|
114
|
+
self.canvas.bind("<B1-Motion>", self._on_drag) # 拖动
|
|
115
|
+
self.canvas.bind("<ButtonRelease-1>", self._on_drag_end) # 拖动结束
|
|
116
|
+
self.canvas.bind("<Double-Button-1>", self._on_double_click) # 双击确认
|
|
117
|
+
|
|
118
|
+
def _on_close(self):
|
|
119
|
+
self._running = False
|
|
120
|
+
self.root.quit()
|
|
121
|
+
self.root.destroy()
|
|
122
|
+
|
|
123
|
+
def _update_status(self, text, color="gray"):
|
|
124
|
+
"""更新状态标签"""
|
|
125
|
+
self.status_label.config(text=text, fg=color)
|
|
126
|
+
|
|
127
|
+
def _on_save(self, event=None):
|
|
128
|
+
"""快捷键保存"""
|
|
129
|
+
self._on_save_click()
|
|
130
|
+
|
|
131
|
+
def _cancel_selection(self, event=None):
|
|
132
|
+
"""取消选择框"""
|
|
133
|
+
if self._selection_rect is not None:
|
|
134
|
+
self.canvas.delete(self._selection_rect)
|
|
135
|
+
self._selection_rect = None
|
|
136
|
+
self._selecting = False
|
|
137
|
+
self._selection_center = None
|
|
138
|
+
self._update_status("已取消选择", "gray")
|
|
139
|
+
|
|
140
|
+
def _get_selection_rect_coords(self, center_x, center_y):
|
|
141
|
+
"""获取选择框的坐标"""
|
|
142
|
+
w = self.width // (2 * self.scalaRate)
|
|
143
|
+
h = self.height // (2 * self.scalaRate)
|
|
144
|
+
return (center_x - w, center_y - h, center_x + w, center_y + h)
|
|
145
|
+
|
|
146
|
+
def _sync_pil_image(self):
|
|
147
|
+
"""同步PIL图像从numpy数组"""
|
|
148
|
+
if HAS_PIL and HAS_NUMPY and self._draw_mode == 'pixel':
|
|
149
|
+
self._pil_image = Image.fromarray(self._pixel_array, 'RGB')
|
|
150
|
+
self._draw = ImageDraw.Draw(self._pil_image)
|
|
151
|
+
|
|
152
|
+
def _on_save_click(self):
|
|
153
|
+
"""点击保存按钮"""
|
|
154
|
+
if self._drawing:
|
|
155
|
+
messagebox.showwarning("提示", "请等待绘图完成后再保存")
|
|
156
|
+
return
|
|
157
|
+
# 确保PIL图像与像素数组同步(仅对像素绘图模式)
|
|
158
|
+
self._sync_pil_image()
|
|
159
|
+
if self.title == "":
|
|
160
|
+
self.title = "fractal"
|
|
161
|
+
if HAS_PIL:
|
|
162
|
+
filename = self.title + str(self.cnt) + ".png"
|
|
163
|
+
self._pil_image.save(filename)
|
|
164
|
+
self._update_status(f"已保存: {filename}", "green")
|
|
165
|
+
messagebox.showinfo("保存成功", f"图片已保存为 {filename}")
|
|
166
|
+
else:
|
|
167
|
+
filename = self.title + str(self.cnt) + ".ps"
|
|
168
|
+
self.canvas.postscript(file=filename, colormode='color')
|
|
169
|
+
self._update_status(f"已保存: {filename}", "green")
|
|
170
|
+
messagebox.showinfo("保存成功", f"图片已保存为 {filename}\n(PostScript格式)")
|
|
171
|
+
self.cnt += 1
|
|
172
|
+
|
|
173
|
+
def _on_save_as_click(self):
|
|
174
|
+
"""另存为对话框"""
|
|
175
|
+
if self._drawing:
|
|
176
|
+
messagebox.showwarning("提示", "请等待绘图完成后再保存")
|
|
177
|
+
return
|
|
178
|
+
# 确保PIL图像与像素数组同步
|
|
179
|
+
self._sync_pil_image()
|
|
180
|
+
if HAS_PIL:
|
|
181
|
+
filetypes = [
|
|
182
|
+
("PNG 图片", "*.png"),
|
|
183
|
+
("JPEG 图片", "*.jpg"),
|
|
184
|
+
("BMP 图片", "*.bmp"),
|
|
185
|
+
("所有文件", "*.*")
|
|
186
|
+
]
|
|
187
|
+
filename = filedialog.asksaveasfilename(
|
|
188
|
+
title="保存图片",
|
|
189
|
+
initialfile=self.title + ".png",
|
|
190
|
+
filetypes=filetypes,
|
|
191
|
+
defaultextension=".png"
|
|
192
|
+
)
|
|
193
|
+
if filename:
|
|
194
|
+
self._pil_image.save(filename)
|
|
195
|
+
self._update_status(f"已保存: {filename}", "green")
|
|
196
|
+
messagebox.showinfo("保存成功", f"图片已保存为 {filename}")
|
|
197
|
+
else:
|
|
198
|
+
filetypes = [("PostScript", "*.ps"), ("所有文件", "*.*")]
|
|
199
|
+
filename = filedialog.asksaveasfilename(
|
|
200
|
+
title="保存图片",
|
|
201
|
+
initialfile=self.title + ".ps",
|
|
202
|
+
filetypes=filetypes,
|
|
203
|
+
defaultextension=".ps"
|
|
204
|
+
)
|
|
205
|
+
if filename:
|
|
206
|
+
self.canvas.postscript(file=filename, colormode='color')
|
|
207
|
+
self._update_status(f"已保存: {filename}", "green")
|
|
208
|
+
messagebox.showinfo("保存成功", f"图片已保存为 {filename}")
|
|
209
|
+
|
|
210
|
+
def _on_left_click(self, event):
|
|
211
|
+
"""左键点击处理"""
|
|
212
|
+
if self._drawing or not hasattr(self, "scala"):
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
# 如果正在选择模式,确认放大
|
|
216
|
+
if self._selecting and self._selection_rect is not None:
|
|
217
|
+
self._confirm_zoom(event.x, event.y)
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
# 第一次点击,显示选择框
|
|
221
|
+
i, j = event.x, event.y
|
|
222
|
+
coords = self._get_selection_rect_coords(i, j)
|
|
223
|
+
self._selection_rect = self.canvas.create_rectangle(
|
|
224
|
+
coords[0], coords[1], coords[2], coords[3],
|
|
225
|
+
outline="red", width=2, dash=(4, 4)
|
|
226
|
+
)
|
|
227
|
+
self._selecting = True
|
|
228
|
+
self._selection_center = (i, j)
|
|
229
|
+
self._update_status("拖动调整位置,点击确认,ESC取消", "blue")
|
|
230
|
+
|
|
231
|
+
def _on_double_click(self, event):
|
|
232
|
+
"""双击确认放大"""
|
|
233
|
+
if self._selecting and self._selection_rect is not None and not self._drawing:
|
|
234
|
+
self._confirm_zoom(self._selection_center[0], self._selection_center[1])
|
|
235
|
+
|
|
236
|
+
def _on_drag(self, event):
|
|
237
|
+
"""拖动选择框"""
|
|
238
|
+
if not self._selecting or self._selection_rect is None:
|
|
239
|
+
return
|
|
240
|
+
# 更新选择框位置
|
|
241
|
+
coords = self._get_selection_rect_coords(event.x, event.y)
|
|
242
|
+
self.canvas.coords(self._selection_rect, coords[0], coords[1], coords[2], coords[3])
|
|
243
|
+
self._selection_center = (event.x, event.y)
|
|
244
|
+
|
|
245
|
+
def _on_drag_end(self, event):
|
|
246
|
+
"""拖动结束"""
|
|
247
|
+
if self._selecting:
|
|
248
|
+
self._selection_center = (event.x, event.y)
|
|
249
|
+
|
|
250
|
+
def _confirm_zoom(self, x, y):
|
|
251
|
+
"""确认放大"""
|
|
252
|
+
# 删除选择框
|
|
253
|
+
if self._selection_rect is not None:
|
|
254
|
+
self.canvas.delete(self._selection_rect)
|
|
255
|
+
self._selection_rect = None
|
|
256
|
+
self._selecting = False
|
|
257
|
+
|
|
258
|
+
# 执行放大
|
|
259
|
+
self._clear_pixels()
|
|
260
|
+
self.scala(x, y, self.scalaRate)
|
|
261
|
+
self._start_draw()
|
|
262
|
+
|
|
263
|
+
def _on_right_click(self, event):
|
|
264
|
+
"""右键点击处理"""
|
|
265
|
+
if self._drawing:
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
# 如果在选择模式,取消选择
|
|
269
|
+
if self._selecting:
|
|
270
|
+
self._cancel_selection()
|
|
271
|
+
return
|
|
272
|
+
|
|
273
|
+
# 否则执行缩小
|
|
274
|
+
if hasattr(self, "scala"):
|
|
275
|
+
self._clear_pixels()
|
|
276
|
+
self.scala(event.x, event.y, 1 / self.scalaRate)
|
|
277
|
+
self._start_draw()
|
|
278
|
+
|
|
279
|
+
def _clear_pixels(self):
|
|
280
|
+
"""清空像素数据"""
|
|
281
|
+
if HAS_NUMPY:
|
|
282
|
+
self._pixel_array.fill(255)
|
|
283
|
+
if HAS_PIL:
|
|
284
|
+
self._pil_image = Image.new('RGB', (self.width, self.height), 'white')
|
|
285
|
+
self._draw = ImageDraw.Draw(self._pil_image)
|
|
286
|
+
|
|
287
|
+
def _start_draw(self):
|
|
288
|
+
"""开始绘图线程"""
|
|
289
|
+
self._drawing = True
|
|
290
|
+
self._update_status("绘图中...", "orange")
|
|
291
|
+
self._draw_thread = threading.Thread(target=self._run_and_update, daemon=True)
|
|
292
|
+
self._draw_thread.start()
|
|
293
|
+
|
|
294
|
+
def _run_and_update(self):
|
|
295
|
+
"""绘图并在完成后更新显示"""
|
|
296
|
+
try:
|
|
297
|
+
self._run()
|
|
298
|
+
# 绘图完成后立即同步PIL图像(在线程中执行)
|
|
299
|
+
if HAS_PIL and HAS_NUMPY and self._draw_mode == 'pixel':
|
|
300
|
+
self._pil_image = Image.fromarray(self._pixel_array, 'RGB')
|
|
301
|
+
self._draw = ImageDraw.Draw(self._pil_image)
|
|
302
|
+
except Exception as e:
|
|
303
|
+
print(f"绘图错误: {e}")
|
|
304
|
+
finally:
|
|
305
|
+
self._drawing = False
|
|
306
|
+
# 在主线程中更新显示
|
|
307
|
+
self.root.after(0, self._finish_draw)
|
|
308
|
+
|
|
309
|
+
def _finish_draw(self):
|
|
310
|
+
"""绘图完成后的处理"""
|
|
311
|
+
self._update_display()
|
|
312
|
+
self._update_status("绘图完成 - 左键选择放大区域", "green")
|
|
313
|
+
|
|
314
|
+
def _update_display(self):
|
|
315
|
+
"""更新显示(在主线程中调用)"""
|
|
316
|
+
if HAS_PIL and HAS_NUMPY and self._draw_mode == 'pixel':
|
|
317
|
+
# 从numpy数组创建PIL图像
|
|
318
|
+
self._pil_image = Image.fromarray(self._pixel_array, 'RGB')
|
|
319
|
+
self._draw = ImageDraw.Draw(self._pil_image)
|
|
320
|
+
if HAS_PIL:
|
|
321
|
+
self.image = ImageTk.PhotoImage(self._pil_image, master=self.root)
|
|
322
|
+
self.canvas.itemconfig(self.canvas_img, image=self.image)
|
|
323
|
+
|
|
324
|
+
def save(self, title):
|
|
325
|
+
"""保存图片"""
|
|
326
|
+
if HAS_PIL:
|
|
327
|
+
self._pil_image.save(title)
|
|
328
|
+
else:
|
|
329
|
+
self.canvas.postscript(file=title, colormode='color')
|
|
330
|
+
|
|
331
|
+
def set_pixel(self, x, y, color):
|
|
332
|
+
"""设置单个像素"""
|
|
333
|
+
if isinstance(color, (list, tuple)):
|
|
334
|
+
r, g, b = int(color[0]), int(color[1]), int(color[2])
|
|
335
|
+
else:
|
|
336
|
+
r, g, b = 0, 0, 0
|
|
337
|
+
|
|
338
|
+
x, y = int(x), int(y)
|
|
339
|
+
if 0 <= x < self.width and 0 <= y < self.height:
|
|
340
|
+
if HAS_NUMPY:
|
|
341
|
+
self._pixel_array[y, x] = [r, g, b]
|
|
342
|
+
elif HAS_PIL:
|
|
343
|
+
self._pil_image.putpixel((x, y), (r, g, b))
|
|
344
|
+
else:
|
|
345
|
+
color_str = "#{:02x}{:02x}{:02x}".format(r, g, b)
|
|
346
|
+
self.image.put(color_str, (x, y))
|
|
347
|
+
|
|
348
|
+
def set_pixel_fast(self, x, y, r, g, b):
|
|
349
|
+
"""快速设置单个像素"""
|
|
350
|
+
x, y = int(x), int(y)
|
|
351
|
+
if 0 <= x < self.width and 0 <= y < self.height:
|
|
352
|
+
if HAS_NUMPY:
|
|
353
|
+
self._pixel_array[y, x] = [r, g, b]
|
|
354
|
+
elif HAS_PIL:
|
|
355
|
+
self._pil_image.putpixel((x, y), (r, g, b))
|
|
356
|
+
else:
|
|
357
|
+
color_str = "#{:02x}{:02x}{:02x}".format(r, g, b)
|
|
358
|
+
self.image.put(color_str, (x, y))
|
|
359
|
+
|
|
360
|
+
def set_pixels(self, pixels):
|
|
361
|
+
"""批量设置像素"""
|
|
362
|
+
for (x, y), color in pixels.items():
|
|
363
|
+
self.set_pixel(x, y, color)
|
|
364
|
+
|
|
365
|
+
def draw_line(self, start, end, color, width=1):
|
|
366
|
+
"""
|
|
367
|
+
绘制线条
|
|
368
|
+
同时在Canvas和PIL图像上绘制,确保保存时图像正确
|
|
369
|
+
"""
|
|
370
|
+
self._draw_mode = 'line' # 标记为线条绘图模式
|
|
371
|
+
|
|
372
|
+
# 在Canvas上绘制
|
|
373
|
+
if isinstance(color, (list, tuple)):
|
|
374
|
+
r, g, b = int(color[0]), int(color[1]), int(color[2])
|
|
375
|
+
canvas_color = "#{:02x}{:02x}{:02x}".format(r, g, b)
|
|
376
|
+
else:
|
|
377
|
+
canvas_color = color
|
|
378
|
+
r, g, b = 0, 0, 0
|
|
379
|
+
|
|
380
|
+
self.canvas.create_line(start[0], start[1], end[0], end[1], fill=canvas_color, width=width)
|
|
381
|
+
|
|
382
|
+
# 在PIL图像上绘制(用于保存)
|
|
383
|
+
if HAS_PIL:
|
|
384
|
+
self._draw.line([start[0], start[1], end[0], end[1]], fill=(r, g, b), width=width)
|
|
385
|
+
|
|
386
|
+
def draw_rect(self, x, y, w, h, color, outline_only=True):
|
|
387
|
+
"""绘制矩形"""
|
|
388
|
+
if isinstance(color, (list, tuple)):
|
|
389
|
+
r, g, b = color
|
|
390
|
+
color = "#{:02x}{:02x}{:02x}".format(int(r), int(g), int(b))
|
|
391
|
+
if outline_only:
|
|
392
|
+
self.canvas.create_rectangle(x, y, x + w, y + h, outline=color, width=1)
|
|
393
|
+
else:
|
|
394
|
+
self.canvas.create_rectangle(x, y, x + w, y + h, fill=color, outline=color)
|
|
395
|
+
|
|
396
|
+
def fill(self, color):
|
|
397
|
+
"""填充背景色"""
|
|
398
|
+
if isinstance(color, (list, tuple)):
|
|
399
|
+
r, g, b = int(color[0]), int(color[1]), int(color[2])
|
|
400
|
+
else:
|
|
401
|
+
r, g, b = 255, 255, 255
|
|
402
|
+
|
|
403
|
+
if HAS_NUMPY:
|
|
404
|
+
self._pixel_array[:, :] = [r, g, b]
|
|
405
|
+
if HAS_PIL:
|
|
406
|
+
self._pil_image = Image.new('RGB', (self.width, self.height), (r, g, b))
|
|
407
|
+
self._draw = ImageDraw.Draw(self._pil_image)
|
|
408
|
+
|
|
409
|
+
def wait(self):
|
|
410
|
+
"""等待用户关闭"""
|
|
411
|
+
self._start_draw()
|
|
412
|
+
self.root.mainloop()
|
fractal/cifs.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Complex Iterated Function System.
|
|
3
|
+
复迭代函数系统
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .base import Base, HAS_NUMPY
|
|
7
|
+
from .colors import *
|
|
8
|
+
from threading import Thread
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CIFS(Base):
|
|
12
|
+
|
|
13
|
+
def __init__(self, size, title=""):
|
|
14
|
+
Base.__init__(self, size, self._run, title)
|
|
15
|
+
self.setRadius(10)
|
|
16
|
+
self.width = size[0]
|
|
17
|
+
self.height = size[1]
|
|
18
|
+
self.setRange(10, 10)
|
|
19
|
+
self.setCentre(0 + 0j)
|
|
20
|
+
self.ifunc = None
|
|
21
|
+
|
|
22
|
+
def setFunction(self, ifunc):
|
|
23
|
+
# 设置迭代函数
|
|
24
|
+
self.ifunc = ifunc
|
|
25
|
+
|
|
26
|
+
def setRadius(self, R):
|
|
27
|
+
# 设置逃逸半径
|
|
28
|
+
self.R = R
|
|
29
|
+
|
|
30
|
+
def color(self, n, r=2):
|
|
31
|
+
if n < len(reds):
|
|
32
|
+
return reds[n]
|
|
33
|
+
else:
|
|
34
|
+
if r < self.R:
|
|
35
|
+
return blues[int((len(blues) - 1) * r / self.R)]
|
|
36
|
+
else:
|
|
37
|
+
return purples[int((len(purples) - 1) * self.R / r)]
|
|
38
|
+
|
|
39
|
+
def setColor(self, call):
|
|
40
|
+
self.color = call
|
|
41
|
+
|
|
42
|
+
def setCentre(self, z0):
|
|
43
|
+
# 设置中心点
|
|
44
|
+
self.z0 = z0
|
|
45
|
+
|
|
46
|
+
def setRange(self, xmax, ymax):
|
|
47
|
+
# 设置坐标范围,范围越小图放大倍数越高
|
|
48
|
+
self.xmax = xmax
|
|
49
|
+
self.ymax = ymax
|
|
50
|
+
|
|
51
|
+
def __getXY(self, i, j):
|
|
52
|
+
# 通过像素坐标获取映射后的坐标
|
|
53
|
+
return complex((i / self.width - 0.5) * self.xmax + self.z0.real,
|
|
54
|
+
(j / self.height - 0.5) * self.ymax + self.z0.imag)
|
|
55
|
+
|
|
56
|
+
def scala(self, i, j, rate):
|
|
57
|
+
# 将(i, j)像素点置于中心位置,放大rete倍
|
|
58
|
+
self.setCentre(self.__getXY(i, j))
|
|
59
|
+
self.xmax /= rate
|
|
60
|
+
self.ymax /= rate
|
|
61
|
+
|
|
62
|
+
def __calc(self, start, w, h):
|
|
63
|
+
# 绘制以start为起点,宽w,高h的子区域
|
|
64
|
+
for i in range(start[0], start[0] + w):
|
|
65
|
+
for j in range(start[1], start[1] + h):
|
|
66
|
+
ct = 0
|
|
67
|
+
z = self.__getXY(i, j)
|
|
68
|
+
for k in range(self.N):
|
|
69
|
+
ct = k
|
|
70
|
+
if abs(z) > self.R:
|
|
71
|
+
break
|
|
72
|
+
z = self.ifunc(z)
|
|
73
|
+
col = self.color(ct, abs(z))
|
|
74
|
+
if HAS_NUMPY and self._pixel_array is not None:
|
|
75
|
+
self._pixel_array[j, i] = [int(col[0]), int(col[1]), int(col[2])]
|
|
76
|
+
else:
|
|
77
|
+
self.set_pixel_fast(i, j, col[0], col[1], col[2])
|
|
78
|
+
|
|
79
|
+
def _run(self):
|
|
80
|
+
print("x range :[-%.2e,%.2e]\ny range :[-%.2e,%.2e]" % (
|
|
81
|
+
self.xmax, self.xmax, self.ymax, self.ymax))
|
|
82
|
+
if self.ifunc is None:
|
|
83
|
+
raise Exception("请设置迭代函数")
|
|
84
|
+
tn = 5
|
|
85
|
+
ci = self.width // tn
|
|
86
|
+
cj = self.height // tn
|
|
87
|
+
ts = []
|
|
88
|
+
for i in range(tn):
|
|
89
|
+
for j in range(tn):
|
|
90
|
+
t = Thread(target=self.__calc, args=([i * ci, j * cj], ci, cj))
|
|
91
|
+
t.start()
|
|
92
|
+
ts.append(t)
|
|
93
|
+
for t in ts:
|
|
94
|
+
t.join()
|
|
95
|
+
del ts
|
|
96
|
+
|
|
97
|
+
def doCifs(self, N):
|
|
98
|
+
# 进入迭代
|
|
99
|
+
# N: 单点最大迭代次数
|
|
100
|
+
self.N = N
|
fractal/colors.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
颜色的RGB值,列表索引从小到大颜色依次加深
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
blues = [(255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (254, 254, 254), (254, 254, 254), (254, 254, 254), (254, 254, 254), (253, 253, 253), (253, 253, 253), (254, 255, 255), (254, 255, 255), (253, 254, 255), (249, 253, 255), (248, 252, 255), (246, 250, 253), (245, 249, 252), (245, 249, 252), (245, 250, 253), (245, 250, 253), (245, 250, 253), (243, 251, 253), (242, 250, 252), (242, 250, 252), (242, 250, 252), (242, 250, 252), (241, 249, 252), (241, 249, 252), (241, 249, 252), (240, 248, 251), (240, 248, 251), (239, 248, 253), (238, 247, 252), (238, 247, 252), (238, 247, 254), (237, 248, 254), (237, 248, 254), (237, 248, 254), (237, 248, 254), (236, 246, 255), (233, 246, 254), (233, 246, 254), (231, 246, 253), (230, 244, 253), (230, 244, 253), (227, 244, 252), (226, 243, 251), (226, 243, 251), (225, 242, 250), (225, 242, 252), (223, 241, 251), (223, 241, 251), (222, 240, 252), (221, 241, 252), (220, 240, 251), (219, 239, 250), (219, 239, 250), (219, 239, 250), (212, 239, 250), (212, 239, 250), (211, 237, 250), (210, 236, 249), (208, 236, 248), (207, 235, 249), (205, 234, 248), (205, 234, 250), (202, 234, 249), (201, 232, 250), (200, 233, 250), (199, 232, 251), (196, 231, 250), (195, 230, 250), (194, 229, 249), (194, 229, 249), (187, 230, 249), (187, 230, 249), (186, 229, 248), (183, 228, 249), (180, 226, 249), (178, 226, 248), (175, 225, 248), (174, 226, 250), (170, 224, 250), (167, 223, 248), (164, 221, 248), (161, 221, 249), (160, 221, 249), (160, 221, 250), (156, 220, 248), (154, 218, 246), (151, 218, 247), (150, 217, 246), (147, 216, 247), (144, 215, 245), (139, 214, 245), (136, 212, 244), (132, 212, 245), (128, 212, 246), (122, 211, 245), (118, 211, 245), (114, 211, 246), (111, 209, 246), (106, 208, 246), (103, 207, 246), (102, 206, 245), (99, 205, 244), (86, 205, 248), (82, 203, 246), (77, 200, 244), (72, 199, 244), (67, 198, 244), (61, 198, 244), (54, 196, 244), (47, 195, 243), (39, 195, 244), (32, 193, 245), (24, 191, 243), (17, 191, 242), (13, 190, 244), (9, 190, 245), (5, 189, 243), (2, 187, 243), (2, 185, 243), (1, 184, 242), (0, 183, 241), (0, 182, 242), (0, 180, 241), (0, 179, 240), (0, 178, 240), (0, 177, 239), (0, 175, 240), (0, 174, 239), (0, 173, 239), (0, 173, 239), (1, 172, 241), (0, 171, 240), (0, 171, 240), (0, 171, 240), (0, 169, 236), (1, 170, 237), (1, 170, 237), (0, 169, 234), (1, 169, 234), (0, 168, 233), (1, 166, 232), (0, 165, 231), (1, 164, 229), (1, 164, 229), (1, 163, 228), (0, 162, 226), (2, 161, 226), (1, 160, 225), (0, 159, 224), (0, 159, 224), (0, 159, 221), (0, 159, 221),
|
|
8
|
+
(0, 159, 221), (0, 159, 221), (0, 158, 220), (0, 157, 220), (0, 157, 220), (1, 156, 220), (0, 155, 219), (0, 154, 218), (0, 154, 218), (0, 152, 217), (0, 152, 217), (0, 151, 216), (1, 150, 216), (0, 149, 215), (2, 150, 214), (2, 150, 212), (1, 149, 211), (1, 149, 211), (0, 148, 210), (0, 147, 209), (1, 146, 209), (1, 147, 207), (1, 147, 207), (1, 145, 206), (1, 145, 205), (0, 144, 204), (0, 144, 204), (0, 143, 203), (0, 142, 202), (0, 142, 202), (1, 141, 202), (1, 141, 202), (0, 140, 201), (0, 140, 201), (0, 139, 198), (0, 138, 197), (0, 138, 197), (0, 138, 197), (1, 137, 195), (0, 136, 194), (0, 136, 194), (0, 136, 194), (1, 136, 192), (0, 135, 191), (0, 135, 191), (0, 135, 191), (0, 132, 189), (0, 132, 189), (0, 132, 189), (0, 131, 188), (0, 131, 188), (0, 131, 188), (0, 130, 188), (0, 129, 187), (0, 128, 186), (0, 128, 186), (0, 128, 186), (0, 128, 186), (0, 127, 185), (0, 127, 185), (0, 127, 185), (0, 127, 185), (0, 125, 181), (0, 125, 181), (0, 124, 180), (0, 124, 180), (0, 124, 180), (0, 123, 180), (0, 122, 179), (0, 122, 179), (0, 121, 178), (1, 122, 179), (1, 122, 179), (1, 122, 179), (1, 122, 179), (1, 120, 178), (0, 119, 177), (0, 119, 177), (0, 117, 171), (0, 117, 171), (0, 115, 170), (0, 115, 170), (0, 115, 170), (0, 114, 169), (0, 114, 169), (0, 113, 169), (0, 113, 169), (0, 113, 169), (0, 113, 169), (1, 112, 168), (1, 112, 168), (1, 112, 168), (0, 111, 167), (0, 111, 167), (0, 109, 165), (0, 109, 165), (0, 109, 165), (0, 109, 165), (0, 109, 165), (2, 109, 165), (2, 109, 165), (2, 109, 165), (0, 107, 163), (1, 106, 163), (1, 106, 163), (0, 105, 162), (0, 104, 161), (0, 104, 161), (0, 102, 160), (0, 102, 160), (0, 105, 160), (0, 105, 160), (0, 104, 159), (0, 103, 158), (0, 103, 158), (0, 102, 155), (0, 101, 155), (0, 100, 154), (0, 98, 153), (0, 97, 150), (0, 97, 150), (1, 96, 150), (1, 96, 150), (0, 95, 149), (0, 96, 147), (1, 95, 147), (1, 95, 149), (1, 95, 149), (0, 94, 148), (0, 94, 148), (0, 94, 148), (0, 94, 148), (1, 92, 145), (1, 92, 145), (0, 90, 143), (0, 90, 143), (1, 91, 143), (1, 91, 143), (1, 91, 143), (2, 92, 144), (2, 92, 144), (4, 91, 144), (0, 89, 141), (0, 89, 141), (1, 88, 141), (1, 88, 141), (0, 87, 140), (0, 87, 140), (0, 87, 140), (1, 86, 140), (0, 85, 139), (0, 85, 139), (0, 85, 139), (2, 85, 139), (2, 85, 139), (2, 85, 139), (2, 85, 139), (2, 85, 139), (0, 83, 133), (0, 83, 133), (0, 83, 133), (0, 82, 132), (0, 82, 132), (0, 82, 132), (0, 82, 132), (0, 81, 131), (0, 80, 131), (0, 80, 131), (0, 80, 131), (0, 80, 131), (0, 79, 130), (1, 78, 130), (1, 78, 130), (1, 78, 130), (0, 78, 127), (0, 76, 126), (0, 76, 126), (0, 76, 126), (0, 76, 126), (0, 75, 125), (0, 75, 125), (1, 74, 125), (1, 74, 125), (1, 74, 125), (2, 73, 125), (1, 72, 124), (1, 72, 124), (1, 72, 124), (0, 71, 123), (0, 71, 123), (0, 71, 121), (0, 71, 121), (0, 71, 121), (0, 71, 121), (0, 69, 120), (0, 69, 120), (0, 69, 120), (0, 69, 120), (0, 67, 119), (0, 67, 119), (0, 66, 118), (0, 66, 118), (0, 66, 118), (0, 65, 117), (0, 65, 117), (0, 65, 117), (1, 65, 113), (1, 65, 113), (1, 65, 113), (0, 64, 112), (0, 64, 112), (0, 64, 112), (0, 62, 111), (0, 62, 111), (1, 63, 112), (1, 63, 112), (1, 61, 111), (1, 61, 111), (0, 60, 110), (0, 59, 109), (0, 59, 109)]
|
|
9
|
+
|
|
10
|
+
reds = [(255, 254, 255), (255, 254, 255), (255, 255, 255), (255, 255, 255), (255, 255, 255), (254, 255, 255), (254, 255, 255), (255, 255, 255), (254, 254, 254), (253, 251, 252), (252, 248, 247), (250, 244, 244), (249, 240, 241), (248, 238, 239), (248, 236, 238), (248, 236, 238), (255, 228, 227), (255, 225, 225), (253, 221, 222), (252, 218, 219), (252, 216, 216), (252, 212, 213), (250, 208, 209), (251, 205, 207), (253, 203, 204), (254, 199, 202), (254, 195, 197), (254, 192, 195), (254, 190, 191), (253, 187, 189), (252, 183, 186), (250, 181, 184), (253, 177, 177), (252, 174, 174), (250, 170, 171), (249, 167, 169), (250, 164, 165), (249, 160, 162), (248, 156, 157), (251, 152, 155), (251, 148, 151), (251, 145, 149), (251, 141, 144), (252, 137, 142), (252, 136, 139), (253, 132, 137), (252, 129, 134), (249, 126, 131), (253, 120, 125), (252, 119, 122), (251, 116, 120), (249, 113, 117), (249, 108, 114), (248, 105, 109), (248, 101, 107), (249, 98, 103), (251, 96, 102), (251, 92, 97), (251, 88, 93), (250, 85, 91), (251, 82, 89), (250, 79, 85), (246, 75, 81), (246, 73, 79), (252, 67, 75), (250, 63, 72), (247, 60, 69), (248, 57, 65), (249, 55, 64), (249, 52, 62), (248, 48, 58), (249, 45, 56), (248, 41, 51), (249, 38, 47), (249, 33, 44), (248, 31, 40), (249, 27, 38), (248, 24, 35), (246, 20, 32),
|
|
11
|
+
(244, 18, 29), (247, 18, 25), (247, 18, 25), (247, 16, 24), (247, 16, 24), (246, 15, 23), (249, 15, 24), (249, 15, 24), (249, 15, 24), (246, 15, 23), (247, 16, 24), (246, 17, 24), (244, 17, 24), (243, 18, 24), (241, 18, 23), (238, 17, 22), (238, 17, 22), (236, 17, 23), (235, 16, 22), (235, 16, 22), (232, 15, 23), (231, 16, 23), (229, 16, 22), (226, 15, 21), (225, 16, 21), (221, 14, 20), (220, 15, 20), (219, 16, 20), (216, 15, 21), (214, 15, 20), (214, 15, 20), (211, 15, 19), (211, 15, 19), (208, 15, 18), (208, 15, 18), (207, 14, 19), (205, 14, 19), (203, 14, 18), (200, 14, 19), (199, 14, 19), (198, 15, 20), (193, 12, 17), (192, 13, 19), (189, 12, 18), (188, 13, 20), (186, 13, 19), (185, 12, 18), (183, 12, 20), (183, 12, 20), (174, 13, 19), (174, 13, 19), (172, 13, 18), (171, 12, 17), (169, 13, 17), (168, 12, 16), (167, 12, 16), (166, 11, 15), (166, 11, 15), (167, 11, 15), (169, 10, 15), (170, 11, 16), (171, 10, 16), (173, 9, 16), (174, 10, 17), (175, 10, 17), (172, 11, 17), (172, 11, 17), (171, 12, 17), (171, 12, 17), (172, 13, 18), (173, 14, 18), (175, 14, 19), (176, 15, 20), (179, 16, 19), (180, 15, 19), (182, 16, 20), (185, 14, 20), (188, 15, 21), (190, 15, 20), (191, 14, 20), (191, 14, 20), (199, 14, 20), (200, 13, 20), (201, 14, 21), (204, 15, 22), (205, 14, 22), (208, 14, 23)]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
purples = [(235, 233, 236), (235, 233, 236), (235, 233, 236), (236, 231, 235), (235, 230, 236), (235, 230, 236), (234, 229, 236), (234, 229, 236), (237, 229, 240), (236, 228, 239), (235, 227, 240), (236, 225, 239), (235, 224, 240), (234, 223, 239), (234, 223, 239), (233, 222, 238), (234, 221, 239), (233, 220, 238), (232, 219, 237), (232, 219, 237), (230, 217, 237), (230, 217, 237), (230, 215, 238), (229, 214, 237), (228, 212, 238), (229, 211, 237), (228, 209, 237), (228, 209, 237), (226, 207, 237), (226, 207, 237), (225, 206, 236), (225, 204, 235), (225, 200, 239), (225, 200, 239), (224, 199, 238), (223, 198, 237), (223, 196, 237), (222, 195, 236), (223, 194, 238), (222, 193, 237), (222, 190, 237), (222, 190, 237), (222, 188, 238), (221, 187, 237), (221, 184, 236), (220, 183, 235), (219, 182, 234), (218, 181, 233), (218, 179, 236), (218, 179, 236), (217, 177, 237), (216, 176, 236), (215, 173, 236), (214, 172, 235), (215, 170, 235), (215, 170, 237), (214, 167, 237), (213, 166, 236), (214, 164, 237), (213, 162, 237), (213, 160, 238), (211, 158, 236), (210, 157, 235), (210, 157, 235), (210, 155, 238), (209, 154, 237), (210, 152, 237), (209, 151, 236), (208, 150, 237), (207, 147, 235), (206, 146, 236), (208, 144, 238), (206, 142, 238), (206, 140, 238), (205, 139, 237), (205, 137, 238), (204, 136, 239), (205, 134, 238), (204, 133, 237), (203, 132, 238), (202, 128, 239), (202, 128, 239), (203, 126, 238), (201, 124, 238), (200, 123, 237),
|
|
15
|
+
(200, 121, 238), (199, 120, 239), (199, 118, 238), (197, 116, 237), (198, 114, 238), (197, 112, 239), (197, 111, 238), (195, 108, 238), (196, 106, 238), (195, 105, 237), (195, 105, 237), (192, 102, 236), (191, 101, 235), (191, 99, 236), (190, 98, 235), (189, 96, 236), (189, 95, 235), (188, 93, 235), (189, 91, 236), (187, 89, 236), (188, 88, 238), (187, 87, 237), (187, 84, 237), (186, 82, 237), (186, 81, 236), (185, 80, 235), (184, 78, 236), (182, 76, 236), (183, 75, 236), (182, 74, 237), (181, 73, 236), (179, 71, 235), (180, 69, 234), (179, 68, 235), (180, 67, 237), (181, 65, 238), (181, 64, 239), (181, 62, 238), (180, 61, 239), (178, 59, 239), (178, 57, 238), (177, 56, 237), (176, 55, 238), (176, 52, 238), (175, 51, 237), (175, 50, 238), (174, 49, 237), (174, 48, 236), (173, 46, 237), (174, 45, 236), (174, 44, 238), (173, 41, 236), (172, 40, 237), (174, 39, 238), (173, 38, 237), (173, 36, 236), (172, 35, 237), (171, 34, 236), (171, 34, 236), (169, 31, 238), (168, 30, 237), (169, 29, 239), (168, 28, 238), (167, 27, 237), (166, 26, 237), (165, 25, 236), (167, 24, 238), (166, 23, 237), (165, 22, 238), (164, 21, 237), (165, 19, 239), (163, 17, 237), (163, 17, 238), (162, 16, 237), (161, 15, 236), (163, 12, 239), (162, 11, 238), (162, 11, 238), (161, 10, 237), (161, 10, 237), (160, 9, 238), (160, 7, 237), (160, 7, 237), (158, 5, 235), (160, 4, 236), (160, 4, 236), (159, 3, 235), (158, 2, 234), (158, 2, 234), (158, 0, 235)]
|
|
16
|
+
|
|
17
|
+
yellows = [(250, 248, 249), (249, 247, 248), (249, 247, 248), (249, 247, 248), (249, 248, 246), (248, 247, 245), (248, 247, 243), (248, 247, 243), (248, 247, 242), (248, 247, 242), (248, 248, 240), (248, 248, 240), (248, 248, 238), (248, 248, 238), (248, 248, 238), (248, 248, 238), (250, 248, 235), (250, 248, 235), (250, 248, 233), (250, 248, 233), (250, 249, 231), (250, 249, 231), (250, 249, 229), (250, 249, 228), (248, 248, 224), (248, 248, 222), (248, 248, 222), (248, 248, 220), (248, 249, 218), (248, 249, 218), (248, 249, 218), (248, 249, 217), (252, 249, 214), (252, 249, 214), (252, 249, 214), (251, 249, 211), (251, 249, 211), (251, 249, 210), (250, 248, 207), (250, 248, 207), (250, 249, 205), (250, 249, 203), (250, 249, 201), (250, 250, 200), (249, 249, 199), (249, 249, 197), (249, 249, 197), (249, 249, 197), (250, 249, 193), (250, 249, 192), (250, 249, 192), (250, 250, 190), (249, 249, 187), (249, 249, 187), (249, 249, 185), (249, 250, 182), (249, 250, 180), (249, 251, 178), (248, 250, 175), (248, 251, 174), (248, 251, 174), (247, 250, 171), (247, 250, 169), (247, 250, 169), (248, 250, 167), (250, 249, 166), (250, 249, 166), (249, 249, 163), (249, 249, 161), (249, 249, 161), (248, 248, 158), (249, 248, 155), (249, 249, 153), (249, 249, 151), (251, 249, 149), (250, 248, 147), (250, 248, 147), (250, 248, 145), (250, 249, 143), (249, 248, 142), (247, 249, 139), (247, 250, 137), (247, 250, 137), (249, 249, 135), (248, 249, 132), (248, 249, 132), (248, 249, 130),
|
|
18
|
+
(248, 250, 127), (250, 250, 126), (249, 250, 123), (249, 250, 122), (250, 250, 120), (250, 250, 120), (249, 249, 117), (249, 249, 115), (249, 249, 115), (249, 250, 112), (249, 250, 110), (248, 249, 109), (248, 250, 107), (248, 250, 105), (249, 248, 104), (249, 249, 103), (249, 249, 99), (248, 249, 96), (249, 248, 95), (249, 249, 93), (248, 248, 90), (248, 248, 90), (248, 248, 88), (250, 248, 86), (249, 247, 85), (249, 250, 84), (251, 250, 84), (251, 250, 84), (251, 250, 82), (250, 249, 79), (250, 250, 78), (250, 250, 76), (250, 249, 73), (249, 248, 70), (249, 248, 69), (250, 248, 67), (250, 248, 65), (249, 248, 62), (249, 248, 62), (249, 248, 60), (249, 248, 60), (251, 248, 57), (251, 248, 57), (250, 247, 54), (250, 247, 54), (250, 248, 52), (249, 247, 51), (249, 247, 50), (249, 247, 48), (250, 249, 47), (250, 249, 45), (249, 248, 44), (249, 248, 42), (249, 249, 41), (249, 249, 41), (248, 248, 40), (248, 248, 38), (250, 249, 35), (250, 249, 35), (250, 249, 35), (249, 248, 33), (249, 248, 33), (249, 249, 31), (248, 248, 28), (248, 248, 28), (248, 248, 26), (248, 249, 25), (248, 249, 23), (248, 249, 21), (248, 249, 21), (248, 250, 19), (248, 250, 19), (248, 250, 19), (249, 249, 15), (249, 249, 15), (249, 249, 15), (249, 249, 15), (249, 249, 13), (249, 249, 13), (249, 249, 11), (249, 249, 11), (249, 250, 9), (249, 250, 9), (249, 250, 8), (249, 250, 8), (248, 249, 5), (248, 249, 5), (248, 249, 5), (248, 249, 5), (248, 248, 0), (248, 248, 0), (248, 248, 0), (248, 248, 0)]
|
|
19
|
+
|
|
20
|
+
rainbow = [(118, 16, 11), (120, 17, 12), (125, 18, 10), (131, 20, 11), (140, 23, 13), (149, 26, 11), (158, 28, 12), (166, 29, 10), (175, 34, 7), (184, 36, 6), (193, 41, 4), (200, 44, 3), (206, 48, 3), (212, 52, 4), (218, 57, 5), (222, 61, 7), (234, 65, 0), (237, 68, 0), (243, 72, 2), (246, 74, 2), (248, 76, 2), (250, 77, 1), (252, 79, 2), (255, 81, 0), (255, 81, 0), (255, 83, 0), (255, 84, 0), (255, 86, 1), (255, 86, 1), (255, 87, 2), (255, 89, 3), (255, 89, 3), (253, 90, 0), (254, 91, 0), (254, 91, 0), (255, 92, 1), (255, 92, 1), (254, 94, 0), (254, 94, 0), (254, 94, 0), (254, 94, 0), (253, 95, 0), (254, 96, 0), (254, 96, 0), (255, 97, 0), (255, 98, 1), (255, 100, 0), (255, 100, 0), (255, 101, 3), (254, 102, 1), (255, 104, 3), (255, 106, 3), (255, 109, 5), (255, 111, 4), (255, 114, 4), (255, 115, 2), (254, 118, 0), (255, 121, 0), (254, 124, 0), (254, 126, 0), (252, 127, 0), (253, 128, 0), (254, 131, 1), (255, 134, 1), (252, 136, 0), (254, 138, 1), (254, 141, 1), (255, 144, 2), (254, 145, 2), (254, 148, 0), (253, 152, 0), (254, 155, 0), (253, 159, 1), (254, 162, 1), (253, 166, 1), (253, 168, 0), (252, 170, 0), (253, 173, 0), (255, 175, 0), (255, 178, 2), (251, 186, 0), (252, 187, 1), (252, 189, 0), (255, 192, 3), (254, 195, 3), (255, 198, 3), (255, 201, 4), (253, 205, 1), (255, 209, 2), (253, 211, 2), (253, 213, 1), (253, 217, 1), (253, 220, 1), (255, 223, 1), (254, 224, 2), (255, 226, 2), (252, 230, 0), (252, 230, 0), (252, 231, 0), (251, 233, 1), (252, 236, 1), (251, 239, 3), (248, 241, 2), (245, 243, 2), (244, 245, 3), (241, 247, 1), (239, 248, 1), (238, 250, 2), (236, 251, 0), (236, 253, 1), (234, 253, 1), (233, 254, 1), (228, 255, 0), (225, 255, 0), (224, 254, 0), (223, 254, 0), (222, 255, 0), (219, 254, 1), (215, 255, 0), (213, 254, 0), (210, 255, 0), (208, 255, 1), (204, 255, 0), (202, 255, 1), (199, 255, 0), (198, 255, 0), (195, 255, 1), (194, 254, 0), (186, 255, 0), (183, 254, 0), (177, 254, 0), (169, 254, 2), (164, 255, 4), (156, 255, 6), (150, 254, 7), (146, 254, 8), (137, 251, 6), (129, 250, 7), (120, 249, 9), (109, 247, 11), (97, 247, 15), (87, 247, 17), (78, 245, 17), (72, 245, 16), (53, 245, 22), (49, 243, 24), (43, 240, 26), (37, 239, 31), (30, 239, 36), (23, 237, 41), (15, 236, 43), (9, 235, 47),
|
|
21
|
+
(5, 238, 51), (3, 238, 57), (2, 239, 63), (2, 239, 71), (2, 238, 78), (3, 237, 86), (5, 237, 93), (5, 236, 96), (2, 239, 109), (2, 238, 112), (3, 238, 120), (4, 237, 128), (6, 238, 139), (8, 238, 148), (6, 238, 154), (6, 237, 159), (5, 235, 162), (5, 237, 164), (5, 235, 170), (6, 235, 176), (6, 234, 181), (7, 234, 188), (7, 233, 193), (5, 234, 195), (4, 231, 204), (6, 230, 204), (6, 230, 206), (6, 229, 208), (6, 229, 211), (6, 228, 215), (6, 227, 218), (7, 226, 222), (7, 225, 226), (7, 224, 229), (8, 222, 232), (7, 220, 234), (5, 218, 236), (3, 215, 236), (3, 215, 237), (3, 215, 237), (6, 209, 239), (6, 207, 237), (4, 204, 237), (3, 201, 236), (4, 200, 238), (5, 196, 238), (5, 193, 238), (5, 189, 239), (6, 186, 239), (7, 183, 240), (8, 180, 242), (7, 176, 241), (6, 173, 241), (6, 171, 239), (5, 167, 239), (4, 166, 238), (5, 161, 238), (4, 160, 237), (5, 158, 236), (4, 155, 236), (3, 152, 236), (3, 149, 235), (5, 146, 236), (5, 144, 237), (6, 140, 237), (6, 138, 239), (8, 135, 240), (7, 132, 240), (7, 129, 240), (6, 126, 238), (5, 125, 238), (6, 123, 237), (5, 118, 238), (5, 115, 236), (3, 113, 236), (2, 110, 236), (4, 109, 237), (4, 105, 239), (4, 102, 239), (5, 98, 238), (3, 93, 239), (5, 91, 240), (7, 88, 240), (5, 84, 237), (4, 81, 237), (3, 77, 234), (3, 75, 235), (2, 74, 234), (8, 66, 238), (7, 64, 239), (6, 63, 238), (5, 61, 236), (3, 58, 236), (3, 55, 235), (4, 52, 236), (5, 50, 237), (6, 47, 237), (9, 43, 238), (10, 39, 237), (10, 36, 235), (12, 33, 236), (15, 31, 238), (16, 31, 238), (18, 30, 238), (23, 24, 238), (24, 23, 238), (26, 23, 238), (27, 20, 237), (32, 18, 237), (35, 15, 236), (38, 13, 236), (42, 11, 236), (44, 7, 234), (48, 7, 235), (51, 5, 235), (53, 6, 236), (54, 5, 236), (57, 5, 237), (57, 5, 237), (58, 4, 237), (63, 4, 236), (65, 6, 238), (68, 6, 239), (69, 5, 239), (71, 5, 237), (73, 3, 237), (77, 5, 237), (81, 5, 238), (82, 4, 237), (85, 3, 237), (87, 3, 236), (91, 3, 237), (94, 5, 237), (98, 6, 239), (99, 5, 239), (99, 5, 239), (106, 4, 238), (106, 4, 236), (108, 4, 237), (111, 5, 238), (113, 5, 239), (116, 4, 238), (118, 4, 239), (121, 3, 237), (123, 4, 236), (128, 4, 238), (131, 5, 237), (134, 4, 238), (136, 4, 238), (137, 3, 237), (141, 5, 239), (143, 7, 241), (143, 6, 236), (143, 6, 236)]
|
fractal/ifs.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
迭代函数系统(IFS)
|
|
3
|
+
"""
|
|
4
|
+
from random import random
|
|
5
|
+
from math import sin, cos
|
|
6
|
+
from .base import Base, HAS_NUMPY
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def getIfsCode(matrix):
|
|
10
|
+
# 极坐标形式变换到矩阵
|
|
11
|
+
res = []
|
|
12
|
+
for i in matrix:
|
|
13
|
+
r, s, the, fu, e, f, p = i
|
|
14
|
+
item = [r * cos(the), -s * sin(fu), r * sin(the), s * cos(fu), e, f, p]
|
|
15
|
+
res.append(item)
|
|
16
|
+
return res
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IFS(Base):
|
|
20
|
+
|
|
21
|
+
def __init__(self, size, title="", color=None):
|
|
22
|
+
Base.__init__(self, size, self._run, title)
|
|
23
|
+
self.setPx()
|
|
24
|
+
self.setIfsCode()
|
|
25
|
+
self.__coo = True
|
|
26
|
+
if color:
|
|
27
|
+
self.color = color
|
|
28
|
+
else:
|
|
29
|
+
self.color = [255, 255, 255]
|
|
30
|
+
self.fill(self.color)
|
|
31
|
+
|
|
32
|
+
def setCoordinate(self):
|
|
33
|
+
# 纵轴的反向
|
|
34
|
+
self.__coo = not self.__coo
|
|
35
|
+
|
|
36
|
+
def ifsp(self, x, y):
|
|
37
|
+
# 变换规则(返回迭代后的坐标)
|
|
38
|
+
return (x, y)
|
|
39
|
+
|
|
40
|
+
def setIfsp(self, call):
|
|
41
|
+
self.ifsp = call
|
|
42
|
+
|
|
43
|
+
def setIfsCode(self, ifsCode=None):
|
|
44
|
+
# 设置ifs码
|
|
45
|
+
self.ifsCode = ifsCode
|
|
46
|
+
if ifsCode != None:
|
|
47
|
+
p = [i[-1] for i in self.ifsCode]
|
|
48
|
+
self.__fn = len(p)
|
|
49
|
+
sp = sum(p)
|
|
50
|
+
self.__pe = [sum(p[:i + 1])/sp for i in range(self.__fn)]
|
|
51
|
+
|
|
52
|
+
def __parseIfsCode(self, x, y):
|
|
53
|
+
rand = random()
|
|
54
|
+
i = 0
|
|
55
|
+
while i < self.__fn:
|
|
56
|
+
if rand <= self.__pe[i]:
|
|
57
|
+
a, b, c, d, e, f, p = self.ifsCode[i]
|
|
58
|
+
return (a * x + b * y + e, c * x + d * y + f)
|
|
59
|
+
i += 1
|
|
60
|
+
return (0, 0)
|
|
61
|
+
|
|
62
|
+
def setPx(self, enlarge=1, pl=0, pt=0):
|
|
63
|
+
# 平移、放大调整
|
|
64
|
+
self.enlarge = enlarge
|
|
65
|
+
self.pl = pl
|
|
66
|
+
self.pt = pt
|
|
67
|
+
|
|
68
|
+
def _run(self):
|
|
69
|
+
start = self.start
|
|
70
|
+
for i in range(self.n):
|
|
71
|
+
px = int(self.enlarge * start[0] + self.pl)
|
|
72
|
+
py = int(self.enlarge * start[1] + self.pt)
|
|
73
|
+
if not self.__coo:
|
|
74
|
+
py = self.height - py
|
|
75
|
+
if HAS_NUMPY and self._pixel_array is not None:
|
|
76
|
+
if 0 <= px < self.width and 0 <= py < self.height:
|
|
77
|
+
self._pixel_array[py, px] = [int(self.pcolor[0]), int(self.pcolor[1]), int(self.pcolor[2])]
|
|
78
|
+
else:
|
|
79
|
+
self.set_pixel_fast(px, py, self.pcolor[0], self.pcolor[1], self.pcolor[2])
|
|
80
|
+
start = self.ifsp(*start)
|
|
81
|
+
|
|
82
|
+
def doIFS(self, n, start=None, color=None):
|
|
83
|
+
"""
|
|
84
|
+
开始迭代
|
|
85
|
+
start: 迭代起点
|
|
86
|
+
color: 描点的颜色
|
|
87
|
+
"""
|
|
88
|
+
self.n = n
|
|
89
|
+
if start == None:
|
|
90
|
+
self.start = (0, 0)
|
|
91
|
+
else:
|
|
92
|
+
self.start = start
|
|
93
|
+
if color == None:
|
|
94
|
+
self.pcolor = [0, 0, 0]
|
|
95
|
+
else:
|
|
96
|
+
self.pcolor = color
|
|
97
|
+
if self.ifsCode != None:
|
|
98
|
+
self.ifsp = self.__parseIfsCode
|
fractal/julia.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Julia集
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import Base, HAS_NUMPY
|
|
6
|
+
from .colors import *
|
|
7
|
+
from threading import Thread
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Julia(Base):
|
|
11
|
+
|
|
12
|
+
def __init__(self, size, title=""):
|
|
13
|
+
Base.__init__(self, size, self._run, title)
|
|
14
|
+
self.setExp(2)
|
|
15
|
+
self.setC(None)
|
|
16
|
+
self.setRadius(2)
|
|
17
|
+
self.width = size[0]
|
|
18
|
+
self.height = size[1]
|
|
19
|
+
self.setRange(3.5, 3.5)
|
|
20
|
+
self.setCentre(0 + 0j)
|
|
21
|
+
|
|
22
|
+
def setRadius(self, R):
|
|
23
|
+
# 设置逃逸半径
|
|
24
|
+
self.R = R
|
|
25
|
+
|
|
26
|
+
def setC(self, C):
|
|
27
|
+
# 设置参考值C
|
|
28
|
+
self.C = C
|
|
29
|
+
|
|
30
|
+
def setExp(self, expc):
|
|
31
|
+
# 设置指数,默认2
|
|
32
|
+
self.expc = expc
|
|
33
|
+
|
|
34
|
+
def color(self, n, r=2):
|
|
35
|
+
if n < len(reds):
|
|
36
|
+
return reds[n]
|
|
37
|
+
else:
|
|
38
|
+
if r < self.R:
|
|
39
|
+
return blues[int((len(blues) - 1) * r / self.R)]
|
|
40
|
+
else:
|
|
41
|
+
return purples[int((len(purples) - 1) * self.R / r)]
|
|
42
|
+
|
|
43
|
+
def setColor(self, call):
|
|
44
|
+
self.color = call
|
|
45
|
+
|
|
46
|
+
def setCentre(self, z0):
|
|
47
|
+
# 设置中心点
|
|
48
|
+
self.z0 = z0
|
|
49
|
+
|
|
50
|
+
def setRange(self, xmax, ymax):
|
|
51
|
+
# 设置坐标范围,范围越小图放大倍数越高
|
|
52
|
+
self.xmax = xmax
|
|
53
|
+
self.ymax = ymax
|
|
54
|
+
|
|
55
|
+
def __getXY(self, i, j):
|
|
56
|
+
# 通过像素坐标获取映射后的坐标
|
|
57
|
+
return complex((i / self.width - 0.5) * self.xmax + self.z0.real,
|
|
58
|
+
(j / self.height - 0.5) * self.ymax + self.z0.imag)
|
|
59
|
+
|
|
60
|
+
def scala(self, i, j, rate):
|
|
61
|
+
# 将(i, j)像素点置于中心位置,放大rete倍
|
|
62
|
+
self.setCentre(self.__getXY(i, j))
|
|
63
|
+
self.xmax /= rate
|
|
64
|
+
self.ymax /= rate
|
|
65
|
+
|
|
66
|
+
def __calc(self, start, w, h):
|
|
67
|
+
"""计算子区域像素"""
|
|
68
|
+
for i in range(start[0], start[0] + w):
|
|
69
|
+
for j in range(start[1], start[1] + h):
|
|
70
|
+
ct = 0
|
|
71
|
+
z = self.__getXY(i, j)
|
|
72
|
+
for k in range(self.N):
|
|
73
|
+
ct = k
|
|
74
|
+
if abs(z) > self.R:
|
|
75
|
+
break
|
|
76
|
+
z = z**self.expc + self.C
|
|
77
|
+
col = self.color(ct, abs(z))
|
|
78
|
+
# 直接写入numpy数组
|
|
79
|
+
if HAS_NUMPY and self._pixel_array is not None:
|
|
80
|
+
self._pixel_array[j, i] = [int(col[0]), int(col[1]), int(col[2])]
|
|
81
|
+
else:
|
|
82
|
+
self.set_pixel_fast(i, j, col[0], col[1], col[2])
|
|
83
|
+
|
|
84
|
+
def _run(self):
|
|
85
|
+
# 线程中
|
|
86
|
+
print("x range :[-%.2e,%.2e]\ny range :[-%.2e,%.2e]" % (
|
|
87
|
+
self.xmax, self.xmax, self.ymax, self.ymax))
|
|
88
|
+
if self.C == None:
|
|
89
|
+
raise Exception("请设置迭代常数")
|
|
90
|
+
tn = 5 # 25 个子线程绘图
|
|
91
|
+
ci = self.width // tn
|
|
92
|
+
cj = self.height // tn
|
|
93
|
+
ts = []
|
|
94
|
+
for i in range(tn):
|
|
95
|
+
for j in range(tn):
|
|
96
|
+
t = Thread(target=self.__calc, args=([i * ci, j * cj], ci, cj))
|
|
97
|
+
t.start()
|
|
98
|
+
ts.append(t)
|
|
99
|
+
for t in ts:
|
|
100
|
+
t.join()
|
|
101
|
+
del ts
|
|
102
|
+
|
|
103
|
+
def doJulia(self, N):
|
|
104
|
+
# 进入迭代
|
|
105
|
+
# N: 单点最大迭代次数
|
|
106
|
+
self.N = N
|
fractal/lsystem.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
L-System for fractal
|
|
3
|
+
已完成功能:
|
|
4
|
+
1.简单D0L-系统
|
|
5
|
+
2.合成D0L-系统
|
|
6
|
+
3.简单分叉结构
|
|
7
|
+
待完成功能:
|
|
8
|
+
1.带年龄树(分叉)的生成
|
|
9
|
+
2.随机L-系统
|
|
10
|
+
3.参数L-系统
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from math import sin, cos, pi
|
|
14
|
+
from .base import Base
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def left(screen, st, angle, d):
|
|
18
|
+
# st: 起点坐标
|
|
19
|
+
# angle: 向左偏转的度数
|
|
20
|
+
# d: 距离
|
|
21
|
+
angle = pi * angle / 180
|
|
22
|
+
return [st[0] + d * cos(angle), st[1] - d * sin(angle)]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Pen(Base):
|
|
26
|
+
|
|
27
|
+
def __init__(self, size, title=""):
|
|
28
|
+
# size 画布的宽高 [width, hight]
|
|
29
|
+
# title 画布标题
|
|
30
|
+
Base.__init__(self, size, self._run, title)
|
|
31
|
+
self.setPoint([size[0] / 2, size[1] / 2])
|
|
32
|
+
self.setColor([0, 0, 0])
|
|
33
|
+
self.setWidth(1)
|
|
34
|
+
self.setAngle(0)
|
|
35
|
+
|
|
36
|
+
def setAngle(self, angle):
|
|
37
|
+
self.angle = angle
|
|
38
|
+
|
|
39
|
+
def setPoint(self, pos):
|
|
40
|
+
# 设置笔的位置
|
|
41
|
+
self.pos = pos
|
|
42
|
+
|
|
43
|
+
def setWidth(self, width):
|
|
44
|
+
# 设置线宽
|
|
45
|
+
self.width = width
|
|
46
|
+
|
|
47
|
+
def setColor(self, color):
|
|
48
|
+
# 设置颜色
|
|
49
|
+
self.color = color
|
|
50
|
+
|
|
51
|
+
def left(self, angle):
|
|
52
|
+
# 向左转angle度
|
|
53
|
+
self.angle = self.angle + angle
|
|
54
|
+
|
|
55
|
+
def right(self, angle):
|
|
56
|
+
# 向右转angle度
|
|
57
|
+
self.angle = self.angle - angle
|
|
58
|
+
|
|
59
|
+
def forward(self, d):
|
|
60
|
+
# 向前走d步长
|
|
61
|
+
np = left(self.canvas, self.pos, self.angle, d)
|
|
62
|
+
self.draw_line(self.pos, np, self.color, self.width)
|
|
63
|
+
self.pos = np
|
|
64
|
+
|
|
65
|
+
def _run(self):
|
|
66
|
+
# 线程
|
|
67
|
+
self.draw(self.omega, self.P, self.delta, self.length)
|
|
68
|
+
|
|
69
|
+
def draw(self, omega, P, delta, length):
|
|
70
|
+
i = 0
|
|
71
|
+
while i < len(omega):
|
|
72
|
+
if omega[i] == '+':
|
|
73
|
+
self.left(delta)
|
|
74
|
+
elif omega[i] == '-':
|
|
75
|
+
self.right(delta)
|
|
76
|
+
elif omega[i] == '[':
|
|
77
|
+
k = 0
|
|
78
|
+
st = i
|
|
79
|
+
while i < len(omega):
|
|
80
|
+
if omega[i] == "[":
|
|
81
|
+
k += 1
|
|
82
|
+
elif omega[i] == "]":
|
|
83
|
+
k -= 1
|
|
84
|
+
if k == 0:
|
|
85
|
+
break
|
|
86
|
+
i += 1
|
|
87
|
+
sub = omega[st + 1:i]
|
|
88
|
+
curpoint = self.pos[:]
|
|
89
|
+
curangle = self.angle
|
|
90
|
+
self.draw(sub, P, delta, length)
|
|
91
|
+
self.pos = curpoint
|
|
92
|
+
self.angle = curangle
|
|
93
|
+
else:
|
|
94
|
+
self.forward(length)
|
|
95
|
+
i += 1
|
|
96
|
+
|
|
97
|
+
def doD0L(self, omega, P, delta, times, length, rate):
|
|
98
|
+
# omega: 公理(初始字符串)
|
|
99
|
+
# P: 产生式(映射规则)
|
|
100
|
+
# delta: 角度增量
|
|
101
|
+
# times: 迭代次数
|
|
102
|
+
# length: 初始线长
|
|
103
|
+
# rate: 每次迭代后缩小的倍数
|
|
104
|
+
length /= (rate**times)
|
|
105
|
+
for i in range(times): # 完成字符串迭代
|
|
106
|
+
ct = 0
|
|
107
|
+
for key in P:
|
|
108
|
+
omega = omega.replace(key, str(ct))
|
|
109
|
+
ct += 1
|
|
110
|
+
ct = 0
|
|
111
|
+
for key in P:
|
|
112
|
+
omega = omega.replace(str(ct), P[key])
|
|
113
|
+
ct += 1
|
|
114
|
+
self.omega = omega
|
|
115
|
+
self.P = P
|
|
116
|
+
self.delta = delta
|
|
117
|
+
self.length = length
|
fractal/mandelbrot.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mandelbrot集
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .base import Base, HAS_NUMPY
|
|
6
|
+
from .colors import *
|
|
7
|
+
from threading import Thread
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Mandelbrot(Base):
|
|
11
|
+
|
|
12
|
+
def __init__(self, size, title=""):
|
|
13
|
+
Base.__init__(self, size, self._run, title)
|
|
14
|
+
self.setExp(2)
|
|
15
|
+
self.setRadius(2)
|
|
16
|
+
self.setZ0(0 + 0j)
|
|
17
|
+
self.width = size[0]
|
|
18
|
+
self.height = size[1]
|
|
19
|
+
self.setRange(3.5, 3.5)
|
|
20
|
+
self.setCentre(0 + 0j)
|
|
21
|
+
|
|
22
|
+
def setRadius(self, R):
|
|
23
|
+
# 设置逃逸半径
|
|
24
|
+
self.R = R
|
|
25
|
+
|
|
26
|
+
def setZ0(self, Z0):
|
|
27
|
+
# 设置起始迭代复数(一般为0+0j)
|
|
28
|
+
self.Z0 = Z0
|
|
29
|
+
|
|
30
|
+
def setCentre(self, z0):
|
|
31
|
+
# 设置中心点
|
|
32
|
+
self.z0 = z0
|
|
33
|
+
|
|
34
|
+
def setRange(self, xmax, ymax):
|
|
35
|
+
# 设置坐标范围,范围越小图放大倍数越高
|
|
36
|
+
self.xmax = xmax
|
|
37
|
+
self.ymax = ymax
|
|
38
|
+
|
|
39
|
+
def __getXY(self, i, j):
|
|
40
|
+
# 通过像素坐标获取映射后的坐标
|
|
41
|
+
return complex((i / self.width - 0.5) * self.xmax + self.z0.real,
|
|
42
|
+
(j / self.height - 0.5) * self.ymax + self.z0.imag)
|
|
43
|
+
|
|
44
|
+
def scala(self, i, j, rate):
|
|
45
|
+
# 将(i, j)像素点置于中心位置,放大rete倍
|
|
46
|
+
self.setCentre(self.__getXY(i, j))
|
|
47
|
+
self.xmax /= rate
|
|
48
|
+
self.ymax /= rate
|
|
49
|
+
|
|
50
|
+
def setExp(self, expc):
|
|
51
|
+
# 设置指数,默认2
|
|
52
|
+
self.expc = expc
|
|
53
|
+
|
|
54
|
+
def color(self, n, r=2):
|
|
55
|
+
if n < len(reds):
|
|
56
|
+
return reds[n]
|
|
57
|
+
else:
|
|
58
|
+
if r < self.R:
|
|
59
|
+
return blues[int((len(blues) - 1) * r / self.R)]
|
|
60
|
+
else:
|
|
61
|
+
return purples[int((len(purples) - 1) * self.R / r)]
|
|
62
|
+
|
|
63
|
+
def setColor(self, call):
|
|
64
|
+
self.color = call
|
|
65
|
+
|
|
66
|
+
def __calc(self, start, w, h):
|
|
67
|
+
"""计算子区域像素"""
|
|
68
|
+
for i in range(start[0], start[0] + w):
|
|
69
|
+
for j in range(start[1], start[1] + h):
|
|
70
|
+
ct = 0
|
|
71
|
+
z = self.Z0
|
|
72
|
+
c = self.__getXY(i, j)
|
|
73
|
+
for k in range(self.N):
|
|
74
|
+
ct = k
|
|
75
|
+
if abs(z) > self.R:
|
|
76
|
+
break
|
|
77
|
+
z = z**self.expc + c
|
|
78
|
+
col = self.color(ct, abs(z))
|
|
79
|
+
# 直接写入numpy数组
|
|
80
|
+
if HAS_NUMPY and self._pixel_array is not None:
|
|
81
|
+
self._pixel_array[j, i] = [int(col[0]), int(col[1]), int(col[2])]
|
|
82
|
+
else:
|
|
83
|
+
self.set_pixel_fast(i, j, col[0], col[1], col[2])
|
|
84
|
+
|
|
85
|
+
def _run(self):
|
|
86
|
+
# 绘图
|
|
87
|
+
print("x range :[-%.2e,%.2e]\ny range :[-%.2e,%.2e]" % (
|
|
88
|
+
self.xmax, self.xmax, self.ymax, self.ymax))
|
|
89
|
+
tn = 5 # 25 个子线程绘图
|
|
90
|
+
ci = self.width // tn
|
|
91
|
+
cj = self.height // tn
|
|
92
|
+
ts = []
|
|
93
|
+
for i in range(tn):
|
|
94
|
+
for j in range(tn):
|
|
95
|
+
t = Thread(target=self.__calc, args=([i * ci, j * cj], ci, cj))
|
|
96
|
+
t.start()
|
|
97
|
+
ts.append(t)
|
|
98
|
+
for t in ts:
|
|
99
|
+
t.join()
|
|
100
|
+
del ts
|
|
101
|
+
|
|
102
|
+
def doMandelbrot(self, N):
|
|
103
|
+
# 开始迭代
|
|
104
|
+
# N: 最大迭代次数
|
|
105
|
+
self.N = N
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fractal
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: 分形绘图库,支持Julia集、Mandelbrot集、IFS、L-System等
|
|
5
|
+
Home-page: https://github.com/pysrc/fractal
|
|
6
|
+
Author: L.Chen
|
|
7
|
+
Author-email: 1570184051@qq.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: fractal,分形,Julia,Mandelbrot,IFS,L-System
|
|
10
|
+
Platform: any
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: Pillow>=8.0.0
|
|
27
|
+
Requires-Dist: numpy>=1.20.0
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description
|
|
32
|
+
Dynamic: description-content-type
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: keywords
|
|
35
|
+
Dynamic: license
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
Dynamic: platform
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: summary
|
|
40
|
+
|
|
41
|
+
# Draw Fractal Image By Python
|
|
42
|
+
|
|
43
|
+
A Python fractal drawing library supporting Julia Set, Mandelbrot Set, IFS, L-System and more.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install fractal
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or download the package:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Dependencies:
|
|
58
|
+
- Pillow >= 8.0.0
|
|
59
|
+
- numpy >= 1.20.0
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **Julia Set** - Interactive zoom with mouse
|
|
64
|
+
- **Mandelbrot Set** - Classic Mandelbrot fractal
|
|
65
|
+
- **IFS** - Iterated Function System (fern, leaf, tree, etc.)
|
|
66
|
+
- **L-System** - Lindenmayer System (Koch curve, dragon, tree, etc.)
|
|
67
|
+
- **CIFS** - Complex Iterated Function System
|
|
68
|
+
|
|
69
|
+
## GUI Operations
|
|
70
|
+
|
|
71
|
+
- **Save**: Click "Save" button or Ctrl+P
|
|
72
|
+
- **Save As**: Click "Save As..." to choose format (PNG/JPG/BMP)
|
|
73
|
+
- **Zoom In**: Left click to show selection box, drag to move, click again to confirm
|
|
74
|
+
- **Zoom Out**: Right click
|
|
75
|
+
- **Cancel Selection**: Press ESC or right click
|
|
76
|
+
|
|
77
|
+
## Examples
|
|
78
|
+
|
|
79
|
+
### L-System
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from fractal import Pen
|
|
83
|
+
from math import sqrt
|
|
84
|
+
p = Pen([350, 270])
|
|
85
|
+
p.setPoint([140, 60])
|
|
86
|
+
p.setWidth(1)
|
|
87
|
+
p.doD0L(omega="L", P={"L": "L+R", "R": "L-R"},
|
|
88
|
+
delta=90, times=15, length=200, rate=sqrt(2))
|
|
89
|
+
p.wait()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+

|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from fractal import Pen
|
|
96
|
+
|
|
97
|
+
p = Pen([500, 500], title="Window")
|
|
98
|
+
p.setPoint([495, 495])
|
|
99
|
+
p.setAngle(90)
|
|
100
|
+
p.doD0L(omega="f+f+f+f", P={"f": "ff+f--f+f"},
|
|
101
|
+
delta=90, times=5, length=490, rate=3)
|
|
102
|
+
p.wait()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from fractal import Pen
|
|
109
|
+
p = Pen([420,420])
|
|
110
|
+
p.setPoint([10,10])
|
|
111
|
+
p.doD0L(omega = "L", P = {"L": "LFRFL-FF-RFLFR+FF+LFRFL", "R": "RFLFR+FF+LFRFL-FF-RFLFR"}, delta = 90, times = 4, length = 200 , rate = 3)
|
|
112
|
+
p.wait()
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from fractal import Pen
|
|
119
|
+
p = Pen([400, 470])
|
|
120
|
+
p.setAngle(90)
|
|
121
|
+
p.setPoint([200,470])
|
|
122
|
+
p.doD0L(omega = "f", P = {"f": "h[-f][+f]hf", "h": "hh"}, delta = 25.7, times = 7, length = 400, rate = 2.17)
|
|
123
|
+
p.wait()
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from fractal import Pen
|
|
130
|
+
p = Pen([400, 470])
|
|
131
|
+
p.setAngle(90)
|
|
132
|
+
p.setPoint([170, 470])
|
|
133
|
+
p.doD0L(omega="f", P={"f": "h+[[f]-f]-h[-hf]+f", "h": "hh"},
|
|
134
|
+
delta=22.5, times=6, length=400, rate=2.3)
|
|
135
|
+
p.wait()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+

|
|
139
|
+
|
|
140
|
+
### IFS
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from fractal import IFS
|
|
144
|
+
from random import random
|
|
145
|
+
|
|
146
|
+
def ifsp(x, y):
|
|
147
|
+
p = random()
|
|
148
|
+
if p < 0.01:
|
|
149
|
+
return (0, 0.16 * y)
|
|
150
|
+
elif p < 0.07:
|
|
151
|
+
if random() > 0.5:
|
|
152
|
+
return (0.21 * x - 0.25 * y, 0.25 * x + 0.21 * y + 0.44)
|
|
153
|
+
else:
|
|
154
|
+
return (-0.2 * x + 0.26 * y, 0.23 * x + 0.22 * y + 0.6)
|
|
155
|
+
else:
|
|
156
|
+
return (0.85 * x + 0.1 * y, -0.05 * x + 0.85 * y + 0.6)
|
|
157
|
+
|
|
158
|
+
ob = IFS([400, 500], title = "Leaf")
|
|
159
|
+
ob.setPx(100, 100, 100)
|
|
160
|
+
ob.setIfsp(ifsp)
|
|
161
|
+
ob.doIFS(200000)
|
|
162
|
+
ob.wait()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+

|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# Box IFS
|
|
169
|
+
from fractal import IFS
|
|
170
|
+
from random import randint
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def ifsp(x, y):
|
|
174
|
+
p = randint(1, 5)
|
|
175
|
+
if p == 1:
|
|
176
|
+
return (x / 3, y / 3)
|
|
177
|
+
elif p == 2:
|
|
178
|
+
return (x / 3 + 2 / 3, y / 3)
|
|
179
|
+
elif p == 3:
|
|
180
|
+
return (x / 3 + 1 / 3, y / 3 + 1 / 3)
|
|
181
|
+
elif p == 4:
|
|
182
|
+
return (x / 3, y / 3 + 2 / 3)
|
|
183
|
+
else:
|
|
184
|
+
return (x / 3 + 2 / 3, y / 3 + 2 / 3)
|
|
185
|
+
|
|
186
|
+
ob = IFS([500, 500], title="Box")
|
|
187
|
+
ob.setPx(490, 5, 5)
|
|
188
|
+
ob.setIfsp(ifsp)
|
|
189
|
+
ob.doIFS(200000)
|
|
190
|
+
ob.wait()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+

|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from fractal import IFS
|
|
197
|
+
|
|
198
|
+
ifscode = [
|
|
199
|
+
[0.879, 0.054, -0.051, 0.878, 0.077, 0.123, 0.123],
|
|
200
|
+
[0.1, -0.193, 0.285, 0.224, 0.174, 0.169, 0.169],
|
|
201
|
+
[0.008, 0.135, 0, 0.204, 0.075, 0.074, 0.074],
|
|
202
|
+
[0.402, 0.045, 0.016, -0.197, 0.111, 0.193, 0.193]
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
ifs = IFS([500, 500])
|
|
206
|
+
ifs.setPx(700, 0, 0)
|
|
207
|
+
ifs.setIfsCode(ifscode)
|
|
208
|
+
ifs.doIFS(200000)
|
|
209
|
+
ifs.wait()
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+

|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from fractal import IFS
|
|
216
|
+
|
|
217
|
+
code = [
|
|
218
|
+
[0.195, -0.488, 0.344, 0.443, 0.4431, 0.2452, 0.2],
|
|
219
|
+
[0.462, 0.414, -0.252, 0.361, 0.2511, 0.5692, 0.2],
|
|
220
|
+
[-0.637, 0, 0, 0.501, 0.8562, 0.2512, 0.2],
|
|
221
|
+
[-0.035, 0.07, -0.469, 0.022, 0.4884, 0.5069, 0.2],
|
|
222
|
+
[-0.058, -0.07, -0.453, -0.111, 0.5976, 0.0969, 0.2]
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
ifs = IFS([500,500])
|
|
226
|
+
ifs.setCoordinate()
|
|
227
|
+
ifs.setPx(500, 0, 0)
|
|
228
|
+
ifs.setIfsCode(code)
|
|
229
|
+
ifs.doIFS(200000)
|
|
230
|
+
ifs.wait()
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+

|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
### Julia
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from fractal import Julia
|
|
241
|
+
ju = Julia([500, 500])
|
|
242
|
+
ju.setC(0 - 1j)
|
|
243
|
+
ju.doJulia(500)
|
|
244
|
+
ju.wait()
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+

|
|
248
|
+
|
|
249
|
+
**-1.25 + 0j**
|
|
250
|
+
|
|
251
|
+

|
|
252
|
+
|
|
253
|
+

|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
**-0.605-0.45j**
|
|
258
|
+
|
|
259
|
+
1.jpg)
|
|
260
|
+
|
|
261
|
+
2.jpg)
|
|
262
|
+
|
|
263
|
+
3.jpg)
|
|
264
|
+
|
|
265
|
+
### Mandelbrot
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
```python
|
|
270
|
+
from fractal import Mandelbrot
|
|
271
|
+
man = Mandelbrot([500, 500])
|
|
272
|
+
man.setRange(5, 5)
|
|
273
|
+
man.doMandelbrot(200)
|
|
274
|
+
man.wait()
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+

|
|
278
|
+
|
|
279
|
+

|
|
280
|
+
|
|
281
|
+

|
|
282
|
+
|
|
283
|
+

|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT License
|
|
288
|
+
|
|
289
|
+
## Author
|
|
290
|
+
|
|
291
|
+
L.Chen (pysrc)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
fractal/__init__.py,sha256=aT4xfLoejCQqWE1nRrudKEXIP5a0osreLrPTaBxALk0,170
|
|
2
|
+
fractal/base.py,sha256=kqtzt01ZgmqSLIeeXJkSBsL6Z3vzTHfhJKOaeW4sGHU,15526
|
|
3
|
+
fractal/cifs.py,sha256=-TxYVvmLnF3OU5BtFFA3FM-zG8x-PfU6vsSzYLCmSak,2996
|
|
4
|
+
fractal/colors.py,sha256=3zZKljXc5x3s_Xf_nReC9xwu7kei-armjhnxwlYH1r8,19581
|
|
5
|
+
fractal/ifs.py,sha256=pwBViUOTHYjaynbeqbmr0_fJcla7bgfJ7hEs6M1kcB0,2815
|
|
6
|
+
fractal/julia.py,sha256=JReJl_KTtR5AbW1Z4Nmyk0LzDzAPM9yYBA9Lpwb65uE,3100
|
|
7
|
+
fractal/lsystem.py,sha256=cgTatLlprLnV1AEShPqHLU_hTd2UDqtuTVKQLoGAKlk,3221
|
|
8
|
+
fractal/mandelbrot.py,sha256=aAYXzOXZD8X9OAMH03yT831LWVhu2yjNukBPaY_DP7c,3083
|
|
9
|
+
fractal-0.5.0.dist-info/licenses/LICENSE,sha256=FVQI1mfynzIuUYjS7TllMka382YWm-oFVFwCnQfLTWU,1084
|
|
10
|
+
fractal-0.5.0.dist-info/METADATA,sha256=ecPS7iKVhHI_Q6Yx06ufnBwfJleMBK_IpQOkryOM074,6404
|
|
11
|
+
fractal-0.5.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
fractal-0.5.0.dist-info/top_level.txt,sha256=rEXdmNzoRgT8nHa7k96n4y-bmnhe0nrJ0qDl-5mnjog,8
|
|
13
|
+
fractal-0.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 L.Chen
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fractal
|