findbase/findbase.py
2025-11-01 20:01:44 +01:00

1959 lines
79 KiB
Python

#!/usr/bin/env python
import cv2
import numpy as np
import pygetwindow as gw
import tkinter as tk
from tkinter import messagebox, filedialog, simpledialog
from PIL import Image, ImageGrab, ImageTk
import threading
import time
import pickle
import os
from concurrent.futures import ThreadPoolExecutor
import json
class TemplatePresetManager:
"""Gestionnaire de presets de templates avec sauvegarde/chargement"""
def __init__(self, presets_file="lastwar_presets.json"):
self.presets_file = presets_file
self.presets = {}
self.current_preset = None
self.load_presets()
def load_presets(self):
try:
if os.path.exists(self.presets_file):
with open(self.presets_file, 'r', encoding='utf-8') as f:
preset_list = json.load(f)
self.presets = {}
for preset_info in preset_list:
name = preset_info['name']
pkl_file = preset_info['file']
if os.path.exists(pkl_file):
with open(pkl_file, 'rb') as pf:
templates = pickle.load(pf)
self.presets[name] = {
'templates': templates,
'file': pkl_file
}
print(f"Presets charges: {len(self.presets)}")
return True
except Exception as e:
print(f"Erreur chargement presets: {e}")
return False
def save_presets(self):
try:
preset_list = []
for name, data in self.presets.items():
preset_list.append({
'name': name,
'file': data['file']
})
with open(self.presets_file, 'w', encoding='utf-8') as f:
json.dump(preset_list, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"Erreur sauvegarde presets: {e}")
return False
def add_preset(self, name, templates):
safe_name = "".join(c if c.isalnum() or c in (' ', '_', '-') else '_' for c in name)
pkl_file = f"preset_{safe_name}.pkl"
try:
with open(pkl_file, 'wb') as f:
pickle.dump(templates, f)
self.presets[name] = {
'templates': templates,
'file': pkl_file
}
self.save_presets()
print(f"Preset '{name}' cree avec {len(templates)} templates")
return True
except Exception as e:
print(f"Erreur creation preset: {e}")
return False
def delete_preset(self, name):
if name in self.presets:
try:
pkl_file = self.presets[name]['file']
if os.path.exists(pkl_file):
os.remove(pkl_file)
del self.presets[name]
self.save_presets()
print(f"Preset '{name}' supprime")
return True
except Exception as e:
print(f"Erreur suppression preset: {e}")
return False
def rename_preset(self, old_name, new_name):
if old_name in self.presets and new_name not in self.presets:
self.presets[new_name] = self.presets.pop(old_name)
self.save_presets()
print(f"Preset renomme: '{old_name}' -> '{new_name}'")
return True
return False
def get_preset_names(self):
return list(self.presets.keys())
def load_preset(self, name):
if name in self.presets:
self.current_preset = name
return self.presets[name]['templates']
return None
class GameOverlay:
def __init__(self, window_title="Last War"):
self.window_title = window_title
self.target_window = None
self.running = False
self.overlay_root = None
self.canvas = None
self.overlay_created = False
self.preset_manager = TemplatePresetManager()
self.captured_templates = []
self.mode = "setup"
self.settings_file = "lastwar_settings.pkl"
self.current_threshold = 0.85
self.threshold_step = 0.02
self.min_threshold = 0.70
self.max_threshold = 0.98
self.capture_window = None
self.capture_canvas = None
self.control_window = None
self.main_menu = None
self.template_mode = "normal"
self.debug_matches = []
self.last_positions = []
self.frame_skip = 0
self.max_frame_skip = 1
self.executor = ThreadPoolExecutor(max_workers=3)
# ========== MENU PRINCIPAL ==========
def create_main_menu(self):
"""Menu principal simplifie"""
self.main_menu = tk.Tk()
self.main_menu.title("LastWar - Gestionnaire de Templates")
self.main_menu.geometry("500x600")
self.main_menu.resizable(False, False)
# Titre
title = tk.Label(self.main_menu,
text="LastWar - Gestionnaire de Templates",
font=("Arial", 16, "bold"),
fg="darkblue")
title.pack(pady=15)
# Section Presets
preset_frame = tk.LabelFrame(self.main_menu,
text="PRESETS DE TEMPLATES",
font=("Arial", 12, "bold"),
padx=10, pady=10)
preset_frame.pack(fill="both", expand=True, padx=20, pady=10)
# Liste des presets
list_container = tk.Frame(preset_frame)
list_container.pack(fill="both", expand=True, pady=5)
scrollbar = tk.Scrollbar(list_container)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.preset_listbox = tk.Listbox(list_container,
yscrollcommand=scrollbar.set,
font=("Arial", 11),
height=10)
self.preset_listbox.pack(side=tk.LEFT, fill="both", expand=True)
scrollbar.config(command=self.preset_listbox.yview)
self.refresh_preset_list()
# Info preset actuel
self.current_preset_label = tk.Label(preset_frame,
text=f"Preset actuel: {self.preset_manager.current_preset or 'Aucun'}",
font=("Arial", 10, "italic"),
fg="gray")
self.current_preset_label.pack(pady=5)
# Boutons de gestion des presets
btn_frame = tk.Frame(preset_frame)
btn_frame.pack(pady=10)
tk.Button(btn_frame, text="Creer Nouveau Preset",
command=self.create_new_preset,
bg="green", fg="white", font=("Arial", 10, "bold"),
width=20, height=2).grid(row=0, column=0, padx=5, pady=2)
tk.Button(btn_frame, text="Charger & Demarrer",
command=self.load_and_start_preset,
bg="blue", fg="white", font=("Arial", 10, "bold"),
width=20, height=2).grid(row=0, column=1, padx=5, pady=2)
tk.Button(btn_frame, text="Editer Preset",
command=self.edit_selected_preset,
bg="orange", fg="white", font=("Arial", 9),
width=20).grid(row=1, column=0, padx=5, pady=2)
tk.Button(btn_frame, text="Supprimer Preset",
command=self.delete_selected_preset,
bg="red", fg="white", font=("Arial", 9),
width=20).grid(row=1, column=1, padx=5, pady=2)
tk.Button(btn_frame, text="Renommer Preset",
command=self.rename_selected_preset,
bg="purple", fg="white", font=("Arial", 9),
width=20).grid(row=2, column=0, columnspan=2, padx=5, pady=2)
# Bouton quitter
tk.Button(self.main_menu, text="Quitter",
command=self.quit_application,
bg="gray", fg="white", font=("Arial", 10),
width=20).pack(pady=10)
self.main_menu.protocol("WM_DELETE_WINDOW", self.quit_application)
self.main_menu.mainloop()
def refresh_preset_list(self):
"""Rafraichit la liste des presets"""
self.preset_listbox.delete(0, tk.END)
for name in self.preset_manager.get_preset_names():
templates_count = len(self.preset_manager.presets[name]['templates'])
self.preset_listbox.insert(tk.END, f"{name} ({templates_count} templates)")
def create_new_preset(self):
"""Cree un nouveau preset"""
name = simpledialog.askstring("Nouveau Preset",
"Nom du nouveau preset:",
parent=self.main_menu)
if not name:
return
if name in self.preset_manager.presets:
messagebox.showerror("Erreur", f"Le preset '{name}' existe deja")
return
# Cacher le menu principal
self.main_menu.withdraw()
# Lancer la capture
self.captured_templates = []
if self.create_capture_interface():
if hasattr(self, 'capture_window') and self.capture_window:
self.capture_window.wait_window()
# Verifier le resultat
if self.captured_templates and len(self.captured_templates) >= 2:
# Sauvegarder comme nouveau preset
if self.preset_manager.add_preset(name, self.captured_templates):
self.refresh_preset_list()
# Reafficher le menu
self.main_menu.deiconify()
def load_and_start_preset(self):
"""Charge le preset selectionne et demarre la detection"""
selection = self.preset_listbox.curselection()
if not selection:
messagebox.showwarning("Aucune selection", "Selectionnez un preset")
return
preset_name = self.preset_listbox.get(selection[0]).split(" (")[0]
templates = self.preset_manager.load_preset(preset_name)
if templates:
self.captured_templates = templates
for template in self.captured_templates:
template['strict_threshold'] = self.current_threshold
# Fermer le menu principal
self.main_menu.destroy()
self.main_menu = None
# Demarrer la detection
self.mode = "detection"
self.start_overlay_detection()
def edit_selected_preset(self):
"""Edite le preset selectionne"""
selection = self.preset_listbox.curselection()
if not selection:
messagebox.showwarning("Aucune selection", "Selectionnez un preset")
return
preset_name = self.preset_listbox.get(selection[0]).split(" (")[0]
templates = self.preset_manager.load_preset(preset_name)
if templates:
self.captured_templates = templates
# Cacher le menu
self.main_menu.withdraw()
# Ouvrir l'editeur
self.open_template_editor(preset_name)
def delete_selected_preset(self):
"""Supprime le preset selectionne"""
selection = self.preset_listbox.curselection()
if not selection:
messagebox.showwarning("Aucune selection", "Selectionnez un preset")
return
preset_name = self.preset_listbox.get(selection[0]).split(" (")[0]
if messagebox.askyesno("Confirmer", f"Supprimer le preset '{preset_name}' ?"):
if self.preset_manager.delete_preset(preset_name):
self.refresh_preset_list()
def rename_selected_preset(self):
"""Renomme le preset selectionne"""
selection = self.preset_listbox.curselection()
if not selection:
messagebox.showwarning("Aucune selection", "Selectionnez un preset")
return
old_name = self.preset_listbox.get(selection[0]).split(" (")[0]
new_name = simpledialog.askstring("Renommer",
f"Nouveau nom pour '{old_name}':",
parent=self.main_menu)
if new_name and new_name != old_name:
if self.preset_manager.rename_preset(old_name, new_name):
self.refresh_preset_list()
# ========== EDITEUR DE TEMPLATES ==========
def open_template_editor(self, preset_name):
"""Ouvre l'editeur de templates pour un preset"""
editor = tk.Toplevel()
editor.title(f"Edition: {preset_name}")
editor.geometry("400x600")
# Titre
tk.Label(editor, text=f"Edition: {preset_name}",
font=("Arial", 14, "bold")).pack(pady=10)
# Liste des templates
list_frame = tk.LabelFrame(editor, text="Templates", font=("Arial", 10, "bold"))
list_frame.pack(fill="both", expand=True, padx=10, pady=10)
template_list = tk.Listbox(list_frame, font=("Arial", 10), height=15)
template_list.pack(fill="both", expand=True, padx=5, pady=5)
def refresh_template_list():
template_list.delete(0, tk.END)
for i, tmpl in enumerate(self.captured_templates):
size = tmpl.get('size', (0, 0))
template_list.insert(tk.END, f"Template {i}: {size[0]}x{size[1]}")
refresh_template_list()
# Boutons d'action
btn_frame = tk.Frame(editor)
btn_frame.pack(pady=10)
def add_template():
editor.withdraw()
self.create_template_capture_window()
# Attendre la fermeture
editor.after(100, lambda: check_capture_done(editor))
def check_capture_done(ed):
# Verifier periodiquement si la capture est terminee
if hasattr(self, 'capture_window') and self.capture_window and self.capture_window.winfo_exists():
ed.after(100, lambda: check_capture_done(ed))
else:
refresh_template_list()
ed.deiconify()
def delete_template():
sel = template_list.curselection()
if sel:
idx = sel[0]
if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"):
self.captured_templates.pop(idx)
refresh_template_list()
def save_changes():
if self.preset_manager.add_preset(preset_name, self.captured_templates):
editor.destroy()
self.main_menu.deiconify()
self.refresh_preset_list()
def cancel_edit():
editor.destroy()
self.main_menu.deiconify()
tk.Button(btn_frame, text="Ajouter Template",
command=add_template,
bg="green", fg="white", width=18).pack(pady=2)
tk.Button(btn_frame, text="Supprimer Template",
command=delete_template,
bg="red", fg="white", width=18).pack(pady=2)
tk.Button(btn_frame, text="Sauvegarder",
command=save_changes,
bg="blue", fg="white", width=18,
font=("Arial", 10, "bold")).pack(pady=10)
tk.Button(btn_frame, text="Annuler",
command=cancel_edit,
bg="gray", fg="white", width=18).pack(pady=2)
# ========== CAPTURE DE TEMPLATES ==========
def create_capture_interface(self):
"""Overlay transparent pour capturer des templates"""
try:
print("Mode capture - Cliquez sur le jeu pour selectionner")
print(" Entree = Valider | Echap = Annuler")
self.capture_window = tk.Toplevel()
self.capture_window.title("CAPTURE TEMPLATES")
self.capture_window.attributes("-topmost", True)
self.capture_window.attributes("-alpha", 0.3)
self.capture_window.geometry(f"{self.target_window.width}x{self.target_window.height}+{self.target_window.left}+{self.target_window.top}")
self.capture_window.overrideredirect(True)
self.capture_canvas = tk.Canvas(self.capture_window,
width=self.target_window.width,
height=self.target_window.height,
highlightthickness=0,
bg='gray')
self.capture_canvas.pack()
self.capture_canvas.create_text(
self.target_window.width // 2, 30,
text="CLIQUEZ SUR LES TEMPLATES (2 minimum)\nEntree = Valider | Echap = Annuler",
fill="yellow",
font=("Arial", 14, "bold"),
justify=tk.CENTER,
tags="instructions"
)
self.counter_text = self.capture_canvas.create_text(
self.target_window.width // 2, 70,
text="Templates: 0/2 minimum",
fill="white",
font=("Arial", 12, "bold"),
tags="counter"
)
def on_click(event):
if len(self.captured_templates) >= 8:
return
image = self.capture_window_fast(self.target_window)
if image is None:
return
x, y = event.x, event.y
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
size = 24
x1 = max(0, x - size//2)
y1 = max(0, y - size//2)
x2 = min(gray.shape[1], x + size//2)
y2 = min(gray.shape[0], y + size//2)
template = gray[y1:y2, x1:x2]
if template.size > 100:
self.captured_templates.append({
'template': template,
'size': (x2-x1, y2-y1),
'position': (x, y),
'strict_threshold': self.current_threshold
})
self.capture_canvas.create_rectangle(
x - size//2, y - size//2,
x + size//2, y + size//2,
outline="green",
width=3,
tags="selection"
)
self.capture_canvas.create_text(
x, y,
text=str(len(self.captured_templates)),
fill="green",
font=("Arial", 16, "bold"),
tags="selection"
)
count = len(self.captured_templates)
color = "lime" if count >= 2 else "white"
self.capture_canvas.itemconfig(self.counter_text,
text=f"Templates: {count}/2 minimum (max 8)",
fill=color)
def validate():
if len(self.captured_templates) >= 2:
self.capture_window.destroy()
else:
print("Minimum 2 templates requis")
def cancel():
self.captured_templates = []
self.capture_window.destroy()
self.capture_canvas.bind("<Button-1>", on_click)
self.capture_window.bind("<Return>", lambda e: validate())
self.capture_window.bind("<Escape>", lambda e: cancel())
self.capture_window.focus_set()
return True
except Exception as e:
print(f"Erreur creation interface: {e}")
return False
def create_template_capture_window(self):
"""Overlay pour ajouter UN template (mode edition) avec detection visible"""
try:
temp_overlay = tk.Toplevel()
temp_overlay.title("AJOUT TEMPLATE")
temp_overlay.attributes("-topmost", True)
temp_overlay.attributes("-alpha", 0.3)
temp_overlay.geometry(f"{self.target_window.width}x{self.target_window.height}+{self.target_window.left}+{self.target_window.top}")
temp_overlay.overrideredirect(True)
canvas = tk.Canvas(temp_overlay,
width=self.target_window.width,
height=self.target_window.height,
highlightthickness=0,
bg='gray')
canvas.pack()
canvas.create_text(
self.target_window.width // 2, 30,
text="CLIQUEZ POUR AJOUTER UN TEMPLATE\nEchap pour annuler",
fill="yellow",
font=("Arial", 16, "bold"),
justify=tk.CENTER
)
# Variable pour suivre l'etat
is_active = [True]
def update_detections():
"""Met a jour l'affichage des detections existantes"""
if not is_active[0]:
return
try:
# Capturer l'image actuelle
image = self.capture_window_fast(self.target_window)
if image is not None:
# Detecter avec les templates existants
positions = self.find_matches_with_templates(image)
# Effacer les anciennes detections
canvas.delete("detection")
# Afficher les detections en rouge
for x, y, w, h in positions:
margin = 4
x1 = max(0, x - margin)
y1 = max(0, y - margin)
x2 = x + w + margin
y2 = y + h + margin
canvas.create_rectangle(
x1, y1, x2, y2,
outline="#FF0000",
width=2,
fill="",
tags="detection"
)
# Continuer a mettre a jour
if is_active[0]:
temp_overlay.after(100, update_detections)
except:
pass
def on_click(event):
image = self.capture_window_fast(self.target_window)
if image is None:
return
x, y = event.x, event.y
if self.add_template_at_position(image, x, y):
canvas.create_oval(x-30, y-30, x+30, y+30,
outline="green", width=4)
canvas.create_text(x, y, text="OK",
fill="green", font=("Arial", 32, "bold"))
is_active[0] = False
temp_overlay.after(1000, temp_overlay.destroy)
else:
canvas.create_text(x, y, text="X",
fill="red", font=("Arial", 32, "bold"))
def on_close():
is_active[0] = False
temp_overlay.destroy()
canvas.bind("<Button-1>", on_click)
temp_overlay.bind("<Escape>", lambda e: on_close())
temp_overlay.focus_set()
# Demarrer la mise a jour des detections
update_detections()
except Exception as e:
print(f"Erreur overlay capture: {e}")
def add_template_at_position(self, image, x, y):
"""Ajoute un template a la position specifiee"""
try:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
size = 24
x1 = max(0, x - size//2)
y1 = max(0, y - size//2)
x2 = min(gray.shape[1], x + size//2)
y2 = min(gray.shape[0], y + size//2)
template = gray[y1:y2, x1:x2]
if template.size > 100:
self.captured_templates.append({
'template': template,
'size': (x2-x1, y2-y1),
'position': (x, y),
'strict_threshold': self.current_threshold
})
print(f"Template ajoute ({x2-x1}x{y2-y1})")
# Sauvegarder et rafraîchir l'affichage
if self.preset_manager.current_preset:
self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates)
# Rafraîchir la liste dans la fenêtre de contrôle
if hasattr(self, 'refresh_templates_display'):
try:
self.refresh_templates_display()
except:
pass
return True
return False
except Exception as e:
print(f"Erreur ajout template: {e}")
return False
# ========== UTILITAIRES ==========
def find_game_window(self):
try:
windows = gw.getWindowsWithTitle("Last War-Survival Game")
if windows:
return windows[0]
all_windows = gw.getAllWindows()
for window in all_windows:
if "last war" in window.title.lower():
return window
return None
except:
return None
def capture_window_fast(self, window):
try:
left, top, width, height = window.left, window.top, window.width, window.height
if width <= 0 or height <= 0:
return None
current_bbox = (left, top, left + width, top + height)
screenshot = ImageGrab.grab(current_bbox, all_screens=False)
return np.array(screenshot)
except:
return None
def save_settings(self):
try:
settings = {'threshold': self.current_threshold, 'version': '1.0'}
with open(self.settings_file, 'wb') as f:
pickle.dump(settings, f)
return True
except:
return False
def load_settings(self):
try:
if os.path.exists(self.settings_file):
with open(self.settings_file, 'rb') as f:
settings = pickle.load(f)
self.current_threshold = settings.get('threshold', 0.85)
return True
return False
except:
return False
def adjust_threshold_up(self):
if self.current_threshold < self.max_threshold:
self.current_threshold = min(self.max_threshold, self.current_threshold + self.threshold_step)
for template in self.captured_templates:
template['strict_threshold'] = self.current_threshold
print(f"Seuil: {self.current_threshold:.2f} (plus strict)")
self.update_threshold_display()
self.save_settings()
def adjust_threshold_down(self):
if self.current_threshold > self.min_threshold:
self.current_threshold = max(self.min_threshold, self.current_threshold - self.threshold_step)
for template in self.captured_templates:
template['strict_threshold'] = self.current_threshold
print(f"Seuil: {self.current_threshold:.2f} (moins strict)")
self.update_threshold_display()
self.save_settings()
def reset_threshold(self):
self.current_threshold = 0.85
for template in self.captured_templates:
template['strict_threshold'] = self.current_threshold
print(f"Seuil: {self.current_threshold:.2f}")
self.update_threshold_display()
self.save_settings()
# ========== DETECTION ==========
def find_matches_single_template(self, gray_image, template_data, template_id):
try:
template = template_data['template']
threshold = template_data.get('strict_threshold', self.current_threshold)
if template.size == 0:
return []
result = cv2.matchTemplate(gray_image, template, cv2.TM_CCOEFF_NORMED)
locations = np.where(result >= threshold)
matches = []
for pt in zip(*locations[::-1]):
x, y = pt
h, w = template.shape
score = result[y, x]
if score >= threshold:
matches.append({
'x': x, 'y': y, 'w': w, 'h': h,
'score': score, 'template_id': template_id
})
return matches
except Exception as e:
return []
def find_matches_with_templates(self, image):
positions = []
if not self.captured_templates:
return positions
try:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
futures = []
for i, template_data in enumerate(self.captured_templates):
future = self.executor.submit(
self.find_matches_single_template,
gray, template_data, i
)
futures.append(future)
all_matches = []
for future in futures:
try:
matches = future.result(timeout=0.1)
all_matches.extend(matches)
except:
continue
all_matches.sort(key=lambda m: m['score'], reverse=True)
template_counts = {}
debug_matches = []
for match in all_matches:
x, y, w, h = match['x'], match['y'], match['w'], match['h']
score = match['score']
template_id = match['template_id']
if template_counts.get(template_id, 0) >= 15:
continue
is_duplicate = False
for existing_x, existing_y, _, _ in positions:
if abs(x - existing_x) < 15 and abs(y - existing_y) < 10:
is_duplicate = True
break
if not is_duplicate and score >= self.current_threshold:
positions.append((x, y, w, h))
debug_matches.append((x, y, w, h, template_id, score))
template_counts[template_id] = template_counts.get(template_id, 0) + 1
if len(positions) >= 25:
break
self.debug_matches = debug_matches
except Exception as e:
print(f"Erreur matching: {e}")
return positions
def toggle_debug_mode(self):
if self.template_mode == "debug":
self.template_mode = "normal"
print("Mode debug desactive")
else:
self.template_mode = "debug"
print("Mode debug active")
self.update_control_interface()
def update_overlay(self, positions):
if not self.canvas or not self.overlay_root or not self.running:
return
try:
if not self.overlay_root.winfo_exists():
return
if positions != self.last_positions:
self.canvas.delete("detection")
if self.template_mode == "debug" and hasattr(self, 'debug_matches'):
for x, y, w, h, template_id, score in self.debug_matches:
margin = 4
x1 = max(0, x - margin)
y1 = max(0, y - margin)
x2 = x + w + margin
y2 = y + h + margin
colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFA500", "#800080"]
color = colors[template_id % len(colors)]
self.canvas.create_rectangle(
x1, y1, x2, y2,
outline=color,
width=2,
tags="detection"
)
self.canvas.create_text(
x + w//2, y + h//2,
text=f"T{template_id}",
fill=color,
font=("Arial", 10, "bold"),
tags="detection"
)
self.canvas.create_text(
x + w//2, y + h + 15,
text=f"{score:.2f}",
fill=color,
font=("Arial", 8),
tags="detection"
)
else:
for x, y, w, h in positions:
margin = 4
x1 = max(0, x - margin)
y1 = max(0, y - margin)
x2 = x + w + margin
y2 = y + h + margin
self.canvas.create_rectangle(
x1, y1, x2, y2,
outline="#FF0000",
width=2,
fill="",
tags="detection"
)
self.last_positions = positions.copy()
except (tk.TclError, RuntimeError):
self.running = False
def safe_update_overlay(self, positions):
if self.overlay_root and self.running:
try:
self.overlay_root.after(0, lambda: self.update_overlay(positions))
except (tk.TclError, RuntimeError):
self.running = False
def create_overlay(self, window):
try:
self.overlay_root = tk.Toplevel(self.control_window)
self.overlay_root.title("LastWar Detection Active")
self.overlay_root.attributes("-alpha", 0.85)
self.overlay_root.attributes("-topmost", True)
self.overlay_root.overrideredirect(True)
self.overlay_root.geometry(f"{window.width}x{window.height}+{window.left}+{window.top}")
self.canvas = tk.Canvas(
self.overlay_root,
width=window.width,
height=window.height,
highlightthickness=0,
bg='black'
)
self.canvas.pack()
self.overlay_root.attributes("-transparentcolor", "black")
self.overlay_root.bind("<Escape>", lambda e: self.stop())
self.overlay_root.protocol("WM_DELETE_WINDOW", self.stop)
self.overlay_created = True
print("Overlay detection actif")
except Exception as e:
print(f"Erreur overlay: {e}")
self.running = False
def create_control_window(self):
try:
self.control_window = tk.Toplevel()
self.control_window.title("Controles Detection")
self.control_window.geometry("700x700")
self.control_window.attributes("-topmost", True)
# Section seuil
seuil_frame = tk.LabelFrame(self.control_window, text="AJUSTEMENT SEUIL", font=("Arial", 10, "bold"))
seuil_frame.pack(fill="x", padx=5, pady=5)
self.threshold_label = tk.Label(seuil_frame,
text=f"Seuil: {self.current_threshold:.2f}",
font=("Arial", 12, "bold"),
fg="green")
self.threshold_label.pack(pady=5)
btn_frame = tk.Frame(seuil_frame)
btn_frame.pack(pady=5)
tk.Button(btn_frame, text="+ STRICT",
command=self.adjust_threshold_up,
bg="red", fg="white", font=("Arial", 9, "bold"), width=12).pack(side=tk.LEFT, padx=2)
tk.Button(btn_frame, text="- STRICT",
command=self.adjust_threshold_down,
bg="orange", fg="white", font=("Arial", 9, "bold"), width=12).pack(side=tk.LEFT, padx=2)
tk.Button(seuil_frame, text="RESET SEUIL", command=self.reset_threshold,
bg="blue", fg="white", font=("Arial", 8), width=20).pack(pady=2)
tk.Label(seuil_frame, text="(Sauvegarde automatique)",
font=("Arial", 8, "italic"), fg="gray").pack()
# Section templates avec liste directe
template_frame = tk.LabelFrame(self.control_window, text="GESTION TEMPLATES", font=("Arial", 10, "bold"))
template_frame.pack(fill="both", expand=True, padx=5, pady=5)
# Bouton ajouter en haut
tk.Button(template_frame, text="+ AJOUTER TEMPLATE",
command=self.add_template_during_detection,
bg="green", fg="white",
font=("Arial", 10, "bold"),
width=30).pack(pady=5)
# Zone scrollable pour la liste des templates
list_frame = tk.Frame(template_frame)
list_frame.pack(fill="both", expand=True, padx=5, pady=5)
canvas_scroll = tk.Canvas(list_frame)
scrollbar = tk.Scrollbar(list_frame, orient="vertical", command=canvas_scroll.yview)
self.templates_list_frame = tk.Frame(canvas_scroll)
self.templates_list_frame.bind(
"<Configure>",
lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all"))
)
canvas_scroll.create_window((0, 0), window=self.templates_list_frame, anchor="nw")
canvas_scroll.configure(yscrollcommand=scrollbar.set)
canvas_scroll.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Stocker les images pour tkinter
self.template_photos = []
# Afficher les templates
self.refresh_templates_display()
# Section mode debug
mode_frame = tk.LabelFrame(self.control_window, text="MODE DEBUG", font=("Arial", 10, "bold"))
mode_frame.pack(fill="x", padx=5, pady=5)
self.mode_label = tk.Label(mode_frame,
text="Mode Normal",
font=("Arial", 10, "bold"),
fg="blue")
self.mode_label.pack(pady=5)
tk.Button(mode_frame, text="DEBUG ON/OFF",
command=self.toggle_debug_mode,
bg="orange", fg="white",
font=("Arial", 9, "bold"),
width=20).pack(pady=5)
# Bouton retour menu
tk.Button(self.control_window, text="<< RETOUR MENU PRINCIPAL",
command=self.return_to_main_menu,
bg="purple", fg="white",
font=("Arial", 10, "bold"),
width=30).pack(pady=10)
self.control_window.bind("<KeyPress>", self.handle_keypress)
self.control_window.focus_set()
self.control_window.protocol("WM_DELETE_WINDOW", self.return_to_main_menu)
except Exception as e:
print(f"Erreur fenetre controle: {e}")
def handle_keypress(self, event):
key = event.keysym.lower()
if key in ['up', 'plus', 'equal']:
self.adjust_threshold_up()
elif key in ['down', 'minus']:
self.adjust_threshold_down()
elif key == 'r':
self.reset_threshold()
elif key == 'a':
self.add_template_during_detection()
elif key == 'd':
self.toggle_debug_mode()
elif key == 'escape':
self.return_to_main_menu()
def refresh_templates_display(self):
"""Rafraichit l'affichage de la liste des templates"""
if not hasattr(self, 'templates_list_frame'):
return
# Effacer
for widget in self.templates_list_frame.winfo_children():
widget.destroy()
self.template_photos.clear()
if not self.captured_templates:
tk.Label(self.templates_list_frame,
text="Aucun template\nUtilisez 'AJOUTER TEMPLATE'",
font=("Arial", 10),
fg="gray").pack(pady=20)
return
for i, tmpl in enumerate(self.captured_templates):
# Frame pour chaque template
template_frame = tk.Frame(self.templates_list_frame, relief=tk.RIDGE, borderwidth=2, bg="white")
template_frame.pack(fill="x", padx=2, pady=3)
# Image du template
try:
template_data = tmpl['template']
scale = 3
h, w = template_data.shape
template_resized = cv2.resize(template_data, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST)
if len(template_resized.shape) == 2:
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB)
else:
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(template_rgb)
photo = ImageTk.PhotoImage(pil_image)
self.template_photos.append(photo)
img_label = tk.Label(template_frame, image=photo, bg="white")
img_label.pack(side=tk.LEFT, padx=5, pady=5)
except:
tk.Label(template_frame, text="[Img]", bg="white").pack(side=tk.LEFT, padx=5)
# Infos
info_frame = tk.Frame(template_frame, bg="white")
info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=5)
tk.Label(info_frame, text=f"T{i}",
font=("Arial", 10, "bold"), bg="white").pack(anchor="w")
size = tmpl.get('size', (0, 0))
tk.Label(info_frame, text=f"{size[0]}x{size[1]}px",
font=("Arial", 8), bg="white").pack(anchor="w")
# Boutons
btn_frame = tk.Frame(template_frame, bg="white")
btn_frame.pack(side=tk.RIGHT, padx=5)
tk.Button(btn_frame, text="Recadrer",
command=lambda idx=i: self.recrop_from_control(idx),
bg="orange", fg="white", font=("Arial", 8), width=9).pack(pady=1)
tk.Button(btn_frame, text="Supprimer",
command=lambda idx=i: self.delete_template_from_control(idx),
bg="red", fg="white", font=("Arial", 8), width=9).pack(pady=1)
def recrop_from_control(self, idx):
"""Recadre depuis la fenetre de controle"""
self.recrop_template_inline(idx, self.refresh_templates_display)
def delete_template_from_control(self, idx):
"""Supprime depuis la fenetre de controle"""
if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"):
self.captured_templates.pop(idx)
self.refresh_templates_display()
if self.preset_manager.current_preset:
self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates)
def add_template_during_detection(self):
"""Ajoute un template pendant la detection"""
print("Mode ajout template active...")
self.create_template_capture_window()
def open_delete_menu(self):
"""Gestionnaire visuel complet des templates"""
if not self.captured_templates:
messagebox.showinfo("Aucun template", "Aucun template disponible")
return
manager_window = tk.Toplevel(self.control_window)
manager_window.title("Gestion des Templates")
manager_window.geometry("700x600")
manager_window.attributes("-topmost", True)
tk.Label(manager_window,
text="Gestion des Templates",
font=("Arial", 12, "bold")).pack(pady=10)
# Frame principal avec canvas scrollable
main_frame = tk.Frame(manager_window)
main_frame.pack(fill="both", expand=True, padx=10, pady=5)
canvas_scroll = tk.Canvas(main_frame)
scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=canvas_scroll.yview)
scrollable_frame = tk.Frame(canvas_scroll)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all"))
)
canvas_scroll.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas_scroll.configure(yscrollcommand=scrollbar.set)
canvas_scroll.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Images de reference pour tkinter
template_images = []
def refresh_templates():
# Effacer
for widget in scrollable_frame.winfo_children():
widget.destroy()
template_images.clear()
for i, tmpl in enumerate(self.captured_templates):
# Frame pour chaque template
template_frame = tk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=2, bg="white")
template_frame.pack(fill="x", padx=5, pady=5)
# Image du template (agrandie)
try:
template_data = tmpl['template']
# Agrandir x4 pour voir les details
scale = 4
h, w = template_data.shape
new_size = (w * scale, h * scale)
template_resized = cv2.resize(template_data, new_size, interpolation=cv2.INTER_NEAREST)
# Convertir pour tkinter
if len(template_resized.shape) == 2:
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB)
else:
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(template_rgb)
photo = ImageTk.PhotoImage(pil_image)
template_images.append(photo)
img_label = tk.Label(template_frame, image=photo, bg="white")
img_label.pack(side=tk.LEFT, padx=10, pady=10)
except Exception as e:
tk.Label(template_frame, text="Erreur image", bg="white").pack(side=tk.LEFT, padx=10)
# Infos
info_frame = tk.Frame(template_frame, bg="white")
info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=10)
tk.Label(info_frame, text=f"Template {i}",
font=("Arial", 12, "bold"), bg="white").pack(anchor="w")
size = tmpl.get('size', (0, 0))
tk.Label(info_frame, text=f"Taille: {size[0]}x{size[1]} pixels",
font=("Arial", 9), bg="white").pack(anchor="w")
threshold = tmpl.get('strict_threshold', self.current_threshold)
tk.Label(info_frame, text=f"Seuil: {threshold:.2f}",
font=("Arial", 9), bg="white").pack(anchor="w")
# Boutons
btn_frame = tk.Frame(template_frame, bg="white")
btn_frame.pack(side=tk.RIGHT, padx=10)
tk.Button(btn_frame, text="Recadrer",
command=lambda idx=i: recrop_and_refresh(idx),
bg="orange", fg="white", font=("Arial", 9), width=10).pack(pady=2)
tk.Button(btn_frame, text="Supprimer",
command=lambda idx=i: delete_and_refresh(idx),
bg="red", fg="white", font=("Arial", 9), width=10).pack(pady=2)
def recrop_and_refresh(idx):
"""Recadre et rafraichit l'affichage"""
self.recrop_template_inline(idx, refresh_templates)
def delete_and_refresh(idx):
if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"):
self.captured_templates.pop(idx)
refresh_templates()
self.update_control_interface()
if self.preset_manager.current_preset:
self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates)
refresh_templates()
tk.Button(manager_window, text="Fermer",
command=manager_window.destroy,
font=("Arial", 10), width=20).pack(pady=10)
def recrop_template_inline(self, template_idx, callback_refresh):
"""Recadre le template et rafraichit automatiquement"""
if template_idx >= len(self.captured_templates):
return
try:
current_template = self.captured_templates[template_idx]
template_image = current_template['template']
# Fenetre de recadrage
crop_window = tk.Toplevel()
crop_window.title(f"Recadrage Template {template_idx}")
crop_window.geometry("700x600")
crop_window.attributes("-topmost", True)
crop_window.grab_set() # Modale
tk.Label(crop_window,
text=f"Recadrage Template {template_idx}",
font=("Arial", 14, "bold")).pack(pady=10)
tk.Label(crop_window,
text="Cliquez et glissez pour tracer le rectangle de selection",
font=("Arial", 10), fg="blue").pack()
# Agrandir l'image
scale = 8
h, w = template_image.shape
template_resized = cv2.resize(template_image, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST)
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB)
# Canvas
canvas_frame = tk.Frame(crop_window, relief=tk.SUNKEN, borderwidth=2)
canvas_frame.pack(pady=10, padx=20)
canvas = tk.Canvas(canvas_frame, width=w*scale, height=h*scale, bg="white", cursor="cross")
canvas.pack(padx=5, pady=5)
# Afficher l'image
pil_img = Image.fromarray(template_rgb)
photo = ImageTk.PhotoImage(pil_img)
canvas.create_image(0, 0, anchor=tk.NW, image=photo, tags="image")
canvas.image = photo
# Variables pour le rectangle
start_x = None
start_y = None
rect_id = None
selection = [None, None, None, None]
def on_mouse_down(event):
nonlocal start_x, start_y, rect_id
start_x = event.x
start_y = event.y
if rect_id:
canvas.delete(rect_id)
def on_mouse_drag(event):
nonlocal rect_id
if start_x is not None and start_y is not None:
if rect_id:
canvas.delete(rect_id)
x = max(0, min(event.x, w*scale))
y = max(0, min(event.y, h*scale))
rect_id = canvas.create_rectangle(
start_x, start_y, x, y,
outline="red", width=2, tags="selection"
)
width_px = abs(x - start_x) // scale
height_px = abs(y - start_y) // scale
size_label.config(text=f"Selection: {width_px}x{height_px} pixels")
def on_mouse_up(event):
nonlocal start_x, start_y
if start_x is not None and start_y is not None:
x = max(0, min(event.x, w*scale))
y = max(0, min(event.y, h*scale))
x1 = min(start_x, x) // scale
y1 = min(start_y, y) // scale
x2 = max(start_x, x) // scale
y2 = max(start_y, y) // scale
selection[0] = x1
selection[1] = y1
selection[2] = x2
selection[3] = y2
width_px = x2 - x1
height_px = y2 - y1
size_label.config(text=f"Selection: {width_px}x{height_px} pixels")
canvas.bind("<ButtonPress-1>", on_mouse_down)
canvas.bind("<B1-Motion>", on_mouse_drag)
canvas.bind("<ButtonRelease-1>", on_mouse_up)
# Info
info_frame = tk.Frame(crop_window)
info_frame.pack(pady=5)
size_label = tk.Label(info_frame,
text="Tracez un rectangle sur l'image",
font=("Arial", 10))
size_label.pack()
# Boutons
btn_frame = tk.Frame(crop_window)
btn_frame.pack(pady=15)
def apply_crop():
if selection[0] is None:
messagebox.showwarning("Aucune selection", "Tracez d'abord un rectangle")
return
x1, y1, x2, y2 = selection
if x2 - x1 < 8 or y2 - y1 < 8:
messagebox.showerror("Selection trop petite", "Minimum 8x8 pixels")
return
# Extraire le nouveau template
cropped = template_image[y1:y2, x1:x2]
# Mettre a jour
self.captured_templates[template_idx] = {
'template': cropped,
'size': (x2-x1, y2-y1),
'position': current_template.get('position', (0, 0)),
'strict_threshold': self.current_threshold
}
# Sauvegarder
if self.preset_manager.current_preset:
self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates)
# Fermer et rafraichir
crop_window.destroy()
callback_refresh()
def reset_selection():
nonlocal rect_id, start_x, start_y
if rect_id:
canvas.delete(rect_id)
rect_id = None
start_x = None
start_y = None
selection[0] = None
selection[1] = None
selection[2] = None
selection[3] = None
size_label.config(text="Tracez un rectangle sur l'image")
tk.Button(btn_frame, text="Appliquer",
command=apply_crop,
bg="green", fg="white",
font=("Arial", 10, "bold"),
width=12).grid(row=0, column=0, padx=5)
tk.Button(btn_frame, text="Reinitialiser",
command=reset_selection,
bg="orange", fg="white",
font=("Arial", 10),
width=12).grid(row=0, column=1, padx=5)
tk.Button(btn_frame, text="Annuler",
command=crop_window.destroy,
bg="gray", fg="white",
font=("Arial", 10),
width=12).grid(row=0, column=2, padx=5)
except Exception as e:
print(f"Erreur recadrage: {e}")
"""Ouvre un menu pour visualiser, supprimer ou editer des templates"""
if not self.captured_templates:
messagebox.showinfo("Aucun template", "Aucun template a supprimer")
return
manager_window = tk.Toplevel(self.control_window)
manager_window.title("Gestionnaire de Templates")
manager_window.geometry("600x500")
manager_window.attributes("-topmost", True)
tk.Label(manager_window,
text="Gestion des Templates",
font=("Arial", 12, "bold")).pack(pady=10)
# Frame principal avec canvas scrollable
main_frame = tk.Frame(manager_window)
main_frame.pack(fill="both", expand=True, padx=10, pady=5)
canvas_scroll = tk.Canvas(main_frame)
scrollbar = tk.Scrollbar(main_frame, orient="vertical", command=canvas_scroll.yview)
scrollable_frame = tk.Frame(canvas_scroll)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all"))
)
canvas_scroll.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas_scroll.configure(yscrollcommand=scrollbar.set)
canvas_scroll.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Images de reference pour tkinter
template_images = []
def refresh_templates():
# Effacer
for widget in scrollable_frame.winfo_children():
widget.destroy()
template_images.clear()
for i, tmpl in enumerate(self.captured_templates):
# Frame pour chaque template
template_frame = tk.Frame(scrollable_frame, relief=tk.RIDGE, borderwidth=2, bg="white")
template_frame.pack(fill="x", padx=5, pady=5)
# Image du template (agrandie)
try:
template_data = tmpl['template']
# Agrandir x4 pour voir les details
scale = 4
h, w = template_data.shape
new_size = (w * scale, h * scale)
template_resized = cv2.resize(template_data, new_size, interpolation=cv2.INTER_NEAREST)
# Convertir pour tkinter
if len(template_resized.shape) == 2:
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB)
else:
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(template_rgb)
photo = ImageTk.PhotoImage(pil_image)
template_images.append(photo)
img_label = tk.Label(template_frame, image=photo, bg="white")
img_label.pack(side=tk.LEFT, padx=10, pady=10)
except Exception as e:
tk.Label(template_frame, text="Erreur image", bg="white").pack(side=tk.LEFT, padx=10)
# Infos
info_frame = tk.Frame(template_frame, bg="white")
info_frame.pack(side=tk.LEFT, fill="both", expand=True, padx=10)
tk.Label(info_frame, text=f"Template {i}",
font=("Arial", 12, "bold"), bg="white").pack(anchor="w")
size = tmpl.get('size', (0, 0))
tk.Label(info_frame, text=f"Taille: {size[0]}x{size[1]} pixels",
font=("Arial", 9), bg="white").pack(anchor="w")
threshold = tmpl.get('strict_threshold', self.current_threshold)
tk.Label(info_frame, text=f"Seuil: {threshold:.2f}",
font=("Arial", 9), bg="white").pack(anchor="w")
# Boutons
btn_frame = tk.Frame(template_frame, bg="white")
btn_frame.pack(side=tk.RIGHT, padx=10)
tk.Button(btn_frame, text="Recadrer",
command=lambda idx=i: recrop_and_refresh(idx),
bg="orange", fg="white", font=("Arial", 9), width=10).pack(pady=2)
tk.Button(btn_frame, text="Supprimer",
command=lambda idx=i: delete_and_refresh(idx),
bg="red", fg="white", font=("Arial", 9), width=10).pack(pady=2)
def recrop_and_refresh(idx):
"""Recadre et rafraichit l'affichage"""
self.recrop_template_inline(idx, refresh_templates)
def delete_and_refresh(idx):
if messagebox.askyesno("Confirmer", f"Supprimer le template {idx} ?"):
self.captured_templates.pop(idx)
refresh_templates()
self.update_control_interface()
if self.preset_manager.current_preset:
self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates)
refresh_templates()
tk.Button(manager_window, text="Fermer",
command=manager_window.destroy,
font=("Arial", 10), width=20).pack(pady=10)
def recrop_template(self, template_idx, parent_window):
"""Permet de recadrer le template en tracant un rectangle"""
if template_idx >= len(self.captured_templates):
return
parent_window.withdraw()
try:
current_template = self.captured_templates[template_idx]
template_image = current_template['template']
# Fenetre de recadrage
crop_window = tk.Toplevel()
crop_window.title(f"Recadrage Template {template_idx}")
crop_window.geometry("700x600")
crop_window.attributes("-topmost", True)
tk.Label(crop_window,
text=f"Recadrage Template {template_idx}",
font=("Arial", 14, "bold")).pack(pady=10)
tk.Label(crop_window,
text="Cliquez et glissez pour tracer le rectangle de selection",
font=("Arial", 10), fg="blue").pack()
# Agrandir l'image pour faciliter le recadrage
scale = 8
h, w = template_image.shape
template_resized = cv2.resize(template_image, (w*scale, h*scale), interpolation=cv2.INTER_NEAREST)
template_rgb = cv2.cvtColor(template_resized, cv2.COLOR_GRAY2RGB)
# Frame avec canvas
canvas_frame = tk.Frame(crop_window, relief=tk.SUNKEN, borderwidth=2)
canvas_frame.pack(pady=10, padx=20)
canvas = tk.Canvas(canvas_frame, width=w*scale, height=h*scale, bg="white", cursor="cross")
canvas.pack(padx=5, pady=5)
# Afficher l'image
pil_img = Image.fromarray(template_rgb)
photo = ImageTk.PhotoImage(pil_img)
canvas.create_image(0, 0, anchor=tk.NW, image=photo, tags="image")
canvas.image = photo
# Variables pour le rectangle de selection
start_x = None
start_y = None
rect_id = None
selection = [None, None, None, None] # x1, y1, x2, y2
def on_mouse_down(event):
nonlocal start_x, start_y, rect_id
start_x = event.x
start_y = event.y
if rect_id:
canvas.delete(rect_id)
def on_mouse_drag(event):
nonlocal rect_id
if start_x is not None and start_y is not None:
if rect_id:
canvas.delete(rect_id)
# Limiter aux bounds de l'image
x = max(0, min(event.x, w*scale))
y = max(0, min(event.y, h*scale))
rect_id = canvas.create_rectangle(
start_x, start_y, x, y,
outline="red", width=2, tags="selection"
)
# Afficher les dimensions
width_px = abs(x - start_x) // scale
height_px = abs(y - start_y) // scale
size_label.config(text=f"Selection: {width_px}x{height_px} pixels")
def on_mouse_up(event):
nonlocal start_x, start_y
if start_x is not None and start_y is not None:
# Limiter aux bounds
x = max(0, min(event.x, w*scale))
y = max(0, min(event.y, h*scale))
# Enregistrer la selection (en pixels de l'image originale)
x1 = min(start_x, x) // scale
y1 = min(start_y, y) // scale
x2 = max(start_x, x) // scale
y2 = max(start_y, y) // scale
selection[0] = x1
selection[1] = y1
selection[2] = x2
selection[3] = y2
width_px = x2 - x1
height_px = y2 - y1
size_label.config(text=f"Selection: {width_px}x{height_px} pixels")
canvas.bind("<ButtonPress-1>", on_mouse_down)
canvas.bind("<B1-Motion>", on_mouse_drag)
canvas.bind("<ButtonRelease-1>", on_mouse_up)
# Info
info_frame = tk.Frame(crop_window)
info_frame.pack(pady=5)
size_label = tk.Label(info_frame,
text="Tracez un rectangle sur l'image",
font=("Arial", 10))
size_label.pack()
# Boutons
btn_frame = tk.Frame(crop_window)
btn_frame.pack(pady=15)
def apply_crop():
if selection[0] is None:
messagebox.showwarning("Aucune selection", "Tracez d'abord un rectangle")
return
x1, y1, x2, y2 = selection
# Verifier que la selection est valide
if x2 - x1 < 8 or y2 - y1 < 8:
messagebox.showerror("Selection trop petite", "La selection doit faire au moins 8x8 pixels")
return
# Extraire le nouveau template
cropped = template_image[y1:y2, x1:x2]
# Mettre a jour le template
self.captured_templates[template_idx] = {
'template': cropped,
'size': (x2-x1, y2-y1),
'position': current_template.get('position', (0, 0)),
'strict_threshold': self.current_threshold
}
# Sauvegarder
if self.preset_manager.current_preset:
self.preset_manager.add_preset(self.preset_manager.current_preset, self.captured_templates)
messagebox.showinfo("Succes", f"Template recadre: {x2-x1}x{y2-y1} pixels")
crop_window.destroy()
parent_window.deiconify()
def reset_selection():
nonlocal rect_id, start_x, start_y
if rect_id:
canvas.delete(rect_id)
rect_id = None
start_x = None
start_y = None
selection[0] = None
selection[1] = None
selection[2] = None
selection[3] = None
size_label.config(text="Tracez un rectangle sur l'image")
def cancel():
crop_window.destroy()
parent_window.deiconify()
tk.Button(btn_frame, text="Appliquer",
command=apply_crop,
bg="green", fg="white",
font=("Arial", 10, "bold"),
width=12).grid(row=0, column=0, padx=5)
tk.Button(btn_frame, text="Reinitialiser",
command=reset_selection,
bg="orange", fg="white",
font=("Arial", 10),
width=12).grid(row=0, column=1, padx=5)
tk.Button(btn_frame, text="Annuler",
command=cancel,
bg="gray", fg="white",
font=("Arial", 10),
width=12).grid(row=0, column=2, padx=5)
except Exception as e:
print(f"Erreur recadrage: {e}")
parent_window.deiconify()
def return_to_main_menu(self):
"""Retourne au menu principal"""
# Demander confirmation
if messagebox.askyesno("Retour menu", "Arreter la detection et retourner au menu principal ?"):
# Arreter la detection
self.running = False
# Fermer l'overlay et le controle
if self.overlay_root:
try:
self.overlay_root.destroy()
except:
pass
if self.control_window:
try:
self.control_window.destroy()
except:
pass
# Recreer le menu principal
self.mode = "setup"
self.create_main_menu()
def update_threshold_display(self):
if hasattr(self, 'control_window') and self.control_window and hasattr(self, 'threshold_label'):
try:
if self.control_window.winfo_exists():
self.threshold_label.config(text=f"Seuil: {self.current_threshold:.2f}")
self.control_window.update_idletasks()
except:
pass
self.update_control_interface()
def update_control_interface(self):
if not hasattr(self, 'control_window') or not self.control_window:
return
try:
if hasattr(self, 'mode_label'):
mode_text = {
"normal": "Mode Normal",
"debug": "Mode Debug (IDs visibles)"
}
mode_color = {
"normal": "blue",
"debug": "orange"
}
self.mode_label.config(
text=mode_text.get(self.template_mode, "Mode inconnu"),
fg=mode_color.get(self.template_mode, "black")
)
# Rafraichir la liste des templates
self.refresh_templates_display()
self.control_window.update_idletasks()
except:
pass
def detection_loop(self):
frame_count = 0
last_detection_time = time.time()
print("Boucle detection demarree")
while self.running and self.mode == "detection":
try:
if not self.target_window or not self.running:
break
if self.overlay_root and not self.overlay_root.winfo_exists():
self.running = False
break
frame_count += 1
current_time = time.time()
if self.frame_skip > 0:
self.frame_skip -= 1
time.sleep(0.016)
continue
image = self.capture_window_fast(self.target_window)
if image is None:
time.sleep(0.02)
continue
start_detection = time.time()
positions = self.find_matches_with_templates(image)
detection_time = time.time() - start_detection
if detection_time > 0.05:
self.frame_skip = self.max_frame_skip
elif detection_time > 0.03:
self.frame_skip = 1
if self.overlay_created and self.overlay_root and self.running:
try:
self.safe_update_overlay(positions)
except:
self.running = False
break
if current_time - last_detection_time > 2.0:
fps = frame_count / (current_time - last_detection_time) if frame_count > 0 else 0
if self.running:
print(f"{len(positions)} bases | FPS: {fps:.1f} | {detection_time*1000:.1f}ms | Seuil: {self.current_threshold:.2f}")
last_detection_time = current_time
frame_count = 0
time.sleep(0.01)
except:
self.running = False
break
print("Boucle detection terminee")
def start_overlay_detection(self):
if not self.target_window:
return
try:
self.create_control_window()
self.create_overlay(self.target_window)
detection_thread = threading.Thread(target=self.detection_loop, daemon=True)
detection_thread.start()
print("Detection active")
if self.control_window:
try:
self.control_window.mainloop()
except:
self.stop()
finally:
self.running = False
except Exception as e:
print(f"Erreur demarrage: {e}")
self.stop()
def start(self):
print("LastWar - Gestionnaire de Templates")
self.load_settings()
self.target_window = self.find_game_window()
if not self.target_window:
print("Fenetre de jeu non trouvee")
return
self.running = True
self.create_main_menu()
def stop(self):
self.running = False
if hasattr(self, 'control_window') and self.control_window:
try:
self.control_window.quit()
self.control_window.destroy()
except:
pass
if hasattr(self, 'overlay_root') and self.overlay_root:
try:
self.overlay_root.quit()
self.overlay_root.destroy()
except:
pass
if hasattr(self, 'executor'):
try:
self.executor.shutdown(wait=False)
except:
pass
print("Arrete proprement")
import sys
sys.exit(0)
def quit_application(self):
self.running = False
if self.main_menu:
try:
self.main_menu.quit()
self.main_menu.destroy()
except:
pass
import sys
sys.exit(0)
def main():
overlay = GameOverlay()
import signal
def signal_handler(sig, frame):
print("\nArret demande (Ctrl+C)...")
overlay.stop()
signal.signal(signal.SIGINT, signal_handler)
try:
overlay.start()
except KeyboardInterrupt:
print("\nArret demande...")
overlay.stop()
except Exception as e:
print(f"Erreur: {e}")
overlay.stop()
if __name__ == "__main__":
main()