Image Annotation Tool.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. # pip install pillow
  2. import tkinter as tk
  3. from tkinter import filedialog, messagebox
  4. from PIL import Image, ImageTk
  5. import sqlite3
  6. import datetime
  7. import os
  8. # Подключение к бд
  9. conn = sqlite3.connect('sqlite.db')
  10. cursor = conn.cursor()
  11. cursor.execute('''
  12. CREATE TABLE IF NOT EXISTS annotations (
  13. id INTEGER PRIMARY KEY AUTOINCREMENT,
  14. filename TEXT NOT NULL,
  15. image_width INTEGER,
  16. image_height INTEGER,
  17. x1 INTEGER,
  18. y1 INTEGER,
  19. x2 INTEGER,
  20. y2 INTEGER,
  21. rectangle_width INTEGER,
  22. rectangle_height INTEGER,
  23. image_left INTEGER,
  24. image_bottom INTEGER,
  25. image_top INTEGER,
  26. image_right INTEGER,
  27. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  28. )
  29. ''')
  30. conn.commit()
  31. root = tk.Tk()
  32. root.title("Image Annotation Tool")
  33. root.state('zoomed')
  34. root.configure(bg='#2b2b2b')
  35. bg_color = '#2b2b2b'
  36. frame_color = '#3c3c3c'
  37. text_color = '#ffffff'
  38. accent_color = '#4a90d9'
  39. panel = tk.Frame(root, width=200, height=600, bg=frame_color)
  40. panel.pack(side='right', fill='y')
  41. panel.pack_propagate(False)
  42. zagolovok = tk.Label(panel, text="Координаты \nизображения:",
  43. font=("Arial", 12, "bold"), bg=frame_color, fg=accent_color)
  44. zagolovok.pack(pady=20)
  45. levo_metka = tk.Label(panel, text="Left: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
  46. levo_metka.pack(pady=8)
  47. niz_metka = tk.Label(panel, text="Bottom: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
  48. niz_metka.pack(pady=8)
  49. verh_metka = tk.Label(panel, text="Top: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
  50. verh_metka.pack(pady=8)
  51. pravo_metka = tk.Label(panel, text="Right: 0", font=("Arial", 10), bg=frame_color, fg=text_color)
  52. pravo_metka.pack(pady=8)
  53. razdelitel = tk.Frame(panel, height=2, bg=accent_color)
  54. razdelitel.pack(fill='x', padx=20, pady=20)
  55. ramka_pryam = tk.Frame(panel, bg=frame_color)
  56. ramka_pryam.pack(pady=10)
  57. metka_pryam = tk.Label(ramka_pryam, text="Координаты прямоугольника:",
  58. font=("Arial", 10, "bold"), bg=frame_color, fg=accent_color)
  59. metka_pryam.pack(pady=5)
  60. vvod_koord = {}
  61. metki_koord = ["X1:", "Y1:", "X2:", "Y2:"]
  62. znach_po_umolch = ["100", "100", "300", "300"]
  63. for i, metka in enumerate(metki_koord):
  64. ramochka = tk.Frame(ramka_pryam, bg=frame_color)
  65. ramochka.pack(pady=2)
  66. tk.Label(ramochka, text=metka, font=("Arial", 9), bg=frame_color, fg=text_color, width=4).pack(side='left')
  67. pole = tk.Entry(ramochka, width=8, font=("Arial", 9))
  68. pole.insert(0, znach_po_umolch[i])
  69. pole.pack(side='left')
  70. vvod_koord[metka.replace(":", "")] = pole
  71. peremesh_aktivno = False
  72. ugol_dlya_peremesh = None
  73. nach_koord_peremesh = None
  74. tekush_pryam = None
  75. tekush_izobr = None
  76. poz_izobr = None
  77. tekush_put_k_file = None
  78. pil_izobr_obj = None
  79. def obnovit_poly_vvoda():
  80. if tekush_pryam and poz_izobr:
  81. koord = holst.coords(tekush_pryam)
  82. if koord and len(koord) >= 4:
  83. img_x, img_y = poz_izobr
  84. vvod_koord["X1"].delete(0, tk.END)
  85. vvod_koord["X1"].insert(0, str(int(koord[0] - img_x)))
  86. vvod_koord["Y1"].delete(0, tk.END)
  87. vvod_koord["Y1"].insert(0, str(int(koord[1] - img_y)))
  88. vvod_koord["X2"].delete(0, tk.END)
  89. vvod_koord["X2"].insert(0, str(int(koord[2] - img_x)))
  90. vvod_koord["Y2"].delete(0, tk.END)
  91. vvod_koord["Y2"].insert(0, str(int(koord[3] - img_y)))
  92. def narisovat_pryam():
  93. global tekush_pryam
  94. try:
  95. x1 = int(vvod_koord["X1"].get())
  96. y1 = int(vvod_koord["Y1"].get())
  97. x2 = int(vvod_koord["X2"].get())
  98. y2 = int(vvod_koord["Y2"].get())
  99. holst.delete("pryamougolnik")
  100. holst.delete("ugol")
  101. if poz_izobr:
  102. img_x, img_y = poz_izobr
  103. abs_x1 = img_x + x1
  104. abs_y1 = img_y + y1
  105. abs_x2 = img_x + x2
  106. abs_y2 = img_y + y2
  107. if abs_x1 > abs_x2:
  108. abs_x1, abs_x2 = abs_x2, abs_x1
  109. if abs_y1 > abs_y2:
  110. abs_y1, abs_y2 = abs_y2, abs_y1
  111. tekush_pryam = holst.create_rectangle(
  112. abs_x1, abs_y1, abs_x2, abs_y2,
  113. outline="red", width=2, tags="pryamougolnik",
  114. dash=(5, 2)
  115. )
  116. narisovat_uglovye_markery(abs_x1, abs_y1, abs_x2, abs_y2)
  117. sohranit_koord_pryam(abs_x1, abs_y1, abs_x2, abs_y2)
  118. except ValueError:
  119. pass
  120. def sohranit_koord_pryam(x1, y1, x2, y2):
  121. global sohran_koord_pryam
  122. sohran_koord_pryam = [x1, y1, x2, y2]
  123. def narisovat_uglovye_markery(x1, y1, x2, y2):
  124. razmer_markera = 8
  125. # Левый верхний
  126. holst.create_oval(
  127. x1 - razmer_markera, y1 - razmer_markera,
  128. x1 + razmer_markera, y1 + razmer_markera,
  129. fill="red", outline="white", width=1, tags="ugol"
  130. )
  131. # Правый верхний
  132. holst.create_oval(
  133. x2 - razmer_markera, y1 - razmer_markera,
  134. x2 + razmer_markera, y1 + razmer_markera,
  135. fill="red", outline="white", width=1, tags="ugol"
  136. )
  137. # Левый нижний
  138. holst.create_oval(
  139. x1 - razmer_markera, y2 - razmer_markera,
  140. x1 + razmer_markera, y2 + razmer_markera,
  141. fill="red", outline="white", width=1, tags="ugol"
  142. )
  143. # Правый нижний
  144. holst.create_oval(
  145. x2 - razmer_markera, y2 - razmer_markera,
  146. x2 + razmer_markera, y2 + razmer_markera,
  147. fill="red", outline="white", width=1, tags="ugol"
  148. )
  149. def ochistit_pryam():
  150. global tekush_pryam
  151. holst.delete("pryamougolnik")
  152. holst.delete("ugol")
  153. tekush_pryam = None
  154. def nachalo_peremesh(event):
  155. global peremesh_aktivno, ugol_dlya_peremesh, nach_koord_peremesh
  156. if not tekush_pryam:
  157. return
  158. x, y = event.x, event.y
  159. koord_pryam = holst.coords(tekush_pryam)
  160. if not koord_pryam:
  161. return
  162. tolshina_klika = 15
  163. ugli = [
  164. (koord_pryam[0], koord_pryam[1], "levyy_verhniy"),
  165. (koord_pryam[2], koord_pryam[1], "pravyy_verhniy"),
  166. (koord_pryam[0], koord_pryam[3], "levyy_nizhniy"),
  167. (koord_pryam[2], koord_pryam[3], "pravyy_nizhniy")
  168. ]
  169. for ugol_x, ugol_y, tip_ugla in ugli:
  170. if abs(x - ugol_x) <= tolshina_klika and abs(y - ugol_y) <= tolshina_klika:
  171. peremesh_aktivno = True
  172. ugol_dlya_peremesh = tip_ugla
  173. nach_koord_peremesh = (x, y, koord_pryam[0], koord_pryam[1], koord_pryam[2], koord_pryam[3])
  174. holst.config(cursor="crosshair")
  175. return
  176. def peremeshchenie(event):
  177. global peremesh_aktivno, ugol_dlya_peremesh, nach_koord_peremesh
  178. if not peremesh_aktivno or not tekush_pryam:
  179. return
  180. x, y = event.x, event.y
  181. nach_x, nach_y, x1, y1, x2, y2 = nach_koord_peremesh
  182. dx = x - nach_x
  183. dy = y - nach_y
  184. x = max(0, min(x, holst.winfo_width()))
  185. y = max(0, min(y, holst.winfo_height()))
  186. if ugol_dlya_peremesh == "levyy_verhniy":
  187. nov_koord = [x, y, x2, y2]
  188. if nov_koord[2] - nov_koord[0] < 10:
  189. nov_koord[0] = nov_koord[2] - 10
  190. if nov_koord[3] - nov_koord[1] < 10:
  191. nov_koord[1] = nov_koord[3] - 10
  192. elif ugol_dlya_peremesh == "pravyy_verhniy":
  193. nov_koord = [x1, y, x, y2]
  194. if nov_koord[2] - nov_koord[0] < 10:
  195. nov_koord[2] = nov_koord[0] + 10
  196. if nov_koord[3] - nov_koord[1] < 10:
  197. nov_koord[1] = nov_koord[3] - 10
  198. elif ugol_dlya_peremesh == "levyy_nizhniy":
  199. nov_koord = [x, y1, x2, y]
  200. if nov_koord[2] - nov_koord[0] < 10:
  201. nov_koord[0] = nov_koord[2] - 10
  202. if nov_koord[3] - nov_koord[1] < 10:
  203. nov_koord[3] = nov_koord[1] + 10
  204. elif ugol_dlya_peremesh == "pravyy_nizhniy":
  205. nov_koord = [x1, y1, x, y]
  206. if nov_koord[2] - nov_koord[0] < 10:
  207. nov_koord[2] = nov_koord[0] + 10
  208. if nov_koord[3] - nov_koord[1] < 10:
  209. nov_koord[3] = nov_koord[1] + 10
  210. else:
  211. return
  212. holst.coords(tekush_pryam, *nov_koord)
  213. holst.delete("ugol")
  214. narisovat_uglovye_markery(*nov_koord)
  215. obnovit_poly_vvoda()
  216. sohranit_koord_pryam(*nov_koord)
  217. def konets_peremesh(event):
  218. global peremesh_aktivno, ugol_dlya_peremesh, nach_koord_peremesh
  219. if peremesh_aktivno:
  220. peremesh_aktivno = False
  221. ugol_dlya_peremesh = None
  222. nach_koord_peremesh = None
  223. holst.config(cursor="")
  224. def sohranit_v_bazu():
  225. if not tekush_put_k_file or not tekush_pryam:
  226. messagebox.showwarning("Нет данных", "Нет изображения или прямоугольника для сохранения")
  227. return
  228. try:
  229. koord = holst.coords(tekush_pryam)
  230. if not koord or len(koord) < 4:
  231. messagebox.showwarning("Ошибка", "Нет координат прямоугольника")
  232. return
  233. if poz_izobr:
  234. img_x, img_y = poz_izobr
  235. x1 = int(koord[0] - img_x)
  236. y1 = int(koord[1] - img_y)
  237. x2 = int(koord[2] - img_x)
  238. y2 = int(koord[3] - img_y)
  239. rect_width = abs(x2 - x1)
  240. rect_height = abs(y2 - y1)
  241. img_width = pil_izobr_obj.width if pil_izobr_obj else 0
  242. img_height = pil_izobr_obj.height if pil_izobr_obj else 0
  243. cursor.execute('''
  244. INSERT INTO annotations
  245. (filename, image_width, image_height, x1, y1, x2, y2,
  246. rectangle_width, rectangle_height, image_left, image_bottom,
  247. image_top, image_right, created_at)
  248. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  249. ''', (
  250. os.path.basename(tekush_put_k_file),
  251. img_width,
  252. img_height,
  253. x1,
  254. y1,
  255. x2,
  256. y2,
  257. rect_width,
  258. rect_height,
  259. int(levo_metka.cget("text").split(": ")[1]) if "Left: " in levo_metka.cget("text") else 0,
  260. int(niz_metka.cget("text").split(": ")[1]) if "Bottom: " in niz_metka.cget("text") else 0,
  261. int(verh_metka.cget("text").split(": ")[1]) if "Top: " in verh_metka.cget("text") else 0,
  262. int(pravo_metka.cget("text").split(": ")[1]) if "Right: " in pravo_metka.cget("text") else 0,
  263. datetime.datetime.now()
  264. ))
  265. conn.commit()
  266. messagebox.showinfo("Успех", "Данные успешно сохранены!")
  267. except Exception as e:
  268. messagebox.showerror("Ошибка", f"Не удалось сохранить данные: {str(e)}")
  269. def zagruzit_iz_istorii(id_zapisi):
  270. """Загрузить изображение и прямоугольник из записи истории"""
  271. try:
  272. cursor.execute("SELECT * FROM annotations WHERE id = ?", (id_zapisi,))
  273. record = cursor.fetchone()
  274. if not record:
  275. messagebox.showwarning("Ошибка", "Запись не найдена")
  276. return
  277. id_num, filename, img_w, img_h, x1, y1, x2, y2, rect_w, rect_h, img_l, img_b, img_t, img_r, created_at = record
  278. # Ищем файл изображения
  279. file_found = False
  280. possible_paths = [
  281. os.path.join(os.path.dirname(tekush_put_k_file) if tekush_put_k_file else "", filename),
  282. filename,
  283. os.path.join("images", filename),
  284. os.path.join(os.getcwd(), filename)
  285. ]
  286. for path in possible_paths:
  287. if os.path.exists(path):
  288. # Загружаем изображение
  289. zagruzit(path)
  290. # Ждем пока изображение загрузится
  291. root.update()
  292. # Устанавливаем координаты изображения
  293. levo_metka.config(text=f"Left: {img_l}")
  294. niz_metka.config(text=f"Bottom: {img_b}")
  295. verh_metka.config(text=f"Top: {img_t}")
  296. pravo_metka.config(text=f"Right: {img_r}")
  297. # Устанавливаем координаты прямоугольника
  298. vvod_koord["X1"].delete(0, tk.END)
  299. vvod_koord["X1"].insert(0, str(x1))
  300. vvod_koord["Y1"].delete(0, tk.END)
  301. vvod_koord["Y1"].insert(0, str(y1))
  302. vvod_koord["X2"].delete(0, tk.END)
  303. vvod_koord["X2"].insert(0, str(x2))
  304. vvod_koord["Y2"].delete(0, tk.END)
  305. vvod_koord["Y2"].insert(0, str(y2))
  306. # Рисуем прямоугольник
  307. narisovat_pryam()
  308. file_found = True
  309. messagebox.showinfo("Успех", f"Загружена запись ID: {id_num}\nФайл: {filename}")
  310. break
  311. if not file_found:
  312. messagebox.showwarning("Файл не найден",
  313. f"Не удалось найти файл: {filename}\n\n"
  314. f"Пожалуйста, выберите файл вручную.")
  315. # Открываем диалог выбора файла
  316. put_k_file = filedialog.askopenfilename(
  317. title=f"Найдите файл: {filename}",
  318. filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif *.bmp")]
  319. )
  320. if put_k_file:
  321. zagruzit(put_k_file)
  322. # Повторно применяем параметры
  323. zagruzit_iz_istorii(id_zapisi)
  324. except Exception as e:
  325. messagebox.showerror("Ошибка", f"Не удалось загрузить данные: {str(e)}")
  326. def pokazat_istoriju():
  327. try:
  328. cursor.execute("SELECT * FROM annotations ORDER BY id DESC")
  329. records = cursor.fetchall()
  330. if not records:
  331. messagebox.showinfo("История", "База данных пуста")
  332. return
  333. history_window = tk.Toplevel(root)
  334. history_window.title("История")
  335. history_window.geometry("1000x550")
  336. history_window.configure(bg=bg_color)
  337. text_frame = tk.Frame(history_window, bg=bg_color)
  338. text_frame.pack(fill='both', expand=True, padx=10, pady=10)
  339. text_widget = tk.Text(text_frame, bg='#1e1e1e', fg=text_color,
  340. font=("Courier", 10), wrap='none')
  341. scrollbar_y = tk.Scrollbar(text_frame, orient='vertical', command=text_widget.yview)
  342. scrollbar_x = tk.Scrollbar(text_frame, orient='horizontal', command=text_widget.xview)
  343. text_widget.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
  344. text_widget.grid(row=0, column=0, sticky='nsew')
  345. scrollbar_y.grid(row=0, column=1, sticky='ns')
  346. scrollbar_x.grid(row=1, column=0, sticky='ew')
  347. text_frame.grid_rowconfigure(0, weight=1)
  348. text_frame.grid_columnconfigure(0, weight=1)
  349. col_widths = {'id': 4, 'filename': 25, 'img_size': 20, 'rect_coords': 25, 'rect_size': 20, 'date': 20}
  350. header_format = "{:>{id_width}} | {:<{filename_width}} | {:^{img_size_width}} | {:<{rect_coords_width}} | {:^{rect_size_width}} | {:<{date_width}} | {:^15}"
  351. headers = header_format.format(
  352. "ID", "Файл", "Размер изображения", "Прямоугольник", "Размер прямоуг.", "Дата создания", "Действия",
  353. id_width=col_widths['id'],
  354. filename_width=col_widths['filename'],
  355. img_size_width=col_widths['img_size'],
  356. rect_coords_width=col_widths['rect_coords'],
  357. rect_size_width=col_widths['rect_size'],
  358. date_width=col_widths['date']
  359. )
  360. text_widget.insert('end', headers + "\n")
  361. text_widget.insert('end', "-" * len(headers) + "\n")
  362. record_format = "{:>{id_width}} | {:<{filename_width}.{filename_width}} | {:^{img_size_width}} | {:<{rect_coords_width}} | {:^{rect_size_width}} | {:<{date_width}}"
  363. for record in records:
  364. id_num, filename, img_w, img_h, x1, y1, x2, y2, rect_w, rect_h, img_l, img_b, img_t, img_r, created_at = record
  365. img_size = f"{img_w}x{img_h}"
  366. rect_coords = f"({x1},{y1})-({x2},{y2})"
  367. rect_size = f"{rect_w}x{rect_h}"
  368. date_str = created_at[:19] if isinstance(created_at, str) else str(created_at)[:19]
  369. line = record_format.format(
  370. id_num, filename, img_size, rect_coords, rect_size, date_str,
  371. id_width=col_widths['id'],
  372. filename_width=col_widths['filename'],
  373. img_size_width=col_widths['img_size'],
  374. rect_coords_width=col_widths['rect_coords'],
  375. rect_size_width=col_widths['rect_size'],
  376. date_width=col_widths['date']
  377. )
  378. line_with_id = line + f" | ID:{id_num:>5}"
  379. text_widget.insert('end', line_with_id + "\n")
  380. text_widget.config(state='disabled')
  381. button_frame = tk.Frame(history_window, bg=bg_color)
  382. button_frame.pack(pady=10)
  383. id_frame = tk.Frame(button_frame, bg=bg_color)
  384. id_frame.pack(pady=5)
  385. tk.Label(id_frame, text="ID записи:", bg=bg_color, fg=text_color).pack(side='left', padx=5)
  386. entry_id = tk.Entry(id_frame, width=10, font=("Arial", 10))
  387. entry_id.pack(side='left', padx=5)
  388. def zagruzit_po_id():
  389. try:
  390. id_zapisi = int(entry_id.get())
  391. zagruzit_iz_istorii(id_zapisi)
  392. history_window.destroy() # Закрываем окно истории
  393. except ValueError:
  394. messagebox.showwarning("Ошибка", "Введите корректный ID записи")
  395. btn_open = tk.Button(button_frame, text="Загрузить выбранную запись",
  396. command=zagruzit_po_id,
  397. bg=accent_color, fg=text_color,
  398. font=("Arial", 10), relief='flat', padx=10, pady=5)
  399. btn_open.pack(pady=5)
  400. btn_close = tk.Button(button_frame, text="Закрыть",
  401. command=history_window.destroy,
  402. bg='#d94a4a', fg=text_color,
  403. font=("Arial", 10), relief='flat', padx=10, pady=5)
  404. btn_close.pack(pady=5)
  405. except Exception as e:
  406. messagebox.showerror("Ошибка", f"Не удалось загрузить историю: {str(e)}")
  407. ramka_knopok = tk.Frame(ramka_pryam, bg=frame_color)
  408. ramka_knopok.pack(pady=10)
  409. knopka_ris = tk.Button(ramka_knopok, text="Нарисовать",
  410. command=narisovat_pryam, bg=accent_color, fg=text_color,
  411. font=("Arial", 9), relief='flat', padx=5, pady=3)
  412. knopka_ris.pack(side='left', padx=5)
  413. knopka_ochist = tk.Button(ramka_knopok, text="Очистить",
  414. command=ochistit_pryam, bg='#d94a4a', fg=text_color,
  415. font=("Arial", 9), relief='flat', padx=5, pady=3)
  416. knopka_ochist.pack(side='left', padx=5)
  417. razdelitel2 = tk.Frame(panel, height=2, bg=accent_color)
  418. razdelitel2.pack(fill='x', padx=20, pady=20)
  419. def vibor():
  420. global tekush_put_k_file, pil_izobr_obj
  421. put_k_file = filedialog.askopenfilename(
  422. title="Выберите изображение",
  423. filetypes=[("Image files", "*.png *.jpg *.jpeg *.gif *.bmp")]
  424. )
  425. if put_k_file:
  426. tekush_put_k_file = put_k_file
  427. zagruzit(put_k_file)
  428. knopka_vibor = tk.Button(panel, text="Выбрать изображение",
  429. command=vibor, bg=accent_color, fg=text_color,
  430. font=("Arial", 10), relief='flat', padx=10, pady=5)
  431. knopka_vibor.pack(pady=10)
  432. knopka_sohranit = tk.Button(panel, text="Сохранить в базу данных",
  433. command=sohranit_v_bazu, bg='#2ecc71', fg=text_color,
  434. font=("Arial", 10, "bold"), relief='flat', padx=10, pady=5)
  435. knopka_sohranit.pack(pady=10)
  436. knopka_istorija = tk.Button(panel, text="Показать историю",
  437. command=pokazat_istoriju, bg='#9b59b6', fg=text_color,
  438. font=("Arial", 10), relief='flat', padx=10, pady=5)
  439. knopka_istorija.pack(pady=10)
  440. holst = tk.Canvas(root, bg='#1e1e1e', width=600, height=600, highlightthickness=0)
  441. holst.pack(side='left', fill='both', expand=True)
  442. holst.bind("<Button-1>", nachalo_peremesh)
  443. holst.bind("<B1-Motion>", peremeshchenie)
  444. holst.bind("<ButtonRelease-1>", konets_peremesh)
  445. def zagruzit(put_k_file):
  446. global tekush_izobr, poz_izobr, tekush_pryam, pil_izobr_obj
  447. try:
  448. pil_izobr_obj = Image.open(put_k_file)
  449. holst.delete("all")
  450. tekush_pryam = None
  451. shir_holst = holst.winfo_width()
  452. vys_holst = holst.winfo_height()
  453. img_width = pil_izobr_obj.width
  454. img_height = pil_izobr_obj.height
  455. scale_w = shir_holst / img_width
  456. scale_h = vys_holst / img_height
  457. scale = min(scale_w, scale_h, 1.0)
  458. new_width = int(img_width * scale)
  459. new_height = int(img_height * scale)
  460. pil_izobr_obj = pil_izobr_obj.resize((new_width, new_height), Image.Resampling.LANCZOS)
  461. tekush_izobr = ImageTk.PhotoImage(pil_izobr_obj)
  462. x_centr = (shir_holst - new_width) // 2
  463. y_centr = (vys_holst - new_height) // 2
  464. id_izobr = holst.create_image(x_centr, y_centr, anchor='nw', image=tekush_izobr)
  465. koord_izobr = holst.bbox(id_izobr)
  466. poz_izobr = (koord_izobr[0], koord_izobr[1])
  467. levo_metka.config(text=f"Left: {koord_izobr[0]}")
  468. niz_metka.config(text=f"Bottom: {koord_izobr[1]}")
  469. verh_metka.config(text=f"Top: {koord_izobr[2]}")
  470. pravo_metka.config(text=f"Right: {koord_izobr[3]}")
  471. for i, metka in enumerate(metki_koord):
  472. vvod_koord[metka.replace(":", "")].delete(0, tk.END)
  473. vvod_koord[metka.replace(":", "")].insert(0, znach_po_umolch[i])
  474. except Exception as e:
  475. holst.delete("all")
  476. holst.create_text(
  477. 300, 300, text="Ошибка загрузки изображения",
  478. fill=text_color, font=("Arial", 12), anchor='center'
  479. )
  480. def obnovit(event=None):
  481. if tekush_izobr and pil_izobr_obj:
  482. holst.delete("all")
  483. tekush_pryam = None
  484. shir_holst = holst.winfo_width()
  485. vys_holst = holst.winfo_height()
  486. if shir_holst <= 1 or vys_holst <= 1:
  487. return
  488. img_width = pil_izobr_obj.width
  489. img_height = pil_izobr_obj.height
  490. scale_w = shir_holst / img_width
  491. scale_h = vys_holst / img_height
  492. scale = min(scale_w, scale_h, 1.0)
  493. new_width = int(img_width * scale)
  494. new_height = int(img_height * scale)
  495. resized_img = pil_izobr_obj.resize((new_width, new_height), Image.Resampling.LANCZOS)
  496. tekush_izobr = ImageTk.PhotoImage(resized_img)
  497. x_centr = (shir_holst - new_width) // 2
  498. y_centr = (vys_holst - new_height) // 2
  499. id_izobr = holst.create_image(x_centr, y_centr, anchor='nw', image=tekush_izobr)
  500. koord_izobr = holst.bbox(id_izobr)
  501. poz_izobr = (koord_izobr[0], koord_izobr[1])
  502. levo_metka.config(text=f"Left: {koord_izobr[0]}")
  503. niz_metka.config(text=f"Bottom: {koord_izobr[1]}")
  504. verh_metka.config(text=f"Top: {koord_izobr[2]}")
  505. pravo_metka.config(text=f"Right: {koord_izobr[3]}")
  506. holst.bind("<Configure>", obnovit)
  507. holst.create_text(
  508. 300, 300,
  509. text="Нажмите 'Выбрать изображение'\nчтобы загрузить картинку",
  510. fill=text_color, font=("Arial", 12), anchor='center', justify='center'
  511. )
  512. def zakryt_bazu():
  513. conn.close()
  514. root.destroy()
  515. root.protocol("WM_DELETE_WINDOW", zakryt_bazu)
  516. root.mainloop()