-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtk_gui.py
More file actions
287 lines (232 loc) · 10.1 KB
/
Copy pathtk_gui.py
File metadata and controls
287 lines (232 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# -*- coding:utf-8 -*-
"""
@File : tk_gui.py
@Time : 2025/11/19
@Author : mr_guo
@Function : Tkinter GUI界面
"""
import os
import sys
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk
from PIL.Image import Resampling
from coordinate_picker import CoordinatePicker
class CoordinatePickerApp:
def __init__(self, root):
self.coord_text = None
self.file_label = None
self.status_var = None
self.image_label = None
self.info_text = None
self.root = root
self.root.title("动态坐标拾取器 v1.0")
self.root.geometry("1000x700")
# 设置图标 - 兼容打包环境
try:
icon_path = self.resource_path("icon.ico")
if os.path.exists(icon_path):
self.root.iconbitmap(icon_path)
print(f"图标加载成功: {icon_path}")
else:
print(f"图标文件不存在: {icon_path}")
except Exception as e:
print(f"设置图标失败: {e}")
# 继续运行,图标不是关键功能
# 当前图像路径
self.current_image_path = None
self.coordinate_picker = None
# 创建界面
self.create_widgets()
def create_widgets(self):
"""创建界面组件"""
# 主框架
main_frame = ttk.Frame(self.root)
main_frame.pack(fill="both", expand=True, padx=10, pady=10)
# 左侧控制面板
control_frame = ttk.LabelFrame(main_frame, text="控制面板", width=300)
control_frame.pack(side="left", fill="y", padx=(0, 10))
control_frame.pack_propagate(False)
# 右侧图像显示区域
image_frame = ttk.LabelFrame(main_frame, text="图像显示")
image_frame.pack(side="right", fill="both", expand=True)
# 左侧控制面板内容
self.create_control_panel(control_frame)
# 右侧图像显示区域内容
self.create_image_panel(image_frame)
def create_control_panel(self, parent):
"""创建控制面板"""
# 文件选择区域
file_frame = ttk.LabelFrame(parent, text="文件操作")
file_frame.pack(fill="x", padx=5, pady=5)
ttk.Button(file_frame, text="选择图像文件",
command=self.select_image).pack(fill="x", padx=5, pady=5)
self.file_label = ttk.Label(file_frame, text="未选择文件",
wraplength=280, background="white")
self.file_label.pack(fill="x", padx=5, pady=5)
# 图像信息区域
info_frame = ttk.LabelFrame(parent, text="图像信息")
info_frame.pack(fill="x", padx=5, pady=5)
self.info_text = tk.Text(info_frame, height=8, width=35)
self.info_text.pack(fill="both", padx=5, pady=5, expand=True)
self.info_text.config(state="disabled")
# 坐标信息区域
coord_frame = ttk.LabelFrame(parent, text="坐标信息")
coord_frame.pack(fill="x", padx=5, pady=5)
self.coord_text = tk.Text(coord_frame, height=6, width=35)
self.coord_text.pack(fill="both", padx=5, pady=5, expand=True)
self.coord_text.config(state="disabled")
# 操作说明区域
help_frame = ttk.LabelFrame(parent, text="使用说明")
help_frame.pack(fill="both", padx=5, pady=5, expand=True)
help_content = """
操作指南:
• 点击"选择图像文件"加载图片
• 注意图片命名为英文,否则会报错
• 在OpenCV窗口中移动鼠标查看坐标
• 红色十字线显示当前鼠标位置
• 坐标信息实时显示在左侧面板
• 按ESC键退出坐标拾取模式
特性:
• 支持任意尺寸图像
• 智能字体大小调整
• 边界避让显示
• 半透明背景
"""
help_text = tk.Text(help_frame, height=12, width=35)
help_text.pack(fill="both", padx=5, pady=5, expand=True)
help_text.insert(tk.END, help_content)
help_text.config(state="disabled")
def create_image_panel(self, parent):
"""创建图像显示面板"""
# 图像显示区域
self.image_label = ttk.Label(parent, text="请选择图像文件开始使用",
background="lightgray")
self.image_label.pack(fill="both", expand=True, padx=5, pady=5)
# 状态栏
self.status_var = tk.StringVar()
self.status_var.set("就绪 - 请选择图像文件")
status_bar = ttk.Label(parent, textvariable=self.status_var,
relief="sunken", anchor="w")
status_bar.pack(fill="x", side="bottom", padx=5, pady=5)
def select_image(self):
"""选择图像文件"""
file_path = filedialog.askopenfilename(
title="选择图像文件",
filetypes=[
("图像文件", "*.png *.jpg *.jpeg *.bmp *.tiff *.tif"),
("所有文件", "*.*")
]
)
if file_path:
self.load_image(file_path)
def load_image(self, file_path):
"""加载并显示图像"""
try:
if not os.path.exists(file_path):
messagebox.showerror("错误", f"文件不存在: {file_path}")
return
self.current_image_path = file_path
self.file_label.config(text=os.path.basename(file_path))
# 初始化坐标拾取器
self.coordinate_picker = CoordinatePicker(file_path)
self.coordinate_picker.set_coordinate_callback(self.update_coordinate_info)
self.coordinate_picker.set_finished_callback(self.on_picker_finished)
# 获取图像信息
image_info = self.coordinate_picker.get_image_info()
self.display_image_info(image_info)
# 在Tkinter中显示缩略图
self.display_thumbnail(file_path)
# 启动坐标拾取
self.coordinate_picker.start()
self.status_var.set(f"已加载: {os.path.basename(file_path)} - 在OpenCV窗口中移动鼠标查看坐标")
except Exception as e:
messagebox.showerror("错误", f"加载图像失败: {str(e)}")
def display_thumbnail(self, file_path):
"""在Tkinter中显示图像缩略图,按比例自适应"""
try:
# 使用PIL打开图像
image = Image.open(file_path)
original_width, original_height = image.size
# 获取显示区域的可用大小(减去边距)
label_width = self.image_label.winfo_width() - 20
label_height = self.image_label.winfo_height() - 20
# 如果显示区域尺寸无效,使用默认值
if label_width < 10 or label_height < 10:
label_width, label_height = 400, 300
# 计算保持宽高比的缩放尺寸
width_ratio = label_width / original_width
height_ratio = label_height / original_height
# 选择较小的缩放比例,确保图像完全显示在区域内
scale_ratio = min(width_ratio, height_ratio)
new_width = int(original_width * scale_ratio)
new_height = int(original_height * scale_ratio)
# 调整图像大小
resized_image = image.resize((new_width, new_height), Resampling.LANCZOS)
# 转换为PhotoImage
photo = ImageTk.PhotoImage(resized_image)
# 更新标签
self.image_label.configure(image=photo, text="")
self.image_label.image = photo # 保持引用
print(
f"图像尺寸: {original_width}x{original_height} -> 显示尺寸: {new_width}x{new_height} (缩放比例: {scale_ratio:.2f})")
except Exception as e:
print(f"显示缩略图失败: {e}")
self.image_label.configure(image="", text=f"图像预览不可用: {str(e)}")
def on_window_resize(self, _event):
"""窗口大小改变时重新调整图像显示"""
# 未使用event参数(Tkinter事件回调必需)
if self.current_image_path:
self.display_thumbnail(self.current_image_path)
def display_image_info(self, info):
"""显示图像信息"""
info_content = f"""文件信息:
文件名: {info['filename']}
路径: {info['filepath']}
尺寸信息:
宽度: {info['width']} 像素
高度: {info['height']} 像素
缩放因子: {info['scale']:.2f}
支持格式:
PNG, JPG, JPEG, BMP, TIFF
"""
self.info_text.config(state=tk.NORMAL)
self.info_text.delete(1.0, tk.END)
self.info_text.insert(1.0, info_content)
self.info_text.config(state=tk.DISABLED)
def update_coordinate_info(self, x, y):
"""更新坐标信息显示"""
coord_content = f"""当前坐标:
X: {x}
Y: {y}
格式示例:
元组: ({x}, {y})
字典: {{'x': {x}, 'y': {y}}}
JSON: "x": {x}, "y": {y}
"""
self.coord_text.config(state="normal")
self.coord_text.delete(1.0, tk.END)
self.coord_text.insert(1.0, coord_content)
self.coord_text.config(state="disabled")
@staticmethod
def resource_path(relative_path: str) -> str:
"""获取资源的绝对路径。适用于开发环境和PyInstaller打包后环境"""
base_path: str
try:
# PyInstaller 创建的临时文件夹,存储打包后的资源
base_path = getattr(sys, '_MEIPASS', os.path.abspath("."))
except AttributeError:
# 如果不是打包后运行,则使用当前文件的目录,仅处理sys._MEIPASS不存在的情况(开发环境)
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def on_picker_finished(self):
"""坐标拾取器结束时的回调"""
self.status_var.set("坐标拾取已结束")
self.coord_text.config(state="normal")
self.coord_text.delete(1.0, tk.END)
self.coord_text.insert(1.0, "坐标拾取已结束\n请重新选择图像文件以再次使用")
self.coord_text.config(state="disabled")
def __del__(self):
"""析构函数"""
if self.coordinate_picker:
self.coordinate_picker.stop()