import
os
import
numpy as np
import
matplotlib.pyplot as plt
from
matplotlib.backends.backend_tkagg
import
FigureCanvasTkAgg
from
tkinter
import
(
Tk,
Label,
Entry,
Button,
StringVar,
filedialog,
messagebox,
OptionMenu,
Frame,
)
import
time
import
traceback
import
gmpy2
from
gmpy2
import
mpfr, mpc, get_context, log10
import
pyopencl as cl
from
pyopencl
import
array
from
multiprocessing
import
Process, Queue, cpu_count
import
tempfile
import
subprocess
from
threading
import
Thread
from
tkinter
import
BooleanVar
from
tkinter
import
Checkbutton
WORKERS
=
max
(cpu_count()
-
1
,
1
)
plt.rcParams[
"font.sans-serif"
]
=
[
"SimHei"
]
plt.rcParams[
"axes.unicode_minus"
]
=
False
generating
=
False
current_task
=
0
total_tasks
=
0
MAX_WORKERS
=
WORKERS
def
initialize_opencl():
platforms
=
cl.get_platforms()
if
not
platforms:
raise
RuntimeError(
"没有找到可用的 OpenCL 平台"
)
platform
=
platforms[
0
]
print
(f
"选择的 OpenCL 平台: {platform.name}"
)
devices
=
platform.get_devices()
if
not
devices:
raise
RuntimeError(
"没有找到可用的 OpenCL 设备"
)
device
=
devices[
0
]
print
(f
"选择的 OpenCL 设备: {device.name}"
)
ctx
=
cl.Context(devices
=
[device])
queue
=
cl.CommandQueue(
ctx, properties
=
cl.command_queue_properties.PROFILING_ENABLE
)
kernel_code
=
prg
=
cl.Program(ctx, kernel_code).build()
print
(
"内核编译完成"
)
return
ctx, queue, prg
class
MandelbrotGenerator:
def
gmp_high_precision_compute(
self
,
x_min,
x_max,
y_min,
y_max,
width,
height,
max_iter,
precision,
current_task,
):
current_params
=
self
.validate_parameters()
if
current_params
is
None
:
raise
ValueError(
"参数验证失败"
)
binary_precision
=
int
(precision
*
3.324
)
+
16
print
(f
"二进制精度: {binary_precision}"
)
thread_count
=
WORKERS
print
(f
"线程数: {thread_count}"
)
print
(f
"初始缩放比例: {current_params['initial_scale']}"
)
current_scale2
=
mpfr(current_params[
"initial_scale"
])
/
(
mpfr(current_params[
"zoom_factor"
])
*
*
(current_task
-
1
)
)
percentile3
=
float
(
self
.percentile_entry.get())
percentile2
=
log10(percentile3)
/
2
if
self
.julia_var.get():
julia
=
1
else
:
julia
=
0
script_dir
=
os.path.dirname(os.path.abspath(__file__))
file_path
=
os.path.join(script_dir,
"mandelbrot.txt"
)
with
open
(file_path,
"w"
) as temp_file:
temp_file.write(f
"{thread_count}\n"
)
temp_file.write(f
"{binary_precision}\n"
)
temp_file.write(f
"{current_params['center_x']}\n"
)
temp_file.write(f
"{current_params['center_y']}\n"
)
temp_file.write(f
"{width}\n"
)
temp_file.write(f
"{height}\n"
)
temp_file.write(f
"{str(current_scale2)}\n"
)
temp_file.write(f
"{current_params['focus_x']}\n"
)
temp_file.write(f
"{current_params['focus_y']}\n"
)
temp_file.write(f
"{current_params['max_iter']}\n"
)
temp_file.write(f
"{percentile2}\n"
)
temp_file.write(f
"{current_params['c_x']}\n"
)
temp_file.write(f
"{current_params['c_y']}\n"
)
temp_file.write(f
"{julia}\n"
)
temp_file.flush()
os.chdir(script_dir)
command
=
f
"Mandelbrot.exe mandelbrot.txt"
print
(f
"执行命令: {command}"
)
self
.mandelbrot_process
=
subprocess.Popen(command, shell
=
True
)
while
self
.mandelbrot_process.poll()
is
None
:
if
not
generating:
subprocess.run(
"taskkill /F /IM Mandelbrot.exe"
, shell
=
True
)
raise
Exception(
"计算已终止"
)
time.sleep(
0.2
)
print
(
"命令完成"
)
output
=
self
.load_iteration_array_from_file(width, height)
if
output
is
None
:
raise
FileNotFoundError(
"未找到 iteration_array.bin 文件或文件格式不正确"
)
return
output
def
load_iteration_array_from_file(
self
, width, height):
file_path
=
"iteration_array.bin"
if
not
os.path.exists(file_path):
print
(f
"文件 {file_path} 不存在"
)
return
None
try
:
with
open
(file_path,
"rb"
) as
file
:
data
=
np.fromfile(
file
, dtype
=
np.int32)
if
len
(data) !
=
width
*
height:
print
(f
"文件 {file_path} 的数据大小与预期不符"
)
return
None
output
=
data.reshape((height, width))
return
output
except
Exception as e:
print
(f
"读取文件 {file_path} 时出错: {str(e)}"
)
return
None
def
create_buttons(
self
, parent):
buttons_frame
=
Frame(parent, padx
=
10
, pady
=
5
)
buttons_frame.grid(row
=
3
, column
=
0
, sticky
=
"ew"
, padx
=
5
)
regions
=
[
(
"海马谷"
,
-
0.74515135254160040270968367987368282086820830812505531306383690724472274612043
,
0.1301973420730631518534562967071010595298732429535045571757108286281891077748
,
),
(
"花"
,
-
0.3906731935564842
,
0.6243239460184142
),
(
"螺丝线"
,
-
1.999944284562545
,
7.674460749033766e
-
16
),
(
"羽毛"
,
-
1.49005323192932
,
0.00119631499924505
),
(
"螺旋"
,
-
0.6888873943437659
,
0.2810053792512903
),
(
"螺旋2"
,
0.2513567236163006
,
-
9.220290965617807e
-
05
),
]
for
idx, (name, x, y)
in
enumerate
(regions):
btn
=
Button(
buttons_frame,
text
=
name,
width
=
3
,
command
=
lambda
x
=
x, y
=
y:
self
.set_focus(x, y),
)
btn.grid(row
=
idx
/
/
2
, column
=
idx
%
2
, padx
=
3
, pady
=
1.5
)
def
set_focus(
self
, x_val, y_val):
try
:
self
.center_x_entry.delete(
0
,
"end"
)
self
.center_x_entry.insert(
0
, f
"{x_val}"
)
self
.center_y_entry.delete(
0
,
"end"
)
self
.center_y_entry.insert(
0
, f
"{y_val}"
)
except
Exception as e:
messagebox.showerror(
"输入错误"
, f
"坐标设置失败: {str(e)}"
)
def
safe_compute_mandelbrot(
self
,
x_min,
x_max,
y_min,
y_max,
width,
height,
max_iter,
precision_threshold,
current_task,
c_x,
c_y,
precision
=
50
,
):
try
:
if
precision > precision_threshold:
self
.current_method
=
"gmp高精度计算"
return
self
.gmp_high_precision_compute(
x_min,
x_max,
y_min,
y_max,
width,
height,
max_iter,
precision,
current_task,
)
else
:
try
:
self
.current_method
=
"OpenCL"
return
self
.opencl_mandelbrot(
x_min, x_max, y_min, y_max, width, height, max_iter, c_x, c_y
)
except
:
self
.current_method
=
"opencl失败,切gmp高精度计算"
return
self
.gmp_high_precision_compute(
x_min,
x_max,
y_min,
y_max,
width,
height,
max_iter,
precision,
current_task,
)
except
Exception as e:
print
(f
"计算错误: {str(e)}"
)
raise
def
opencl_mandelbrot(
self
, x_min, x_max, y_min, y_max, width, height, max_iter, c_x, c_y
):
if
self
.julia_var.get():
julia
=
0
else
:
julia
=
1
print
(
"开始opencl计算"
)
output
=
np.empty((height, width), dtype
=
np.float32)
output_buf
=
cl.
Buffer
(
self
.ctx,
cl.mem_flags.WRITE_ONLY | cl.mem_flags.ALLOC_HOST_PTR,
size
=
output.nbytes,
)
pre_width
=
max
(width
/
/
10
,
1
)
pre_height
=
max
(height
/
/
10
,
1
)
pre_output
=
np.empty((pre_height, pre_width), dtype
=
np.float32)
pre_output_buf
=
cl.
Buffer
(
self
.ctx,
cl.mem_flags.WRITE_ONLY | cl.mem_flags.ALLOC_HOST_PTR,
size
=
pre_output.nbytes,
)
print
(
"准备发内核"
)
global_size
=
(pre_width, pre_height)
pre_kernel_event
=
self
.prg.mandelbrot(
self
.queue,
global_size,
None
,
pre_output_buf,
np.float64(x_min),
np.float64(x_max),
np.float64(y_min),
np.float64(y_max),
np.int32(pre_width),
np.int32(pre_height),
np.int32(max_iter),
np.float64(c_x),
np.float64(c_y),
np.int32(julia),
)
print
(
"数据已发送内核"
)
pre_kernel_event.wait()
mapped_pre_data, _
=
cl.enqueue_map_buffer(
self
.queue,
pre_output_buf,
cl.map_flags.READ,
0
,
pre_output.shape,
pre_output.dtype,
)
np.copyto(pre_output, mapped_pre_data)
max_iter_value
=
np.
max
(pre_output)
pre_output[pre_output
=
=
max_iter_value]
=
0
percentile
=
float
(
self
.percentile_entry.get())
percentile_log
=
log10(percentile)
*
50
percentile_log
=
float
(percentile_log)
new_max_iter
=
int
(np.percentile(pre_output, percentile_log))
new_max_iter
=
max
(new_max_iter,
100
)
print
(f
"预计算完成,新的最大迭代次数: {new_max_iter}"
)
global_size
=
(width, height)
kernel_event
=
self
.prg.mandelbrot(
self
.queue,
global_size,
None
,
output_buf,
np.float64(x_min),
np.float64(x_max),
np.float64(y_min),
np.float64(y_max),
np.int32(width),
np.int32(height),
np.int32(new_max_iter),
np.float64(c_x),
np.float64(c_y),
np.int32(julia),
)
kernel_event.wait()
mapped_data, _
=
cl.enqueue_map_buffer(
self
.queue, output_buf, cl.map_flags.READ,
0
, output.shape, output.dtype
)
np.copyto(output, mapped_data)
return
output
def
get_zoom_factor(
self
):
try
:
zoom_factor
=
float
(
self
.zoom_factor_entry.get())
if
zoom_factor <
=
0
:
raise
ValueError(
"放大系数必须大于0"
)
return
zoom_factor
except
ValueError as e:
messagebox.showerror(
"输入错误"
, f
"放大系数无效: {str(e)}"
)
raise
def
on_canvas_release(
self
, event):
if
not
generating
and
event.inaxes:
x_pixel, y_pixel
=
event.xdata, event.ydata
default_width
=
int
(
self
.width_entry.get())
default_height
=
int
(
self
.height_entry.get())
focus_x
=
x_pixel
/
default_width
focus_y
=
y_pixel
/
default_height
if
self
.select_c_var.get():
pass
else
:
self
.focus_x_entry.delete(
0
,
"end"
)
self
.focus_x_entry.insert(
0
, f
"{focus_x}"
)
self
.focus_y_entry.delete(
0
,
"end"
)
self
.focus_y_entry.insert(
0
, f
"{focus_y}"
)
self
.start_generation()
def
on_canvas_click(
self
, event):
if
not
generating
and
event.inaxes:
x_pixel, y_pixel
=
event.xdata, event.ydata
x_min, x_max
=
self
.current_metadata[
"x_range"
]
y_min, y_max
=
self
.current_metadata[
"y_range"
]
center_x
=
x_pixel
center_y
=
y_pixel
current_scalex
=
mpfr(x_max)
-
mpfr(x_min)
current_scaley
=
mpfr(y_max)
-
mpfr(y_min)
default_width
=
int
(
self
.width_entry.get())
default_height
=
int
(
self
.height_entry.get())
cc_x
=
mpfr(x_pixel)
/
mpfr(default_width)
*
4
-
2
cc_y
=
mpfr(y_pixel)
/
mpfr(default_height)
*
4
-
2
center_x
=
mpfr(x_pixel)
*
mpfr(current_scalex)
/
mpfr(
default_width
)
+
mpfr(x_min)
center_y
=
mpfr(y_pixel)
*
mpfr(current_scaley)
/
mpfr(
default_height
)
+
mpfr(y_min)
if
self
.select_c_var.get():
self
.c_x_entry.delete(
0
,
"end"
)
self
.c_x_entry.insert(
0
,
str
(cc_x))
self
.c_y_entry.delete(
0
,
"end"
)
self
.c_y_entry.insert(
0
,
str
(cc_y))
self
.start_generation()
else
:
current_scale
=
current_scalex
/
mpfr(
self
.get_zoom_factor()
)
self
.scale_entry.delete(
0
,
"end"
)
self
.scale_entry.insert(
0
,
str
(current_scale))
self
.center_x_entry.delete(
0
,
"end"
)
self
.center_x_entry.insert(
0
,
str
(center_x))
self
.center_y_entry.delete(
0
,
"end"
)
self
.center_y_entry.insert(
0
,
str
(center_y))
def
__init__(
self
, root):
self
.root
=
root
self
.current_image
=
None
self
.current_metadata
=
{}
self
.mandelbrot_process
=
None
self
.setup_ui()
self
.setup_plot()
self
.ctx,
self
.queue,
self
.prg
=
initialize_opencl()
self
.current_method
=
"OpenCL"
self
.last_click_time
=
0
self
.is_double_click
=
False
def
setup_ui(
self
):
self
.root.title(
"Mandelbrot集图片生成器"
)
self
.root.geometry(
"1200x800"
)
self
.root.grid_columnconfigure(
1
, weight
=
1
)
self
.root.grid_rowconfigure(
0
, weight
=
1
)
control_frame
=
Frame(
self
.root, padx
=
10
, pady
=
10
)
control_frame.grid(row
=
0
, column
=
0
, sticky
=
"nsew"
)
control_frame.grid_columnconfigure(
1
, weight
=
1
)
self
.create_inputs(control_frame)
self
.status_var
=
StringVar()
self
.status_var.
set
(
"就绪"
)
status_bar
=
Label(
self
.root,
textvariable
=
self
.status_var,
bd
=
1
,
relief
=
"sunken"
,
anchor
=
"w"
,
font
=
(
"微软雅黑"
,
9
),
bg
=
"#f0f0f0"
,
)
status_bar.grid(row
=
1
, column
=
0
, columnspan
=
2
, sticky
=
"ew"
)
def
create_inputs(
self
, parent):
row
=
0
Label(parent, text
=
"分辨率"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.width_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.width_entry.insert(
0
,
"800"
)
self
.width_entry.grid(row
=
row, column
=
1
, padx
=
2
)
Label(parent, text
=
"×"
, font
=
(
"微软雅黑"
,
10
)).grid(row
=
row, column
=
2
)
self
.height_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.height_entry.insert(
0
,
"600"
)
self
.height_entry.grid(row
=
row, column
=
3
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"焦点数值坐标 X"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.center_x_entry
=
Entry(parent, width
=
20
, font
=
(
"微软雅黑"
,
10
))
self
.center_x_entry.insert(
0
,
"-0.74515135254160040270968367987368282086820830812505531306383690724472274612043"
,
)
self
.center_x_entry.grid(row
=
row, column
=
1
, columnspan
=
3
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"焦点数值坐标 Y"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.center_y_entry
=
Entry(parent, width
=
20
, font
=
(
"微软雅黑"
,
10
))
self
.center_y_entry.insert(
0
,
"0.1301973420730631518534562967071010595298732429535045571757108286281891077748"
,
)
self
.center_y_entry.grid(row
=
row, column
=
1
, columnspan
=
3
, padx
=
2
)
row
+
=
2
self
.julia_var
=
BooleanVar()
self
.julia_checkbox
=
Checkbutton(
parent,
text
=
"启用朱利亚集"
,
variable
=
self
.julia_var,
command
=
self
.toggle_julia_mode,
font
=
(
"微软雅黑"
,
10
),
)
self
.julia_checkbox.grid(row
=
row, column
=
0
, columnspan
=
5
, sticky
=
"w"
, pady
=
2
)
row
+
=
1
self
.select_c_var
=
BooleanVar()
self
.select_c_checkbox
=
Checkbutton(
parent,
text
=
"画布上选择c坐标"
,
variable
=
self
.select_c_var,
font
=
(
"微软雅黑"
,
10
),
)
self
.select_c_checkbox.grid(row
=
row, column
=
0
, columnspan
=
5
, sticky
=
"w"
, pady
=
2
)
row
+
=
1
self
.copy_button
=
Button(
parent,
text
=
"复制焦点坐标到 c 坐标"
,
command
=
self
.copy_center_to_c,
font
=
(
"微软雅黑"
,
10
),
)
self
.copy_button.grid(row
=
row, column
=
0
, columnspan
=
5
, pady
=
5
, sticky
=
"we"
)
row
+
=
1
Label(parent, text
=
"c坐标 X"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.c_x_entry
=
Entry(parent, width
=
20
, font
=
(
"微软雅黑"
,
10
))
self
.c_x_entry.insert(
0
,
"-0.74515135254160040270968367987368282086820830812505531306383690724472274612043"
,
)
self
.c_x_entry.grid(row
=
row, column
=
1
, columnspan
=
3
, padx
=
2
)
self
.c_x_entry.config(state
=
"disabled"
)
row
+
=
1
Label(parent, text
=
"c坐标 Y"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.c_y_entry
=
Entry(parent, width
=
20
, font
=
(
"微软雅黑"
,
10
))
self
.c_y_entry.insert(
0
,
"0.1301973420730631518534562967071010595298732429535045571757108286281891077748"
,
)
self
.c_y_entry.grid(row
=
row, column
=
1
, columnspan
=
3
, padx
=
2
)
self
.c_y_entry.config(state
=
"disabled"
)
row
+
=
1
Label(parent, text
=
"宽度数值"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.scale_entry
=
Entry(parent, width
=
20
, font
=
(
"微软雅黑"
,
10
))
self
.scale_entry.insert(
0
,
"3.0000000"
)
self
.scale_entry.grid(row
=
row, column
=
1
, columnspan
=
3
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"动态迭代数上限"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.max_iter_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.max_iter_entry.insert(
0
,
"9999"
)
self
.max_iter_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"动态迭代阈值(0-100)"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.percentile_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.percentile_entry.insert(
0
,
"90"
)
self
.percentile_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"生成数量"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.num_images_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.num_images_entry.insert(
0
,
"4"
)
self
.num_images_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"放大系数"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.zoom_factor_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.zoom_factor_entry.insert(
0
,
"1.5"
)
self
.zoom_factor_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"焦点位置坐标 X"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.focus_x_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.focus_x_entry.insert(
0
,
"0.5"
)
self
.focus_x_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"焦点位置坐标 Y"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.focus_y_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.focus_y_entry.insert(
0
,
"0.5"
)
self
.focus_y_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"切换cpu阈值"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.precision_threshold_entry
=
Entry(parent, width
=
8
, font
=
(
"微软雅黑"
,
10
))
self
.precision_threshold_entry.insert(
0
,
"11"
)
self
.precision_threshold_entry.grid(row
=
row, column
=
1
, padx
=
2
)
row
+
=
1
Label(parent, text
=
"颜色映射"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.colormap_var
=
StringVar()
self
.colormap_var.
set
(
"jet"
)
colormaps
=
[
"Accent"
,
"Accent_r"
,
"Blues"
,
"Blues_r"
,
"BrBG"
,
"BrBG_r"
,
"BuGn"
,
"BuGn_r"
,
"BuPu"
,
"BuPu_r"
,
"CMRmap"
,
"CMRmap_r"
,
"Dark2"
,
"Dark2_r"
,
"GnBu"
,
"GnBu_r"
,
"Grays"
,
"Greens"
,
"Greens_r"
,
"Greys"
,
"Greys_r"
,
"OrRd"
,
"OrRd_r"
,
"Oranges"
,
"Oranges_r"
,
"PRGn"
,
"PRGn_r"
,
"Paired"
,
"Paired_r"
,
"Pastel1"
,
"Pastel1_r"
,
"Pastel2"
,
"Pastel2_r"
,
"PiYG"
,
"PiYG_r"
,
"PuBu"
,
"PuBuGn"
,
"PuBuGn_r"
,
"PuBu_r"
,
"PuOr"
,
"PuOr_r"
,
"PuRd"
,
"PuRd_r"
,
"Purples"
,
"Purples_r"
,
"RdBu"
,
"RdBu_r"
,
"RdGy"
,
"RdGy_r"
,
"RdPu"
,
"RdPu_r"
,
"RdYlBu"
,
"RdYlBu_r"
,
"RdYlGn"
,
"RdYlGn_r"
,
"Reds"
,
"Reds_r"
,
"Set1"
,
"Set1_r"
,
"Set2"
,
"Set2_r"
,
"Set3"
,
"Set3_r"
,
"Spectral"
,
"Spectral_r"
,
"Wistia"
,
"Wistia_r"
,
"YlGn"
,
"YlGnBu"
,
"YlGnBu_r"
,
"YlGn_r"
,
"YlOrBr"
,
"YlOrBr_r"
,
"YlOrRd"
,
"YlOrRd_r"
,
"afmhot"
,
"afmhot_r"
,
"autumn"
,
"autumn_r"
,
"binary"
,
"binary_r"
,
"bone"
,
"bone_r"
,
"brg"
,
"brg_r"
,
"bwr"
,
"bwr_r"
,
"cividis"
,
"cividis_r"
,
"cool"
,
"cool_r"
,
"coolwarm"
,
"coolwarm_r"
,
"copper"
,
"copper_r"
,
"cubehelix"
,
"cubehelix_r"
,
"flag"
,
"flag_r"
,
"gist_earth"
,
"gist_earth_r"
,
"gist_gray"
,
"gist_gray_r"
,
"gist_grey"
,
"gist_heat"
,
"gist_heat_r"
,
"gist_ncar"
,
"gist_ncar_r"
,
"gist_rainbow"
,
"gist_rainbow_r"
,
"gist_stern"
,
"gist_stern_r"
,
"gist_yarg"
,
"gist_yarg_r"
,
"gist_yerg"
,
"gnuplot"
,
"gnuplot2"
,
"gnuplot2_r"
,
"gnuplot_r"
,
"gray"
,
"gray_r"
,
"grey"
,
"hot"
,
"hot_r"
,
"hsv"
,
"hsv_r"
,
"inferno"
,
"inferno_r"
,
"jet"
,
"jet_r"
,
"magma"
,
"magma_r"
,
"nipy_spectral"
,
"nipy_spectral_r"
,
"ocean"
,
"ocean_r"
,
"pink"
,
"pink_r"
,
"plasma"
,
"plasma_r"
,
"prism"
,
"prism_r"
,
"rainbow"
,
"rainbow_r"
,
"seismic"
,
"seismic_r"
,
"spring"
,
"spring_r"
,
"summer"
,
"summer_r"
,
"tab10"
,
"tab10_r"
,
"tab20"
,
"tab20_r"
,
"tab20b"
,
"tab20b_r"
,
"tab20c"
,
"tab20c_r"
,
"terrain"
,
"terrain_r"
,
"turbo"
,
"turbo_r"
,
"twilight"
,
"twilight_r"
,
"twilight_shifted"
,
"twilight_shifted_r"
,
"viridis"
,
"viridis_r"
,
"winter"
,
"winter_r"
,
]
self
.colormap_menu
=
OptionMenu(parent,
self
.colormap_var,
*
colormaps)
self
.colormap_menu.grid(
row
=
row, column
=
1
, padx
=
2
, sticky
=
"ew"
)
row
+
=
1
Label(parent, text
=
"保存路径"
, font
=
(
"微软雅黑"
,
10
)).grid(
row
=
row, column
=
0
, sticky
=
"w"
, pady
=
2
)
self
.save_path_var
=
StringVar()
Entry(
parent,
textvariable
=
self
.save_path_var,
state
=
"readonly"
,
width
=
20
,
font
=
(
"微软雅黑"
,
10
),
).grid(row
=
row, column
=
1
, columnspan
=
3
, padx
=
2
)
self
.browse_button
=
Button(
parent, text
=
"浏览..."
, command
=
self
.select_save_path, font
=
(
"微软雅黑"
,
9
)
)
self
.browse_button.grid(row
=
row, column
=
4
, padx
=
2
)
row
+
=
1
self
.generate_button
=
Button(
parent,
text
=
"开始生成"
,
command
=
self
.toggle_generation,
font
=
(
"微软雅黑"
,
10
,
"bold"
),
bg
=
"#4CAF50"
,
fg
=
"white"
,
)
self
.generate_button.grid(row
=
row, column
=
0
, columnspan
=
5
, pady
=
10
, sticky
=
"we"
)
self
.create_buttons(parent)
def
copy_center_to_c(
self
):
try
:
center_x
=
self
.center_x_entry.get()
center_y
=
self
.center_y_entry.get()
self
.c_x_entry.config(state
=
"normal"
)
self
.c_x_entry.delete(
0
,
"end"
)
self
.c_x_entry.insert(
0
, center_x)
self
.c_y_entry.config(state
=
"normal"
)
self
.c_y_entry.delete(
0
,
"end"
)
self
.c_y_entry.insert(
0
, center_y)
if
not
self
.julia_var.get():
self
.julia_var.
set
(
True
)
self
.toggle_julia_mode()
messagebox.showinfo(
"复制成功"
,
"焦点坐标已复制到 c 坐标"
)
except
Exception as e:
messagebox.showerror(
"复制失败"
, f
"复制坐标时出错: {str(e)}"
)
def
toggle_julia_mode(
self
):
if
self
.julia_var.get():
self
.c_x_entry.config(state
=
"normal"
)
self
.c_y_entry.config(state
=
"normal"
)
else
:
self
.c_x_entry.config(state
=
"disabled"
)
self
.c_y_entry.config(state
=
"disabled"
)
def
setup_plot(
self
):
self
.fig
=
plt.figure(figsize
=
(
8
,
6
), dpi
=
200
)
self
.canvas
=
FigureCanvasTkAgg(
self
.fig, master
=
self
.root)
self
.canvas.get_tk_widget().grid(
row
=
0
, column
=
1
, sticky
=
"nsew"
, padx
=
10
, pady
=
10
)
self
.canvas.mpl_connect(
"resize_event"
,
self
.on_resize)
self
.canvas.mpl_connect(
"button_press_event"
,
self
.on_canvas_click
)
self
.canvas.mpl_connect(
"button_release_event"
,
self
.on_canvas_release
)
def
toggle_inputs(
self
, state):
entries
=
[
self
.width_entry,
self
.height_entry,
self
.center_x_entry,
self
.center_y_entry,
self
.c_x_entry,
self
.c_y_entry,
self
.scale_entry,
self
.max_iter_entry,
self
.num_images_entry,
self
.zoom_factor_entry,
self
.focus_x_entry,
self
.focus_y_entry,
self
.percentile_entry,
self
.precision_threshold_entry,
]
for
entry
in
entries:
entry.config(state
=
"normal"
if
state
else
"disabled"
)
self
.browse_button.config(state
=
"normal"
if
state
else
"disabled"
)
def
select_save_path(
self
):
path
=
filedialog.askdirectory()
if
path:
self
.save_path_var.
set
(path)
def
update_progress(
self
):
if
not
generating:
return
self
.root.after(
0
,
lambda
:
self
.status_var.
set
(f
"进度: {current_task}/{total_tasks}"
)
)
if
current_task
=
=
total_tasks:
self
.status_var.
set
(
"生成完成"
)
self
.root.update_idletasks()
def
update_canvas(
self
, output, metadata):
if
output
is
None
or
"x_range"
not
in
metadata
or
"y_range"
not
in
metadata:
print
(
"跳过更新画布:缺少必要的数据或元数据。"
)
return
try
:
self
.fig.clear()
print
(
"清除画布成功"
)
selected_colormap
=
self
.colormap_var.get()
ax
=
self
.fig.add_subplot(
111
)
default_width
=
int
(
self
.width_entry.get())
default_height
=
int
(
self
.height_entry.get())
ax.imshow(
output,
cmap
=
selected_colormap,
extent
=
[
0
, default_width,
0
, default_height],
origin
=
"lower"
,
)
outmin_iter
=
int
(np.
min
(output))
outmax_iter
=
int
(np.
max
(output))
title
=
f
"当前计算方法: {self.current_method}"
title
+
=
f
"点击放大,拖动移动\n"
title
+
=
f
"实际迭代范围: [{outmin_iter}, {outmax_iter}]\n"
title
+
=
f
"计算时间: {metadata['compute_time']:.2f}s | 当前精度: {metadata['precision']}位 (第{metadata['task_id']}帧)"
ax.set_title(title)
self
.canvas.draw()
except
Exception as e:
messagebox.showerror(
"界面更新错误"
,
str
(e))
self
.root.update()
def
on_resize(
self
, event):
self
.update_canvas(
self
.current_image,
self
.current_metadata)
if
self
.current_image
is
not
None
and
self
.current_metadata:
self
.update_canvas(
self
.current_image,
self
.current_metadata)
def
toggle_generation(
self
):
global
generating
if
generating:
generating
=
False
self
.generate_button.config(text
=
"开始生成"
, state
=
"normal"
)
self
.status_var.
set
(
"已终止"
)
self
.toggle_inputs(
True
)
self
.toggle_julia_mode()
if
self
.mandelbrot_process
and
self
.mandelbrot_process.poll()
is
None
:
subprocess.run(
"taskkill /F /IM Mandelbrot.exe"
, shell
=
True
)
else
:
self
.start_generation()
def
start_generation(
self
):
global
generating, current_task, total_tasks
try
:
params
=
self
.validate_parameters()
print
(
"参数验证完成,参数如下:"
)
for
key, value
in
params.items():
print
(f
" {key}: {value}"
)
self
.initialize_generation(params)
print
(
"生成任务初始化完成。"
)
tasks
=
self
.create_tasks(params)
print
(f
"任务队列创建完成,共有 {len(tasks)} 个任务。"
)
self
.generation_thread
=
Thread(
target
=
self
.process_tasks, args
=
(tasks, params)
)
self
.generation_thread.start()
print
(
"所有任务处理完成。"
)
except
Exception as e:
error_message
=
f
"发生错误:\n{str(e)}\n{traceback.format_exc()}"
print
(f
"生成过程中发生错误:\n{error_message}"
)
messagebox.showerror(
"错误"
, error_message)
self
.cleanup_after_error()
def
validate_parameters(
self
):
params
=
{
"width"
:
self
.width_entry.get(),
"height"
:
self
.height_entry.get(),
"center_x"
:
self
.center_x_entry.get(),
"center_y"
:
self
.center_y_entry.get(),
"c_x"
:
self
.c_x_entry.get(),
"c_y"
:
self
.c_y_entry.get(),
"initial_scale"
:
self
.scale_entry.get(),
"max_iter"
:
self
.max_iter_entry.get(),
"num_images"
:
self
.num_images_entry.get(),
"zoom_factor"
:
self
.zoom_factor_entry.get(),
"save_path"
:
self
.save_path_var.get(),
"focus_x"
:
self
.focus_x_entry.get(),
"focus_y"
:
self
.focus_y_entry.get(),
}
if
any
(
v <
=
0
for
v
in
[
int
(params[
"width"
]),
int
(params[
"height"
]),
float
(params[
"initial_scale"
]),
int
(params[
"max_iter"
]),
]
):
raise
ValueError(
"参数必须大于0"
)
if
not
os.path.exists(params[
"save_path"
]):
os.makedirs(params[
"save_path"
], exist_ok
=
True
)
self
.params
=
params
return
params
def
initialize_generation(
self
, params):
global
generating, current_task, total_tasks
generating
=
True
current_task
=
0
total_tasks
=
int
(params[
"num_images"
])
self
.generate_button.config(text
=
"终止"
, state
=
"normal"
)
self
.status_var.
set
(
"初始化中..."
)
self
.toggle_inputs(
False
)
self
.root.update()
def
create_tasks(
self
, params):
tasks
=
[]
current_scale
=
mpfr(params[
"initial_scale"
])
zoom_factor
=
mpfr(params[
"zoom_factor"
])
focus_x
=
mpfr(params[
"focus_x"
])
focus_y
=
mpfr(params[
"focus_y"
])
get_context().precision
=
262144
for
i
in
range
(
int
(params[
"num_images"
])):
mpfr_scale
=
mpfr(current_scale)
*
mpfr(
0.9
)
mpfr_log10
=
log10(mpfr_scale)
precision
=
max
(
1
,
int
(
-
mpfr_log10))
print
(
int
(
-
mpfr_log10))
if
(precision
*
3.3
) >
0
:
get_context().precision
=
int
(precision
*
3.324
)
+
16
task
=
{
"task_id"
: i
+
1
,
"width"
:
int
(params[
"width"
]),
"height"
:
int
(params[
"height"
]),
"x_min"
: mpfr(params[
"center_x"
])
-
current_scale
/
2
+
(mpfr(
0.5
)
-
focus_x)
*
current_scale,
"x_max"
: mpfr(params[
"center_x"
])
+
current_scale
/
2
+
(mpfr(
0.5
)
-
focus_x)
*
current_scale,
"y_min"
: mpfr(params[
"center_y"
])
-
current_scale
/
(
2
*
mpfr(params[
"width"
])
/
mpfr(params[
"height"
]))
+
(mpfr(
0.5
)
-
focus_y)
*
current_scale
/
(mpfr(params[
"width"
])
/
mpfr(params[
"height"
])),
"y_max"
: mpfr(params[
"center_y"
])
+
current_scale
/
(
2
*
mpfr(params[
"width"
])
/
mpfr(params[
"height"
]))
+
(mpfr(
0.5
)
-
focus_y)
*
current_scale
/
(mpfr(params[
"width"
])
/
mpfr(params[
"height"
])),
"max_iter"
:
int
(params[
"max_iter"
]),
"c_x"
: mpfr(params[
"c_x"
]),
"c_y"
: mpfr(params[
"c_y"
]),
"precision"
: precision,
}
current_scale
/
=
mpfr(
self
.get_zoom_factor())
print
(
"task创建成功"
)
tasks.append(task)
return
tasks
def
process_tasks(
self
, tasks, params):
global
generating, current_task
for
task
in
tasks:
if
not
generating:
break
current_task
+
=
1
self
.status_var.
set
(f
"正在生成第{current_task}帧..."
)
self
.update_progress()
print
(
"计算开始时间"
)
print
(time.time())
start_time
=
time.time()
precision_threshold
=
int
(
self
.precision_threshold_entry.get())
output
=
self
.safe_compute_mandelbrot(
task[
"x_min"
],
task[
"x_max"
],
task[
"y_min"
],
task[
"y_max"
],
task[
"width"
],
task[
"height"
],
task[
"max_iter"
],
precision_threshold,
current_task,
task[
"c_x"
],
task[
"c_y"
],
task[
"precision"
],
)
compute_time
=
time.time()
-
start_time
print
(
"计算完成时间"
)
print
(time.time())
print
(
"保存前时间"
)
print
(time.time())
filename_base
=
os.path.join(params[
"save_path"
],
"Mandelbrot"
)
file_extension
=
".png"
filename
=
f
"{filename_base}{file_extension}"
counter
=
1
while
os.path.exists(filename):
filename
=
f
"{filename_base}_{str(counter).zfill(7)}{file_extension}"
counter
+
=
1
selected_colormap
=
self
.colormap_var.get()
plt.imsave(filename, output, cmap
=
selected_colormap, origin
=
"lower"
)
print
(f
"图像已保存为: {filename}"
)
print
(
"保存完成时间"
)
print
(time.time())
print
(
"更新界面开始时间"
)
print
(time.time())
metadata
=
{
"x_range"
: (task[
"x_min"
], task[
"x_max"
]),
"y_range"
: (task[
"y_min"
], task[
"y_max"
]),
"task_id"
: task[
"task_id"
],
"compute_time"
: compute_time,
"precision"
: task[
"precision"
],
}
self
.current_image
=
output
self
.current_metadata
=
metadata
self
.root.after(
0
,
self
.update_canvas, output, metadata)
print
(
"更新完界面时间"
)
print
(time.time())
generating
=
False
self
.status_var.
set
(
"生成完成"
if
current_task
=
=
total_tasks
else
"已终止"
)
self
.generate_button.config(text
=
"开始生成"
, state
=
"normal"
)
self
.toggle_inputs(
True
)
self
.toggle_julia_mode()
def
cleanup_after_error(
self
):
global
generating
generating
=
False
if
hasattr
(
self
,
"generation_thread"
):
self
.generation_thread.join(
0
)
self
.generate_button.config(text
=
"开始生成"
, state
=
"normal"
)
self
.status_var.
set
(
"就绪"
)
self
.toggle_inputs(
True
)
self
.toggle_julia_mode()
if
__name__
=
=
"__main__"
:
root
=
Tk()
app
=
MandelbrotGenerator(root)
def
on_close():
global
generating
generating
=
False
root.destroy()
os._exit(
0
)
root.protocol(
"WM_DELETE_WINDOW"
, on_close)
root.mainloop()