ADD file via upload

main
pp5h8yfeq 5 months ago
parent ae8f7eda88
commit c86bb6e1d6

@ -0,0 +1,634 @@
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
import cv2
import numpy as np
from PIL import Image, ImageTk
class MyApp:
def __init__(self, root):
# 三个frame从左到右分别放置原图结果图控制台标为123
self.frame1 = None
self.frame2 = None
self.frame3 = None
self.src = None # 原始图像
# self.src = cv2.imread("fxe.png")
self.image = None # 结果输出图像
self.src2 = None # 二元运算的第二张图
self.tem_img = None # 临时保存本地的图片
# UI设置
self.root = root
self.root.title("简易数字图像处理系统")
self.root.state('zoomed') # 窗口最大化
# 获取窗口大小,得到窗口大小(减去底部任务栏的高度)
self.width = root.winfo_screenwidth()
self.height = root.winfo_screenheight() - 71
# frame12的宽高
self.fwidth = int(self.width * 7 / 16)
self.fheight = self.height
# console控制台的宽高
self.cwidth = int(self.width * 2 / 16)
self.cheight = self.height
self.create_widget() # 布置组件
# 布置菜单
self.menu_bar = tk.Menu(root)
root.config(menu=self.menu_bar)
self.create_menu()
def create_widget(self):
# 布置组件
self.frame1 = tk.Frame(root, width=self.fwidth, height=920, bd=2, relief="ridge")
self.frame2 = tk.Frame(root, width=self.fwidth, height=920, bd=2, relief="ridge")
self.frame3 = tk.Frame(root, width=1600 - 2 * self.fwidth, height=920, bd=2, relief="ridge")
self.frame1.place(x=0, y=0, anchor="nw")
self.frame2.place(x=self.fwidth, y=0, anchor="nw")
self.frame3.place(x=2 * self.fwidth, y=0, anchor="nw")
label1 = tk.Label(self.frame1, text="原始图像", font=("宋体", 30))
label1.place(x=self.fwidth / 2 - 100, y=15, anchor="nw")
label2 = tk.Label(self.frame2, text="输出图像", font=("宋体", 30))
label2.place(x=self.fwidth / 2 - 100, y=15, anchor="nw")
label3 = tk.Label(self.frame3, text="控制台", font=("宋体", 30))
label3.place(x=self.cwidth / 2 - 60, y=15, anchor="nw")
self.src_panel = tk.Label(self.frame1, image=None)
self.img_panel = tk.Label(self.frame2, image=None)
def create_menu(self):
# 布置菜单
self.menu_bar.add_cascade(label="加载图片", command=self.load_src)
self.menu_bar.add_cascade(label="加载输出图像", command=self.load_image_use_src)
self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
self.file_menu.add_command(label="灰化", command=self.convert_to_gray)
self.file_menu.add_command(label="算术运算", command=self.arithmetic_calculation)
self.file_menu.add_command(label="缩放旋转图像", command=self.rotate_image)
self.file_menu.add_command(label="翻转", command=self.flip_image)
self.file_menu.add_command(label="仿射变换", command=self.affine_transform)
self.file_menu.add_command(label="对数变换", command=self.logarithmic_transform)
self.file_menu.add_command(label="线性变换", command=self.linear_transform)
self.file_menu.add_command(label="直方图均衡化", command=self.equalizehist)
self.file_menu.add_command(label="开运算", command=self.open)
self.file_menu.add_command(label="闭运算", command=self.close)
self.menu_bar.add_cascade(label="处理图像", menu=self.file_menu)
self.menu_bar.add_cascade(label="保存图片", command=self.save_image)
def load_image(self):
# 加载本地图片至self.tem_img
file_path = filedialog.askopenfilename()
if len(file_path) > 0:
image = cv2.imread(file_path, cv2.IMREAD_COLOR)
self.tem_img = image
def load_src(self):
# 加载原始图片并显示
self.load_image()
self.src = self.tem_img
self.clear_frame1()
self.clear_frame23()
self.display_image(self.src, self.frame1)
def load_image_use_src(self):
# 把输出图像加载到原始图像并显示
if self.image is not None:
self.src = self.image
self.clear_frame1()
self.display_image(self.src, self.frame1)
else:
messagebox.showerror("Error", "没有输出图像")
def display_image(self, image, frame):
# 将opencv格式的image显示到frame1或2
panel = tk.Label(frame, image=None)
# 将opencv的图片转换成PIL格式并等比例适配frame大小
img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img)
width, height = img.size
rwidth, rheight = 0, 0
q = width / height
# 下面将fheight-200是为了空出上下文字的空间
if q > self.fwidth / (self.fheight - 220): # 图片较宽的情况
rwidth = self.fwidth
rheight = 1 / q * self.fwidth
panel.place(x=0, y=self.fheight / 2 - rheight / 2, anchor="nw")
else: # 图片较高的情况
rheight = self.fheight - 175
rwidth = q * rheight
panel.place(x=self.fwidth / 2 - rwidth / 2, y=60, anchor="nw")
img = img.resize((int(rwidth), int(rheight)))
img = ImageTk.PhotoImage(img)
panel.configure(image=img)
panel.image = img
# 显示图片大小
height, width = image.shape[:2] # image是传进来的opencv格式的图片
width_lab = tk.Label(frame, text=f"宽度:{width}", font=("宋体", 18))
width_lab.place(x=self.fwidth / 2 - 100, y=self.fheight - 110, anchor="nw")
height_lab = tk.Label(frame, text=f"高度:{height}", font=("宋体", 18))
height_lab.place(x=self.fwidth / 2 - 100, y=self.fheight - 60, anchor="nw")
def clear_frame1(self):
for widget in self.frame1.winfo_children():
widget.destroy()
label = tk.Label(self.frame1, text="原始图像", font=("宋体", 30))
label.place(x=self.fwidth / 2 - 100, y=15, anchor="nw")
def clear_frame2(self):
for widget in self.frame2.winfo_children():
widget.destroy()
label = tk.Label(self.frame2, text="输出图像", font=("宋体", 30))
label.place(x=self.fwidth / 2 - 100, y=15, anchor="nw")
def clear_frame23(self):
# 清楚frame23上的所有组件
self.src2 = None
for widget in self.frame3.winfo_children():
widget.destroy()
label3 = tk.Label(self.frame3, text="控制台", font=("宋体", 30))
label3.place(x=self.cwidth / 2 - 60, y=15, anchor="nw")
for widget in self.frame2.winfo_children():
widget.destroy()
label2 = tk.Label(self.frame2, text="输出图像", font=("宋体", 30))
label2.place(x=self.fwidth / 2 - 100, y=15, anchor="nw")
def save_image(self):
# 保存图片至本地
if self.image is not None:
file_path = filedialog.asksaveasfilename(defaultextension=".jpg",
filetypes=[("JPEG files", "*.jpg"), ("PNG files", "*.png")],
initialfile="image.jpg")
if file_path:
cv2.imwrite(file_path, self.image)
else:
messagebox.showerror("Error", "没有输出图像")
def convert_to_gray(self):
# 灰化图片
if self.src is not None:
self.clear_frame23()
self.image = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY)
self.display_image(self.image, self.frame2)
else:
messagebox.showerror("Error", "未加载图像")
def arithmetic_calculation(self):
# 算术运算
def add():
if self.src2 is not None:
height, width = self.src.shape[:2]
src2 = cv2.resize(self.src2, (int(width), int(height)))
self.image = cv2.add(self.src, src2)
self.display_image(self.image, self.frame2)
else:
messagebox.showerror("Error", "未加载图像")
def subtract():
if self.src2 is not None:
height, width, _ = self.src.shape
src2 = cv2.resize(self.src2, (int(width), int(height)))
self.image = cv2.subtract(self.src, src2)
self.display_image(self.image, self.frame2)
else:
messagebox.showerror("Error", "未加载图像")
def multiply():
if self.src2 is not None:
height, width, _ = self.src.shape
src2 = cv2.resize(self.src2, (int(width), int(height)))
img = cv2.subtract(self.src, src2)
image1_float = np.float32(self.src)
image2_float = np.float32(img)
result = cv2.multiply(image1_float, image2_float)
self.image = np.uint8(result)
self.display_image(self.image, self.frame2)
else:
messagebox.showerror("Error", "未加载图像")
def button():
self.load_image()
self.src2 = self.tem_img
label = tk.Label(self.frame3, image=None)
img = cv2.cvtColor(self.src2, cv2.COLOR_BGR2RGB)
img = Image.fromarray(img)
width, height = img.size
rwidth, rheight = 0, 0
q = width / height
if q > self.cwidth / 400: # 图片较宽的情况
rwidth = self.cwidth
rheight = 1 / q * self.cwidth
label.place(x=0, y=325 - rheight / 2, anchor="nw")
else:
rheight = 400
rwidth = q * 400
label.place(x=(1600 - 2 * self.fwidth) / 2 - rwidth / 2, y=100, anchor="nw")
img = img.resize((int(rwidth), int(rheight)))
img = ImageTk.PhotoImage(img)
label.configure(image=img)
label.image = img
if self.src is not None:
self.clear_frame23()
load_but = tk.Button(self.frame3, text="加载第二张图", font=("宋体", 18), command=button)
load_but.place(x=self.cwidth / 2 - 75, y=100, anchor="nw")
load_a = tk.Button(self.frame3, text="加法运算", font=("宋体", 18), command=add)
load_a.place(x=self.cwidth / 2 - 60, y=500, anchor="nw")
load_s = tk.Button(self.frame3, text="减法运算", font=("宋体", 18), command=subtract)
load_s.place(x=self.cwidth / 2 - 60, y=600, anchor="nw")
load_m = tk.Button(self.frame3, text="乘法运算", font=("宋体", 18), command=multiply)
load_m.place(x=self.cwidth / 2 - 60, y=700, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
def rotate_image(self):
# 旋转图像
def button():
try:
angle = int(angel_en.get()) # 旋转角度,可以根据需要调整
scale = float(scale_en.get())
except ValueError:
messagebox.showerror("Error", "输入异常,请输入数字")
height, width = self.src.shape[:2]
center = (width // 2, height // 2)
rotation_matrix = cv2.getRotationMatrix2D(center, angle, scale)
cos = np.abs(rotation_matrix[0, 0])
sin = np.abs(rotation_matrix[0, 1])
new_width = int((height * sin) + (width * cos))
new_height = int((height * cos) + (width * sin))
# 调整旋转矩阵的平移部分
rotation_matrix[0, 2] += (new_width / 2) - center[0]
rotation_matrix[1, 2] += (new_height / 2) - center[1]
# 进行仿射变换
self.image = cv2.warpAffine(self.src, rotation_matrix, (new_width, new_height))
self.display_image(self.image, self.frame2)
if self.src is not None:
self.clear_frame23()
angel_lab = tk.Label(self.frame3, text="旋转角度", font=("宋体", 18))
angel_lab.place(x=10, y=100, anchor="nw")
angel_lab2 = tk.Label(self.frame3, text="(整数)", font=("宋体", 18))
angel_lab2.place(x=90, y=150, anchor="nw")
angel_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
angel_en.place(x=110, y=100, anchor="nw")
angel_en.insert(0, "0")
scale_lab = tk.Label(self.frame3, text="缩放因子", font=("宋体", 18))
scale_lab.place(x=10, y=200, anchor="nw")
scale_lab = tk.Label(self.frame3, text="(小数)", font=("宋体", 18))
scale_lab.place(x=90, y=250, anchor="nw")
scale_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
scale_en.place(x=110, y=200, anchor="nw")
scale_en.insert(0, "1")
but = tk.Button(self.frame3, text="运算", font=("宋体", 18), command=button)
but.place(x=self.cwidth / 2 - 40, y=300, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
def flip_image(self):
# 翻转图像
def horizontal():
self.image = cv2.flip(self.src, 1)
self.display_image(self.image, self.frame2)
def vertical():
self.image = cv2.flip(self.src, 0)
self.display_image(self.image, self.frame2)
def cross():
self.image = cv2.flip(self.src, -1)
self.display_image(self.image, self.frame2)
if self.src is not None:
self.clear_frame23()
h_but = tk.Button(self.frame3, text="水平翻转", font=("宋体", 18), command=horizontal)
h_but.place(x=self.cwidth / 2 - 60, y=100, anchor="nw")
v_but = tk.Button(self.frame3, text="垂直翻转", font=("宋体", 18), command=vertical)
v_but.place(x=self.cwidth / 2 - 60, y=200, anchor="nw")
c_but = tk.Button(self.frame3, text="对角翻转", font=("宋体", 18), command=cross)
c_but.place(x=self.cwidth / 2 - 60, y=300, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
def affine_transform(self):
def get_coordinates(entry, size):
coordinates = entry.get().split(',') # 英文逗号
if len(coordinates) != 2:
coordinates = entry.get().split('') # 中文逗号
if len(coordinates) != 2:
coordinates = entry.get().split(' ')
if len(coordinates) != 2:
messagebox.showerror("Error", "请输入正确的坐标格式x,y")
return -1, -1
try:
x, y = int(coordinates[0]), int(coordinates[1])
if x > size[0] or y > size[1]:
messagebox.showerror("Error", "坐标必须在图像内")
else:
return x, y
except ValueError:
messagebox.showerror("Error", "坐标必须为整数")
return -1, -1
def check():
return get_coordinates(p1_en, size)[0] != -1 and get_coordinates(p2_en, size)[0] != -1 and \
get_coordinates(p3_en, size)[0] != -1 and get_coordinates(p4_en, size)[0] != -1 and \
get_coordinates(p5_en, size)[0] != -1 and get_coordinates(p6_en, size)[0] != -1
def show():
tmp = self.src.copy()
size = tmp.shape[:2]
if check():
radius = int(max(size[0], size[1]) / 100)
cv2.circle(tmp, get_coordinates(p1_en, size), radius, (255, 0, 0), -1)
cv2.circle(tmp, get_coordinates(p2_en, size), radius, (0, 255, 0), -1)
cv2.circle(tmp, get_coordinates(p3_en, size), radius, (0, 0, 255), -1)
cv2.circle(tmp, get_coordinates(p4_en, size), radius, (255, 0, 0), -1)
cv2.circle(tmp, get_coordinates(p5_en, size), radius, (0, 255, 0), -1)
cv2.circle(tmp, get_coordinates(p6_en, size), radius, (0, 0, 255), -1)
self.clear_frame2()
self.display_image(tmp, self.frame2)
def button():
if check():
self.clear_frame2()
post1 = np.float32([get_coordinates(p1_en, size), get_coordinates(p2_en, size), get_coordinates(p3_en, size)])
post2 = np.float32([get_coordinates(p4_en, size), get_coordinates(p5_en, size), get_coordinates(p6_en, size)])
M = cv2.getAffineTransform(post1, post2)
self.image = cv2.warpAffine(self.src, M, self.src.shape[:2])
self.display_image(self.image, self.frame2)
if self.src is not None:
self.clear_frame23()
size = self.src.shape[:2]
try_but = tk.Button(self.frame3, text="显示", font=("宋体", 18), command=show)
try_but.place(x=self.cwidth / 2 - 40, y=150, anchor="nw")
p1_lab = tk.Label(self.frame3, text="原始点1", font=("宋体", 18))
p1_lab.place(x=10, y=200, anchor="nw")
p1_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p1_en.place(x=100, y=200, anchor="nw")
p1_en.insert(0, "50 50")
p2_lab = tk.Label(self.frame3, text="原始点2", font=("宋体", 18))
p2_lab.place(x=10, y=250, anchor="nw")
p2_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p2_en.place(x=100, y=250, anchor="nw")
p2_en.insert(0, "200 50")
p3_lab = tk.Label(self.frame3, text="原始点3", font=("宋体", 18))
p3_lab.place(x=10, y=300, anchor="nw")
p3_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p3_en.place(x=100, y=300, anchor="nw")
p3_en.insert(0, "50 200")
p4_lab = tk.Label(self.frame3, text="目标点1", font=("宋体", 18))
p4_lab.place(x=10, y=350, anchor="nw")
p4_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p4_en.place(x=100, y=350, anchor="nw")
p4_en.insert(0, "10 100")
p5_lab = tk.Label(self.frame3, text="目标点2", font=("宋体", 18))
p5_lab.place(x=10, y=400, anchor="nw")
p5_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p5_en.place(x=100, y=400, anchor="nw")
p5_en.insert(0, "200 50")
p6_lab = tk.Label(self.frame3, text="目标点3", font=("宋体", 18))
p6_lab.place(x=10, y=450, anchor="nw")
p6_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p6_en.place(x=100, y=450, anchor="nw")
p6_en.insert(0, "100 250")
but = tk.Button(self.frame3, text="运算", font=("宋体", 18),
command=button)
but.place(x=self.cwidth / 2 - 40, y=500, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
def logarithmic_transform(self):
# 对数变换
if self.src is not None:
self.clear_frame23()
img=cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY)
C = 255 / np.log(1 + 255)
result = C * np.log(1.0 + img)
self.image = np.uint8(result+0.5)
self.display_image(self.image, self.frame2)
else:
messagebox.showerror("Error", "未加载图像")
def linear_transform(self):
# 线性变换
def get_range(entry):
range = entry.get().split(',') # 英文逗号
if len(range) != 2:
range = entry.get().split('') # 中文逗号
if len(range) != 2:
range = entry.get().split(' ')
if len(range) != 2:
messagebox.showerror("Error", "请输入正确的范围格式x,y")
return -1, -1
try:
x, y = int(range[0]), int(range[1])
if 0 > x or 255 < y:
messagebox.showerror("Error", "范围必须在(0,255)内")
else:
return x, y
except ValueError:
messagebox.showerror("Error", "坐标必须为整数")
return -1, -1
def check():
return get_range(p1_en)[0] != -1 and get_range(p2_en)[0] != -1
def button():
if check():
self.clear_frame2()
img = cv2.cvtColor(self.src, cv2.COLOR_BGR2GRAY)
a,b=get_range(p1_en)
c,d=get_range(p2_en)
result = (d - c) / (b - a) * img + (b * c - a * d) / (b - a)
self.image = np.uint8(result + 0.5)
self.display_image(self.image, self.frame2)
if self.src is not None:
self.clear_frame23()
p1_lab = tk.Label(self.frame3, text="原始区间", font=("宋体", 18))
p1_lab.place(x=10, y=200, anchor="nw")
p1_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p1_en.place(x=110, y=200, anchor="nw")
p1_en.insert(0, "50 100")
p2_lab = tk.Label(self.frame3, text="目标区间", font=("宋体", 18))
p2_lab.place(x=10, y=250, anchor="nw")
p2_en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
p2_en.place(x=110, y=250, anchor="nw")
p2_en.insert(0, "0 255")
but = tk.Button(self.frame3, text="运算", font=("宋体", 18),
command=button)
but.place(x=self.cwidth / 2 - 40, y=300, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
def equalizehist(self):
# 灰化图片
if self.src is not None:
self.clear_frame23()
img = cv2.cvtColor(self.src,cv2.COLOR_BGR2GRAY)
self.image = cv2.equalizeHist(img)
self.display_image(self.image, self.frame2)
else:
messagebox.showerror("Error", "未加载图像")
def open(self):
selected_real_value = cv2.MORPH_CROSS
def get_size(entry,size):
range = entry.get().split(',') # 英文逗号
if len(range) != 2:
range = entry.get().split('') # 中文逗号
if len(range) != 2:
range = entry.get().split(' ')
if len(range) != 2:
messagebox.showerror("Error", "请输入正确的范围格式x,y")
return -1, -1
try:
x, y = int(range[0]), int(range[1])
if x<0 or y<0 or x>size[0] or y>size[1]:
messagebox.showerror("Error", "范围必须在图像内")
else:
return x, y
except ValueError:
messagebox.showerror("Error", "坐标必须为整数")
return -1, -1
def button():
kernel = cv2.getStructuringElement(selected_real_value, get_size(en,self.src.shape[:2]))
self.image = cv2.morphologyEx(self.src, cv2.MORPH_OPEN, kernel)
self.display_image(self.image, self.frame2)
def on_option_selected(*args):
option_value_map = {
"交叉": cv2.MORPH_CROSS,
"椭圆": cv2.MORPH_ELLIPSE,
"矩形": cv2.MORPH_RECT
}
nonlocal selected_real_value
selected_text = selected_value.get()
selected_real_value = option_value_map[selected_text]
if self.src is not None:
self.clear_frame23()
lab = tk.Label(self.frame3, text="结构元", font=("宋体", 18))
lab.place(x=10, y=100, anchor="nw")
en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
en.place(x=100, y=100, anchor="nw")
en.insert(0, "5 5")
framet=tk.Frame(self.frame3)
options = [
"交叉",
"椭圆",
"矩形"
]
# 创建一个变量来存储用户选择的显示文本
selected_value = tk.StringVar(root)
selected_value.set(options[0])
option_menu = tk.OptionMenu(framet, selected_value, *options, command=on_option_selected)
option_menu.pack()
framet.place(x=self.cwidth / 2 - 40, y=200, anchor="nw")
but = tk.Button(self.frame3, text="运算", font=("宋体", 18),
command=button)
but.place(x=self.cwidth / 2 - 40, y=500, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
def close(self):
selected_real_value = cv2.MORPH_CROSS
def get_size(entry,size):
range = entry.get().split(',') # 英文逗号
if len(range) != 2:
range = entry.get().split('') # 中文逗号
if len(range) != 2:
range = entry.get().split(' ')
if len(range) != 2:
messagebox.showerror("Error", "请输入正确的范围格式x,y")
return -1, -1
try:
x, y = int(range[0]), int(range[1])
if x<0 or y<0 or x>size[0] or y>size[1]:
messagebox.showerror("Error", "范围必须在图像内")
else:
return x, y
except ValueError:
messagebox.showerror("Error", "坐标必须为整数")
return -1, -1
def button():
kernel = cv2.getStructuringElement(selected_real_value, get_size(en,self.src.shape[:2]))
self.image = cv2.morphologyEx(self.src, cv2.MORPH_CLOSE, kernel)
self.display_image(self.image, self.frame2)
def on_option_selected(*args):
option_value_map = {
"交叉": cv2.MORPH_CROSS,
"椭圆": cv2.MORPH_ELLIPSE,
"矩形": cv2.MORPH_RECT
}
nonlocal selected_real_value
selected_text = selected_value.get()
selected_real_value = option_value_map[selected_text]
if self.src is not None:
self.clear_frame23()
lab = tk.Label(self.frame3, text="结构元", font=("宋体", 18))
lab.place(x=10, y=100, anchor="nw")
en = tk.Entry(self.frame3, width=5, font=("宋体", 18))
en.place(x=100, y=100, anchor="nw")
en.insert(0, "5 5")
framet=tk.Frame(self.frame3)
options = [
"交叉",
"椭圆",
"矩形"
]
# 创建一个变量来存储用户选择的显示文本
selected_value = tk.StringVar(root)
selected_value.set(options[0])
option_menu = tk.OptionMenu(framet, selected_value, *options, command=on_option_selected)
option_menu.pack()
framet.place(x=self.cwidth / 2 - 40, y=200, anchor="nw")
but = tk.Button(self.frame3, text="运算", font=("宋体", 18),
command=button)
but.place(x=self.cwidth / 2 - 40, y=500, anchor="nw")
else:
messagebox.showerror("Error", "未加载图像")
if __name__ == '__main__':
root = tk.Tk()
app = MyApp(root)
root.mainloop()
Loading…
Cancel
Save